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.InvalidInputError;
024import org.parboiled.matchervisitors.IsSingleCharMatcherVisitor;
025import org.parboiled.support.MatcherPath;
026import org.parboiled.support.ParsingResult;
027import org.parboiled.support.MatcherPath;
028import org.parboiled.support.ParsingResult;
029
030import java.util.ArrayList;
031import java.util.List;
032
033import static org.parboiled.common.Preconditions.checkArgNotNull;
034
035/**
036 * A {@link ParseRunner} implementation that creates an
037 * {@link InvalidInputError} for the error at a known error location.
038 * It never causes the parser to perform more than one parsing run and is rarely used directly.
039 * Instead its functionality is relied upon by the {@link ReportingParseRunner} and {@link RecoveringParseRunner} classes.
040 */
041public class ErrorReportingParseRunner<V> extends AbstractParseRunner<V> implements MatchHandler {
042    private final IsSingleCharMatcherVisitor isSingleCharMatcherVisitor = new IsSingleCharMatcherVisitor();
043    private final int errorIndex;
044    private final MatchHandler inner;
045    private final List<MatcherPath> failedMatchers = new ArrayList<MatcherPath>();
046    private boolean seeking;
047
048    /**
049     * Creates a new ErrorReportingParseRunner instance for the given rule and the given errorIndex.
050     *
051     * @param rule       the parser rule
052     * @param errorIndex the index of the error to report
053     */
054    public ErrorReportingParseRunner(Rule rule, int errorIndex) {
055        this(rule, errorIndex, null);
056    }
057
058    /**
059     * Creates a new ErrorReportingParseRunner instance for the given rule and the given errorIndex.
060     * The given MatchHandler is used as a delegate for the actual match handling.
061     *
062     * @param rule       the parser rule
063     * @param errorIndex the index of the error to report
064     * @param inner      another MatchHandler to delegate the actual match handling to, can be null
065     */
066    public ErrorReportingParseRunner(Rule rule, int errorIndex, MatchHandler inner) {
067        super(rule);
068        this.errorIndex = errorIndex;
069        this.inner = inner;
070    }
071
072    public ParsingResult<V> run(InputBuffer inputBuffer) {
073        checkArgNotNull(inputBuffer, "inputBuffer");
074        resetValueStack();        
075        failedMatchers.clear();
076        seeking = errorIndex > 0;
077
078        // run without fast string matching to properly get to the error location
079        MatcherContext<V> rootContext = createRootContext(inputBuffer, this, false);
080        boolean matched = match(rootContext);
081        if (!matched) {
082            getParseErrors().add(new InvalidInputError(inputBuffer, errorIndex, failedMatchers, null));
083        }
084        return createParsingResult(matched, rootContext);
085    }
086
087    public boolean match(MatcherContext<?> context) {
088        boolean matched = inner == null && context.getMatcher().match(context) || inner != null && inner.match(context);
089        if (context.getCurrentIndex() == errorIndex) {
090            if (matched && seeking) {
091                seeking = false;
092            }
093            if (!matched && !seeking && context.getMatcher().accept(isSingleCharMatcherVisitor)) {
094                failedMatchers.add(context.getPath());
095            }
096        }
097        return matched;
098    }
099}
100