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: case []byte:
if bytes.Compare(expected, actual.([]byte)) != 0 { if bytes.Compare(expected, actual.([]byte)) != 0 {
return failExpectedActual(t, expected, actual, msg...) return failExpectedActual(t, string(expected), string(actual.([]byte)), msg...)
} }
case []int: case []int:
if !equalIntSlice(expected, actual.([]int)) { if !equalIntSlice(expected, actual.([]int)) {
@ -160,7 +160,9 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
case *objects.Char: case *objects.Char:
return Equal(t, expected.Value, actual.(*objects.Char).Value) return Equal(t, expected.Value, actual.(*objects.Char).Value)
case *objects.Bool: case *objects.Bool:
return Equal(t, expected.Value, actual.(*objects.Bool).Value) if expected != actual {
return failExpectedActual(t, expected, actual, msg...)
}
case *objects.ReturnValue: case *objects.ReturnValue:
return Equal(t, expected.Value, actual.(objects.ReturnValue).Value) return Equal(t, expected.Value, actual.(objects.ReturnValue).Value)
case *objects.Array: 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) return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value)
case *objects.Bytes: case *objects.Bytes:
if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 { 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: case *objects.Map:
return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value) 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: case *objects.Closure:
return equalClosure(t, expected, actual.(*objects.Closure)) return equalClosure(t, expected, actual.(*objects.Closure))
case *objects.Undefined: case *objects.Undefined:
return true if expected != actual {
return failExpectedActual(t, expected, actual, msg...)
}
case *objects.Error: case *objects.Error:
return Equal(t, expected.Value, actual.(*objects.Error).Value) return Equal(t, expected.Value, actual.(*objects.Error).Value)
case error: case error:

View file

@ -21,7 +21,16 @@ func (b *Bytecode) Decode(r io.Reader) error {
return err 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. // Encode writes Bytecode data to the writer.
@ -32,9 +41,32 @@ func (b *Bytecode) Encode(w io.Writer) error {
return err return err
} }
// constants
return enc.Encode(b.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() { func init() {
gob.Register(&objects.Int{}) gob.Register(&objects.Int{})
gob.Register(&objects.Float{}) gob.Register(&objects.Float{})

View file

@ -14,16 +14,19 @@ func TestBytecode(t *testing.T) {
testBytecodeSerialization(t, bytecode( testBytecodeSerialization(t, bytecode(
concat(), objectsArray( concat(), objectsArray(
objects.UndefinedValue,
&objects.Array{ &objects.Array{
Value: objectsArray( Value: objectsArray(
&objects.Int{Value: 12}, &objects.Int{Value: 12},
&objects.String{Value: "foo"}, &objects.String{Value: "foo"},
&objects.Bool{Value: true}, objects.TrueValue,
objects.FalseValue,
&objects.Float{Value: 93.11}, &objects.Float{Value: 93.11},
&objects.Char{Value: 'x'}, &objects.Char{Value: 'x'},
objects.UndefinedValue,
), ),
}, },
&objects.Bool{Value: false}, objects.FalseValue,
&objects.Char{Value: 'y'}, &objects.Char{Value: 'y'},
&objects.Float{Value: 93.11}, &objects.Float{Value: 93.11},
compiledFunction(1, 0, compiledFunction(1, 0,
@ -36,11 +39,12 @@ func TestBytecode(t *testing.T) {
&objects.Map{ &objects.Map{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
"a": &objects.Float{Value: -93.1}, "a": &objects.Float{Value: -93.1},
"b": &objects.Bool{Value: false}, "b": objects.FalseValue,
"c": objects.UndefinedValue,
}, },
}, },
&objects.String{Value: "bar"}, &objects.String{Value: "bar"},
&objects.Undefined{}))) objects.UndefinedValue)))
testBytecodeSerialization(t, bytecode( testBytecodeSerialization(t, bytecode(
concat( concat(

View file

@ -43,7 +43,11 @@ func FuncARB(fn func() bool) *objects.UserFunction {
return nil, objects.ErrWrongNumArguments 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 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 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) {}) uf := stdlib.FuncAIR(func(int) {})
ret, err := uf.Call(&objects.Int{Value: 10}) ret, err := uf.Call(&objects.Int{Value: 10})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Undefined{}, ret) assert.Equal(t, objects.UndefinedValue, ret)
ret, err = uf.Call() ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -22,7 +22,7 @@ func TestFuncAR(t *testing.T) {
uf := stdlib.FuncAR(func() {}) uf := stdlib.FuncAR(func() {})
ret, err := uf.Call() ret, err := uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Undefined{}, ret) assert.Equal(t, objects.UndefinedValue, ret)
ret, err = uf.Call(objects.TrueValue) ret, err = uf.Call(objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -227,7 +227,7 @@ func TestFuncAFRB(t *testing.T) {
}) })
ret, err := uf.Call(&objects.Float{Value: 0.1}) ret, err := uf.Call(&objects.Float{Value: 0.1})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Bool{Value: true}, ret) assert.Equal(t, objects.TrueValue, ret)
ret, err = uf.Call() ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
ret, err = uf.Call(objects.TrueValue, objects.TrueValue) 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}) ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Bool{Value: true}, ret) assert.Equal(t, objects.TrueValue, ret)
ret, err = uf.Call() ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
ret, err = uf.Call(objects.TrueValue) ret, err = uf.Call(objects.TrueValue)

View file

@ -6,11 +6,12 @@ import (
// Bool represents a boolean value. // Bool represents a boolean value.
type Bool struct { 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 { func (o *Bool) String() string {
if o.Value { if o.value {
return "true" return "true"
} }
@ -30,22 +31,34 @@ func (o *Bool) BinaryOp(op token.Token, rhs Object) (Object, error) {
// Copy returns a copy of the type. // Copy returns a copy of the type.
func (o *Bool) Copy() Object { func (o *Bool) Copy() Object {
v := Bool{Value: o.Value} return o
return &v
} }
// IsFalsy returns true if the value of the type is falsy. // IsFalsy returns true if the value of the type is falsy.
func (o *Bool) IsFalsy() bool { func (o *Bool) IsFalsy() bool {
return !o.Value return !o.value
} }
// Equals returns true if the value of the type // Equals returns true if the value of the type
// is equal to the value of another object. // is equal to the value of another object.
func (o *Bool) Equals(x Object) bool { func (o *Bool) Equals(x Object) bool {
t, ok := x.(*Bool) return o == x
if !ok { }
return false
// 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]) v, ok := ToBool(args[0])
if ok { if ok {
return &Bool{Value: v}, nil if v {
return TrueValue, nil
}
return FalseValue, nil
} }
return UndefinedValue, nil return UndefinedValue, nil

View file

@ -89,7 +89,7 @@ func builtinIsUndefined(args ...Object) (Object, error) {
return nil, ErrWrongNumArguments return nil, ErrWrongNumArguments
} }
if _, ok := args[0].(*Undefined); ok { if args[0] == UndefinedValue {
return TrueValue, nil return TrueValue, nil
} }

View file

@ -7,7 +7,7 @@ import (
// ToString will try to convert object o to string value. // ToString will try to convert object o to string value.
func ToString(o Object) (v string, ok bool) { func ToString(o Object) (v string, ok bool) {
if _, isUndefined := o.(*Undefined); isUndefined { if o == UndefinedValue {
//ok = false //ok = false
return return
} }
@ -36,7 +36,7 @@ func ToInt(o Object) (v int, ok bool) {
v = int(o.Value) v = int(o.Value)
ok = true ok = true
case *Bool: case *Bool:
if o.Value { if o == TrueValue {
v = 1 v = 1
} }
ok = true ok = true
@ -65,7 +65,7 @@ func ToInt64(o Object) (v int64, ok bool) {
v = int64(o.Value) v = int64(o.Value)
ok = true ok = true
case *Bool: case *Bool:
if o.Value { if o == TrueValue {
v = 1 v = 1
} }
ok = true ok = true
@ -150,7 +150,7 @@ func objectToInterface(o Object) (res interface{}) {
case *Float: case *Float:
res = o.Value res = o.Value
case *Bool: case *Bool:
res = o.Value res = o == TrueValue
case *Char: case *Char:
res = o.Value res = o.Value
case *Bytes: case *Bytes:
@ -176,7 +176,7 @@ func objectToInterface(o Object) (res interface{}) {
func FromInterface(v interface{}) (Object, error) { func FromInterface(v interface{}) (Object, error) {
switch v := v.(type) { switch v := v.(type) {
case nil: case nil:
return &Undefined{}, nil return UndefinedValue, nil
case string: case string:
return &String{Value: v}, nil return &String{Value: v}, nil
case int64: case int64:
@ -184,7 +184,10 @@ func FromInterface(v interface{}) (Object, error) {
case int: case int:
return &Int{Value: int64(v)}, nil return &Int{Value: int64(v)}, nil
case bool: case bool:
return &Bool{Value: v}, nil if v {
return TrueValue, nil
}
return FalseValue, nil
case rune: case rune:
return &Char{Value: v}, nil return &Char{Value: v}, nil
case byte: case byte:

View file

@ -41,28 +41,28 @@ func TestFloat_BinaryOp(t *testing.T) {
// float < float // float < float
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := float64(-2); r <= 2.1; r += 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 // float > float
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := float64(-2); r <= 2.1; r += 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 // float <= float
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := float64(-2); r <= 2.1; r += 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 // float >= float
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := float64(-2); r <= 2.1; r += 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 // float < int
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := int64(-2); r <= 2; r++ { 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 // float > int
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := int64(-2); r <= 2; r++ { 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 // float <= int
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := int64(-2); r <= 2; r++ { 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 // float >= int
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := int64(-2); r <= 2; r++ { 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 // int < int
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := int64(-2); r <= 2; r++ { 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 // int > int
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := int64(-2); r <= 2; r++ { 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 // int <= int
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := int64(-2); r <= 2; r++ { 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 // int >= int
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := int64(-2); r <= 2; r++ { 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 // int < float
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := float64(-2); r <= 2.1; r += 0.5 { 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 // int > float
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := float64(-2); r <= 2.1; r += 0.5 { 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 // int <= float
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := float64(-2); r <= 2.1; r += 0.5 { 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 // int >= float
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := float64(-2); r <= 2.1; r += 0.5 { 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 ( var (
// TrueValue represents a true value. // TrueValue represents a true value.
TrueValue Object = &Bool{Value: true} TrueValue Object = &Bool{value: true}
// FalseValue represents a false value. // FalseValue represents a false value.
FalseValue Object = &Bool{Value: false} FalseValue Object = &Bool{value: false}
// UndefinedValue represents an undefined value. // UndefinedValue represents an undefined value.
UndefinedValue Object = &Undefined{} 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) 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. // Copy returns a copy of the type.
func (o *Undefined) Copy() Object { func (o *Undefined) Copy() Object {
return &Undefined{} return o
} }
// IsFalsy returns true if the value of the type is falsy. // 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 // Equals returns true if the value of the type
// is equal to the value of another object. // is equal to the value of another object.
func (o *Undefined) Equals(x Object) bool { func (o *Undefined) Equals(x Object) bool {
_, ok := x.(*Undefined) return o == x
return ok
} }

View file

@ -617,23 +617,21 @@ func (v *VM) Run() error {
left := v.stack[v.sp-3] left := v.stack[v.sp-3]
v.sp -= 3 v.sp -= 3
var lowIdx, highIdx int64 var lowIdx int64
if *low != objects.UndefinedValue {
switch low := (*low).(type) { if low, ok := (*low).(*objects.Int); ok {
case *objects.Undefined: lowIdx = low.Value
//lowIdx = 0 } else {
case *objects.Int: return fmt.Errorf("non-integer slice index: %s", low.TypeName())
lowIdx = low.Value }
default:
return fmt.Errorf("non-integer slice index: %s", low.TypeName())
} }
switch high := (*high).(type) { var highIdx int64
case *objects.Undefined: if *high == objects.UndefinedValue {
highIdx = -1 // will be replaced by number of elements highIdx = -1
case *objects.Int: } else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value highIdx = high.Value
default: } else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName()) return fmt.Errorf("non-integer slice index: %s", high.TypeName())
} }

View file

@ -2,6 +2,8 @@ package runtime_test
import ( import (
"testing" "testing"
"github.com/d5/tengo/objects"
) )
func TestBuiltinFunction(t *testing.T) { func TestBuiltinFunction(t *testing.T) {
@ -24,14 +26,14 @@ func TestBuiltinFunction(t *testing.T) {
expect(t, `out = int(true)`, 1) expect(t, `out = int(true)`, 1)
expect(t, `out = int(false)`, 0) expect(t, `out = int(false)`, 0)
expect(t, `out = int('8')`, 56) expect(t, `out = int('8')`, 56)
expect(t, `out = int([1])`, undefined()) expect(t, `out = int([1])`, objects.UndefinedValue)
expect(t, `out = int({a: 1})`, undefined()) expect(t, `out = int({a: 1})`, objects.UndefinedValue)
expect(t, `out = int(undefined)`, undefined()) expect(t, `out = int(undefined)`, objects.UndefinedValue)
expect(t, `out = int("-522", 1)`, -522) expect(t, `out = int("-522", 1)`, -522)
expect(t, `out = int(undefined, 1)`, 1) expect(t, `out = int(undefined, 1)`, 1)
expect(t, `out = int(undefined, 1.8)`, 1.8) expect(t, `out = int(undefined, 1.8)`, 1.8)
expect(t, `out = int(undefined, string(1))`, "1") 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)`, "1")
expect(t, `out = string(1.8)`, "1.8") 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('8')`, "8")
expect(t, `out = string([1,8.1,true,3])`, "[1, 8.1, true, 3]") 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({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(1, "-522")`, "1")
expect(t, `out = string(undefined, "-522")`, "-522") // not "undefined" expect(t, `out = string(undefined, "-522")`, "-522") // not "undefined"
expect(t, `out = float(1)`, 1.0) expect(t, `out = float(1)`, 1.0)
expect(t, `out = float(1.8)`, 1.8) expect(t, `out = float(1.8)`, 1.8)
expect(t, `out = float("-52.2")`, -52.2) expect(t, `out = float("-52.2")`, -52.2)
expect(t, `out = float(true)`, undefined()) expect(t, `out = float(true)`, objects.UndefinedValue)
expect(t, `out = float(false)`, undefined()) expect(t, `out = float(false)`, objects.UndefinedValue)
expect(t, `out = float('8')`, undefined()) expect(t, `out = float('8')`, objects.UndefinedValue)
expect(t, `out = float([1,8.1,true,3])`, undefined()) expect(t, `out = float([1,8.1,true,3])`, objects.UndefinedValue)
expect(t, `out = float({a: 1, b: "foo"})`, undefined()) expect(t, `out = float({a: 1, b: "foo"})`, objects.UndefinedValue)
expect(t, `out = float(undefined)`, undefined()) expect(t, `out = float(undefined)`, objects.UndefinedValue)
expect(t, `out = float("-52.2", 1.8)`, -52.2) expect(t, `out = float("-52.2", 1.8)`, -52.2)
expect(t, `out = float(undefined, 1)`, 1) expect(t, `out = float(undefined, 1)`, 1)
expect(t, `out = float(undefined, 1.8)`, 1.8) expect(t, `out = float(undefined, 1.8)`, 1.8)
expect(t, `out = float(undefined, "-52.2")`, "-52.2") expect(t, `out = float(undefined, "-52.2")`, "-52.2")
expect(t, `out = float(undefined, char(56))`, '8') 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(56)`, '8')
expect(t, `out = char(1.8)`, undefined()) expect(t, `out = char(1.8)`, objects.UndefinedValue)
expect(t, `out = char("-52.2")`, undefined()) expect(t, `out = char("-52.2")`, objects.UndefinedValue)
expect(t, `out = char(true)`, undefined()) expect(t, `out = char(true)`, objects.UndefinedValue)
expect(t, `out = char(false)`, undefined()) expect(t, `out = char(false)`, objects.UndefinedValue)
expect(t, `out = char('8')`, '8') expect(t, `out = char('8')`, '8')
expect(t, `out = char([1,8.1,true,3])`, undefined()) expect(t, `out = char([1,8.1,true,3])`, objects.UndefinedValue)
expect(t, `out = char({a: 1, b: "foo"})`, undefined()) expect(t, `out = char({a: 1, b: "foo"})`, objects.UndefinedValue)
expect(t, `out = char(undefined)`, undefined()) expect(t, `out = char(undefined)`, objects.UndefinedValue)
expect(t, `out = char(56, 'a')`, '8') expect(t, `out = char(56, 'a')`, '8')
expect(t, `out = char(undefined, '8')`, '8') expect(t, `out = char(undefined, '8')`, '8')
expect(t, `out = char(undefined, 56)`, 56) expect(t, `out = char(undefined, 56)`, 56)
expect(t, `out = char(undefined, "-52.2")`, "-52.2") 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(1)`, true) // non-zero integer: true
expect(t, `out = bool(0)`, false) // zero: 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 = bool(undefined)`, false) // undefined: false
expect(t, `out = bytes(1)`, []byte{0}) 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("-522")`, []byte{'-', '5', '2', '2'})
expect(t, `out = bytes(true)`, undefined()) expect(t, `out = bytes(true)`, objects.UndefinedValue)
expect(t, `out = bytes(false)`, undefined()) expect(t, `out = bytes(false)`, objects.UndefinedValue)
expect(t, `out = bytes('8')`, undefined()) expect(t, `out = bytes('8')`, objects.UndefinedValue)
expect(t, `out = bytes([1])`, undefined()) expect(t, `out = bytes([1])`, objects.UndefinedValue)
expect(t, `out = bytes({a: 1})`, undefined()) expect(t, `out = bytes({a: 1})`, objects.UndefinedValue)
expect(t, `out = bytes(undefined)`, undefined()) expect(t, `out = bytes(undefined)`, objects.UndefinedValue)
expect(t, `out = bytes("-522", ['8'])`, []byte{'-', '5', '2', '2'}) expect(t, `out = bytes("-522", ['8'])`, []byte{'-', '5', '2', '2'})
expect(t, `out = bytes(undefined, "-522")`, "-522") expect(t, `out = bytes(undefined, "-522")`, "-522")
expect(t, `out = bytes(undefined, 1)`, 1) expect(t, `out = bytes(undefined, 1)`, 1)
expect(t, `out = bytes(undefined, 1.8)`, 1.8) expect(t, `out = bytes(undefined, 1.8)`, 1.8)
expect(t, `out = bytes(undefined, int("-522"))`, -522) 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(error(1))`, true)
expect(t, `out = is_error(1)`, false) expect(t, `out = is_error(1)`, false)

View file

@ -2,13 +2,15 @@ package runtime_test
import ( import (
"testing" "testing"
"github.com/d5/tengo/objects"
) )
func TestFunction(t *testing.T) { func TestFunction(t *testing.T) {
// function with no "return" statement returns "invalid" value. // function with no "return" statement returns "invalid" value.
expect(t, `f1 := func() {}; out = f1();`, undefined()) expect(t, `f1 := func() {}; out = f1();`, objects.UndefinedValue)
expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, undefined()) expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, objects.UndefinedValue)
expect(t, `f := func(x) { x; }; out = f(5);`, undefined()) 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; }; out = f(5);`, 5)
expect(t, `f := func(x) { return x * 2; }; out = f(5);`, 10) expect(t, `f := func(x) { return x * 2; }; out = f(5);`, 10)

View file

@ -2,16 +2,18 @@ package runtime_test
import ( import (
"testing" "testing"
"github.com/d5/tengo/objects"
) )
func TestIf(t *testing.T) { func TestIf(t *testing.T) {
expect(t, `if (true) { out = 10 }`, 10) 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 (false) { out = 10 } else { out = 20 }`, 20)
expect(t, `if (1) { out = 10 }`, 10) expect(t, `if (1) { out = 10 }`, 10)
expect(t, `if (0) { out = 10 } else { out = 20 }`, 20) 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 }`, 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 }`, 10)
expect(t, `if (1 > 2) { out = 10 } else { out = 20 }`, 20) 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"}} } 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["a"]`, "foo", SYM{"dict": dict()})
expectWithSymbols(t, `out = dict["B"]`, "bar", 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()}) expectErrorWithSymbols(t, `out = dict[0]`, SYM{"dict": dict()})
strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } 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"}} } strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr["three"]`, 2, 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[0]`, "one", SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()})
expectErrorWithSymbols(t, `out = arr[-1]`, SYM{"arr": strArr()}) expectErrorWithSymbols(t, `out = arr[-1]`, SYM{"arr": strArr()})

View file

@ -2,6 +2,8 @@ package runtime_test
import ( import (
"testing" "testing"
"github.com/d5/tengo/objects"
) )
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
@ -17,9 +19,9 @@ out = {
}) })
expect(t, `out = {foo: 5}["foo"]`, 5) 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, `key := "foo"; out = {foo: 5}[key]`, 5)
expect(t, `out = {}["foo"]`, undefined()) expect(t, `out = {}["foo"]`, objects.UndefinedValue)
expect(t, ` expect(t, `
m := { m := {

View file

@ -2,12 +2,14 @@ package runtime_test
import ( import (
"testing" "testing"
"github.com/d5/tengo/objects"
) )
func TestSelector(t *testing.T) { 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.k1`, 5)
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, "foo") 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, ` expect(t, `
a := { a := {

View file

@ -133,7 +133,10 @@ func toObject(v interface{}) objects.Object {
case int: // for convenience case int: // for convenience
return &objects.Int{Value: int64(v)} return &objects.Int{Value: int64(v)}
case bool: case bool:
return &objects.Bool{Value: v} if v {
return objects.TrueValue
}
return objects.FalseValue
case rune: case rune:
return &objects.Char{Value: v} return &objects.Char{Value: v}
case byte: // for convenience case byte: // for convenience
@ -326,7 +329,7 @@ func objectZeroCopy(o objects.Object) objects.Object {
case *objects.Map: case *objects.Map:
return &objects.Map{} return &objects.Map{}
case *objects.Undefined: case *objects.Undefined:
return &objects.Undefined{} return objects.UndefinedValue
case *objects.Error: case *objects.Error:
return &objects.Error{} return &objects.Error{}
case *objects.Bytes: case *objects.Bytes:
@ -341,7 +344,3 @@ func objectZeroCopy(o objects.Object) objects.Object {
panic(fmt.Errorf("unknown object type: %s", o.TypeName())) 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 package runtime_test
import "testing" import (
"testing"
"github.com/d5/tengo/objects"
)
func TestUndefined(t *testing.T) { 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 == undefined`, true)
expect(t, `out = undefined == 1`, false) expect(t, `out = undefined == 1`, false)
expect(t, `out = 1 == undefined`, false) expect(t, `out = 1 == undefined`, false)

View file

@ -8,8 +8,6 @@ import (
"github.com/d5/tengo/runtime" "github.com/d5/tengo/runtime"
) )
var undefined objects.Object = &objects.Undefined{}
// Compiled is a compiled instance of the user script. // Compiled is a compiled instance of the user script.
// Use Script.Compile() to create Compiled object. // Use Script.Compile() to create Compiled object.
type Compiled struct { type Compiled struct {
@ -53,20 +51,18 @@ func (c *Compiled) IsDefined(name string) bool {
return false return false
} }
_, isUndefined := (*v).(*objects.Undefined) return *v != objects.UndefinedValue
return !isUndefined
} }
// Get returns a variable identified by the name. // Get returns a variable identified by the name.
func (c *Compiled) Get(name string) *Variable { func (c *Compiled) Get(name string) *Variable {
value := &undefined value := &objects.UndefinedValue
symbol, _, ok := c.symbolTable.Resolve(name) symbol, _, ok := c.symbolTable.Resolve(name)
if ok && symbol.Scope == compiler.ScopeGlobal { if ok && symbol.Scope == compiler.ScopeGlobal {
value = c.machine.Globals()[symbol.Index] value = c.machine.Globals()[symbol.Index]
if value == nil { if value == nil {
value = &undefined value = &objects.UndefinedValue
} }
} }
@ -84,7 +80,7 @@ func (c *Compiled) GetAll() []*Variable {
if ok && symbol.Scope == compiler.ScopeGlobal { if ok && symbol.Scope == compiler.ScopeGlobal {
value := c.machine.Globals()[symbol.Index] value := c.machine.Globals()[symbol.Index]
if value == nil { if value == nil {
value = &undefined value = &objects.UndefinedValue
} }
vars = append(vars, &Variable{ vars = append(vars, &Variable{

View file

@ -15,7 +15,10 @@ func objectToInterface(o objects.Object) interface{} {
case *objects.Float: case *objects.Float:
return val.Value return val.Value
case *objects.Bool: case *objects.Bool:
return val.Value if val == objects.TrueValue {
return true
}
return false
case *objects.Char: case *objects.Char:
return val.Value return val.Value
case *objects.String: case *objects.String:

View file

@ -145,7 +145,5 @@ func (v *Variable) Object() objects.Object {
// IsUndefined returns true if the underlying value is undefined. // IsUndefined returns true if the underlying value is undefined.
func (v *Variable) IsUndefined() bool { func (v *Variable) IsUndefined() bool {
_, isUndefined := (*v.value).(*objects.Undefined) return *v.value == objects.UndefinedValue
return isUndefined
} }

View file

@ -54,7 +54,7 @@ func TestVariable(t *testing.T) {
FloatValue: 0, FloatValue: 0,
BoolValue: true, BoolValue: true,
StringValue: "true", StringValue: "true",
Object: &objects.Bool{Value: true}, Object: objects.TrueValue,
}, },
{ {
Name: "d", Name: "d",