package parser; import source.Source; import source.SourceStream; import grammar.Expression; import grammar.ExpressionVisitor; /** * This class contains the core parsing logic. It parses input from the * associated source stream. We use memoization to improve performance. * * The aim is to build a match tree that indicates how the input matches a given * expression and its sub-expressions. * * TODO explain call stack path */ public class Matcher implements ExpressionVisitor { /****************************************************************************/ private final Source source; /****************************************************************************/ protected final SourceStream stream; /****************************************************************************/ protected final Memo memo; /****************************************************************************/ public Matcher(Source source) { this.source = source; this.stream = new SourceStream(source); this.memo = new LimitedMemo(this); } /***************************************************************************** * true when parsing an atomic expression or one if its sub-expressions. */ private boolean atomic = false; /***************************************************************************** * The parse for the expression currently being parsed. */ private ParseData data = null; /****************************************************************************/ public Source source() { return source; } /****************************************************************************/ public Memo memo() { return memo; } /***************************************************************************** * Returns the result of the last call to {@link Matcher#matches(Expression)}. */ public boolean succeeded() { return data.succeeded; } /***************************************************************************** * The match tree resulting of a successful parse. Call only if the last call * to {@link Matcher#matches(Expression)} returned true. */ public Match match() { return data.match; } /***************************************************************************** * The errors resulting from an unsuccessful parse. Call only if the last call * to {@link Matcher#matches(Expression)} returned false. */ public ParseErrors errors() { return data.errors; } /***************************************************************************** * Parse the source stream using expr as root expression. Returns true if the * parse succeeded. If it succeeded, the input position is moved past the * matched input. */ public boolean matches(Expression expr) { data = new ParseData(expr, stream, atomic); expr.accept(this); return data.succeeded; } /***************************************************************************** * Resets the input position of the matcher. Does not empty the * memoization table. */ public void reset() { stream.reset(); atomic = false; data = null; } /***************************************************************************** * Dirty hack to allow parsing files where memoization would overflow memory. */ void accept(Expression expr) { boolean ok = false; while (!ok) try { expr.accept(this); ok = true; } catch (OutOfMemoryError e) { System.out.println("Warning: heap exhausted, memoization table emptied."); stream.position = data.begin; memo.clear(); System.gc(); } } /****************************************************************************/ ParseData parse(Expression expr) { boolean _atomic = atomic; ParseData _data = data; try { atomic = atomic || expr.atomic; data = new ParseData(expr, stream, expr.atomic); accept(expr); // expr.accept(this); // non-hack version return data; } finally { atomic = _atomic; data = _data; } } /****************************************************************************/ boolean visitChild(Expression child) { ParseData childData = memo.get(stream.position, child); stream.position = childData.end; // necessary because of memoization data.merge(childData); return childData.succeeded; } /****************************************************************************/ void succeed() { data.succeed(stream.position); } //============================================================================ // EXPRESSION VISITOR //============================================================================ /****************************************************************************/ public void visitChoice(Expression expr) { for (Expression e : expr.children()) { if (visitChild(e)) { succeed(); return; } } data.fail(); } /****************************************************************************/ @Override public void visit(Expression.Choice expr) { visitChoice(expr); } /****************************************************************************/ @Override public void visit(Expression.Rule expr) { visitChoice(expr); } /****************************************************************************/ @Override public void visit(Expression.Sequence expr) { for (Expression e : expr.children()) { if (!visitChild(e)) { data.fail(); return; } } succeed(); } /****************************************************************************/ @Override public void visit(Expression.And expr) { if (visitChild(expr.child())) { data.succeed(data.begin); } else { data.fail(); } } /****************************************************************************/ @Override public void visit(Expression.Not expr) { if (visitChild(expr.child())) { data.fail(); } else { succeed(); } } /****************************************************************************/ @Override public void visit(Expression.Plus expr) { if (!visitChild(expr.child())) { data.fail(); } else { while (visitChild(expr.child())) ; succeed(); } } /****************************************************************************/ @Override public void visit(Expression.Star expr) { while (visitChild(expr.child())) ; succeed(); } /****************************************************************************/ @Override public void visit(Expression.Optional expr) { visitChild(expr.child()); succeed(); } /****************************************************************************/ @Override public void visit(Expression.StringLiteral expr) { String srcString = stream.get(expr.string.length()); if (!stream.isPastEnd() && srcString.equals(expr.string)) { succeed(); } else { data.fail(); } } /****************************************************************************/ @Override public void visit(Expression.CharClass expr) { char c = stream.get(); int index = expr.chars.indexOf(c); if (!stream.isPastEnd() && (!expr.negated && index != -1 || expr.negated && index == -1)) { succeed(); } else { data.fail(); } } /****************************************************************************/ @Override public void visit(Expression.Range expr) { char c = stream.get(); if (!stream.isPastEnd() && (!expr.negated && (expr.first <= c && c <= expr.last) || expr.negated && (c < expr.first || expr.last < c)) ) { succeed(); } else { data.fail(); } } /****************************************************************************/ @Override public void visit(Expression.Any expr) { stream.get(); if (!stream.isPastEnd()) { succeed(); } else { data.fail(); } } /***************************************************************************** * Delegate to the unique child. */ @Override public void visit(Expression.Capture expr) { if (visitChild(expr.child())) { succeed(); } else { data.fail(); } } }