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.ir.GoloModule; 013import org.eclipse.golo.compiler.parser.ASTCompilationUnit; 014import org.eclipse.golo.compiler.parser.GoloOffsetParser; 015import org.eclipse.golo.compiler.parser.GoloParser; 016import org.eclipse.golo.compiler.parser.ParseException; 017 018import java.io.*; 019import java.nio.charset.Charset; 020import java.nio.charset.UnsupportedCharsetException; 021import java.util.Collections; 022import java.util.List; 023import java.util.jar.JarOutputStream; 024import java.util.zip.ZipEntry; 025 026import static gololang.Messages.message; 027 028/** 029 * The Golo compiler. 030 * <p> 031 * Instances of this class may be reused to compile several sources. 032 * <p> 033 * Several methods are made public while they do not necessarily need so for the needs of the Golo compiler. 034 * Such deviations from a "good and clean" design are on-purpose, as this facilitates the implementation of 035 * Golo support in IDEs. 036 */ 037public class GoloCompiler { 038 039 private GoloParser parser; 040 private GoloCompilationException.Builder exceptionBuilder = null; 041 042 043 /** 044 * Initializes an ExceptionBuilder to collect errors instead of throwing immediately. 045 * This method is made public for the requirements of IDEs support. 046 * 047 * @param builder the exception builder to add problems into. 048 */ 049 public final void setExceptionBuilder(GoloCompilationException.Builder builder) { 050 exceptionBuilder = builder; 051 } 052 053 private GoloCompilationException.Builder getOrCreateExceptionBuilder(String goloSourceFile) { 054 if (exceptionBuilder == null) { 055 exceptionBuilder = new GoloCompilationException.Builder(goloSourceFile); 056 } 057 return exceptionBuilder; 058 } 059 060 public void resetExceptionBuilder() { 061 exceptionBuilder = null; 062 } 063 /** 064 * Initializes a parser from an input stream. This method is made public for the requirements of IDEs support. 065 * 066 * @param sourceCodeInputStream the source code input stream. 067 * @return the parser. 068 */ 069 public final GoloParser initParser(String goloSourceFilename, InputStream sourceCodeInputStream) throws GoloCompilationException { 070 try { 071 return initParser(new InputStreamReader(sourceCodeInputStream, Charset.forName("UTF-8"))); 072 } catch (UnsupportedCharsetException e) { 073 getOrCreateExceptionBuilder(goloSourceFilename).report(e).doThrow(); 074 return null; 075 } 076 } 077 078 /** 079 * Initializes a parser from a reader. This method is made public for the requirements of IDEs support. 080 * 081 * @param sourceReader the source code reader. 082 * @return the parser. 083 */ 084 public final GoloParser initParser(Reader sourceReader) { 085 if (parser == null) { 086 parser = createGoloParser(sourceReader); 087 } else { 088 parser.ReInit(sourceReader); 089 } 090 return parser; 091 } 092 093 /** 094 * Compiles a Golo source file from an input stream, and returns a collection of results. 095 * 096 * @param goloSourceFilename the source file name. 097 * @param sourceCodeInputStream the source code input stream. 098 * @return a list of compilation results. 099 * @throws GoloCompilationException if a problem occurs during any phase of the compilation work. 100 */ 101 public final List<CodeGenerationResult> compile(String goloSourceFilename, InputStream sourceCodeInputStream) throws GoloCompilationException { 102 resetExceptionBuilder(); 103 ASTCompilationUnit compilationUnit = parse(goloSourceFilename, initParser(goloSourceFilename, sourceCodeInputStream)); 104 GoloModule goloModule = check(compilationUnit); 105 JavaBytecodeGenerationGoloIrVisitor bytecodeGenerator = new JavaBytecodeGenerationGoloIrVisitor(); 106 return bytecodeGenerator.generateBytecode(goloModule, goloSourceFilename); 107 } 108 109 private void throwIfErrorEncountered() { 110 if (!getProblems().isEmpty()) { 111 exceptionBuilder.doThrow(); 112 } 113 } 114 115 /** 116 * Returns the list of problems encountered during the last compilation 117 * 118 * @return a list of compilation problems. 119 */ 120 public List<GoloCompilationException.Problem> getProblems() { 121 if (exceptionBuilder == null) { 122 return Collections.emptyList(); 123 } 124 return exceptionBuilder.getProblems(); 125 } 126 127 /** 128 * Compiles a Golo source file and writes the resulting JVM bytecode {@code .class} files to a target 129 * folder. The class files are written in a directory structure that respects package names. 130 * 131 * @param goloSourceFilename the source file name. 132 * @param sourceCodeInputStream the source code input stream. 133 * @param targetFolder the output target folder. 134 * @throws GoloCompilationException if a problem occurs during any phase of the compilation work. 135 * @throws IOException if writing the {@code .class} files fails for some reason. 136 */ 137 public final void compileTo(String goloSourceFilename, InputStream sourceCodeInputStream, File targetFolder) throws GoloCompilationException, IOException { 138 if (targetFolder.isFile()) { 139 throw new IllegalArgumentException(message("file_exists", targetFolder)); 140 } 141 List<CodeGenerationResult> results = compile(goloSourceFilename, sourceCodeInputStream); 142 for (CodeGenerationResult result : results) { 143 File outputFolder = new File(targetFolder, result.getPackageAndClass().packageName().replaceAll("\\.", "/")); 144 if (!outputFolder.exists() && !outputFolder.mkdirs()) { 145 throw new IOException(message("directory_not_created", outputFolder)); 146 } 147 File outputFile = new File(outputFolder, result.getPackageAndClass().className() + ".class"); 148 try (FileOutputStream out = new FileOutputStream(outputFile)) { 149 out.write(result.getBytecode()); 150 } 151 } 152 } 153 154 /** 155 * Compiles a Golo source fila and writes the resulting JVM bytecode {@code .class} files to a Jar file stream. 156 * The class files are written in a directory structure that respects package names. 157 * 158 * @param goloSourceFilename the source file name. 159 * @param sourceCodeInputStream the source code input stream. 160 * @param jarOutputStream the output Jar stream 161 * @throws IOException if writing the {@code .class} files fails for some reason. 162 */ 163 public final void compileToJar(String goloSourceFilename, InputStream sourceCodeInputStream, JarOutputStream jarOutputStream) throws IOException { 164 List<CodeGenerationResult> results = compile(goloSourceFilename, sourceCodeInputStream); 165 for (CodeGenerationResult result : results) { 166 String entryName = result.getPackageAndClass().packageName().replaceAll("\\.", "/"); 167 if (!entryName.isEmpty()) { 168 entryName = entryName + "/"; 169 } 170 entryName = entryName + result.getPackageAndClass().className() + ".class"; 171 jarOutputStream.putNextEntry(new ZipEntry(entryName)); 172 jarOutputStream.write(result.getBytecode()); 173 jarOutputStream.closeEntry(); 174 } 175 } 176 177 /** 178 * Produces a parse tree for a Golo source file. This is mostly useful to IDEs. 179 * 180 * @param goloSourceFilename the source file name. 181 * @param parser the parser to use. 182 * @return the resulting parse tree. 183 * @throws GoloCompilationException if the parser encounters an error. 184 */ 185 public final ASTCompilationUnit parse(String goloSourceFilename, GoloParser parser) throws GoloCompilationException { 186 ASTCompilationUnit compilationUnit = null; 187 parser.exceptionBuilder = getOrCreateExceptionBuilder(goloSourceFilename); 188 try { 189 compilationUnit = parser.CompilationUnit(); 190 } catch (ParseException pe) { 191 exceptionBuilder.report(pe, compilationUnit); 192 } 193 throwIfErrorEncountered(); 194 return compilationUnit; 195 } 196 197 public final ASTCompilationUnit parse(String goloSourceFilename) throws GoloCompilationException, IOException { 198 try (FileInputStream in = new FileInputStream(goloSourceFilename)) { 199 return parse(goloSourceFilename, initParser(goloSourceFilename, in)); 200 } 201 } 202 203 /** 204 * Checks that the source code is minimally sound by converting a parse tree to an intermediate representation, and 205 * running a few classic visitors over it. This is mostly useful to IDEs. 206 * 207 * @param compilationUnit the source parse tree. 208 * @return the intermediate representation of the source. 209 * @throws GoloCompilationException if an error exists in the source represented by the input parse tree. 210 */ 211 public final GoloModule check(ASTCompilationUnit compilationUnit) { 212 GoloModule goloModule = transform(compilationUnit); 213 return refine(goloModule); 214 } 215 216 public final GoloModule transform(ASTCompilationUnit compilationUnit) { 217 return new ParseTreeToGoloIrVisitor().transform(compilationUnit, exceptionBuilder); 218 } 219 220 public final GoloModule refine(GoloModule goloModule) { 221 if (goloModule != null) { 222 goloModule.accept(new SugarExpansionVisitor()); 223 goloModule.accept(new ClosureCaptureGoloIrVisitor()); 224 goloModule.accept(new LocalReferenceAssignmentAndVerificationVisitor(exceptionBuilder)); 225 } 226 throwIfErrorEncountered(); 227 return goloModule; 228 } 229 230 231 /** 232 * Makes a Golo parser from a reader. 233 * 234 * @param sourceReader the reader. 235 * @return the parser for <code>sourceReader</code>. 236 */ 237 protected GoloParser createGoloParser(Reader sourceReader) { 238 return new GoloOffsetParser(sourceReader); 239 } 240}