You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by an...@apache.org on 2019/03/25 19:58:31 UTC

[mynewt-newt] branch master updated: Allow to specify setting value range

This is an automated email from the ASF dual-hosted git repository.

andk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git


The following commit(s) were added to refs/heads/master by this push:
     new 7bd68ba  Allow to specify setting value range
7bd68ba is described below

commit 7bd68bab152e395179e2943ada82df504c607b3b
Author: Andrzej Kaczmarek <an...@codecoup.pl>
AuthorDate: Tue Feb 26 16:41:04 2019 +0100

    Allow to specify setting value range
    
    This allows to specify allowed range for numeric syscfg values in a
    simple way instead of requiring complex expressions. It allows for any
    combinations of discrete values and value ranges to be used in single
    restriction.
    
    Allowed range is specified in "range" key in syscfg definition as a
    comma-separated list of ranges and values, for example:
    
      range: 0..10        # value is between 0 and 10 (inclusive)
      range: 1,2,4,8,16   # value is 1, 2, 4, 8 or 16
      range: 1..4,8       # value is between 1 and 2 or equal to 8
    
    Since ranges are internally converted to proper expressions and then
    evaluated it is possible to reference other syscfg values in ranges, for
    example:
    
      FOOVAL:
          description: foo
          value: 10
      BARVAL:
          description: bar
          value:
          range: 1..FOOVAL  # value is between 1 and FOOVAL (i.e. 10)
---
 newt/syscfg/restrict.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++-
 newt/syscfg/syscfg.go   |  15 +++++++
 2 files changed, 120 insertions(+), 1 deletion(-)

diff --git a/newt/syscfg/restrict.go b/newt/syscfg/restrict.go
index 28a9fc5..542becb 100644
--- a/newt/syscfg/restrict.go
+++ b/newt/syscfg/restrict.go
@@ -71,20 +71,30 @@ const (
 	CFG_RESTRICTION_CODE_NOTNULL = iota
 	CFG_RESTRICTION_CODE_EXPR
 	CFG_RESTRICTION_CODE_CHOICE
+	CFG_RESTRICTION_CODE_RANGE
 )
 
 var cfgRestrictionNameCodeMap = map[string]CfgRestrictionCode{
 	"$notnull": CFG_RESTRICTION_CODE_NOTNULL,
 	"expr":     CFG_RESTRICTION_CODE_EXPR,
 	"choice":   CFG_RESTRICTION_CODE_CHOICE,
+	"range":    CFG_RESTRICTION_CODE_RANGE,
+}
+
+type CfgRestrictionRange struct {
+	LExpr string
+	RExpr string
 }
 
 type CfgRestriction struct {
 	BaseSetting string
 	Code        CfgRestrictionCode
 
-	// Only used if Code is CFG_RESTRICTION_CODE_EXPR
+	// Only used if Code is either CFG_RESTRICTION_CODE_EXPR or CFG_RESTRICTION_CODE_RANGE
 	Expr string
+
+	// Only used if Code is CFG_RESTRICTION_CODE_RANGE
+	Ranges []CfgRestrictionRange
 }
 
 func (c CfgRestrictionCode) String() string {
@@ -192,12 +202,45 @@ func normalizeExpr(expr string, baseSetting string) string {
 	return expr
 }
 
+func (r *CfgRestriction) validateRangesBounds(settings map[string]string) bool {
+	for _, rtoken := range r.Ranges {
+		if len(rtoken.RExpr) > 0 {
+			expr := fmt.Sprintf("(%s) <= (%s)", rtoken.LExpr, rtoken.RExpr)
+			val, err := parse.ParseAndEval(expr, settings)
+			if !val || err != nil {
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
+func (r *CfgRestriction) createRangeExpr() string {
+	exprOutTokens := []string{}
+
+	for _, rtoken := range r.Ranges {
+		if len(rtoken.RExpr) > 0 {
+			exprOutTokens = append(exprOutTokens,
+				fmt.Sprintf("(((%s) >= (%s)) && ((%s) <= (%s)))",
+					r.BaseSetting, rtoken.LExpr, r.BaseSetting, rtoken.RExpr))
+		} else {
+			exprOutTokens = append(exprOutTokens, fmt.Sprintf("((%s) == (%s))",
+				r.BaseSetting, rtoken.LExpr))
+		}
+	}
+
+	return strings.Join(exprOutTokens," || ")
+}
+
 func (cfg *Cfg) settingViolationText(entry CfgEntry, r CfgRestriction) string {
 	prefix := fmt.Sprintf("Setting %s(%s) ", entry.Name, entry.Value)
 	if r.Code == CFG_RESTRICTION_CODE_NOTNULL {
 		return prefix + "must not be null"
 	} else if r.Code == CFG_RESTRICTION_CODE_CHOICE {
 		return prefix + "must be one of defined choices (see definition)"
+	} else if r.Code == CFG_RESTRICTION_CODE_RANGE {
+		return prefix + "must be in range: " + r.Expr
 	} else {
 		return prefix + "requires: " + r.Expr
 	}
@@ -221,6 +264,13 @@ func (r *CfgRestriction) relevantSettingNames() []string {
 				names = append(names, token.Text)
 			}
 		}
+	} else if r.Code == CFG_RESTRICTION_CODE_RANGE {
+		tokens, _ := parse.Lex(r.createRangeExpr())
+		for _, token := range tokens {
+			if token.Code == parse.TOKEN_IDENT {
+				names = append(names, token.Text)
+			}
+		}
 	}
 
 	return names
@@ -248,6 +298,32 @@ func (cfg *Cfg) restrictionMet(
 		}
 		return false
 
+
+	case CFG_RESTRICTION_CODE_RANGE:
+		expr := r.createRangeExpr()
+		if expr == "" {
+			util.OneTimeWarning(
+				"Ignoring illegal range expression for setting \"%s\": "+
+					"`%s`\n", r.BaseSetting, r.Expr)
+			return true
+		}
+
+		val, err := parse.ParseAndEval(expr, settings)
+		if err != nil {
+			util.OneTimeWarning(
+				"Ignoring illegal range expression for setting \"%s\": "+
+					"`%s`\n", r.BaseSetting, r.Expr)
+			return true
+		}
+
+		// invalid bounds may or may not result in an error so just emit a warning
+		if !r.validateRangesBounds(settings) {
+			util.OneTimeWarning(
+				"Invalid bounds (lval > rval) for range expression for setting \"%s\": "+
+					"`%s`\n", r.BaseSetting, r.Expr)
+		}
+
+		return val
 	case CFG_RESTRICTION_CODE_EXPR:
 		var expr string
 		if r.BaseSetting != "" {
@@ -269,3 +345,31 @@ func (cfg *Cfg) restrictionMet(
 		panic("Invalid restriction code: " + string(r.Code))
 	}
 }
+
+func createRangeRestriction(baseSetting string, expr string) (CfgRestriction, error) {
+	r := CfgRestriction{
+		BaseSetting: baseSetting,
+		Code: CFG_RESTRICTION_CODE_RANGE,
+		Expr: expr,
+		Ranges: []CfgRestrictionRange{},
+	}
+
+	exprTokens := strings.Split(expr, ",")
+	for _,token := range exprTokens {
+		rtoken := CfgRestrictionRange{}
+
+		limits := strings.Split(token, "..")
+		if len(limits) == 1 {
+			rtoken.LExpr = limits[0]
+		} else if len(limits) == 2 && len(strings.TrimSpace(limits[1])) > 0 {
+			rtoken.LExpr = limits[0]
+			rtoken.RExpr = limits[1]
+		} else {
+			return r, util.FmtNewtError("invalid token in range expression \"%s\"", token)
+		}
+
+		r.Ranges = append(r.Ranges, rtoken)
+	}
+
+	return r, nil
+}
diff --git a/newt/syscfg/syscfg.go b/newt/syscfg/syscfg.go
index 6ae8801..41c1c87 100644
--- a/newt/syscfg/syscfg.go
+++ b/newt/syscfg/syscfg.go
@@ -468,6 +468,11 @@ func readSetting(name string, lpkg *pkg.LocalPackage,
 		entry.Restrictions = append(entry.Restrictions, r)
 	}
 
+	if vals["choices"] != nil && vals["range"] != nil {
+		return entry, util.FmtNewtError(
+			"setting %s uses both choice and range restrictions", name)
+	}
+
 	if vals["choices"] != nil {
 		choices := cast.ToStringSlice(vals["choices"])
 		entry.ValidChoices = choices
@@ -498,6 +503,16 @@ func readSetting(name string, lpkg *pkg.LocalPackage,
 		entry.Restrictions = append(entry.Restrictions, r)
 	}
 
+	if vals["range"] != nil {
+		r, err := createRangeRestriction(name, stringValue(vals["range"]))
+		if err != nil {
+			return entry,
+				util.PreNewtError(err, "error parsing setting %s", name)
+		}
+
+		entry.Restrictions = append(entry.Restrictions, r)
+	}
+
 	return entry, nil
 }