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 gololang.ir.GoloModule;
014import org.eclipse.golo.compiler.macro.MacroExpansionIrVisitor;
015import org.eclipse.golo.compiler.parser.ASTCompilationUnit;
016import org.eclipse.golo.compiler.parser.GoloOffsetParser;
017import org.eclipse.golo.compiler.parser.GoloParser;
018import org.eclipse.golo.compiler.parser.ParseException;
019
020import java.io.*;
021import java.util.Collections;
022import java.util.List;
023
024
025/**
026 * The Golo compiler.
027 * <p>
028 * Instances of this class may be reused to compile several sources.
029 * <p>
030 * Several methods are made public while they do not necessarily need so for the needs of the Golo compiler.
031 * Such deviations from a "good and clean" design are on-purpose, as this facilitates the implementation of
032 * Golo support in IDEs.
033 * <p>
034 *
035 */
036public final class GoloCompiler {
037
038  private GoloParser parser;
039  private GoloCompilationException.Builder exceptionBuilder = null;
040  private final ClassLoader classloader;
041
042  public GoloCompiler() {
043    this(Thread.currentThread().getContextClassLoader());
044  }
045
046  public GoloCompiler(ClassLoader loader) {
047    this.classloader = loader;
048  }
049
050  /**
051   * Initializes an ExceptionBuilder to collect errors instead of throwing immediately.
052   * This method is made public for the requirements of IDEs support.
053   *
054   * @param builder the exception builder to add problems into.
055   */
056  public void setExceptionBuilder(GoloCompilationException.Builder builder) {
057    exceptionBuilder = builder;
058  }
059
060  private GoloCompilationException.Builder getOrCreateExceptionBuilder(String goloSourceFile) {
061    if (exceptionBuilder == null) {
062      exceptionBuilder = new GoloCompilationException.Builder(goloSourceFile);
063    }
064    return exceptionBuilder;
065  }
066
067  public void resetExceptionBuilder() {
068    exceptionBuilder = null;
069  }
070
071  /**
072   * Initializes a parser from a reader. This method is made public for the requirements of IDEs support.
073   *
074   * @param sourceReader the source code reader.
075   * @return the parser.
076   */
077  public GoloParser initParser(Reader sourceReader) {
078    if (this.parser == null) {
079      this.parser = createGoloParser(sourceReader);
080    } else {
081      this.parser.ReInit(sourceReader);
082    }
083    return this.parser;
084  }
085
086  /**
087   * Compiles a Golo source file from an input stream, and returns a collection of results.
088   *
089   * @param goloSourceFilename    the source file name.
090   * @param sourceCode the source code reader.
091   * @return a list of compilation results.
092   * @throws GoloCompilationException if a problem occurs during any phase of the compilation work.
093   */
094  public List<CodeGenerationResult> compile(String goloSourceFilename, Reader sourceCode) throws GoloCompilationException {
095    return generate(check(parse(goloSourceFilename, initParser(sourceCode))));
096  }
097
098  public List<CodeGenerationResult> compile(File src) throws IOException {
099    return generate(check(parse(src)));
100  }
101
102  private void throwIfErrorEncountered() {
103    if (!getProblems().isEmpty()) {
104      exceptionBuilder.doThrow();
105    }
106  }
107
108  /**
109   * Returns the list of problems encountered during the last compilation.
110   *
111   * @return a list of compilation problems.
112   */
113  public List<GoloCompilationException.Problem> getProblems() {
114    if (exceptionBuilder == null) {
115      return Collections.emptyList();
116    }
117    return exceptionBuilder.getProblems();
118  }
119
120  /**
121   * Produces a parse tree for a Golo source file. This is mostly useful to IDEs.
122   *
123   * @param goloSourceFilename the source file name.
124   * @param parser             the parser to use.
125   * @return the resulting parse tree.
126   * @throws GoloCompilationException if the parser encounters an error.
127   */
128  public ASTCompilationUnit parse(String goloSourceFilename, GoloParser parser) throws GoloCompilationException {
129    resetExceptionBuilder();
130    ASTCompilationUnit compilationUnit = null;
131    parser.exceptionBuilder = getOrCreateExceptionBuilder(goloSourceFilename);
132    try {
133      compilationUnit = parser.CompilationUnit();
134      compilationUnit.setFilename(goloSourceFilename);
135    } catch (ParseException pe) {
136      exceptionBuilder.report(pe, compilationUnit);
137    }
138    throwIfErrorEncountered();
139    return compilationUnit;
140  }
141
142  public ASTCompilationUnit parse(File goloSourceFile) throws GoloCompilationException, IOException {
143    try (Reader in = new FileReader(goloSourceFile)) {
144      return parse(goloSourceFile.getPath(), initParser(in));
145    }
146  }
147  /**
148   * Checks that the source code is minimally sound by converting a parse tree to an intermediate representation, and
149   * running a few classic visitors over it. This is mostly useful to IDEs.
150   *
151   * @param compilationUnit the source parse tree.
152   * @return the intermediate representation of the source.
153   * @throws GoloCompilationException if an error exists in the source represented by the input parse tree.
154   */
155  public GoloModule check(ASTCompilationUnit compilationUnit) {
156    return refine(expand(transform(compilationUnit)));
157  }
158
159  public List<CodeGenerationResult> generate(GoloModule goloModule) {
160    if (goloModule.isEmpty()) {
161      return Collections.emptyList();
162    }
163    JavaBytecodeGenerationGoloIrVisitor bytecodeGenerator = new JavaBytecodeGenerationGoloIrVisitor();
164    return bytecodeGenerator.generateBytecode(goloModule);
165  }
166
167  public GoloModule transform(ASTCompilationUnit compilationUnit) {
168    GoloModule mod = new ParseTreeToGoloIrVisitor().transform(compilationUnit, exceptionBuilder);
169    throwIfErrorEncountered();
170    return mod;
171  }
172
173  public GoloModule expand(GoloModule goloModule, boolean recurse) {
174    resetExceptionBuilder();
175    goloModule.accept(new MacroExpansionIrVisitor(classloader, recurse, getOrCreateExceptionBuilder(goloModule.sourceFile())));
176    throwIfErrorEncountered();
177    return goloModule;
178  }
179
180  public GoloModule expand(GoloModule goloModule) {
181    return expand(goloModule, true);
182  }
183
184  public GoloModule expandOnce(GoloModule goloModule) {
185    return expand(goloModule, false);
186  }
187
188  public GoloModule refine(GoloModule goloModule) {
189    if (goloModule != null) {
190      goloModule.accept(new SugarExpansionVisitor());
191      goloModule.accept(new ClosureCaptureGoloIrVisitor());
192      goloModule.accept(new LocalReferenceAssignmentAndVerificationVisitor(getOrCreateExceptionBuilder(goloModule.sourceFile())));
193    }
194    throwIfErrorEncountered();
195    return goloModule;
196  }
197
198  /**
199   * Makes a Golo parser from a reader.
200   *
201   * @param sourceReader the reader.
202   * @return the parser for <code>sourceReader</code>.
203   */
204  private GoloParser createGoloParser(Reader sourceReader) {
205    return new GoloOffsetParser(sourceReader);
206  }
207}