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.Context;
020import org.parboiled.MatchHandler;
021import org.parboiled.MatcherContext;
022import org.parboiled.Rule;
023import org.parboiled.buffers.InputBuffer;
024import org.parboiled.common.ConsoleSink;
025import org.parboiled.common.Predicate;
026import org.parboiled.common.Predicates;
027import org.parboiled.common.Sink;
028import org.parboiled.common.Tuple2;
029import org.parboiled.matchers.Matcher;
030import org.parboiled.support.Filters;
031import org.parboiled.support.MatcherPath;
032import org.parboiled.support.ParsingResult;
033import org.parboiled.support.Position;
034import org.parboiled.matchers.Matcher;
035import org.parboiled.support.Filters;
036import org.parboiled.support.MatcherPath;
037import org.parboiled.support.ParsingResult;
038import org.parboiled.support.Position;
039
040import static org.parboiled.common.Preconditions.checkArgNotNull;
041
042/**
043 * A {@link ParseRunner} implementation used for debugging purposes.
044 * It exhibits the same behavior as the {@link ReportingParseRunner} but collects debugging information as to which
045 * rules did match and which didn't.
046 */
047public class TracingParseRunner<V> extends ReportingParseRunner<V> implements MatchHandler {
048    private Predicate<Tuple2<Context<?>, Boolean>> filter;
049    private Sink<String> log;
050    private MatcherPath lastPath;
051    private int line;
052
053    /**
054     * Creates a new TracingParseRunner instance without filter and a console log for the given rule.
055     *
056     * @param rule the parser rule
057     */
058    public TracingParseRunner(Rule rule) {
059        super(rule);
060    }
061
062    /**
063     * Attaches the given filter to this TracingParseRunner instance.
064     * The given filter is used to select the matchers to print tracing statements for.
065     * NOTE: The given filter must be of type {@literal Predicate<Tuple2<Context<?>, Boolean>>}. The reason this type is not
066     * directly specified in the constructors signature is that this would make predicate expressions using the
067     * {@link Predicates} operations and the predefined predicate constructors in {@link Filters}
068     * much more cumbersome to write (due to Java limited type parameters inference logic you would have to explicitly
069     * state the type parameters in many places).
070     *
071     * @param filter the matcher filter selecting the matchers to print tracing statements for. Must be of type
072     *               {@literal Predicate<Tuple2<Context<?>, Boolean>>}.
073     * @return this instance
074     */
075    @SuppressWarnings( {"unchecked"})
076    public TracingParseRunner<V> withFilter(Predicate<?> filter) {
077        this.filter = (Predicate<Tuple2<Context<?>, Boolean>>) checkArgNotNull(filter, "filter");
078        return this;
079    }
080
081    public Predicate<Tuple2<Context<?>, Boolean>> getFilter() {
082        if (filter == null) {
083            withFilter(Predicates.alwaysTrue());
084        }
085        return filter;
086    }
087
088    /**
089     * Attaches the given log to this TracingParseRunner instance.
090     *
091     * @param log the log to use
092     * @return this instance
093     */
094    public TracingParseRunner<V> withLog(Sink<String> log) {
095        this.log = log;
096        return this;
097    }
098
099    public Sink<String> getLog() {
100        if (log == null) {
101            withLog(new ConsoleSink());
102        }
103        return log;
104    }
105
106    @Override
107    protected ParsingResult<V> runBasicMatch(InputBuffer inputBuffer) {
108        getLog().receive("Starting new parsing run\n");
109        lastPath = null;
110
111        MatcherContext<V> rootContext = createRootContext(inputBuffer, this, true);
112        boolean matched = rootContext.runMatcher();
113        return createParsingResult(matched, rootContext);
114    }
115
116    @SuppressWarnings( {"unchecked"})
117    public boolean match(MatcherContext<?> context) {
118        Matcher matcher = context.getMatcher();
119        boolean matched = matcher.match(context);
120        if (getFilter().apply(new Tuple2<Context<?>, Boolean>(context, matched))) {
121            line++;
122            print(context, matched); // set line-dependent breakpoint here
123        }
124        return matched;
125    }
126
127    private void print(MatcherContext<?> context, boolean matched) {
128        Position pos = context.getInputBuffer().getPosition(context.getCurrentIndex());
129        MatcherPath path = context.getPath();
130        MatcherPath prefix = lastPath != null ? path.commonPrefix(lastPath) : null;
131        if (prefix != null && prefix.length() > 1) getLog().receive("..(" + (prefix.length() - 1) + ")../");
132        getLog().receive(path.toString(prefix != null ? prefix.parent : null));
133        String line = context.getInputBuffer().extractLine(pos.line);
134        getLog().receive(", " + (matched ? "matched" : "failed") + ", cursor at " + pos.line + ':' + pos.column +
135                " after \"" + line.substring(0, Math.min(line.length(), pos.column - 1)) + "\"\n");
136        lastPath = path;
137    }
138}
139