All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy