force using TrueValue, FalseValue, UndefinedValue

This commit is contained in:
Daniel Kang 2019-01-28 13:09:40 -08:00
parent 481d79cf68
commit 85da0cdc24
27 changed files with 214 additions and 128 deletions

View file

@ -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:

View file

@ -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{})

View file

@ -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(

View file

@ -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
},
}
}

View file

@ -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)

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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:

View file

@ -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)))
}
}
}

View file

@ -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))
}
}
}

View file

@ -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{}

View file

@ -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
}

View file

@ -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
}

View file

@ -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())
}

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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()})

View file

@ -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 := {

View file

@ -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 := {

View file

@ -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{}
}

View file

@ -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)

View file

@ -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{

View file

@ -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:

View file

@ -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
}

View file

@ -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",