001/*
002 * Copyright (C) 2009-2011 Mathias Doenitz
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.parboiled;
018
019import org.parboiled.buffers.InputBuffer;
020import org.parboiled.common.ImmutableLinkedList;
021import org.parboiled.common.Preconditions;
022import org.parboiled.common.StringUtils;
023import org.parboiled.errors.BasicParseError;
024import org.parboiled.errors.ErrorUtils;
025import org.parboiled.errors.GrammarException;
026import org.parboiled.errors.ParseError;
027import org.parboiled.errors.ParserRuntimeException;
028import org.parboiled.matchers.ActionMatcher;
029import org.parboiled.matchers.Matcher;
030import org.parboiled.matchers.MatcherUtils;
031import org.parboiled.matchers.ProxyMatcher;
032import org.parboiled.matchers.SequenceMatcher;
033import org.parboiled.matchers.TestMatcher;
034import org.parboiled.matchers.TestNotMatcher;
035import org.parboiled.parserunners.RecoveringParseRunner;
036import org.parboiled.parserunners.ReportingParseRunner;
037import org.parboiled.support.Checks;
038import org.parboiled.support.IndexRange;
039import org.parboiled.support.MatcherPath;
040import org.parboiled.support.MatcherPosition;
041import org.parboiled.support.ParseTreeUtils;
042import org.parboiled.support.Position;
043import org.parboiled.support.ValueStack;
044import org.parboiled.matchers.ActionMatcher;
045import org.parboiled.matchers.Matcher;
046import org.parboiled.matchers.MatcherUtils;
047import org.parboiled.matchers.ProxyMatcher;
048import org.parboiled.matchers.SequenceMatcher;
049import org.parboiled.matchers.TestMatcher;
050import org.parboiled.matchers.TestNotMatcher;
051import org.parboiled.parserunners.RecoveringParseRunner;
052import org.parboiled.parserunners.ReportingParseRunner;
053import org.parboiled.support.Checks;
054import org.parboiled.support.IndexRange;
055import org.parboiled.support.MatcherPath;
056import org.parboiled.support.MatcherPosition;
057import org.parboiled.support.ParseTreeUtils;
058import org.parboiled.support.Position;
059import org.parboiled.support.ValueStack;
060
061import java.util.HashSet;
062import java.util.List;
063import java.util.Set;
064
065/**
066 * <p>The Context implementation orchestrating most of the matching process.</p>
067 * <p>The parsing process works as following:
068 * After the rule tree (which is in fact a directed and potentially even cyclic graph of {@link Matcher} instances)
069 * has been created a root MatcherContext is instantiated for the root rule (Matcher).
070 * A subsequent call to {@link #runMatcher()} starts the parsing process.</p>
071 * <p>The MatcherContext delegates to a given {@link MatchHandler} to call {@link Matcher#match(MatcherContext)},
072 * passing itself to the Matcher which executes its logic, potentially calling sub matchers.
073 * For each sub matcher the matcher creates/initializes a subcontext with {@link Matcher#getSubContext(MatcherContext)}
074 * and then calls {@link #runMatcher()} on it.</p>
075 * <p>This basically creates a stack of MatcherContexts, each corresponding to their rule matchers. The MatcherContext
076 * instances serve as companion objects to the matchers, providing them with support for building the
077 * parse tree nodes, keeping track of input locations and error recovery.</p>
078 * <p>At each point during the parsing process the matchers and action expressions have access to the current
079 * MatcherContext and all "open" parent MatcherContexts through the {@link #getParent()} chain.</p>
080 * <p>For performance reasons subcontext instances are reused instead of being recreated. If a MatcherContext instance
081 * returns null on a {@link #getMatcher()} call it has been retired (is invalid) and is waiting to be reinitialized
082 * with a new Matcher by its parent</p>
083 */
084public class MatcherContext<V> implements Context<V> {
085
086    private final InputBuffer inputBuffer;
087    private final ValueStack<V> valueStack;
088    private final List<ParseError> parseErrors;
089    private final MatchHandler matchHandler;
090    private final MatcherContext<V> parent;
091    private final int level;
092    private final boolean fastStringMatching;
093    private final Set<MatcherPosition> memoizedMismatches;
094
095    private MatcherContext<V> subContext;
096    private int startIndex;
097    private int currentIndex;
098    private char currentChar;
099    private Matcher matcher;
100    private Node<V> node;
101    private ImmutableLinkedList<Node<V>> subNodes = ImmutableLinkedList.nil();
102    private MatcherPath path;
103    private int intTag;
104    private boolean hasError;
105    private boolean nodeSuppressed;
106    private boolean inErrorRecovery;
107
108    /**
109     * Initializes a new root MatcherContext.
110     *
111     * @param inputBuffer        the InputBuffer for the parsing run
112     * @param valueStack         the ValueStack instance to use for the parsing run
113     * @param parseErrors        the parse error list to create ParseError objects in
114     * @param matchHandler       the MatcherHandler to use for the parsing run
115     * @param matcher            the root matcher
116     * @param fastStringMatching <p>Fast string matching "short-circuits" the default practice of treating string rules
117     *                           as simple Sequence of character rules. When fast string matching is enabled strings are
118     *                           matched at once, without relying on inner CharacterMatchers. Even though this can lead
119     *                           to significant increases of parsing performance it does not play well with error
120     *                           reporting and recovery, which relies on character level matches.
121     *                           Therefore the {@link ReportingParseRunner} and {@link RecoveringParseRunner}
122     *                           implementations only enable fast string matching during their basic first parsing run
123     *                           and disable it once the input has proven to contain errors.</p>
124     */
125    public MatcherContext(InputBuffer inputBuffer, ValueStack<V> valueStack, List<ParseError> parseErrors,
126                          MatchHandler matchHandler, Matcher matcher, boolean fastStringMatching) {
127        this(Preconditions.checkArgNotNull(inputBuffer, "inputBuffer"), Preconditions.checkArgNotNull(valueStack, "valueStack"),
128                Preconditions.checkArgNotNull(parseErrors, "parseErrors"), Preconditions.checkArgNotNull(matchHandler, "matchHandler"),
129                null, 0, fastStringMatching,  new HashSet<MatcherPosition>());
130        this.currentChar = inputBuffer.charAt(0);
131        this.matcher = ProxyMatcher.unwrap(Preconditions.checkArgNotNull(matcher, "matcher"));
132        this.nodeSuppressed = matcher.isNodeSuppressed();
133    }
134
135    private MatcherContext(InputBuffer inputBuffer, ValueStack<V> valueStack, List<ParseError> parseErrors,
136                           MatchHandler matchHandler, MatcherContext<V> parent, int level, boolean fastStringMatching,
137                           Set<MatcherPosition> memoizedMismatches) {
138        this.inputBuffer = inputBuffer;
139        this.valueStack = valueStack;
140        this.parseErrors = parseErrors;
141        this.matchHandler = matchHandler;
142        this.parent = parent;
143        this.level = level;
144        this.fastStringMatching = fastStringMatching;
145        this.memoizedMismatches = memoizedMismatches;
146    }
147
148    @Override
149    public String toString() {
150        return getPath().toString();
151    }
152
153    //////////////////////////////// CONTEXT INTERFACE ////////////////////////////////////
154
155    public MatcherContext<V> getParent() {
156        return parent;
157    }
158
159    public InputBuffer getInputBuffer() {
160        return inputBuffer;
161    }
162
163    public int getStartIndex() {
164        return startIndex;
165    }
166
167    public Matcher getMatcher() {
168        return matcher;
169    }
170
171    public char getCurrentChar() {
172        return currentChar;
173    }
174
175    public List<ParseError> getParseErrors() {
176        return parseErrors;
177    }
178
179    public int getCurrentIndex() {
180        return currentIndex;
181    }
182
183    public MatcherPath getPath() {
184        if (path == null) {
185            path = new MatcherPath(new MatcherPath.Element(matcher, startIndex, level),
186                    parent != null ? parent.getPath() : null);
187        }
188        return path;
189    }
190
191    public int getLevel() {
192        return level;
193    }
194
195    public boolean fastStringMatching() {
196        return fastStringMatching;
197    }
198
199    public ImmutableLinkedList<Node<V>> getSubNodes() {
200        return matcher.isNodeSkipped() ? subNodes : getSubNodes(subNodes, ImmutableLinkedList.<Node<V>>nil());
201    }
202
203    private static <V> ImmutableLinkedList<Node<V>> getSubNodes(ImmutableLinkedList<Node<V>> remaining,
204                                                                ImmutableLinkedList<Node<V>> tail) {
205        while (!remaining.isEmpty()) {
206            Node<V> head = remaining.head();
207            if (head.getMatcher().isNodeSkipped()) {
208                tail = getSubNodes(((ImmutableLinkedList<Node<V>>)head.getChildren()), tail);
209            } else {
210                tail = tail.prepend(head);
211            }
212            remaining = remaining.tail();
213        }
214        return tail;
215    }
216
217    public boolean inPredicate() {
218        return matcher instanceof TestMatcher || matcher instanceof TestNotMatcher ||
219                parent != null && parent.inPredicate();
220    }
221
222    public boolean inErrorRecovery() {
223        return inErrorRecovery;
224    }
225    
226    public boolean isNodeSuppressed() {
227        return nodeSuppressed;
228    }
229
230    public boolean hasError() {
231        return hasError;
232    }
233
234    public String getMatch() {
235        checkActionContext();
236        MatcherContext prevContext = subContext;
237        if (hasError) {
238            Node prevNode = prevContext.node;
239            return prevNode != null ? ParseTreeUtils.getNodeText(prevNode, inputBuffer) : "";
240        }
241        return inputBuffer.extract(prevContext.startIndex, prevContext.currentIndex);
242    }
243
244    public char getFirstMatchChar() {
245        checkActionContext();
246        int ix = subContext.startIndex;
247        if (subContext.currentIndex <= ix) {
248            throw new GrammarException("getFirstMatchChar called but previous rule did not match anything");
249        }
250        return inputBuffer.charAt(ix);
251    }
252
253    public int getMatchStartIndex() {
254        checkActionContext();
255        return subContext.startIndex;
256    }
257
258    public int getMatchEndIndex() {
259        checkActionContext();
260        return subContext.currentIndex;
261    }
262
263    public int getMatchLength() {
264        checkActionContext();
265        return subContext.currentIndex - subContext.getStartIndex();
266    }
267
268    public Position getPosition() {
269        return inputBuffer.getPosition(currentIndex);
270    }
271
272    public IndexRange getMatchRange() {
273        checkActionContext();
274        return new IndexRange(subContext.startIndex, subContext.currentIndex);
275    }
276
277    private void checkActionContext() {
278        // make sure all the constraints are met
279        Checks.ensure(MatcherUtils.unwrap(matcher) instanceof SequenceMatcher && intTag > 0 &&
280                subContext.matcher instanceof ActionMatcher,
281                "Illegal call to getMatch(), getMatchStartIndex(), getMatchEndIndex() or getMatchRange(), " +
282                        "only valid in Sequence rule actions that are not in first position");
283    }
284
285    public ValueStack<V> getValueStack() {
286        return valueStack;
287    }
288
289    //////////////////////////////// PUBLIC ////////////////////////////////////
290
291    public void setMatcher(Matcher matcher) {
292        this.matcher = matcher;
293    }
294
295    public void setStartIndex(int startIndex) {
296        Preconditions.checkArgument(startIndex >= 0);
297        this.startIndex = startIndex;
298    }
299
300    public void setCurrentIndex(int currentIndex) {
301        Preconditions.checkArgument(currentIndex >= 0);
302        this.currentIndex = currentIndex;
303        currentChar = inputBuffer.charAt(currentIndex);
304    }
305    
306    public void setInErrorRecovery(boolean flag) {
307        inErrorRecovery = flag;
308    }
309
310    public void advanceIndex(int delta) {
311        currentIndex += delta;
312        currentChar = inputBuffer.charAt(currentIndex);
313    }
314
315    public Node<V> getNode() {
316        return node;
317    }
318
319    public int getIntTag() {
320        return intTag;
321    }
322
323    public void setIntTag(int intTag) {
324        this.intTag = intTag;
325    }
326
327    public void markError() {
328        if (!hasError) {
329            hasError = true;
330            if (parent != null) parent.markError();
331        }
332    }
333
334    public Boolean hasMismatched() {
335        return memoizedMismatches.contains(MatcherPosition.at(matcher, currentIndex));
336    }
337
338    public void memoizeMismatch() {
339        memoizedMismatches.add(MatcherPosition.at(matcher, currentIndex));
340    }
341
342    @SuppressWarnings({"ConstantConditions"})
343    public void createNode() {
344        if (!nodeSuppressed) {
345            node = new NodeImpl<V>(matcher, getSubNodes(), startIndex, currentIndex,
346                    valueStack.isEmpty() ? null : valueStack.peek(), hasError);
347            if (parent != null) {
348                parent.subNodes = parent.subNodes.prepend(node);
349            }
350        }
351    }
352
353    public final MatcherContext<V> getBasicSubContext() {
354        if (subContext == null) {
355            // init new level
356            subContext = new MatcherContext<V>(inputBuffer, valueStack, parseErrors, matchHandler, this, level + 1,
357                        fastStringMatching, memoizedMismatches);
358        } else {
359            subContext.path = null; // we always need to reset the MatcherPath, even for actions
360        }
361        return subContext;
362    }
363
364    public final MatcherContext<V> getSubContext(Matcher matcher) {
365        MatcherContext<V> sc = getBasicSubContext();
366        sc.matcher = matcher;
367        sc.startIndex = sc.currentIndex = currentIndex;
368        sc.currentChar = currentChar;
369        sc.node = null;
370        sc.subNodes = ImmutableLinkedList.nil();
371        sc.nodeSuppressed = nodeSuppressed || this.matcher.areSubnodesSuppressed() || matcher.isNodeSuppressed();
372        sc.hasError = false;
373        return sc;
374    }
375
376    public boolean runMatcher() {
377        try {
378            if (matchHandler.match(this)) {
379                if (parent != null) {
380                    parent.currentIndex = currentIndex;
381                    parent.currentChar = currentChar;
382                }
383                matcher = null; // "retire" this context
384                return true;
385            }
386            matcher = null; // "retire" this context until is "activated" again by a getSubContext(...) on the parent
387            return false;
388        } catch (ParserRuntimeException e) {
389            throw e; // don't wrap, just bubble up
390        } catch (RecoveringParseRunner.TimeoutException e) {
391            throw e; // don't wrap, just bubble up
392        } catch (Throwable e) {
393            throw new ParserRuntimeException(e,
394                    ErrorUtils.printParseError(new BasicParseError(inputBuffer, currentIndex,
395                            StringUtils.escape(String.format("Error while parsing %s '%s' at input position",
396                                    matcher instanceof ActionMatcher ? "action" : "rule", getPath())))) + '\n' + e);
397        }
398    }
399}