Where We Are
Can parse a (simple) Makefile to create rules
Real parsers are more complex
But it's a difference of degree, not kind
Can store dependencies in a graph
And then traverse the graph
Remaining steps:
Transform parser output to graph
Update graph nodes according to Make's rules
But First
Best to start with some refactoring
Refactoring an equation means extracting common values and eliminating redundancies
Refactoring code is similar
Two changes:
Allow clients of DirectedGraph to associate arbitrary data with nodes
Like a set of actions
Don't tie the parser to a particular rule class
May find lots of uses for it, each with its own needs
Require them all to implement an interface IRule
Associating Data With Graph Nodes
Two choices:
A second map from nodes to data
But then have to worry about consistency
A map from nodes to objects that store arcs and data
Create an inner class for this
No-one outside the graph class needs to know it exists
class DirectedGraph {
public DirectedGraph() {
fNodes = new HashMap();
}
public Set getArcs(Object node) {
return getStorage(node).fArcs;
}
public Object getData(Object node) {
return getStorage(node).fData;
}
protected void addEmptyNode(Object node) {
if (!fNodes.containsKey(node)) {
fNodes.put(node, new Storage());
}
}
protected Storage getStorage(Object node) {
return (Storage)fNodes.get(node);
}
protected class Storage {
public Storage() {
fArcs = new HashSet();
fData = null;
}
Set fArcs;
Object fData;
}
protected Map fNodes;
}
Generalizing the Parser
Problem: want a parser to build objects of a user-specified class
Solution 1: make the parser an abstract class
Users must derive from it, and implement a method createRule()
public abstract class DirectedGraphParserBase {
// Parsing method calls createRule() to make new rules.
protected void startRule(String line) {
fCurrentRule = createRule();
...fill in rule as before...
}
// Derived class must override this.
protected abstract IRule createRule();
}
public class AppRule implements IRule {
...whatever the application needs...
}
public class AppDirectedGraphParser extends DirectedGraphParserBase {
protected IRule createRule() {
return new AppRule();
}
}
Building Things With Factories
Solution 2: create a factory
A class whose only job is to create instances of another class
Each application passes its own factory to the parser
We can require that this class implement some interface IRuleFactory
Then client applications can have the parser create whatever rules they want
The Rule and Rule Factory Interfaces
interface IRule {
String getTarget();
void setTarget(String target);
Iterator getPrereqIter();
void addPrereq(String prereq);
Iterator getActionIter();
void addAction(String action);
String toString();
}
interface IRuleFactory {
IRule makeRule();
IRule makeRule(String target, String[] prereqs, String[] actions);
}
Factory-Based Parser
class ParseMakefile {
public ParseMakefile(IRuleFactory ruleFactory) {
fRuleFactory = ruleFactory;
}
protected void startRule(String line) {
fCurrentRule = fRuleFactory.makeRule();
...build up rule...
}
protected IRuleFactory fRuleFactory;
}
Main Body of Make
class Make {
public static void main(String[] args) {
// Create input stream
// Read rules
// Create dependency graph
// Create rules for pre-requisites that aren't targets
// Mark specified targets as out of date
// Update
}
}
Converting Rule List to Graph
public static DirectedGraph rulesToGraph(List ruleList) {
DirectedGraph graph = new DirectedGraph();
Iterator ir = ruleList.iterator();
while (ir.hasNext()) {
IRule rule = (IRule)ir.next();
String target = rule.getTarget();
graph.addNode(target);
graph.setData(target, rule);
Iterator ia = rule.getPrereqIter();
while (ia.hasNext()) {
graph.addArc(target, ia.next());
}
}
return graph;
}
Filling In the Graph
Makefiles can mention nodes without providing rules for them
Treating this as an error would put a heavy burden on users
Need to act as if a do-nothing rule for y.class had been defined
Creating on the fly would complicate the logic
Solution: fill in the graph before starting update
Often called pre-processing
Filling In
public static void fillInGraph(DirectedGraph graph) {
Iterator in = graph.getNodes().iterator();
while (in.hasNext()) {
Object node = in.next();
if (graph.getData(node) == null) {
IRule rule = new MakeRule();
rule.setTarget((String)node);
graph.setData(node, rule);
}
}
}
The Update Algorithm
A node is to be updated if:
Any pre-requisites have changed
The user said to on the command line
Useful for testing
Never execute a rule more than once
Have each node keep track of whether it has already executed
Cannot execute rule before all pre-requisites have been checked
Bottom-up traversal
Update Implementation
public static boolean update(DirectedGraph graph, Object node) {
MakeRule rule = (MakeRule)graph.getData(node);
// Check pre-requisites first
boolean prereqChanged = false;
Iterator ia = graph.getArcs(node).iterator();
while (ia.hasNext()) {
Object prereq = ia.next();
if (update(graph, prereq)) {
prereqChanged = true;
}
}
// Explicit change, or pre-requisites have changed?
if (prereqChanged || rule.isChanged()) {
rule.execute();
return true;
}
// No reason to update parent
return false;
}
Smart Rules
Last step is to build nodes that keep track of:
Whether they have changed
Whether their rules have been executed
Could:
Create a new class that implements IRule
Create a wrapper around SimpleRule
Extend SimpleRule
This is a case where derivation makes sense
A MakeRule really is a SimpleRule, only smarter
Smart Rules
class MakeRule extends SimpleRule {
public MakeRule() {
super();
init();
}
public void execute() {
if (!fExecuted) {
System.out.println("Executing rules for " + this.getTarget());
Iterator ia = this.getActionIter();
if (!ia.hasNext()) {
System.out.println("\t<no rules>");
}
else {
while (ia.hasNext()) {
System.out.println("\t" + (String)ia.next());
}
}
fExecuted = true;
}
}
protected void init() {
fChanged = false;
fExecuted = false;
}
protected boolean fChanged; // Has this target changed?
protected boolean fExecuted; // Have actions been executed?
}
$Id: remake.html,v 1.1.1.1 2004/01/04 05:02:31 reid Exp $