Skip to content

Commit be750a4

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,...) - return 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 be750a4

File tree

5 files changed

+438
-6
lines changed

5 files changed

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

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)