package parser;
import grammar.Expression;
import java.util.ArrayList;
import java.util.List;
import source.Source;
import util.StringUtils;
/*******************************************************************************
* There is currently a single way to handle parse errors. The system will
* report only a single parse error. This error will be the one that happens the
* farthest ahead in the input stream.
*
* You might be wondering why we can reach this farthest point if we encountered
* errors earlier in the input. The answer is that the errors could be
* encountered when parsing alternatives of a choice expression. If the first
* alternative fails, we carry on by trying the second one, etc. The errors
* could also happen when failing to match an optional expression (in ?, *, +).
*
* Actually, the system may report more than one error if more than one error
* happen at the farthest error position. This means that at any given point
* during a parse we must keep the farthest input position and a list of
* expressions that fail there. This data is held in the ErrorGroup class.
*
* "at any given point during a parse" ? Can't we discard the current error
* information once we have successfully parsed an expression? Unfortunately no.
* Consider the following example:
*
* <pre>
* input = A, B, E
* expr = seq(choice(seq(A, B, C),
* A),
* D)
* </pre>
*
* Despite the fact that the choice succeeds with its second alternative (A),
* the error in the first alternative is still the most relevant to the failure
* to parse the input sequence. The error on expression C happens at input
* position 3 (E), while the error on D happens at input position 2 (B).
*
* -----------------------------------------------------------------------------
*
* A few details about particular expressions:
*
* Not: Since a parse error in what is expected in a Not expression, all parse
* errors are discarded on success. This is achieved as a side effect of having
* Not expression being atomic.
*
* And: Despite resetting the input position, And is treated like any other
* expression with regards to error handling.
*
* Opt, Star and Plus: The failure to match an optional expression does register
* an error.
*
* -----------------------------------------------------------------------------
*
* We record the path in the expression to a failing expression by building a
* graph of ParseErrors objects. Each ParseErrors object refers to the Errors
* object for its sub-expressions.
*
* ParseErrors are built regardless of whether there was or not an error.
*/
public class ParseErrors
{
/*****************************************************************************
* Expression to which this error group belongs.
*/
final Expression expression;
/*****************************************************************************
* Position corresponding to this error group. -1 if the error group is empty.
*/
int position;
/*****************************************************************************
* Errors in the sub-expressions, or null if all errors happen at the begin of
* the expression.
*/
private List<ParseErrors> subs = new ArrayList<>(1);
/*****************************************************************************
* Begin of the match for the expression.
*/
private final int begin;
/****************************************************************************/
ParseErrors(final Expression expression, final int begin)
{
this.expression = expression;
this.begin = begin;
this.position = begin - 1;
}
/*****************************************************************************
* Indicates an error at given input position for the current expression. If
* the error happened in a sub-expression, and information about this error is
* available, merge(ErroGroup) should be used instead.
*/
void merge(final int position)
{
if (position > this.position) {
this.position = position;
subs.clear();
}
}
/****************************************************************************
* Merges the error group for a sub-expression to this error group. Called on
* error groups corresponding to non-leaf expressions.
*
* The procedure works as explained in the class comment. In particular, only
* the farthest errors are kept. The error list is truncated if all
* sub-expression fail at the beginning of the expression.
*/
void merge(final ParseErrors childErrors)
{
if (childErrors.position == this.begin && this.position <= this.begin)
{
this.position = this.begin;
}
else if (childErrors.position > this.position)
{
this.position = childErrors.position;
subs.clear();
subs.add(childErrors);
}
else if (childErrors.position == this.position)
{
subs.add(childErrors);
}
}
/*****************************************************************************
* Reports all errors in this group on the standard output.
*/
public String report(Source src)
{
StringBuilder builder = new StringBuilder();
builder.append("Error at input position ");
builder.append(position);
builder.append(" (");
builder.append(src.where(position));
builder.append(")");
// append an excerpt around the error site
int excerptStart = Math.max(0, position - 50);
int excerptStop = Math.min(position + 50, src.end());
builder.append("\n\n[start excerpt]\n");
builder.append(src.at(excerptStart, position));
builder.append("\n[the error is at the begin of next line]\n");
builder.append(src.at(position, excerptStop));
builder.append("\n[end excerpt]\n\n");
// print path
builder.append("in ");
trace(0, builder);
return builder.toString();
}
/*****************************************************************************
* Appends the whole path to the faulty expression(s) to $builder.
*/
private void trace(int indent, StringBuilder builder)
{
builder.append(expression);
if (subs.size() == 1) {
builder.append(" > ");
subs.get(0).trace(indent, builder);
}
else {
indent += 2;
for (ParseErrors e : subs) {
builder.append("\n");
builder.append(StringUtils.repeat(" ", indent));
builder.append(">> ");
e.trace(indent, builder);
}
}
}
// UNUSED CODE
//
// /*****************************************************************************
// * Indicates whether there was an error while parsing the expression.
// */
// public boolean erroneous()
// {
// return this.position != this.begin - 1;
// }
}