001/*
002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010package org.eclipse.golo.compiler.parser;
011
012import static gololang.Messages.message;
013
014/**
015 * This exception is thrown when parse errors are encountered.
016 * You can explicitly create objects of this exception type by
017 * calling the method generateParseException in the generated
018 * parser.
019 *
020 * You can modify this class to customize your error reporting
021 * mechanisms so long as you retain the public fields.
022 */
023public class ParseException extends Exception {
024
025  /**
026   * The version identifier for this Serializable class.
027   * Increment only if the <i>serialized</i> form of the
028   * class changes.
029   */
030  private static final long serialVersionUID = 1L;
031
032  /**
033   * The end of line string for this machine.
034   */
035  protected static String EOL = System.getProperty("line.separator", "\n");
036
037  /**
038   * This constructor is used by the method "generateParseException"
039   * in the generated parser.  Calling this constructor generates
040   * a new object of this type with the fields "currentToken",
041   * "expectedTokenSequences", and "tokenImage" set.
042   */
043  public ParseException(Token currentTokenVal,
044                        int[][] expectedTokenSequencesVal,
045                        String[] tokenImageVal
046  ) {
047    super(initialise(currentTokenVal, expectedTokenSequencesVal, tokenImageVal));
048    currentToken = currentTokenVal;
049    expectedTokenSequences = expectedTokenSequencesVal;
050    tokenImage = tokenImageVal;
051  }
052
053  /**
054   * The following constructors are for use by you for whatever
055   * purpose you can think of.  Constructing the exception in this
056   * manner makes the exception behave in the normal way - i.e., as
057   * documented in the class "Throwable".  The fields "errorToken",
058   * "expectedTokenSequences", and "tokenImage" do not contain
059   * relevant information.  The JavaCC generated code does not use
060   * these constructors.
061   */
062
063  public ParseException() {
064    super();
065  }
066
067  /**
068   * Constructor with message.
069   */
070  public ParseException(String message) {
071    super(message);
072  }
073
074
075  /**
076   * This is the last token that has been consumed successfully.  If
077   * this object has been created due to a parse error, the token
078   * followng this token will (therefore) be the first error token.
079   */
080  public Token currentToken;
081
082  /**
083   * Each entry in this array is an array of integers.  Each array
084   * of integers represents a sequence of tokens (by their ordinal
085   * values) that is expected at this point of the parse.
086   */
087  public int[][] expectedTokenSequences;
088
089  /**
090   * This is a reference to the "tokenImage" array of the generated
091   * parser within which the parse error occurred.  This array is
092   * defined in the generated ...Constants interface.
093   */
094  public String[] tokenImage;
095
096  /**
097   * It uses "currentToken" and "expectedTokenSequences" to generate a parse
098   * error message and returns it.  If this object has been created
099   * due to a parse error, and you do not catch it (it gets thrown
100   * from the parser) the correct error message
101   * gets displayed.
102   */
103  private static String initialise(Token currentToken,
104                                   int[][] expectedTokenSequences,
105                                   String[] tokenImage) {
106
107    StringBuffer expected = new StringBuffer();
108    int maxSize = 0;
109    for (int[] expectedTokenSequence : expectedTokenSequences) {
110      if (maxSize < expectedTokenSequence.length) {
111        maxSize = expectedTokenSequence.length;
112      }
113      for (int j = 0; j < expectedTokenSequence.length; j++) {
114        expected.append(tokenImage[expectedTokenSequence[j]]).append(' ');
115      }
116      if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0) {
117        expected.append("...");
118      }
119      expected.append(EOL).append("    ");
120    }
121    StringBuilder retval = new StringBuilder(message("unexpected_token"));
122    Token tok = currentToken.next;
123    for (int i = 0; i < maxSize; i++) {
124      if (i != 0) {
125        retval.append(" ");
126      }
127      if (tok.kind == 0) {
128        retval.append(tokenImage[0]);
129        break;
130      }
131      retval.append(" ").append(tokenImage[tok.kind]);
132      retval.append(" `").append(addEscapes(tok.image)).append("` ");
133      tok = tok.next;
134    }
135    retval.append(message("source_position", currentToken.next.beginLine, currentToken.next.beginColumn));
136
137    return retval.toString();
138  }
139
140  /**
141   * Used to convert raw characters to their escaped version
142   * when these raw version cannot be used as part of an ASCII
143   * string literal.
144   */
145  static String addEscapes(String str) {
146    StringBuffer retval = new StringBuffer();
147    char ch;
148    for (int i = 0; i < str.length(); i++) {
149      switch (str.charAt(i)) {
150        case '\b':
151          retval.append("\\b");
152          continue;
153        case '\t':
154          retval.append("\\t");
155          continue;
156        case '\n':
157          retval.append("\\n");
158          continue;
159        case '\f':
160          retval.append("\\f");
161          continue;
162        case '\r':
163          retval.append("\\r");
164          continue;
165        case '\"':
166          retval.append("\\\"");
167          continue;
168        case '\'':
169          retval.append("\\\'");
170          continue;
171        case '\\':
172          retval.append("\\\\");
173          continue;
174        default:
175          if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
176            String s = "0000" + Integer.toString(ch, 16);
177            retval.append("\\u" + s.substring(s.length() - 4, s.length()));
178          } else {
179            retval.append(ch);
180          }
181          continue;
182      }
183    }
184    return retval.toString();
185  }
186
187}