Skip to content

Commit e1d34cb

Browse files
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 build-in functions shall be integer only, except for "a" and "b" in ite() and all arguments in in_set().
1 parent 763d5c0 commit e1d34cb

File tree

5 files changed

+428
-6
lines changed

5 files changed

+428
-6
lines changed

newt/resolve/resolve.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,7 @@ func (r *Resolver) reloadCfg() (bool, error) {
600600

601601
cfg.AddInjectedSettings()
602602
cfg.ResolveValueRefs()
603+
cfg.EvaluateExpressions()
603604

604605
// Determine if any new settings have been added or if any existing
605606
// settings have changed.

newt/syscfg/eval.go

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package syscfg
21+
22+
import (
23+
"fmt"
24+
"go/ast"
25+
"go/parser"
26+
"go/token"
27+
"mynewt.apache.org/newt/util"
28+
"strconv"
29+
)
30+
31+
func int2bool(x int) bool {
32+
return x != 0
33+
}
34+
35+
func bool2int(b bool) int {
36+
if b {
37+
return 1
38+
}
39+
40+
return 0
41+
}
42+
43+
func (cfg *Cfg) exprEvalLiteral(e *ast.BasicLit) (interface{}, error) {
44+
kind := e.Kind
45+
val := e.Value
46+
47+
switch kind {
48+
case token.INT:
49+
return strconv.Atoi(val)
50+
case token.STRING:
51+
return val, nil
52+
}
53+
54+
return 0, util.FmtNewtError("Invalid exprEvalLiteral used in expression")
55+
}
56+
57+
func (cfg *Cfg) exprEvalBinaryExpr(e *ast.BinaryExpr) (int, error) {
58+
switch e.Op {
59+
case token.ADD:
60+
case token.SUB:
61+
case token.MUL:
62+
case token.QUO:
63+
case token.REM:
64+
case token.LAND:
65+
case token.LOR:
66+
case token.EQL:
67+
case token.LSS:
68+
case token.GTR:
69+
case token.NEQ:
70+
case token.LEQ:
71+
case token.GEQ:
72+
default:
73+
return 0, util.FmtNewtError("Invalid \"%s\" operator in expression", e.Op.String())
74+
}
75+
76+
var x interface{}
77+
var y interface{}
78+
var err error
79+
80+
x, err = cfg.exprEvalNode(e.X)
81+
if err != nil {
82+
return 0, err
83+
}
84+
y, err = cfg.exprEvalNode(e.Y)
85+
if err != nil {
86+
return 0, err
87+
}
88+
89+
xv, xok := x.(int)
90+
yv, yok := y.(int)
91+
92+
if xok != yok {
93+
return 0, util.FmtNewtError("Mismatched types for \"%s\" operator in expression", e.Op.String())
94+
}
95+
96+
ret := 0
97+
98+
if xok {
99+
switch e.Op {
100+
case token.ADD:
101+
ret = xv + yv
102+
case token.SUB:
103+
ret = xv - yv
104+
case token.MUL:
105+
ret = xv * yv
106+
case token.QUO:
107+
ret = xv / yv
108+
case token.REM:
109+
ret = xv % yv
110+
case token.LAND:
111+
ret = bool2int(int2bool(xv) && int2bool(yv))
112+
case token.LOR:
113+
ret = bool2int(int2bool(xv) || int2bool(yv))
114+
case token.EQL:
115+
ret = bool2int(xv == yv)
116+
case token.LSS:
117+
ret = bool2int(xv < yv)
118+
case token.GTR:
119+
ret = bool2int(xv > yv)
120+
case token.NEQ:
121+
ret = bool2int(xv != yv)
122+
case token.LEQ:
123+
ret = bool2int(xv <= yv)
124+
case token.GEQ:
125+
ret = bool2int(xv >= yv)
126+
}
127+
} else {
128+
// Each node is evaluated to int/string only so below assertions
129+
// should never fail
130+
switch e.Op {
131+
case token.EQL:
132+
ret = bool2int(x.(string) == y.(string))
133+
case token.NEQ:
134+
ret = bool2int(x.(string) != y.(string))
135+
default:
136+
return 0, util.FmtNewtError("Operator \"%s\" not supported for string literals",
137+
e.Op.String())
138+
}
139+
}
140+
141+
return ret, nil
142+
}
143+
144+
func (cfg *Cfg) exprEvalUnaryExpr(e *ast.UnaryExpr) (int, error) {
145+
if e.Op != token.NOT {
146+
return 0, util.FmtNewtError("Invalid \"%s\" operator in expression", e.Op.String())
147+
}
148+
149+
x, err := cfg.exprEvalNode(e.X)
150+
if err != nil {
151+
return 0, err
152+
}
153+
154+
xv, ok := x.(int)
155+
if !ok {
156+
return 0, util.FmtNewtError("String literals not applicable for \"%s\" operator", e.Op.String())
157+
}
158+
159+
ret := bool2int(!int2bool(xv))
160+
161+
return ret, nil
162+
}
163+
164+
func (cfg *Cfg) exprEvalCallExpr(e *ast.CallExpr) (interface{}, error) {
165+
f := e.Fun.(*ast.Ident)
166+
expectedArgc := -1
167+
minArgc := -1
168+
169+
switch f.Name {
170+
case "min", "max":
171+
expectedArgc = 2
172+
case "in_range", "clamp", "ite":
173+
expectedArgc = 3
174+
case "in_set":
175+
minArgc = 2
176+
default:
177+
return 0, util.FmtNewtError("Invalid function in expression: \"%s\"", f.Name)
178+
}
179+
180+
argc := len(e.Args)
181+
182+
if expectedArgc > 0 && argc != expectedArgc {
183+
return 0, util.FmtNewtError("Invalid number of arguments for \"%s\": expected %d, got %d",
184+
f.Name, expectedArgc, argc)
185+
}
186+
187+
if minArgc > 0 && argc < minArgc {
188+
return 0, util.FmtNewtError("Invalid number of arguments for \"%s\": expected at least %d, got %d",
189+
f.Name, minArgc, argc)
190+
}
191+
192+
argv := []interface{}{}
193+
argvs := []string{}
194+
for _, node := range e.Args {
195+
arg, err := cfg.exprEvalNode(node)
196+
if err != nil {
197+
return 0, err
198+
}
199+
200+
argv = append(argv, arg)
201+
argvs = append(argvs, fmt.Sprintf("%v", arg))
202+
}
203+
204+
var ret interface{}
205+
206+
switch f.Name {
207+
case "min":
208+
a, ok1 := argv[0].(int)
209+
b, ok2 := argv[1].(int)
210+
if !ok1 || !ok2 {
211+
return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name)
212+
}
213+
ret = util.Min(a, b)
214+
case "max":
215+
a, ok1 := argv[0].(int)
216+
b, ok2 := argv[1].(int)
217+
if !ok1 || !ok2 {
218+
return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name)
219+
}
220+
ret = util.Max(a, b)
221+
case "clamp":
222+
v, ok1 := argv[0].(int)
223+
a, ok2 := argv[1].(int)
224+
b, ok3 := argv[2].(int)
225+
if !ok1 || !ok2 || !ok3 {
226+
return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name)
227+
}
228+
if v < a {
229+
ret = a
230+
} else if v > b {
231+
ret = b
232+
} else {
233+
ret = v
234+
}
235+
case "ite":
236+
v, ok1 := argv[0].(int)
237+
if !ok1 {
238+
return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name)
239+
}
240+
if v != 0 {
241+
ret = argv[1]
242+
} else {
243+
ret = argv[2]
244+
}
245+
case "in_range":
246+
v, ok1 := argv[0].(int)
247+
a, ok2 := argv[1].(int)
248+
b, ok3 := argv[2].(int)
249+
if !ok1 || !ok2 || !ok3 {
250+
return 0, util.FmtNewtError("Invalid argument type for \"%s\"", f.Name)
251+
}
252+
ret = bool2int(v >= a && v <= b)
253+
case "in_set":
254+
m := make(map[interface{}]struct{})
255+
for _, arg := range argv[1:] {
256+
m[arg] = struct{}{}
257+
}
258+
_, ok := m[argv[0]]
259+
ret = bool2int(ok)
260+
}
261+
262+
return ret, nil
263+
}
264+
265+
func (cfg *Cfg) exprEvalIdentifier(e *ast.Ident) (interface{}, error) {
266+
name := e.Name
267+
268+
entry, ok := cfg.Settings[name]
269+
if !ok {
270+
return 0, util.FmtNewtError("Undefined identifier referenced: %s", name)
271+
}
272+
273+
var val interface{}
274+
var err error
275+
276+
switch entry.EvalState {
277+
case CFG_EVAL_STATE_NONE:
278+
entry, err = cfg.evalEntry(entry)
279+
val = entry.EvalValue
280+
case CFG_EVAL_STATE_RUNNING:
281+
err = util.FmtNewtError("Circular identifier dependency in expression")
282+
case CFG_EVAL_STATE_SUCCESS:
283+
val = entry.EvalValue
284+
case CFG_EVAL_STATE_FAILED:
285+
err = util.FmtNewtError("")
286+
}
287+
288+
return val, err
289+
}
290+
291+
func (cfg *Cfg) exprEvalNode(node ast.Node) (interface{}, error) {
292+
switch e := node.(type) {
293+
case *ast.BasicLit:
294+
return cfg.exprEvalLiteral(e)
295+
case *ast.BinaryExpr:
296+
return cfg.exprEvalBinaryExpr(e)
297+
case *ast.UnaryExpr:
298+
return cfg.exprEvalUnaryExpr(e)
299+
case *ast.CallExpr:
300+
return cfg.exprEvalCallExpr(e)
301+
case *ast.Ident:
302+
return cfg.exprEvalIdentifier(e)
303+
case *ast.ParenExpr:
304+
return cfg.exprEvalNode(e.X)
305+
}
306+
307+
return 0, util.FmtNewtError("Invalid token in expression")
308+
}
309+
310+
func (cfg *Cfg) evalEntry(entry CfgEntry) (CfgEntry, error) {
311+
name := entry.Name
312+
313+
if entry.EvalState != CFG_EVAL_STATE_NONE {
314+
panic("This should never happen :>")
315+
}
316+
317+
entry.EvalState = CFG_EVAL_STATE_RUNNING
318+
cfg.Settings[name] = entry
319+
320+
entry.EvalOrigValue = entry.Value
321+
322+
node, _ := parser.ParseExpr(entry.Value)
323+
newVal, err := cfg.exprEvalNode(node)
324+
if err != nil {
325+
entry.EvalState = CFG_EVAL_STATE_FAILED
326+
entry.EvalError = err
327+
cfg.Settings[entry.Name] = entry
328+
cfg.InvalidExpressions[entry.Name] = struct{}{}
329+
err = util.FmtNewtError("")
330+
return entry, err
331+
}
332+
333+
switch val := newVal.(type) {
334+
case int:
335+
entry.EvalValue = val
336+
entry.Value = strconv.Itoa(val)
337+
case string:
338+
entry.EvalValue = val
339+
entry.Value = val
340+
default:
341+
panic("This should never happen :>")
342+
}
343+
344+
entry.EvalState = CFG_EVAL_STATE_SUCCESS
345+
cfg.Settings[entry.Name] = entry
346+
347+
return entry, nil
348+
}
349+
350+
func (cfg *Cfg) Evaluate(name string) {
351+
entry := cfg.Settings[name]
352+
353+
switch entry.EvalState {
354+
case CFG_EVAL_STATE_NONE:
355+
cfg.evalEntry(entry)
356+
case CFG_EVAL_STATE_RUNNING:
357+
panic("This should never happen :>")
358+
case CFG_EVAL_STATE_SUCCESS:
359+
// Already evaluated
360+
case CFG_EVAL_STATE_FAILED:
361+
// Already evaluated
362+
}
363+
}

newt/syscfg/marshal.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var cfgSettingNameTypeMap = map[string]CfgSettingType{
2929
"raw": CFG_SETTING_TYPE_RAW,
3030
"task_priority": CFG_SETTING_TYPE_TASK_PRIO,
3131
"flash_owner": CFG_SETTING_TYPE_FLASH_OWNER,
32+
"expr": CFG_SETTING_TYPE_EXPRESSION,
3233
}
3334

3435
var cfgSettingNameStateMap = map[string]CfgSettingState{

0 commit comments

Comments
 (0)