diff --git a/README.md b/README.md index 96284e5..26ca75e 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,10 @@ In addition to boolean expressions, sepcial contants `True` and `False` may be u Do not double-quote them, or they will become plain strings! +## MultiValue + +This is container `Value`. It can contain zero or any number of `Value`'s. Currently, this is only truly useful with functions, mostly because it is yet undecided how to define what operations would mean on a `MultiValue`. + ## Supported operations * Operators: `+` `-` `*` `/` `%` `**` `<<` `>>` `<` `<=` `==` `!=` `>` `>=` `And` `&&` `Or` `||` @@ -144,6 +148,7 @@ Do not double-quote them, or they will become plain strings! * Go classifies bit shift operators with the higher `*`. * `&&` is synonymous of `And`. * `||` is synonymous of `Or`. + * Worded operators such as `And` and `Or` are **case-sensitive** and must be followed by a blank character. `True Or (False)` is a Bool expression with the `Or` operator but `True Or(False)` is an invalid expression attempting to call a user-defined function called `Or()`. * Types: String, Number, Bool, MultiValue * Associativity with parentheses: `(` and `)` * Functions: diff --git a/gal_test.go b/gal_test.go index b5d856e..9096ebb 100644 --- a/gal_test.go +++ b/gal_test.go @@ -134,6 +134,16 @@ func TestEval_Boolean(t *testing.T) { expr = `True Or False` val = gal.Parse(expr).Eval() assert.Equal(t, gal.True.String(), val.String()) + + expr = `True Or (False)` + val = gal.Parse(expr).Eval() + assert.Equal(t, gal.True.String(), val.String()) + + // in this expression, the `()` are attached to `Or` which makes `Or()` a user-defined + // function, rather than the `Or` operator. + expr = `True Or(False)` + val = gal.Parse(expr).Eval() + assert.Equal(t, `undefined: unknown function 'Or'`, val.String()) } func TestWithVariablesAndFunctions(t *testing.T) { diff --git a/tree.go b/tree.go index 4be3046..5be8742 100644 --- a/tree.go +++ b/tree.go @@ -174,7 +174,13 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree var val entry var op Operator = invalidOperator //nolint: stylecheck + slog.Debug("Tree.Calc: start walking Tree", "tree", tree.String()) for i := 0; i < tree.TrunkLen(); i++ { + if v, ok := val.(Undefined); ok { + slog.Debug("Tree.Calc: val is Undefined", "i", i, "val", v.String()) + return Tree{v} + } + e := tree[i] slog.Debug("Tree.Calc: entry in Tree", "i", i, "kind", e.kind().String()) if e == nil { @@ -211,6 +217,10 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree } rhsVal := e.(Tree).Eval(WithFunctions(cfg.functions), WithVariables(cfg.variables)) + if v, ok := rhsVal.(Undefined); ok { + slog.Debug("Tree.Calc: val is Undefined", "i", i, "val", v.String()) + return Tree{v} + } if val == nil { val = rhsVal continue @@ -230,6 +240,7 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree outTree = append(outTree, val) } outTree = append(outTree, op) + // just found and process the current operator - now, reset val and op and start again from fresh val = nil op = invalidOperator @@ -239,14 +250,20 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree if f.BodyFn == nil { f.BodyFn = cfg.functions.Function(f.Name) } + rhsVal := f.Eval(WithFunctions(cfg.functions), WithVariables(cfg.variables)) + if v, ok := rhsVal.(Undefined); ok { + slog.Debug("Tree.Calc: val is Undefined", "i", i, "val", v.String()) + return Tree{v} + } if val == nil { val = rhsVal continue } + lhsVal := val val = calculate(val.(Value), op, rhsVal) - slog.Debug("Tree.Calc: functionEntryKind - calculate", "i", i, "val", val.(Value).String(), "op", op.String(), "rhsVal", rhsVal.String(), "result", val.(Value).String()) + slog.Debug("Tree.Calc: functionEntryKind - calculate", "i", i, "lhsVal", lhsVal.(Value).String(), "op", op.String(), "rhsVal", rhsVal.String(), "result", val.(Value).String()) case variableEntryKind: slog.Debug("Tree.Calc: variableEntryKind", "i", i, "name", e.(Variable).Name) @@ -268,11 +285,13 @@ func (tree Tree) Calc(isOperatorInPrecedenceGroup func(Operator) bool, cfg *tree slog.Debug("Tree.Calc: variableEntryKind - calculate", "i", i, "val", val.(Value).String(), "op", op.String(), "rhsVal", rhsVal.String(), "result", val.(Value).String()) case unknownEntryKind: + slog.Debug("Tree.Calc: unknownEntryKind", "i", i, "val", val, "op", op.String(), "e", e) return Tree{e} default: + slog.Debug("Tree.Calc: default case", "i", i, "val", val, "op", op.String(), "e", e) return Tree{ - NewUndefinedWithReasonf("internal error: unknown entry kind: '%v'", e.kind()), + NewUndefinedWithReasonf("internal error: unknown entry kind: '%s'", e.kind().String()), } } } diff --git a/value.go b/value.go index 8f0a405..87d46a4 100644 --- a/value.go +++ b/value.go @@ -76,7 +76,7 @@ func (m MultiValue) AsString() String { func (m MultiValue) Get(i int) Value { if i > len(m.values) { - return NewUndefinedWithReasonf(fmt.Sprintf("out of bounds: trying to get arg #%d on MultiValue that has %d arguments", i, len(m.values))) + return NewUndefinedWithReasonf("out of bounds: trying to get arg #%d on MultiValue that has %d arguments", i, len(m.values)) } return m.values[i] @@ -400,7 +400,7 @@ func (n Number) Trunc(precision int32) Number { func (n Number) Factorial() Value { if !n.value.IsInteger() || n.value.IsNegative() { - return NewUndefinedWithReasonf(fmt.Sprintf("Factorial: requires a positive integer, cannot accept %s", n.String())) + return NewUndefinedWithReasonf("Factorial: requires a positive integer, cannot accept %s", n.String()) } res := decimal.NewFromInt(1)