add os File functions; add Bytes type

This commit is contained in:
Daniel Kang 2019-01-18 01:43:46 -08:00
parent 44c55ea296
commit 8cad04841e
32 changed files with 794 additions and 325 deletions

View file

@ -97,6 +97,8 @@ c5 := char("X") // 'X'
``` ```
> [Run in Playground](https://tengolang.com/?s=8d57905b82959eb244e9bbd2111e12ee04a33045) > [Run in Playground](https://tengolang.com/?s=8d57905b82959eb244e9bbd2111e12ee04a33045)
_See [Variable Types](https://github.com/d5/tengo/wiki/Variable-Types) for more details on the variable types._
You can use the dot selector (`.`) and indexer (`[]`) operator to read or write elemens of arrays, strings, or maps. You can use the dot selector (`.`) and indexer (`[]`) operator to read or write elemens of arrays, strings, or maps.
```golang ```golang
@ -115,7 +117,7 @@ m.x = 5 // add 'x' to map 'm'
``` ```
> [Run in Playground](https://tengolang.com/?s=d510c75ed8f06ef1e22c1aaf8a7d4565c793514c) > [Run in Playground](https://tengolang.com/?s=d510c75ed8f06ef1e22c1aaf8a7d4565c793514c)
For sequence types (string or array), you can use slice operator (`[:]`) too. For sequence types (string, bytes, array), you can use slice operator (`[:]`) too.
```golang ```golang
a := [1, 2, 3, 4, 5][1:3] // == [2, 3] a := [1, 2, 3, 4, 5][1:3] // == [2, 3]

View file

@ -165,6 +165,10 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
return Equal(t, expected.Value, actual.(objects.ReturnValue).Value) return Equal(t, expected.Value, actual.(objects.ReturnValue).Value)
case *objects.Array: case *objects.Array:
return equalArray(t, expected, actual.(*objects.Array)) return equalArray(t, expected, actual.(*objects.Array))
case *objects.Bytes:
if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 {
return failExpectedActual(t, expected.Value, actual.(*objects.Bytes).Value, msg...)
}
case *objects.Map: case *objects.Map:
return equalMap(t, expected, actual.(*objects.Map)) return equalMap(t, expected, actual.(*objects.Map))
case *objects.CompiledFunction: case *objects.CompiledFunction:

View file

@ -45,4 +45,11 @@ func init() {
gob.Register(&objects.Map{}) gob.Register(&objects.Map{})
gob.Register(&objects.CompiledFunction{}) gob.Register(&objects.CompiledFunction{})
gob.Register(&objects.Undefined{}) gob.Register(&objects.Undefined{})
gob.Register(&objects.Error{})
gob.Register(&objects.ImmutableMap{})
gob.Register(&objects.Bytes{})
gob.Register(&objects.StringIterator{})
gob.Register(&objects.MapIterator{})
gob.Register(&objects.ImmutableMapIterator{})
gob.Register(&objects.ArrayIterator{})
} }

View file

@ -6,7 +6,7 @@ import (
"reflect" "reflect"
"github.com/d5/tengo/compiler/ast" "github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/stdmods" "github.com/d5/tengo/compiler/stdlib"
"github.com/d5/tengo/compiler/token" "github.com/d5/tengo/compiler/token"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
@ -440,7 +440,7 @@ func (c *Compiler) Compile(node ast.Node) error {
c.emit(OpCall, len(node.Args)) c.emit(OpCall, len(node.Args))
case *ast.ImportExpr: case *ast.ImportExpr:
stdMod, ok := stdmods.Modules[node.ModuleName] stdMod, ok := stdlib.Modules[node.ModuleName]
if ok { if ok {
// standard modules contain only globals with no code. // standard modules contain only globals with no code.
// so no need to compile anything // so no need to compile anything

View file

@ -1,4 +1,4 @@
package stdmods package stdlib
import "github.com/d5/tengo/objects" import "github.com/d5/tengo/objects"

View file

@ -1,4 +1,4 @@
package stdmods package stdlib
import ( import (
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
@ -34,6 +34,20 @@ func FuncARI(fn func() int) *objects.UserFunction {
} }
} }
// FuncARE transform a function of 'func() error' signature
// into a user function object.
func FuncARE(fn func() error) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
return wrapError(fn()), nil
},
}
}
// FuncARS transform a function of 'func() string' signature // FuncARS transform a function of 'func() string' signature
// into a user function object. // into a user function object.
func FuncARS(fn func() string) *objects.UserFunction { func FuncARS(fn func() string) *objects.UserFunction {
@ -427,6 +441,30 @@ func FuncASI64RE(fn func(string, int64) error) *objects.UserFunction {
} }
} }
// FuncAIIRE transform a function of 'func(int, int) error' signature
// into a user function object.
func FuncAIIRE(fn func(int, int) error) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i2, ok := objects.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
return wrapError(fn(i1, i2)), nil
},
}
}
// FuncASIIRE transform a function of 'func(string, int, int) error' signature // FuncASIIRE transform a function of 'func(string, int, int) error' signature
// into a user function object. // into a user function object.
func FuncASIIRE(fn func(string, int, int) error) *objects.UserFunction { func FuncASIIRE(fn func(string, int, int) error) *objects.UserFunction {
@ -455,3 +493,80 @@ func FuncASIIRE(fn func(string, int, int) error) *objects.UserFunction {
}, },
} }
} }
// FuncAYRIE transform a function of 'func([]byte) (int, error)' signature
// into a user function object.
func FuncAYRIE(fn func([]byte) (int, error)) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
y1, ok := objects.ToByteSlice(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := fn(y1)
if err != nil {
return wrapError(err), nil
}
return &objects.Int{Value: int64(res)}, nil
},
}
}
// FuncASRIE transform a function of 'func(string) (int, error)' signature
// into a user function object.
func FuncASRIE(fn func(string) (int, error)) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := fn(s1)
if err != nil {
return wrapError(err), nil
}
return &objects.Int{Value: int64(res)}, nil
},
}
}
// FuncAIRSsE transform a function of 'func(int) ([]string, error)' signature
// into a user function object.
func FuncAIRSsE(fn func(int) ([]string, error)) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := fn(i1)
if err != nil {
return wrapError(err), nil
}
arr := &objects.Array{}
for _, osArg := range res {
arr.Value = append(arr.Value, &objects.String{Value: osArg})
}
return arr, nil
},
}
}

View file

@ -1,16 +1,16 @@
package stdmods_test package stdlib_test
import ( import (
"errors" "errors"
"testing" "testing"
"github.com/d5/tengo/assert" "github.com/d5/tengo/assert"
"github.com/d5/tengo/compiler/stdmods" "github.com/d5/tengo/compiler/stdlib"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
func TestFuncAIR(t *testing.T) { func TestFuncAIR(t *testing.T) {
uf := stdmods.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.Undefined{}, ret)
@ -19,7 +19,7 @@ func TestFuncAIR(t *testing.T) {
} }
func TestFuncAR(t *testing.T) { func TestFuncAR(t *testing.T) {
uf := stdmods.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.Undefined{}, ret)
@ -28,7 +28,7 @@ func TestFuncAR(t *testing.T) {
} }
func TestFuncARI(t *testing.T) { func TestFuncARI(t *testing.T) {
uf := stdmods.FuncARI(func() int { return 10 }) uf := stdlib.FuncARI(func() int { return 10 })
ret, err := uf.Call() ret, err := uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 10}, ret) assert.Equal(t, &objects.Int{Value: 10}, ret)
@ -36,12 +36,25 @@ func TestFuncARI(t *testing.T) {
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARE(t *testing.T) {
uf := stdlib.FuncARE(func() error { return nil })
ret, err := uf.Call()
assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret)
uf = stdlib.FuncARE(func() error { return errors.New("some error") })
ret, err = uf.Call()
assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
ret, err = uf.Call(objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncARIsE(t *testing.T) { func TestFuncARIsE(t *testing.T) {
uf := stdmods.FuncARIsE(func() ([]int, error) { return []int{1, 2, 3}, nil }) uf := stdlib.FuncARIsE(func() ([]int, error) { return []int{1, 2, 3}, nil })
ret, err := uf.Call() ret, err := uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, array(&objects.Int{Value: 1}, &objects.Int{Value: 2}, &objects.Int{Value: 3}), ret) assert.Equal(t, array(&objects.Int{Value: 1}, &objects.Int{Value: 2}, &objects.Int{Value: 3}), ret)
uf = stdmods.FuncARIsE(func() ([]int, error) { return nil, errors.New("some error") }) uf = stdlib.FuncARIsE(func() ([]int, error) { return nil, errors.New("some error") })
ret, err = uf.Call() ret, err = uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
@ -50,7 +63,7 @@ func TestFuncARIsE(t *testing.T) {
} }
func TestFuncARS(t *testing.T) { func TestFuncARS(t *testing.T) {
uf := stdmods.FuncARS(func() string { return "foo" }) uf := stdlib.FuncARS(func() string { return "foo" })
ret, err := uf.Call() ret, err := uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo"}, ret) assert.Equal(t, &objects.String{Value: "foo"}, ret)
@ -59,11 +72,11 @@ func TestFuncARS(t *testing.T) {
} }
func TestFuncARSE(t *testing.T) { func TestFuncARSE(t *testing.T) {
uf := stdmods.FuncARSE(func() (string, error) { return "foo", nil }) uf := stdlib.FuncARSE(func() (string, error) { return "foo", nil })
ret, err := uf.Call() ret, err := uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo"}, ret) assert.Equal(t, &objects.String{Value: "foo"}, ret)
uf = stdmods.FuncARSE(func() (string, error) { return "", errors.New("some error") }) uf = stdlib.FuncARSE(func() (string, error) { return "", errors.New("some error") })
ret, err = uf.Call() ret, err = uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
@ -72,7 +85,7 @@ func TestFuncARSE(t *testing.T) {
} }
func TestFuncARSs(t *testing.T) { func TestFuncARSs(t *testing.T) {
uf := stdmods.FuncARSs(func() []string { return []string{"foo", "bar"} }) uf := stdlib.FuncARSs(func() []string { return []string{"foo", "bar"} })
ret, err := uf.Call() ret, err := uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret)
@ -81,11 +94,11 @@ func TestFuncARSs(t *testing.T) {
} }
func TestFuncASRE(t *testing.T) { func TestFuncASRE(t *testing.T) {
uf := stdmods.FuncASRE(func(a string) error { return nil }) uf := stdlib.FuncASRE(func(a string) error { return nil })
ret, err := uf.Call(&objects.String{Value: "foo"}) ret, err := uf.Call(&objects.String{Value: "foo"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
uf = stdmods.FuncASRE(func(a string) error { return errors.New("some error") }) uf = stdlib.FuncASRE(func(a string) error { return errors.New("some error") })
ret, err = uf.Call(&objects.String{Value: "foo"}) ret, err = uf.Call(&objects.String{Value: "foo"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
@ -94,7 +107,7 @@ func TestFuncASRE(t *testing.T) {
} }
func TestFuncASRS(t *testing.T) { func TestFuncASRS(t *testing.T) {
uf := stdmods.FuncASRS(func(a string) string { return a }) uf := stdlib.FuncASRS(func(a string) string { return a })
ret, err := uf.Call(&objects.String{Value: "foo"}) ret, err := uf.Call(&objects.String{Value: "foo"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo"}, ret) assert.Equal(t, &objects.String{Value: "foo"}, ret)
@ -103,11 +116,11 @@ func TestFuncASRS(t *testing.T) {
} }
func TestFuncASI64RE(t *testing.T) { func TestFuncASI64RE(t *testing.T) {
uf := stdmods.FuncASI64RE(func(a string, b int64) error { return nil }) uf := stdlib.FuncASI64RE(func(a string, b int64) error { return nil })
ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}) ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
uf = stdmods.FuncASI64RE(func(a string, b int64) error { return errors.New("some error") }) uf = stdlib.FuncASI64RE(func(a string, b int64) error { return errors.New("some error") })
ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}) ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
@ -115,12 +128,25 @@ func TestFuncASI64RE(t *testing.T) {
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAIIRE(t *testing.T) {
uf := stdlib.FuncAIIRE(func(a, b int) error { return nil })
ret, err := uf.Call(&objects.Int{Value: 5}, &objects.Int{Value: 7})
assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret)
uf = stdlib.FuncAIIRE(func(a, b int) error { return errors.New("some error") })
ret, err = uf.Call(&objects.Int{Value: 5}, &objects.Int{Value: 7})
assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncASIIRE(t *testing.T) { func TestFuncASIIRE(t *testing.T) {
uf := stdmods.FuncASIIRE(func(a string, b, c int) error { return nil }) uf := stdlib.FuncASIIRE(func(a string, b, c int) error { return nil })
ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7}) ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
uf = stdmods.FuncASIIRE(func(a string, b, c int) error { return errors.New("some error") }) uf = stdlib.FuncASIIRE(func(a string, b, c int) error { return errors.New("some error") })
ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7}) ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
@ -129,11 +155,11 @@ func TestFuncASIIRE(t *testing.T) {
} }
func TestFuncASRSE(t *testing.T) { func TestFuncASRSE(t *testing.T) {
uf := stdmods.FuncASRSE(func(a string) (string, error) { return a, nil }) uf := stdlib.FuncASRSE(func(a string) (string, error) { return a, nil })
ret, err := uf.Call(&objects.String{Value: "foo"}) ret, err := uf.Call(&objects.String{Value: "foo"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo"}, ret) assert.Equal(t, &objects.String{Value: "foo"}, ret)
uf = stdmods.FuncASRSE(func(a string) (string, error) { return a, errors.New("some error") }) uf = stdlib.FuncASRSE(func(a string) (string, error) { return a, errors.New("some error") })
ret, err = uf.Call(&objects.String{Value: "foo"}) ret, err = uf.Call(&objects.String{Value: "foo"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
@ -146,7 +172,7 @@ func TestFuncASSRE(t *testing.T) {
} }
func TestFuncARF(t *testing.T) { func TestFuncARF(t *testing.T) {
uf := stdmods.FuncARF(func() float64 { uf := stdlib.FuncARF(func() float64 {
return 10.0 return 10.0
}) })
ret, err := uf.Call() ret, err := uf.Call()
@ -157,7 +183,7 @@ func TestFuncARF(t *testing.T) {
} }
func TestFuncAFRF(t *testing.T) { func TestFuncAFRF(t *testing.T) {
uf := stdmods.FuncAFRF(func(a float64) float64 { uf := stdlib.FuncAFRF(func(a float64) float64 {
return a return a
}) })
ret, err := uf.Call(&objects.Float{Value: 10.0}) ret, err := uf.Call(&objects.Float{Value: 10.0})
@ -170,7 +196,7 @@ func TestFuncAFRF(t *testing.T) {
} }
func TestFuncAIRF(t *testing.T) { func TestFuncAIRF(t *testing.T) {
uf := stdmods.FuncAIRF(func(a int) float64 { uf := stdlib.FuncAIRF(func(a int) float64 {
return float64(a) return float64(a)
}) })
ret, err := uf.Call(&objects.Int{Value: 10.0}) ret, err := uf.Call(&objects.Int{Value: 10.0})
@ -183,7 +209,7 @@ func TestFuncAIRF(t *testing.T) {
} }
func TestFuncAFRI(t *testing.T) { func TestFuncAFRI(t *testing.T) {
uf := stdmods.FuncAFRI(func(a float64) int { uf := stdlib.FuncAFRI(func(a float64) int {
return int(a) return int(a)
}) })
ret, err := uf.Call(&objects.Float{Value: 10.5}) ret, err := uf.Call(&objects.Float{Value: 10.5})
@ -196,7 +222,7 @@ func TestFuncAFRI(t *testing.T) {
} }
func TestFuncAFRB(t *testing.T) { func TestFuncAFRB(t *testing.T) {
uf := stdmods.FuncAFRB(func(a float64) bool { uf := stdlib.FuncAFRB(func(a float64) bool {
return a > 0.0 return a > 0.0
}) })
ret, err := uf.Call(&objects.Float{Value: 0.1}) ret, err := uf.Call(&objects.Float{Value: 0.1})
@ -209,7 +235,7 @@ func TestFuncAFRB(t *testing.T) {
} }
func TestFuncAFFRF(t *testing.T) { func TestFuncAFFRF(t *testing.T) {
uf := stdmods.FuncAFFRF(func(a, b float64) float64 { uf := stdlib.FuncAFFRF(func(a, b float64) float64 {
return a + b return a + b
}) })
ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Float{Value: 20.0}) ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Float{Value: 20.0})
@ -222,7 +248,7 @@ func TestFuncAFFRF(t *testing.T) {
} }
func TestFuncAIFRF(t *testing.T) { func TestFuncAIFRF(t *testing.T) {
uf := stdmods.FuncAIFRF(func(a int, b float64) float64 { uf := stdlib.FuncAIFRF(func(a int, b float64) float64 {
return float64(a) + b return float64(a) + b
}) })
ret, err := uf.Call(&objects.Int{Value: 10}, &objects.Float{Value: 20.0}) ret, err := uf.Call(&objects.Int{Value: 10}, &objects.Float{Value: 20.0})
@ -235,7 +261,7 @@ func TestFuncAIFRF(t *testing.T) {
} }
func TestFuncAFIRF(t *testing.T) { func TestFuncAFIRF(t *testing.T) {
uf := stdmods.FuncAFIRF(func(a float64, b int) float64 { uf := stdlib.FuncAFIRF(func(a float64, b int) float64 {
return a + float64(b) return a + float64(b)
}) })
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})
@ -248,7 +274,7 @@ func TestFuncAFIRF(t *testing.T) {
} }
func TestFuncAFIRB(t *testing.T) { func TestFuncAFIRB(t *testing.T) {
uf := stdmods.FuncAFIRB(func(a float64, b int) bool { uf := stdlib.FuncAFIRB(func(a float64, b int) bool {
return a < float64(b) return a < float64(b)
}) })
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})

View file

@ -1,4 +1,4 @@
package stdmods package stdlib
import ( import (
"math" "math"

154
compiler/stdlib/os.go Normal file
View file

@ -0,0 +1,154 @@
package stdlib
import (
"os"
"github.com/d5/tengo/objects"
)
var osModule = map[string]objects.Object{
"o_rdonly": &objects.Int{Value: int64(os.O_RDONLY)},
"o_wronly": &objects.Int{Value: int64(os.O_WRONLY)},
"o_rdwr": &objects.Int{Value: int64(os.O_RDWR)},
"o_append": &objects.Int{Value: int64(os.O_APPEND)},
"o_create": &objects.Int{Value: int64(os.O_CREATE)},
"o_excl": &objects.Int{Value: int64(os.O_EXCL)},
"o_sync": &objects.Int{Value: int64(os.O_SYNC)},
"o_trunc": &objects.Int{Value: int64(os.O_TRUNC)},
"mode_dir": &objects.Int{Value: int64(os.ModeDir)},
"mode_append": &objects.Int{Value: int64(os.ModeAppend)},
"mode_exclusive": &objects.Int{Value: int64(os.ModeExclusive)},
"mode_temporary": &objects.Int{Value: int64(os.ModeTemporary)},
"mode_symlink": &objects.Int{Value: int64(os.ModeSymlink)},
"mode_device": &objects.Int{Value: int64(os.ModeDevice)},
"mode_named_pipe": &objects.Int{Value: int64(os.ModeNamedPipe)},
"mode_socket": &objects.Int{Value: int64(os.ModeSocket)},
"mode_setuid": &objects.Int{Value: int64(os.ModeSetuid)},
"mode_setgui": &objects.Int{Value: int64(os.ModeSetgid)},
"mode_char_device": &objects.Int{Value: int64(os.ModeCharDevice)},
"mode_sticky": &objects.Int{Value: int64(os.ModeSticky)},
"mode_irregular": &objects.Int{Value: int64(os.ModeIrregular)},
"mode_type": &objects.Int{Value: int64(os.ModeType)},
"mode_perm": &objects.Int{Value: int64(os.ModePerm)},
"path_separator": &objects.Char{Value: os.PathSeparator},
"path_list_separator": &objects.Char{Value: os.PathListSeparator},
"dev_null": &objects.String{Value: os.DevNull},
"args": &objects.UserFunction{Value: osArgs},
"chdir": FuncASRE(os.Chdir),
"chmod": osFuncASFmRE(os.Chmod),
"chown": FuncASIIRE(os.Chown),
"clearenv": FuncAR(os.Clearenv),
"environ": FuncARSs(os.Environ),
"executable": &objects.UserFunction{Value: osExecutable},
"exit": FuncAIR(os.Exit),
"expand_env": FuncASRS(os.ExpandEnv),
"getegid": FuncARI(os.Getegid),
"getenv": FuncASRS(os.Getenv),
"geteuid": FuncARI(os.Geteuid),
"getgid": FuncARI(os.Getgid),
"getgroups": FuncARIsE(os.Getgroups),
"getpagesize": FuncARI(os.Getpagesize),
"getpid": FuncARI(os.Getpid),
"getppid": FuncARI(os.Getppid),
"getuid": FuncARI(os.Getuid),
"getwd": FuncARSE(os.Getwd),
"hostname": FuncARSE(os.Hostname),
"lchown": FuncASIIRE(os.Lchown),
"link": FuncASSRE(os.Link),
"lookup_env": &objects.UserFunction{Value: osLookupEnv},
"mkdir": osFuncASFmRE(os.Mkdir),
"mkdir_all": osFuncASFmRE(os.MkdirAll),
"readlink": FuncASRSE(os.Readlink),
"remove": FuncASRE(os.Remove),
"remove_all": FuncASRE(os.RemoveAll),
"rename": FuncASSRE(os.Rename),
"setenv": FuncASSRE(os.Setenv),
"symlink": FuncASSRE(os.Symlink),
"temp_dir": FuncARS(os.TempDir),
"truncate": FuncASI64RE(os.Truncate),
"unsetenv": FuncASRE(os.Unsetenv),
"user_cache_dir": FuncARSE(os.UserCacheDir),
"create": &objects.UserFunction{Value: osCreate},
"open": &objects.UserFunction{Value: osOpen},
"open_file": &objects.UserFunction{Value: osOpenFile},
// TODO: not implemented yet
//"stdin": nil,
//"stdout": nil,
//"stderr": nil,
//"chtimes": nil,
//"expand": nil,
//"is_exists": nil,
//"is_not_exist": nil,
//"is_path_separator": nil,
//"is_permission": nil,
//"is_timeout": nil,
//"new_syscall_error": nil,
//"pipe": nil,
//"same_file": nil,
}
func osArgs(args ...objects.Object) (objects.Object, error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
arr := &objects.Array{}
for _, osArg := range os.Args {
arr.Value = append(arr.Value, &objects.String{Value: osArg})
}
return arr, nil
}
func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (objects.Object, error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i2, ok := objects.ToInt64(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
return wrapError(fn(s1, os.FileMode(i2))), nil
},
}
}
func osExecutable(args ...objects.Object) (objects.Object, error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
res, err := os.Executable()
if err != nil {
return wrapError(err), nil
}
return &objects.String{Value: res}, nil
}
func osLookupEnv(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, ok := os.LookupEnv(s1)
if !ok {
return objects.FalseValue, nil
}
return &objects.String{Value: res}, nil
}

141
compiler/stdlib/os_file.go Normal file
View file

@ -0,0 +1,141 @@
package stdlib
import (
"os"
"github.com/d5/tengo/objects"
)
func osFileImmutableMap(file *os.File) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// chdir() => true/error
"chdir": FuncARE(file.Chdir),
// chown(uid int, gid int) => true/error
"chown": FuncAIIRE(file.Chown),
// close() => error
"close": FuncARE(file.Close),
// name() => string
"name": FuncARS(file.Name),
// readdirnames(n int) => array/error
"readdirnames": FuncAIRSsE(file.Readdirnames),
// sync() => error
"sync": FuncARE(file.Sync),
// write(bytes) => int/error
"write": FuncAYRIE(file.Write),
// write(string) => int/error
"write_string": FuncASRIE(file.WriteString),
// read(bytes) => int/error
"read": FuncAYRIE(file.Read),
"chmod": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt64(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
return wrapError(file.Chmod(os.FileMode(i1))), nil
},
},
"seek": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
i1, ok := objects.ToInt64(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i2, ok := objects.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := file.Seek(i1, i2)
if err != nil {
return wrapError(err), nil
}
return &objects.Int{Value: res}, nil
},
},
//"fd": nil,
//"read_at": nil,
//"readdir": nil,
//"set_deadline": nil,
//"set_read_deadline": nil,
//"set_write_deadline": nil,
//"stat": nil,
//"write_at": nil,
},
}
}
func osCreate(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := os.Create(s1)
if err != nil {
return wrapError(err), nil
}
return osFileImmutableMap(res), nil
}
func osOpen(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := os.Open(s1)
if err != nil {
return wrapError(err), nil
}
return osFileImmutableMap(res), nil
}
func osOpenFile(args ...objects.Object) (objects.Object, error) {
if len(args) != 3 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i2, ok := objects.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i3, ok := objects.ToInt(args[2])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := os.OpenFile(s1, i2, os.FileMode(i3))
if err != nil {
return wrapError(err), nil
}
return osFileImmutableMap(res), nil
}

View file

@ -1,9 +1,9 @@
package stdmods package stdlib
import "github.com/d5/tengo/objects" import "github.com/d5/tengo/objects"
// Modules contain the standard modules. // Modules contain the standard modules.
var Modules = map[string]*objects.ModuleMap{ var Modules = map[string]*objects.ImmutableMap{
"math": {Value: mathModule}, "math": {Value: mathModule},
"os": {Value: osModule}, "os": {Value: osModule},
} }

View file

@ -1,154 +0,0 @@
package stdmods
import (
"os"
"github.com/d5/tengo/objects"
)
var osModule = map[string]objects.Object{
"o_rdonly": &objects.Int{Value: int64(os.O_RDONLY)},
"o_wronly": &objects.Int{Value: int64(os.O_WRONLY)},
"o_rdwr": &objects.Int{Value: int64(os.O_RDWR)},
"o_append": &objects.Int{Value: int64(os.O_APPEND)},
"o_create": &objects.Int{Value: int64(os.O_CREATE)},
"o_excl": &objects.Int{Value: int64(os.O_EXCL)},
"o_sync": &objects.Int{Value: int64(os.O_SYNC)},
"o_trunc": &objects.Int{Value: int64(os.O_TRUNC)},
"mode_dir": &objects.Int{Value: int64(os.ModeDir)},
"mode_append": &objects.Int{Value: int64(os.ModeAppend)},
"mode_exclusive": &objects.Int{Value: int64(os.ModeExclusive)},
"mode_temporary": &objects.Int{Value: int64(os.ModeTemporary)},
"mode_symlink": &objects.Int{Value: int64(os.ModeSymlink)},
"mode_device": &objects.Int{Value: int64(os.ModeDevice)},
"mode_named_pipe": &objects.Int{Value: int64(os.ModeNamedPipe)},
"mode_socket": &objects.Int{Value: int64(os.ModeSocket)},
"mode_setuid": &objects.Int{Value: int64(os.ModeSetuid)},
"mode_setgui": &objects.Int{Value: int64(os.ModeSetgid)},
"mode_char_device": &objects.Int{Value: int64(os.ModeCharDevice)},
"mode_sticky": &objects.Int{Value: int64(os.ModeSticky)},
"mode_irregular": &objects.Int{Value: int64(os.ModeIrregular)},
"mode_type": &objects.Int{Value: int64(os.ModeType)},
"mode_perm": &objects.Int{Value: int64(os.ModePerm)},
"path_separator": &objects.Char{Value: os.PathSeparator},
"path_list_separator": &objects.Char{Value: os.PathListSeparator},
"dev_null": &objects.String{Value: os.DevNull},
"args": &objects.UserFunction{Value: osArgs},
"chdir": FuncASRE(os.Chdir),
"chmod": osFuncASFmRE(os.Chmod),
"chown": FuncASIIRE(os.Chown),
"clearenv": FuncAR(os.Clearenv),
"environ": FuncARSs(os.Environ),
"executable": &objects.UserFunction{Value: osExecutable},
"exit": FuncAIR(os.Exit),
"expand_env": FuncASRS(os.ExpandEnv),
"getegid": FuncARI(os.Getegid),
"getenv": FuncASRS(os.Getenv),
"geteuid": FuncARI(os.Geteuid),
"getgid": FuncARI(os.Getgid),
"getgroups": FuncARIsE(os.Getgroups),
"getpagesize": FuncARI(os.Getpagesize),
"getpid": FuncARI(os.Getpid),
"getppid": FuncARI(os.Getppid),
"getuid": FuncARI(os.Getuid),
"getwd": FuncARSE(os.Getwd),
"hostname": FuncARSE(os.Hostname),
"lchown": FuncASIIRE(os.Lchown),
"link": FuncASSRE(os.Link),
"lookup_env": &objects.UserFunction{Value: osLookupEnv},
"mkdir": osFuncASFmRE(os.Mkdir),
"mkdir_all": osFuncASFmRE(os.MkdirAll),
"readlink": FuncASRSE(os.Readlink),
"remove": FuncASRE(os.Remove),
"remove_all": FuncASRE(os.RemoveAll),
"rename": FuncASSRE(os.Rename),
"setenv": FuncASSRE(os.Setenv),
"symlink": FuncASSRE(os.Symlink),
"temp_dir": FuncARS(os.TempDir),
"truncate": FuncASI64RE(os.Truncate),
"unsetenv": FuncASRE(os.Unsetenv),
"user_cache_dir": FuncARSE(os.UserCacheDir),
// TODO: not implemented yet
//"stdin": nil,
//"stdout": nil,
//"stderr": nil,
//"chtimes": nil,
//"expand": nil,
//"is_exists": nil,
//"is_not_exist": nil,
//"is_path_separator": nil,
//"is_permission": nil,
//"is_timeout": nil,
//"new_syscall_error": nil,
//"pipe": nil,
//"same_file": nil,
}
func osArgs(args ...objects.Object) (objects.Object, error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
arr := &objects.Array{}
for _, osArg := range os.Args {
arr.Value = append(arr.Value, &objects.String{Value: osArg})
}
return arr, nil
}
func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (objects.Object, error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i2, ok := objects.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
return wrapError(fn(s1, os.FileMode(i2))), nil
},
}
}
func osExecutable(args ...objects.Object) (objects.Object, error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
res, err := os.Executable()
if err != nil {
return wrapError(err), nil
}
return &objects.String{Value: res}, nil
}
func osLookupEnv(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, ok := os.LookupEnv(s1)
if !ok {
return objects.FalseValue, nil
}
return &objects.String{Value: res}, nil
}

View file

@ -30,7 +30,7 @@ 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(*o) v := Bool{Value: o.Value}
return &v return &v
} }

View file

@ -84,3 +84,21 @@ func builtinChar(args ...Object) (Object, error) {
return UndefinedValue, nil return UndefinedValue, nil
} }
func builtinBytes(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
// bytes(N) => create a new bytes with given size N
if n, ok := args[0].(*Int); ok {
return &Bytes{Value: make([]byte, int(n.Value))}, nil
}
v, ok := ToByteSlice(args[0])
if ok {
return &Bytes{Value: v}, nil
}
return UndefinedValue, nil
}

View file

@ -14,9 +14,11 @@ func builtinLen(args ...Object) (Object, error) {
return &Int{Value: int64(len(arg.Value))}, nil return &Int{Value: int64(len(arg.Value))}, nil
case *String: case *String:
return &Int{Value: int64(len(arg.Value))}, nil return &Int{Value: int64(len(arg.Value))}, nil
case *Bytes:
return &Int{Value: int64(len(arg.Value))}, nil
case *Map: case *Map:
return &Int{Value: int64(len(arg.Value))}, nil return &Int{Value: int64(len(arg.Value))}, nil
case *ModuleMap: case *ImmutableMap:
return &Int{Value: int64(len(arg.Value))}, nil return &Int{Value: int64(len(arg.Value))}, nil
default: default:
return nil, fmt.Errorf("unsupported type for 'len' function: %s", arg.TypeName()) return nil, fmt.Errorf("unsupported type for 'len' function: %s", arg.TypeName())

View file

@ -6,7 +6,11 @@ import (
func builtinPrint(args ...Object) (Object, error) { func builtinPrint(args ...Object) (Object, error) {
for _, arg := range args { for _, arg := range args {
fmt.Println(arg.String()) if str, ok := arg.(*String); ok {
fmt.Println(str.Value)
} else {
fmt.Println(arg.String())
}
} }
return nil, nil return nil, nil

View file

@ -60,6 +60,18 @@ func builtinIsChar(args ...Object) (Object, error) {
return FalseValue, nil return FalseValue, nil
} }
func builtinIsBytes(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Bytes); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsError(args ...Object) (Object, error) { func builtinIsError(args ...Object) (Object, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, ErrWrongNumArguments return nil, ErrWrongNumArguments

View file

@ -41,6 +41,10 @@ var Builtins = []struct {
Name: "char", Name: "char",
Func: builtinChar, Func: builtinChar,
}, },
{
Name: "bytes",
Func: builtinBytes,
},
{ {
Name: "is_int", Name: "is_int",
Func: builtinIsInt, Func: builtinIsInt,
@ -61,6 +65,10 @@ var Builtins = []struct {
Name: "is_char", Name: "is_char",
Func: builtinIsChar, Func: builtinIsChar,
}, },
{
Name: "is_bytes",
Func: builtinIsBytes,
},
{ {
Name: "is_error", Name: "is_error",
Func: builtinIsError, Func: builtinIsError,

56
objects/bytes.go Normal file
View file

@ -0,0 +1,56 @@
package objects
import (
"bytes"
"github.com/d5/tengo/compiler/token"
)
// Bytes represents a byte array.
type Bytes struct {
Value []byte
}
func (o *Bytes) String() string {
return string(o.Value)
}
// TypeName returns the name of the type.
func (o *Bytes) TypeName() string {
return "bytes"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) {
switch op {
case token.Add:
switch rhs := rhs.(type) {
case *Bytes:
return &Bytes{Value: append(o.Value, rhs.Value...)}, nil
}
}
return nil, ErrInvalidOperator
}
// Copy returns a copy of the type.
func (o *Bytes) Copy() Object {
return &Bytes{Value: append([]byte{}, o.Value...)}
}
// IsFalsy returns true if the value of the type is falsy.
func (o *Bytes) IsFalsy() bool {
return len(o.Value) == 0
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (o *Bytes) Equals(x Object) bool {
t, ok := x.(*Bytes)
if !ok {
return false
}
return bytes.Compare(o.Value, t.Value) == 0
}

View file

@ -118,13 +118,21 @@ func ToRune(o Object) (v rune, ok bool) {
case *Char: case *Char:
v = rune(o.Value) v = rune(o.Value)
ok = true ok = true
case *String: }
rs := []rune(o.Value)
switch len(rs) { //ok = false
case 1: return
v = rs[0] }
ok = true
} // ToByteSlice will try to convert object o to []byte value.
func ToByteSlice(o Object) (v []byte, ok bool) {
switch o := o.(type) {
case *Bytes:
v = o.Value
ok = true
case *String:
v = []byte(o.Value)
ok = true
} }
//ok = false //ok = false

View file

@ -2,6 +2,12 @@ package objects
import "errors" import "errors"
// ErrNotCallable represents an error for calling on non-function objects.
var ErrNotCallable = errors.New("not a callable object")
// ErrNotIndexable represents an error for indexing on non-indexable objects.
var ErrNotIndexable = errors.New("non-indexable object")
// ErrInvalidOperator represents an error for invalid operator usage. // ErrInvalidOperator represents an error for invalid operator usage.
var ErrInvalidOperator = errors.New("invalid operator") var ErrInvalidOperator = errors.New("invalid operator")

View file

@ -7,17 +7,17 @@ import (
"github.com/d5/tengo/compiler/token" "github.com/d5/tengo/compiler/token"
) )
// ModuleMap represents a module map object. // ImmutableMap represents a module map object.
type ModuleMap struct { type ImmutableMap struct {
Value map[string]Object Value map[string]Object
} }
// TypeName returns the name of the type. // TypeName returns the name of the type.
func (o *ModuleMap) TypeName() string { func (o *ImmutableMap) TypeName() string {
return "module" return "immutable-map"
} }
func (o *ModuleMap) String() string { func (o *ImmutableMap) String() string {
var pairs []string var pairs []string
for k, v := range o.Value { for k, v := range o.Value {
pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String()))
@ -28,27 +28,27 @@ func (o *ModuleMap) String() string {
// BinaryOp returns another object that is the result of // BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object. // a given binary operator and a right-hand side object.
func (o *ModuleMap) BinaryOp(op token.Token, rhs Object) (Object, error) { func (o *ImmutableMap) BinaryOp(op token.Token, rhs Object) (Object, error) {
return nil, ErrInvalidOperator return nil, ErrInvalidOperator
} }
// Copy returns a copy of the type. // Copy returns a copy of the type.
func (o *ModuleMap) Copy() Object { func (o *ImmutableMap) Copy() Object {
c := make(map[string]Object) c := make(map[string]Object)
for k, v := range o.Value { for k, v := range o.Value {
c[k] = v.Copy() c[k] = v.Copy()
} }
return &ModuleMap{Value: c} return &ImmutableMap{Value: c}
} }
// IsFalsy returns true if the value of the type is falsy. // IsFalsy returns true if the value of the type is falsy.
func (o *ModuleMap) IsFalsy() bool { func (o *ImmutableMap) IsFalsy() bool {
return len(o.Value) == 0 return len(o.Value) == 0
} }
// Get returns the value for the given key. // Get returns the value for the given key.
func (o *ModuleMap) Get(key string) (Object, bool) { func (o *ImmutableMap) Get(key string) (Object, bool) {
val, ok := o.Value[key] val, ok := o.Value[key]
return val, ok return val, ok
@ -56,8 +56,8 @@ func (o *ModuleMap) Get(key string) (Object, 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 *ModuleMap) Equals(x Object) bool { func (o *ImmutableMap) Equals(x Object) bool {
t, ok := x.(*ModuleMap) t, ok := x.(*ImmutableMap)
if !ok { if !ok {
return false return false
} }

View file

@ -2,8 +2,8 @@ package objects
import "github.com/d5/tengo/compiler/token" import "github.com/d5/tengo/compiler/token"
// ModuleMapIterator represents an iterator for the module map. // ImmutableMapIterator represents an iterator for the immutable map.
type ModuleMapIterator struct { type ImmutableMapIterator struct {
v map[string]Object v map[string]Object
k []string k []string
i int i int
@ -11,13 +11,13 @@ type ModuleMapIterator struct {
} }
// NewModuleMapIterator creates a module iterator. // NewModuleMapIterator creates a module iterator.
func NewModuleMapIterator(v *ModuleMap) Iterator { func NewModuleMapIterator(v *ImmutableMap) Iterator {
var keys []string var keys []string
for k := range v.Value { for k := range v.Value {
keys = append(keys, k) keys = append(keys, k)
} }
return &ModuleMapIterator{ return &ImmutableMapIterator{
v: v.Value, v: v.Value,
k: keys, k: keys,
l: len(keys), l: len(keys),
@ -25,51 +25,51 @@ func NewModuleMapIterator(v *ModuleMap) Iterator {
} }
// TypeName returns the name of the type. // TypeName returns the name of the type.
func (i *ModuleMapIterator) TypeName() string { func (i *ImmutableMapIterator) TypeName() string {
return "module-iterator" return "module-iterator"
} }
func (i *ModuleMapIterator) String() string { func (i *ImmutableMapIterator) String() string {
return "<module-iterator>" return "<module-iterator>"
} }
// BinaryOp returns another object that is the result of // BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object. // a given binary operator and a right-hand side object.
func (i *ModuleMapIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { func (i *ImmutableMapIterator) BinaryOp(op token.Token, rhs Object) (Object, error) {
return nil, ErrInvalidOperator return nil, ErrInvalidOperator
} }
// IsFalsy returns true if the value of the type is falsy. // IsFalsy returns true if the value of the type is falsy.
func (i *ModuleMapIterator) IsFalsy() bool { func (i *ImmutableMapIterator) IsFalsy() bool {
return true return true
} }
// 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 (i *ModuleMapIterator) Equals(Object) bool { func (i *ImmutableMapIterator) Equals(Object) bool {
return false return false
} }
// Copy returns a copy of the type. // Copy returns a copy of the type.
func (i *ModuleMapIterator) Copy() Object { func (i *ImmutableMapIterator) Copy() Object {
return &ModuleMapIterator{v: i.v, k: i.k, i: i.i, l: i.l} return &ImmutableMapIterator{v: i.v, k: i.k, i: i.i, l: i.l}
} }
// Next returns true if there are more elements to iterate. // Next returns true if there are more elements to iterate.
func (i *ModuleMapIterator) Next() bool { func (i *ImmutableMapIterator) Next() bool {
i.i++ i.i++
return i.i <= i.l return i.i <= i.l
} }
// Key returns the key or index value of the current element. // Key returns the key or index value of the current element.
func (i *ModuleMapIterator) Key() Object { func (i *ImmutableMapIterator) Key() Object {
k := i.k[i.i-1] k := i.k[i.i-1]
return &String{Value: k} return &String{Value: k}
} }
// Value returns the value of the current element. // Value returns the value of the current element.
func (i *ModuleMapIterator) Value() Object { func (i *ImmutableMapIterator) Value() Object {
k := i.k[i.i-1] k := i.k[i.i-1]
return i.v[k] return i.v[k]

View file

@ -46,6 +46,8 @@ func TestObject_TypeName(t *testing.T) {
assert.Equal(t, "undefined", o.TypeName()) assert.Equal(t, "undefined", o.TypeName())
o = &objects.Error{} o = &objects.Error{}
assert.Equal(t, "error", o.TypeName()) assert.Equal(t, "error", o.TypeName())
o = &objects.Bytes{}
assert.Equal(t, "bytes", o.TypeName())
} }
func TestObject_IsFalsy(t *testing.T) { func TestObject_IsFalsy(t *testing.T) {
@ -96,6 +98,10 @@ func TestObject_IsFalsy(t *testing.T) {
assert.True(t, o.IsFalsy()) assert.True(t, o.IsFalsy())
o = &objects.Error{} o = &objects.Error{}
assert.True(t, o.IsFalsy()) assert.True(t, o.IsFalsy())
o = &objects.Bytes{}
assert.True(t, o.IsFalsy())
o = &objects.Bytes{Value: []byte{1, 2}}
assert.False(t, o.IsFalsy())
} }
func TestObject_String(t *testing.T) { func TestObject_String(t *testing.T) {
@ -138,6 +144,10 @@ func TestObject_String(t *testing.T) {
assert.Equal(t, "<return-value>", o.String()) assert.Equal(t, "<return-value>", o.String())
o = &objects.Undefined{} o = &objects.Undefined{}
assert.Equal(t, "<undefined>", o.String()) assert.Equal(t, "<undefined>", o.String())
o = &objects.Bytes{}
assert.Equal(t, "", o.String())
o = &objects.Bytes{Value: []byte("foo")}
assert.Equal(t, "foo", o.String())
} }
func TestObject_BinaryOp(t *testing.T) { func TestObject_BinaryOp(t *testing.T) {

View file

@ -40,7 +40,7 @@ func (o *UserFunction) Equals(x Object) bool {
return false return false
} }
// Call executes a builtin function. // Call invokes a user function.
func (o *UserFunction) Call(args ...Object) (Object, error) { func (o *UserFunction) Call(args ...Object) (Object, error) {
return o.Value(args...) return o.Value(args...)
} }

View file

@ -622,6 +622,25 @@ func (v *VM) Run() error {
v.stack[v.sp] = &val v.stack[v.sp] = &val
v.sp++ v.sp++
case *objects.Bytes:
idx, ok := (*index).(*objects.Int)
if !ok {
return fmt.Errorf("non-integer array index: %s", left.TypeName())
}
if idx.Value < 0 || idx.Value >= int64(len(left.Value)) {
return fmt.Errorf("index out of bounds: %d", index)
}
if v.sp >= StackSize {
return ErrStackOverflow
}
var val objects.Object = &objects.Int{Value: int64(left.Value[idx.Value])}
v.stack[v.sp] = &val
v.sp++
case *objects.Map: case *objects.Map:
key, ok := (*index).(*objects.String) key, ok := (*index).(*objects.String)
if !ok { if !ok {
@ -641,7 +660,7 @@ func (v *VM) Run() error {
v.stack[v.sp] = &res v.stack[v.sp] = &res
v.sp++ v.sp++
case *objects.ModuleMap: case *objects.ImmutableMap:
key, ok := (*index).(*objects.String) key, ok := (*index).(*objects.String)
if !ok { if !ok {
return fmt.Errorf("non-string key: %s", left.TypeName()) return fmt.Errorf("non-string key: %s", left.TypeName())
@ -754,6 +773,31 @@ func (v *VM) Run() error {
v.stack[v.sp] = &val v.stack[v.sp] = &val
v.sp++ v.sp++
case *objects.Bytes:
numElements := int64(len(left.Value))
if lowIdx < 0 || lowIdx >= numElements {
return fmt.Errorf("index out of bounds: %d", lowIdx)
}
if highIdx < 0 {
highIdx = numElements
} else if highIdx < 0 || highIdx > numElements {
return fmt.Errorf("index out of bounds: %d", highIdx)
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
}
if v.sp >= StackSize {
return ErrStackOverflow
}
var val objects.Object = &objects.Bytes{Value: left.Value[lowIdx:highIdx]}
v.stack[v.sp] = &val
v.sp++
default: default:
return fmt.Errorf("cannot slice %s", left.TypeName()) return fmt.Errorf("cannot slice %s", left.TypeName())
} }
@ -1005,7 +1049,7 @@ func (v *VM) Run() error {
iterator = objects.NewArrayIterator(dst) iterator = objects.NewArrayIterator(dst)
case *objects.Map: case *objects.Map:
iterator = objects.NewMapIterator(dst) iterator = objects.NewMapIterator(dst)
case *objects.ModuleMap: case *objects.ImmutableMap:
iterator = objects.NewModuleMapIterator(dst) iterator = objects.NewModuleMapIterator(dst)
case *objects.String: case *objects.String:
iterator = objects.NewStringIterator(dst) iterator = objects.NewStringIterator(dst)
@ -1213,7 +1257,7 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
return nil return nil
} }
// TODO: should reuse *objects.ModuleMap for the same imports? // TODO: should reuse *objects.ImmutableMap for the same imports?
func (v *VM) importModule(compiledModule *objects.CompiledModule) error { func (v *VM) importModule(compiledModule *objects.CompiledModule) error {
// import module is basically to create a new instance of VM // import module is basically to create a new instance of VM
// and run the module code and retrieve all global variables after execution. // and run the module code and retrieve all global variables after execution.
@ -1230,7 +1274,7 @@ func (v *VM) importModule(compiledModule *objects.CompiledModule) error {
mmValue[name] = *moduleVM.globals[index] mmValue[name] = *moduleVM.globals[index]
} }
var mm objects.Object = &objects.ModuleMap{Value: mmValue} var mm objects.Object = &objects.ImmutableMap{Value: mmValue}
if v.sp >= StackSize { if v.sp >= StackSize {
return ErrStackOverflow return ErrStackOverflow

15
runtime/vm_bytes_test.go Normal file
View file

@ -0,0 +1,15 @@
package runtime_test
import (
"testing"
)
func TestBytes(t *testing.T) {
expect(t, `out = bytes("Hello World!")`, []byte("Hello World!"))
expect(t, `out = bytes("Hello") + bytes(" ") + bytes("World!")`, []byte("Hello World!"))
// bytes[] -> int
expect(t, `out = bytes("abcde")[0]`, 97)
expect(t, `out = bytes("abcde")[1]`, 98)
expect(t, `out = bytes("abcde")[4]`, 101)
}

View file

@ -3,12 +3,48 @@ package runtime_test
import "testing" import "testing"
func TestModule(t *testing.T) { func TestModule(t *testing.T) {
// stdmods // stdlib
expect(t, `math := import("math"); out = math.abs(1)`, 1.0) expect(t, `math := import("math"); out = math.abs(1)`, 1.0)
expect(t, `math := import("math"); out = math.abs(-1)`, 1.0) expect(t, `math := import("math"); out = math.abs(-1)`, 1.0)
expect(t, `math := import("math"); out = math.abs(1.0)`, 1.0) expect(t, `math := import("math"); out = math.abs(1.0)`, 1.0)
expect(t, `math := import("math"); out = math.abs(-1.0)`, 1.0) expect(t, `math := import("math"); out = math.abs(-1.0)`, 1.0)
// os.File
expect(t, `
os := import("os")
write_file := func(filename, data) {
file := os.create(filename)
if !file { return file }
if res := file.write(bytes(data)); is_error(res) {
return res
}
return file.close()
}
read_file := func(filename) {
file := os.open(filename)
if !file { return file }
data := bytes(100)
cnt := file.read(data)
if is_error(cnt) {
return cnt
}
file.close()
return data[:cnt]
}
if write_file("./temp", "foobar") {
out = string(read_file("./temp"))
}
os.remove("./temp")
`, "foobar")
// user modules // user modules
expectWithUserModules(t, `out = import("mod1").bar()`, 5.0, map[string]string{ expectWithUserModules(t, `out = import("mod1").bar()`, 5.0, map[string]string{
"mod1": `bar := func() { return 5.0 }`, "mod1": `bar := func() { return 5.0 }`,

View file

@ -112,6 +112,8 @@ func toObject(v interface{}) objects.Object {
return &objects.Char{Value: rune(v)} return &objects.Char{Value: rune(v)}
case float64: case float64:
return &objects.Float{Value: v} return &objects.Float{Value: v}
case []byte:
return &objects.Bytes{Value: v}
case MAP: case MAP:
objs := make(map[string]objects.Object) objs := make(map[string]objects.Object)
for k, v := range v { for k, v := range v {
@ -278,6 +280,8 @@ func objectZeroCopy(o objects.Object) objects.Object {
return &objects.Undefined{} return &objects.Undefined{}
case *objects.Error: case *objects.Error:
return &objects.Error{} return &objects.Error{}
case *objects.Bytes:
return &objects.Bytes{}
case nil: case nil:
panic("nil") panic("nil")
default: default:

View file

@ -2,44 +2,10 @@ package script
import ( import (
"fmt" "fmt"
"strconv"
"strings"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
func objectToString(o objects.Object) string {
switch val := o.(type) {
case *objects.Array:
var s []string
for _, e := range val.Value {
s = append(s, objectToString(e))
}
return "[" + strings.Join(s, ", ") + "]"
case *objects.Map:
var s []string
for k, v := range val.Value {
s = append(s, k+": "+objectToString(v))
}
return "{" + strings.Join(s, ", ") + "}"
case *objects.Int:
return strconv.FormatInt(val.Value, 10)
case *objects.Float:
return strconv.FormatFloat(val.Value, 'f', -1, 64)
case *objects.Bool:
if val.Value {
return "true"
}
return "false"
case *objects.Char:
return string(val.Value)
case *objects.String:
return val.Value
}
return o.String()
}
func objectToInterface(o objects.Object) interface{} { func objectToInterface(o objects.Object) interface{} {
switch val := o.(type) { switch val := o.(type) {
case *objects.Array: case *objects.Array:
@ -56,6 +22,8 @@ func objectToInterface(o objects.Object) interface{} {
return val.Value return val.Value
case *objects.String: case *objects.String:
return val.Value return val.Value
case *objects.Bytes:
return val.Value
case *objects.Undefined: case *objects.Undefined:
return nil return nil
} }
@ -81,6 +49,10 @@ func interfaceToObject(v interface{}) (objects.Object, error) {
return &objects.Char{Value: rune(v)}, nil return &objects.Char{Value: rune(v)}, nil
case float64: case float64:
return &objects.Float{Value: v}, nil return &objects.Float{Value: v}, nil
case []byte:
return &objects.Bytes{Value: v}, nil
case error:
return &objects.Error{Value: &objects.String{Value: v.Error()}}, nil
case map[string]objects.Object: case map[string]objects.Object:
return &objects.Map{Value: v}, nil return &objects.Map{Value: v}, nil
case map[string]interface{}: case map[string]interface{}:

View file

@ -1,8 +1,6 @@
package script package script
import ( import (
"strconv"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
@ -43,73 +41,41 @@ func (v *Variable) ValueType() string {
// Int returns int value of the variable value. // Int returns int value of the variable value.
// It returns 0 if the value is not convertible to int. // It returns 0 if the value is not convertible to int.
func (v *Variable) Int() int { func (v *Variable) Int() int {
return int(v.Int64()) c, _ := objects.ToInt(*v.value)
return c
} }
// Int64 returns int64 value of the variable value. // Int64 returns int64 value of the variable value.
// It returns 0 if the value is not convertible to int64. // It returns 0 if the value is not convertible to int64.
func (v *Variable) Int64() int64 { func (v *Variable) Int64() int64 {
switch val := (*v.value).(type) { c, _ := objects.ToInt64(*v.value)
case *objects.Int:
return val.Value
case *objects.Float:
return int64(val.Value)
case *objects.Bool:
if val.Value {
return 1
}
return 0
case *objects.Char:
return int64(val.Value)
case *objects.String:
n, _ := strconv.ParseInt(val.Value, 10, 64)
return n
}
return 0 return c
} }
// Float returns float64 value of the variable value. // Float returns float64 value of the variable value.
// It returns 0.0 if the value is not convertible to float64. // It returns 0.0 if the value is not convertible to float64.
func (v *Variable) Float() float64 { func (v *Variable) Float() float64 {
switch val := (*v.value).(type) { c, _ := objects.ToFloat64(*v.value)
case *objects.Int:
return float64(val.Value)
case *objects.Float:
return val.Value
case *objects.Bool:
if val.Value {
return 1
}
return 0
case *objects.String:
f, _ := strconv.ParseFloat(val.Value, 64)
return f
}
return 0 return c
} }
// Char returns rune value of the variable value. // Char returns rune value of the variable value.
// It returns 0 if the value is not convertible to rune. // It returns 0 if the value is not convertible to rune.
func (v *Variable) Char() rune { func (v *Variable) Char() rune {
switch val := (*v.value).(type) { c, _ := objects.ToRune(*v.value)
case *objects.Char:
return val.Value
}
return 0 return c
} }
// Bool returns bool value of the variable value. // Bool returns bool value of the variable value.
// It returns 0 if the value is not convertible to bool. // It returns 0 if the value is not convertible to bool.
func (v *Variable) Bool() bool { func (v *Variable) Bool() bool {
switch val := (*v.value).(type) { c, _ := objects.ToBool(*v.value)
case *objects.Bool:
return val.Value
}
return false return c
} }
// Array returns []interface value of the variable value. // Array returns []interface value of the variable value.
@ -145,7 +111,17 @@ func (v *Variable) Map() map[string]interface{} {
// String returns string value of the variable value. // String returns string value of the variable value.
// It returns 0 if the value is not convertible to string. // It returns 0 if the value is not convertible to string.
func (v *Variable) String() string { func (v *Variable) String() string {
return objectToString(*v.value) c, _ := objects.ToString(*v.value)
return c
}
// Bytes returns a byte slice of the variable value.
// It returns nil if the value is not convertible to byte slice.
func (v *Variable) Bytes() []byte {
c, _ := objects.ToByteSlice(*v.value)
return c
} }
// Object returns an underlying Object of the variable value. // Object returns an underlying Object of the variable value.

View file

@ -31,6 +31,8 @@ func TestVariable(t *testing.T) {
IntValue: 1, IntValue: 1,
Int64Value: 1, Int64Value: 1,
FloatValue: 1.0, FloatValue: 1.0,
CharValue: rune(1),
BoolValue: true,
StringValue: "1", StringValue: "1",
Object: &objects.Int{Value: 1}, Object: &objects.Int{Value: 1},
}, },
@ -40,6 +42,7 @@ func TestVariable(t *testing.T) {
ValueType: "string", ValueType: "string",
FloatValue: 52.11, FloatValue: 52.11,
StringValue: "52.11", StringValue: "52.11",
BoolValue: true,
Object: &objects.String{Value: "52.11"}, Object: &objects.String{Value: "52.11"},
}, },
{ {
@ -48,7 +51,7 @@ func TestVariable(t *testing.T) {
ValueType: "bool", ValueType: "bool",
IntValue: 1, IntValue: 1,
Int64Value: 1, Int64Value: 1,
FloatValue: 1, FloatValue: 0,
BoolValue: true, BoolValue: true,
StringValue: "true", StringValue: "true",
Object: &objects.Bool{Value: true}, Object: &objects.Bool{Value: true},
@ -57,7 +60,7 @@ func TestVariable(t *testing.T) {
Name: "d", Name: "d",
Value: nil, Value: nil,
ValueType: "undefined", ValueType: "undefined",
StringValue: "<undefined>", StringValue: "",
Object: objects.UndefinedValue, Object: objects.UndefinedValue,
IsUndefined: true, IsUndefined: true,
}, },
@ -66,15 +69,15 @@ func TestVariable(t *testing.T) {
for _, tc := range vars { for _, tc := range vars {
v, err := script.NewVariable(tc.Name, tc.Value) v, err := script.NewVariable(tc.Name, tc.Value)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tc.Value, v.Value()) assert.Equal(t, tc.Value, v.Value(), "Name: %s", tc.Name)
assert.Equal(t, tc.ValueType, v.ValueType()) assert.Equal(t, tc.ValueType, v.ValueType(), "Name: %s", tc.Name)
assert.Equal(t, tc.IntValue, v.Int()) assert.Equal(t, tc.IntValue, v.Int(), "Name: %s", tc.Name)
assert.Equal(t, tc.Int64Value, v.Int64()) assert.Equal(t, tc.Int64Value, v.Int64(), "Name: %s", tc.Name)
assert.Equal(t, tc.FloatValue, v.Float()) assert.Equal(t, tc.FloatValue, v.Float(), "Name: %s", tc.Name)
assert.Equal(t, tc.CharValue, v.Char()) assert.Equal(t, tc.CharValue, v.Char(), "Name: %s", tc.Name)
assert.Equal(t, tc.BoolValue, v.Bool()) assert.Equal(t, tc.BoolValue, v.Bool(), "Name: %s", tc.Name)
assert.Equal(t, tc.StringValue, v.String()) assert.Equal(t, tc.StringValue, v.String(), "Name: %s", tc.Name)
assert.Equal(t, tc.Object, v.Object()) assert.Equal(t, tc.Object, v.Object(), "Name: %s", tc.Name)
assert.Equal(t, tc.IsUndefined, v.IsUndefined()) assert.Equal(t, tc.IsUndefined, v.IsUndefined(), "Name: %s", tc.Name)
} }
} }