/** * 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 ); } }