package de.codesourcery.jasm16.compiler.dependencyanalysis; /** * 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. */ import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.WordAddress; import de.codesourcery.jasm16.ast.ASTNode; import de.codesourcery.jasm16.ast.IncludeSourceFileNode; import de.codesourcery.jasm16.ast.OriginNode; import de.codesourcery.jasm16.compiler.CompilationUnit; import de.codesourcery.jasm16.compiler.ICompilationUnit; import de.codesourcery.jasm16.compiler.ICompilationUnitResolver; import de.codesourcery.jasm16.compiler.SymbolTable; import de.codesourcery.jasm16.compiler.dependencyanalysis.DependencyNode.NodeVisitor; import de.codesourcery.jasm16.compiler.io.IResource; import de.codesourcery.jasm16.compiler.io.IResourceMatcher; import de.codesourcery.jasm16.compiler.io.IResourceResolver; import de.codesourcery.jasm16.compiler.io.IResource.ResourceType; import de.codesourcery.jasm16.exceptions.ResourceNotFoundException; import de.codesourcery.jasm16.exceptions.UnknownCompilationOrderException; import de.codesourcery.jasm16.lexer.ILexer; import de.codesourcery.jasm16.lexer.Lexer; import de.codesourcery.jasm16.lexer.TokenType; import de.codesourcery.jasm16.parser.IParseContext; import de.codesourcery.jasm16.parser.IParser; import de.codesourcery.jasm16.parser.IParser.ParserOption; import de.codesourcery.jasm16.parser.ParseContext; import de.codesourcery.jasm16.scanner.Scanner; import de.codesourcery.jasm16.utils.Misc; /** * Analyzes dependencies between compilation units (source files) * created by include directives. * * <p>This class does not do full parsing using a {@link IParser} but instead just uses an {@link ILexer} * and looks for {@link TokenType#INCLUDE_SOURCE} tokens,parsing only those.</p> * * @author tobias.gierke@code-sourcery.de */ public class SourceFileDependencyAnalyzer { // debugging only private static final AtomicLong DOT_IDS = new AtomicLong(0); private static class ParsingResult { public final List<IResource> includedSources=new ArrayList<IResource>(); public Address objectCodeStartingAddress=Address.wordAddress( 0 ); } /** * Calculates the root set for a given set of <code>ICompilationUnit</code>s. * * <p>The root set consists of the dependency graph's root nodes (=the compilation * units that no other files depend on and thus may be the starting point of a compilation run).</p> * * <p>Use {@link linearize(DependencyNode)} to determine the order in which all compilation units * of a root node need to be visited</p>. * * @param units * @param resolver * @param resourceMatcher * @return * @throws UnknownCompilationOrderException * @throws ResourceNotFoundException */ public List<DependencyNode> calculateRootSet(List<ICompilationUnit> units,IResourceResolver resolver,IResourceMatcher resourceMatcher) throws UnknownCompilationOrderException, ResourceNotFoundException { if ( units.isEmpty() ) { return new ArrayList<>(); } // create dependency graph from units final List<DependencyNode> graph = new ArrayList<>(); for ( ICompilationUnit unit : units ) { if ( ! unit.getResource().hasType( ResourceType.SOURCE_CODE ) ) { throw new IllegalArgumentException("Refusing to parse non-source-code file "+unit.getResource()+" with unrecognized type "+unit.getResource().getType() ); } ParsingResult parseResult; try { parseResult = getIncludedSources( unit , resolver ); } catch (IOException | ParseException e) { throw new UnknownCompilationOrderException("Failed to parse "+unit,e); } final List<IResource> sourceFile = parseResult.includedSources; final DependencyNode node = getOrCreateGraphNode( unit , graph); Address address1 = unit.getObjectCodeStartOffset(); Address address2 = parseResult.objectCodeStartingAddress; node.setObjectCodeStartingAddress( address1 != null ? address1 : address2 ); for ( IResource r : sourceFile ) { final DependencyNode dependency = getOrCreateGraphNode( findCompilationUnit( r , resourceMatcher , units ) , graph ); node.dependencies.add( dependency ); // A -> B dependency.dependentNodes.add( node ); // B <- A } } // linearize graph root set return determineRootSet( graph ); } private ICompilationUnit findCompilationUnit(IResource resource , IResourceMatcher matcher , List<ICompilationUnit> units) { for ( ICompilationUnit unit : units ) { if ( matcher.isSame( unit.getResource() , resource ) ) { return unit; } } throw new RuntimeException("Unable to find resource "+resource+" in "+units); } /** * Extracts compilation units from a dependency graph so that dependencies come before the * unit that depends on them. * * @param dependencyNode * @return */ public List<ICompilationUnit> linearize(DependencyNode dependencyNode) { // resolve so that dependencies are compiled before the // unit that depends on them final List<ICompilationUnit> result = new ArrayList<ICompilationUnit>(); recursiveAdd( dependencyNode , result ); return result; } protected void recursiveAdd(DependencyNode node,List<ICompilationUnit> result) { for ( DependencyNode dep : node.dependencies ) { recursiveAdd( dep , result); } result.add( node.unit ); } /** * The root set contains the graph nodes that no other nodes depends on. * * @param graph * @return */ protected List<DependencyNode> determineRootSet(List<DependencyNode> graph) { List<DependencyNode> result = new ArrayList<DependencyNode> (); for ( DependencyNode n : graph ) { result.addAll( determineRootSet( n ) ); } return result; } protected List<DependencyNode> determineRootSet(DependencyNode n) { final List<DependencyNode> result = new ArrayList<DependencyNode> (); final NodeVisitor visitor= new NodeVisitor() { @Override public boolean visit(DependencyNode node) { if ( node.dependentNodes.isEmpty() ) { result.add( node ); } return true; } }; n.visitNodeAndDirectDependenciesOnly( visitor ); return result; } protected DependencyNode getOrCreateGraphNode(ICompilationUnit unit,List<DependencyNode> graph) { for ( DependencyNode n : graph ) { DependencyNode result = findNodeForUnit( n , unit ); if ( result != null ) { return result; } } final DependencyNode node = new DependencyNode( unit ); graph.add( node ); return node; } protected DependencyNode findNodeForUnit(DependencyNode n,ICompilationUnit unit) { if ( n.unit == unit ) { return n; } for ( DependencyNode child : n.dependencies ) { DependencyNode result = findNodeForUnit( child , unit ); if ( result != null ) { return result; } } return null; } /** * (DEBUG) Creates a .dot graph description from a dependency graph. * * <p>The generated graph description can be visualized using the fantastic GraphViz package by AT&T.</p> * * @param graphName * @param graph * @return */ public static String toDOTGraph(String graphName, DependencyNode graph) { final IdentityHashMap<DependencyNode, String> dotIds = new IdentityHashMap<>(); final StringBuilder edgeBuilder = new StringBuilder(); visitGraph( graph , edgeBuilder , dotIds ); final StringBuilder graphBuilder = new StringBuilder("digraph \""+graphName+"\" {"); for (Map.Entry<DependencyNode, String> entry : dotIds.entrySet() ) { graphBuilder.append( entry.getValue()+" [label=\""+entry.getKey().getCompilationUnit().getResource().getIdentifier()+"\"]\n"); } graphBuilder.append( edgeBuilder.toString() ); graphBuilder.append("\n}"); return graphBuilder.toString(); } private static void visitGraph(DependencyNode graph, StringBuilder edgeBuilder, IdentityHashMap<DependencyNode, String> dotIds) { if ( dotIds.containsKey( graph ) ) { return; } final String nodeId = getDOTIdentifier( graph , dotIds ); for ( DependencyNode child : graph.getDependencies() ) { visitGraph( child , edgeBuilder , dotIds ); edgeBuilder.append( nodeId+" -> "+getDOTIdentifier( child , dotIds )+"\n"); } } private static String getDOTIdentifier(DependencyNode node,IdentityHashMap<DependencyNode, String> map) { String result = map.get(node); if ( result == null ) { result = Long.toString( DOT_IDS.incrementAndGet() ); map.put( node , result ); } return result; } protected ParsingResult getIncludedSources(ICompilationUnit original,IResourceResolver resolver) throws IOException, ParseException { // create a copy since the parsing process may add compilation errors , symbols etc. final ICompilationUnit copy = CompilationUnit.createInstance( original.getIdentifier() , original.getResource() ); final ParsingResult parseResult = new ParsingResult(); final Set<ParserOption> parserOptions = Collections.singleton( ParserOption.NO_SOURCE_INCLUDE_PROCESSING ); final ICompilationUnitResolver compUnitResolver = new ICompilationUnitResolver() { @Override public ICompilationUnit getOrCreateCompilationUnit(IResource resource) throws IOException { throw new UnsupportedOperationException("Should not be called"); } @Override public ICompilationUnit getCompilationUnit(IResource resource) throws IOException { throw new UnsupportedOperationException("Should not be called"); } }; final String input = Misc.readSource(copy); final ILexer lexer = new Lexer( new Scanner( input ) ); while( ! lexer.eof() ) { if ( lexer.peek().hasType( TokenType.SINGLE_LINE_COMMENT ) ) { while ( ! lexer.eof() ) { if ( lexer.peek().isEOL() ) { lexer.read(); break; } lexer.read(); } continue; } else if ( lexer.peek().hasType( TokenType.STRING_DELIMITER) ) { while ( ! lexer.eof() ) { if ( lexer.peek().isEOL() || lexer.peek().hasType( TokenType.STRING_DELIMITER ) ) { lexer.read(); break; } lexer.read(); } continue; } if ( lexer.peek().hasType( TokenType.INCLUDE_SOURCE ) || lexer.peek().hasType( TokenType.ORIGIN ) ) { parseNode(copy, resolver, parseResult, parserOptions, compUnitResolver, lexer); } else { lexer.read(); // skip token } } return parseResult; } protected void parseNode(ICompilationUnit unit, IResourceResolver resolver, final ParsingResult parseResult, final Set<ParserOption> parserOptions, final ICompilationUnitResolver compUnitResolver, final ILexer lexer) throws ParseException { final IParseContext context = new ParseContext(unit, new SymbolTable("SourceFileDependencyAnalyzer") , lexer , resolver , compUnitResolver, parserOptions , null ); if ( lexer.peek().hasType( TokenType.INCLUDE_SOURCE ) ) { final ASTNode node = new IncludeSourceFileNode().parse( context ); if ( node instanceof IncludeSourceFileNode && ! unit.hasErrors() ) { final IResource resource = ((IncludeSourceFileNode) node).getResource(); if ( resource != null ) { parseResult.includedSources.add( resource ); } return; /* RETURN */ } try { Misc.printCompilationErrors( unit , unit.getResource(),true); } catch(Exception e) { } String errorMsg = "Failed to parse .includesource directive in "+unit+", got node: "+node+" , unit.hasErrors = "+unit.hasErrors(); throw new ParseException(errorMsg,-1); } if ( lexer.peek().hasType( TokenType.ORIGIN ) ) { final ASTNode node = new OriginNode().parse( context ); if ( node instanceof OriginNode && ! unit.hasErrors() ) { final Address addr = ((OriginNode) node).getAddress(); if ( addr != null ) { if ( WordAddress.ZERO.equals( unit.getObjectCodeStartOffset() ) ) { unit.setObjectCodeStartOffset( addr ); parseResult.objectCodeStartingAddress = addr; } return; } } throw new ParseException("Failed to parse .org directive in "+unit,-1); } throw new RuntimeException("Unreachable code reached"); } }