package trees;
import java.util.Arrays;
import driver.Hints;
import grammar.Expression;
import grammar.Expression.Rule;
import parser.Match;
import source.Source;
import static util.StringUtils.escape;
import static util.StringUtils.join;
/**
* A specification that can match Match objects (yes, this is confusing).
*/
public abstract class MatchSpec
{
/****************************************************************************/
public abstract boolean matches(Match match);
/*****************************************************************************
* Matches Match objects whose source is a given string.
*/
public static class StringSpec extends MatchSpec
{
private final String str;
public StringSpec(String str)
{
this.str = str;
}
@Override public boolean matches(Match match)
{
return match.string().equals(str);
}
@Override public String toString()
{
return "match string \"" + escape(str) + "\"";
}
}
/****************************************************************************/
public static StringSpec str(String str)
{
return new StringSpec(str);
}
/*****************************************************************************
* Matches Match objects matching a given grammar rule.
*/
public static class RuleSpec extends MatchSpec
{
private final String rule;
public RuleSpec(String rule)
{
this.rule = rule;
}
public RuleSpec(Rule rule)
{
this.rule = rule.name;
}
@Override public boolean matches(Match match)
{
return match.expr instanceof Rule
&& ((Rule)match.expr).name.equals(rule);
}
@Override public String toString()
{
return "match rule \"" + rule + "\"";
}
}
/****************************************************************************/
public static RuleSpec rule(String rule)
{
return new RuleSpec(rule);
}
/****************************************************************************/
public static RuleSpec rule(Rule rule)
{
return new RuleSpec(rule);
}
/*****************************************************************************
* Matches Match objects that are capture with the given name. You usually
* want to use Match#get() instead of this.
*/
public static class CaptureSpec extends MatchSpec
{
public final String captureName;
CaptureSpec(String captureName)
{
this.captureName = captureName;
}
@Override public boolean matches(Match match)
{
return match.expr instanceof Expression.Capture
&& ((Expression.Capture)match.expr).captureName.equals(captureName);
}
@Override public String toString()
{
return "match capture \"" + captureName + "\"";
}
}
/****************************************************************************/
public static CaptureSpec capture(String rule)
{
return new CaptureSpec(rule);
}
/*****************************************************************************
* Matches Match objects matching a given expression. The expression can't be
* a rule, because rules don't have a canonical representation (see
* ExpressionTreeCleaner for specifics). Use RuleSpec to match rules.
*/
public static class ExprSpec extends MatchSpec
{
private final Expression expr;
public ExprSpec(Expression expr)
{
this.expr = expr;
}
@Override public boolean matches(Match match)
{
/* Calling the cleaner now enables the spec to be used with multiple
* grammars, although it isn't the case now. It also avoid having to pass
* the grammar to the spec constructor. */
return match.expr == match.expr.grammar.clean(expr);
}
@Override public String toString()
{
return "match expression [" + expr + "]";
}
}
/****************************************************************************/
public static ExprSpec expr(Expression expr)
{
return new ExprSpec(expr);
}
/*****************************************************************************
* Matches Match objects matched by one of its child specifications.
*/
public static class OrSpec extends MatchSpec
{
private final MatchSpec[] specs;
public OrSpec(MatchSpec... specs)
{
this.specs = specs;
}
@Override public boolean matches(Match match)
{
for (MatchSpec spec : specs) {
if (spec.matches(match)) { return true; }
}
return false;
}
@Override public String toString()
{
return join(Arrays.asList(specs), " or ");
}
}
/****************************************************************************/
public static OrSpec or(MatchSpec... specs)
{
return new OrSpec(specs);
}
/*****************************************************************************
* Matches Match objects matched by all of its child specifications.
*/
public static class AndSpec extends MatchSpec
{
private final MatchSpec[] specs;
public AndSpec(MatchSpec... specs)
{
this.specs = specs;
}
@Override public boolean matches(Match match)
{
for (MatchSpec spec : specs) {
if (!spec.matches(match)) { return false; }
}
return true;
}
@Override public String toString()
{
return join(Arrays.asList(specs), " and ");
}
}
/****************************************************************************/
public static AndSpec and(MatchSpec... specs)
{
return new AndSpec(specs);
}
/*****************************************************************************
* Matches Match objects not matched by its child specification.
*/
public static class NotSpec extends MatchSpec
{
private final MatchSpec spec;
public NotSpec(MatchSpec spec)
{
this.spec = spec;
}
@Override public boolean matches(Match match)
{
return !spec.matches(match);
}
@Override public String toString()
{
return "not " + spec.toString();
}
}
/****************************************************************************/
public static NotSpec not(MatchSpec spec)
{
return new NotSpec(spec);
}
/*****************************************************************************
* Matches Match objects that have been matched at the given position.
*/
public static class PositionSpec extends MatchSpec
{
private final int position;
public PositionSpec(int position)
{
this.position = position;
}
@Override public boolean matches(Match match)
{
return match.begin == position;
}
@Override public String toString()
{
Source source = Hints.get().source();
return source == null
? "at position " + position
: source.where(position);
}
}
/****************************************************************************/
public static PositionSpec at(int position)
{
return new PositionSpec(position);
}
/*****************************************************************************
* Matches Match objects that have a sub-match matched by the child
* specification.
*/
public static class HasSpec extends MatchSpec
{
private final MatchSpec spec;
public HasSpec(MatchSpec spec)
{
this.spec = spec;
}
@Override public boolean matches(Match match)
{
return match.has(spec);
}
@Override public String toString()
{
return "has submatch matching (" + spec.toString() + ")";
}
}
/****************************************************************************/
public static HasSpec has(MatchSpec spec)
{
return new HasSpec(spec);
}
/*****************************************************************************
* Matches all expressions.
*/
public static class AnySpec extends MatchSpec
{
@Override public boolean matches(Match match)
{
return true;
}
@Override public String toString()
{
return "match anything";
}
}
/****************************************************************************/
public static final AnySpec anySpec = new AnySpec();
/*****************************************************************************
* Returns a MatchSpec matching Match object having a sub-match with given
* expression at the given position.
*/
public static MatchSpec hasExprAtPos(Expression expr, int position)
{
return has(and(at(position), expr(expr)));
}
/*****************************************************************************
* Returns a MatchSpec matching Match object having a sub-match similar to
* the one supplied at the given position.
*/
public static MatchSpec hasMatchAtPos(Match match, int position)
{
return has(and(at(position), expr(match.expr), str(match.string())));
}
}