![JAR search and dependency download from the Maven repository](/logo.png)
org.parboiled.MatcherContext Maven / Gradle / Ivy
/*
* Copyright (C) 2009-2011 Mathias Doenitz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.parboiled;
import org.parboiled.buffers.InputBuffer;
import org.parboiled.common.ImmutableLinkedList;
import org.parboiled.common.StringUtils;
import org.parboiled.errors.BasicParseError;
import org.parboiled.errors.GrammarException;
import org.parboiled.errors.ParseError;
import org.parboiled.errors.ParserRuntimeException;
import org.parboiled.matchers.*;
import org.parboiled.parserunners.RecoveringParseRunner;
import org.parboiled.support.*;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import static org.parboiled.errors.ErrorUtils.printParseError;
import static org.parboiled.common.Preconditions.*;
import static org.parboiled.matchers.MatcherUtils.unwrap;
/**
* The Context implementation orchestrating most of the matching process.
* The parsing process works as following:
* After the rule tree (which is in fact a directed and potentially even cyclic graph of {@link Matcher} instances)
* has been created a root MatcherContext is instantiated for the root rule (Matcher).
* A subsequent call to {@link #runMatcher()} starts the parsing process.
* The MatcherContext delegates to a given {@link MatchHandler} to call {@link Matcher#match(MatcherContext)},
* passing itself to the Matcher which executes its logic, potentially calling sub matchers.
* For each sub matcher the matcher creates/initializes a subcontext with {@link Matcher#getSubContext(MatcherContext)}
* and then calls {@link #runMatcher()} on it.
* This basically creates a stack of MatcherContexts, each corresponding to their rule matchers. The MatcherContext
* instances serve as companion objects to the matchers, providing them with support for building the
* parse tree nodes, keeping track of input locations and error recovery.
* At each point during the parsing process the matchers and action expressions have access to the current
* MatcherContext and all "open" parent MatcherContexts through the {@link #getParent()} chain.
* For performance reasons subcontext instances are reused instead of being recreated. If a MatcherContext instance
* returns null on a {@link #getMatcher()} call it has been retired (is invalid) and is waiting to be reinitialized
* with a new Matcher by its parent
*/
public class MatcherContext implements Context {
private final InputBuffer inputBuffer;
private final ValueStack valueStack;
private final List parseErrors;
private final MatchHandler matchHandler;
private final MatcherContext parent;
private final int level;
private final boolean fastStringMatching;
private final Set memoizedMismatches;
private MatcherContext subContext;
private int startIndex;
private int currentIndex;
private char currentChar;
private Matcher matcher;
private Node node;
private ImmutableLinkedList> subNodes = ImmutableLinkedList.nil();
private MatcherPath path;
private int intTag;
private boolean hasError;
private boolean nodeSuppressed;
private boolean inErrorRecovery;
/**
* Initializes a new root MatcherContext.
*
* @param inputBuffer the InputBuffer for the parsing run
* @param valueStack the ValueStack instance to use for the parsing run
* @param parseErrors the parse error list to create ParseError objects in
* @param matchHandler the MatcherHandler to use for the parsing run
* @param matcher the root matcher
* @param fastStringMatching Fast string matching "short-circuits" the default practice of treating string rules
* as simple Sequence of character rules. When fast string matching is enabled strings are
* matched at once, without relying on inner CharacterMatchers. Even though this can lead
* to significant increases of parsing performance it does not play well with error
* reporting and recovery, which relies on character level matches.
* Therefore the {@link org.parboiled.parserunners.ReportingParseRunner} and {@link org.parboiled.parserunners.RecoveringParseRunner}
* implementations only enable fast string matching during their basic first parsing run
* and disable it once the input has proven to contain errors.
*/
public MatcherContext(InputBuffer inputBuffer, ValueStack valueStack, List parseErrors,
MatchHandler matchHandler, Matcher matcher, boolean fastStringMatching) {
this(checkArgNotNull(inputBuffer, "inputBuffer"), checkArgNotNull(valueStack, "valueStack"),
checkArgNotNull(parseErrors, "parseErrors"), checkArgNotNull(matchHandler, "matchHandler"),
null, 0, fastStringMatching, new HashSet());
this.currentChar = inputBuffer.charAt(0);
this.matcher = ProxyMatcher.unwrap(checkArgNotNull(matcher, "matcher"));
this.nodeSuppressed = matcher.isNodeSuppressed();
}
private MatcherContext(InputBuffer inputBuffer, ValueStack valueStack, List parseErrors,
MatchHandler matchHandler, MatcherContext parent, int level, boolean fastStringMatching,
Set memoizedMismatches) {
this.inputBuffer = inputBuffer;
this.valueStack = valueStack;
this.parseErrors = parseErrors;
this.matchHandler = matchHandler;
this.parent = parent;
this.level = level;
this.fastStringMatching = fastStringMatching;
this.memoizedMismatches = memoizedMismatches;
}
@Override
public String toString() {
return getPath().toString();
}
//////////////////////////////// CONTEXT INTERFACE ////////////////////////////////////
public MatcherContext getParent() {
return parent;
}
public InputBuffer getInputBuffer() {
return inputBuffer;
}
public int getStartIndex() {
return startIndex;
}
public Matcher getMatcher() {
return matcher;
}
public char getCurrentChar() {
return currentChar;
}
public List getParseErrors() {
return parseErrors;
}
public int getCurrentIndex() {
return currentIndex;
}
public MatcherPath getPath() {
if (path == null) {
path = new MatcherPath(new MatcherPath.Element(matcher, startIndex, level),
parent != null ? parent.getPath() : null);
}
return path;
}
public int getLevel() {
return level;
}
public boolean fastStringMatching() {
return fastStringMatching;
}
public ImmutableLinkedList> getSubNodes() {
return matcher.isNodeSkipped() ? subNodes : getSubNodes(subNodes, ImmutableLinkedList.>nil());
}
private static ImmutableLinkedList> getSubNodes(ImmutableLinkedList> remaining,
ImmutableLinkedList> tail) {
while (!remaining.isEmpty()) {
Node head = remaining.head();
if (head.getMatcher().isNodeSkipped()) {
tail = getSubNodes(((ImmutableLinkedList>)head.getChildren()), tail);
} else {
tail = tail.prepend(head);
}
remaining = remaining.tail();
}
return tail;
}
public boolean inPredicate() {
return matcher instanceof TestMatcher || matcher instanceof TestNotMatcher ||
parent != null && parent.inPredicate();
}
public boolean inErrorRecovery() {
return inErrorRecovery;
}
public boolean isNodeSuppressed() {
return nodeSuppressed;
}
public boolean hasError() {
return hasError;
}
public String getMatch() {
checkActionContext();
MatcherContext prevContext = subContext;
if (hasError) {
Node prevNode = prevContext.node;
return prevNode != null ? ParseTreeUtils.getNodeText(prevNode, inputBuffer) : "";
}
return inputBuffer.extract(prevContext.startIndex, prevContext.currentIndex);
}
public char getFirstMatchChar() {
checkActionContext();
int ix = subContext.startIndex;
if (subContext.currentIndex <= ix) {
throw new GrammarException("getFirstMatchChar called but previous rule did not match anything");
}
return inputBuffer.charAt(ix);
}
public int getMatchStartIndex() {
checkActionContext();
return subContext.startIndex;
}
public int getMatchEndIndex() {
checkActionContext();
return subContext.currentIndex;
}
public int getMatchLength() {
checkActionContext();
return subContext.currentIndex - subContext.getStartIndex();
}
public Position getPosition() {
return inputBuffer.getPosition(currentIndex);
}
public IndexRange getMatchRange() {
checkActionContext();
return new IndexRange(subContext.startIndex, subContext.currentIndex);
}
private void checkActionContext() {
// make sure all the constraints are met
Checks.ensure(unwrap(matcher) instanceof SequenceMatcher && intTag > 0 &&
subContext.matcher instanceof ActionMatcher,
"Illegal call to getMatch(), getMatchStartIndex(), getMatchEndIndex() or getMatchRange(), " +
"only valid in Sequence rule actions that are not in first position");
}
public ValueStack getValueStack() {
return valueStack;
}
//////////////////////////////// PUBLIC ////////////////////////////////////
public void setMatcher(Matcher matcher) {
this.matcher = matcher;
}
public void setStartIndex(int startIndex) {
checkArgument(startIndex >= 0);
this.startIndex = startIndex;
}
public void setCurrentIndex(int currentIndex) {
checkArgument(currentIndex >= 0);
this.currentIndex = currentIndex;
currentChar = inputBuffer.charAt(currentIndex);
}
public void setInErrorRecovery(boolean flag) {
inErrorRecovery = flag;
}
public void advanceIndex(int delta) {
currentIndex += delta;
currentChar = inputBuffer.charAt(currentIndex);
}
public Node getNode() {
return node;
}
public int getIntTag() {
return intTag;
}
public void setIntTag(int intTag) {
this.intTag = intTag;
}
public void markError() {
if (!hasError) {
hasError = true;
if (parent != null) parent.markError();
}
}
public Boolean hasMismatched() {
return memoizedMismatches.contains(MatcherPosition.at(matcher, currentIndex));
}
public void memoizeMismatch() {
memoizedMismatches.add(MatcherPosition.at(matcher, currentIndex));
}
@SuppressWarnings({"ConstantConditions"})
public void createNode() {
if (!nodeSuppressed) {
node = new NodeImpl(matcher, getSubNodes(), startIndex, currentIndex,
valueStack.isEmpty() ? null : valueStack.peek(), hasError);
if (parent != null) {
parent.subNodes = parent.subNodes.prepend(node);
}
}
}
public final MatcherContext getBasicSubContext() {
if (subContext == null) {
// init new level
subContext = new MatcherContext(inputBuffer, valueStack, parseErrors, matchHandler, this, level + 1,
fastStringMatching, memoizedMismatches);
} else {
subContext.path = null; // we always need to reset the MatcherPath, even for actions
}
return subContext;
}
public final MatcherContext getSubContext(Matcher matcher) {
MatcherContext sc = getBasicSubContext();
sc.matcher = matcher;
sc.startIndex = sc.currentIndex = currentIndex;
sc.currentChar = currentChar;
sc.node = null;
sc.subNodes = ImmutableLinkedList.nil();
sc.nodeSuppressed = nodeSuppressed || this.matcher.areSubnodesSuppressed() || matcher.isNodeSuppressed();
sc.hasError = false;
return sc;
}
public boolean runMatcher() {
try {
if (matchHandler.match(this)) {
if (parent != null) {
parent.currentIndex = currentIndex;
parent.currentChar = currentChar;
}
matcher = null; // "retire" this context
return true;
}
matcher = null; // "retire" this context until is "activated" again by a getSubContext(...) on the parent
return false;
} catch (ParserRuntimeException e) {
throw e; // don't wrap, just bubble up
} catch (RecoveringParseRunner.TimeoutException e) {
throw e; // don't wrap, just bubble up
} catch (Throwable e) {
throw new ParserRuntimeException(e,
printParseError(new BasicParseError(inputBuffer, currentIndex,
StringUtils.escape(String.format("Error while parsing %s '%s' at input position",
matcher instanceof ActionMatcher ? "action" : "rule", getPath())))) + '\n' + e);
}
}
}