de.codesourcery.jasm16.compiler.Compiler.java Source code

Java tutorial

Introduction

Here is the source code for de.codesourcery.jasm16.compiler.Compiler.java

Source

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

}