blob: c3dfd96b833116fe3bc03f985da769195b747a02 [file] [log] [blame]
package main
import (
"bytes"
"fmt"
"strings"
)
type EvalResult struct {
vars map[string]string
rules []*Rule
refs map[string]bool
}
type Evaluator struct {
outVars map[string]string
outRules []*Rule
refs map[string]bool
vars map[string]string
curRule *Rule
funcs map[string]Func
filename string
lineno int
}
func newEvaluator(vars map[string]string) *Evaluator {
return &Evaluator{
outVars: make(map[string]string),
refs: make(map[string]bool),
vars: vars,
funcs: map[string]Func{
"subst": funcSubst,
"wildcard": funcWildcard,
"shell": funcShell,
"warning": funcWarning,
},
}
}
func (ev *Evaluator) evalFunction(args []string) (string, bool) {
if len(args) == 0 {
return "", false
}
i := strings.IndexAny(args[0], " \t")
if i < 0 {
return "", false
}
cmd := strings.TrimSpace(args[0][:i])
args[0] = strings.TrimLeft(args[0][i+1:], " \t")
if f, ok := ev.funcs[cmd]; ok {
return f(ev, args), true
}
return "", false
}
func (ev *Evaluator) evalExprSlice(ex string) (string, int) {
var buf bytes.Buffer
i := 0
Loop:
for i < len(ex) {
ch := ex[i]
i++
switch ch {
case '$':
if i >= len(ex) {
break Loop
}
var varname string
switch ex[i] {
case '@':
buf.WriteString(ev.curRule.output)
i++
continue
case '(', '{':
args, rest, err := parseExpr(ex[i:])
if err != nil {
}
i += rest
if r, done := ev.evalFunction(args); done {
buf.WriteString(r)
continue
}
varname = strings.Join(args, ",")
default:
varname = string(ex[i])
i++
}
value, present := ev.vars[varname]
if !present {
ev.refs[varname] = true
value = ev.outVars[varname]
}
buf.WriteString(ev.evalExpr(value))
default:
buf.WriteByte(ch)
}
}
return buf.String(), i
}
func (ev *Evaluator) evalExpr(ex string) string {
r, i := ev.evalExprSlice(ex)
if len(ex) != i {
panic(fmt.Sprintf("Had a null character? %q, %d", ex, i))
}
return r
}
func (ev *Evaluator) evalAssign(ast *AssignAST) {
ev.filename = ast.filename
ev.lineno = ast.lineno
lhs := ev.evalExpr(ast.lhs)
rhs := ast.evalRHS(ev, lhs)
Log("ASSIGN: %s=%s", lhs, rhs)
ev.outVars[lhs] = rhs
}
func (ev *Evaluator) evalMaybeRule(ast *MaybeRuleAST) {
ev.filename = ast.filename
ev.lineno = ast.lineno
line := ev.evalExpr(ast.expr)
if strings.TrimSpace(line) == "" {
if len(ast.cmds) > 0 {
Error(ast.filename, ast.cmdLineno, "*** commands commence before first target.")
}
return
}
ev.curRule = &Rule{
filename: ast.filename,
lineno: ast.lineno,
cmdLineno: ast.cmdLineno,
}
ev.curRule.parse(line)
var cmds []string
for _, cmd := range ast.cmds {
cmds = append(cmds, ev.evalExpr(cmd))
}
// TODO: Pretty print.
//Log("RULE: %s=%s (%d commands)", lhs, rhs, len(cmds))
ev.curRule.cmds = cmds
ev.outRules = append(ev.outRules, ev.curRule)
ev.curRule = nil
}
/*
func (ev *Evaluator) evalRawExpr(ast *RawExprAST) {
ev.filename = ast.filename
ev.lineno = ast.lineno
result := ev.evalExpr(ast.expr)
if result != "" {
// TODO: Fix rule_in_var.mk.
Error(ast.filename, ast.lineno, "*** missing separator.")
}
}
*/
func (ev *Evaluator) getVar(name string) (string, bool) {
value, present := ev.outVars[name]
if present {
return value, true
}
value, present = ev.vars[name]
if present {
return value, true
}
return "", false
}
func (ev *Evaluator) getVars() map[string]string {
vars := make(map[string]string)
for k, v := range ev.vars {
vars[k] = v
}
for k, v := range ev.outVars {
vars[k] = v
}
return vars
}
func (ev *Evaluator) evalInclude(ast *IncludeAST) {
ev.filename = ast.filename
ev.lineno = ast.lineno
// TODO: Handle glob
files := strings.Split(ev.evalExpr(ast.expr), " ")
for _, file := range files {
mk, err := ParseMakefile(file)
if err != nil {
if ast.op == "include" {
panic(err)
} else {
continue
}
}
er, err2 := Eval(mk, ev.getVars())
if err2 != nil {
panic(err2)
}
for k, v := range er.vars {
ev.outVars[k] = v
}
for _, r := range er.rules {
ev.outRules = append(ev.outRules, r)
}
for r, _ := range er.refs {
ev.refs[r] = true
}
}
}
func (ev *Evaluator) evalIf(ast *IfAST) {
var isTrue bool
switch ast.op {
case "ifdef", "ifndef":
value, _ := ev.getVar(ev.evalExpr(ast.lhs))
isTrue = (value != "") == (ast.op == "ifdef")
case "ifeq", "ifneq":
lhs := ev.evalExpr(ast.lhs)
rhs := ev.evalExpr(ast.rhs)
isTrue = (lhs == rhs) == (ast.op == "ifeq")
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 map[string]string) (er *EvalResult, err error) {
ev := newEvaluator(vars)
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
for _, stmt := range mk.stmts {
ev.eval(stmt)
}
return &EvalResult{
vars: ev.outVars,
rules: ev.outRules,
refs: ev.refs,
}, nil
}