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