001/*
002 * Copyright (c) 2012-2021 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      UNKNOWN_MACRO,
049      MACRO_EXPANSION
050    }
051
052    private final Type type;
053    private final PositionInSourceCode position;
054    private final String description;
055    private final Object source;
056    private final Throwable cause;
057
058    /**
059     * Constructs a new problem to report.
060     *
061     * @param type        the problem type.
062     * @param source      the problem source, which may be of any meaningful type.
063     * @param description the problem description in a human-readable form.
064     */
065    private Problem(Type type, PositionInSourceCode position, String description, Object source, Throwable cause) {
066      this.type = type;
067      this.position = position;
068      this.description = description;
069      this.source = source;
070      this.cause = cause;
071    }
072
073    /**
074     * @return the problem type.
075     */
076    public Type getType() {
077      return type;
078    }
079
080    /**
081     * @return the problem description.
082     */
083    public String getDescription() {
084      return description;
085    }
086
087    /**
088     * @return the position in the source code.
089     */
090    public PositionInSourceCode getPositionInSourceCode() {
091      return position;
092    }
093
094    /**
095     * @return the source of the problem.
096     */
097    public Object getSource() {
098      return source;
099    }
100
101    /**
102     * @return the cause exception
103     */
104    public Throwable getCause() {
105      return cause;
106    }
107
108    @Override
109    public String toString() {
110      return String.format("Problem{type=%s, description='%s', position=%s}", type, description, position);
111    }
112  }
113
114  /**
115   * An exception builder object allows preparing an exception by progressively adding problems.
116   */
117  public static class Builder {
118
119    private final GoloCompilationException exception;
120
121    /**
122     * Makes a builder to report problems in a source file.
123     *
124     * @param goloSourceFilename the source file name.
125     */
126    public Builder(String goloSourceFilename) {
127      exception = new GoloCompilationException(message("in_module", goloSourceFilename));
128      exception.setSourceCode(goloSourceFilename);
129    }
130
131    /**
132     * Report a problem to the exception being built.
133     *
134     * @param type        the problem type.
135     * @param source      the problem source.
136     * @param description the problem description.
137     * @return the same builder object.
138     */
139    public Builder report(Problem.Type type, GoloASTNode source, String description) {
140      exception.report(new Problem(type,
141            source != null ? source.getPositionInSourceCode() : null,
142            description,
143            source, null));
144      return this;
145    }
146
147    /**
148     * Report a problem to the exception being built.
149     *
150     * @param type        the problem type.
151     * @param source      the problem source.
152     * @param description the problem description.
153     * @return the same builder object.
154     */
155    public Builder report(Problem.Type type, GoloElement<?> source, String description) {
156      exception.report(new Problem(type,
157            source != null ? source.positionInSourceCode() : null,
158            description,
159            source, null));
160      return this;
161    }
162
163    /**
164     * Report a problem to the exception being built.
165     *
166     * @param type        the problem type.
167     * @param source      the problem source.
168     * @param description the problem description.
169     * @param cause       the exception that caused the problem
170     * @return the same builder object.
171     */
172    public Builder report(Problem.Type type, GoloElement<?> source, String description, Throwable cause) {
173      exception.report(new Problem(type,
174            source != null ? source.positionInSourceCode() : null,
175            description,
176            source, cause));
177      return this;
178    }
179
180
181
182    /**
183     * Report a parsing error problem to the exception being built.
184     *
185     * @param pe     the caught {@code ParseException}.
186     * @param source the node of the {@code ParseException}.
187     * @return the same builder object.
188     */
189    public Builder report(ParseException pe, GoloASTNode source) {
190      exception.report(new Problem(Problem.Type.PARSING,
191            PositionInSourceCode.of(pe.currentToken.beginLine, pe.currentToken.beginColumn, pe.currentToken.endLine, pe.currentToken.endColumn),
192            pe.getMessage(),
193            source, pe));
194      return this;
195    }
196
197    /**
198     * Report an encoding error problem to the exception being built.
199     *
200     * @param uce     the caught {@code UnsupportedCharsetException}.
201     * @return the same builder object.
202     */
203    public Builder report(UnsupportedCharsetException uce) {
204      exception.report(new Problem(Problem.Type.INVALID_ENCODING, null, uce.getMessage(), null, uce));
205      return this;
206    }
207
208    /**
209     * Stops adding problems and throws the exception,
210     *
211     * @throws GoloCompilationException everytime.
212     */
213    public void doThrow() throws GoloCompilationException {
214      throw exception;
215    }
216
217    public List<Problem> getProblems() {
218      return exception.getProblems();
219    }
220  }
221
222  private final List<Problem> problems = new LinkedList<>();
223
224  private String sourceCode;
225
226  /**
227   * @return all reported problems.
228   */
229  public List<Problem> getProblems() {
230    return unmodifiableList(problems);
231  }
232
233  private void report(Problem problem) {
234    problems.add(problem);
235  }
236
237  private GoloCompilationException() {
238    super();
239  }
240
241  /**
242   * Gives the problematic source code, if specified.
243   *
244   * @return the source code, or {@code null} if none has been specified.
245   */
246  public String getSourceCode() {
247    return sourceCode;
248  }
249
250  /**
251   * Specifies the problematic source code.
252   *
253   * @param sourceCode the raw source code.
254   */
255  public void setSourceCode(String sourceCode) {
256    this.sourceCode = sourceCode;
257  }
258
259  /**
260   * Makes a new compiler exception with a message.
261   *
262   * @param message the message.
263   */
264  public GoloCompilationException(String message) {
265    super(message);
266  }
267
268  /**
269   * Makes a new compiler exception from a root cause.
270   *
271   * @param throwable the cause.
272   */
273  public GoloCompilationException(Throwable throwable) {
274    super(throwable);
275  }
276
277  /**
278   * Makes a new exception from a message and a root cause.
279   *
280   * @param message the message.
281   * @param cause   the cause.
282   */
283  public GoloCompilationException(String message, Throwable cause) {
284    super(message, cause);
285  }
286}