diff --git a/newt/cli/util.go b/newt/cli/util.go index 75f60282f..06ac6a475 100644 --- a/newt/cli/util.go +++ b/newt/cli/util.go @@ -40,6 +40,8 @@ import ( "mynewt.apache.org/newt/util" ) +var GlobalResolver *resolve.Resolver + const TARGET_KEYWORD_ALL string = "all" const TARGET_DEFAULT_DIR string = "targets" const MFG_DEFAULT_DIR string = "mfgs" diff --git a/newt/expr/expr.go b/newt/expr/expr.go new file mode 100644 index 000000000..885d4dda8 --- /dev/null +++ b/newt/expr/expr.go @@ -0,0 +1,404 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package expr + +import ( + "go/ast" + "go/parser" + "go/token" + "mynewt.apache.org/newt/util" + "strconv" +) + +type RawString struct { + S string +} + +type ExprQuery interface { + ExprGetValue(name string) (string, bool) + ExprGetValueChoices(name string) ([]string, bool) + ExprSetValue(name string, value interface{}, err error) + ExprQueryPkg(name string, pkgName string) bool +} + +type exprEntry struct { + val interface{} + failed bool + done bool +} + +type exprCtx struct { + q ExprQuery + ees map[string]*exprEntry + entryName string +} + +func int2bool(x int) bool { + return x != 0 +} + +func bool2int(b bool) int { + if b { + return 1 + } + + return 0 +} + +func (expr *exprCtx) evalBasicLit(e *ast.BasicLit) (interface{}, error) { + kind := e.Kind + val := e.Value + + switch kind { + case token.INT: + v, err := strconv.ParseInt(val, 0, 0) + return int(v), err + case token.STRING: + v, err := strconv.Unquote(val) + return string(v), err + case token.FLOAT: + return 0, util.FmtNewtError("Unsupported non-integer number (%s) literal found, "+ + "consider using integer division instead", e.Value) + } + + return 0, util.FmtNewtError("Invalid literal used in expression") +} + +func (expr *exprCtx) evalBinaryExpr(e *ast.BinaryExpr) (int, error) { + switch e.Op { + case token.ADD: + case token.SUB: + case token.MUL: + case token.QUO: + case token.REM: + case token.LAND: + case token.LOR: + case token.EQL: + case token.LSS: + case token.GTR: + case token.NEQ: + case token.LEQ: + case token.GEQ: + default: + return 0, util.FmtNewtError("Invalid \"%s\" operator in expression", e.Op.String()) + } + + var x interface{} + var y interface{} + var err error + + x, err = expr.evalNode(e.X, false) + if err != nil { + return 0, err + } + y, err = expr.evalNode(e.Y, false) + if err != nil { + return 0, err + } + + xv, xok := x.(int) + yv, yok := y.(int) + + if xok != yok { + return 0, util.FmtNewtError("Mismatched types for \"%s\" operator in expression", e.Op.String()) + } + + ret := 0 + + if xok { + switch e.Op { + case token.ADD: + ret = xv + yv + case token.SUB: + ret = xv - yv + case token.MUL: + ret = xv * yv + case token.QUO: + ret = xv / yv + case token.REM: + ret = xv % yv + case token.LAND: + ret = bool2int(int2bool(xv) && int2bool(yv)) + case token.LOR: + ret = bool2int(int2bool(xv) || int2bool(yv)) + case token.EQL: + ret = bool2int(xv == yv) + case token.LSS: + ret = bool2int(xv < yv) + case token.GTR: + ret = bool2int(xv > yv) + case token.NEQ: + ret = bool2int(xv != yv) + case token.LEQ: + ret = bool2int(xv <= yv) + case token.GEQ: + ret = bool2int(xv >= yv) + } + } else { + // Each node is evaluated to int/string only so below assertions + // should never fail + switch e.Op { + case token.EQL: + ret = bool2int(x.(string) == y.(string)) + case token.NEQ: + ret = bool2int(x.(string) != y.(string)) + default: + return 0, util.FmtNewtError("Operator \"%s\" not supported for string literals", + e.Op.String()) + } + } + + return ret, nil +} + +func (expr *exprCtx) evalUnaryExpr(e *ast.UnaryExpr) (int, error) { + if e.Op != token.NOT && e.Op != token.SUB { + return 0, util.FmtNewtError("Invalid \"%s\" operator in expression", e.Op.String()) + } + + x, err := expr.evalNode(e.X, false) + if err != nil { + return 0, err + } + + xv, ok := x.(int) + if !ok { + return 0, util.FmtNewtError("String literals not applicable for \"%s\" operator", e.Op.String()) + } + + var ret int + + switch e.Op { + case token.NOT: + ret = bool2int(!int2bool(xv)) + case token.SUB: + ret = -xv + } + + return ret, nil +} + +func (expr *exprCtx) evalCallExpr(e *ast.CallExpr) (interface{}, error) { + f := e.Fun.(*ast.Ident) + expectedArgc := -1 + minArgc := -1 + + switch f.Name { + case "raw", "has_pkg": + expectedArgc = 1 + case "min", "max": + expectedArgc = 2 + case "in_range", "clamp", "ite": + expectedArgc = 3 + case "in_set": + minArgc = 2 + default: + return 0, util.FmtNewtError("Invalid function in expression: \"%s\"", f.Name) + } + + argc := len(e.Args) + + if expectedArgc > 0 && argc != expectedArgc { + return 0, util.FmtNewtError("Invalid number of arguments for \"%s\": expected %d, got %d", + f.Name, expectedArgc, argc) + } + + if minArgc > 0 && argc < minArgc { + return 0, util.FmtNewtError("Invalid number of arguments for \"%s\": expected at least %d, got %d", + f.Name, minArgc, argc) + } + + var argv []interface{} + for _, node := range e.Args { + arg, err := expr.evalNode(node, false) + if err != nil { + return 0, err + } + + argv = append(argv, arg) + } + + var ret interface{} + + switch f.Name { + case "raw": + s, _ := argv[0].(string) + rs := RawString{s} + return rs, nil + case "has_pkg": + ret = bool2int(expr.q.ExprQueryPkg(expr.entryName, argv[0].(string))) + case "min": + a, ok1 := argv[0].(int) + b, ok2 := argv[1].(int) + if !ok1 || !ok2 { + return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name) + } + ret = util.Min(a, b) + case "max": + a, ok1 := argv[0].(int) + b, ok2 := argv[1].(int) + if !ok1 || !ok2 { + return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name) + } + ret = util.Max(a, b) + case "clamp": + v, ok1 := argv[0].(int) + a, ok2 := argv[1].(int) + b, ok3 := argv[2].(int) + if !ok1 || !ok2 || !ok3 { + return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name) + } + if v < a { + ret = a + } else if v > b { + ret = b + } else { + ret = v + } + case "ite": + v, ok1 := argv[0].(int) + if !ok1 { + return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name) + } + if v != 0 { + ret = argv[1] + } else { + ret = argv[2] + } + case "in_range": + v, ok1 := argv[0].(int) + a, ok2 := argv[1].(int) + b, ok3 := argv[2].(int) + if !ok1 || !ok2 || !ok3 { + return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name) + } + ret = bool2int(v >= a && v <= b) + case "in_set": + m := make(map[interface{}]struct{}) + for _, arg := range argv[1:] { + m[arg] = struct{}{} + } + _, ok := m[argv[0]] + ret = bool2int(ok) + default: + panic("This should never happen :>") + } + + return ret, nil +} + +func (expr *exprCtx) evalIdent(node *ast.Ident, direct bool) (interface{}, error) { + name := node.Name + + if direct { + vs, ok := expr.q.ExprGetValueChoices(expr.entryName) + if ok { + for _, v := range vs { + if v == name { + return v, nil + } + } + } + } + + ee, err := expr.evalEntry(name) + if err != nil { + return nil, err + } + + return ee.val, err +} + +func (expr *exprCtx) evalNode(node ast.Node, direct bool) (interface{}, error) { + switch n := node.(type) { + case *ast.BasicLit: + return expr.evalBasicLit(n) + case *ast.BinaryExpr: + return expr.evalBinaryExpr(n) + case *ast.UnaryExpr: + return expr.evalUnaryExpr(n) + case *ast.CallExpr: + return expr.evalCallExpr(n) + case *ast.Ident: + return expr.evalIdent(n, direct) + case *ast.ParenExpr: + return expr.evalNode(n.X, false) + } + + return 0, util.FmtNewtError("Invalid token in expression") +} + +func (expr *exprCtx) evalEntry(name string) (*exprEntry, error) { + ee, ok := expr.ees[name] + if ok { + if !ee.done { + return ee, util.FmtNewtError("Circular dependency") + } + if ee.failed { + // Return an empty error here. This can be used to detect case + // when entry value cannot be evaluated because of an error in + // another value entry. This can prevent returning the same error + // for each entry that references invalid entry, but otherwise + // is likely valid. + return ee, util.FmtNewtError("") + } + return ee, nil + } + + ee = &exprEntry{} + expr.ees[name] = ee + + sval, ok := expr.q.ExprGetValue(name) + if !ok { + return ee, util.FmtNewtError("Unknown identifier referenced: %s", name) + } + + prevEntryName := expr.entryName + expr.entryName = name + + var val interface{} = nil + var err error = nil + + if len(sval) > 0 { + node, _ := parser.ParseExpr(sval) + val, err = expr.evalNode(node, true) + if err != nil { + ee.failed = true + } + } + + expr.entryName = prevEntryName + + ee.val = val + ee.done = true + expr.q.ExprSetValue(name, ee.val, err) + + return ee, err +} + +func (expr *exprCtx) Evaluate(s string) (interface{}, error) { + ee, err := expr.evalEntry(s) + + return ee.val, err +} + +func CreateCtx(q ExprQuery) *exprCtx { + return &exprCtx{q: q, ees: make(map[string]*exprEntry)} +} diff --git a/newt/resolve/resolve.go b/newt/resolve/resolve.go index 84e2aab15..04c046b4a 100644 --- a/newt/resolve/resolve.go +++ b/newt/resolve/resolve.go @@ -600,6 +600,7 @@ func (r *Resolver) reloadCfg() (bool, error) { cfg.AddInjectedSettings() cfg.ResolveValueRefs() + cfg.EvaluateExpressions(lpkgs) // Determine if any new settings have been added or if any existing // settings have changed. diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go new file mode 100644 index 000000000..16f4f4cde --- /dev/null +++ b/newt/syscfg/eval.go @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package syscfg + +import ( + "mynewt.apache.org/newt/newt/newtutil" +) + +type ExprEvalCtx struct { + cfg *Cfg + lpkgm map[string]struct{} +} + +func (ctx *ExprEvalCtx) ExprGetValue(name string) (string, bool) { + e, ok := ctx.cfg.Settings[name] + + if ok && e.EvalDone { + panic("This should never happen :>") + } + + return e.Value, ok +} + +func (ctx *ExprEvalCtx) ExprGetValueChoices(name string) ([]string, bool) { + e, ok := ctx.cfg.Settings[name] + + return e.ValidChoices, ok +} + +func (ctx *ExprEvalCtx) ExprSetValue(name string, value interface{}, err error) { + e, ok := ctx.cfg.Settings[name] + if !ok { + panic("This should never happen :>") + } + + if e.EvalDone { + panic("This should never happen :>") + } + + e.EvalDone = true + e.EvalValue = value + if err != nil && len(err.Error()) > 0 { + e.EvalError = err + ctx.cfg.InvalidExpressions[name] = struct{}{} + } + + ctx.cfg.Settings[name] = e +} + +func (ctx *ExprEvalCtx) ExprQueryPkg(name string, pkgStr string) bool { + e, ok := ctx.cfg.Settings[name] + if !ok { + panic("This should never happen :>") + } + + repoName, pkgName, err := newtutil.ParsePackageString(pkgStr) + if err != nil { + return false + } + + if len(repoName) == 0 { + repoName = e.PackageDef.Repo().Name() + } + + pkgName = newtutil.BuildPackageString(repoName, pkgName) + _, ok = ctx.lpkgm[pkgName] + + return ok +} diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go index 985cae9b5..30ddbc0dd 100644 --- a/newt/syscfg/syscfg.go +++ b/newt/syscfg/syscfg.go @@ -24,6 +24,7 @@ import ( "fmt" "io" "io/ioutil" + "mynewt.apache.org/newt/newt/expr" "os" "path/filepath" "regexp" @@ -102,6 +103,10 @@ type CfgEntry struct { PackageDef *pkg.LocalPackage History []CfgPoint State CfgSettingState + + EvalDone bool + EvalValue interface{} + EvalError error } type CfgPriority struct { @@ -159,6 +164,9 @@ type Cfg struct { // Unresolved value references UnresolvedValueRefs map[string]struct{} + + // Invalid expressions + InvalidExpressions map[string]struct{} } func NewCfg() Cfg { @@ -177,6 +185,7 @@ func NewCfg() Cfg { Consts: map[string]struct{}{}, Experimental: map[string]struct{}{}, UnresolvedValueRefs: map[string]struct{}{}, + InvalidExpressions: map[string]struct{}{}, } } @@ -257,6 +266,24 @@ func (cfg *Cfg) ResolveValueRefs() { } } +func (cfg *Cfg) EvaluateExpressions(lpkgs []*pkg.LocalPackage) { + lpkgm := make(map[string]struct{}) + for _, lpkg := range lpkgs { + fn := lpkg.FullName() + lpkgm[fn] = struct{}{} + } + + ctx := expr.CreateCtx(&ExprEvalCtx{cfg: cfg, lpkgm: lpkgm}) + + for name, entry := range cfg.Settings { + if entry.State == CFG_SETTING_STATE_DEFUNCT { + continue + } + + ctx.Evaluate(name) + } +} + // If the specified package has any injected settings, returns a new map // consisting of the union of the injected settings and the provided base // settings. @@ -1093,6 +1120,25 @@ func (cfg *Cfg) ErrorText() string { } } + // Invalid expressions + if len(cfg.InvalidExpressions) > 0 { + str += "Invalid syscfg expressions:\n" + + names := make([]string, 0, len(cfg.InvalidExpressions)) + for name, _ := range cfg.InvalidExpressions { + names = append(names, name) + } + sort.Strings(names) + + for _, name := range names { + entry := cfg.Settings[name] + if len(entry.EvalError.Error()) > 0 { + str += " " + fmt.Sprintf("%s: %s\n", name, entry.EvalError.Error()) + historyMap[name] = entry.History + } + } + } + if len(historyMap) > 0 { str += "\n" + historyText(historyMap) } @@ -1433,31 +1479,38 @@ func writeComment(entry CfgEntry, w io.Writer) { fmt.Fprintf(w, "/* Value copied from %s */\n", entry.ValueRefName) } + + fmt.Fprintf(w, "/* value: %s */\n", entry.Value) } -func writeDefine(key string, value string, w io.Writer) { - if value == "" { +func writeDefine(key string, value interface{}, w io.Writer) { + if value == nil { fmt.Fprintf(w, "#undef %s\n", key) } else { fmt.Fprintf(w, "#ifndef %s\n", key) - if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { - fmt.Fprintf(w, "#define %s %s\n", key, value) - } else { - fmt.Fprintf(w, "#define %s (%s)\n", key, value) + switch v := value.(type) { + case string: + fmt.Fprintf(w, "#define %s \"%s\"\n", key, v) + case int: + fmt.Fprintf(w, "#define %s (%d)\n", key, v) + case expr.RawString: + fmt.Fprintf(w, "#define %s (%s)\n", key, v.S) + default: + panic("This should not happen :>") } fmt.Fprintf(w, "#endif\n") } } -func writeChoiceDefine(key string, value string, choices []string, w io.Writer) { - parentVal := "" - value = strings.ToLower(value) +func writeChoiceDefine(key string, value interface{}, choices []string, w io.Writer) { + parentVal := 0 + value = strings.ToLower(value.(string)) for _, choice := range choices { definedVal := 0 if value == strings.ToLower(choice) { definedVal = 1 - parentVal = "1" + parentVal = 1 } fmt.Fprintf(w, "#ifndef %s__%s\n", key, choice) fmt.Fprintf(w, "#define %s__%s (%d)\n", key, choice, definedVal) @@ -1494,6 +1547,11 @@ func writeSettingsOnePkg(cfg Cfg, pkgName string, pkgEntries []CfgEntry, first := true for _, n := range names { entry := cfg.Settings[n] + + if entry.State == CFG_SETTING_STATE_DEFUNCT { + continue + } + if first { first = false } else { @@ -1501,10 +1559,13 @@ func writeSettingsOnePkg(cfg Cfg, pkgName string, pkgEntries []CfgEntry, } writeComment(entry, w) + if !entry.EvalDone { + panic("This should never happen :>") + } if entry.ValidChoices != nil { - writeChoiceDefine(settingName(n), entry.Value, entry.ValidChoices, w) + writeChoiceDefine(settingName(n), entry.EvalValue, entry.ValidChoices, w) } else { - writeDefine(settingName(n), entry.Value, w) + writeDefine(settingName(n), entry.EvalValue, w) } } }