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

org.parboiled.DefaultMatcherContext 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 com.github.parboiled1.grappa.buffers.InputBuffer;
import com.github.parboiled1.grappa.matchers.ActionMatcher;
import com.github.parboiled1.grappa.matchers.base.Matcher;
import com.github.parboiled1.grappa.matchers.delegate.SequenceMatcher;
import com.github.parboiled1.grappa.matchers.predicates.TestMatcher;
import com.github.parboiled1.grappa.matchers.predicates.TestNotMatcher;
import com.github.parboiled1.grappa.matchers.wrap.ProxyMatcher;
import com.github.parboiled1.grappa.stack.ValueStack;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import org.parboiled.errors.BasicParseError;
import org.parboiled.errors.GrammarException;
import org.parboiled.errors.ParseError;
import org.parboiled.errors.ParserRuntimeException;
import org.parboiled.support.CharsEscaper;
import org.parboiled.support.Checks;
import org.parboiled.support.IndexRange;
import org.parboiled.support.MatcherPath;
import org.parboiled.support.MatcherPosition;
import org.parboiled.support.Position;
import org.parboiled.trees.ParseTreeUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static com.github.parboiled1.grappa.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 final class DefaultMatcherContext implements MatcherContext { private final InputBuffer inputBuffer; private final ValueStack valueStack; private final List parseErrors; private final MatchHandler matchHandler; private final DefaultMatcherContext parent; private final int level; private final Set memoizedMismatches; private DefaultMatcherContext subContext; private int startIndex; private int currentIndex; private char currentChar; private Matcher matcher; private Node node; // TODO! Replace! private List> subNodes = Lists.newArrayList(); private MatcherPath path; private int intTag; private boolean hasError; private boolean nodeSuppressed; /** * 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 */ public DefaultMatcherContext(@Nonnull final InputBuffer inputBuffer, @Nonnull final ValueStack valueStack, @Nonnull final List parseErrors, @Nonnull final MatchHandler matchHandler, @Nonnull final Matcher matcher) { this(Preconditions.checkNotNull(inputBuffer, "inputBuffer"), Preconditions.checkNotNull(valueStack, "valueStack"), Preconditions.checkNotNull(parseErrors, "parseErrors"), Preconditions.checkNotNull(matchHandler, "matchHandler"), null, 0, new HashSet()); currentChar = inputBuffer.charAt(0); Preconditions.checkNotNull(matcher); // TODO: what the... this.matcher = ProxyMatcher.unwrap(matcher); nodeSuppressed = matcher.isNodeSuppressed(); } private DefaultMatcherContext(final InputBuffer inputBuffer, final ValueStack valueStack, final List parseErrors, final MatchHandler matchHandler, @Nullable final DefaultMatcherContext parent, final int level, final Set memoizedMismatches) { this.inputBuffer = inputBuffer; this.valueStack = valueStack; this.parseErrors = parseErrors; this.matchHandler = matchHandler; this.parent = parent; this.level = level; this.memoizedMismatches = memoizedMismatches; } @Override public String toString() { return getPath().toString(); } //////////////////////////////// CONTEXT INTERFACE //////////////////////////////////// @Override public MatcherContext getParent() { return parent; } @Nonnull @Override public InputBuffer getInputBuffer() { return inputBuffer; } @Override public int getStartIndex() { return startIndex; } @Override public Matcher getMatcher() { return matcher; } @Override public char getCurrentChar() { return currentChar; } @Nonnull @Override public List getParseErrors() { return parseErrors; } @Override public int getCurrentIndex() { return currentIndex; } @Nonnull @Override public MatcherPath getPath() { if (path != null) return path; path = new MatcherPath(new MatcherPath.Element(matcher, startIndex, level), parent != null ? parent.getPath() : null); return path; } @Override public int getLevel() { return level; } @Override @Nonnull public List> getSubNodes() { if (matcher.isNodeSkipped()) return subNodes; final Deque> remaining = Queues.newArrayDeque(subNodes); final List> ret = Lists.newArrayList(); collectSubNodes(remaining, ret); Collections.reverse(ret); return ret; } private static void collectSubNodes(final Deque> remaining, final List> into) { Node head; while (!remaining.isEmpty()) { head = remaining.pop(); if (!head.getMatcher().isNodeSkipped()) { into.add(head); continue; } collectSubNodes(Queues.newArrayDeque(head.getChildren()), into); } } @Override public boolean inPredicate() { if (matcher instanceof TestMatcher) return true; if (matcher instanceof TestNotMatcher) return true; if (parent == null) return false; return parent.inPredicate(); } @Override public boolean isNodeSuppressed() { return nodeSuppressed; } @Override public boolean hasError() { return hasError; } @Override public String getMatch() { checkActionContext(); final DefaultMatcherContext prevContext = subContext; if (!hasError) return inputBuffer.extract(prevContext.startIndex, prevContext.currentIndex); final Node prevNode = prevContext.node; return prevNode != null ? ParseTreeUtils.getNodeText(prevNode, inputBuffer) : ""; } @Override public char getFirstMatchChar() { checkActionContext(); final int index = subContext.startIndex; if (subContext.currentIndex > index) return inputBuffer.charAt(index); // TODO: figure out why it says that throw new GrammarException("getFirstMatchChar called but previous rule" + " did not match anything"); } @Override public int getMatchStartIndex() { checkActionContext(); return subContext.startIndex; } @Override public int getMatchEndIndex() { checkActionContext(); return subContext.currentIndex; } @Override public int getMatchLength() { checkActionContext(); return subContext.currentIndex - subContext.startIndex; } @Override public Position getPosition() { return inputBuffer.getPosition(currentIndex); } @Override public IndexRange getMatchRange() { checkActionContext(); return new IndexRange(subContext.startIndex, subContext.currentIndex); } // TODO: pain point! private void checkActionContext() { // make sure all the constraints are met // TODO: why is intTag required to be > 0 at all? final boolean condition = unwrap(matcher) instanceof SequenceMatcher && intTag > 0 && subContext.matcher instanceof ActionMatcher; Checks.ensure(condition, "Illegal call to getMatch(), getMatchStartIndex()," + " getMatchEndIndex() or getMatchRange(), only valid in Sequence" + " rule actions that are not in first position" ); } @Override public ValueStack getValueStack() { return valueStack; } //////////////////////////////// PUBLIC //////////////////////////////////// @Override public void setMatcher(final Matcher matcher) { this.matcher = matcher; } @Override public void setStartIndex(final int startIndex) { Preconditions.checkArgument(startIndex >= 0); this.startIndex = startIndex; } @Override public void setCurrentIndex(final int currentIndex) { Preconditions.checkArgument(currentIndex >= 0); this.currentIndex = currentIndex; currentChar = inputBuffer.charAt(currentIndex); } @Override public void advanceIndex(final int delta) { currentIndex += delta; currentChar = inputBuffer.charAt(currentIndex); } @Override public Node getNode() { return node; } @Override public void setIntTag(final int intTag) { this.intTag = intTag; } @Override public boolean hasMismatched() { return memoizedMismatches.contains(MatcherPosition.at(matcher, currentIndex)); } @Override public void memoizeMismatch() { memoizedMismatches.add(MatcherPosition.at(matcher, currentIndex)); } @Override public void createNode() { if (nodeSuppressed) return; node = new DefaultParsingNode<>(matcher, getSubNodes(), startIndex, currentIndex, valueStack.isEmpty() ? null : valueStack.peek(), hasError); if (parent != null) { parent.subNodes.add(0, node); } } @Override public MatcherContext getBasicSubContext() { if (subContext == null) { // init new level subContext = new DefaultMatcherContext<>(inputBuffer, valueStack, parseErrors, matchHandler, this, level + 1, memoizedMismatches); } else { // we always need to reset the MatcherPath, even for actions subContext.path = null; } return subContext; } @Override public MatcherContext getSubContext(final Matcher matcher) { final DefaultMatcherContext sc = (DefaultMatcherContext) getBasicSubContext(); sc.matcher = matcher; sc.setStartIndex(currentIndex); sc.setCurrentIndex(currentIndex); sc.currentChar = currentChar; sc.subNodes = Lists.newArrayList(); sc.nodeSuppressed = nodeSuppressed || this.matcher.areSubnodesSuppressed() || matcher.isNodeSuppressed(); sc.hasError = false; return sc; } @Override public boolean runMatcher() { try { final boolean ret = matchHandler.match(this); // Retire this context // TODO: what does the above really mean? matcher = null; if (ret && parent != null) { parent.currentIndex = currentIndex; parent.currentChar = currentChar; } return ret; } catch (ParserRuntimeException e) { throw e; // don't wrap, just bubble up } catch (Throwable e) { // TODO: Throwable? What the... final String msg = String.format( "Error while parsing %s '%s' at input position\n%s", matcher instanceof ActionMatcher ? "action" : "rule", getPath(), e); final BasicParseError error = new BasicParseError(inputBuffer, currentIndex, CharsEscaper.INSTANCE.escape(msg)); // TODO: UGLY throw new ParserRuntimeException(e, error.toString()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy