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.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.ast.AST; import de.codesourcery.jasm16.compiler.io.FileResource; import de.codesourcery.jasm16.compiler.io.IResource; import de.codesourcery.jasm16.compiler.io.IResource.ResourceType; import de.codesourcery.jasm16.compiler.io.StringResource; import de.codesourcery.jasm16.exceptions.CircularSourceIncludeException; import de.codesourcery.jasm16.utils.ITextRegion; import de.codesourcery.jasm16.utils.Line; import de.codesourcery.jasm16.utils.TextRegion; /** * Default {@link ICompilationUnit} implementation. * * @author tobias.gierke@code-sourcery.de */ public final class CompilationUnit implements ICompilationUnit { private static final Logger LOG = Logger.getLogger(CompilationUnit.class); private final String identifier; private final Map<String, List<IMarker>> markers = new HashMap<String, List<IMarker>>(); private final IResource resource; private volatile AST ast; private volatile Address objectCodeStartAddress = Address.ZERO; private final List<ICompilationUnit> dependencies; private final ISymbolTable symbolTable; private final RelocationTable relocationTable; private Map<Integer, Line> lines = new HashMap<Integer, Line>(); private CompilationUnit(CompilationUnit unit, IResource resource) { this.identifier = unit.getIdentifier(); // this.markers = unit.markers; this.resource = resource; // this.ast = unit.getAST(); // this.objectCodeStartAddress = unit.getObjectCodeStartOffset(); this.dependencies = unit.dependencies; this.symbolTable = unit.symbolTable; this.relocationTable = unit.relocationTable; this.lines = unit.lines; } protected CompilationUnit(String identifier, IResource resource) { if (StringUtils.isBlank(identifier)) { throw new IllegalArgumentException("identifier must not be NULL/blank"); } if (resource == null) { throw new IllegalArgumentException("resource must not be NULL."); } if (!resource.hasType(ResourceType.SOURCE_CODE)) { throw new IllegalArgumentException("Cannot create compilation unit from resource " + resource); } this.dependencies = new ArrayList<ICompilationUnit>(); this.resource = resource; this.identifier = identifier; this.symbolTable = new SymbolTable("CompilationUnit: " + resource); this.relocationTable = new RelocationTable(); } @Override public ICompilationUnit withResource(IResource resource) { return new CompilationUnit(this, resource); } @Override public void dumpSourceLines() { List<Integer> keys = new ArrayList<>(lines.keySet()); Collections.sort(keys); for (Integer key : keys) { System.out.println(lines.get(key)); } } @Override public void beforeCompilationStart() { System.err.println("----------------- before compilation of " + this + " -----------------"); this.relocationTable.clear(); this.lines.clear(); this.ast = null; this.symbolTable.clear(); this.symbolTable.setParent(null); this.markers.remove(IMarker.TYPE_COMPILATION_WARNING); this.markers.remove(IMarker.TYPE_COMPILATION_ERROR); this.markers.remove(IMarker.TYPE_GENERIC_COMPILATION_ERROR); this.objectCodeStartAddress = Address.ZERO; this.dependencies.clear(); } public static ICompilationUnit createInstance(final String identifier, final IResource resource) { return new CompilationUnit(identifier, resource); } public static ICompilationUnit createInstance(final String identifier, final String source) { return new CompilationUnit(identifier, new StringResource(identifier, source, ResourceType.SOURCE_CODE)); } @Override public RelocationTable getRelocationTable() { return relocationTable; } @Override public void setLine(Line l) { if (l == null) { throw new IllegalArgumentException("line must not be NULL."); } Line existing = lines.get(l.getLineNumber()); /* * Do not update line references that have already been parsed. * * The sanity check (commented-out below) is wrong during macro expansion because * instructions/lines in expanded macros get mapped to the * lines of the macro definition. If the macro takes parameters, * the absolute line starting offsets may change because of different * parameter lengths. * * Example: * * .macro dummy(param1) // line start offset: 0 * SET A,param1 // line start offset: 21 * SET B,2 // line start offset: 37 <<<<<<<<<< sanity check would break compilation * .endmacro * * dummy(X) * * For the marked line, parsing the source yields an absolute line start offset of 37. * When the macro invocation is expanded (virtually at the same location of the original macro body) * , "param1' will be replaced with 'X' , shifting the line starting offset of the marked line by -5. * Since AST#parseInternal() unconditionally invokes ICompilationUnit#setLine() , this would trip * our sanity check which is thus disabled. */ if (existing == null) { lines.put(l.getLineNumber(), l); } // if ( existing != null && ! existing.equals( l ) ) { // TextRegion r1= new TextRegion(existing.getLineStartingOffset() , 10 ); // TextRegion r2= new TextRegion(l.getLineStartingOffset() , 10 ); // String text1 = "<invalid region "+r1+">"; // String text2 = "<invalid region "+r2+">"; // try { // text1 = getSource(r1); // } catch(Exception e) { /* can't help it */ } // try { // text2 = getSource(r2); // } catch(Exception e) { /* can't help it */ } // throw new RuntimeException("Won't replace existing line "+existing+" with "+l); // } } @Override public int getParsedLineCount() { return lines.size(); } @Override public SourceLocation getSourceLocation(ITextRegion textRegion) { final Line l = getLineForOffset(textRegion.getStartingOffset()); return new SourceLocation(this, l, textRegion); } @Override public Line getPreviousLine(Line line) { if (line == null) { throw new IllegalArgumentException("line must not be NULL."); } Line previous = null; for (Iterator<Line> it = lines.values().iterator(); it.hasNext();) { final Line l = it.next(); if (line.equals(l)) { return previous; } previous = l; } return null; } @Override public Line getLineForOffset(int offset) throws NoSuchElementException { if (offset < 0) { throw new IllegalArgumentException("offset must not be negative"); } Line previousLine = null; for (Iterator<Line> it = lines.values().iterator(); it.hasNext();) { final Line l = it.next(); if (l.getLineStartingOffset() == offset) { return l; } if (previousLine != null && previousLine.getLineStartingOffset() < offset && l.getLineStartingOffset() > offset) { return previousLine; } previousLine = l; } if (previousLine != null && previousLine.getLineStartingOffset() <= offset) { return previousLine; } throw new NoSuchElementException("Found no line with offset " + offset); } /** * * @param range * @return lines ordered ascending by line number */ @Override public List<Line> getLinesForRange(ITextRegion range) { if (range == null) { throw new IllegalArgumentException("range must not be NULL."); } final List<Line> result = new ArrayList<Line>(); for (Line l : lines.values()) { if (range.contains(l.getLineStartingOffset())) { result.add(l); } } Comparator<Line> comparator = new Comparator<Line>() { @Override public int compare(Line o1, Line o2) { return Integer.valueOf(o1.getLineNumber()).compareTo(Integer.valueOf(o2.getLineNumber())); } }; Collections.sort(result, comparator); return result; } public static ICompilationUnit createInstance(final String identifier, final File sourceFile) { return new CompilationUnit(identifier, new FileResource(sourceFile, ResourceType.SOURCE_CODE)); } @SuppressWarnings({ "unchecked", "cast" }) @Override public List<ICompilationError> getErrors() { return (List<ICompilationError>) internalGetMarkers(IMarker.TYPE_COMPILATION_ERROR, IMarker.TYPE_GENERIC_COMPILATION_ERROR); } @SuppressWarnings("unchecked") @Override public List<ICompilationError> getWarnings() { return (List<ICompilationError>) internalGetMarkers(IMarker.TYPE_COMPILATION_WARNING); } @Override public boolean hasErrors() { return this.markers.get(IMarker.TYPE_COMPILATION_ERROR) != null || this.markers.get(IMarker.TYPE_GENERIC_COMPILATION_ERROR) != null; } @Override public void addMarker(IMarker marker) { if (marker == null) { throw new IllegalArgumentException("marker must not be NULL"); } LOG.debug("addMarker(): " + marker); List<IMarker> markersByType = this.markers.get(marker.getType()); if (markersByType == null) { markersByType = new ArrayList<IMarker>(); this.markers.put(marker.getType(), markersByType); } markersByType.add(marker); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof ICompilationUnit) { return this.identifier.equals(((ICompilationUnit) obj).getIdentifier()); } return super.equals(obj); } @Override public int hashCode() { return this.identifier.hashCode(); } @Override public String getIdentifier() { return identifier; } @Override public AST getAST() { return ast; } @Override public void setAST(AST ast) { if (ast == null) { throw new IllegalArgumentException("ast must not be NULL"); } this.ast = ast; } @Override public IResource getResource() { return resource; } @Override public String getSource(ITextRegion range) throws IOException { return getResource().readText(range); } @Override public Line getLineByNumber(int lineNumber) throws IndexOutOfBoundsException { final Line result = lines.get(lineNumber); if (result == null) { throw new IndexOutOfBoundsException("No line with number " + lineNumber); } return result; } @Override public void deleteMarker(IMarker marker) { final List<IMarker> markers = this.markers.get(marker.getType()); if (markers != null) { for (Iterator<IMarker> iterator = markers.iterator(); iterator.hasNext();) { final IMarker iMarker = iterator.next(); if (iMarker == marker) { iterator.remove(); break; } } if (markers.isEmpty()) { this.markers.remove(marker.getType()); } } } @SuppressWarnings("unchecked") @Override public List<IMarker> getMarkers(String... types) { return internalGetMarkers(types); } @SuppressWarnings("rawtypes") private List internalGetMarkers(String... types) { final List<IMarker> result = new ArrayList<IMarker>(); if (ArrayUtils.isEmpty(types)) { for (List<IMarker> l : this.markers.values()) { result.addAll(l); } return result; } final Set<String> uniqueTypes = new HashSet<String>(Arrays.asList(types)); for (String expectedType : uniqueTypes) { final List<IMarker> existing = this.markers.get(expectedType); if (existing != null) { result.addAll(new ArrayList<IMarker>(existing)); } } return result; } @Override public String toString() { return getResource().toString() + " (has_errors=" + hasErrors() + "]"; } @Override public Address getObjectCodeStartOffset() { return objectCodeStartAddress; } @Override public void setObjectCodeStartOffset(Address address) { if (address == null) { throw new IllegalArgumentException("address must not be NULL."); } this.objectCodeStartAddress = address; } @Override public void addDependency(ICompilationUnit unit) { if (unit == null) { throw new IllegalArgumentException("unit must not be NULL"); } checkForCircularDependencies(unit); this.dependencies.add(unit); } private void checkForCircularDependencies(ICompilationUnit unit) { final Set<String> resourceIdentifiers = new HashSet<String>(); final List<ICompilationUnit> newSet = new ArrayList<ICompilationUnit>(this.dependencies); newSet.add(unit); for (ICompilationUnit current : newSet) { checkForCircularDependencies(current, resourceIdentifiers); } } private void checkForCircularDependencies(ICompilationUnit current, Set<String> resourceIdentifiers) { if (resourceIdentifiers.contains(current.getResource().getIdentifier())) { LOG.error("createParseContextForInclude(): Circular dependency detected: resource '" + resource.getIdentifier() + "'"); throw new CircularSourceIncludeException("Detected circular source inclusion", current); } resourceIdentifiers.add(current.getResource().getIdentifier()); for (ICompilationUnit dep : current.getDependencies()) { checkForCircularDependencies(dep, resourceIdentifiers); } } @Override public List<ICompilationUnit> getDependencies() { return new ArrayList<ICompilationUnit>(this.dependencies); } @Override public ISymbolTable getSymbolTable() { return symbolTable; } @Override public List<Line> getLines() { return new ArrayList<>(this.lines.values()); } }