/* * 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 com.github.fge.grappa.run.context; import com.github.fge.grappa.buffers.InputBuffer; import com.github.fge.grappa.exceptions.GrappaException; import com.github.fge.grappa.exceptions.InvalidGrammarException; import com.github.fge.grappa.matchers.ActionMatcher; import com.github.fge.grappa.matchers.MatcherType; import com.github.fge.grappa.matchers.base.Matcher; import com.github.fge.grappa.matchers.wrap.ProxyMatcher; import com.github.fge.grappa.run.MatchHandler; import com.github.fge.grappa.stack.ValueStack; import com.github.fge.grappa.support.IndexRange; import com.github.fge.grappa.support.Position; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; /** * <p>The Context implementation orchestrating most of the matching process.</p> * * <p>The parsing process works as follows:</p> * * <p>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.</p> * * <p>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.</p> * * <p>This basically creates a stack of MatcherContexts, each corresponding to * their rule matchers.</p> * * <p>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.</p> */ @SuppressWarnings("InstanceVariableMayNotBeInitialized") public final class DefaultMatcherContext<V> implements MatcherContext<V> { private static final Joiner JOINER = Joiner.on('/'); private final InputBuffer inputBuffer; private final ValueStack<V> valueStack; private final MatchHandler matchHandler; private final DefaultMatcherContext<V> parent; private final int level; private DefaultMatcherContext<V> subContext; private int startIndex; private int currentIndex; private Matcher matcher; private String path; private boolean hasError; /** * 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 matchHandler the MatcherHandler to use for the parsing run * @param matcher the root matcher */ public DefaultMatcherContext(@Nonnull final InputBuffer inputBuffer, @Nonnull final ValueStack<V> valueStack, @Nonnull final MatchHandler matchHandler, @Nonnull final Matcher matcher) { this(Objects.requireNonNull(inputBuffer, "inputBuffer"), Objects.requireNonNull(valueStack, "valueStack"), Objects.requireNonNull(matchHandler, "matchHandler"), null, 0); Objects.requireNonNull(matcher); // TODO: what the... this.matcher = ProxyMatcher.unwrap(matcher); } private DefaultMatcherContext(final InputBuffer inputBuffer, final ValueStack<V> valueStack, final MatchHandler matchHandler, @Nullable final DefaultMatcherContext<V> parent, final int level) { this.inputBuffer = inputBuffer; this.valueStack = valueStack; this.matchHandler = matchHandler; this.parent = parent; this.level = level; } @Override public String toString() { return getPath(); } //////////////////////////////// CONTEXT INTERFACE ///////////////////////// @Override public MatcherContext<V> getParent() { return parent; } @Nonnull @Override public InputBuffer getInputBuffer() { return inputBuffer; } @Override public int getStartIndex() { return startIndex; } @Override public Matcher getMatcher() { return matcher; } @Override public boolean atEnd() { return currentIndex == inputBuffer.length(); } @Override public char getCurrentChar() { return inputBuffer.charAt(currentIndex); } @Override public int getCurrentCodePoint() { return inputBuffer.codePointAt(currentIndex); } @Override public int getCurrentIndex() { return currentIndex; } @Override public int getLevel() { return level; } @Override public boolean inPredicate() { //noinspection SimplifiableIfStatement if (matcher.getType() == MatcherType.PREDICATE) return true; return parent != null && parent.inPredicate(); } @Override public boolean hasError() { return hasError; } @Override public String getMatch() { final DefaultMatcherContext<V> ctx = subContext; return inputBuffer.extract(ctx.startIndex, ctx.currentIndex); } @Override public char getFirstMatchChar() { final int index = subContext.startIndex; if (subContext.currentIndex > index) return inputBuffer.charAt(index); // TODO: figure out why it says that throw new InvalidGrammarException("getFirstMatchChar called " + "but previous rule did not match anything"); } @Override public int getMatchStartIndex() { return subContext.startIndex; } @Override public int getMatchEndIndex() { return subContext.currentIndex; } @Override public int getMatchLength() { return subContext.currentIndex - subContext.startIndex; } @Override public Position getPosition() { return inputBuffer.getPosition(currentIndex); } @Override public IndexRange getMatchRange() { return new IndexRange(subContext.startIndex, subContext.currentIndex); } @Override public ValueStack<V> 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; } @Override public void advanceIndex(final int delta) { currentIndex += delta; } @Override public MatcherContext<V> getBasicSubContext() { if (subContext == null) { // init new level subContext = new DefaultMatcherContext<>(inputBuffer, valueStack, matchHandler, this, level + 1); } else { // we always need to reset the MatcherPath, even for actions subContext.path = null; } return subContext; } @Override public MatcherContext<V> getSubContext(final Matcher matcher) { final DefaultMatcherContext<V> sc = (DefaultMatcherContext<V>) getBasicSubContext(); sc.matcher = matcher; sc.setStartIndex(currentIndex); sc.setCurrentIndex(currentIndex); 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; return ret; } catch (GrappaException e) { throw e; // don't wrap, just bubble up } catch (Throwable e) { // TODO: Throwable? What the... final String msg = String.format( "exception thrown when parsing %s '%s' at input position %s", matcher instanceof ActionMatcher ? "action" : "rule", getPath(), inputBuffer.getPosition(currentIndex)); throw new GrappaException(msg, e); } } private String getPath() { if (path != null) return path; final List<String> list = new ArrayList<>(); MatcherContext<V> ctx; Matcher matcher; for (ctx = this; ctx != null; ctx = ctx.getParent()) { matcher = ctx.getMatcher(); // TODO: can this really happen? if (matcher != null) list.add(matcher.toString()); } Collections.reverse(list); path = JOINER.join(list); return path; } }