Java tutorial
/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.jasm16.compiler; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.NoSuchElementException; import java.util.Stack; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.ast.ASTUtils; import de.codesourcery.jasm16.compiler.ICompiler.CompilerOption; import de.codesourcery.jasm16.compiler.io.AbstractObjectCodeWriter; import de.codesourcery.jasm16.compiler.io.AbstractObjectCodeWriterFactory; import de.codesourcery.jasm16.compiler.io.FileObjectCodeWriter; import de.codesourcery.jasm16.compiler.io.FileResource; import de.codesourcery.jasm16.compiler.io.IObjectCodeWriter; import de.codesourcery.jasm16.compiler.io.IObjectCodeWriterFactory; import de.codesourcery.jasm16.compiler.io.IResource; import de.codesourcery.jasm16.compiler.io.SimpleFileObjectCodeWriterFactory; import de.codesourcery.jasm16.compiler.phases.VerboseCodeGenerationPhase; import de.codesourcery.jasm16.utils.DebugCompilationListener; import de.codesourcery.jasm16.utils.FormattingVisitor; import de.codesourcery.jasm16.utils.Misc; /** * Class to invoke the compiler from the command-line. * * <p>Run this class without options (or with '--help') to see the available command-line options.</p> * @author tobias.gierke@code-sourcery.de */ public class Main { private final Compiler compiler = new Compiler(); private ByteArrayOutputStream generatedObjectCode = new ByteArrayOutputStream(); private File outputFile; /* * Options. */ private boolean printSymbolTable = false; private boolean printStackTraces = false; private boolean printDebugStats = false; private boolean verboseOutput = false; private boolean dumpObjectCode = false; private boolean printSourceCode = false; private boolean relaxedParsing = false; private boolean relaxedValidation = false; private boolean disableLiteralInlining = false; private boolean enableLocalLabelSupport = false; public static void main(String[] args) throws Exception { try { System.exit(new Main().run(args)); } catch (Exception e) { System.out.println("\n\nERROR: " + e.getMessage() + "\n"); e.printStackTrace(); System.exit(1); } } private int run(String[] args) throws Exception { final List<ICompilationUnit> units = new ArrayList<ICompilationUnit>(); final Stack<String> arguments = new Stack<String>(); for (String arg : args) { arguments.push(arg); } Collections.reverse(arguments); while (!arguments.isEmpty()) { final String arg = arguments.peek(); if (arg.startsWith("-") || arg.startsWith("--")) { try { handleCommandlineOption(arg, arguments); } catch (NoSuchElementException e) { printError("Invalid command line, option " + arg + " lacks argument."); return 1; } } else { units.add(createCompilationUnit(arguments.pop())); } } if (verboseOutput) { printVersionInfo(); } if (units.isEmpty()) { printError("No input files."); return 1; } setupCompiler(units); final ICompilationListener listener; if (printDebugStats || verboseOutput) { listener = new DebugCompilationListener(printDebugStats); } else { listener = new CompilationListener(); } if (printSourceCode) { compiler.insertCompilerPhaseAfter(new CompilerPhase("format-code") { @Override protected void run(ICompilationUnit unit, ICompilationContext context) throws IOException { if (unit.getAST() != null) { ASTUtils.visitInOrder(unit.getAST(), new FormattingVisitor(context)); } }; }, ICompilerPhase.PHASE_GENERATE_CODE); } // invoke compiler compiler.compile(units, listener); boolean hasErrors = false; for (ICompilationUnit unit : units) { if (unit.hasErrors()) { Misc.printCompilationErrors(unit, Misc.readSource(unit), printStackTraces); hasErrors = true; } } if (dumpObjectCode) { dumpObjectCode(); } return hasErrors ? 1 : 0; } private void printVersionInfo() { System.out.println(Compiler.VERSION + "\n(c) 2012 by tobias.gierke@code-sourcery.de\n"); } private void dumpObjectCode() { final byte[] combined = this.generatedObjectCode.toByteArray(); if (ArrayUtils.isEmpty(combined)) { System.out.println("No object code generated."); return; } System.out.println("\nHex dump:\n\n" + Misc.toHexDumpWithAddresses(Address.byteAddress(0), combined, 8)); } private void setupCompiler(List<ICompilationUnit> units) { if (printSymbolTable) { compiler.insertCompilerPhaseAfter(new PrintSymbolTablePhase(), ICompilerPhase.PHASE_GENERATE_CODE); } setObjectCodeWriterFactory(units); if (verboseOutput) { compiler.replaceCompilerPhase(new VerboseCodeGenerationPhase(), ICompilerPhase.PHASE_GENERATE_CODE); } if (enableLocalLabelSupport) { compiler.setCompilerOption(CompilerOption.LOCAL_LABELS_SUPPORTED, true); } if (printStackTraces) { compiler.setCompilerOption(CompilerOption.DEBUG_MODE, true); } if (relaxedParsing) { compiler.setCompilerOption(CompilerOption.RELAXED_PARSING, true); } if (relaxedValidation) { compiler.setCompilerOption(CompilerOption.RELAXED_VALIDATION, true); } if (disableLiteralInlining) { compiler.setCompilerOption(CompilerOption.DISABLE_INLINING, true); } } private void setObjectCodeWriterFactory(List<ICompilationUnit> units) { final IObjectCodeWriterFactory factory; if (dumpObjectCode) { factory = new AbstractObjectCodeWriterFactory() { @Override protected void deleteOutputHook() throws IOException { generatedObjectCode = new ByteArrayOutputStream(); } @Override protected IObjectCodeWriter createObjectCodeWriter(ICompilationContext context) { return new AbstractObjectCodeWriter() { @Override protected void deleteOutputHook() throws IOException { generatedObjectCode = new ByteArrayOutputStream(); } @Override protected OutputStream createOutputStream() throws IOException { return generatedObjectCode; } @Override protected void closeHook() throws IOException { } }; } }; } else if (outputFile != null) { outputFile.delete(); final boolean append = units.size() > 1; factory = new SimpleFileObjectCodeWriterFactory(outputFile, append); } else { // no output file given, just dump source.dasm16 into source.o factory = new SimpleFileObjectCodeWriterFactory() { @Override protected IObjectCodeWriter createObjectCodeWriter(ICompilationContext context) { final IResource resource = context.getCurrentCompilationUnit().getResource(); if (!(resource instanceof FileResource)) { throw new RuntimeException("Internal error, not a file resoure: " + resource); } final FileResource fileResource = (FileResource) resource; return new FileObjectCodeWriter(new File(toObjectFileName(fileResource.getFile())), false); } private String toObjectFileName(File sourceFile) { final List<String> nameComponents = Arrays.asList(sourceFile.getName().split("\\.")); if (nameComponents.size() == 1) { return nameComponents.get(0) + ".dcpu16"; } final String nameWithoutSuffix = StringUtils .join(nameComponents.subList(0, nameComponents.size() - 1), ""); return nameWithoutSuffix + ".dcpu16"; } }; } compiler.setObjectCodeWriterFactory(factory); } private void handleCommandlineOption(String option, Stack<String> arguments) { if ("-d".equalsIgnoreCase(option) || "--debug".equalsIgnoreCase(option)) { this.printStackTraces = true; this.printDebugStats = true; this.verboseOutput = true; arguments.pop(); } else if ("--local-labels".equalsIgnoreCase(option)) { this.enableLocalLabelSupport = true; arguments.pop(); } else if ("--relaxed-validation".equalsIgnoreCase(option)) { this.relaxedValidation = true; arguments.pop(); } else if ("--disable-literal-inlining".equalsIgnoreCase(option)) { this.disableLiteralInlining = true; arguments.pop(); } else if ("--relaxed-parsing".equalsIgnoreCase(option)) { this.relaxedParsing = true; arguments.pop(); } else if ("--print".equalsIgnoreCase(option)) { this.printSourceCode = true; arguments.pop(); } else if ("--print-symbols".equalsIgnoreCase(option)) { this.printSymbolTable = true; arguments.pop(); } else if ("-v".equalsIgnoreCase(option) || "--verbose".equalsIgnoreCase(option)) { this.verboseOutput = true; arguments.pop(); } else if ("--dump".equalsIgnoreCase(option)) { this.dumpObjectCode = true; arguments.pop(); } else if ("-o".equalsIgnoreCase(option)) { arguments.pop(); this.outputFile = new File(arguments.pop()); } else if ("-h".equalsIgnoreCase(option) || "--help".equalsIgnoreCase(option)) { printUsage(); System.exit(1); } else { printError("ERROR: Unrecognized option '" + option + "'\n\n"); printUsage(); System.exit(1); } } private void printUsage() { printVersionInfo(); final String usage = "\nUsage: [options] [-o <output file>] source1 source2 ...\n\n" + "-o => output file to write generated assembly code to, otherwise code will be written to source.dcpu16\n" + "-d or --debug => print debug output\n" + "--print => print formatted source code along with hex dump of generated assembly\n" + "--print-symbols => print symbol table\n" + "--local-labels => treat identifiers starting with a dot ('.') as local labels\n" + "--disable-literal-inlining => disable inlining of literals -1 ... 30\n" + "--dump => instead of writing generated object code to a file, write a hexdump to std out\n" + "--relaxed-parsing => relaxed parsing (instructions are parsed case-insensitive)\n" + "--relaxed-validation => out-of-range values only cause a warning)\n" + "-v or --verbose => print more verbose output during compilation\n\n"; System.out.println(usage); } // DEBUG protected static class PrintSymbolTablePhase extends CompilerPhase { public PrintSymbolTablePhase() { super("debug-symbols"); } @Override protected void run(ICompilationUnit unit, ICompilationContext context) throws IOException { final List<ISymbol> symbols = context.getSymbolTable().getSymbols(); final Comparator<ISymbol> comp = new Comparator<ISymbol>() { @Override public int compare(ISymbol o1, ISymbol o2) { if (o1 instanceof Label && o2 instanceof Label) { Address addr1 = ((Label) o1).getAddress(); Address addr2 = ((Label) o2).getAddress(); if (addr1 != null && addr2 != null) { return (int) Math.signum(addr1.getValue() - addr2.getValue()); } } return 1; } }; Collections.sort(symbols, comp); System.out.println("\nSymbol table:\n\n"); for (ISymbol s : symbols) { String name = s.getFullyQualifiedName(); final String sAddress; if (s instanceof Label) { final Address addr = ((Label) s).getAddress(); if (addr != null) { sAddress = "0x" + Misc.toHexString(addr.getValue()); } else { sAddress = "< not calculated yet >"; } } else { sAddress = "< not a label? >"; } System.out.println(Misc.padRight(name, 20) + " " + sAddress); } } } private void printError(String message) { System.out.println("ERROR: " + message); } private ICompilationUnit createCompilationUnit(String file) throws IOException { final File infile = new File(file); if (!infile.exists()) { throw new IOException("ERROR: File '" + file + "' does not exist."); } if (!infile.isFile()) { throw new IOException("ERROR: '" + file + "' is no file."); } return CompilationUnit.createInstance(file, infile); } }