package parser;
import static trees.MatchFinder.find;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import grammar.Expression;
import grammar.Expression.Capture;
import grammar.Expression.Rule;
import source.Source;
import trees.MatchFinder.Finder;
import trees.MatchSpec;
/**
* The parser constructs a match tree. Each node in the tree represents a match
* between a parsing expression and a chunk of input.
*
* The match tree is what is usually called the parse tree. It is also sometimes
* abusively called AST (Abstract Syntax Tree). This is no AST, because the tree
* is not "abstract", it is closely tied to the way the syntax of the language
* was specified in the grammar.
*
* When using the finders, remember that Not-expressions are atomic (the
* sub-matches of their matches don't show up in the match tree), while
* And-expressions are not.
*/
public class Match
{
/*****************************************************************************
* Empty Match array to be used as type witness or to signal the lack of
* result in a find operation.
*/
public static final Match[] EMPTY = new Match[0];
/*****************************************************************************
* Converts an array of objects to strings, using the {@link #string()} method
* for any instance of {@link Match}, {@link Object#toString()} otherwise.
*/
public static String[] strings(Object[] objects)
{
String[] out = new String[objects.length];
for (int i = 0 ; i < objects.length ; ++i)
{
out[i] = objects[i] instanceof Match
? ((Match)objects[i]).string()
: objects[i].toString();
}
return out;
}
/*****************************************************************************
* Matched expression.
*/
public final Expression expr;
/*****************************************************************************
* Input position of the begin of the match.
*/
public final int begin;
/*****************************************************************************
* Input position of the end of the match.
*/
public final int end;
/*****************************************************************************
* Matches of the sub-expressions of expr.
*/
private final List<Match> children;
/*****************************************************************************
* The source whose input is matched.
*/
public final Source source;
/****************************************************************************/
public Match(Expression expr, Source src, int begin, int end)
{
this(expr, src, begin, end, Collections.<Match>emptyList());
}
/****************************************************************************/
public Match(Expression expr, Source source, int begin,int end,
List<Match> children)
{
this.expr = expr;
this.source = source;
this.begin = begin;
this.end = end;
this.children = Collections.unmodifiableList(children);
}
/****************************************************************************/
@Override public String toString()
{
return "match for [" + expr + "] " + source.where(begin)
+ " (len: " + length() + ")";
}
/****************************************************************************/
public List<Match> children()
{
return children;
}
/****************************************************************************/
public Match child()
{
return children.get(0);
}
/*****************************************************************************
* Returns true if the match has no children. This happens if the match is
* atomic or if the match is an optional expression (?, *) matching nothing.
*/
public boolean empty()
{
return children.isEmpty();
}
/****************************************************************************/
public int length()
{
return end - begin;
}
/*****************************************************************************
* Returns a string describing the location of the match (the source and
* the position in the source).
*/
public String where()
{
return "in [" + source + "] at [" + source.where(begin) + "]";
}
/****************************************************************************/
public Expression expression()
{
return expr;
}
/*****************************************************************************
* Return the matched string, trimmed of leading and trailing whitespace, of
* all comments, and with whitespace sequences compressed to a single space.
*/
public String string()
{
return source.at(begin, end).trim();
}
/*****************************************************************************
* Return the matched string, but do not trim or compress whitespace.
*/
public String originalString()
{
return source.at(begin, end);
}
/*****************************************************************************
* Indicates if this match is matched by the supplied specification.
*/
public boolean is(MatchSpec spec)
{
return spec.matches(this);
}
/*****************************************************************************
* Indicates if the match has a descendant matching the supplied
* specification.
*/
public boolean has(MatchSpec spec)
{
return first(spec) != null;
}
/*****************************************************************************
* Indicates if a previous call to a finder method yielded a result (the
* supplied argument should be the return value from such a call).
*/
public boolean has(Match[] matchs)
{
return matchs.length > 0;
}
/*****************************************************************************
* Indicates if a previous call to a finder method yielded a result (the
* supplied argument should be the return value from such a call).
*/
public boolean has(Match match)
{
return match != null;
}
/*****************************************************************************
* Get all the captures with the given name in this match tree. The recursion
* is cut off when a matching capture is encountered, or when a sub-rule is
* encountered.
*
* If the match this method is called on is itself a capture with the given
* name, it won't be returned.
*/
public Match[] getCaptures(String captureName)
{
List<Match> out = new ArrayList<>();
/* Do the first recursion step here, allows this method to be called
* on rules. */
for (Match m : children) {
m.getCaptures(captureName, out);
}
return out.toArray(new Match[out.size()]);
}
/****************************************************************************/
private void getCaptures(String captureName, List<Match> captures)
{
if (expr instanceof Capture
&& ((Expression.Capture)expr).captureName.equals(captureName))
{
captures.add(child());
}
else if (!(expr instanceof Rule))
{
for (Match m : children) {
m.getCaptures(captureName, captures);
}
}
}
//============================================================================
// FINDERS
//============================================================================
/*****************************************************************************
* Used as default parameter to some methods. MSA for MatchSpec Array.
*/
private static final MatchSpec[] EMPTY_MSA = new MatchSpec[0];
/****************************************************************************/
private static Match single(Match[] result)
{
return result.length == 1 ? result[0] : null;
}
/****************************************************************************/
public Match first(MatchSpec spec)
{
return single(find(this, spec, Finder.FIRST, EMPTY_MSA, EMPTY_MSA, true));
}
/****************************************************************************/
public Match firstAfterFirst(MatchSpec spec, MatchSpec... before)
{
return single(find(this, spec, Finder.FIRST, before, EMPTY_MSA, true));
}
/****************************************************************************/
public Match firstAfterLast(MatchSpec spec, MatchSpec... before)
{
return single(find(this, spec, Finder.FIRST, EMPTY_MSA, before, false));
}
/****************************************************************************/
public Match firstBeforeLast(MatchSpec spec, MatchSpec... after)
{
return single(find(this, spec, Finder.FIRST, EMPTY_MSA, after, true));
}
/****************************************************************************/
public Match firstBeforeFirst(MatchSpec spec, MatchSpec... after)
{
return single(find(this, spec, Finder.FIRST, after, EMPTY_MSA, false));
}
/****************************************************************************/
public Match firstBetween(
MatchSpec spec, MatchSpec[] before, MatchSpec[] after)
{
return single(find(this, spec, Finder.FIRST, before, after, true));
}
/****************************************************************************/
public Match firstOutside(
MatchSpec spec, MatchSpec[] before, MatchSpec[] after)
{
return single(find(this, spec, Finder.FIRST, before, after, false));
}
/****************************************************************************/
public Match last(MatchSpec spec)
{
return single(find(this, spec, Finder.LAST, EMPTY_MSA, EMPTY_MSA, true));
}
/****************************************************************************/
public Match lastAfterFirst(MatchSpec spec, MatchSpec... before)
{
return single(find(this, spec, Finder.LAST, before, EMPTY_MSA, true));
}
/****************************************************************************/
public Match lastAfterLast(MatchSpec spec, MatchSpec... before)
{
return single(find(this, spec, Finder.LAST, EMPTY_MSA, before, false));
}
/****************************************************************************/
public Match lastBeforeLast(MatchSpec spec, MatchSpec... after)
{
return single(find(this, spec, Finder.LAST, EMPTY_MSA, after, true));
}
/****************************************************************************/
public Match lastBeforeFirst(MatchSpec spec, MatchSpec... after)
{
return single(find(this, spec, Finder.LAST, after, EMPTY_MSA, false));
}
/****************************************************************************/
public Match lastBetween(
MatchSpec spec, MatchSpec[] before, MatchSpec[] after)
{
return single(find(this, spec, Finder.LAST, before, after, true));
}
/****************************************************************************/
public Match lastOutside(
MatchSpec spec, MatchSpec[] before, MatchSpec[] after)
{
return single(find(this, spec, Finder.LAST, before, after, false));
}
/****************************************************************************/
public Match[] all(MatchSpec spec)
{
return find(this, spec, Finder.ALL, EMPTY_MSA, EMPTY_MSA, true);
}
/****************************************************************************/
public Match[] allAfterFirst(MatchSpec spec, MatchSpec... before)
{
return find(this, spec, Finder.ALL, before, EMPTY_MSA, true);
}
/****************************************************************************/
public Match[] allAfterLast(MatchSpec spec, MatchSpec... before)
{
return find(this, spec, Finder.ALL, EMPTY_MSA, before, true);
}
/****************************************************************************/
public Match[] allBeforeLast(MatchSpec spec, MatchSpec... after)
{
return find(this, spec, Finder.ALL, EMPTY_MSA, after, true);
}
/****************************************************************************/
public Match[] allBeforeFirst(MatchSpec spec, MatchSpec... after)
{
return find(this, spec, Finder.ALL, after, EMPTY_MSA, false);
}
/****************************************************************************/
public Match[] allBetween(
MatchSpec spec, MatchSpec[] before, MatchSpec[] after)
{
return find(this, spec, Finder.ALL, before, after, true);
}
/****************************************************************************/
public Match[] allOutside(
MatchSpec spec, MatchSpec[] before, MatchSpec[] after)
{
return find(this, spec, Finder.ALL, before, after, false);
}
}