diff --git a/assert/assert.go b/assert/assert.go index bb9cb06..55ebfb8 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -123,7 +123,7 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool } case []byte: if bytes.Compare(expected, actual.([]byte)) != 0 { - return failExpectedActual(t, expected, actual, msg...) + return failExpectedActual(t, string(expected), string(actual.([]byte)), msg...) } case []int: if !equalIntSlice(expected, actual.([]int)) { @@ -160,7 +160,9 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool case *objects.Char: return Equal(t, expected.Value, actual.(*objects.Char).Value) case *objects.Bool: - return Equal(t, expected.Value, actual.(*objects.Bool).Value) + if expected != actual { + return failExpectedActual(t, expected, actual, msg...) + } case *objects.ReturnValue: return Equal(t, expected.Value, actual.(objects.ReturnValue).Value) case *objects.Array: @@ -169,7 +171,7 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value) case *objects.Bytes: if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 { - return failExpectedActual(t, expected.Value, actual.(*objects.Bytes).Value, msg...) + return failExpectedActual(t, string(expected.Value), string(actual.(*objects.Bytes).Value), msg...) } case *objects.Map: return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value) @@ -180,7 +182,9 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool case *objects.Closure: return equalClosure(t, expected, actual.(*objects.Closure)) case *objects.Undefined: - return true + if expected != actual { + return failExpectedActual(t, expected, actual, msg...) + } case *objects.Error: return Equal(t, expected.Value, actual.(*objects.Error).Value) case error: diff --git a/compiler/bytecode.go b/compiler/bytecode.go index 6e87585..aa806b5 100644 --- a/compiler/bytecode.go +++ b/compiler/bytecode.go @@ -21,7 +21,16 @@ func (b *Bytecode) Decode(r io.Reader) error { return err } - return dec.Decode(&b.Constants) + if err := dec.Decode(&b.Constants); err != nil { + return err + } + + // replace Bool and Undefined with known value + for i, v := range b.Constants { + b.Constants[i] = cleanupObjects(v) + } + + return nil } // Encode writes Bytecode data to the writer. @@ -32,9 +41,32 @@ func (b *Bytecode) Encode(w io.Writer) error { return err } + // constants return enc.Encode(b.Constants) } +func cleanupObjects(o objects.Object) objects.Object { + switch o := o.(type) { + case *objects.Bool: + if o.IsFalsy() { + return objects.FalseValue + } + return objects.TrueValue + case *objects.Undefined: + return objects.UndefinedValue + case *objects.Array: + for i, v := range o.Value { + o.Value[i] = cleanupObjects(v) + } + case *objects.Map: + for k, v := range o.Value { + o.Value[k] = cleanupObjects(v) + } + } + + return o +} + func init() { gob.Register(&objects.Int{}) gob.Register(&objects.Float{}) diff --git a/compiler/bytecode_test.go b/compiler/bytecode_test.go index b899f2c..15265e4 100644 --- a/compiler/bytecode_test.go +++ b/compiler/bytecode_test.go @@ -14,16 +14,19 @@ func TestBytecode(t *testing.T) { testBytecodeSerialization(t, bytecode( concat(), objectsArray( + objects.UndefinedValue, &objects.Array{ Value: objectsArray( &objects.Int{Value: 12}, &objects.String{Value: "foo"}, - &objects.Bool{Value: true}, + objects.TrueValue, + objects.FalseValue, &objects.Float{Value: 93.11}, &objects.Char{Value: 'x'}, + objects.UndefinedValue, ), }, - &objects.Bool{Value: false}, + objects.FalseValue, &objects.Char{Value: 'y'}, &objects.Float{Value: 93.11}, compiledFunction(1, 0, @@ -36,11 +39,12 @@ func TestBytecode(t *testing.T) { &objects.Map{ Value: map[string]objects.Object{ "a": &objects.Float{Value: -93.1}, - "b": &objects.Bool{Value: false}, + "b": objects.FalseValue, + "c": objects.UndefinedValue, }, }, &objects.String{Value: "bar"}, - &objects.Undefined{}))) + objects.UndefinedValue))) testBytecodeSerialization(t, bytecode( concat( diff --git a/compiler/stdlib/func_typedefs.go b/compiler/stdlib/func_typedefs.go index ab07845..375b29d 100644 --- a/compiler/stdlib/func_typedefs.go +++ b/compiler/stdlib/func_typedefs.go @@ -43,7 +43,11 @@ func FuncARB(fn func() bool) *objects.UserFunction { return nil, objects.ErrWrongNumArguments } - return &objects.Bool{Value: fn()}, nil + if fn() { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil }, } } @@ -340,7 +344,11 @@ func FuncAFIRB(fn func(float64, int) bool) *objects.UserFunction { return nil, objects.ErrInvalidTypeConversion } - return &objects.Bool{Value: fn(f1, i2)}, nil + if fn(f1, i2) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil }, } } @@ -359,7 +367,11 @@ func FuncAFRB(fn func(float64) bool) *objects.UserFunction { return nil, objects.ErrInvalidTypeConversion } - return &objects.Bool{Value: fn(f1)}, nil + if fn(f1) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil }, } } diff --git a/compiler/stdlib/func_typedefs_test.go b/compiler/stdlib/func_typedefs_test.go index 610e132..7d24f85 100644 --- a/compiler/stdlib/func_typedefs_test.go +++ b/compiler/stdlib/func_typedefs_test.go @@ -13,7 +13,7 @@ func TestFuncAIR(t *testing.T) { uf := stdlib.FuncAIR(func(int) {}) ret, err := uf.Call(&objects.Int{Value: 10}) assert.NoError(t, err) - assert.Equal(t, &objects.Undefined{}, ret) + assert.Equal(t, objects.UndefinedValue, ret) ret, err = uf.Call() assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -22,7 +22,7 @@ func TestFuncAR(t *testing.T) { uf := stdlib.FuncAR(func() {}) ret, err := uf.Call() assert.NoError(t, err) - assert.Equal(t, &objects.Undefined{}, ret) + assert.Equal(t, objects.UndefinedValue, ret) ret, err = uf.Call(objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -227,7 +227,7 @@ func TestFuncAFRB(t *testing.T) { }) ret, err := uf.Call(&objects.Float{Value: 0.1}) assert.NoError(t, err) - assert.Equal(t, &objects.Bool{Value: true}, ret) + assert.Equal(t, objects.TrueValue, ret) ret, err = uf.Call() assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue, objects.TrueValue) @@ -279,7 +279,7 @@ func TestFuncAFIRB(t *testing.T) { }) ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20}) assert.NoError(t, err) - assert.Equal(t, &objects.Bool{Value: true}, ret) + assert.Equal(t, objects.TrueValue, ret) ret, err = uf.Call() assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue) diff --git a/objects/bool.go b/objects/bool.go index b7ca19c..ac9949e 100644 --- a/objects/bool.go +++ b/objects/bool.go @@ -6,11 +6,12 @@ import ( // Bool represents a boolean value. type Bool struct { - Value bool + // this is intentionally non-public to force using objects.TrueValue and FalseValue always + value bool } func (o *Bool) String() string { - if o.Value { + if o.value { return "true" } @@ -30,22 +31,34 @@ func (o *Bool) BinaryOp(op token.Token, rhs Object) (Object, error) { // Copy returns a copy of the type. func (o *Bool) Copy() Object { - v := Bool{Value: o.Value} - return &v + return o } // IsFalsy returns true if the value of the type is falsy. func (o *Bool) IsFalsy() bool { - return !o.Value + return !o.value } // Equals returns true if the value of the type // is equal to the value of another object. func (o *Bool) Equals(x Object) bool { - t, ok := x.(*Bool) - if !ok { - return false + return o == x +} + +// GobDecode decodes bool value from input bytes. +func (o *Bool) GobDecode(b []byte) (err error) { + o.value = b[0] == 1 + + return +} + +// GobEncode encodes bool values into bytes. +func (o *Bool) GobEncode() (b []byte, err error) { + if o.value { + b = []byte{1} + } else { + b = []byte{0} } - return o.Value == t.Value + return } diff --git a/objects/builtin_convert.go b/objects/builtin_convert.go index cd2823d..f555ea8 100644 --- a/objects/builtin_convert.go +++ b/objects/builtin_convert.go @@ -77,7 +77,11 @@ func builtinBool(args ...Object) (Object, error) { v, ok := ToBool(args[0]) if ok { - return &Bool{Value: v}, nil + if v { + return TrueValue, nil + } + + return FalseValue, nil } return UndefinedValue, nil diff --git a/objects/builtin_type_checks.go b/objects/builtin_type_checks.go index b2af058..4009330 100644 --- a/objects/builtin_type_checks.go +++ b/objects/builtin_type_checks.go @@ -89,7 +89,7 @@ func builtinIsUndefined(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - if _, ok := args[0].(*Undefined); ok { + if args[0] == UndefinedValue { return TrueValue, nil } diff --git a/objects/conversion.go b/objects/conversion.go index 6b762a3..1c0ca81 100644 --- a/objects/conversion.go +++ b/objects/conversion.go @@ -7,7 +7,7 @@ import ( // ToString will try to convert object o to string value. func ToString(o Object) (v string, ok bool) { - if _, isUndefined := o.(*Undefined); isUndefined { + if o == UndefinedValue { //ok = false return } @@ -36,7 +36,7 @@ func ToInt(o Object) (v int, ok bool) { v = int(o.Value) ok = true case *Bool: - if o.Value { + if o == TrueValue { v = 1 } ok = true @@ -65,7 +65,7 @@ func ToInt64(o Object) (v int64, ok bool) { v = int64(o.Value) ok = true case *Bool: - if o.Value { + if o == TrueValue { v = 1 } ok = true @@ -150,7 +150,7 @@ func objectToInterface(o Object) (res interface{}) { case *Float: res = o.Value case *Bool: - res = o.Value + res = o == TrueValue case *Char: res = o.Value case *Bytes: @@ -176,7 +176,7 @@ func objectToInterface(o Object) (res interface{}) { func FromInterface(v interface{}) (Object, error) { switch v := v.(type) { case nil: - return &Undefined{}, nil + return UndefinedValue, nil case string: return &String{Value: v}, nil case int64: @@ -184,7 +184,10 @@ func FromInterface(v interface{}) (Object, error) { case int: return &Int{Value: int64(v)}, nil case bool: - return &Bool{Value: v}, nil + if v { + return TrueValue, nil + } + return FalseValue, nil case rune: return &Char{Value: v}, nil case byte: diff --git a/objects/float_test.go b/objects/float_test.go index 971b26e..c7f89c2 100644 --- a/objects/float_test.go +++ b/objects/float_test.go @@ -41,28 +41,28 @@ func TestFloat_BinaryOp(t *testing.T) { // float < float for l := float64(-2); l <= 2.1; l += 0.4 { for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Float{Value: r}, &objects.Bool{Value: l < r}) + testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Float{Value: r}, boolValue(l < r)) } } // float > float for l := float64(-2); l <= 2.1; l += 0.4 { for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Float{Value: r}, &objects.Bool{Value: l > r}) + testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Float{Value: r}, boolValue(l > r)) } } // float <= float for l := float64(-2); l <= 2.1; l += 0.4 { for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Float{Value: r}, &objects.Bool{Value: l <= r}) + testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Float{Value: r}, boolValue(l <= r)) } } // float >= float for l := float64(-2); l <= 2.1; l += 0.4 { for r := float64(-2); r <= 2.1; r += 0.4 { - testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Float{Value: r}, &objects.Bool{Value: l >= r}) + testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Float{Value: r}, boolValue(l >= r)) } } @@ -99,28 +99,28 @@ func TestFloat_BinaryOp(t *testing.T) { // float < int for l := float64(-2); l <= 2.1; l += 0.4 { for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Int{Value: r}, &objects.Bool{Value: l < float64(r)}) + testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Int{Value: r}, boolValue(l < float64(r))) } } // float > int for l := float64(-2); l <= 2.1; l += 0.4 { for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Int{Value: r}, &objects.Bool{Value: l > float64(r)}) + testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Int{Value: r}, boolValue(l > float64(r))) } } // float <= int for l := float64(-2); l <= 2.1; l += 0.4 { for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Int{Value: r}, &objects.Bool{Value: l <= float64(r)}) + testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Int{Value: r}, boolValue(l <= float64(r))) } } // float >= int for l := float64(-2); l <= 2.1; l += 0.4 { for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Int{Value: r}, &objects.Bool{Value: l >= float64(r)}) + testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Int{Value: r}, boolValue(l >= float64(r))) } } } diff --git a/objects/int_test.go b/objects/int_test.go index 3e896c4..8e7b350 100644 --- a/objects/int_test.go +++ b/objects/int_test.go @@ -114,28 +114,28 @@ func TestInt_BinaryOp(t *testing.T) { // int < int for l := int64(-2); l <= 2; l++ { for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Int{Value: r}, &objects.Bool{Value: l < r}) + testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Int{Value: r}, boolValue(l < r)) } } // int > int for l := int64(-2); l <= 2; l++ { for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Int{Value: r}, &objects.Bool{Value: l > r}) + testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Int{Value: r}, boolValue(l > r)) } } // int <= int for l := int64(-2); l <= 2; l++ { for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Int{Value: r}, &objects.Bool{Value: l <= r}) + testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Int{Value: r}, boolValue(l <= r)) } } // int >= int for l := int64(-2); l <= 2; l++ { for r := int64(-2); r <= 2; r++ { - testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Int{Value: r}, &objects.Bool{Value: l >= r}) + testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Int{Value: r}, boolValue(l >= r)) } } @@ -172,28 +172,28 @@ func TestInt_BinaryOp(t *testing.T) { // int < float for l := int64(-2); l <= 2; l++ { for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Float{Value: r}, &objects.Bool{Value: float64(l) < r}) + testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Float{Value: r}, boolValue(float64(l) < r)) } } // int > float for l := int64(-2); l <= 2; l++ { for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Float{Value: r}, &objects.Bool{Value: float64(l) > r}) + testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Float{Value: r}, boolValue(float64(l) > r)) } } // int <= float for l := int64(-2); l <= 2; l++ { for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Float{Value: r}, &objects.Bool{Value: float64(l) <= r}) + testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Float{Value: r}, boolValue(float64(l) <= r)) } } // int >= float for l := int64(-2); l <= 2; l++ { for r := float64(-2); r <= 2.1; r += 0.5 { - testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Float{Value: r}, &objects.Bool{Value: float64(l) >= r}) + testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Float{Value: r}, boolValue(float64(l) >= r)) } } } diff --git a/objects/objects.go b/objects/objects.go index eaa29dd..f3878b1 100644 --- a/objects/objects.go +++ b/objects/objects.go @@ -2,10 +2,10 @@ package objects var ( // TrueValue represents a true value. - TrueValue Object = &Bool{Value: true} + TrueValue Object = &Bool{value: true} // FalseValue represents a false value. - FalseValue Object = &Bool{Value: false} + FalseValue Object = &Bool{value: false} // UndefinedValue represents an undefined value. UndefinedValue Object = &Undefined{} diff --git a/objects/objects_test.go b/objects/objects_test.go index 23e8872..c411736 100644 --- a/objects/objects_test.go +++ b/objects/objects_test.go @@ -15,3 +15,11 @@ func testBinaryOp(t *testing.T, lhs objects.Object, op token.Token, rhs objects. return assert.NoError(t, err) && assert.Equal(t, expected, actual) } + +func boolValue(b bool) objects.Object { + if b { + return objects.TrueValue + } + + return objects.FalseValue +} diff --git a/objects/undefined.go b/objects/undefined.go index fbad34c..ab6f549 100644 --- a/objects/undefined.go +++ b/objects/undefined.go @@ -22,7 +22,7 @@ func (o *Undefined) BinaryOp(op token.Token, rhs Object) (Object, error) { // Copy returns a copy of the type. func (o *Undefined) Copy() Object { - return &Undefined{} + return o } // IsFalsy returns true if the value of the type is falsy. @@ -33,7 +33,5 @@ func (o *Undefined) IsFalsy() bool { // Equals returns true if the value of the type // is equal to the value of another object. func (o *Undefined) Equals(x Object) bool { - _, ok := x.(*Undefined) - - return ok + return o == x } diff --git a/runtime/vm.go b/runtime/vm.go index 47164ed..b4be714 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -617,23 +617,21 @@ func (v *VM) Run() error { left := v.stack[v.sp-3] v.sp -= 3 - var lowIdx, highIdx int64 - - switch low := (*low).(type) { - case *objects.Undefined: - //lowIdx = 0 - case *objects.Int: - lowIdx = low.Value - default: - return fmt.Errorf("non-integer slice index: %s", low.TypeName()) + var lowIdx int64 + if *low != objects.UndefinedValue { + if low, ok := (*low).(*objects.Int); ok { + lowIdx = low.Value + } else { + return fmt.Errorf("non-integer slice index: %s", low.TypeName()) + } } - switch high := (*high).(type) { - case *objects.Undefined: - highIdx = -1 // will be replaced by number of elements - case *objects.Int: + var highIdx int64 + if *high == objects.UndefinedValue { + highIdx = -1 + } else if high, ok := (*high).(*objects.Int); ok { highIdx = high.Value - default: + } else { return fmt.Errorf("non-integer slice index: %s", high.TypeName()) } diff --git a/runtime/vm_builtin_test.go b/runtime/vm_builtin_test.go index 5e853f2..aebbe78 100644 --- a/runtime/vm_builtin_test.go +++ b/runtime/vm_builtin_test.go @@ -2,6 +2,8 @@ package runtime_test import ( "testing" + + "github.com/d5/tengo/objects" ) func TestBuiltinFunction(t *testing.T) { @@ -24,14 +26,14 @@ func TestBuiltinFunction(t *testing.T) { expect(t, `out = int(true)`, 1) expect(t, `out = int(false)`, 0) expect(t, `out = int('8')`, 56) - expect(t, `out = int([1])`, undefined()) - expect(t, `out = int({a: 1})`, undefined()) - expect(t, `out = int(undefined)`, undefined()) + expect(t, `out = int([1])`, objects.UndefinedValue) + expect(t, `out = int({a: 1})`, objects.UndefinedValue) + expect(t, `out = int(undefined)`, objects.UndefinedValue) expect(t, `out = int("-522", 1)`, -522) expect(t, `out = int(undefined, 1)`, 1) expect(t, `out = int(undefined, 1.8)`, 1.8) expect(t, `out = int(undefined, string(1))`, "1") - expect(t, `out = int(undefined, undefined)`, undefined()) + expect(t, `out = int(undefined, undefined)`, objects.UndefinedValue) expect(t, `out = string(1)`, "1") expect(t, `out = string(1.8)`, "1.8") @@ -41,40 +43,40 @@ func TestBuiltinFunction(t *testing.T) { expect(t, `out = string('8')`, "8") expect(t, `out = string([1,8.1,true,3])`, "[1, 8.1, true, 3]") expect(t, `out = string({b: "foo"})`, `{b: "foo"}`) - expect(t, `out = string(undefined)`, undefined()) // not "undefined" + expect(t, `out = string(undefined)`, objects.UndefinedValue) // not "undefined" expect(t, `out = string(1, "-522")`, "1") expect(t, `out = string(undefined, "-522")`, "-522") // not "undefined" expect(t, `out = float(1)`, 1.0) expect(t, `out = float(1.8)`, 1.8) expect(t, `out = float("-52.2")`, -52.2) - expect(t, `out = float(true)`, undefined()) - expect(t, `out = float(false)`, undefined()) - expect(t, `out = float('8')`, undefined()) - expect(t, `out = float([1,8.1,true,3])`, undefined()) - expect(t, `out = float({a: 1, b: "foo"})`, undefined()) - expect(t, `out = float(undefined)`, undefined()) + expect(t, `out = float(true)`, objects.UndefinedValue) + expect(t, `out = float(false)`, objects.UndefinedValue) + expect(t, `out = float('8')`, objects.UndefinedValue) + expect(t, `out = float([1,8.1,true,3])`, objects.UndefinedValue) + expect(t, `out = float({a: 1, b: "foo"})`, objects.UndefinedValue) + expect(t, `out = float(undefined)`, objects.UndefinedValue) expect(t, `out = float("-52.2", 1.8)`, -52.2) expect(t, `out = float(undefined, 1)`, 1) expect(t, `out = float(undefined, 1.8)`, 1.8) expect(t, `out = float(undefined, "-52.2")`, "-52.2") expect(t, `out = float(undefined, char(56))`, '8') - expect(t, `out = float(undefined, undefined)`, undefined()) + expect(t, `out = float(undefined, undefined)`, objects.UndefinedValue) expect(t, `out = char(56)`, '8') - expect(t, `out = char(1.8)`, undefined()) - expect(t, `out = char("-52.2")`, undefined()) - expect(t, `out = char(true)`, undefined()) - expect(t, `out = char(false)`, undefined()) + expect(t, `out = char(1.8)`, objects.UndefinedValue) + expect(t, `out = char("-52.2")`, objects.UndefinedValue) + expect(t, `out = char(true)`, objects.UndefinedValue) + expect(t, `out = char(false)`, objects.UndefinedValue) expect(t, `out = char('8')`, '8') - expect(t, `out = char([1,8.1,true,3])`, undefined()) - expect(t, `out = char({a: 1, b: "foo"})`, undefined()) - expect(t, `out = char(undefined)`, undefined()) + expect(t, `out = char([1,8.1,true,3])`, objects.UndefinedValue) + expect(t, `out = char({a: 1, b: "foo"})`, objects.UndefinedValue) + expect(t, `out = char(undefined)`, objects.UndefinedValue) expect(t, `out = char(56, 'a')`, '8') expect(t, `out = char(undefined, '8')`, '8') expect(t, `out = char(undefined, 56)`, 56) expect(t, `out = char(undefined, "-52.2")`, "-52.2") - expect(t, `out = char(undefined, undefined)`, undefined()) + expect(t, `out = char(undefined, undefined)`, objects.UndefinedValue) expect(t, `out = bool(1)`, true) // non-zero integer: true expect(t, `out = bool(0)`, false) // zero: true @@ -93,20 +95,20 @@ func TestBuiltinFunction(t *testing.T) { expect(t, `out = bool(undefined)`, false) // undefined: false expect(t, `out = bytes(1)`, []byte{0}) - expect(t, `out = bytes(1.8)`, undefined()) + expect(t, `out = bytes(1.8)`, objects.UndefinedValue) expect(t, `out = bytes("-522")`, []byte{'-', '5', '2', '2'}) - expect(t, `out = bytes(true)`, undefined()) - expect(t, `out = bytes(false)`, undefined()) - expect(t, `out = bytes('8')`, undefined()) - expect(t, `out = bytes([1])`, undefined()) - expect(t, `out = bytes({a: 1})`, undefined()) - expect(t, `out = bytes(undefined)`, undefined()) + expect(t, `out = bytes(true)`, objects.UndefinedValue) + expect(t, `out = bytes(false)`, objects.UndefinedValue) + expect(t, `out = bytes('8')`, objects.UndefinedValue) + expect(t, `out = bytes([1])`, objects.UndefinedValue) + expect(t, `out = bytes({a: 1})`, objects.UndefinedValue) + expect(t, `out = bytes(undefined)`, objects.UndefinedValue) expect(t, `out = bytes("-522", ['8'])`, []byte{'-', '5', '2', '2'}) expect(t, `out = bytes(undefined, "-522")`, "-522") expect(t, `out = bytes(undefined, 1)`, 1) expect(t, `out = bytes(undefined, 1.8)`, 1.8) expect(t, `out = bytes(undefined, int("-522"))`, -522) - expect(t, `out = bytes(undefined, undefined)`, undefined()) + expect(t, `out = bytes(undefined, undefined)`, objects.UndefinedValue) expect(t, `out = is_error(error(1))`, true) expect(t, `out = is_error(1)`, false) diff --git a/runtime/vm_function_test.go b/runtime/vm_function_test.go index e541700..4d4d115 100644 --- a/runtime/vm_function_test.go +++ b/runtime/vm_function_test.go @@ -2,13 +2,15 @@ package runtime_test import ( "testing" + + "github.com/d5/tengo/objects" ) func TestFunction(t *testing.T) { // function with no "return" statement returns "invalid" value. - expect(t, `f1 := func() {}; out = f1();`, undefined()) - expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, undefined()) - expect(t, `f := func(x) { x; }; out = f(5);`, undefined()) + expect(t, `f1 := func() {}; out = f1();`, objects.UndefinedValue) + expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, objects.UndefinedValue) + expect(t, `f := func(x) { x; }; out = f(5);`, objects.UndefinedValue) expect(t, `f := func(x) { return x; }; out = f(5);`, 5) expect(t, `f := func(x) { return x * 2; }; out = f(5);`, 10) diff --git a/runtime/vm_if_test.go b/runtime/vm_if_test.go index 34ab117..648f064 100644 --- a/runtime/vm_if_test.go +++ b/runtime/vm_if_test.go @@ -2,16 +2,18 @@ package runtime_test import ( "testing" + + "github.com/d5/tengo/objects" ) func TestIf(t *testing.T) { expect(t, `if (true) { out = 10 }`, 10) - expect(t, `if (false) { out = 10 }`, undefined()) + expect(t, `if (false) { out = 10 }`, objects.UndefinedValue) expect(t, `if (false) { out = 10 } else { out = 20 }`, 20) expect(t, `if (1) { out = 10 }`, 10) expect(t, `if (0) { out = 10 } else { out = 20 }`, 20) expect(t, `if (1 < 2) { out = 10 }`, 10) - expect(t, `if (1 > 2) { out = 10 }`, undefined()) + expect(t, `if (1 > 2) { out = 10 }`, objects.UndefinedValue) expect(t, `if (1 < 2) { out = 10 } else { out = 20 }`, 10) expect(t, `if (1 > 2) { out = 10 } else { out = 20 }`, 20) diff --git a/runtime/vm_indexable_test.go b/runtime/vm_indexable_test.go index 19959ca..a98a8e3 100644 --- a/runtime/vm_indexable_test.go +++ b/runtime/vm_indexable_test.go @@ -159,7 +159,7 @@ func TestIndexable(t *testing.T) { dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} } expectWithSymbols(t, `out = dict["a"]`, "foo", SYM{"dict": dict()}) expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()}) - expectWithSymbols(t, `out = dict["x"]`, undefined(), SYM{"dict": dict()}) + expectWithSymbols(t, `out = dict["x"]`, objects.UndefinedValue, SYM{"dict": dict()}) expectErrorWithSymbols(t, `out = dict[0]`, SYM{"dict": dict()}) strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } @@ -173,7 +173,7 @@ func TestIndexable(t *testing.T) { strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr["three"]`, 2, SYM{"arr": strArr()}) - expectWithSymbols(t, `out = arr["four"]`, undefined(), SYM{"arr": strArr()}) + expectWithSymbols(t, `out = arr["four"]`, objects.UndefinedValue, SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()}) expectErrorWithSymbols(t, `out = arr[-1]`, SYM{"arr": strArr()}) diff --git a/runtime/vm_map_test.go b/runtime/vm_map_test.go index 740d559..35090b3 100644 --- a/runtime/vm_map_test.go +++ b/runtime/vm_map_test.go @@ -2,6 +2,8 @@ package runtime_test import ( "testing" + + "github.com/d5/tengo/objects" ) func TestMap(t *testing.T) { @@ -17,9 +19,9 @@ out = { }) expect(t, `out = {foo: 5}["foo"]`, 5) - expect(t, `out = {foo: 5}["bar"]`, undefined()) + expect(t, `out = {foo: 5}["bar"]`, objects.UndefinedValue) expect(t, `key := "foo"; out = {foo: 5}[key]`, 5) - expect(t, `out = {}["foo"]`, undefined()) + expect(t, `out = {}["foo"]`, objects.UndefinedValue) expect(t, ` m := { diff --git a/runtime/vm_selector_test.go b/runtime/vm_selector_test.go index 9410773..1212d20 100644 --- a/runtime/vm_selector_test.go +++ b/runtime/vm_selector_test.go @@ -2,12 +2,14 @@ package runtime_test import ( "testing" + + "github.com/d5/tengo/objects" ) func TestSelector(t *testing.T) { expect(t, `a := {k1: 5, k2: "foo"}; out = a.k1`, 5) expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, "foo") - expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, undefined()) + expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, objects.UndefinedValue) expect(t, ` a := { diff --git a/runtime/vm_test.go b/runtime/vm_test.go index 6a11697..4b21e82 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -133,7 +133,10 @@ func toObject(v interface{}) objects.Object { case int: // for convenience return &objects.Int{Value: int64(v)} case bool: - return &objects.Bool{Value: v} + if v { + return objects.TrueValue + } + return objects.FalseValue case rune: return &objects.Char{Value: v} case byte: // for convenience @@ -326,7 +329,7 @@ func objectZeroCopy(o objects.Object) objects.Object { case *objects.Map: return &objects.Map{} case *objects.Undefined: - return &objects.Undefined{} + return objects.UndefinedValue case *objects.Error: return &objects.Error{} case *objects.Bytes: @@ -341,7 +344,3 @@ func objectZeroCopy(o objects.Object) objects.Object { panic(fmt.Errorf("unknown object type: %s", o.TypeName())) } } - -func undefined() *objects.Undefined { - return &objects.Undefined{} -} diff --git a/runtime/vm_undefined_test.go b/runtime/vm_undefined_test.go index 4e445b0..4e8b75e 100644 --- a/runtime/vm_undefined_test.go +++ b/runtime/vm_undefined_test.go @@ -1,9 +1,13 @@ package runtime_test -import "testing" +import ( + "testing" + + "github.com/d5/tengo/objects" +) func TestUndefined(t *testing.T) { - expect(t, `out = undefined`, undefined()) + expect(t, `out = undefined`, objects.UndefinedValue) expect(t, `out = undefined == undefined`, true) expect(t, `out = undefined == 1`, false) expect(t, `out = 1 == undefined`, false) diff --git a/script/compiled.go b/script/compiled.go index 64d6fa8..c26b8f2 100644 --- a/script/compiled.go +++ b/script/compiled.go @@ -8,8 +8,6 @@ import ( "github.com/d5/tengo/runtime" ) -var undefined objects.Object = &objects.Undefined{} - // Compiled is a compiled instance of the user script. // Use Script.Compile() to create Compiled object. type Compiled struct { @@ -53,20 +51,18 @@ func (c *Compiled) IsDefined(name string) bool { return false } - _, isUndefined := (*v).(*objects.Undefined) - - return !isUndefined + return *v != objects.UndefinedValue } // Get returns a variable identified by the name. func (c *Compiled) Get(name string) *Variable { - value := &undefined + value := &objects.UndefinedValue symbol, _, ok := c.symbolTable.Resolve(name) if ok && symbol.Scope == compiler.ScopeGlobal { value = c.machine.Globals()[symbol.Index] if value == nil { - value = &undefined + value = &objects.UndefinedValue } } @@ -84,7 +80,7 @@ func (c *Compiled) GetAll() []*Variable { if ok && symbol.Scope == compiler.ScopeGlobal { value := c.machine.Globals()[symbol.Index] if value == nil { - value = &undefined + value = &objects.UndefinedValue } vars = append(vars, &Variable{ diff --git a/script/conversion.go b/script/conversion.go index 440ef9a..c35c141 100644 --- a/script/conversion.go +++ b/script/conversion.go @@ -15,7 +15,10 @@ func objectToInterface(o objects.Object) interface{} { case *objects.Float: return val.Value case *objects.Bool: - return val.Value + if val == objects.TrueValue { + return true + } + return false case *objects.Char: return val.Value case *objects.String: diff --git a/script/variable.go b/script/variable.go index 13aa210..c5e01bd 100644 --- a/script/variable.go +++ b/script/variable.go @@ -145,7 +145,5 @@ func (v *Variable) Object() objects.Object { // IsUndefined returns true if the underlying value is undefined. func (v *Variable) IsUndefined() bool { - _, isUndefined := (*v.value).(*objects.Undefined) - - return isUndefined + return *v.value == objects.UndefinedValue } diff --git a/script/variable_test.go b/script/variable_test.go index 45894f8..1a521ee 100644 --- a/script/variable_test.go +++ b/script/variable_test.go @@ -54,7 +54,7 @@ func TestVariable(t *testing.T) { FloatValue: 0, BoolValue: true, StringValue: "true", - Object: &objects.Bool{Value: true}, + Object: objects.TrueValue, }, { Name: "d",