Split exec.go into exec.go and dep.go

Now the dependency graph is constructed by dep.go
diff --git a/dep.go b/dep.go
new file mode 100644
index 0000000..a6bcba3
--- /dev/null
+++ b/dep.go
@@ -0,0 +1,436 @@
+package main
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+)
+
+type DepNode struct {
+	Output             string
+	Cmds               []string
+	Deps               []*DepNode
+	HasRule            bool
+	IsOrderOnly        bool
+	ActualInputs       []string
+	TargetSpecificVars Vars
+	Filename           string
+	Lineno             int
+}
+
+func (n *DepNode) String() string {
+	return fmt.Sprintf("Dep{output=%s cmds=%d deps=%d hasRule=%t orderOnly=%t, filename=%s lineno=%d}",
+		n.Output, len(n.Cmds), len(n.Deps), n.HasRule, n.IsOrderOnly, n.Filename, n.Lineno)
+}
+
+type DepBuilder struct {
+	rules         map[string]*Rule
+	ruleVars      map[string]Vars
+	implicitRules []*Rule
+	suffixRules   map[string][]*Rule
+	firstRule     *Rule
+	vars          Vars
+	done          map[string]*DepNode
+
+	trace                         []string
+	nodeCnt                       int
+	pickExplicitRuleCnt           int
+	pickImplicitRuleCnt           int
+	pickSuffixRuleCnt             int
+	pickExplicitRuleWithoutCmdCnt int
+}
+
+func replaceSuffix(s string, newsuf string) string {
+	// TODO: Factor out the logic around suffix rules and use
+	// it from substitution references.
+	// http://www.gnu.org/software/make/manual/make.html#Substitution-Refs
+	return fmt.Sprintf("%s.%s", stripExt(s), newsuf)
+}
+
+func (db *DepBuilder) exists(target string) bool {
+	_, present := db.rules[target]
+	if present {
+		return true
+	}
+	rule, present := db.rules[".PHONY"]
+	if present {
+		for _, input := range rule.inputs {
+			if target == input {
+				return true
+			}
+		}
+	}
+	return exists(target)
+}
+
+func (db *DepBuilder) canPickImplicitRule(rule *Rule, output string) bool {
+	outputPattern := rule.outputPatterns[0]
+	if !matchPattern(outputPattern, output) {
+		return false
+	}
+	for _, input := range rule.inputs {
+		input = substPattern(outputPattern, input, output)
+		if !db.exists(input) {
+			return false
+		}
+	}
+	return true
+}
+
+func (db *DepBuilder) mergeImplicitRuleVars(outputs []string, vars Vars) Vars {
+	if len(outputs) != 1 {
+		panic(fmt.Sprintf("Implicit rule should have only one output but %q", outputs))
+	}
+	Log("merge? %q", db.ruleVars)
+	Log("merge? %q", outputs[0])
+	ivars, present := db.ruleVars[outputs[0]]
+	if !present {
+		return vars
+	}
+	if vars == nil {
+		return ivars
+	}
+	Log("merge!")
+	v := make(Vars)
+	v.Merge(ivars)
+	v.Merge(vars)
+	return v
+}
+
+func (db *DepBuilder) pickRule(output string) (*Rule, Vars, bool) {
+	rule, present := db.rules[output]
+	vars := db.ruleVars[output]
+	if present {
+		db.pickExplicitRuleCnt++
+		if len(rule.cmds) > 0 {
+			return rule, vars, true
+		}
+		// If none of the explicit rules for a target has commands,
+		// then `make' searches for an applicable implicit rule to
+		// find some commands.
+		db.pickExplicitRuleWithoutCmdCnt++
+	}
+
+	for _, irule := range db.implicitRules {
+		if !db.canPickImplicitRule(irule, output) {
+			continue
+		}
+		db.pickImplicitRuleCnt++
+		if rule != nil {
+			r := &Rule{}
+			*r = *rule
+			r.outputPatterns = irule.outputPatterns
+			// implicit rule's prerequisites will be used for $<
+			r.inputs = append(irule.inputs, r.inputs...)
+			r.cmds = irule.cmds
+			// TODO(ukai): filename, lineno?
+			r.cmdLineno = irule.cmdLineno
+			return r, vars, true
+		}
+		if vars != nil {
+			vars = db.mergeImplicitRuleVars(irule.outputPatterns, vars)
+		}
+		// TODO(ukai): check len(irule.cmd) ?
+		return irule, vars, true
+	}
+
+	outputSuffix := filepath.Ext(output)
+	if !strings.HasPrefix(outputSuffix, ".") {
+		return rule, vars, rule != nil
+	}
+	rules, present := db.suffixRules[outputSuffix[1:]]
+	if !present {
+		return rule, vars, rule != nil
+	}
+	for _, irule := range rules {
+		if len(irule.inputs) != 1 {
+			panic(fmt.Sprintf("unexpected number of input for a suffix rule (%d)", len(irule.inputs)))
+		}
+		if !db.exists(replaceSuffix(output, irule.inputs[0])) {
+			continue
+		}
+		db.pickSuffixRuleCnt++
+		if rule != nil {
+			r := &Rule{}
+			*r = *rule
+			// TODO(ukai): input order is correct?
+			r.inputs = append([]string{replaceSuffix(output, irule.inputs[0])}, r.inputs...)
+			r.cmds = irule.cmds
+			// TODO(ukai): filename, lineno?
+			r.cmdLineno = irule.cmdLineno
+			return r, vars, true
+		}
+		if vars != nil {
+			vars = db.mergeImplicitRuleVars(irule.outputs, vars)
+		}
+		// TODO(ukai): check len(irule.cmd) ?
+		return irule, vars, true
+	}
+	return rule, vars, rule != nil
+}
+
+func (db *DepBuilder) buildPlan(output string, neededBy string, tsvs Vars) (*DepNode, error) {
+	Log("Evaluating command: %s", output)
+	db.nodeCnt++
+	if db.nodeCnt%100 == 0 {
+		db.reportStats()
+	}
+
+	if n, present := db.done[output]; present {
+		return n, nil
+	}
+	n := &DepNode{Output: output}
+	db.done[output] = n
+
+	rule, vars, present := db.pickRule(output)
+	if !present {
+		return n, nil
+	}
+
+	var restores []func()
+	if vars != nil {
+		for name, v := range vars {
+			// TODO: Consider not updating db.vars.
+			tsv := v.(TargetSpecificVar)
+			restores = append(restores, db.vars.save(name))
+			restores = append(restores, tsvs.save(name))
+			switch tsv.op {
+			case ":=", "=":
+				db.vars[name] = tsv
+				tsvs[name] = v
+			case "+=":
+				oldVar, present := db.vars[name]
+				if !present || oldVar.String() == "" {
+					db.vars[name] = tsv
+				} else {
+					v = oldVar.AppendVar(newEvaluator(db.vars), tsv)
+					db.vars[name] = v
+				}
+				tsvs[name] = v
+			case "?=":
+				if _, present := db.vars[name]; !present {
+					db.vars[name] = tsv
+					tsvs[name] = v
+				}
+			}
+		}
+		defer func() {
+			for _, restore := range restores {
+				restore()
+			}
+		}()
+	}
+
+	var children []*DepNode
+	var actualInputs []string
+	Log("Evaluating command: %s inputs:%q", output, rule.inputs)
+	for _, input := range rule.inputs {
+		if len(rule.outputPatterns) > 0 {
+			if len(rule.outputPatterns) > 1 {
+				panic("TODO: multiple output pattern is not supported yet")
+			}
+			input = substPattern(rule.outputPatterns[0], input, output)
+		} else if rule.isSuffixRule {
+			input = replaceSuffix(output, input)
+		}
+		actualInputs = append(actualInputs, input)
+
+		db.trace = append(db.trace, input)
+		n, err := db.buildPlan(input, output, tsvs)
+		db.trace = db.trace[0 : len(db.trace)-1]
+		if err != nil {
+			return nil, err
+		}
+		if n != nil {
+			children = append(children, n)
+		}
+	}
+
+	for _, input := range rule.orderOnlyInputs {
+		db.trace = append(db.trace, input)
+		n, err := db.buildPlan(input, output, tsvs)
+		db.trace = db.trace[0 : len(db.trace)-1]
+		if err != nil {
+			return nil, err
+		}
+		if n != nil {
+			n.IsOrderOnly = true
+			children = append(children, n)
+		}
+	}
+
+	n.Deps = children
+	n.HasRule = true
+	n.Cmds = rule.cmds
+	n.ActualInputs = actualInputs
+	n.TargetSpecificVars = make(Vars)
+	for k, v := range tsvs {
+		n.TargetSpecificVars[k] = v
+	}
+	n.Filename = rule.filename
+	if len(rule.cmds) > 0 {
+		n.Lineno = rule.cmdLineno
+	}
+	return n, nil
+}
+
+func (db *DepBuilder) populateSuffixRule(rule *Rule, output string) bool {
+	if len(output) == 0 || output[0] != '.' {
+		return false
+	}
+	rest := output[1:]
+	dotIndex := strings.IndexByte(rest, '.')
+	// If there is only a single dot or the third dot, this is not a
+	// suffix rule.
+	if dotIndex < 0 || strings.IndexByte(rest[dotIndex+1:], '.') >= 0 {
+		return false
+	}
+
+	// This is a suffix rule.
+	inputSuffix := rest[:dotIndex]
+	outputSuffix := rest[dotIndex+1:]
+	r := &Rule{}
+	*r = *rule
+	r.inputs = []string{inputSuffix}
+	r.isSuffixRule = true
+	db.suffixRules[outputSuffix] = append([]*Rule{r}, db.suffixRules[outputSuffix]...)
+	return true
+}
+
+func mergeRules(oldRule, rule *Rule, output string, isSuffixRule bool) *Rule {
+	if oldRule.isDoubleColon != rule.isDoubleColon {
+		Error(rule.filename, rule.lineno, "*** target file %q has both : and :: entries.", output)
+	}
+	if len(oldRule.cmds) > 0 && len(rule.cmds) > 0 && !isSuffixRule && !rule.isDoubleColon {
+		Warn(rule.filename, rule.cmdLineno, "overriding commands for target %q", output)
+		Warn(oldRule.filename, oldRule.cmdLineno, "ignoring old commands for target %q", output)
+	}
+
+	r := &Rule{}
+	*r = *rule
+	if rule.isDoubleColon {
+		r.cmds = append(oldRule.cmds, r.cmds...)
+	} else if len(oldRule.cmds) > 0 && len(rule.cmds) == 0 {
+		r.cmds = oldRule.cmds
+	}
+	// If the latter rule has a command (regardless of the
+	// commands in oldRule), inputs in the latter rule has a
+	// priority.
+	if len(rule.cmds) > 0 {
+		r.inputs = append(r.inputs, oldRule.inputs...)
+		r.orderOnlyInputs = append(r.orderOnlyInputs, oldRule.orderOnlyInputs...)
+	} else {
+		r.inputs = append(oldRule.inputs, r.inputs...)
+		r.orderOnlyInputs = append(oldRule.orderOnlyInputs, r.orderOnlyInputs...)
+	}
+	r.outputPatterns = append(r.outputPatterns, oldRule.outputPatterns...)
+	return r
+}
+
+func (db *DepBuilder) populateExplicitRule(rule *Rule) {
+	// It seems rules with no outputs are siliently ignored.
+	if len(rule.outputs) == 0 {
+		return
+	}
+	for _, output := range rule.outputs {
+		output = filepath.Clean(output)
+
+		isSuffixRule := db.populateSuffixRule(rule, output)
+
+		if oldRule, present := db.rules[output]; present {
+			r := mergeRules(oldRule, rule, output, isSuffixRule)
+			db.rules[output] = r
+		} else {
+			db.rules[output] = rule
+			if db.firstRule == nil && !strings.HasPrefix(output, ".") {
+				db.firstRule = rule
+			}
+		}
+	}
+}
+
+func (db *DepBuilder) populateImplicitRule(rule *Rule) {
+	for _, outputPattern := range rule.outputPatterns {
+		r := &Rule{}
+		*r = *rule
+		r.outputPatterns = []string{outputPattern}
+		db.implicitRules = append(db.implicitRules, r)
+	}
+}
+
+func (db *DepBuilder) populateRules(er *EvalResult) {
+	for _, rule := range er.rules {
+		for i, input := range rule.inputs {
+			rule.inputs[i] = filepath.Clean(input)
+		}
+		for i, orderOnlyInput := range rule.orderOnlyInputs {
+			rule.orderOnlyInputs[i] = filepath.Clean(orderOnlyInput)
+		}
+		db.populateExplicitRule(rule)
+
+		if len(rule.outputs) == 0 {
+			db.populateImplicitRule(rule)
+		}
+	}
+
+	// Reverse the implicit rule for easier lookup.
+	for i, r := range db.implicitRules {
+		if i >= len(db.implicitRules)/2 {
+			break
+		}
+		j := len(db.implicitRules) - i - 1
+		db.implicitRules[i] = db.implicitRules[j]
+		db.implicitRules[j] = r
+	}
+}
+
+func (db *DepBuilder) reportStats() {
+	if !katiLogFlag && !katiStatsFlag {
+		return
+	}
+
+	LogStats("node=%d explicit=%d implicit=%d suffix=%d explicitWOCmd=%d",
+		db.nodeCnt, db.pickExplicitRuleCnt, db.pickImplicitRuleCnt, db.pickSuffixRuleCnt, db.pickExplicitRuleWithoutCmdCnt)
+	if len(db.trace) > 1 {
+		LogStats("trace=%q", db.trace)
+	}
+}
+
+func NewDepBuilder(er *EvalResult, vars Vars) *DepBuilder {
+	db := &DepBuilder{
+		rules:       make(map[string]*Rule),
+		ruleVars:    er.ruleVars,
+		suffixRules: make(map[string][]*Rule),
+		vars:        vars,
+		done:        make(map[string]*DepNode),
+	}
+
+	db.populateRules(er)
+	return db
+}
+
+func (db *DepBuilder) Eval(targets []string) ([]*DepNode, error) {
+	if len(targets) == 0 {
+		if db.firstRule == nil {
+			ErrorNoLocation("*** No targets.")
+		}
+		targets = append(targets, db.firstRule.outputs[0])
+	}
+
+	LogStats("%d variables", len(db.vars))
+	LogStats("%d explicit rules", len(db.rules))
+	LogStats("%d implicit rules", len(db.implicitRules))
+	LogStats("%d suffix rules", len(db.suffixRules))
+
+	var nodes []*DepNode
+	for _, target := range targets {
+		db.trace = []string{target}
+		n, err := db.buildPlan(target, "", make(Vars))
+		if err != nil {
+			return nil, err
+		}
+		nodes = append(nodes, n)
+	}
+	db.reportStats()
+	return nodes, nil
+}
diff --git a/exec.go b/exec.go
index 07378f4..3c0afba 100644
--- a/exec.go
+++ b/exec.go
@@ -14,7 +14,6 @@
 
 type Executor struct {
 	rules         map[string]*Rule
-	ruleVars      map[string]Vars
 	implicitRules []*Rule
 	suffixRules   map[string][]*Rule
 	firstRule     *Rule
@@ -28,16 +27,12 @@
 	currentInputs []string
 	currentStem   string
 
-	trace                         []string
-	buildCnt                      int
-	alreadyDoneCnt                int
-	noRuleCnt                     int
-	upToDateCnt                   int
-	runCommandCnt                 int
-	pickExplicitRuleCnt           int
-	pickImplicitRuleCnt           int
-	pickSuffixRuleCnt             int
-	pickExplicitRuleWithoutCmdCnt int
+	trace          []string
+	buildCnt       int
+	alreadyDoneCnt int
+	noRuleCnt      int
+	upToDateCnt    int
+	runCommandCnt  int
 }
 
 type AutoVar struct{ ex *Executor }
@@ -126,30 +121,6 @@
 	}
 }
 
-func newExecutor(vars Vars, ruleVars map[string]Vars) *Executor {
-	ex := &Executor{
-		rules:       make(map[string]*Rule),
-		ruleVars:    ruleVars,
-		suffixRules: make(map[string][]*Rule),
-		done:        make(map[string]int64),
-		vars:        vars,
-	}
-
-	for k, v := range map[string]Var{
-		"@": AutoAtVar{AutoVar: AutoVar{ex: ex}},
-		"<": AutoLessVar{AutoVar: AutoVar{ex: ex}},
-		"^": AutoHatVar{AutoVar: AutoVar{ex: ex}},
-		"+": AutoPlusVar{AutoVar: AutoVar{ex: ex}},
-		"*": AutoStarVar{AutoVar: AutoVar{ex: ex}},
-	} {
-		ex.vars[k] = v
-		ex.vars[k+"D"] = AutoSuffixDVar{v: v}
-		ex.vars[k+"F"] = AutoSuffixFVar{v: v}
-	}
-
-	return ex
-}
-
 // TODO(ukai): use time.Time?
 func getTimestamp(filename string) int64 {
 	st, err := os.Stat(filename)
@@ -159,22 +130,6 @@
 	return st.ModTime().Unix()
 }
 
-func (ex *Executor) exists(target string) bool {
-	_, present := ex.rules[target]
-	if present {
-		return true
-	}
-	rule, present := ex.rules[".PHONY"]
-	if present {
-		for _, input := range rule.inputs {
-			if target == input {
-				return true
-			}
-		}
-	}
-	return exists(target)
-}
-
 type runner struct {
 	output      string
 	cmd         string
@@ -232,11 +187,11 @@
 	return r
 }
 
-func (r runner) run() error {
-	if r.echo {
+func (r runner) run(output string) error {
+	if r.echo || dryRunFlag {
 		fmt.Printf("%s\n", r.cmd)
 	}
-	if r.dryRun {
+	if dryRunFlag {
 		return nil
 	}
 	args := []string{r.shell, "-c", r.cmd}
@@ -248,7 +203,7 @@
 	fmt.Printf("%s", out)
 	exit := exitStatus(err)
 	if r.ignoreError && exit != 0 {
-		fmt.Printf("[%s] Error %d (ignored)\n", r.output, exit)
+		fmt.Printf("[%s] Error %d (ignored)\n", output, exit)
 		err = nil
 	}
 	return err
@@ -267,120 +222,8 @@
 	return exit
 }
 
-func replaceSuffix(s string, newsuf string) string {
-	// TODO: Factor out the logic around suffix rules and use
-	// it from substitution references.
-	// http://www.gnu.org/software/make/manual/make.html#Substitution-Refs
-	return fmt.Sprintf("%s.%s", stripExt(s), newsuf)
-}
-
-func (ex *Executor) canPickImplicitRule(rule *Rule, output string) bool {
-	outputPattern := rule.outputPatterns[0]
-	if !matchPattern(outputPattern, output) {
-		return false
-	}
-	for _, input := range rule.inputs {
-		input = substPattern(outputPattern, input, output)
-		if !ex.exists(input) {
-			return false
-		}
-	}
-	return true
-}
-
-func (ex *Executor) mergeImplicitRuleVars(outputs []string, vars Vars) Vars {
-	if len(outputs) != 1 {
-		panic(fmt.Sprintf("Implicit rule should have only one output but %q", outputs))
-	}
-	Log("merge? %q", ex.ruleVars)
-	Log("merge? %q", outputs[0])
-	ivars, present := ex.ruleVars[outputs[0]]
-	if !present {
-		return vars
-	}
-	if vars == nil {
-		return ivars
-	}
-	Log("merge!")
-	v := make(Vars)
-	v.Merge(ivars)
-	v.Merge(vars)
-	return v
-}
-
-func (ex *Executor) pickRule(output string) (*Rule, Vars, bool) {
-	rule, present := ex.rules[output]
-	vars := ex.ruleVars[output]
-	if present {
-		ex.pickExplicitRuleCnt++
-		if len(rule.cmds) > 0 {
-			return rule, vars, true
-		}
-		// If none of the explicit rules for a target has commands,
-		// then `make' searches for an applicable implicit rule to
-		// find some commands.
-		ex.pickExplicitRuleWithoutCmdCnt++
-	}
-
-	for _, irule := range ex.implicitRules {
-		if !ex.canPickImplicitRule(irule, output) {
-			continue
-		}
-		ex.pickImplicitRuleCnt++
-		if rule != nil {
-			r := &Rule{}
-			*r = *rule
-			r.outputPatterns = irule.outputPatterns
-			// implicit rule's prerequisites will be used for $<
-			r.inputs = append(irule.inputs, r.inputs...)
-			r.cmds = irule.cmds
-			// TODO(ukai): filename, lineno?
-			r.cmdLineno = irule.cmdLineno
-			return r, vars, true
-		}
-		if vars != nil {
-			vars = ex.mergeImplicitRuleVars(irule.outputPatterns, vars)
-		}
-		// TODO(ukai): check len(irule.cmd) ?
-		return irule, vars, true
-	}
-
-	outputSuffix := filepath.Ext(output)
-	if !strings.HasPrefix(outputSuffix, ".") {
-		return rule, vars, rule != nil
-	}
-	rules, present := ex.suffixRules[outputSuffix[1:]]
-	if !present {
-		return rule, vars, rule != nil
-	}
-	for _, irule := range rules {
-		if len(irule.inputs) != 1 {
-			panic(fmt.Sprintf("unexpected number of input for a suffix rule (%d)", len(irule.inputs)))
-		}
-		if !ex.exists(replaceSuffix(output, irule.inputs[0])) {
-			continue
-		}
-		ex.pickSuffixRuleCnt++
-		if rule != nil {
-			r := &Rule{}
-			*r = *rule
-			// TODO(ukai): input order is correct?
-			r.inputs = append([]string{replaceSuffix(output, irule.inputs[0])}, r.inputs...)
-			r.cmds = irule.cmds
-			// TODO(ukai): filename, lineno?
-			r.cmdLineno = irule.cmdLineno
-			return r, vars, true
-		}
-		if vars != nil {
-			vars = ex.mergeImplicitRuleVars(irule.outputs, vars)
-		}
-		// TODO(ukai): check len(irule.cmd) ?
-		return irule, vars, true
-	}
-	return rule, vars, rule != nil
-}
-
-func (ex *Executor) build(output string, neededBy string) (int64, error) {
+func (ex *Executor) build(n *DepNode, neededBy string) (int64, error) {
+	output := n.Output
 	Log("Building: %s", output)
 	ex.buildCnt++
 	if ex.buildCnt%100 == 0 {
@@ -399,8 +242,7 @@
 	ex.done[output] = -1
 	outputTs = getTimestamp(output)
 
-	rule, vars, present := ex.pickRule(output)
-	if !present {
+	if !n.HasRule {
 		if outputTs >= 0 {
 			ex.done[output] = outputTs
 			ex.noRuleCnt++
@@ -414,66 +256,15 @@
 		return outputTs, fmt.Errorf("no rule to make target %q", output)
 	}
 
-	var restores []func()
-	if vars != nil {
-		for name, v := range vars {
-			tsv := v.(TargetSpecificVar)
-			restores = append(restores, ex.vars.save(name))
-			switch tsv.op {
-			case ":=", "=":
-				ex.vars[name] = tsv
-			case "+=":
-				oldVar, present := ex.vars[name]
-				if !present || oldVar.String() == "" {
-					ex.vars[name] = tsv
-				} else {
-					ex.vars[name] = oldVar.AppendVar(newEvaluator(ex.vars), tsv)
-				}
-			case "?=":
-				if _, present := ex.vars[name]; !present {
-					ex.vars[name] = tsv
-				}
-			}
-		}
-		defer func() {
-			for _, restore := range restores {
-				restore()
-			}
-		}()
-	}
-
 	latest := int64(-1)
-	var actualInputs []string
-	Log("Building: %s inputs:%q", output, rule.inputs)
-	for _, input := range rule.inputs {
-		if len(rule.outputPatterns) > 0 {
-			if len(rule.outputPatterns) > 1 {
-				panic("TODO: multiple output pattern is not supported yet")
-			}
-			input = substPattern(rule.outputPatterns[0], input, output)
-		} else if rule.isSuffixRule {
-			input = replaceSuffix(output, input)
-		}
-		actualInputs = append(actualInputs, input)
-
-		ex.trace = append(ex.trace, input)
-		ts, err := ex.build(input, output)
-		ex.trace = ex.trace[0 : len(ex.trace)-1]
-		if err != nil {
-			return outputTs, err
-		}
-		if latest < ts {
-			latest = ts
-		}
-	}
-
-	for _, input := range rule.orderOnlyInputs {
-		if exists(input) {
+	Log("Building: %s inputs:%q", output, n.Deps)
+	for _, d := range n.Deps {
+		if d.IsOrderOnly && exists(d.Output) {
 			continue
 		}
 
-		ex.trace = append(ex.trace, input)
-		ts, err := ex.build(input, output)
+		ex.trace = append(ex.trace, d.Output)
+		ts, err := ex.build(d, output)
 		ex.trace = ex.trace[0 : len(ex.trace)-1]
 		if err != nil {
 			return outputTs, err
@@ -491,31 +282,43 @@
 
 	// For automatic variables.
 	ex.currentOutput = output
-	ex.currentInputs = actualInputs
+	ex.currentInputs = n.ActualInputs
+
+	var restores []func()
+	for k, v := range n.TargetSpecificVars {
+		restores = append(restores, ex.vars.save(k))
+		ex.vars[k] = v
+	}
+	defer func() {
+		for _, restore := range restores {
+			restore()
+		}
+	}()
 
 	ev := newEvaluator(ex.vars)
-	ev.filename = rule.filename
-	ev.lineno = rule.cmdLineno
+	ev.filename = n.Filename
+	ev.lineno = n.Lineno
 	var runners []runner
-	Log("Building: %s cmds:%q", output, rule.cmds)
+	Log("Building: %s cmds:%q", output, n.Cmds)
 	r := runner{
 		output: output,
 		echo:   true,
 		dryRun: dryRunFlag,
 		shell:  ex.shell,
 	}
-	for _, cmd := range rule.cmds {
+	for _, cmd := range n.Cmds {
 		for _, r := range evalCmd(ev, r, cmd) {
 			if len(r.cmd) != 0 {
 				runners = append(runners, r)
 			}
 		}
 	}
+
 	for _, r := range runners {
-		err := r.run()
+		err := r.run(output)
 		if err != nil {
 			exit := exitStatus(err)
-			fmt.Printf("[%s] Error %d: %v\n", r.output, exit, err)
+			fmt.Printf("[%s] Error %d: %v\n", output, exit, err)
 			return outputTs, err
 		}
 	}
@@ -530,116 +333,6 @@
 	return outputTs, nil
 }
 
-func (ex *Executor) populateSuffixRule(rule *Rule, output string) bool {
-	if len(output) == 0 || output[0] != '.' {
-		return false
-	}
-	rest := output[1:]
-	dotIndex := strings.IndexByte(rest, '.')
-	// If there is only a single dot or the third dot, this is not a
-	// suffix rule.
-	if dotIndex < 0 || strings.IndexByte(rest[dotIndex+1:], '.') >= 0 {
-		return false
-	}
-
-	// This is a suffix rule.
-	inputSuffix := rest[:dotIndex]
-	outputSuffix := rest[dotIndex+1:]
-	r := &Rule{}
-	*r = *rule
-	r.inputs = []string{inputSuffix}
-	r.isSuffixRule = true
-	ex.suffixRules[outputSuffix] = append([]*Rule{r}, ex.suffixRules[outputSuffix]...)
-	return true
-}
-
-func mergeRules(oldRule, rule *Rule, output string, isSuffixRule bool) *Rule {
-	if oldRule.isDoubleColon != rule.isDoubleColon {
-		Error(rule.filename, rule.lineno, "*** target file %q has both : and :: entries.", output)
-	}
-	if len(oldRule.cmds) > 0 && len(rule.cmds) > 0 && !isSuffixRule && !rule.isDoubleColon {
-		Warn(rule.filename, rule.cmdLineno, "overriding commands for target %q", output)
-		Warn(oldRule.filename, oldRule.cmdLineno, "ignoring old commands for target %q", output)
-	}
-
-	r := &Rule{}
-	*r = *rule
-	if rule.isDoubleColon {
-		r.cmds = append(oldRule.cmds, r.cmds...)
-	} else if len(oldRule.cmds) > 0 && len(rule.cmds) == 0 {
-		r.cmds = oldRule.cmds
-	}
-	// If the latter rule has a command (regardless of the
-	// commands in oldRule), inputs in the latter rule has a
-	// priority.
-	if len(rule.cmds) > 0 {
-		r.inputs = append(r.inputs, oldRule.inputs...)
-		r.orderOnlyInputs = append(r.orderOnlyInputs, oldRule.orderOnlyInputs...)
-	} else {
-		r.inputs = append(oldRule.inputs, r.inputs...)
-		r.orderOnlyInputs = append(oldRule.orderOnlyInputs, r.orderOnlyInputs...)
-	}
-	r.outputPatterns = append(r.outputPatterns, oldRule.outputPatterns...)
-	return r
-}
-
-func (ex *Executor) populateExplicitRule(rule *Rule) {
-	// It seems rules with no outputs are siliently ignored.
-	if len(rule.outputs) == 0 {
-		return
-	}
-	for _, output := range rule.outputs {
-		output = filepath.Clean(output)
-
-		isSuffixRule := ex.populateSuffixRule(rule, output)
-
-		if oldRule, present := ex.rules[output]; present {
-			r := mergeRules(oldRule, rule, output, isSuffixRule)
-			ex.rules[output] = r
-		} else {
-			ex.rules[output] = rule
-			if ex.firstRule == nil && !strings.HasPrefix(output, ".") {
-				ex.firstRule = rule
-			}
-		}
-	}
-}
-
-func (ex *Executor) populateImplicitRule(rule *Rule) {
-	for _, outputPattern := range rule.outputPatterns {
-		r := &Rule{}
-		*r = *rule
-		r.outputPatterns = []string{outputPattern}
-		ex.implicitRules = append(ex.implicitRules, r)
-	}
-}
-
-func (ex *Executor) populateRules(er *EvalResult) {
-	for _, rule := range er.rules {
-		for i, input := range rule.inputs {
-			rule.inputs[i] = filepath.Clean(input)
-		}
-		for i, orderOnlyInput := range rule.orderOnlyInputs {
-			rule.orderOnlyInputs[i] = filepath.Clean(orderOnlyInput)
-		}
-		ex.populateExplicitRule(rule)
-
-		if len(rule.outputs) == 0 {
-			ex.populateImplicitRule(rule)
-		}
-	}
-
-	// Reverse the implicit rule for easier lookup.
-	for i, r := range ex.implicitRules {
-		if i >= len(ex.implicitRules)/2 {
-			break
-		}
-		j := len(ex.implicitRules) - i - 1
-		ex.implicitRules[i] = ex.implicitRules[j]
-		ex.implicitRules[j] = r
-	}
-}
-
 func (ex *Executor) reportStats() {
 	if !katiLogFlag && !katiStatsFlag {
 		return
@@ -647,44 +340,39 @@
 
 	LogStats("build=%d alreadyDone=%d noRule=%d, upToDate=%d runCommand=%d",
 		ex.buildCnt, ex.alreadyDoneCnt, ex.noRuleCnt, ex.upToDateCnt, ex.runCommandCnt)
-	LogStats("explicit=%d implicit=%d suffix=%d explicitWOCmd=%d",
-		ex.pickExplicitRuleCnt, ex.pickImplicitRuleCnt, ex.pickSuffixRuleCnt, ex.pickExplicitRuleWithoutCmdCnt)
 	if len(ex.trace) > 1 {
 		LogStats("trace=%q", ex.trace)
 	}
 }
 
-func NewExecutor(er *EvalResult, vars Vars) *Executor {
-	ex := newExecutor(vars, er.ruleVars)
+func NewExecutor(vars Vars) *Executor {
+	ex := &Executor{
+		rules:       make(map[string]*Rule),
+		suffixRules: make(map[string][]*Rule),
+		done:        make(map[string]int64),
+		vars:        vars,
+	}
 	// TODO: We should move this to somewhere around evalCmd so that
 	// we can handle SHELL in target specific variables.
 	shellVar := ex.vars.Lookup("SHELL")
 	ex.shell = shellVar.String()
-
-	ex.populateRules(er)
+	for k, v := range map[string]Var{
+		"@": AutoAtVar{AutoVar: AutoVar{ex: ex}},
+		"<": AutoLessVar{AutoVar: AutoVar{ex: ex}},
+		"^": AutoHatVar{AutoVar: AutoVar{ex: ex}},
+		"+": AutoPlusVar{AutoVar: AutoVar{ex: ex}},
+		"*": AutoStarVar{AutoVar: AutoVar{ex: ex}},
+	} {
+		ex.vars[k] = v
+		ex.vars[k+"D"] = AutoSuffixDVar{v: v}
+		ex.vars[k+"F"] = AutoSuffixFVar{v: v}
+	}
 	return ex
 }
 
-func (ex *Executor) Exec(targets []string) error {
-	if len(targets) == 0 {
-		if ex.firstRule == nil {
-			ErrorNoLocation("*** No targets.")
-		}
-		targets = append(targets, ex.firstRule.outputs[0])
+func (ex *Executor) Exec(roots []*DepNode) error {
+	for _, root := range roots {
+		ex.build(root, "")
 	}
-
-	LogStats("%d variables", len(ex.vars))
-	LogStats("%d explicit rules", len(ex.rules))
-	LogStats("%d implicit rules", len(ex.implicitRules))
-	LogStats("%d suffix rules", len(ex.suffixRules))
-
-	for _, target := range targets {
-		ex.trace = []string{target}
-		_, err := ex.build(target, "")
-		if err != nil {
-			return err
-		}
-	}
-	ex.reportStats()
 	return nil
 }
diff --git a/main.go b/main.go
index 5f81dc1..bfcd7f7 100644
--- a/main.go
+++ b/main.go
@@ -152,11 +152,19 @@
 	LogStats("eval time: %q", time.Now().Sub(startTime))
 
 	startTime = time.Now()
-	ex := NewExecutor(er, vars)
-	LogStats("exec prepare time: %q", time.Now().Sub(startTime))
+	db := NewDepBuilder(er, vars)
+	LogStats("eval command prepare time: %q", time.Now().Sub(startTime))
 
 	startTime = time.Now()
-	err = ex.Exec(targets)
+	nodes, err2 := db.Eval(targets)
+	if err2 != nil {
+		panic(err2)
+	}
+	LogStats("eval command time: %q", time.Now().Sub(startTime))
+
+	startTime = time.Now()
+	ex := NewExecutor(vars)
+	err = ex.Exec(nodes)
 	if err != nil {
 		panic(err)
 	}