package main import ( "bytes" "fmt" "strings" ) type EvalResult struct { vars Vars rules []*Rule ruleVars map[string]Vars } type Evaluator struct { paramVars []tmpval // $1 => paramVars[1] outVars Vars outRules []*Rule outRuleVars map[string]Vars vars Vars lastRule *Rule currentScope Vars filename string lineno int } func newEvaluator(vars map[string]Var) *Evaluator { return &Evaluator{ outVars: make(Vars), vars: vars, outRuleVars: make(map[string]Vars), } } func (ev *Evaluator) Value(v Value) []byte { if v, ok := v.(Valuer); ok { return v.Value() } var buf bytes.Buffer v.Eval(&buf, ev) return buf.Bytes() } // TODO(ukai): use unicode.IsSpace? func isWhitespace(b byte) bool { switch b { case ' ', '\t', '\n', '\r': return true } return false } func (ev *Evaluator) Values(v Value) [][]byte { var buf bytes.Buffer v.Eval(&buf, ev) val := buf.Bytes() var values [][]byte b := -1 for i := 0; i < len(val); i++ { if b < 0 { if isWhitespace(val[i]) { continue } b = i } else { if isWhitespace(val[i]) { values = append(values, val[b:i]) b = -1 continue } } } if b >= 0 { values = append(values, val[b:]) } return values } func (ev *Evaluator) evalAssign(ast *AssignAST) { ev.lastRule = nil lhs, rhs := ev.evalAssignAST(ast) Log("ASSIGN: %s=%q (flavor:%q)", lhs, rhs, rhs.Flavor()) if len(lhs) == 0 { Error(ast.filename, ast.lineno, "*** empty variable name.") } ev.outVars.Assign(lhs, rhs) } func (ev *Evaluator) evalAssignAST(ast *AssignAST) (string, Var) { ev.filename = ast.filename ev.lineno = ast.lineno v, _, err := parseExpr([]byte(ast.lhs), nil) if err != nil { panic(fmt.Errorf("parse %s:%d %v", ev.filename, ev.lineno, err)) } var lhs string switch v := v.(type) { case literal: lhs = string(v) case tmpval: lhs = string(v) default: lhs = string(bytes.TrimSpace(ev.Value(v))) } rhs := ast.evalRHS(ev, lhs) return lhs, rhs } func (ev *Evaluator) setTargetSpecificVar(assign *AssignAST, output string) { vars, present := ev.outRuleVars[output] if !present { vars = make(Vars) ev.outRuleVars[output] = vars } ev.currentScope = vars lhs, rhs := ev.evalAssignAST(assign) Log("rule outputs:%q assign:%q=%q (flavor:%q)", output, lhs, rhs, rhs.Flavor()) vars.Assign(lhs, TargetSpecificVar{v: rhs, op: assign.op}) ev.currentScope = nil } func (ev *Evaluator) evalMaybeRule(ast *MaybeRuleAST) { ev.lastRule = nil ev.filename = ast.filename ev.lineno = ast.lineno expr := ast.expr if ast.semicolonIndex >= 0 { expr = expr[0:ast.semicolonIndex] } if ast.equalIndex >= 0 { expr = expr[0:ast.equalIndex] } lexpr, _, err := parseExpr([]byte(expr), nil) if err != nil { panic(fmt.Errorf("parse %s:%d %v", ev.filename, ev.lineno, err)) } line := ev.Value(lexpr) if ast.equalIndex >= 0 { line = append(line, []byte(ast.expr[ast.equalIndex:])...) } Log("rule? %q=>%q", ast.expr, line) // See semicolon.mk. if len(bytes.TrimRight(line, " \t\n;")) == 0 { return } rule := &Rule{ filename: ast.filename, lineno: ast.lineno, } assign, err := rule.parse(string(line)) // use []byte? if err != nil { Error(ast.filename, ast.lineno, "%v", err.Error()) } Log("rule %q => outputs:%q, inputs:%q", line, rule.outputs, rule.inputs) // TODO: Pretty print. //Log("RULE: %s=%s (%d commands)", lhs, rhs, len(cmds)) if assign != nil { if ast.semicolonIndex >= 0 { // TODO(ukai): reuse lexpr above? lexpr, _, err := parseExpr([]byte(ast.expr), nil) if err != nil { panic(fmt.Errorf("parse %s:%d %v", ev.filename, ev.lineno, err)) } assign, err = rule.parse(string(ev.Value(lexpr))) if err != nil { Error(ast.filename, ast.lineno, "%v", err.Error()) } } for _, output := range rule.outputs { ev.setTargetSpecificVar(assign, output) } for _, output := range rule.outputPatterns { ev.setTargetSpecificVar(assign, output) } return } if ast.semicolonIndex > 0 { rule.cmds = append(rule.cmds, ast.expr[ast.semicolonIndex+1:]) } Log("rule outputs:%q cmds:%q", rule.outputs, rule.cmds) ev.lastRule = rule ev.outRules = append(ev.outRules, rule) } func (ev *Evaluator) evalCommand(ast *CommandAST) { ev.filename = ast.filename ev.lineno = ast.lineno if ev.lastRule == nil { // This could still be an assignment statement. See // assign_after_tab.mk. if strings.IndexByte(ast.cmd, '=') >= 0 { line := trimLeftSpace(ast.cmd) mk, err := ParseMakefileString(line, ast.filename, ast.lineno) if err != nil { panic(err) } if len(mk.stmts) == 1 && mk.stmts[0].(*AssignAST) != nil { ev.eval(mk.stmts[0]) } return } Error(ast.filename, ast.lineno, "*** commands commence before first target.") } ev.lastRule.cmds = append(ev.lastRule.cmds, ast.cmd) if ev.lastRule.cmdLineno == 0 { ev.lastRule.cmdLineno = ast.lineno } } func (ev *Evaluator) LookupVar(name string) Var { if ev.currentScope != nil { v := ev.currentScope.Lookup(name) if v.IsDefined() { return v } } v := ev.outVars.Lookup(name) if v.IsDefined() { return v } return ev.vars.Lookup(name) } func (ev *Evaluator) LookupVarInCurrentScope(name string) Var { if ev.currentScope != nil { v := ev.currentScope.Lookup(name) return v } v := ev.outVars.Lookup(name) if v.IsDefined() { return v } return ev.vars.Lookup(name) } func (ev *Evaluator) evalInclude(ast *IncludeAST) { ev.lastRule = nil ev.filename = ast.filename ev.lineno = ast.lineno Log("%s:%d include %q", ev.filename, ev.lineno, ast.expr) // TODO: Handle glob v, _, err := parseExpr([]byte(ast.expr), nil) if err != nil { panic(err) } files := ev.Values(v) for _, f := range files { file := string(f) Log("Reading makefile %q", file) mk, err := ParseMakefile(file) if err != nil { if ast.op == "include" { panic(err) } else { continue } } makefileList := ev.outVars.Lookup("MAKEFILE_LIST") makefileList = makefileList.Append(ev, mk.filename) ev.outVars.Assign("MAKEFILE_LIST", makefileList) for _, stmt := range mk.stmts { ev.eval(stmt) } } } func (ev *Evaluator) evalIf(ast *IfAST) { var isTrue bool switch ast.op { case "ifdef", "ifndef": expr, _, err := parseExpr([]byte(ast.lhs), nil) if err != nil { panic(fmt.Errorf("ifdef parse %s:%d %v", ast.filename, ast.lineno, err)) } vname := ev.Value(expr) v := ev.LookupVar(string(vname)) value := ev.Value(v) isTrue = (len(value) > 0) == (ast.op == "ifdef") Log("%s lhs=%q value=%q => %t", ast.op, ast.lhs, value, isTrue) case "ifeq", "ifneq": lexpr, _, err := parseExpr([]byte(ast.lhs), nil) if err != nil { panic(fmt.Errorf("ifeq lhs parse %s:%d %v", ast.filename, ast.lineno, err)) } rexpr, _, err := parseExpr([]byte(ast.rhs), nil) if err != nil { panic(fmt.Errorf("ifeq rhs parse %s:%d %v", ast.filename, ast.lineno, err)) } lhs := string(ev.Value(lexpr)) rhs := string(ev.Value(rexpr)) isTrue = (lhs == rhs) == (ast.op == "ifeq") Log("%s lhs=%q %q rhs=%q %q => %t", ast.op, ast.lhs, lhs, ast.rhs, rhs, isTrue) default: panic(fmt.Sprintf("unknown if statement: %q", ast.op)) } var stmts []AST if isTrue { stmts = ast.trueStmts } else { stmts = ast.falseStmts } for _, stmt := range stmts { ev.eval(stmt) } } func (ev *Evaluator) eval(ast AST) { ast.eval(ev) } func Eval(mk Makefile, vars Vars) (er *EvalResult, err error) { ev := newEvaluator(vars) defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic in eval %s: %v", mk.filename, r) } }() makefile_list := vars.Lookup("MAKEFILE_LIST") makefile_list = makefile_list.Append(ev, mk.filename) ev.outVars.Assign("MAKEFILE_LIST", makefile_list) for _, stmt := range mk.stmts { ev.eval(stmt) } return &EvalResult{ vars: ev.outVars, rules: ev.outRules, ruleVars: ev.outRuleVars, }, nil }