/* * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.template.soy.exprparse; import static com.google.template.soy.base.internal.BaseUtils.formatParseExceptionDetails; import com.google.common.collect.ImmutableSet; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.error.SoyErrorKind; /** Helpers for interpreting parse errors as soy errors. */ final class ParseErrors { private ParseErrors() {} static void reportExprParseException( ErrorReporter reporter, SourceLocation parentSourceLocation, ParseException e) { // currentToken is the 'last successfully consumed token', but the error is usually due to the // first unsuccessful token. use that for the source location Token errorToken = e.currentToken; if (errorToken.next != null) { errorToken = errorToken.next; } SourceLocation errorLocation = Tokens.createSrcLoc(parentSourceLocation, errorToken); // handle a few special cases that come up in v1->v2 migrations. We have no production rules // that reference these tokens so they will always be unexpected. Another option we could take // is add production rules/matchers for these in the expected places (e.q. allow '&&' in the // same place we match 'and'), then we could report errors and keep going in the parser. switch (errorToken.kind) { case ExpressionParserConstants.LEGACY_AND: reporter.report(errorLocation, V1ExpressionErrors.LEGACY_AND_ERROR); return; case ExpressionParserConstants.LEGACY_OR: reporter.report(errorLocation, V1ExpressionErrors.LEGACY_OR_ERROR); return; case ExpressionParserConstants.LEGACY_NOT: reporter.report(errorLocation, V1ExpressionErrors.LEGACY_NOT_ERROR); return; case ExpressionParserConstants.DOUBLE_QUOTE: reporter.report(errorLocation, V1ExpressionErrors.LEGACY_DOUBLE_QUOTED_STRING); return; default: break; } // otherwise log a generic unexpected token error message ImmutableSet.Builder<String> expectedTokenImages = ImmutableSet.builder(); for (int[] expected : e.expectedTokenSequences) { // We only display the first token expectedTokenImages.add(getSoyFileParserTokenDisplayName(expected[0])); } reporter.report( errorLocation, SoyErrorKind.of("{0}"), formatParseExceptionDetails(errorToken.image, expectedTokenImages.build().asList())); } /** * Returns a human friendly display name for tokens. By default we use the generated token image * which is appropriate for literal tokens. */ private static String getSoyFileParserTokenDisplayName(int tokenId) { switch (tokenId) { case ExpressionParserConstants.EOF: return "eof"; case ExpressionParserConstants.HEX_INTEGER: case ExpressionParserConstants.DEC_INTEGER: case ExpressionParserConstants.FLOAT: return "number"; case ExpressionParserConstants.STRING: return "string"; case ExpressionParserConstants.IDENT: return "an identifier"; case ExpressionParserConstants.DOLLAR_IDENT: return "variable"; case ExpressionParserConstants.UNEXPECTED_TOKEN: throw new AssertionError("we should never expect the unexpected token"); default: return ExpressionParserConstants.tokenImage[tokenId]; } } }