From 19adfe6553415c8f4aa0f7d595219e6d897974a2 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Wed, 23 Mar 2022 23:17:38 +0100 Subject: [PATCH 01/13] Add support for expressions in syscfg values This adds support for evaluating syscfg values as expressions. To make it compatible with existing code, syscfg value is only evaluated as expression if its type is explicitly set to "expr", i.e.: syscfg.defs: FOO: description: ... type: expr value: 1 + 2 + 3 Following tokens are allowed in expressions: - literals (integers and strings) - identifiers (references to other syscfg values) - parentheses - binary operators (arthmetic, relational and boolean) - unary operator (boolean negation) - built-in function calls Most of operators support only integer values. Strings are supported by "==" and "!=" only. Available built-in functions are: - min(a,b) - returns lesser of "a" and "b" - max(a,b) - returns greater of "a" and "b" - in_range(v,a,b) - returns if "v" is inside [a,b] range - clamp(v,a,b) - clamps "v" to be inside [a,b] range - ite(v,a,b) - if-then-else, returns "a" if "v", otherwise returns "b" - in_set(v,...) - returns if "v" is one of remaining arguments Note: all arguments to built-in functions shall be integer only, except for "a" and "b" in ite() and all arguments in in_set(). --- newt/resolve/resolve.go | 1 + newt/syscfg/eval.go | 363 ++++++++++++++++++++++++++++++++++++++++ newt/syscfg/marshal.go | 1 + newt/syscfg/syscfg.go | 67 +++++++- newt/val/valsetting.go | 2 +- 5 files changed, 428 insertions(+), 6 deletions(-) create mode 100644 newt/syscfg/eval.go diff --git a/newt/resolve/resolve.go b/newt/resolve/resolve.go index 84e2aab15b..2f8025c555 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() // 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 0000000000..48fc82c2db --- /dev/null +++ b/newt/syscfg/eval.go @@ -0,0 +1,363 @@ +/** + * 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 ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "mynewt.apache.org/newt/util" + "strconv" +) + +func int2bool(x int) bool { + return x != 0 +} + +func bool2int(b bool) int { + if b { + return 1 + } + + return 0 +} + +func (cfg *Cfg) exprEvalLiteral(e *ast.BasicLit) (interface{}, error) { + kind := e.Kind + val := e.Value + + switch kind { + case token.INT: + return strconv.Atoi(val) + case token.STRING: + return val, nil + } + + return 0, util.FmtNewtError("Invalid exprEvalLiteral used in expression") +} + +func (cfg *Cfg) exprEvalBinaryExpr(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 = cfg.exprEvalNode(e.X) + if err != nil { + return 0, err + } + y, err = cfg.exprEvalNode(e.Y) + 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 (cfg *Cfg) exprEvalUnaryExpr(e *ast.UnaryExpr) (int, error) { + if e.Op != token.NOT { + return 0, util.FmtNewtError("Invalid \"%s\" operator in expression", e.Op.String()) + } + + x, err := cfg.exprEvalNode(e.X) + 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()) + } + + ret := bool2int(!int2bool(xv)) + + return ret, nil +} + +func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { + f := e.Fun.(*ast.Ident) + expectedArgc := -1 + minArgc := -1 + + switch f.Name { + 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) + } + + argv := []interface{}{} + argvs := []string{} + for _, node := range e.Args { + arg, err := cfg.exprEvalNode(node) + if err != nil { + return 0, err + } + + argv = append(argv, arg) + argvs = append(argvs, fmt.Sprintf("%v", arg)) + } + + var ret interface{} + + switch f.Name { + 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) + } + + return ret, nil +} + +func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident) (interface{}, error) { + name := e.Name + + entry, ok := cfg.Settings[name] + if !ok { + return 0, util.FmtNewtError("Undefined identifier referenced: %s", name) + } + + var val interface{} + var err error + + switch entry.EvalState { + case CFG_EVAL_STATE_NONE: + entry, err = cfg.evalEntry(entry) + val = entry.EvalValue + case CFG_EVAL_STATE_RUNNING: + err = util.FmtNewtError("Circular identifier dependency in expression") + case CFG_EVAL_STATE_SUCCESS: + val = entry.EvalValue + case CFG_EVAL_STATE_FAILED: + err = util.FmtNewtError("") + } + + return val, err +} + +func (cfg *Cfg) exprEvalNode(node ast.Node) (interface{}, error) { + switch e := node.(type) { + case *ast.BasicLit: + return cfg.exprEvalLiteral(e) + case *ast.BinaryExpr: + return cfg.exprEvalBinaryExpr(e) + case *ast.UnaryExpr: + return cfg.exprEvalUnaryExpr(e) + case *ast.CallExpr: + return cfg.exprEvalCallExpr(e) + case *ast.Ident: + return cfg.exprEvalIdentifier(e) + case *ast.ParenExpr: + return cfg.exprEvalNode(e.X) + } + + return 0, util.FmtNewtError("Invalid token in expression") +} + +func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) { + name := entry.Name + + if entry.EvalState != CFG_EVAL_STATE_NONE { + panic("This should never happen :>") + } + + entry.EvalState = CFG_EVAL_STATE_RUNNING + cfg.Settings[name] = entry + + entry.EvalOrigValue = entry.Value + + node, _ := parser.ParseExpr(entry.Value) + newVal, err := cfg.exprEvalNode(node) + if err != nil { + entry.EvalState = CFG_EVAL_STATE_FAILED + entry.EvalError = err + cfg.Settings[entry.Name] = entry + cfg.InvalidExpressions[entry.Name] = struct{}{} + err = util.FmtNewtError("") + return entry, err + } + + switch val := newVal.(type) { + case int: + entry.EvalValue = val + entry.Value = strconv.Itoa(val) + case string: + entry.EvalValue = val + entry.Value = val + default: + panic("This should never happen :>") + } + + entry.EvalState = CFG_EVAL_STATE_SUCCESS + cfg.Settings[entry.Name] = entry + + return entry, nil +} + +func (cfg *Cfg) Evaluate(name string) { + entry := cfg.Settings[name] + + switch entry.EvalState { + case CFG_EVAL_STATE_NONE: + cfg.evalEntry(entry) + case CFG_EVAL_STATE_RUNNING: + panic("This should never happen :>") + case CFG_EVAL_STATE_SUCCESS: + // Already evaluated + case CFG_EVAL_STATE_FAILED: + // Already evaluated + } +} diff --git a/newt/syscfg/marshal.go b/newt/syscfg/marshal.go index 5b3f2a006e..33fe163bf3 100644 --- a/newt/syscfg/marshal.go +++ b/newt/syscfg/marshal.go @@ -29,6 +29,7 @@ var cfgSettingNameTypeMap = map[string]CfgSettingType{ "raw": CFG_SETTING_TYPE_RAW, "task_priority": CFG_SETTING_TYPE_TASK_PRIO, "flash_owner": CFG_SETTING_TYPE_FLASH_OWNER, + "expr": CFG_SETTING_TYPE_EXPRESSION, } var cfgSettingNameStateMap = map[string]CfgSettingState{ diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go index 985cae9b5f..0fcc763f97 100644 --- a/newt/syscfg/syscfg.go +++ b/newt/syscfg/syscfg.go @@ -54,6 +54,7 @@ const ( CFG_SETTING_TYPE_TASK_PRIO CFG_SETTING_TYPE_INTERRUPT_PRIO CFG_SETTING_TYPE_FLASH_OWNER + CFG_SETTING_TYPE_EXPRESSION ) type CfgSettingState int @@ -75,6 +76,15 @@ const ( const SYSCFG_PRIO_ANY = "any" +type CfgEvalState int + +const ( + CFG_EVAL_STATE_NONE CfgEvalState = iota + CFG_EVAL_STATE_RUNNING + CFG_EVAL_STATE_SUCCESS + CFG_EVAL_STATE_FAILED +) + // Reserve last 16 priorities for the system (sanity, idle). const SYSCFG_TASK_PRIO_MAX = 0xef @@ -102,6 +112,11 @@ type CfgEntry struct { PackageDef *pkg.LocalPackage History []CfgPoint State CfgSettingState + + EvalState CfgEvalState + EvalValue interface{} + EvalOrigValue string + EvalError error } type CfgPriority struct { @@ -159,6 +174,9 @@ type Cfg struct { // Unresolved value references UnresolvedValueRefs map[string]struct{} + + // Invalid expressions + InvalidExpressions map[string]struct{} } func NewCfg() Cfg { @@ -177,6 +195,7 @@ func NewCfg() Cfg { Consts: map[string]struct{}{}, Experimental: map[string]struct{}{}, UnresolvedValueRefs: map[string]struct{}{}, + InvalidExpressions: map[string]struct{}{}, } } @@ -205,20 +224,20 @@ func ResolveValueRefName(val string) string { } } -func (cfg *Cfg) ExpandRef(val string) (string, string, error) { +func (cfg *Cfg) ExpandRef(val string) (string, string, CfgSettingType, error) { refName := ResolveValueRefName(val) if refName == "" { // Not a reference. - return "", val, nil + return "", val, CFG_SETTING_TYPE_RAW, nil } entry, ok := cfg.Settings[refName] if !ok { - return "", "", util.FmtNewtError( + return "", "", CFG_SETTING_TYPE_RAW, util.FmtNewtError( "setting value \"%s\" references undefined setting", val) } - return entry.Name, entry.Value, nil + return entry.Name, entry.Value, entry.SettingType, nil } @@ -241,7 +260,7 @@ func (cfg *Cfg) AddInjectedSettings() { func (cfg *Cfg) ResolveValueRefs() { for k, entry := range cfg.Settings { - refName, val, err := cfg.ExpandRef(strings.TrimSpace(entry.Value)) + refName, val, stype, err := cfg.ExpandRef(strings.TrimSpace(entry.Value)) if err != nil { // Referenced setting doesn't exist. Set unresolved setting value // to 0, this way restrictions can be evaluated and won't create @@ -252,11 +271,26 @@ func (cfg *Cfg) ResolveValueRefs() { } else if refName != "" { entry.ValueRefName = refName entry.Value = val + // If referenced setting is an expression, make this one also + // an expression so it can be calculated properly + if stype == CFG_SETTING_TYPE_EXPRESSION { + entry.SettingType = stype + } cfg.Settings[k] = entry } } } +func (cfg *Cfg) EvaluateExpressions() { + for k, entry := range cfg.Settings { + if entry.SettingType != CFG_SETTING_TYPE_EXPRESSION { + continue + } + + cfg.Evaluate(k) + } +} + // 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 +1127,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,6 +1486,10 @@ func writeComment(entry CfgEntry, w io.Writer) { fmt.Fprintf(w, "/* Value copied from %s */\n", entry.ValueRefName) } + if len(entry.EvalOrigValue) > 1 { + fmt.Fprintf(w, "/* Expression: %s */\n", + entry.EvalOrigValue) + } } func writeDefine(key string, value string, w io.Writer) { diff --git a/newt/val/valsetting.go b/newt/val/valsetting.go index 0d24218a79..63a329bfac 100644 --- a/newt/val/valsetting.go +++ b/newt/val/valsetting.go @@ -55,7 +55,7 @@ func (vs *ValSetting) IntVal() (int, error) { // Constructs a setting from a YAML string. func ResolveValSetting(s string, cfg *syscfg.Cfg) (ValSetting, error) { - refName, val, err := cfg.ExpandRef(s) + refName, val, _, err := cfg.ExpandRef(s) if err != nil { return ValSetting{}, util.FmtNewtError("value \"%s\" references undefined setting", s) From 181abdb0b71e32802993ea5416f4ff93b4c05437 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 12:05:13 +0100 Subject: [PATCH 02/13] [expr] Add support for unary "-" --- newt/syscfg/eval.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index 48fc82c2db..d02874f2c6 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -142,7 +142,7 @@ func (cfg *Cfg) exprEvalBinaryExpr(e *ast.BinaryExpr) (int, error) { } func (cfg *Cfg) exprEvalUnaryExpr(e *ast.UnaryExpr) (int, error) { - if e.Op != token.NOT { + if e.Op != token.NOT && e.Op != token.SUB { return 0, util.FmtNewtError("Invalid \"%s\" operator in expression", e.Op.String()) } @@ -156,7 +156,14 @@ func (cfg *Cfg) exprEvalUnaryExpr(e *ast.UnaryExpr) (int, error) { return 0, util.FmtNewtError("String literals not applicable for \"%s\" operator", e.Op.String()) } - ret := bool2int(!int2bool(xv)) + var ret int + + switch e.Op { + case token.NOT: + ret = bool2int(!int2bool(xv)) + case token.SUB: + ret = -xv + } return ret, nil } From 8d0898560ee138a5324e0eb6bef94b3c46182cf6 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 12:05:31 +0100 Subject: [PATCH 03/13] [expr] Add support for hex values --- newt/syscfg/eval.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index d02874f2c6..d051a58c0c 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -46,7 +46,8 @@ func (cfg *Cfg) exprEvalLiteral(e *ast.BasicLit) (interface{}, error) { switch kind { case token.INT: - return strconv.Atoi(val) + v, err := strconv.ParseInt(val, 0, 0) + return int(v), err case token.STRING: return val, nil } From e553f167f0ab6ef68b2edafd35d1534e11e5bd1c Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 12:06:17 +0100 Subject: [PATCH 04/13] [expr] Add support for empty values --- newt/syscfg/eval.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index d051a58c0c..d460d2bedc 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -327,15 +327,22 @@ func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) { entry.EvalOrigValue = entry.Value - node, _ := parser.ParseExpr(entry.Value) - newVal, err := cfg.exprEvalNode(node) - if err != nil { - entry.EvalState = CFG_EVAL_STATE_FAILED - entry.EvalError = err - cfg.Settings[entry.Name] = entry - cfg.InvalidExpressions[entry.Name] = struct{}{} - err = util.FmtNewtError("") - return entry, err + var newVal interface{} + + if len(entry.Value) > 0 { + var err error + node, _ := parser.ParseExpr(entry.Value) + newVal, err = cfg.exprEvalNode(node, &entry) + if err != nil { + entry.EvalState = CFG_EVAL_STATE_FAILED + entry.EvalError = err + cfg.Settings[entry.Name] = entry + cfg.InvalidExpressions[entry.Name] = struct{}{} + err = util.FmtNewtError("") + return entry, err + } + } else { + newVal = "" } switch val := newVal.(type) { From d5ae4e7f8b8d716d52675964f253c20820c80d3c Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 12:07:18 +0100 Subject: [PATCH 05/13] [expr] Evaluate all syscfg as expressions Except for defunc settings since those are not used anyway. --- newt/syscfg/marshal.go | 1 - newt/syscfg/syscfg.go | 18 ++++++------------ newt/val/valsetting.go | 2 +- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/newt/syscfg/marshal.go b/newt/syscfg/marshal.go index 33fe163bf3..5b3f2a006e 100644 --- a/newt/syscfg/marshal.go +++ b/newt/syscfg/marshal.go @@ -29,7 +29,6 @@ var cfgSettingNameTypeMap = map[string]CfgSettingType{ "raw": CFG_SETTING_TYPE_RAW, "task_priority": CFG_SETTING_TYPE_TASK_PRIO, "flash_owner": CFG_SETTING_TYPE_FLASH_OWNER, - "expr": CFG_SETTING_TYPE_EXPRESSION, } var cfgSettingNameStateMap = map[string]CfgSettingState{ diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go index 0fcc763f97..e0ed909e36 100644 --- a/newt/syscfg/syscfg.go +++ b/newt/syscfg/syscfg.go @@ -54,7 +54,6 @@ const ( CFG_SETTING_TYPE_TASK_PRIO CFG_SETTING_TYPE_INTERRUPT_PRIO CFG_SETTING_TYPE_FLASH_OWNER - CFG_SETTING_TYPE_EXPRESSION ) type CfgSettingState int @@ -224,20 +223,20 @@ func ResolveValueRefName(val string) string { } } -func (cfg *Cfg) ExpandRef(val string) (string, string, CfgSettingType, error) { +func (cfg *Cfg) ExpandRef(val string) (string, string, error) { refName := ResolveValueRefName(val) if refName == "" { // Not a reference. - return "", val, CFG_SETTING_TYPE_RAW, nil + return "", val, nil } entry, ok := cfg.Settings[refName] if !ok { - return "", "", CFG_SETTING_TYPE_RAW, util.FmtNewtError( + return "", "", util.FmtNewtError( "setting value \"%s\" references undefined setting", val) } - return entry.Name, entry.Value, entry.SettingType, nil + return entry.Name, entry.Value, nil } @@ -260,7 +259,7 @@ func (cfg *Cfg) AddInjectedSettings() { func (cfg *Cfg) ResolveValueRefs() { for k, entry := range cfg.Settings { - refName, val, stype, err := cfg.ExpandRef(strings.TrimSpace(entry.Value)) + refName, val, err := cfg.ExpandRef(strings.TrimSpace(entry.Value)) if err != nil { // Referenced setting doesn't exist. Set unresolved setting value // to 0, this way restrictions can be evaluated and won't create @@ -271,11 +270,6 @@ func (cfg *Cfg) ResolveValueRefs() { } else if refName != "" { entry.ValueRefName = refName entry.Value = val - // If referenced setting is an expression, make this one also - // an expression so it can be calculated properly - if stype == CFG_SETTING_TYPE_EXPRESSION { - entry.SettingType = stype - } cfg.Settings[k] = entry } } @@ -283,7 +277,7 @@ func (cfg *Cfg) ResolveValueRefs() { func (cfg *Cfg) EvaluateExpressions() { for k, entry := range cfg.Settings { - if entry.SettingType != CFG_SETTING_TYPE_EXPRESSION { + if entry.State == CFG_SETTING_STATE_DEFUNCT { continue } diff --git a/newt/val/valsetting.go b/newt/val/valsetting.go index 63a329bfac..0d24218a79 100644 --- a/newt/val/valsetting.go +++ b/newt/val/valsetting.go @@ -55,7 +55,7 @@ func (vs *ValSetting) IntVal() (int, error) { // Constructs a setting from a YAML string. func ResolveValSetting(s string, cfg *syscfg.Cfg) (ValSetting, error) { - refName, val, _, err := cfg.ExpandRef(s) + refName, val, err := cfg.ExpandRef(s) if err != nil { return ValSetting{}, util.FmtNewtError("value \"%s\" references undefined setting", s) From 3205dd26d23bb36d85b561699b615aad2caf1b56 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 12:08:06 +0100 Subject: [PATCH 06/13] [expr] Add fallback for MYNEWT_VAL_ prefix identifiers Some settings may reference values with MYNEWT_VAL_ prefix - this was done to actually evaluate them as expression in preprocessor. We can fix those and emit warning instead of failing. --- newt/syscfg/eval.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index d460d2bedc..a8e33b1b31 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -26,6 +26,7 @@ import ( "go/token" "mynewt.apache.org/newt/util" "strconv" + "strings" ) func int2bool(x int) bool { @@ -275,7 +276,18 @@ func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident) (interface{}, error) { entry, ok := cfg.Settings[name] if !ok { - return 0, util.FmtNewtError("Undefined identifier referenced: %s", name) + fixedName := name + if strings.HasPrefix(fixedName, SYSCFG_PREFIX_SETTING) { + fixedName = strings.TrimPrefix(fixedName, SYSCFG_PREFIX_SETTING) + } + + entry, ok = cfg.Settings[fixedName] + if !ok { + return 0, util.FmtNewtError("Undefined identifier referenced: %s", name) + } + + util.OneTimeWarning("Referenced identifier \"%s\" does not exist, did you mean \"%s\"?", + name, fixedName) } var val interface{} From 02174f0bcff7a475f906337fbcc53a77ea7f6d89 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 12:12:07 +0100 Subject: [PATCH 07/13] [expr] Add support for choices values If syscfg value is a choice, it's evaluated as an identifier so we need to add special handling in such case - if identifier is evaluated directly "from" entry, we should first check if value is a valid choice. --- newt/syscfg/eval.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index a8e33b1b31..85073cdc95 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -79,11 +79,11 @@ func (cfg *Cfg) exprEvalBinaryExpr(e *ast.BinaryExpr) (int, error) { var y interface{} var err error - x, err = cfg.exprEvalNode(e.X) + x, err = cfg.exprEvalNode(e.X, nil) if err != nil { return 0, err } - y, err = cfg.exprEvalNode(e.Y) + y, err = cfg.exprEvalNode(e.Y, nil) if err != nil { return 0, err } @@ -148,7 +148,7 @@ func (cfg *Cfg) exprEvalUnaryExpr(e *ast.UnaryExpr) (int, error) { return 0, util.FmtNewtError("Invalid \"%s\" operator in expression", e.Op.String()) } - x, err := cfg.exprEvalNode(e.X) + x, err := cfg.exprEvalNode(e.X, nil) if err != nil { return 0, err } @@ -201,7 +201,7 @@ func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { argv := []interface{}{} argvs := []string{} for _, node := range e.Args { - arg, err := cfg.exprEvalNode(node) + arg, err := cfg.exprEvalNode(node, nil) if err != nil { return 0, err } @@ -271,9 +271,17 @@ func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { return ret, nil } -func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident) (interface{}, error) { +func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident, parentEntry *CfgEntry) (interface{}, error) { name := e.Name + if parentEntry != nil { + for _, s := range parentEntry.ValidChoices { + if s == name { + return s, nil + } + } + } + entry, ok := cfg.Settings[name] if !ok { fixedName := name @@ -308,7 +316,7 @@ func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident) (interface{}, error) { return val, err } -func (cfg *Cfg) exprEvalNode(node ast.Node) (interface{}, error) { +func (cfg *Cfg) exprEvalNode(node ast.Node, parentEntry *CfgEntry) (interface{}, error) { switch e := node.(type) { case *ast.BasicLit: return cfg.exprEvalLiteral(e) @@ -319,9 +327,9 @@ func (cfg *Cfg) exprEvalNode(node ast.Node) (interface{}, error) { case *ast.CallExpr: return cfg.exprEvalCallExpr(e) case *ast.Ident: - return cfg.exprEvalIdentifier(e) + return cfg.exprEvalIdentifier(e, parentEntry) case *ast.ParenExpr: - return cfg.exprEvalNode(e.X) + return cfg.exprEvalNode(e.X, nil) } return 0, util.FmtNewtError("Invalid token in expression") From b1367b6c46a24c11a2ac4bb4e429e27918242873 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 16:21:55 +0100 Subject: [PATCH 08/13] [expr] Add support for raw syscfg values This adds function "raw" that expects a single string argument and will return that argument contents as raw value, i.e. syscfg value will be exactly that string without quotes. --- newt/syscfg/eval.go | 13 +++++++++++ newt/syscfg/syscfg.go | 51 ++++++++++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index 85073cdc95..66687c6c8f 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -29,6 +29,10 @@ import ( "strings" ) +type RawString struct { + string +} + func int2bool(x int) bool { return x != 0 } @@ -176,6 +180,8 @@ func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { minArgc := -1 switch f.Name { + case "raw": + expectedArgc = 1 case "min", "max": expectedArgc = 2 case "in_range", "clamp", "ite": @@ -213,6 +219,10 @@ func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { var ret interface{} switch f.Name { + case "raw": + s, _ := strconv.Unquote(argv[0].(string)) + rs := RawString{s} + return rs, nil case "min": a, ok1 := argv[0].(int) b, ok2 := argv[1].(int) @@ -372,6 +382,9 @@ func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) { case string: entry.EvalValue = val entry.Value = val + case RawString: + entry.EvalValue = val + entry.Value = val.string default: panic("This should never happen :>") } diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go index e0ed909e36..539314ff25 100644 --- a/newt/syscfg/syscfg.go +++ b/newt/syscfg/syscfg.go @@ -1480,35 +1480,44 @@ func writeComment(entry CfgEntry, w io.Writer) { fmt.Fprintf(w, "/* Value copied from %s */\n", entry.ValueRefName) } - if len(entry.EvalOrigValue) > 1 { - fmt.Fprintf(w, "/* Expression: %s */\n", - entry.EvalOrigValue) + + if entry.Value != entry.EvalOrigValue { + fmt.Fprintf(w, "/* %s */\n", entry.EvalOrigValue) } } -func writeDefine(key string, value string, w io.Writer) { - if value == "" { - 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) +func writeDefine(key string, value interface{}, w io.Writer) { + switch v := value.(type) { + case string: + if len(v) == 0 { + fmt.Fprintf(w, "#undef %s\n", key) } else { - fmt.Fprintf(w, "#define %s (%s)\n", key, value) + fmt.Fprintf(w, "#ifndef %s\n", key) + fmt.Fprintf(w, "#define %s %s\n", key, v) + fmt.Fprintf(w, "#endif\n") } + case int: + fmt.Fprintf(w, "#ifndef %s\n", key) + fmt.Fprintf(w, "#define %s (%d)\n", key, v) + fmt.Fprintf(w, "#endif\n") + case RawString: + fmt.Fprintf(w, "#ifndef %s\n", key) + fmt.Fprintf(w, "#define %s (%s)\n", key, v.string) fmt.Fprintf(w, "#endif\n") + default: + panic("This should not happen :>") } } -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) @@ -1545,6 +1554,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 { @@ -1552,10 +1566,13 @@ func writeSettingsOnePkg(cfg Cfg, pkgName string, pkgEntries []CfgEntry, } writeComment(entry, w) + if entry.EvalState != CFG_EVAL_STATE_SUCCESS { + panic("This should not 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) } } } From 2e457b9bd4bed3831388187a5dcbfae8287f2334 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 18:01:44 +0100 Subject: [PATCH 09/13] [expr] Fix unquoting string literals --- newt/syscfg/eval.go | 5 +++-- newt/syscfg/syscfg.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index 66687c6c8f..dd889590ed 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -54,7 +54,8 @@ func (cfg *Cfg) exprEvalLiteral(e *ast.BasicLit) (interface{}, error) { v, err := strconv.ParseInt(val, 0, 0) return int(v), err case token.STRING: - return val, nil + v, err := strconv.Unquote(val) + return string(v), err } return 0, util.FmtNewtError("Invalid exprEvalLiteral used in expression") @@ -220,7 +221,7 @@ func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { switch f.Name { case "raw": - s, _ := strconv.Unquote(argv[0].(string)) + s, _ := argv[0].(string) rs := RawString{s} return rs, nil case "min": diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go index 539314ff25..6203f29a0b 100644 --- a/newt/syscfg/syscfg.go +++ b/newt/syscfg/syscfg.go @@ -1493,7 +1493,7 @@ func writeDefine(key string, value interface{}, w io.Writer) { fmt.Fprintf(w, "#undef %s\n", key) } else { fmt.Fprintf(w, "#ifndef %s\n", key) - fmt.Fprintf(w, "#define %s %s\n", key, v) + fmt.Fprintf(w, "#define %s \"%s\"\n", key, v) fmt.Fprintf(w, "#endif\n") } case int: From 9f920db97b64348e3d4ba7a4d50b563dbf4a11d3 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 18:22:48 +0100 Subject: [PATCH 10/13] [expr] Add support for empty string values This allows to differentiate empty strings (i.e. #define XXX "") and empty values (i.e. #undef XXX). --- newt/syscfg/eval.go | 36 +++++++++++++++++------------------- newt/syscfg/syscfg.go | 28 ++++++++++++---------------- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index dd889590ed..acbd577b2a 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -358,12 +358,9 @@ func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) { entry.EvalOrigValue = entry.Value - var newVal interface{} - if len(entry.Value) > 0 { - var err error node, _ := parser.ParseExpr(entry.Value) - newVal, err = cfg.exprEvalNode(node, &entry) + newVal, err := cfg.exprEvalNode(node, &entry) if err != nil { entry.EvalState = CFG_EVAL_STATE_FAILED entry.EvalError = err @@ -372,22 +369,23 @@ func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) { err = util.FmtNewtError("") return entry, err } - } else { - newVal = "" - } - switch val := newVal.(type) { - case int: - entry.EvalValue = val - entry.Value = strconv.Itoa(val) - case string: - entry.EvalValue = val - entry.Value = val - case RawString: - entry.EvalValue = val - entry.Value = val.string - default: - panic("This should never happen :>") + switch val := newVal.(type) { + case int: + entry.EvalValue = val + entry.Value = strconv.Itoa(val) + case string: + entry.EvalValue = val + entry.Value = val + case RawString: + entry.EvalValue = val + entry.Value = val.string + default: + panic("This should never happen :>") + } + } else { + entry.EvalValue = nil + entry.Value = "" } entry.EvalState = CFG_EVAL_STATE_SUCCESS diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go index 6203f29a0b..75e92e845e 100644 --- a/newt/syscfg/syscfg.go +++ b/newt/syscfg/syscfg.go @@ -1487,25 +1487,21 @@ func writeComment(entry CfgEntry, w io.Writer) { } func writeDefine(key string, value interface{}, w io.Writer) { - switch v := value.(type) { - case string: - if len(v) == 0 { - fmt.Fprintf(w, "#undef %s\n", key) - } else { - fmt.Fprintf(w, "#ifndef %s\n", key) + if value == nil { + fmt.Fprintf(w, "#undef %s\n", key) + } else { + fmt.Fprintf(w, "#ifndef %s\n", key) + switch v := value.(type) { + case string: fmt.Fprintf(w, "#define %s \"%s\"\n", key, v) - fmt.Fprintf(w, "#endif\n") + case int: + fmt.Fprintf(w, "#define %s (%d)\n", key, v) + case RawString: + fmt.Fprintf(w, "#define %s (%s)\n", key, v.string) + default: + panic("This should not happen :>") } - case int: - fmt.Fprintf(w, "#ifndef %s\n", key) - fmt.Fprintf(w, "#define %s (%d)\n", key, v) fmt.Fprintf(w, "#endif\n") - case RawString: - fmt.Fprintf(w, "#ifndef %s\n", key) - fmt.Fprintf(w, "#define %s (%s)\n", key, v.string) - fmt.Fprintf(w, "#endif\n") - default: - panic("This should not happen :>") } } From a473a1a673ee6b69c00acfb5c2ebe2d0507a0f18 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 19:09:33 +0100 Subject: [PATCH 11/13] [expr] Add proper error if non-integer numbers are used --- newt/syscfg/eval.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/newt/syscfg/eval.go b/newt/syscfg/eval.go index acbd577b2a..540b0d478c 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -56,9 +56,12 @@ func (cfg *Cfg) exprEvalLiteral(e *ast.BasicLit) (interface{}, error) { 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 exprEvalLiteral used in expression") + return 0, util.FmtNewtError("Invalid literal used in expression") } func (cfg *Cfg) exprEvalBinaryExpr(e *ast.BinaryExpr) (int, error) { From 369dea17756d215593da7ffb1a8ad1e0bdf761b2 Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Thu, 24 Mar 2022 22:52:44 +0100 Subject: [PATCH 12/13] [expr] Add 'has_pkg' function --- newt/resolve/resolve.go | 2 +- newt/syscfg/eval.go | 75 +++++++++++++++++++++++++++++------------ newt/syscfg/syscfg.go | 12 +++++-- 3 files changed, 64 insertions(+), 25 deletions(-) diff --git a/newt/resolve/resolve.go b/newt/resolve/resolve.go index 2f8025c555..04c046b4a0 100644 --- a/newt/resolve/resolve.go +++ b/newt/resolve/resolve.go @@ -600,7 +600,7 @@ func (r *Resolver) reloadCfg() (bool, error) { cfg.AddInjectedSettings() cfg.ResolveValueRefs() - cfg.EvaluateExpressions() + 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 index 540b0d478c..758dcc651b 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -24,11 +24,18 @@ import ( "go/ast" "go/parser" "go/token" + "mynewt.apache.org/newt/newt/newtutil" "mynewt.apache.org/newt/util" "strconv" "strings" ) +type EvalCtx struct { + cfg *Cfg + currEntry *CfgEntry + lpkgm map[string]struct{} +} + type RawString struct { string } @@ -45,7 +52,11 @@ func bool2int(b bool) int { return 0 } -func (cfg *Cfg) exprEvalLiteral(e *ast.BasicLit) (interface{}, error) { +func (cfg *Cfg) NewEvalCtx(lpkgm map[string]struct{}) EvalCtx { + return EvalCtx{cfg: cfg, currEntry: nil, lpkgm: lpkgm} +} + +func (ctx *EvalCtx) exprEvalLiteral(e *ast.BasicLit) (interface{}, error) { kind := e.Kind val := e.Value @@ -64,7 +75,7 @@ func (cfg *Cfg) exprEvalLiteral(e *ast.BasicLit) (interface{}, error) { return 0, util.FmtNewtError("Invalid literal used in expression") } -func (cfg *Cfg) exprEvalBinaryExpr(e *ast.BinaryExpr) (int, error) { +func (ctx *EvalCtx) exprEvalBinaryExpr(e *ast.BinaryExpr) (int, error) { switch e.Op { case token.ADD: case token.SUB: @@ -87,11 +98,11 @@ func (cfg *Cfg) exprEvalBinaryExpr(e *ast.BinaryExpr) (int, error) { var y interface{} var err error - x, err = cfg.exprEvalNode(e.X, nil) + x, err = ctx.exprEvalNode(e.X, nil) if err != nil { return 0, err } - y, err = cfg.exprEvalNode(e.Y, nil) + y, err = ctx.exprEvalNode(e.Y, nil) if err != nil { return 0, err } @@ -151,12 +162,12 @@ func (cfg *Cfg) exprEvalBinaryExpr(e *ast.BinaryExpr) (int, error) { return ret, nil } -func (cfg *Cfg) exprEvalUnaryExpr(e *ast.UnaryExpr) (int, error) { +func (ctx *EvalCtx) exprEvalUnaryExpr(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 := cfg.exprEvalNode(e.X, nil) + x, err := ctx.exprEvalNode(e.X, nil) if err != nil { return 0, err } @@ -178,13 +189,13 @@ func (cfg *Cfg) exprEvalUnaryExpr(e *ast.UnaryExpr) (int, error) { return ret, nil } -func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { +func (ctx *EvalCtx) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { f := e.Fun.(*ast.Ident) expectedArgc := -1 minArgc := -1 switch f.Name { - case "raw": + case "raw", "has_pkg": expectedArgc = 1 case "min", "max": expectedArgc = 2 @@ -211,7 +222,7 @@ func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { argv := []interface{}{} argvs := []string{} for _, node := range e.Args { - arg, err := cfg.exprEvalNode(node, nil) + arg, err := ctx.exprEvalNode(node, nil) if err != nil { return 0, err } @@ -227,6 +238,18 @@ func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { s, _ := argv[0].(string) rs := RawString{s} return rs, nil + case "has_pkg": + s, _ := argv[0].(string) + repo, name, err := newtutil.ParsePackageString(s) + if err != nil { + return 0, util.FmtNewtError("Invalid package string (%s) in has_pkg()", s) + } + if len(repo) == 0 { + repo = ctx.currEntry.PackageDef.Repo().Name() + } + fullName := newtutil.BuildPackageString(repo, name) + _, ok := ctx.lpkgm[fullName] + ret = bool2int(ok) case "min": a, ok1 := argv[0].(int) b, ok2 := argv[1].(int) @@ -285,7 +308,8 @@ func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) { return ret, nil } -func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident, parentEntry *CfgEntry) (interface{}, error) { +func (ctx *EvalCtx) exprEvalIdentifier(e *ast.Ident, parentEntry *CfgEntry) (interface{}, error) { + cfg := ctx.cfg name := e.Name if parentEntry != nil { @@ -317,7 +341,7 @@ func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident, parentEntry *CfgEntry) (interfa switch entry.EvalState { case CFG_EVAL_STATE_NONE: - entry, err = cfg.evalEntry(entry) + entry, err = ctx.evalEntry(entry) val = entry.EvalValue case CFG_EVAL_STATE_RUNNING: err = util.FmtNewtError("Circular identifier dependency in expression") @@ -330,32 +354,36 @@ func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident, parentEntry *CfgEntry) (interfa return val, err } -func (cfg *Cfg) exprEvalNode(node ast.Node, parentEntry *CfgEntry) (interface{}, error) { +func (ctx *EvalCtx) exprEvalNode(node ast.Node, parentEntry *CfgEntry) (interface{}, error) { switch e := node.(type) { case *ast.BasicLit: - return cfg.exprEvalLiteral(e) + return ctx.exprEvalLiteral(e) case *ast.BinaryExpr: - return cfg.exprEvalBinaryExpr(e) + return ctx.exprEvalBinaryExpr(e) case *ast.UnaryExpr: - return cfg.exprEvalUnaryExpr(e) + return ctx.exprEvalUnaryExpr(e) case *ast.CallExpr: - return cfg.exprEvalCallExpr(e) + return ctx.exprEvalCallExpr(e) case *ast.Ident: - return cfg.exprEvalIdentifier(e, parentEntry) + return ctx.exprEvalIdentifier(e, parentEntry) case *ast.ParenExpr: - return cfg.exprEvalNode(e.X, nil) + return ctx.exprEvalNode(e.X, nil) } return 0, util.FmtNewtError("Invalid token in expression") } -func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) { +func (ctx *EvalCtx) evalEntry(entry CfgEntry) (CfgEntry, error) { + cfg := ctx.cfg name := entry.Name if entry.EvalState != CFG_EVAL_STATE_NONE { panic("This should never happen :>") } + prevEntry := ctx.currEntry + ctx.currEntry = &entry + entry.EvalState = CFG_EVAL_STATE_RUNNING cfg.Settings[name] = entry @@ -363,7 +391,7 @@ func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) { if len(entry.Value) > 0 { node, _ := parser.ParseExpr(entry.Value) - newVal, err := cfg.exprEvalNode(node, &entry) + newVal, err := ctx.exprEvalNode(node, &entry) if err != nil { entry.EvalState = CFG_EVAL_STATE_FAILED entry.EvalError = err @@ -394,15 +422,18 @@ func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) { entry.EvalState = CFG_EVAL_STATE_SUCCESS cfg.Settings[entry.Name] = entry + ctx.currEntry = prevEntry + return entry, nil } -func (cfg *Cfg) Evaluate(name string) { +func (ctx *EvalCtx) Evaluate(name string) { + cfg := ctx.cfg entry := cfg.Settings[name] switch entry.EvalState { case CFG_EVAL_STATE_NONE: - cfg.evalEntry(entry) + ctx.evalEntry(entry) case CFG_EVAL_STATE_RUNNING: panic("This should never happen :>") case CFG_EVAL_STATE_SUCCESS: diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go index 75e92e845e..c8280f0711 100644 --- a/newt/syscfg/syscfg.go +++ b/newt/syscfg/syscfg.go @@ -275,13 +275,21 @@ func (cfg *Cfg) ResolveValueRefs() { } } -func (cfg *Cfg) EvaluateExpressions() { +func (cfg *Cfg) EvaluateExpressions(lpkgs []*pkg.LocalPackage) { + lpkgm := make(map[string]struct{}) + for _, lpkg := range lpkgs { + fn := lpkg.FullName() + lpkgm[fn] = struct{}{} + } + + ctx := cfg.NewEvalCtx(lpkgm) + for k, entry := range cfg.Settings { if entry.State == CFG_SETTING_STATE_DEFUNCT { continue } - cfg.Evaluate(k) + ctx.Evaluate(k) } } From 7f1a65de398aaa14980990f87fa894d768a2ae7c Mon Sep 17 00:00:00 2001 From: Andrzej Kaczmarek Date: Fri, 25 Mar 2022 23:21:23 +0100 Subject: [PATCH 13/13] [expr] rewrite evaluator to separate package this moves evaluator to separate package which interfaces with syscfg via interface. this removes direct dependency on syscfg package. --- newt/cli/util.go | 2 + newt/expr/expr.go | 404 ++++++++++++++++++++++++++++++++++++++++ newt/syscfg/eval.go | 424 ++++-------------------------------------- newt/syscfg/syscfg.go | 35 ++-- 4 files changed, 451 insertions(+), 414 deletions(-) create mode 100644 newt/expr/expr.go diff --git a/newt/cli/util.go b/newt/cli/util.go index 75f60282f6..06ac6a4753 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 0000000000..885d4dda85 --- /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/syscfg/eval.go b/newt/syscfg/eval.go index 758dcc651b..16f4f4cdec 100644 --- a/newt/syscfg/eval.go +++ b/newt/syscfg/eval.go @@ -20,425 +20,67 @@ package syscfg import ( - "fmt" - "go/ast" - "go/parser" - "go/token" "mynewt.apache.org/newt/newt/newtutil" - "mynewt.apache.org/newt/util" - "strconv" - "strings" ) -type EvalCtx struct { - cfg *Cfg - currEntry *CfgEntry - lpkgm map[string]struct{} +type ExprEvalCtx struct { + cfg *Cfg + lpkgm map[string]struct{} } -type RawString struct { - string -} - -func int2bool(x int) bool { - return x != 0 -} +func (ctx *ExprEvalCtx) ExprGetValue(name string) (string, bool) { + e, ok := ctx.cfg.Settings[name] -func bool2int(b bool) int { - if b { - return 1 + if ok && e.EvalDone { + panic("This should never happen :>") } - return 0 -} - -func (cfg *Cfg) NewEvalCtx(lpkgm map[string]struct{}) EvalCtx { - return EvalCtx{cfg: cfg, currEntry: nil, lpkgm: lpkgm} + return e.Value, ok } -func (ctx *EvalCtx) exprEvalLiteral(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) - } +func (ctx *ExprEvalCtx) ExprGetValueChoices(name string) ([]string, bool) { + e, ok := ctx.cfg.Settings[name] - return 0, util.FmtNewtError("Invalid literal used in expression") + return e.ValidChoices, ok } -func (ctx *EvalCtx) exprEvalBinaryExpr(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 = ctx.exprEvalNode(e.X, nil) - if err != nil { - return 0, err - } - y, err = ctx.exprEvalNode(e.Y, nil) - 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 (ctx *EvalCtx) exprEvalUnaryExpr(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 := ctx.exprEvalNode(e.X, nil) - if err != nil { - return 0, err - } - - xv, ok := x.(int) +func (ctx *ExprEvalCtx) ExprSetValue(name string, value interface{}, err error) { + e, ok := ctx.cfg.Settings[name] 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 (ctx *EvalCtx) exprEvalCallExpr(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) + panic("This should never happen :>") } - argv := []interface{}{} - argvs := []string{} - for _, node := range e.Args { - arg, err := ctx.exprEvalNode(node, nil) - if err != nil { - return 0, err - } - - argv = append(argv, arg) - argvs = append(argvs, fmt.Sprintf("%v", arg)) + if e.EvalDone { + panic("This should never happen :>") } - var ret interface{} - - switch f.Name { - case "raw": - s, _ := argv[0].(string) - rs := RawString{s} - return rs, nil - case "has_pkg": - s, _ := argv[0].(string) - repo, name, err := newtutil.ParsePackageString(s) - if err != nil { - return 0, util.FmtNewtError("Invalid package string (%s) in has_pkg()", s) - } - if len(repo) == 0 { - repo = ctx.currEntry.PackageDef.Repo().Name() - } - fullName := newtutil.BuildPackageString(repo, name) - _, ok := ctx.lpkgm[fullName] - ret = bool2int(ok) - 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) + e.EvalDone = true + e.EvalValue = value + if err != nil && len(err.Error()) > 0 { + e.EvalError = err + ctx.cfg.InvalidExpressions[name] = struct{}{} } - return ret, nil + ctx.cfg.Settings[name] = e } -func (ctx *EvalCtx) exprEvalIdentifier(e *ast.Ident, parentEntry *CfgEntry) (interface{}, error) { - cfg := ctx.cfg - name := e.Name - - if parentEntry != nil { - for _, s := range parentEntry.ValidChoices { - if s == name { - return s, nil - } - } - } - - entry, ok := cfg.Settings[name] +func (ctx *ExprEvalCtx) ExprQueryPkg(name string, pkgStr string) bool { + e, ok := ctx.cfg.Settings[name] if !ok { - fixedName := name - if strings.HasPrefix(fixedName, SYSCFG_PREFIX_SETTING) { - fixedName = strings.TrimPrefix(fixedName, SYSCFG_PREFIX_SETTING) - } - - entry, ok = cfg.Settings[fixedName] - if !ok { - return 0, util.FmtNewtError("Undefined identifier referenced: %s", name) - } - - util.OneTimeWarning("Referenced identifier \"%s\" does not exist, did you mean \"%s\"?", - name, fixedName) - } - - var val interface{} - var err error - - switch entry.EvalState { - case CFG_EVAL_STATE_NONE: - entry, err = ctx.evalEntry(entry) - val = entry.EvalValue - case CFG_EVAL_STATE_RUNNING: - err = util.FmtNewtError("Circular identifier dependency in expression") - case CFG_EVAL_STATE_SUCCESS: - val = entry.EvalValue - case CFG_EVAL_STATE_FAILED: - err = util.FmtNewtError("") - } - - return val, err -} - -func (ctx *EvalCtx) exprEvalNode(node ast.Node, parentEntry *CfgEntry) (interface{}, error) { - switch e := node.(type) { - case *ast.BasicLit: - return ctx.exprEvalLiteral(e) - case *ast.BinaryExpr: - return ctx.exprEvalBinaryExpr(e) - case *ast.UnaryExpr: - return ctx.exprEvalUnaryExpr(e) - case *ast.CallExpr: - return ctx.exprEvalCallExpr(e) - case *ast.Ident: - return ctx.exprEvalIdentifier(e, parentEntry) - case *ast.ParenExpr: - return ctx.exprEvalNode(e.X, nil) - } - - return 0, util.FmtNewtError("Invalid token in expression") -} - -func (ctx *EvalCtx) evalEntry(entry CfgEntry) (CfgEntry, error) { - cfg := ctx.cfg - name := entry.Name - - if entry.EvalState != CFG_EVAL_STATE_NONE { panic("This should never happen :>") } - prevEntry := ctx.currEntry - ctx.currEntry = &entry - - entry.EvalState = CFG_EVAL_STATE_RUNNING - cfg.Settings[name] = entry - - entry.EvalOrigValue = entry.Value - - if len(entry.Value) > 0 { - node, _ := parser.ParseExpr(entry.Value) - newVal, err := ctx.exprEvalNode(node, &entry) - if err != nil { - entry.EvalState = CFG_EVAL_STATE_FAILED - entry.EvalError = err - cfg.Settings[entry.Name] = entry - cfg.InvalidExpressions[entry.Name] = struct{}{} - err = util.FmtNewtError("") - return entry, err - } - - switch val := newVal.(type) { - case int: - entry.EvalValue = val - entry.Value = strconv.Itoa(val) - case string: - entry.EvalValue = val - entry.Value = val - case RawString: - entry.EvalValue = val - entry.Value = val.string - default: - panic("This should never happen :>") - } - } else { - entry.EvalValue = nil - entry.Value = "" + repoName, pkgName, err := newtutil.ParsePackageString(pkgStr) + if err != nil { + return false } - entry.EvalState = CFG_EVAL_STATE_SUCCESS - cfg.Settings[entry.Name] = entry - - ctx.currEntry = prevEntry - - return entry, nil -} + if len(repoName) == 0 { + repoName = e.PackageDef.Repo().Name() + } -func (ctx *EvalCtx) Evaluate(name string) { - cfg := ctx.cfg - entry := cfg.Settings[name] + pkgName = newtutil.BuildPackageString(repoName, pkgName) + _, ok = ctx.lpkgm[pkgName] - switch entry.EvalState { - case CFG_EVAL_STATE_NONE: - ctx.evalEntry(entry) - case CFG_EVAL_STATE_RUNNING: - panic("This should never happen :>") - case CFG_EVAL_STATE_SUCCESS: - // Already evaluated - case CFG_EVAL_STATE_FAILED: - // Already evaluated - } + return ok } diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go index c8280f0711..30ddbc0dd3 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" @@ -75,15 +76,6 @@ const ( const SYSCFG_PRIO_ANY = "any" -type CfgEvalState int - -const ( - CFG_EVAL_STATE_NONE CfgEvalState = iota - CFG_EVAL_STATE_RUNNING - CFG_EVAL_STATE_SUCCESS - CFG_EVAL_STATE_FAILED -) - // Reserve last 16 priorities for the system (sanity, idle). const SYSCFG_TASK_PRIO_MAX = 0xef @@ -112,10 +104,9 @@ type CfgEntry struct { History []CfgPoint State CfgSettingState - EvalState CfgEvalState - EvalValue interface{} - EvalOrigValue string - EvalError error + EvalDone bool + EvalValue interface{} + EvalError error } type CfgPriority struct { @@ -282,14 +273,14 @@ func (cfg *Cfg) EvaluateExpressions(lpkgs []*pkg.LocalPackage) { lpkgm[fn] = struct{}{} } - ctx := cfg.NewEvalCtx(lpkgm) + ctx := expr.CreateCtx(&ExprEvalCtx{cfg: cfg, lpkgm: lpkgm}) - for k, entry := range cfg.Settings { + for name, entry := range cfg.Settings { if entry.State == CFG_SETTING_STATE_DEFUNCT { continue } - ctx.Evaluate(k) + ctx.Evaluate(name) } } @@ -1489,9 +1480,7 @@ func writeComment(entry CfgEntry, w io.Writer) { entry.ValueRefName) } - if entry.Value != entry.EvalOrigValue { - fmt.Fprintf(w, "/* %s */\n", entry.EvalOrigValue) - } + fmt.Fprintf(w, "/* value: %s */\n", entry.Value) } func writeDefine(key string, value interface{}, w io.Writer) { @@ -1504,8 +1493,8 @@ func writeDefine(key string, value interface{}, w io.Writer) { fmt.Fprintf(w, "#define %s \"%s\"\n", key, v) case int: fmt.Fprintf(w, "#define %s (%d)\n", key, v) - case RawString: - fmt.Fprintf(w, "#define %s (%s)\n", key, v.string) + case expr.RawString: + fmt.Fprintf(w, "#define %s (%s)\n", key, v.S) default: panic("This should not happen :>") } @@ -1570,8 +1559,8 @@ func writeSettingsOnePkg(cfg Cfg, pkgName string, pkgEntries []CfgEntry, } writeComment(entry, w) - if entry.EvalState != CFG_EVAL_STATE_SUCCESS { - panic("This should not happen :>") + if !entry.EvalDone { + panic("This should never happen :>") } if entry.ValidChoices != nil { writeChoiceDefine(settingName(n), entry.EvalValue, entry.ValidChoices, w)