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.errors; 018 019import org.parboiled.buffers.InputBuffer; 020import org.parboiled.common.Formatter; 021import org.parboiled.common.StringUtils; 022import org.parboiled.matchers.Matcher; 023import org.parboiled.matchers.TestNotMatcher; 024import org.parboiled.support.MatcherPath; 025import org.parboiled.support.ParsingResult; 026import org.parboiled.support.Position; 027import org.parboiled.common.Formatter; 028import org.parboiled.common.Preconditions; 029import org.parboiled.common.StringUtils; 030import org.parboiled.matchers.Matcher; 031import org.parboiled.matchers.TestNotMatcher; 032import org.parboiled.support.MatcherPath; 033import org.parboiled.support.ParsingResult; 034import org.parboiled.support.Position; 035 036import java.util.List; 037 038import static org.parboiled.common.Preconditions.checkArgNotNull; 039import static org.parboiled.common.Preconditions.checkArgument; 040 041/** 042 * General utility methods regarding parse errors. 043 */ 044public final class ErrorUtils { 045 046 private ErrorUtils() {} 047 048 /** 049 * Finds the Matcher in the given failedMatcherPath whose label is best for presentation in "expected" strings 050 * of parse error messages, given the provided lastMatchPath. 051 * 052 * @param path the path to the failed matcher 053 * @param errorIndex the start index of the respective parse error 054 * @return the matcher whose label is best for presentation in "expected" strings 055 */ 056 static Matcher findProperLabelMatcher(MatcherPath path, int errorIndex) { 057 try { return findProperLabelMatcher0(path, errorIndex); } 058 catch(RuntimeException e) { 059 if (e == UnderneathTestNot) return null; else throw e; 060 } 061 } 062 063 private static RuntimeException UnderneathTestNot = new RuntimeException() { 064 @Override public synchronized Throwable fillInStackTrace() { return this; } 065 }; 066 private static Matcher findProperLabelMatcher0(MatcherPath path, int errorIndex) { 067 Preconditions.checkArgNotNull(path, "path"); 068 Matcher found = path.parent != null ? findProperLabelMatcher0(path.parent, errorIndex) : null; 069 if (found != null) return found; 070 Matcher m = path.element.matcher; 071 if (m instanceof TestNotMatcher) throw UnderneathTestNot; 072 if (path.element.startIndex == errorIndex && m.hasCustomLabel()) return m; 073 return null; 074 } 075 076 /** 077 * Pretty prints the parse errors of the given ParsingResult showing their location in the given input buffer. 078 * 079 * @param parsingResult the parsing result 080 * @return the pretty print text 081 */ 082 public static String printParseErrors(ParsingResult<?> parsingResult) { 083 Preconditions.checkArgNotNull(parsingResult, "parsingResult"); 084 return printParseErrors(parsingResult.parseErrors); 085 } 086 087 /** 088 * Pretty prints the given parse errors showing their location in the given input buffer. 089 * 090 * @param errors the parse errors 091 * @return the pretty print text 092 */ 093 public static String printParseErrors(List<ParseError> errors) { 094 Preconditions.checkArgNotNull(errors, "errors"); 095 StringBuilder sb = new StringBuilder(); 096 for (ParseError error : errors) { 097 if (sb.length() > 0) sb.append("---\n"); 098 sb.append(printParseError(error)); 099 } 100 return sb.toString(); 101 } 102 103 /** 104 * Pretty prints the given parse error showing its location in the given input buffer. 105 * 106 * @param error the parse error 107 * @return the pretty print text 108 */ 109 public static String printParseError(ParseError error) { 110 Preconditions.checkArgNotNull(error, "error"); 111 return printParseError(error, new DefaultInvalidInputErrorFormatter()); 112 } 113 114 /** 115 * Pretty prints the given parse error showing its location in the given input buffer. 116 * 117 * @param error the parse error 118 * @param formatter the formatter for InvalidInputErrors 119 * @return the pretty print text 120 */ 121 public static String printParseError(ParseError error, Formatter<InvalidInputError> formatter) { 122 Preconditions.checkArgNotNull(error, "error"); 123 Preconditions.checkArgNotNull(formatter, "formatter"); 124 String message = error.getErrorMessage() != null ? error.getErrorMessage() : 125 error instanceof InvalidInputError ? 126 formatter.format((InvalidInputError) error) : error.getClass().getSimpleName(); 127 return printErrorMessage("%s (line %s, pos %s):", message, 128 error.getStartIndex(), error.getEndIndex(), error.getInputBuffer()); 129 } 130 131 /** 132 * Prints an error message showing a location in the given InputBuffer. 133 * 134 * @param format the format string, must include three placeholders for a string 135 * (the error message) and two integers (the error line / column respectively) 136 * @param errorMessage the error message 137 * @param errorIndex the error location as an index into the inputBuffer 138 * @param inputBuffer the underlying InputBuffer 139 * @return the error message including the relevant line from the underlying input plus location indicator 140 */ 141 public static String printErrorMessage(String format, String errorMessage, int errorIndex, 142 InputBuffer inputBuffer) { 143 Preconditions.checkArgNotNull(inputBuffer, "inputBuffer"); 144 return printErrorMessage(format, errorMessage, errorIndex, errorIndex + 1, inputBuffer); 145 } 146 147 /** 148 * Prints an error message showing a location in the given InputBuffer. 149 * 150 * @param format the format string, must include three placeholders for a string 151 * (the error message) and two integers (the error line / column respectively) 152 * @param errorMessage the error message 153 * @param startIndex the start location of the error as an index into the inputBuffer 154 * @param endIndex the end location of the error as an index into the inputBuffer 155 * @param inputBuffer the underlying InputBuffer 156 * @return the error message including the relevant line from the underlying input plus location indicators 157 */ 158 public static String printErrorMessage(String format, String errorMessage, int startIndex, int endIndex, 159 InputBuffer inputBuffer) { 160 Preconditions.checkArgNotNull(inputBuffer, "inputBuffer"); 161 Preconditions.checkArgument(startIndex <= endIndex); 162 Position pos = inputBuffer.getPosition(startIndex); 163 StringBuilder sb = new StringBuilder(String.format(format, errorMessage, pos.line, pos.column)); 164 sb.append('\n'); 165 166 String line = inputBuffer.extractLine(pos.line); 167 sb.append(line); 168 sb.append('\n'); 169 170 int charCount = Math.max(Math.min(endIndex - startIndex, StringUtils.length(line) - pos.column + 2), 1); 171 for (int i = 0; i < pos.column - 1; i++) sb.append(' '); 172 for (int i = 0; i < charCount; i++) sb.append('^'); 173 sb.append("\n"); 174 175 return sb.toString(); 176 } 177}