Repository: d5/tengo
Pull request #391 (2022-08-12 11:49:46)

fix: do not invert token.Less to token.Greater in compiler
I have posted an issue explaining this https://github.com/d5/tengo/issues/390

shiyuge: Thanks for the reply. A new test case is added

Patch:
diff --git a/compiler.go b/compiler.go
index 0d46a4d..f5fc553 100644
--- a/compiler.go
+++ b/compiler.go
@@ -141,25 +141,7 @@ func (c *Compiler) Compile(node parser.Node) error {
 		if node.Token == token.LAnd || node.Token == token.LOr {
 			return c.compileLogical(node)
 		}
-		if node.Token == token.Less {
-			if err := c.Compile(node.RHS); err != nil {
-				return err
-			}
-			if err := c.Compile(node.LHS); err != nil {
-				return err
-			}
-			c.emit(node, parser.OpBinaryOp, int(token.Greater))
-			return nil
-		} else if node.Token == token.LessEq {
-			if err := c.Compile(node.RHS); err != nil {
-				return err
-			}
-			if err := c.Compile(node.LHS); err != nil {
-				return err
-			}
-			c.emit(node, parser.OpBinaryOp, int(token.GreaterEq))
-			return nil
-		}
+
 		if err := c.Compile(node.LHS); err != nil {
 			return err
 		}
@@ -182,6 +164,10 @@ func (c *Compiler) Compile(node parser.Node) error {
 			c.emit(node, parser.OpBinaryOp, int(token.Greater))
 		case token.GreaterEq:
 			c.emit(node, parser.OpBinaryOp, int(token.GreaterEq))
+		case token.Less:
+			c.emit(node, parser.OpBinaryOp, int(token.Less))
+		case token.LessEq:
+			c.emit(node, parser.OpBinaryOp, int(token.LessEq))
 		case token.Equal:
 			c.emit(node, parser.OpEqual)
 		case token.NotEqual:


Test patch:
diff --git a/compiler_test.go b/compiler_test.go
index d525b47..28cc901 100644
--- a/compiler_test.go
+++ b/compiler_test.go
@@ -107,12 +107,12 @@ func TestCompiler_Compile(t *testing.T) {
 			concatInsts(
 				tengo.MakeInstruction(parser.OpConstant, 0),
 				tengo.MakeInstruction(parser.OpConstant, 1),
-				tengo.MakeInstruction(parser.OpBinaryOp, 39),
+				tengo.MakeInstruction(parser.OpBinaryOp, 38),
 				tengo.MakeInstruction(parser.OpPop),
 				tengo.MakeInstruction(parser.OpSuspend)),
 			objectsArray(
-				intObject(2),
-				intObject(1))))
+				intObject(1),
+				intObject(2))))
 
 	expectCompile(t, `1 >= 2`,
 		bytecode(
@@ -131,12 +131,12 @@ func TestCompiler_Compile(t *testing.T) {
 			concatInsts(
 				tengo.MakeInstruction(parser.OpConstant, 0),
 				tengo.MakeInstruction(parser.OpConstant, 1),
-				tengo.MakeInstruction(parser.OpBinaryOp, 44),
+				tengo.MakeInstruction(parser.OpBinaryOp, 43),
 				tengo.MakeInstruction(parser.OpPop),
 				tengo.MakeInstruction(parser.OpSuspend)),
 			objectsArray(
-				intObject(2),
-				intObject(1))))
+				intObject(1),
+				intObject(2))))
 
 	expectCompile(t, `1 == 2`,
 		bytecode(
@@ -929,9 +929,9 @@ func() {
 			concatInsts(
 				tengo.MakeInstruction(parser.OpConstant, 0),
 				tengo.MakeInstruction(parser.OpSetGlobal, 0),
-				tengo.MakeInstruction(parser.OpConstant, 1),
 				tengo.MakeInstruction(parser.OpGetGlobal, 0),
-				tengo.MakeInstruction(parser.OpBinaryOp, 39),
+				tengo.MakeInstruction(parser.OpConstant, 1),
+				tengo.MakeInstruction(parser.OpBinaryOp, 38),
 				tengo.MakeInstruction(parser.OpJumpFalsy, 31),
 				tengo.MakeInstruction(parser.OpGetGlobal, 0),
 				tengo.MakeInstruction(parser.OpConstant, 2),
@@ -978,9 +978,9 @@ func() {
 				tengo.MakeInstruction(parser.OpConstant, 1),
 				tengo.MakeInstruction(parser.OpNotEqual),
 				tengo.MakeInstruction(parser.OpOrJump, 34),
-				tengo.MakeInstruction(parser.OpConstant, 1),
 				tengo.MakeInstruction(parser.OpGetGlobal, 0),
-				tengo.MakeInstruction(parser.OpBinaryOp, 39),
+				tengo.MakeInstruction(parser.OpConstant, 1),
+				tengo.MakeInstruction(parser.OpBinaryOp, 38),
 				tengo.MakeInstruction(parser.OpPop),
 				tengo.MakeInstruction(parser.OpSuspend)),
 			objectsArray(
diff --git a/script_test.go b/script_test.go
index 5e8f363..87cbe51 100644
--- a/script_test.go
+++ b/script_test.go
@@ -5,6 +5,7 @@ import (
 	"errors"
 	"fmt"
 	"math/rand"
+	"strconv"
 	"strings"
 	"sync"
 	"testing"
@@ -479,6 +480,67 @@ func TestCompiled_RunContext(t *testing.T) {
 	require.Equal(t, context.DeadlineExceeded, err)
 }
 
+func TestCompiled_CustomObject(t *testing.T) {
+	c := compile(t, `r := (t<130)`, M{"t": &customNumber{value: 123}})
+	compiledRun(t, c)
+	compiledGet(t, c, "r", true)
+
+	c = compile(t, `r := (t>13)`, M{"t": &customNumber{value: 123}})
+	compiledRun(t, c)
+	compiledGet(t, c, "r", true)
+}
+
+// customNumber is a user defined object that can compare to tengo.Int
+// very shitty implementation, just to test that token.Less and token.Greater in BinaryOp works
+type customNumber struct {
+	tengo.ObjectImpl
+	value int64
+}
+
+func (n *customNumber) TypeName() string {
+	return "Number"
+}
+
+func (n *customNumber) String() string {
+	return strconv.FormatInt(n.value, 10)
+}
+
+func (n *customNumber) BinaryOp(op token.Token, rhs tengo.Object) (tengo.Object, error) {
+	tengoInt, ok := rhs.(*tengo.Int)
+	if !ok {
+		return nil, tengo.ErrInvalidOperator
+	}
+	return n.binaryOpInt(op, tengoInt)
+}
+
+func (n *customNumber) binaryOpInt(op token.Token, rhs *tengo.Int) (tengo.Object, error) {
+	i := n.value
+
+	switch op {
+	case token.Less:
+		if i < rhs.Value {
+			return tengo.TrueValue, nil
+		}
+		return tengo.FalseValue, nil
+	case token.Greater:
+		if i > rhs.Value {
+			return tengo.TrueValue, nil
+		}
+		return tengo.FalseValue, nil
+	case token.LessEq:
+		if i <= rhs.Value {
+			return tengo.TrueValue, nil
+		}
+		return tengo.FalseValue, nil
+	case token.GreaterEq:
+		if i >= rhs.Value {
+			return tengo.TrueValue, nil
+		}
+		return tengo.FalseValue, nil
+	}
+	return nil, tengo.ErrInvalidOperator
+}
+
 func compile(t *testing.T, input string, vars M) *tengo.Compiled {
 	s := tengo.NewScript([]byte(input))
 	for vn, vv := range vars {