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.parserunners;
018
019import org.parboiled.MatchHandler;
020import org.parboiled.MatcherContext;
021import org.parboiled.Rule;
022import org.parboiled.buffers.InputBuffer;
023import org.parboiled.errors.BasicParseError;
024import org.parboiled.matchers.TestNotMatcher;
025import org.parboiled.support.ParsingResult;
026import org.parboiled.matchers.TestNotMatcher;
027import org.parboiled.support.ParsingResult;
028
029import static org.parboiled.common.Preconditions.checkArgNotNull;
030
031/**
032 * A {@link ParseRunner} implementation that creates a simple {@link BasicParseError} for the first error found in the
033 * input and adds it to the list of ParseErrors.
034 * It never causes the parser to perform more than one parsing run and is rarely used directly.
035 * Instead its functionality is relied upon by the {@link ReportingParseRunner} and {@link RecoveringParseRunner} classes.
036 */
037public class ErrorLocatingParseRunner<V> extends AbstractParseRunner<V> implements MatchHandler {
038    private final MatchHandler inner;
039    private int errorIndex;
040
041    /**
042     * Creates a new ErrorLocatingParseRunner instance for the given rule.
043     *
044     * @param rule the parser rule
045     */
046    public ErrorLocatingParseRunner(Rule rule) {
047        this(rule, null);
048    }
049    
050    /**
051     * Creates a new ErrorLocatingParseRunner instance for the given rule.
052     * The given MatchHandler is used as a delegate for the actual match handling.
053     *
054     * @param rule the parser rule
055     * @param inner another MatchHandler to delegate the actual match handling to, can be null
056     */
057    public ErrorLocatingParseRunner(Rule rule, MatchHandler inner) {
058        super(rule);
059        this.inner = inner;
060    }
061
062    public ParsingResult<V> run(InputBuffer inputBuffer) {
063        checkArgNotNull(inputBuffer, "inputBuffer");
064        resetValueStack();
065        errorIndex = 0;
066        
067        // run without fast string matching to properly get the error location
068        MatcherContext<V> rootContext = createRootContext(inputBuffer, this, false);
069        boolean matched = match(rootContext);
070        if (!matched) {
071            getParseErrors().add(new BasicParseError(inputBuffer, errorIndex, null));
072        }
073        return createParsingResult(matched, rootContext);
074    }
075
076    public boolean match(MatcherContext<?> context) {
077        if (inner == null && context.getMatcher().match(context) || inner != null && inner.match(context)) {
078            if (errorIndex < context.getCurrentIndex() && notTestNot(context)) {
079                errorIndex = context.getCurrentIndex();
080            }
081            return true;
082        }
083        return false;
084    }
085
086    private boolean notTestNot(MatcherContext context) {
087        return !(context.getMatcher() instanceof TestNotMatcher) &&
088                (context.getParent() == null || notTestNot(context.getParent()));
089    }
090}