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 */
010
011package org.eclipse.golo.compiler;
012
013import org.eclipse.golo.compiler.parser.GoloASTNode;
014import org.eclipse.golo.compiler.parser.ParseException;
015import gololang.ir.GoloElement;
016
017import java.nio.charset.UnsupportedCharsetException;
018import java.util.LinkedList;
019import java.util.List;
020
021import static java.util.Collections.unmodifiableList;
022import static gololang.Messages.message;
023
024/**
025 * A Golo compilation exception that may also report a cause and several identified problems.
026 */
027public class GoloCompilationException extends RuntimeException {
028
029  /**
030   * A problem reported either while compiling the source code or processing the intermediate representation.
031   */
032  public static final class Problem {
033
034    /**
035     * The possible problem types.
036     */
037    public enum Type {
038      PARSING,
039      AUGMENT_FUNCTION_NO_ARGS,
040      UNDECLARED_REFERENCE,
041      ASSIGN_CONSTANT,
042      BREAK_OR_CONTINUE_OUTSIDE_LOOP,
043      REFERENCE_ALREADY_DECLARED_IN_BLOCK,
044      UNINITIALIZED_REFERENCE_ACCESS,
045      INVALID_ENCODING,
046      INCOMPLETE_NAMED_ARGUMENTS_USAGE,
047      AMBIGUOUS_DECLARATION
048    }
049
050    private final Type type;
051    private final PositionInSourceCode position;
052    private final String description;
053    private final Object source;
054    private final Throwable cause;
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    private Problem(Type type, PositionInSourceCode position, String description, Object source, Throwable cause) {
064      this.type = type;
065      this.position = position;
066      this.description = description;
067      this.source = source;
068      this.cause = cause;
069    }
070
071    /**
072     * @return the problem type.
073     */
074    public Type getType() {
075      return type;
076    }
077
078    /**
079     * @return the problem description.
080     */
081    public String getDescription() {
082      return description;
083    }
084
085    /**
086     * @return the position in the source code.
087     */
088    public PositionInSourceCode getPositionInSourceCode() {
089      return position;
090    }
091
092    /**
093     * @return the source of the problem.
094     */
095    public Object getSource() {
096      return source;
097    }
098
099    /**
100     * @return the cause exception
101     */
102    public Throwable getCause() {
103      return cause;
104    }
105
106    @Override
107    public String toString() {
108      return String.format("Problem{type=%s, description='%s', position=%s}", type, description, position);
109    }
110  }
111
112  /**
113   * An exception builder object allows preparing an exception by progressively adding problems.
114   */
115  public static class Builder {
116
117    private final GoloCompilationException exception;
118
119    /**
120     * Makes a builder to report problems in a source file.
121     *
122     * @param goloSourceFilename the source file name.
123     */
124    public Builder(String goloSourceFilename) {
125      exception = new GoloCompilationException(message("in_module", goloSourceFilename));
126      exception.setSourceCode(goloSourceFilename);
127    }
128
129    /**
130     * Report a problem to the exception being built.
131     *
132     * @param type        the problem type.
133     * @param source      the problem source.
134     * @param description the problem description.
135     * @return the same builder object.
136     */
137    public Builder report(Problem.Type type, GoloASTNode source, String description) {
138      exception.report(new Problem(type,
139            source != null ? source.getPositionInSourceCode() : null,
140            description,
141            source, null));
142      return this;
143    }
144
145    /**
146     * Report a problem to the exception being built.
147     *
148     * @param type        the problem type.
149     * @param source      the problem source.
150     * @param description the problem description.
151     * @return the same builder object.
152     */
153    public Builder report(Problem.Type type, GoloElement<?> source, String description) {
154      exception.report(new Problem(type,
155            source != null ? source.positionInSourceCode() : null,
156            description,
157            source, null));
158      return this;
159    }
160
161    /**
162     * Report a problem to the exception being built.
163     *
164     * @param type        the problem type.
165     * @param source      the problem source.
166     * @param description the problem description.
167     * @param cause       the exception that caused the problem
168     * @return the same builder object.
169     */
170    public Builder report(Problem.Type type, GoloElement<?> source, String description, Throwable cause) {
171      exception.report(new Problem(type,
172            source != null ? source.positionInSourceCode() : null,
173            description,
174            source, cause));
175      return this;
176    }
177
178
179
180    /**
181     * Report a parsing error problem to the exception being built.
182     *
183     * @param pe     the caught {@code ParseException}.
184     * @param source the node of the {@code ParseException}.
185     * @return the same builder object.
186     */
187    public Builder report(ParseException pe, GoloASTNode source) {
188      exception.report(new Problem(Problem.Type.PARSING,
189            PositionInSourceCode.of(pe.currentToken.beginLine, pe.currentToken.beginColumn, pe.currentToken.endLine, pe.currentToken.endColumn),
190            pe.getMessage(),
191            source, pe));
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(Problem.Type.INVALID_ENCODING, null, uce.getMessage(), null, 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}