/**
* 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.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import de.codesourcery.jasm16.compiler.io.DefaultResourceMatcher;
import de.codesourcery.jasm16.compiler.io.FileResourceResolver;
import de.codesourcery.jasm16.compiler.io.IObjectCodeWriterFactory;
import de.codesourcery.jasm16.compiler.io.IResource;
import de.codesourcery.jasm16.compiler.io.IResource.ResourceType;
import de.codesourcery.jasm16.compiler.io.IResourceMatcher;
import de.codesourcery.jasm16.compiler.io.IResourceResolver;
import de.codesourcery.jasm16.compiler.phases.ASTValidationPhase1;
import de.codesourcery.jasm16.compiler.phases.ASTValidationPhase2;
import de.codesourcery.jasm16.compiler.phases.CalculateAddressesPhase;
import de.codesourcery.jasm16.compiler.phases.CodeGenerationPhase;
import de.codesourcery.jasm16.compiler.phases.ExpandMacrosPhase;
import de.codesourcery.jasm16.compiler.phases.ParseSourcePhase;
import de.codesourcery.jasm16.exceptions.ResourceNotFoundException;
import de.codesourcery.jasm16.exceptions.UnknownCompilationOrderException;
/**
* Default compiler (assembler) implementation.
*
* @author tobias.gierke@code-sourcery.de
*/
public class Compiler implements ICompiler {
/**
* Returned by {@link #getVersionNumber()} if reading
* the version number failed.
*/
public static final String NO_VERSION_NUMBER = "<no version number>";
public static final String VERSION ="jASM_16 V"+getVersionNumber();
private static final Logger LOG = Logger.getLogger(Compiler.class);
private final List<ICompilerPhase> compilerPhases = new ArrayList<ICompilerPhase>();
private IObjectCodeWriterFactory writerFactory;
private IResourceResolver resourceResolver = new FileResourceResolver()
{
protected de.codesourcery.jasm16.compiler.io.IResource.ResourceType determineResourceType(java.io.File file) {
return ResourceType.SOURCE_CODE;
}
};
private final Set<CompilerOption> options = new HashSet<CompilerOption>();
private ICompilationOrderProvider linkOrderProvider;
/**
* Reads the compiler's version number from the Maven2 pom.properties
* file in the classpath.
*
* @return version number or {@link #NO_VERSION_NUMBER}.
*/
public static final String getVersionNumber() {
final String path = "META-INF/maven/de.codesourcery.dcpu16/jasm16/pom.properties";
try {
final InputStream in = Compiler.class.getClassLoader().getResourceAsStream( path );
try {
if ( in != null ) {
final Properties props = new Properties();
props.load( in);
final String version = props.getProperty("version");
if ( ! StringUtils.isBlank( version ) ) {
return version;
}
}
} finally {
IOUtils.closeQuietly( in );
}
} catch(Exception e) {
}
return NO_VERSION_NUMBER;
}
public Compiler()
{
compilerPhases.addAll( setupCompilerPhases() );
}
@Override
public void compile(final List<ICompilationUnit> units)
{
compile( units , new CompilationListener() );
}
@Override
public DebugInfo compile(final List<ICompilationUnit> unitsToCompile, ICompilationListener listener)
{
return compile( unitsToCompile , new ArrayList<ICompilationUnit>() , new ParentSymbolTable("generated in compile()") , new CompilationListener() , DefaultResourceMatcher.INSTANCE );
}
@Override
public DebugInfo compile(final List<ICompilationUnit> unitsToCompile,
IParentSymbolTable parentSymbolTable ,
ICompilationListener listener,
IResourceMatcher resourceMatcher)
{
return compile(unitsToCompile,new ArrayList<ICompilationUnit>() , parentSymbolTable , listener , resourceMatcher );
}
@Override
public DebugInfo compile(final List<ICompilationUnit> unitsToCompile,
final List<ICompilationUnit> dependencies,
IParentSymbolTable parentSymbolTable ,
ICompilationListener listener,
IResourceMatcher resourceMatcher)
{
if ( hasCompilerOption(CompilerOption.DEBUG_MODE ) )
{
// new Exception("------------- compiling ---------------").printStackTrace();
System.out.println("----------------------------------");
System.out.println("----------- COMPILING ------------");
System.out.println("----------------------------------");
System.out.println( StringUtils.join( unitsToCompile , "\n" ) );
System.out.println("-------------------------------------");
System.out.println("----------- DEPENDENCIES ------------");
System.out.println("-------------------------------------");
System.out.println( StringUtils.join( dependencies, "\n" ) );
}
// sanity check for duplicate compilation units
// or compilation units that are in both unitsToCompile
// and otherUnits
for ( ICompilationUnit u1 : unitsToCompile )
{
for ( ICompilationUnit u2 : dependencies)
{
if ( u1 == u2 || resourceMatcher.isSame( u1.getResource(), u2.getResource() ) ) {
throw new IllegalArgumentException("ICompilationUnit for "+u1.getResource()+" must not be present in both lists, unitsToCompile and otherUnits");
}
}
}
for ( int i = 0 ; i < unitsToCompile.size() ; i++ )
{
final ICompilationUnit unit1 = unitsToCompile.get(i);
for ( int j = 0 ; j < unitsToCompile.size() ; j++ )
{
final ICompilationUnit unit2 = unitsToCompile.get(j);
if ( j != i && ( unit1 == unit2 || resourceMatcher.isSame( unit1.getResource() , unit2.getResource() ) ) ) {
throw new IllegalArgumentException("Duplicate compilation unit "+unit1+" in unitsToCompile");
}
}
}
for ( int i = 0 ; i < dependencies.size() ; i++ )
{
final ICompilationUnit unit1 = dependencies.get(i);
for ( int j = 0 ; j < dependencies.size() ; j++ )
{
final ICompilationUnit unit2 = dependencies.get(j);
if ( j != i && ( unit1 == unit2 || resourceMatcher.isSame( unit1.getResource() , unit2.getResource() ) ) ) {
throw new IllegalArgumentException("Duplicate compilation unit "+unit1+" in otherUnits");
}
}
}
// determine compilation order
final ICompilationOrderProvider orderProvider;
if ( linkOrderProvider == null ) {
orderProvider = new ICompilationOrderProvider() {
@Override
public List<ICompilationUnit> determineCompilationOrder(List<ICompilationUnit> units,IResourceResolver resolver,IResourceMatcher resourceMatcher)
{
return units;
}
};
} else {
orderProvider = linkOrderProvider;
}
final List<ICompilationUnit> unitsInCompilationOrder;
try {
unitsInCompilationOrder = orderProvider.determineCompilationOrder( unitsToCompile , resourceResolver , resourceMatcher );
}
catch (ResourceNotFoundException e1) {
throw new UnknownCompilationOrderException( e1.getMessage(), e1);
}
final DebugInfo debugInfo = new DebugInfo();
// notify listeners of compilation start
final ICompilerPhase firstPhase = compilerPhases.isEmpty() ? null : compilerPhases.get(0);
listener.onCompileStart( firstPhase );
ICompilerPhase lastPhase = firstPhase;
try
{
// discard any symbols that may have been defined in a previous compilation run
final IParentSymbolTable globalSymbolTable;
if ( parentSymbolTable == null ) {
globalSymbolTable = new ParentSymbolTable("compiler-generated");
} else {
globalSymbolTable = parentSymbolTable;
}
globalSymbolTable.clear();
for ( ICompilationUnit unit : unitsToCompile )
{
unit.beforeCompilationStart(); // clears symbol table as well
// globalSymbolTable.clear( unit );
unit.getSymbolTable().setParent( globalSymbolTable );
}
// make sure to pass-in a COPY of the input list into
// ICompilerPhase#execute() ... this method argument is actually MODIFIED by
// the compilation phases.
// Whenever a - previously unseen - include is being processed
// , a new ICompilationUnit may be added to this copy
final List<ICompilationUnit> unitsToProcess = new ArrayList<ICompilationUnit>(unitsInCompilationOrder);
final ICompilationUnitResolver compUnitResolver = createCompilationUnitResolver( unitsToProcess , dependencies , parentSymbolTable );
for ( ICompilerPhase phase : compilerPhases )
{
lastPhase = phase;
listener.start( phase );
boolean success = false;
try {
success = phase.execute(
unitsInCompilationOrder ,
debugInfo,
globalSymbolTable ,
writerFactory ,
listener,
resourceResolver,
options,
compUnitResolver );
}
catch(Exception e) {
LOG.error("compile(): Internal compiler error during phase "+phase.getName(),e);
}
finally
{
if ( ! success )
{
listener.failure( phase );
} else {
listener.success( phase );
}
}
if ( ! success || phase.isStopAfterExecution() ) {
return debugInfo;
}
}
}
finally {
listener.afterCompile( lastPhase );
}
return debugInfo;
}
protected ICompilationUnitResolver createCompilationUnitResolver(
final List<ICompilationUnit> unitsToCompile,
final List<ICompilationUnit> otherUnits,
final IParentSymbolTable parentSymbolTable)
{
final ICompilationUnitResolver unitResolver = new ICompilationUnitResolver()
{
@Override
public ICompilationUnit getOrCreateCompilationUnit(IResource resource) throws IOException
{
ICompilationUnit result = getCompilationUnit( resource );
if ( result != null ) {
return result;
}
result = CompilationUnit.createInstance( resource.getIdentifier() , resource );
result.getSymbolTable().setParent( parentSymbolTable );
System.out.println("Creating new ICompilationUnit - did not find "+resource+" in "+unitsToCompile+" NOR "+otherUnits);
// !!!! the next call actually modifies the method's input argument....
unitsToCompile.add( result );
return result;
}
@Override
public ICompilationUnit getCompilationUnit(IResource resource) throws IOException
{
ICompilationUnit result = findCompilationUnit( resource , unitsToCompile );
if ( result == null ) {
result = findCompilationUnit( resource , otherUnits );
}
return result;
}
private ICompilationUnit findCompilationUnit(IResource resource,List<ICompilationUnit> units)
{
for ( ICompilationUnit unit : units )
{
if ( unit.getResource().getIdentifier().equals( resource.getIdentifier() ) ) {
return unit;
}
}
return null;
}
};
return unitResolver;
}
protected List<ICompilerPhase> setupCompilerPhases()
{
final List<ICompilerPhase> phases = new ArrayList<ICompilerPhase>();
// parse sources
phases.add( new ParseSourcePhase() );
// expand macros
phases.add( new ExpandMacrosPhase() );
// validate existence of referenced labels
phases.add( new ASTValidationPhase1() );
// calculate size information and set addresses
phases.add( new CalculateAddressesPhase() );
// validate before object code generation
phases.add( new ASTValidationPhase2() );
// generate object code
phases.add( new CodeGenerationPhase() );
return phases;
}
@Override
public List<ICompilerPhase> getCompilerPhases() {
return Collections.unmodifiableList( this.compilerPhases );
}
@Override
public void insertCompilerPhaseAfter(ICompilerPhase phase, String name) {
if ( phase == null ) {
throw new IllegalArgumentException("phase must not be NULL");
}
assertHasUniqueName( phase );
final int index = getCompilerPhaseIndex( name );
if ( (index+1) >= compilerPhases.size() ) {
compilerPhases.add( phase );
} else {
compilerPhases.add( index+1 , phase );
}
}
private void assertHasUniqueName(ICompilerPhase phase) {
for ( ICompilerPhase p : compilerPhases) {
if ( p.getName().equals( phase.getName() ) ) {
throw new IllegalArgumentException("Duplicate compiler phase with name '"+phase.getName()+"'");
}
}
}
private int getCompilerPhaseIndex(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("name must not be NULL/blank");
}
for ( int i = 0 ; i < this.compilerPhases.size() ; i++ )
{
if ( this.compilerPhases.get(i).getName().equals( name ) )
{
return i;
}
}
throw new IllegalArgumentException("Found no compiler phase '"+name+"'");
}
@Override
public ICompilerPhase getCompilerPhaseByName(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("name must not be NULL/blank");
}
for ( ICompilerPhase p : compilerPhases ) {
if ( p.getName().equals( name ) )
{
return p;
}
}
throw new IllegalArgumentException("Found no compiler phase '"+name+"'");
}
@Override
public void replaceCompilerPhase(ICompilerPhase phase, String name)
{
if ( phase == null ) {
throw new IllegalArgumentException("phase must not be NULL");
}
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("name must not be NULL/blank.");
}
if ( ! name.equals( phase.getName() ) ) {
assertHasUniqueName( phase );
}
compilerPhases.set( getCompilerPhaseIndex( name ) , phase );
}
@Override
public void removeCompilerPhase(String name) {
for (Iterator<ICompilerPhase> it = compilerPhases.iterator(); it.hasNext();) {
final ICompilerPhase type = it.next();
if ( type.getName().equals( name ) ) {
it.remove();
return;
}
}
throw new NoSuchElementException("Failed to remove phase '"+name+"'");
}
@Override
public void insertCompilerPhaseBefore(ICompilerPhase phase, String name)
{
if ( phase == null ) {
throw new IllegalArgumentException("phase must not be NULL");
}
if ( name == null ) {
throw new IllegalArgumentException("name must not be NULL");
}
assertHasUniqueName( phase );
compilerPhases.add( getCompilerPhaseIndex( name ) , phase );
}
@Override
public void setObjectCodeWriterFactory(IObjectCodeWriterFactory factory)
{
if (factory == null) {
throw new IllegalArgumentException("factory must not be NULL.");
}
this.writerFactory = factory;
}
@Override
public void setResourceResolver(IResourceResolver resolver)
{
if (resolver == null) {
throw new IllegalArgumentException("resolver must not be NULL.");
}
this.resourceResolver = resolver;
}
@Override
public boolean hasCompilerOption(CompilerOption option) {
if (option == null) {
throw new IllegalArgumentException("option must not be NULL");
}
return this.options.contains( option );
}
@Override
public ICompiler setCompilerOption(CompilerOption option, boolean onOff) {
if ( option == null ) {
throw new IllegalArgumentException("option must not be NULL");
}
if ( onOff ) {
options.add( option );
} else {
options.remove( option );
}
return this;
}
@Override
public void setCompilationOrderProvider(ICompilationOrderProvider provider)
{
if (provider == null) {
throw new IllegalArgumentException("provider must not be NULL.");
}
this.linkOrderProvider = provider;
}
}