kati: initial commit (from hamaji)
diff --git a/eval.go b/eval.go
new file mode 100644
index 0000000..a676171
--- /dev/null
+++ b/eval.go
@@ -0,0 +1,167 @@
+package main
+
+import (
+	"bytes"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+type Rule struct {
+	output string
+	inputs []string
+	cmds   []string
+}
+
+type EvalResult struct {
+	vars  map[string]string
+	rules []*Rule
+	refs  map[string]bool
+}
+
+type Evaluator struct {
+	out_vars  map[string]string
+	out_rules []*Rule
+	refs      map[string]bool
+	vars      map[string]string
+	cur_rule  *Rule
+}
+
+func newEvaluator() *Evaluator {
+	return &Evaluator{
+		out_vars: make(map[string]string),
+		refs:     make(map[string]bool),
+		vars:     make(map[string]string),
+	}
+}
+
+func (ev *Evaluator) evalFunction(ex string) (string, bool) {
+	if strings.HasPrefix(ex, "wildcard ") {
+		arg := ex[len("wildcard "):]
+
+		files, err := filepath.Glob(arg)
+		if err != nil {
+			panic(err)
+		}
+		return strings.Join(files, " "), true
+	} else if strings.HasPrefix(ex, "shell ") {
+		arg := ex[len("shell "):]
+
+		args := []string{"/bin/sh", "-c", arg}
+		cmd := exec.Cmd{
+			Path: args[0],
+			Args: args,
+		}
+		out, err := cmd.CombinedOutput()
+		if err != nil {
+			panic(err)
+		}
+		re, err := regexp.Compile("\\s")
+		if err != nil {
+			panic(err)
+		}
+		return string(re.ReplaceAllString(string(out), " ")), true
+	}
+	return "", false
+}
+
+func (ev *Evaluator) evalExprSlice(ex string, term byte) (string, int) {
+	var buf bytes.Buffer
+	i := 0
+	for i < len(ex) && ex[i] != term {
+		ch := ex[i]
+		i++
+		switch ch {
+		case '$':
+			if i >= len(ex) || ex[i] == term {
+				continue
+			}
+
+			var varname string
+			switch ex[i] {
+			case '@':
+				buf.WriteString(ev.cur_rule.output)
+				i++
+				continue
+			case '(':
+				v, j := ev.evalExprSlice(ex[i+1:], ')')
+				i += j + 2
+				if r, done := ev.evalFunction(v); done {
+					buf.WriteString(r)
+					continue
+				}
+
+				varname = v
+			default:
+				varname = string(ex[i])
+				i++
+			}
+
+			value, present := ev.vars[varname]
+			if !present {
+				ev.refs[varname] = true
+				value = ev.out_vars[varname]
+			}
+			buf.WriteString(value)
+
+		default:
+			buf.WriteByte(ch)
+		}
+	}
+	return buf.String(), i
+}
+
+func (ev *Evaluator) evalExpr(ex string) string {
+	r, i := ev.evalExprSlice(ex, 0)
+	if len(ex) != i {
+		panic("Had a null character?")
+	}
+	return r
+}
+
+func (ev *Evaluator) evalAssign(ast *AssignAST) {
+	lhs := ev.evalExpr(ast.lhs)
+	rhs := ev.evalExpr(ast.rhs)
+	Log("ASSIGN: %s=%s", lhs, rhs)
+	ev.out_vars[lhs] = rhs
+}
+
+func (ev *Evaluator) evalRule(ast *RuleAST) {
+	ev.cur_rule = &Rule{}
+	lhs := ev.evalExpr(ast.lhs)
+	ev.cur_rule.output = lhs
+	rhs := ev.evalExpr(ast.rhs)
+	if rhs != "" {
+		ev.cur_rule.inputs = strings.Split(rhs, " ")
+	}
+	var cmds []string
+	for _, cmd := range ast.cmds {
+		cmds = append(cmds, ev.evalExpr(cmd))
+	}
+	Log("RULE: %s=%s", lhs, rhs)
+	ev.cur_rule.cmds = cmds
+	ev.out_rules = append(ev.out_rules, ev.cur_rule)
+	ev.cur_rule = nil
+}
+
+func (ev *Evaluator) eval(ast AST) {
+	switch ast.typ() {
+	case AST_ASSIGN:
+		ev.evalAssign(ast.(*AssignAST))
+	case AST_RULE:
+		ev.evalRule(ast.(*RuleAST))
+	}
+}
+
+func Eval(mk Makefile) *EvalResult {
+	ev := newEvaluator()
+	for _, stmt := range mk.stmts {
+		ev.eval(stmt)
+	}
+	return &EvalResult{
+		vars:  ev.out_vars,
+		rules: ev.out_rules,
+		refs:  ev.refs,
+	}
+}