blob: 5dc776477edacfadd80344c547f5e0f62773eeab [file] [log] [blame]
package main
import (
"bytes"
"fmt"
"strings"
)
type EvalResult struct {
vars *VarTab
rules []*Rule
}
type Evaluator struct {
outVars *VarTab
outRules []*Rule
vars *VarTab
funcs map[string]Func
filename string
lineno int
}
func newEvaluator(vars *VarTab) *Evaluator {
return &Evaluator{
outVars: NewVarTab(nil),
vars: vars,
// TODO(ukai): use singleton global func tab?
funcs: map[string]Func{
"subst": funcSubst,
"patsubst": funcPatsubst,
"strip": funcStrip,
"findstring": funcFindstring,
"filter": funcFilter,
"filter-out": funcFilterOut,
"sort": funcSort,
"word": funcWord,
"wordlist": funcWordlist,
"words": funcWords,
"firstword": funcFirstword,
"lastword": funcLastword,
"join": funcJoin,
"wildcard": funcWildcard,
"dir": funcDir,
"notdir": funcNotdir,
"suffix": funcSuffix,
"basename": funcBasename,
"addsuffix": funcAddsuffix,
"addprefix": funcAddprefix,
"realpath": funcRealpath,
"abspath": funcAbspath,
"if": funcIf,
"and": funcAnd,
"or": funcOr,
"foreach": funcForeach,
"value": funcValue,
"eval": funcEval,
"origin": funcOrigin,
"shell": funcShell,
"call": funcCall,
"flavor": funcFlavor,
"info": funcInfo,
"warning": funcWarning,
"error": funcError,
},
}
}
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
var subst []string
switch ex[i] {
case '$':
buf.WriteByte('$')
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, ",")
vs := strings.SplitN(varname, ":", 2)
if len(vs) == 2 {
ss := strings.SplitN(vs[1], "=", 2)
if len(ss) == 2 {
varname = vs[0]
subst = ss
}
}
varname = ev.evalExpr(varname)
default:
varname = string(ex[i])
i++
}
// TODO: lookup order is correct?
value := ev.vars.Lookup(varname)
if !value.IsDefined() {
value = ev.outVars.Lookup(varname)
}
val := value.Eval(ev)
Log("var %q=>%q=>%q", varname, value, val)
if subst != nil {
var vals []string
for _, v := range splitSpaces(val) {
vals = append(vals, substRef(subst[0], subst[1], v))
}
val = strings.Join(vals, " ")
}
buf.WriteString(val)
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) {
lhs, rhs := ev.evalAssignAST(ast)
Log("ASSIGN: %s=%q (flavor:%q)", lhs, rhs, rhs.Flavor())
ev.outVars.Assign(lhs, rhs)
}
func (ev *Evaluator) evalAssignAST(ast *AssignAST) (string, Var) {
ev.filename = ast.filename
ev.lineno = ast.lineno
lhs := ev.evalExpr(ast.lhs)
rhs := ast.evalRHS(ev, lhs)
return lhs, rhs
}
func (ev *Evaluator) evalMaybeRule(ast *MaybeRuleAST) {
ev.filename = ast.filename
ev.lineno = ast.lineno
line := ev.evalExpr(ast.expr)
Log("rule? %q=>%q", ast.expr, line)
if strings.TrimSpace(line) == "" {
if len(ast.cmds) > 0 {
Error(ast.filename, ast.cmdLineno, "*** commands commence before first target.")
}
return
}
rule := &Rule{
filename: ast.filename,
lineno: ast.lineno,
cmdLineno: ast.cmdLineno,
}
assign, err := rule.parse(line)
if err != nil {
Error(ast.filename, ast.lineno, err.Error())
}
Log("rule %q => outputs:%q, inputs:%q", line, rule.outputs, rule.inputs)
// It seems rules with no outputs are siliently ignored.
if len(rule.outputs) == 0 && len(rule.outputPatterns) == 0 {
return
}
// TODO: Pretty print.
//Log("RULE: %s=%s (%d commands)", lhs, rhs, len(cmds))
if assign != nil {
if len(ast.cmds) > 0 {
Error(ast.filename, ast.lineno, "*** commands commence before first target. Stop.")
}
rule.vars = NewVarTab(nil)
lhs, rhs := ev.evalAssignAST(assign)
rule.vars.Assign(lhs, rhs)
} else {
rule.cmds = ast.cmds
}
ev.outRules = append(ev.outRules, rule)
}
func (ev *Evaluator) LookupVar(name string) Var {
v := ev.outVars.Lookup(name)
if v.IsDefined() {
return v
}
return ev.vars.Lookup(name)
}
func (ev *Evaluator) VarTab() *VarTab {
vars := NewVarTab(nil)
for k, v := range ev.vars.Vars() {
vars.Assign(k, v)
}
for k, v := range ev.outVars.Vars() {
vars.Assign(k, v)
}
return vars
}
func (ev *Evaluator) evalInclude(ast *IncludeAST) {
ev.filename = ast.filename
ev.lineno = ast.lineno
// TODO: Handle glob
files := splitSpaces(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.VarTab())
if err2 != nil {
panic(err2)
}
for k, v := range er.vars.Vars() {
ev.outVars.Assign(k, v)
}
for _, r := range er.rules {
ev.outRules = append(ev.outRules, r)
}
}
}
func (ev *Evaluator) evalIf(ast *IfAST) {
var isTrue bool
switch ast.op {
case "ifdef", "ifndef":
value := ev.LookupVar(ev.evalExpr(ast.lhs)).Eval(ev)
isTrue = value != "" == (ast.op == "ifdef")
Log("%s lhs=%q value=%q => %t", ast.op, ast.lhs, value, isTrue)
case "ifeq", "ifneq":
lhs := ev.evalExpr(ast.lhs)
rhs := ev.evalExpr(ast.rhs)
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 *VarTab) (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,
}, nil
}