'and' and 'or' operators are now evaluated using logic shortcut (this behaviour can be changed setting system var '_bool_shortcut' to false

This commit is contained in:
Celestino Amoroso 2024-03-31 05:09:24 +02:00
parent 94ad968d5e
commit aa705e68bf
2 changed files with 87 additions and 11 deletions

View File

@ -4,6 +4,8 @@
// operator-bool.go
package expr
import "fmt"
//-------- NOT term
func newNotTerm(tk *Token) (inst *term) {
@ -48,6 +50,15 @@ func newAndTerm(tk *Token) (inst *term) {
}
func evalAnd(ctx exprContext, self *term) (v any, err error) {
if isEnabled(ctx, preset_bool_shortcut) {
v, err = evalAndWithShortcut(ctx, self)
} else {
v, err = evalAndWithoutShortcut(ctx, self)
}
return
}
func evalAndWithoutShortcut(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftBool, rightBool bool
var lok, rok bool
@ -67,6 +78,32 @@ func evalAnd(ctx exprContext, self *term) (v any, err error) {
return
}
func evalAndWithShortcut(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if err = self.checkOperands(); err != nil {
return
}
if leftValue, err = self.children[0].compute(ctx); err != nil {
return
}
if leftBool, lok := toBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool)
return
} else if !leftBool {
v = false
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if rightBool, rok := toBool(rightValue); rok {
v = rightBool
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
}
return
}
//-------- OR term
func newOrTerm(tk *Token) (inst *term) {
@ -82,6 +119,15 @@ func newOrTerm(tk *Token) (inst *term) {
}
func evalOr(ctx exprContext, self *term) (v any, err error) {
if isEnabled(ctx, preset_bool_shortcut) {
v, err = evalOrWithShortcut(ctx, self)
} else {
v, err = evalOrWithoutShortcut(ctx, self)
}
return
}
func evalOrWithoutShortcut(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftBool, rightBool bool
var lok, rok bool
@ -101,6 +147,32 @@ func evalOr(ctx exprContext, self *term) (v any, err error) {
return
}
func evalOrWithShortcut(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if err = self.checkOperands(); err != nil {
return
}
if leftValue, err = self.children[0].compute(ctx); err != nil {
return
}
if leftBool, lok := toBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool)
return
} else if leftBool {
v = true
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if rightBool, rok := toBool(rightValue); rok {
v = rightBool
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
}
return
}
// init
func init() {
registerTermConstructor(SymNot, newNotTerm)

View File

@ -18,15 +18,9 @@ func TestParser(t *testing.T) {
wantErr error
}
// ctx := newTestContext()
ctx := NewSimpleFuncStore()
ctx.SetValue("var1", int64(123))
ctx.SetValue("var2", "abc")
// ctx.addFunc("add", addFunc)
importMathFuncs(ctx)
// inputs1 := []inputType{
// {`x=2 but x*10`, int64(20), nil},
// {`true AND true`, true, nil},
// {`"a" < "b" AND ~ 2 == 1`, true, nil},
// }
inputs := []inputType{
@ -125,17 +119,27 @@ func TestParser(t *testing.T) {
/* 93 */ {`2+3 but 5*2`, int64(10), nil},
/* 94 */ {`add(1,2) but var2`, "abc", nil},
/* 95 */ {`x=2`, int64(2), nil},
/* 95 */ {`x=2 but x*10`, int64(20), nil},
/* 96 */ {`x=2 but x*10`, int64(20), nil},
/* 97 */ {`false and true`, false, nil},
/* 98 */ {`false and (x==2)`, false, nil},
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable "x"`)},
/*100 */ {`false or true`, true, nil},
/*101 */ {`false or (x==2)`, nil, errors.New(`undefined variable "x"`)},
}
succeeded := 0
failed := 0
parser := NewParser(ctx)
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetValue("var1", int64(123))
ctx.SetValue("var2", "abc")
importMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
@ -167,7 +171,7 @@ func TestParser(t *testing.T) {
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
func TestListParser(t *testing.T) {
func NoTestListParser(t *testing.T) {
type inputType struct {
source string
wantResult any