001/*
002 * Copyright (c) 2012-2017 Institut National des Sciences Appliquées de Lyon (INSA-Lyon)
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 */
009
010package org.eclipse.golo.compiler;
011
012import org.eclipse.golo.compiler.parser.GoloASTNode;
013import org.eclipse.golo.compiler.parser.ParseException;
014import org.eclipse.golo.compiler.parser.Token;
015
016import java.nio.charset.UnsupportedCharsetException;
017import java.util.LinkedList;
018import java.util.List;
019
020import static java.util.Collections.unmodifiableList;
021import static gololang.Messages.message;
022
023/**
024 * A Golo compilation exception that may also report a cause and several identified problems.
025 */
026public class GoloCompilationException extends RuntimeException {
027
028  /**
029   * A problem reported either while compiling the source code or processing the intermediate representation.
030   */
031  public static class Problem {
032
033    /**
034     * The possible problem types.
035     */
036    public static enum Type {
037      PARSING,
038      AUGMENT_FUNCTION_NO_ARGS,
039      UNDECLARED_REFERENCE,
040      ASSIGN_CONSTANT,
041      BREAK_OR_CONTINUE_OUTSIDE_LOOP,
042      REFERENCE_ALREADY_DECLARED_IN_BLOCK,
043      UNINITIALIZED_REFERENCE_ACCESS,
044      INVALID_ENCODING,
045      INCOMPLETE_NAMED_ARGUMENTS_USAGE,
046      AMBIGUOUS_DECLARATION
047    }
048
049    private final Type type;
050    private final GoloASTNode source;
051    private final Token firstToken;
052    private final Token lastToken;
053
054    private final String description;
055
056    /**
057     * Constructs a new problem to report.
058     *
059     * @param type        the problem type.
060     * @param source      the problem source, which may be of any meaningful type.
061     * @param description the problem description in a human-readable form.
062     */
063    public Problem(Type type, GoloASTNode source, String description) {
064      this.type = type;
065      this.description = description;
066      this.source = source;
067      if (source != null) {
068        this.firstToken = source.jjtGetFirstToken();
069        this.lastToken = source.jjtGetLastToken();
070      } else {
071        this.firstToken = null;
072        this.lastToken = null;
073      }
074    }
075
076    /**
077     * Constructs a new problem to report.
078     *
079     * @param type        the problem type.
080     * @param source      the problem source, which may be of any meaningful type.
081     * @param token       the precise source token, where the problem is located.
082     * @param description the problem description in a human-readable form.
083     */
084    public Problem(Type type, GoloASTNode source, Token token, String description) {
085      this.type = type;
086      this.source = source;
087      this.firstToken = token;
088      this.lastToken = token;
089      this.description = description;
090    }
091
092    public Problem(ParseException pe, GoloASTNode source) {
093      this.type = Type.PARSING;
094      this.source = source;
095      this.firstToken = pe.currentToken;
096      this.lastToken = pe.currentToken;
097      this.description = pe.getMessage();
098    }
099
100    public Problem(UnsupportedCharsetException uce) {
101      this.type = Type.INVALID_ENCODING;
102      this.source = null;
103      this.firstToken = null;
104      this.lastToken = null;
105      this.description = uce.getMessage();
106    }
107
108    /**
109     * @return the problem type.
110     */
111    public Type getType() {
112      return type;
113    }
114
115    /**
116     * @return the problem source.
117     */
118    public GoloASTNode getSource() {
119      return source;
120    }
121
122    /**
123     * @return the problem detailed start token in source.
124     */
125    public Token getFirstToken() {
126      return firstToken;
127    }
128
129    /**
130     * @return the problem detailed end token in source.
131     */
132    public Token getLastToken() {
133      return lastToken;
134    }
135
136    /**
137     * @return the problem description.
138     */
139    public String getDescription() {
140      return description;
141    }
142
143    @Override
144    public String toString() {
145      return "Problem{" +
146          "type=" + type +
147          ", source=" + source +
148          ", description='" + description + '\'' +
149          '}';
150    }
151  }
152
153  /**
154   * An exception builder object allows preparing an exception by progressively adding problems.
155   */
156  public static class Builder {
157
158    private final GoloCompilationException exception;
159
160    /**
161     * Makes a builder to report problems in a source file.
162     *
163     * @param goloSourceFilename the source file name.
164     */
165    public Builder(String goloSourceFilename) {
166      exception = new GoloCompilationException(message("in_module", goloSourceFilename));
167      exception.setSourceCode(goloSourceFilename);
168    }
169
170    /**
171     * Report a problem to the exception being built.
172     *
173     * @param type        the problem type.
174     * @param source      the problem source.
175     * @param description the problem description.
176     * @return the same builder object.
177     */
178    public Builder report(Problem.Type type, GoloASTNode source, String description) {
179      exception.report(new Problem(type, source, description));
180      return this;
181    }
182
183    /**
184     * Report a parsing error problem to the exception being built.
185     *
186     * @param pe     the caught {@code ParseException}.
187     * @param source the node of the {@code ParseException}.
188     * @return the same builder object.
189     */
190    public Builder report(ParseException pe, GoloASTNode source) {
191      exception.report(new Problem(pe, source));
192      return this;
193    }
194
195    /**
196     * Report an encoding error problem to the exception being built.
197     *
198     * @param uce     the caught {@code UnsupportedCharsetException}.
199     * @return the same builder object.
200     */
201    public Builder report(UnsupportedCharsetException uce) {
202      exception.report(new Problem(uce));
203      return this;
204    }
205
206    /**
207     * Stops adding problems and throws the exception,
208     *
209     * @throws GoloCompilationException everytime.
210     */
211    public void doThrow() throws GoloCompilationException {
212      throw exception;
213    }
214
215    public List<Problem> getProblems() {
216      return exception.getProblems();
217    }
218  }
219
220  private final List<Problem> problems = new LinkedList<>();
221
222  private String sourceCode;
223
224  /**
225   * @return all reported problems.
226   */
227  public List<Problem> getProblems() {
228    return unmodifiableList(problems);
229  }
230
231  private void report(Problem problem) {
232    problems.add(problem);
233  }
234
235  private GoloCompilationException() {
236    super();
237  }
238
239  /**
240   * Gives the problematic source code, if specified.
241   *
242   * @return the source code, or {@code null} if none has been specified.
243   */
244  public String getSourceCode() {
245    return sourceCode;
246  }
247
248  /**
249   * Specifies the problematic source code.
250   *
251   * @param sourceCode the raw source code.
252   */
253  public void setSourceCode(String sourceCode) {
254    this.sourceCode = sourceCode;
255  }
256
257  /**
258   * Makes a new compiler exception with a message.
259   *
260   * @param message the message.
261   */
262  public GoloCompilationException(String message) {
263    super(message);
264  }
265
266  /**
267   * Makes a new compiler exception from a root cause.
268   *
269   * @param throwable the cause.
270   */
271  public GoloCompilationException(Throwable throwable) {
272    super(throwable);
273  }
274
275  /**
276   * Makes a new exception from a message and a root cause.
277   *
278   * @param message the message.
279   * @param cause   the cause.
280   */
281  public GoloCompilationException(String message, Throwable cause) {
282    super(message, cause);
283  }
284}