From 8cad04841ea13e2c348648b7fee6068a7d49feca Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 18 Jan 2019 01:43:46 -0800 Subject: [PATCH] add os File functions; add Bytes type --- README.md | 4 +- assert/assert.go | 4 + compiler/bytecode.go | 7 + compiler/compiler.go | 4 +- compiler/{stdmods => stdlib}/errors.go | 2 +- compiler/{stdmods => stdlib}/func_typedefs.go | 117 ++++++++++++- .../{stdmods => stdlib}/func_typedefs_test.go | 84 ++++++---- compiler/{stdmods => stdlib}/math.go | 2 +- compiler/stdlib/os.go | 154 ++++++++++++++++++ compiler/stdlib/os_file.go | 141 ++++++++++++++++ .../{stdmods/stdmods.go => stdlib/stdlib.go} | 4 +- compiler/stdmods/os.go | 154 ------------------ objects/bool.go | 2 +- objects/builtin_convert.go | 18 ++ objects/builtin_len.go | 4 +- objects/builtin_print.go | 6 +- objects/builtin_type_checks.go | 12 ++ objects/builtins.go | 8 + objects/bytes.go | 56 +++++++ objects/conversion.go | 22 ++- objects/errors.go | 6 + objects/{module_map.go => immutable_map.go} | 24 +-- ..._iterator.go => immutable_map_iterator.go} | 28 ++-- objects/object_test.go | 10 ++ objects/user_function.go | 2 +- runtime/vm.go | 52 +++++- runtime/vm_bytes_test.go | 15 ++ runtime/vm_module_test.go | 38 ++++- runtime/vm_test.go | 4 + script/conversion.go | 40 +---- script/variable.go | 68 +++----- script/variable_test.go | 27 +-- 32 files changed, 794 insertions(+), 325 deletions(-) rename compiler/{stdmods => stdlib}/errors.go (92%) rename compiler/{stdmods => stdlib}/func_typedefs.go (80%) rename compiler/{stdmods => stdlib}/func_typedefs_test.go (72%) rename compiler/{stdmods => stdlib}/math.go (99%) create mode 100644 compiler/stdlib/os.go create mode 100644 compiler/stdlib/os_file.go rename compiler/{stdmods/stdmods.go => stdlib/stdlib.go} (68%) delete mode 100644 compiler/stdmods/os.go create mode 100644 objects/bytes.go rename objects/{module_map.go => immutable_map.go} (67%) rename objects/{module_map_iterator.go => immutable_map_iterator.go} (58%) create mode 100644 runtime/vm_bytes_test.go diff --git a/README.md b/README.md index ab86de3..95096b6 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,8 @@ c5 := char("X") // 'X' ``` > [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. ```golang @@ -115,7 +117,7 @@ m.x = 5 // add 'x' to map 'm' ``` > [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 a := [1, 2, 3, 4, 5][1:3] // == [2, 3] diff --git a/assert/assert.go b/assert/assert.go index 6488a87..29efa82 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -165,6 +165,10 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool return Equal(t, expected.Value, actual.(objects.ReturnValue).Value) case *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: return equalMap(t, expected, actual.(*objects.Map)) case *objects.CompiledFunction: diff --git a/compiler/bytecode.go b/compiler/bytecode.go index 04bd1db..a0f10ba 100644 --- a/compiler/bytecode.go +++ b/compiler/bytecode.go @@ -45,4 +45,11 @@ func init() { gob.Register(&objects.Map{}) gob.Register(&objects.CompiledFunction{}) 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{}) } diff --git a/compiler/compiler.go b/compiler/compiler.go index f78c302..ba5a26b 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -6,7 +6,7 @@ import ( "reflect" "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/objects" ) @@ -440,7 +440,7 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(OpCall, len(node.Args)) case *ast.ImportExpr: - stdMod, ok := stdmods.Modules[node.ModuleName] + stdMod, ok := stdlib.Modules[node.ModuleName] if ok { // standard modules contain only globals with no code. // so no need to compile anything diff --git a/compiler/stdmods/errors.go b/compiler/stdlib/errors.go similarity index 92% rename from compiler/stdmods/errors.go rename to compiler/stdlib/errors.go index 3c265eb..a2942bb 100644 --- a/compiler/stdmods/errors.go +++ b/compiler/stdlib/errors.go @@ -1,4 +1,4 @@ -package stdmods +package stdlib import "github.com/d5/tengo/objects" diff --git a/compiler/stdmods/func_typedefs.go b/compiler/stdlib/func_typedefs.go similarity index 80% rename from compiler/stdmods/func_typedefs.go rename to compiler/stdlib/func_typedefs.go index 5220cd8..83c540f 100644 --- a/compiler/stdmods/func_typedefs.go +++ b/compiler/stdlib/func_typedefs.go @@ -1,4 +1,4 @@ -package stdmods +package stdlib import ( "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 // into a user function object. 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 // into a user function object. 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 + }, + } +} diff --git a/compiler/stdmods/func_typedefs_test.go b/compiler/stdlib/func_typedefs_test.go similarity index 72% rename from compiler/stdmods/func_typedefs_test.go rename to compiler/stdlib/func_typedefs_test.go index f020116..d6baed4 100644 --- a/compiler/stdmods/func_typedefs_test.go +++ b/compiler/stdlib/func_typedefs_test.go @@ -1,16 +1,16 @@ -package stdmods_test +package stdlib_test import ( "errors" "testing" "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/stdmods" + "github.com/d5/tengo/compiler/stdlib" "github.com/d5/tengo/objects" ) func TestFuncAIR(t *testing.T) { - uf := stdmods.FuncAIR(func(int) {}) + uf := stdlib.FuncAIR(func(int) {}) ret, err := uf.Call(&objects.Int{Value: 10}) assert.NoError(t, err) assert.Equal(t, &objects.Undefined{}, ret) @@ -19,7 +19,7 @@ func TestFuncAIR(t *testing.T) { } func TestFuncAR(t *testing.T) { - uf := stdmods.FuncAR(func() {}) + uf := stdlib.FuncAR(func() {}) ret, err := uf.Call() assert.NoError(t, err) assert.Equal(t, &objects.Undefined{}, ret) @@ -28,7 +28,7 @@ func TestFuncAR(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() assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 10}, ret) @@ -36,12 +36,25 @@ func TestFuncARI(t *testing.T) { 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) { - 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() assert.NoError(t, err) 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() assert.NoError(t, err) 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) { - uf := stdmods.FuncARS(func() string { return "foo" }) + uf := stdlib.FuncARS(func() string { return "foo" }) ret, err := uf.Call() assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "foo"}, ret) @@ -59,11 +72,11 @@ func TestFuncARS(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() assert.NoError(t, err) 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() assert.NoError(t, err) 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) { - uf := stdmods.FuncARSs(func() []string { return []string{"foo", "bar"} }) + uf := stdlib.FuncARSs(func() []string { return []string{"foo", "bar"} }) ret, err := uf.Call() assert.NoError(t, err) 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) { - 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"}) assert.NoError(t, err) 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"}) assert.NoError(t, err) 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) { - 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"}) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "foo"}, ret) @@ -103,11 +116,11 @@ func TestFuncASRS(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}) assert.NoError(t, err) 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}) assert.NoError(t, err) 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) } +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) { - 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}) assert.NoError(t, err) 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}) assert.NoError(t, err) 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) { - 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"}) assert.NoError(t, err) 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"}) assert.NoError(t, err) 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) { - uf := stdmods.FuncARF(func() float64 { + uf := stdlib.FuncARF(func() float64 { return 10.0 }) ret, err := uf.Call() @@ -157,7 +183,7 @@ func TestFuncARF(t *testing.T) { } func TestFuncAFRF(t *testing.T) { - uf := stdmods.FuncAFRF(func(a float64) float64 { + uf := stdlib.FuncAFRF(func(a float64) float64 { return a }) ret, err := uf.Call(&objects.Float{Value: 10.0}) @@ -170,7 +196,7 @@ func TestFuncAFRF(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) }) ret, err := uf.Call(&objects.Int{Value: 10.0}) @@ -183,7 +209,7 @@ func TestFuncAIRF(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) }) ret, err := uf.Call(&objects.Float{Value: 10.5}) @@ -196,7 +222,7 @@ func TestFuncAFRI(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 }) ret, err := uf.Call(&objects.Float{Value: 0.1}) @@ -209,7 +235,7 @@ func TestFuncAFRB(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 }) 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) { - uf := stdmods.FuncAIFRF(func(a int, b float64) float64 { + uf := stdlib.FuncAIFRF(func(a int, b float64) float64 { return float64(a) + b }) 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) { - uf := stdmods.FuncAFIRF(func(a float64, b int) float64 { + uf := stdlib.FuncAFIRF(func(a float64, b int) float64 { return a + float64(b) }) 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) { - uf := stdmods.FuncAFIRB(func(a float64, b int) bool { + uf := stdlib.FuncAFIRB(func(a float64, b int) bool { return a < float64(b) }) ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20}) diff --git a/compiler/stdmods/math.go b/compiler/stdlib/math.go similarity index 99% rename from compiler/stdmods/math.go rename to compiler/stdlib/math.go index d2bb075..a5b2182 100644 --- a/compiler/stdmods/math.go +++ b/compiler/stdlib/math.go @@ -1,4 +1,4 @@ -package stdmods +package stdlib import ( "math" diff --git a/compiler/stdlib/os.go b/compiler/stdlib/os.go new file mode 100644 index 0000000..a855ca1 --- /dev/null +++ b/compiler/stdlib/os.go @@ -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 +} diff --git a/compiler/stdlib/os_file.go b/compiler/stdlib/os_file.go new file mode 100644 index 0000000..845541a --- /dev/null +++ b/compiler/stdlib/os_file.go @@ -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 +} diff --git a/compiler/stdmods/stdmods.go b/compiler/stdlib/stdlib.go similarity index 68% rename from compiler/stdmods/stdmods.go rename to compiler/stdlib/stdlib.go index c32f02a..cd24166 100644 --- a/compiler/stdmods/stdmods.go +++ b/compiler/stdlib/stdlib.go @@ -1,9 +1,9 @@ -package stdmods +package stdlib import "github.com/d5/tengo/objects" // Modules contain the standard modules. -var Modules = map[string]*objects.ModuleMap{ +var Modules = map[string]*objects.ImmutableMap{ "math": {Value: mathModule}, "os": {Value: osModule}, } diff --git a/compiler/stdmods/os.go b/compiler/stdmods/os.go deleted file mode 100644 index 5dfe574..0000000 --- a/compiler/stdmods/os.go +++ /dev/null @@ -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 -} diff --git a/objects/bool.go b/objects/bool.go index 719e48c..b7ca19c 100644 --- a/objects/bool.go +++ b/objects/bool.go @@ -30,7 +30,7 @@ func (o *Bool) BinaryOp(op token.Token, rhs Object) (Object, error) { // Copy returns a copy of the type. func (o *Bool) Copy() Object { - v := Bool(*o) + v := Bool{Value: o.Value} return &v } diff --git a/objects/builtin_convert.go b/objects/builtin_convert.go index f8fdd8a..922efc0 100644 --- a/objects/builtin_convert.go +++ b/objects/builtin_convert.go @@ -84,3 +84,21 @@ func builtinChar(args ...Object) (Object, error) { 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 +} diff --git a/objects/builtin_len.go b/objects/builtin_len.go index df6620e..fd6529a 100644 --- a/objects/builtin_len.go +++ b/objects/builtin_len.go @@ -14,9 +14,11 @@ func builtinLen(args ...Object) (Object, error) { return &Int{Value: int64(len(arg.Value))}, nil case *String: return &Int{Value: int64(len(arg.Value))}, nil + case *Bytes: + return &Int{Value: int64(len(arg.Value))}, nil case *Map: return &Int{Value: int64(len(arg.Value))}, nil - case *ModuleMap: + case *ImmutableMap: return &Int{Value: int64(len(arg.Value))}, nil default: return nil, fmt.Errorf("unsupported type for 'len' function: %s", arg.TypeName()) diff --git a/objects/builtin_print.go b/objects/builtin_print.go index a512721..46be159 100644 --- a/objects/builtin_print.go +++ b/objects/builtin_print.go @@ -6,7 +6,11 @@ import ( func builtinPrint(args ...Object) (Object, error) { 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 diff --git a/objects/builtin_type_checks.go b/objects/builtin_type_checks.go index 99ee4ba..b2af058 100644 --- a/objects/builtin_type_checks.go +++ b/objects/builtin_type_checks.go @@ -60,6 +60,18 @@ func builtinIsChar(args ...Object) (Object, error) { 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) { if len(args) != 1 { return nil, ErrWrongNumArguments diff --git a/objects/builtins.go b/objects/builtins.go index 41c664d..acf2419 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -41,6 +41,10 @@ var Builtins = []struct { Name: "char", Func: builtinChar, }, + { + Name: "bytes", + Func: builtinBytes, + }, { Name: "is_int", Func: builtinIsInt, @@ -61,6 +65,10 @@ var Builtins = []struct { Name: "is_char", Func: builtinIsChar, }, + { + Name: "is_bytes", + Func: builtinIsBytes, + }, { Name: "is_error", Func: builtinIsError, diff --git a/objects/bytes.go b/objects/bytes.go new file mode 100644 index 0000000..58e8059 --- /dev/null +++ b/objects/bytes.go @@ -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 +} diff --git a/objects/conversion.go b/objects/conversion.go index 064aa69..99996bb 100644 --- a/objects/conversion.go +++ b/objects/conversion.go @@ -118,13 +118,21 @@ func ToRune(o Object) (v rune, ok bool) { case *Char: v = rune(o.Value) ok = true - case *String: - rs := []rune(o.Value) - switch len(rs) { - case 1: - v = rs[0] - ok = true - } + } + + //ok = false + return +} + +// 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 diff --git a/objects/errors.go b/objects/errors.go index a0f9038..19fbaa9 100644 --- a/objects/errors.go +++ b/objects/errors.go @@ -2,6 +2,12 @@ package objects 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. var ErrInvalidOperator = errors.New("invalid operator") diff --git a/objects/module_map.go b/objects/immutable_map.go similarity index 67% rename from objects/module_map.go rename to objects/immutable_map.go index baa2f46..dcf5333 100644 --- a/objects/module_map.go +++ b/objects/immutable_map.go @@ -7,17 +7,17 @@ import ( "github.com/d5/tengo/compiler/token" ) -// ModuleMap represents a module map object. -type ModuleMap struct { +// ImmutableMap represents a module map object. +type ImmutableMap struct { Value map[string]Object } // TypeName returns the name of the type. -func (o *ModuleMap) TypeName() string { - return "module" +func (o *ImmutableMap) TypeName() string { + return "immutable-map" } -func (o *ModuleMap) String() string { +func (o *ImmutableMap) String() string { var pairs []string for k, v := range o.Value { 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 // 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 } // Copy returns a copy of the type. -func (o *ModuleMap) Copy() Object { +func (o *ImmutableMap) Copy() Object { c := make(map[string]Object) for k, v := range o.Value { c[k] = v.Copy() } - return &ModuleMap{Value: c} + return &ImmutableMap{Value: c} } // 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 } // 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] return val, ok @@ -56,8 +56,8 @@ func (o *ModuleMap) Get(key string) (Object, bool) { // Equals returns true if the value of the type // is equal to the value of another object. -func (o *ModuleMap) Equals(x Object) bool { - t, ok := x.(*ModuleMap) +func (o *ImmutableMap) Equals(x Object) bool { + t, ok := x.(*ImmutableMap) if !ok { return false } diff --git a/objects/module_map_iterator.go b/objects/immutable_map_iterator.go similarity index 58% rename from objects/module_map_iterator.go rename to objects/immutable_map_iterator.go index 9aa49a1..4222616 100644 --- a/objects/module_map_iterator.go +++ b/objects/immutable_map_iterator.go @@ -2,8 +2,8 @@ package objects import "github.com/d5/tengo/compiler/token" -// ModuleMapIterator represents an iterator for the module map. -type ModuleMapIterator struct { +// ImmutableMapIterator represents an iterator for the immutable map. +type ImmutableMapIterator struct { v map[string]Object k []string i int @@ -11,13 +11,13 @@ type ModuleMapIterator struct { } // NewModuleMapIterator creates a module iterator. -func NewModuleMapIterator(v *ModuleMap) Iterator { +func NewModuleMapIterator(v *ImmutableMap) Iterator { var keys []string for k := range v.Value { keys = append(keys, k) } - return &ModuleMapIterator{ + return &ImmutableMapIterator{ v: v.Value, k: keys, l: len(keys), @@ -25,51 +25,51 @@ func NewModuleMapIterator(v *ModuleMap) Iterator { } // TypeName returns the name of the type. -func (i *ModuleMapIterator) TypeName() string { +func (i *ImmutableMapIterator) TypeName() string { return "module-iterator" } -func (i *ModuleMapIterator) String() string { +func (i *ImmutableMapIterator) String() string { return "" } // BinaryOp returns another object that is the result of // 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 } // IsFalsy returns true if the value of the type is falsy. -func (i *ModuleMapIterator) IsFalsy() bool { +func (i *ImmutableMapIterator) IsFalsy() bool { return true } // Equals returns true if the value of the type // is equal to the value of another object. -func (i *ModuleMapIterator) Equals(Object) bool { +func (i *ImmutableMapIterator) Equals(Object) bool { return false } // Copy returns a copy of the type. -func (i *ModuleMapIterator) Copy() Object { - return &ModuleMapIterator{v: i.v, k: i.k, i: i.i, l: i.l} +func (i *ImmutableMapIterator) Copy() Object { + return &ImmutableMapIterator{v: i.v, k: i.k, i: i.i, l: i.l} } // Next returns true if there are more elements to iterate. -func (i *ModuleMapIterator) Next() bool { +func (i *ImmutableMapIterator) Next() bool { i.i++ return i.i <= i.l } // 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] return &String{Value: k} } // Value returns the value of the current element. -func (i *ModuleMapIterator) Value() Object { +func (i *ImmutableMapIterator) Value() Object { k := i.k[i.i-1] return i.v[k] diff --git a/objects/object_test.go b/objects/object_test.go index 0159b2b..88ca669 100644 --- a/objects/object_test.go +++ b/objects/object_test.go @@ -46,6 +46,8 @@ func TestObject_TypeName(t *testing.T) { assert.Equal(t, "undefined", o.TypeName()) o = &objects.Error{} assert.Equal(t, "error", o.TypeName()) + o = &objects.Bytes{} + assert.Equal(t, "bytes", o.TypeName()) } func TestObject_IsFalsy(t *testing.T) { @@ -96,6 +98,10 @@ func TestObject_IsFalsy(t *testing.T) { assert.True(t, o.IsFalsy()) o = &objects.Error{} 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) { @@ -138,6 +144,10 @@ func TestObject_String(t *testing.T) { assert.Equal(t, "", o.String()) o = &objects.Undefined{} assert.Equal(t, "", 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) { diff --git a/objects/user_function.go b/objects/user_function.go index c83866a..74b818c 100644 --- a/objects/user_function.go +++ b/objects/user_function.go @@ -40,7 +40,7 @@ func (o *UserFunction) Equals(x Object) bool { return false } -// Call executes a builtin function. +// Call invokes a user function. func (o *UserFunction) Call(args ...Object) (Object, error) { return o.Value(args...) } diff --git a/runtime/vm.go b/runtime/vm.go index 74e5bfe..f4254e8 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -622,6 +622,25 @@ func (v *VM) Run() error { v.stack[v.sp] = &val 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: key, ok := (*index).(*objects.String) if !ok { @@ -641,7 +660,7 @@ func (v *VM) Run() error { v.stack[v.sp] = &res v.sp++ - case *objects.ModuleMap: + case *objects.ImmutableMap: key, ok := (*index).(*objects.String) if !ok { return fmt.Errorf("non-string key: %s", left.TypeName()) @@ -754,6 +773,31 @@ func (v *VM) Run() error { v.stack[v.sp] = &val 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: return fmt.Errorf("cannot slice %s", left.TypeName()) } @@ -1005,7 +1049,7 @@ func (v *VM) Run() error { iterator = objects.NewArrayIterator(dst) case *objects.Map: iterator = objects.NewMapIterator(dst) - case *objects.ModuleMap: + case *objects.ImmutableMap: iterator = objects.NewModuleMapIterator(dst) case *objects.String: iterator = objects.NewStringIterator(dst) @@ -1213,7 +1257,7 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje 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 { // import module is basically to create a new instance of VM // 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] } - var mm objects.Object = &objects.ModuleMap{Value: mmValue} + var mm objects.Object = &objects.ImmutableMap{Value: mmValue} if v.sp >= StackSize { return ErrStackOverflow diff --git a/runtime/vm_bytes_test.go b/runtime/vm_bytes_test.go new file mode 100644 index 0000000..9ea13c7 --- /dev/null +++ b/runtime/vm_bytes_test.go @@ -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) +} diff --git a/runtime/vm_module_test.go b/runtime/vm_module_test.go index 5c97afc..9dc8d49 100644 --- a/runtime/vm_module_test.go +++ b/runtime/vm_module_test.go @@ -3,12 +3,48 @@ package runtime_test import "testing" 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.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 expectWithUserModules(t, `out = import("mod1").bar()`, 5.0, map[string]string{ "mod1": `bar := func() { return 5.0 }`, diff --git a/runtime/vm_test.go b/runtime/vm_test.go index c0b36f2..d14eaef 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -112,6 +112,8 @@ func toObject(v interface{}) objects.Object { return &objects.Char{Value: rune(v)} case float64: return &objects.Float{Value: v} + case []byte: + return &objects.Bytes{Value: v} case MAP: objs := make(map[string]objects.Object) for k, v := range v { @@ -278,6 +280,8 @@ func objectZeroCopy(o objects.Object) objects.Object { return &objects.Undefined{} case *objects.Error: return &objects.Error{} + case *objects.Bytes: + return &objects.Bytes{} case nil: panic("nil") default: diff --git a/script/conversion.go b/script/conversion.go index 9785fba..d1617ff 100644 --- a/script/conversion.go +++ b/script/conversion.go @@ -2,44 +2,10 @@ package script import ( "fmt" - "strconv" - "strings" "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{} { switch val := o.(type) { case *objects.Array: @@ -56,6 +22,8 @@ func objectToInterface(o objects.Object) interface{} { return val.Value case *objects.String: return val.Value + case *objects.Bytes: + return val.Value case *objects.Undefined: return nil } @@ -81,6 +49,10 @@ func interfaceToObject(v interface{}) (objects.Object, error) { return &objects.Char{Value: rune(v)}, nil case float64: 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: return &objects.Map{Value: v}, nil case map[string]interface{}: diff --git a/script/variable.go b/script/variable.go index f0decb4..bf9189b 100644 --- a/script/variable.go +++ b/script/variable.go @@ -1,8 +1,6 @@ package script import ( - "strconv" - "github.com/d5/tengo/objects" ) @@ -43,73 +41,41 @@ func (v *Variable) ValueType() string { // Int returns int value of the variable value. // It returns 0 if the value is not convertible to 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. // It returns 0 if the value is not convertible to int64. func (v *Variable) Int64() int64 { - switch val := (*v.value).(type) { - 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 - } + c, _ := objects.ToInt64(*v.value) - return 0 + return c } // Float returns float64 value of the variable value. // It returns 0.0 if the value is not convertible to float64. func (v *Variable) Float() float64 { - switch val := (*v.value).(type) { - 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 - } + c, _ := objects.ToFloat64(*v.value) - return 0 + return c } // Char returns rune value of the variable value. // It returns 0 if the value is not convertible to rune. func (v *Variable) Char() rune { - switch val := (*v.value).(type) { - case *objects.Char: - return val.Value - } + c, _ := objects.ToRune(*v.value) - return 0 + return c } // Bool returns bool value of the variable value. // It returns 0 if the value is not convertible to bool. func (v *Variable) Bool() bool { - switch val := (*v.value).(type) { - case *objects.Bool: - return val.Value - } + c, _ := objects.ToBool(*v.value) - return false + return c } // 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. // It returns 0 if the value is not convertible to 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. diff --git a/script/variable_test.go b/script/variable_test.go index 3a3a3d5..45894f8 100644 --- a/script/variable_test.go +++ b/script/variable_test.go @@ -31,6 +31,8 @@ func TestVariable(t *testing.T) { IntValue: 1, Int64Value: 1, FloatValue: 1.0, + CharValue: rune(1), + BoolValue: true, StringValue: "1", Object: &objects.Int{Value: 1}, }, @@ -40,6 +42,7 @@ func TestVariable(t *testing.T) { ValueType: "string", FloatValue: 52.11, StringValue: "52.11", + BoolValue: true, Object: &objects.String{Value: "52.11"}, }, { @@ -48,7 +51,7 @@ func TestVariable(t *testing.T) { ValueType: "bool", IntValue: 1, Int64Value: 1, - FloatValue: 1, + FloatValue: 0, BoolValue: true, StringValue: "true", Object: &objects.Bool{Value: true}, @@ -57,7 +60,7 @@ func TestVariable(t *testing.T) { Name: "d", Value: nil, ValueType: "undefined", - StringValue: "", + StringValue: "", Object: objects.UndefinedValue, IsUndefined: true, }, @@ -66,15 +69,15 @@ func TestVariable(t *testing.T) { for _, tc := range vars { v, err := script.NewVariable(tc.Name, tc.Value) assert.NoError(t, err) - assert.Equal(t, tc.Value, v.Value()) - assert.Equal(t, tc.ValueType, v.ValueType()) - assert.Equal(t, tc.IntValue, v.Int()) - assert.Equal(t, tc.Int64Value, v.Int64()) - assert.Equal(t, tc.FloatValue, v.Float()) - assert.Equal(t, tc.CharValue, v.Char()) - assert.Equal(t, tc.BoolValue, v.Bool()) - assert.Equal(t, tc.StringValue, v.String()) - assert.Equal(t, tc.Object, v.Object()) - assert.Equal(t, tc.IsUndefined, v.IsUndefined()) + assert.Equal(t, tc.Value, v.Value(), "Name: %s", tc.Name) + assert.Equal(t, tc.ValueType, v.ValueType(), "Name: %s", tc.Name) + assert.Equal(t, tc.IntValue, v.Int(), "Name: %s", tc.Name) + assert.Equal(t, tc.Int64Value, v.Int64(), "Name: %s", tc.Name) + assert.Equal(t, tc.FloatValue, v.Float(), "Name: %s", tc.Name) + assert.Equal(t, tc.CharValue, v.Char(), "Name: %s", tc.Name) + assert.Equal(t, tc.BoolValue, v.Bool(), "Name: %s", tc.Name) + assert.Equal(t, tc.StringValue, v.String(), "Name: %s", tc.Name) + assert.Equal(t, tc.Object, v.Object(), "Name: %s", tc.Name) + assert.Equal(t, tc.IsUndefined, v.IsUndefined(), "Name: %s", tc.Name) } }