From d4757efc68ed966472de0eb35879d805872165e0 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Thu, 17 Jan 2019 16:51:00 -0800 Subject: [PATCH 01/12] use 'strconv.Quote()` instead of string formatter --- objects/string.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/objects/string.go b/objects/string.go index c95de59..67d5ddd 100644 --- a/objects/string.go +++ b/objects/string.go @@ -1,7 +1,7 @@ package objects import ( - "fmt" + "strconv" "github.com/d5/tengo/compiler/token" ) @@ -17,7 +17,7 @@ func (o *String) TypeName() string { } func (o *String) String() string { - return fmt.Sprintf("%q", o.Value) + return strconv.Quote(o.Value) } // BinaryOp returns another object that is the result of From cb5cbad3adf92ecd39ec71e3f07c5e958e140cfc Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Thu, 17 Jan 2019 16:51:26 -0800 Subject: [PATCH 02/12] `len` builtin supports map and module map --- objects/builtin_len.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/objects/builtin_len.go b/objects/builtin_len.go index 26d59a9..df6620e 100644 --- a/objects/builtin_len.go +++ b/objects/builtin_len.go @@ -14,6 +14,10 @@ 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 *Map: + return &Int{Value: int64(len(arg.Value))}, nil + case *ModuleMap: + return &Int{Value: int64(len(arg.Value))}, nil default: return nil, fmt.Errorf("unsupported type for 'len' function: %s", arg.TypeName()) } From 552e9c01c56d2ab7943bc27d407e47d1e6c7b20e Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Thu, 17 Jan 2019 16:52:07 -0800 Subject: [PATCH 03/12] separate objects conversion functions --- objects/builtin_convert.go | 95 +++++++++++--------------- objects/conversion.go | 132 +++++++++++++++++++++++++++++++++++++ objects/errors.go | 3 + 3 files changed, 174 insertions(+), 56 deletions(-) create mode 100644 objects/conversion.go diff --git a/objects/builtin_convert.go b/objects/builtin_convert.go index 618691d..f8fdd8a 100644 --- a/objects/builtin_convert.go +++ b/objects/builtin_convert.go @@ -1,22 +1,20 @@ package objects -import ( - "strconv" -) - func builtinString(args ...Object) (Object, error) { if len(args) != 1 { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *String: - return arg, nil - case *Undefined: - return UndefinedValue, nil - default: - return &String{Value: arg.String()}, nil + if _, ok := args[0].(*String); ok { + return args[0], nil } + + v, ok := ToString(args[0]) + if ok { + return &String{Value: v}, nil + } + + return UndefinedValue, nil } func builtinInt(args ...Object) (Object, error) { @@ -24,23 +22,13 @@ func builtinInt(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *Int: - return arg, nil - case *Float: - return &Int{Value: int64(arg.Value)}, nil - case *Char: - return &Int{Value: int64(arg.Value)}, nil - case *Bool: - if arg.Value { - return &Int{Value: 1}, nil - } - return &Int{Value: 0}, nil - case *String: - n, err := strconv.ParseInt(arg.Value, 10, 64) - if err == nil { - return &Int{Value: n}, nil - } + if _, ok := args[0].(*Int); ok { + return args[0], nil + } + + v, ok := ToInt64(args[0]) + if ok { + return &Int{Value: v}, nil } return UndefinedValue, nil @@ -51,16 +39,13 @@ func builtinFloat(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *Float: - return arg, nil - case *Int: - return &Float{Value: float64(arg.Value)}, nil - case *String: - f, err := strconv.ParseFloat(arg.Value, 64) - if err == nil { - return &Float{Value: f}, nil - } + if _, ok := args[0].(*Float); ok { + return args[0], nil + } + + v, ok := ToFloat64(args[0]) + if ok { + return &Float{Value: v}, nil } return UndefinedValue, nil @@ -71,12 +56,16 @@ func builtinBool(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *Bool: - return arg, nil - default: - return &Bool{Value: !arg.IsFalsy()}, nil + if _, ok := args[0].(*Bool); ok { + return args[0], nil } + + v, ok := ToBool(args[0]) + if ok { + return &Bool{Value: v}, nil + } + + return UndefinedValue, nil } func builtinChar(args ...Object) (Object, error) { @@ -84,19 +73,13 @@ func builtinChar(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *Char: - return arg, nil - case *Int: - return &Char{Value: rune(arg.Value)}, nil - case *String: - rs := []rune(arg.Value) - switch len(rs) { - case 0: - return &Char{}, nil - case 1: - return &Char{Value: rs[0]}, nil - } + if _, ok := args[0].(*Char); ok { + return args[0], nil + } + + v, ok := ToRune(args[0]) + if ok { + return &Char{Value: v}, nil } return UndefinedValue, nil diff --git a/objects/conversion.go b/objects/conversion.go new file mode 100644 index 0000000..064aa69 --- /dev/null +++ b/objects/conversion.go @@ -0,0 +1,132 @@ +package objects + +import ( + "strconv" +) + +// ToString will try to convert object o to string value. +func ToString(o Object) (v string, ok bool) { + if _, isUndefined := o.(*Undefined); isUndefined { + //ok = false + return + } + + ok = true + + if str, isStr := o.(*String); isStr { + v = str.Value + } else { + v = o.String() + } + + return +} + +// ToInt will try to convert object o to int value. +func ToInt(o Object) (v int, ok bool) { + switch o := o.(type) { + case *Int: + v = int(o.Value) + ok = true + case *Float: + v = int(o.Value) + ok = true + case *Char: + v = int(o.Value) + ok = true + case *Bool: + if o.Value { + v = 1 + } + ok = true + case *String: + c, err := strconv.ParseInt(o.Value, 10, 64) + if err == nil { + v = int(c) + ok = true + } + } + + //ok = false + return +} + +// ToInt64 will try to convert object o to int64 value. +func ToInt64(o Object) (v int64, ok bool) { + switch o := o.(type) { + case *Int: + v = o.Value + ok = true + case *Float: + v = int64(o.Value) + ok = true + case *Char: + v = int64(o.Value) + ok = true + case *Bool: + if o.Value { + v = 1 + } + ok = true + case *String: + c, err := strconv.ParseInt(o.Value, 10, 64) + if err == nil { + v = c + ok = true + } + } + + //ok = false + return +} + +// ToFloat64 will try to convert object o to float64 value. +func ToFloat64(o Object) (v float64, ok bool) { + switch o := o.(type) { + case *Int: + v = float64(o.Value) + ok = true + case *Float: + v = o.Value + ok = true + case *String: + c, err := strconv.ParseFloat(o.Value, 64) + if err == nil { + v = c + ok = true + } + } + + //ok = false + return +} + +// ToBool will try to convert object o to bool value. +func ToBool(o Object) (v bool, ok bool) { + ok = true + v = !o.IsFalsy() + + return +} + +// ToRune will try to convert object o to rune value. +func ToRune(o Object) (v rune, ok bool) { + switch o := o.(type) { + case *Int: + v = rune(o.Value) + ok = true + 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 +} diff --git a/objects/errors.go b/objects/errors.go index 78d6a40..a0f9038 100644 --- a/objects/errors.go +++ b/objects/errors.go @@ -7,3 +7,6 @@ var ErrInvalidOperator = errors.New("invalid operator") // ErrWrongNumArguments represents a wrong number of arguments error. var ErrWrongNumArguments = errors.New("wrong number of arguments") + +// ErrInvalidTypeConversion represents an invalid type conversion error. +var ErrInvalidTypeConversion = errors.New("invalid type conversion") From a14f6ec1c0f560c394a6ac169f858a17d1d27449 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Thu, 17 Jan 2019 16:52:30 -0800 Subject: [PATCH 04/12] incomplete implementation of 'os' module --- compiler/stdmods/errors.go | 11 ++ compiler/stdmods/func_typedefs.go | 224 +++++++++++++++--------------- compiler/stdmods/os.go | 103 ++++++++++++++ compiler/stdmods/stdmods.go | 1 + 4 files changed, 228 insertions(+), 111 deletions(-) create mode 100644 compiler/stdmods/errors.go create mode 100644 compiler/stdmods/os.go diff --git a/compiler/stdmods/errors.go b/compiler/stdmods/errors.go new file mode 100644 index 0000000..3c265eb --- /dev/null +++ b/compiler/stdmods/errors.go @@ -0,0 +1,11 @@ +package stdmods + +import "github.com/d5/tengo/objects" + +func wrapError(err error) objects.Object { + if err == nil { + return objects.TrueValue + } + + return &objects.Error{Value: &objects.String{Value: err.Error()}} +} diff --git a/compiler/stdmods/func_typedefs.go b/compiler/stdmods/func_typedefs.go index cf71edd..7db07f8 100644 --- a/compiler/stdmods/func_typedefs.go +++ b/compiler/stdmods/func_typedefs.go @@ -1,28 +1,21 @@ package stdmods import ( - "fmt" - "github.com/d5/tengo/objects" ) -// FuncAFRF transform a function of 'func(float64) float64' signature +// FuncAR transform a function of 'func()' signature // into a user function object. -func FuncAFRF(fn func(float64) float64) *objects.UserFunction { +func FuncAR(fn func()) *objects.UserFunction { return &objects.UserFunction{ Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { + if len(args) != 0 { return nil, objects.ErrWrongNumArguments } - switch arg := args[0].(type) { - case *objects.Int: - return &objects.Float{Value: fn(float64(arg.Value))}, nil - case *objects.Float: - return &objects.Float{Value: fn(arg.Value)}, nil - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } + fn() + + return objects.UndefinedValue, nil }, } } @@ -41,6 +34,44 @@ func FuncARF(fn func() float64) *objects.UserFunction { } } +// FuncARSs transform a function of 'func() []string' signature +// into a user function object. +func FuncARSs(fn func() []string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + arr := &objects.Array{} + for _, osArg := range fn() { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil + }, + } +} + +// FuncAFRF transform a function of 'func(float64) float64' signature +// into a user function object. +func FuncAFRF(fn func(float64) float64) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.Float{Value: fn(f1)}, nil + }, + } +} + // FuncAIRF transform a function of 'func(int) float64' signature // into a user function object. func FuncAIRF(fn func(int) float64) *objects.UserFunction { @@ -50,14 +81,12 @@ func FuncAIRF(fn func(int) float64) *objects.UserFunction { return nil, objects.ErrWrongNumArguments } - switch arg := args[0].(type) { - case *objects.Int: - return &objects.Float{Value: fn(int(arg.Value))}, nil - case *objects.Float: - return &objects.Float{Value: fn(int(arg.Value))}, nil - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion } + + return &objects.Float{Value: fn(i1)}, nil }, } } @@ -71,14 +100,12 @@ func FuncAFRI(fn func(float64) int) *objects.UserFunction { return nil, objects.ErrWrongNumArguments } - switch arg := args[0].(type) { - case *objects.Int: - return &objects.Int{Value: int64(fn(float64(arg.Value)))}, nil - case *objects.Float: - return &objects.Int{Value: int64(fn(arg.Value))}, nil - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion } + + return &objects.Int{Value: int64(fn(f1))}, nil }, } } @@ -92,26 +119,17 @@ func FuncAFFRF(fn func(float64, float64) float64) *objects.UserFunction { return nil, objects.ErrWrongNumArguments } - var arg0, arg1 float64 - - switch arg := args[0].(type) { - case *objects.Int: - arg0 = float64(arg.Value) - case *objects.Float: - arg0 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - switch arg := args[1].(type) { - case *objects.Int: - arg1 = float64(arg.Value) - case *objects.Float: - arg1 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion } - return &objects.Float{Value: fn(arg0, arg1)}, nil + f2, ok := objects.ToFloat64(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.Float{Value: fn(f1, f2)}, nil }, } } @@ -125,27 +143,17 @@ func FuncAIFRF(fn func(int, float64) float64) *objects.UserFunction { return nil, objects.ErrWrongNumArguments } - var arg0 int - var arg1 float64 - - switch arg := args[0].(type) { - case *objects.Int: - arg0 = int(arg.Value) - case *objects.Float: - arg0 = int(arg.Value) - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - switch arg := args[1].(type) { - case *objects.Int: - arg1 = float64(arg.Value) - case *objects.Float: - arg1 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion } - return &objects.Float{Value: fn(arg0, arg1)}, nil + f2, ok := objects.ToFloat64(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.Float{Value: fn(i1, f2)}, nil }, } } @@ -159,27 +167,17 @@ func FuncAFIRF(fn func(float64, int) float64) *objects.UserFunction { return nil, objects.ErrWrongNumArguments } - var arg0 float64 - var arg1 int - - switch arg := args[0].(type) { - case *objects.Int: - arg0 = float64(arg.Value) - case *objects.Float: - arg0 = float64(arg.Value) - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - switch arg := args[1].(type) { - case *objects.Int: - arg1 = int(arg.Value) - case *objects.Float: - arg1 = int(arg.Value) - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion } - return &objects.Float{Value: fn(arg0, arg1)}, nil + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.Float{Value: fn(f1, i2)}, nil }, } } @@ -193,27 +191,17 @@ func FuncAFIRB(fn func(float64, int) bool) *objects.UserFunction { return nil, objects.ErrWrongNumArguments } - var arg0 float64 - var arg1 int - - switch arg := args[0].(type) { - case *objects.Int: - arg0 = float64(arg.Value) - case *objects.Float: - arg0 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - switch arg := args[1].(type) { - case *objects.Int: - arg1 = int(arg.Value) - case *objects.Float: - arg1 = int(arg.Value) - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion } - return &objects.Bool{Value: fn(arg0, arg1)}, nil + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.Bool{Value: fn(f1, i2)}, nil }, } } @@ -227,17 +215,31 @@ func FuncAFRB(fn func(float64) bool) *objects.UserFunction { return nil, objects.ErrWrongNumArguments } - var arg0 float64 - switch arg := args[0].(type) { - case *objects.Int: - arg0 = float64(arg.Value) - case *objects.Float: - arg0 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion } - return &objects.Bool{Value: fn(arg0)}, nil + return &objects.Bool{Value: fn(f1)}, nil + }, + } +} + +// FuncASRE transform a function of 'func(string) error' signature into a user function object. +// User function will return 'true' if underlying native function returns nil. +func FuncASRE(fn func(string) error) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(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 + } + + return wrapError(fn(s1)), nil }, } } diff --git a/compiler/stdmods/os.go b/compiler/stdmods/os.go new file mode 100644 index 0000000..c04fa94 --- /dev/null +++ b/compiler/stdmods/os.go @@ -0,0 +1,103 @@ +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": &objects.UserFunction{Value: osChmod}, + "chown": &objects.UserFunction{Value: osChown}, + "clearenv": FuncAR(os.Clearenv), + "environ": FuncARSs(os.Environ), + + // TODO: system errors + //"err_invalid": &objects.Error{Value: os.ErrInvalid.Error()}, + // TODO: STDIN, STDOUT, STDERR + // "stdin": nil, + // "stdout": nil, + // "stderr": 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 osChmod(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(os.Chmod(s1, os.FileMode(i2))), nil +} + +func osChown(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 + } + + return wrapError(os.Chown(s1, i2, i3)), nil +} diff --git a/compiler/stdmods/stdmods.go b/compiler/stdmods/stdmods.go index 9cf5963..c32f02a 100644 --- a/compiler/stdmods/stdmods.go +++ b/compiler/stdmods/stdmods.go @@ -5,4 +5,5 @@ import "github.com/d5/tengo/objects" // Modules contain the standard modules. var Modules = map[string]*objects.ModuleMap{ "math": {Value: mathModule}, + "os": {Value: osModule}, } From 44c55ea296114825a196e8c99e3623a74c363dd3 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Thu, 17 Jan 2019 21:23:20 -0800 Subject: [PATCH 05/12] add some more os module functions, add more built-int functions --- assert/assert.go | 4 + compiler/stdmods/func_typedefs.go | 212 +++++++++++++++++++++++++ compiler/stdmods/func_typedefs_test.go | 175 ++++++++++++++++++-- compiler/stdmods/os.go | 127 ++++++++++----- objects/builtin_is_error.go | 18 --- objects/builtin_is_undefined.go | 18 --- objects/builtin_type_checks.go | 85 ++++++++++ objects/builtins.go | 20 +++ 8 files changed, 568 insertions(+), 91 deletions(-) delete mode 100644 objects/builtin_is_error.go delete mode 100644 objects/builtin_is_undefined.go create mode 100644 objects/builtin_type_checks.go diff --git a/assert/assert.go b/assert/assert.go index 35b7117..6488a87 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -175,6 +175,10 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool return true case *objects.Error: return Equal(t, expected.Value, actual.(*objects.Error).Value) + case error: + if expected != actual.(error) { + return failExpectedActual(t, expected, actual, msg...) + } default: panic(fmt.Errorf("type not implemented: %T", expected)) } diff --git a/compiler/stdmods/func_typedefs.go b/compiler/stdmods/func_typedefs.go index 7db07f8..5220cd8 100644 --- a/compiler/stdmods/func_typedefs.go +++ b/compiler/stdmods/func_typedefs.go @@ -20,6 +20,53 @@ func FuncAR(fn func()) *objects.UserFunction { } } +// FuncARI transform a function of 'func() int' signature +// into a user function object. +func FuncARI(fn func() int) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Int{Value: int64(fn())}, nil + }, + } +} + +// FuncARS transform a function of 'func() string' signature +// into a user function object. +func FuncARS(fn func() string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.String{Value: fn()}, nil + }, + } +} + +// FuncARSE transform a function of 'func() (string, error)' signature +// into a user function object. +func FuncARSE(fn func() (string, error)) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + return &objects.String{Value: res}, nil + }, + } +} + // FuncARF transform a function of 'func() float64' signature // into a user function object. func FuncARF(fn func() float64) *objects.UserFunction { @@ -53,6 +100,30 @@ func FuncARSs(fn func() []string) *objects.UserFunction { } } +// FuncARIsE transform a function of 'func() ([]int, error)' signature +// into a user function object. +func FuncARIsE(fn func() ([]int, error)) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + arr := &objects.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + } + + return arr, nil + }, + } +} + // FuncAFRF transform a function of 'func(float64) float64' signature // into a user function object. func FuncAFRF(fn func(float64) float64) *objects.UserFunction { @@ -72,6 +143,27 @@ func FuncAFRF(fn func(float64) float64) *objects.UserFunction { } } +// FuncAIR transform a function of 'func(int)' signature +// into a user function object. +func FuncAIR(fn func(int)) *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 + } + + fn(i1) + + return objects.UndefinedValue, nil + }, + } +} + // FuncAIRF transform a function of 'func(int) float64' signature // into a user function object. func FuncAIRF(fn func(int) float64) *objects.UserFunction { @@ -225,6 +317,49 @@ func FuncAFRB(fn func(float64) bool) *objects.UserFunction { } } +// FuncASRS transform a function of 'func(string) string' signature into a user function object. +// User function will return 'true' if underlying native function returns nil. +func FuncASRS(fn func(string) string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(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 + } + + return &objects.String{Value: fn(s1)}, nil + }, + } +} + +// FuncASRSE transform a function of 'func(string) (string, error)' signature into a user function object. +// User function will return 'true' if underlying native function returns nil. +func FuncASRSE(fn func(string) (string, error)) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(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 := fn(s1) + if err != nil { + return wrapError(err), nil + } + + return &objects.String{Value: res}, nil + }, + } +} + // FuncASRE transform a function of 'func(string) error' signature into a user function object. // User function will return 'true' if underlying native function returns nil. func FuncASRE(fn func(string) error) *objects.UserFunction { @@ -243,3 +378,80 @@ func FuncASRE(fn func(string) error) *objects.UserFunction { }, } } + +// FuncASSRE transform a function of 'func(string, string) error' signature into a user function object. +// User function will return 'true' if underlying native function returns nil. +func FuncASSRE(fn func(string, string) 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 + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return wrapError(fn(s1, s2)), nil + }, + } +} + +// FuncASI64RE transform a function of 'func(string, int64) error' signature +// into a user function object. +func FuncASI64RE(fn func(string, int64) error) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err 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, 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 { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err 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 + } + + return wrapError(fn(s1, i2, i3)), nil + }, + } +} diff --git a/compiler/stdmods/func_typedefs_test.go b/compiler/stdmods/func_typedefs_test.go index c06b930..f020116 100644 --- a/compiler/stdmods/func_typedefs_test.go +++ b/compiler/stdmods/func_typedefs_test.go @@ -1,6 +1,7 @@ package stdmods_test import ( + "errors" "testing" "github.com/d5/tengo/assert" @@ -8,6 +9,142 @@ import ( "github.com/d5/tengo/objects" ) +func TestFuncAIR(t *testing.T) { + uf := stdmods.FuncAIR(func(int) {}) + ret, err := uf.Call(&objects.Int{Value: 10}) + assert.NoError(t, err) + assert.Equal(t, &objects.Undefined{}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncAR(t *testing.T) { + uf := stdmods.FuncAR(func() {}) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.Undefined{}, ret) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncARI(t *testing.T) { + uf := stdmods.FuncARI(func() int { return 10 }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.Int{Value: 10}, 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 }) + 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") }) + 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 TestFuncARS(t *testing.T) { + uf := stdmods.FuncARS(func() string { return "foo" }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.String{Value: "foo"}, ret) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncARSE(t *testing.T) { + uf := stdmods.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") }) + 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 TestFuncARSs(t *testing.T) { + uf := stdmods.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) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncASRE(t *testing.T) { + uf := stdmods.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") }) + ret, err = uf.Call(&objects.String{Value: "foo"}) + 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 TestFuncASRS(t *testing.T) { + uf := stdmods.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) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncASI64RE(t *testing.T) { + uf := stdmods.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") }) + 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) + 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 }) + 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") }) + 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) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncASRSE(t *testing.T) { + uf := stdmods.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") }) + ret, err = uf.Call(&objects.String{Value: "foo"}) + 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 TestFuncASSRE(t *testing.T) { + +} + func TestFuncARF(t *testing.T) { uf := stdmods.FuncARF(func() float64 { return 10.0 @@ -16,7 +153,7 @@ func TestFuncARF(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 10.0}, ret) ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAFRF(t *testing.T) { @@ -27,9 +164,9 @@ func TestFuncAFRF(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 10.0}, ret) ret, err = uf.Call() - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue, objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAIRF(t *testing.T) { @@ -40,9 +177,9 @@ func TestFuncAIRF(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 10.0}, ret) ret, err = uf.Call() - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue, objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAFRI(t *testing.T) { @@ -53,9 +190,9 @@ func TestFuncAFRI(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 10}, ret) ret, err = uf.Call() - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue, objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAFRB(t *testing.T) { @@ -66,9 +203,9 @@ func TestFuncAFRB(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Bool{Value: true}, ret) ret, err = uf.Call() - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue, objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAFFRF(t *testing.T) { @@ -79,9 +216,9 @@ func TestFuncAFFRF(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 30.0}, ret) ret, err = uf.Call() - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAIFRF(t *testing.T) { @@ -92,9 +229,9 @@ func TestFuncAIFRF(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 30.0}, ret) ret, err = uf.Call() - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAFIRF(t *testing.T) { @@ -105,9 +242,9 @@ func TestFuncAFIRF(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 30.0}, ret) ret, err = uf.Call() - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAFIRB(t *testing.T) { @@ -118,7 +255,11 @@ func TestFuncAFIRB(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &objects.Bool{Value: true}, ret) ret, err = uf.Call() - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func array(elements ...objects.Object) *objects.Array { + return &objects.Array{Value: elements} } diff --git a/compiler/stdmods/os.go b/compiler/stdmods/os.go index c04fa94..5dfe574 100644 --- a/compiler/stdmods/os.go +++ b/compiler/stdmods/os.go @@ -36,19 +36,56 @@ var osModule = map[string]objects.Object{ "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": &objects.UserFunction{Value: osChmod}, - "chown": &objects.UserFunction{Value: osChown}, - "clearenv": FuncAR(os.Clearenv), - "environ": FuncARSs(os.Environ), + "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: system errors - //"err_invalid": &objects.Error{Value: os.ErrInvalid.Error()}, - // TODO: STDIN, STDOUT, STDERR - // "stdin": nil, - // "stdout": nil, - // "stderr": nil, + // 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) { @@ -64,8 +101,42 @@ func osArgs(args ...objects.Object) (objects.Object, error) { return arr, nil } -func osChmod(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { +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 } @@ -73,31 +144,11 @@ func osChmod(args ...objects.Object) (objects.Object, error) { if !ok { return nil, objects.ErrInvalidTypeConversion } - i2, ok := objects.ToInt(args[1]) + + res, ok := os.LookupEnv(s1) if !ok { - return nil, objects.ErrInvalidTypeConversion + return objects.FalseValue, nil } - return wrapError(os.Chmod(s1, os.FileMode(i2))), nil -} - -func osChown(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 - } - - return wrapError(os.Chown(s1, i2, i3)), nil + return &objects.String{Value: res}, nil } diff --git a/objects/builtin_is_error.go b/objects/builtin_is_error.go deleted file mode 100644 index ffa3f2f..0000000 --- a/objects/builtin_is_error.go +++ /dev/null @@ -1,18 +0,0 @@ -package objects - -import ( - "fmt" -) - -func builtinIsError(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, fmt.Errorf("wrong number of arguments (got=%d, want=1)", len(args)) - } - - switch args[0].(type) { - case *Error: - return TrueValue, nil - } - - return FalseValue, nil -} diff --git a/objects/builtin_is_undefined.go b/objects/builtin_is_undefined.go deleted file mode 100644 index 80c0c9f..0000000 --- a/objects/builtin_is_undefined.go +++ /dev/null @@ -1,18 +0,0 @@ -package objects - -import ( - "fmt" -) - -func builtinIsUndefined(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, fmt.Errorf("wrong number of arguments (got=%d, want=1)", len(args)) - } - - switch args[0].(type) { - case *Undefined: - return TrueValue, nil - } - - return FalseValue, nil -} diff --git a/objects/builtin_type_checks.go b/objects/builtin_type_checks.go new file mode 100644 index 0000000..99ee4ba --- /dev/null +++ b/objects/builtin_type_checks.go @@ -0,0 +1,85 @@ +package objects + +func builtinIsString(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*String); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsInt(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Int); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsFloat(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Float); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsBool(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Bool); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsChar(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Char); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsError(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Error); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsUndefined(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Undefined); ok { + return TrueValue, nil + } + + return FalseValue, nil +} diff --git a/objects/builtins.go b/objects/builtins.go index 8ec3b7f..41c664d 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -41,6 +41,26 @@ var Builtins = []struct { Name: "char", Func: builtinChar, }, + { + Name: "is_int", + Func: builtinIsInt, + }, + { + Name: "is_float", + Func: builtinIsFloat, + }, + { + Name: "is_string", + Func: builtinIsString, + }, + { + Name: "is_bool", + Func: builtinIsBool, + }, + { + Name: "is_char", + Func: builtinIsChar, + }, { Name: "is_error", Func: builtinIsError, From 8cad04841ea13e2c348648b7fee6068a7d49feca Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 18 Jan 2019 01:43:46 -0800 Subject: [PATCH 06/12] 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) } } From 9b1a0a36bffe06c45c64ae859a5d7e0024dfce45 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 18 Jan 2019 01:57:14 -0800 Subject: [PATCH 07/12] add more comments --- compiler/stdlib/os.go | 116 ++++++++++++++++++++++++------------- compiler/stdlib/os_file.go | 5 +- objects/immutable_map.go | 2 +- 3 files changed, 82 insertions(+), 41 deletions(-) diff --git a/compiler/stdlib/os.go b/compiler/stdlib/os.go index a855ca1..06aa1d9 100644 --- a/compiler/stdlib/os.go +++ b/compiler/stdlib/os.go @@ -33,46 +33,84 @@ var osModule = map[string]objects.Object{ "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}, + // args() => array(string) + "args": &objects.UserFunction{Value: osArgs}, + // chdir(dir string) => error + "chdir": FuncASRE(os.Chdir), + // chmod(name string, mode int) => error + "chmod": osFuncASFmRE(os.Chmod), + // chown(name string, uid int, gid int) => error + "chown": FuncASIIRE(os.Chown), + // clearenv() + "clearenv": FuncAR(os.Clearenv), + // environ() => array(string) + "environ": FuncARSs(os.Environ), + // executable() => string/error + "executable": &objects.UserFunction{Value: osExecutable}, + // exit(code int) + "exit": FuncAIR(os.Exit), + // expand_env(s string) => string + "expand_env": FuncASRS(os.ExpandEnv), + // getegid() => int + "getegid": FuncARI(os.Getegid), + // getenv(s string) => string + "getenv": FuncASRS(os.Getenv), + // geteuid() => int + "geteuid": FuncARI(os.Geteuid), + // getgid() => int + "getgid": FuncARI(os.Getgid), + // getgroups() => array(string)/error + "getgroups": FuncARIsE(os.Getgroups), + // getpagesize() => int + "getpagesize": FuncARI(os.Getpagesize), + // getpid() => int + "getpid": FuncARI(os.Getpid), + // getppid() => int + "getppid": FuncARI(os.Getppid), + // getuid() => int + "getuid": FuncARI(os.Getuid), + // getwd() => string/error + "getwd": FuncARSE(os.Getwd), + // hostname() => string/error + "hostname": FuncARSE(os.Hostname), + // lchown(name string, uid int, gid int) => error + "lchown": FuncASIIRE(os.Lchown), + // link(oldname string, newname string) => error + "link": FuncASSRE(os.Link), + // lookup_env(key string) => string/false + "lookup_env": &objects.UserFunction{Value: osLookupEnv}, + // mkdir(name string, perm int) => error + "mkdir": osFuncASFmRE(os.Mkdir), + // mkdir_all(name string, perm int) => error + "mkdir_all": osFuncASFmRE(os.MkdirAll), + // readlink(name string) => string/error + "readlink": FuncASRSE(os.Readlink), + // remove(name string) => error + "remove": FuncASRE(os.Remove), + // remove_all(name string) => error + "remove_all": FuncASRE(os.RemoveAll), + // rename(oldpath string, newpath string) => error + "rename": FuncASSRE(os.Rename), + // setenv(key string, value string) => error + "setenv": FuncASSRE(os.Setenv), + // symlink(oldname string newname string) => error + "symlink": FuncASSRE(os.Symlink), + // temp_dir() => string + "temp_dir": FuncARS(os.TempDir), + // truncate(name string, size int) => error + "truncate": FuncASI64RE(os.Truncate), + // unsetenv(key string) => error + "unsetenv": FuncASRE(os.Unsetenv), + // user_cache_dir() => string/error + "user_cache_dir": FuncARSE(os.UserCacheDir), + // create(name string) => imap(file)/error + "create": &objects.UserFunction{Value: osCreate}, + // open(name string) => imap(file)/error + "open": &objects.UserFunction{Value: osOpen}, + // open_file(name string, flag int, perm int) => imap(file)/error + "open_file": &objects.UserFunction{Value: osOpenFile}, - // TODO: not implemented yet + // TODO: implemented more functions //"stdin": nil, //"stdout": nil, //"stderr": nil, diff --git a/compiler/stdlib/os_file.go b/compiler/stdlib/os_file.go index 845541a..5c0007b 100644 --- a/compiler/stdlib/os_file.go +++ b/compiler/stdlib/os_file.go @@ -17,7 +17,7 @@ func osFileImmutableMap(file *os.File) *objects.ImmutableMap { "close": FuncARE(file.Close), // name() => string "name": FuncARS(file.Name), - // readdirnames(n int) => array/error + // readdirnames(n int) => array(string)/error "readdirnames": FuncAIRSsE(file.Readdirnames), // sync() => error "sync": FuncARE(file.Sync), @@ -27,6 +27,7 @@ func osFileImmutableMap(file *os.File) *objects.ImmutableMap { "write_string": FuncASRIE(file.WriteString), // read(bytes) => int/error "read": FuncAYRIE(file.Read), + // chmod(mode int) => error "chmod": &objects.UserFunction{ Value: func(args ...objects.Object) (ret objects.Object, err error) { if len(args) != 1 { @@ -41,6 +42,7 @@ func osFileImmutableMap(file *os.File) *objects.ImmutableMap { return wrapError(file.Chmod(os.FileMode(i1))), nil }, }, + // seek(offset int, whence int) => int/error "seek": &objects.UserFunction{ Value: func(args ...objects.Object) (ret objects.Object, err error) { if len(args) != 2 { @@ -64,6 +66,7 @@ func osFileImmutableMap(file *os.File) *objects.ImmutableMap { return &objects.Int{Value: res}, nil }, }, + // TODO: implement more functions //"fd": nil, //"read_at": nil, //"readdir": nil, diff --git a/objects/immutable_map.go b/objects/immutable_map.go index dcf5333..4e29281 100644 --- a/objects/immutable_map.go +++ b/objects/immutable_map.go @@ -7,7 +7,7 @@ import ( "github.com/d5/tengo/compiler/token" ) -// ImmutableMap represents a module map object. +// ImmutableMap represents an immutable map object. type ImmutableMap struct { Value map[string]Object } From b5108b8065f4eeb9461e2dbc9ddcab8ccc01f0e9 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 18 Jan 2019 02:28:58 -0800 Subject: [PATCH 08/12] os Process functions --- compiler/stdlib/func_typedefs.go | 14 ++++ compiler/stdlib/os.go | 7 ++ compiler/stdlib/os_process.go | 129 +++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 compiler/stdlib/os_process.go diff --git a/compiler/stdlib/func_typedefs.go b/compiler/stdlib/func_typedefs.go index 83c540f..022c77f 100644 --- a/compiler/stdlib/func_typedefs.go +++ b/compiler/stdlib/func_typedefs.go @@ -34,6 +34,20 @@ func FuncARI(fn func() int) *objects.UserFunction { } } +// FuncARB transform a function of 'func() bool' signature +// into a user function object. +func FuncARB(fn func() bool) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Bool{Value: fn()}, nil + }, + } +} + // FuncARE transform a function of 'func() error' signature // into a user function object. func FuncARE(fn func() error) *objects.UserFunction { diff --git a/compiler/stdlib/os.go b/compiler/stdlib/os.go index 06aa1d9..660594d 100644 --- a/compiler/stdlib/os.go +++ b/compiler/stdlib/os.go @@ -33,6 +33,9 @@ var osModule = map[string]objects.Object{ "path_separator": &objects.Char{Value: os.PathSeparator}, "path_list_separator": &objects.Char{Value: os.PathListSeparator}, "dev_null": &objects.String{Value: os.DevNull}, + "seek_set": &objects.Int{Value: int64(os.SEEK_SET)}, + "seek_cur": &objects.Int{Value: int64(os.SEEK_CUR)}, + "seek_end": &objects.Int{Value: int64(os.SEEK_END)}, // args() => array(string) "args": &objects.UserFunction{Value: osArgs}, // chdir(dir string) => error @@ -109,6 +112,10 @@ var osModule = map[string]objects.Object{ "open": &objects.UserFunction{Value: osOpen}, // open_file(name string, flag int, perm int) => imap(file)/error "open_file": &objects.UserFunction{Value: osOpenFile}, + // find_process(pid int) => imap(process)/error + "find_process": &objects.UserFunction{Value: osFindProcess}, + // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error + "start_process": &objects.UserFunction{Value: osStartProcess}, // TODO: implemented more functions //"stdin": nil, diff --git a/compiler/stdlib/os_process.go b/compiler/stdlib/os_process.go new file mode 100644 index 0000000..45872d7 --- /dev/null +++ b/compiler/stdlib/os_process.go @@ -0,0 +1,129 @@ +package stdlib + +import ( + "os" + "syscall" + + "github.com/d5/tengo/objects" +) + +func osProcessStateImmutableMap(state *os.ProcessState) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "exited": FuncARB(state.Exited), + "pid": FuncARI(state.Pid), + "string": FuncARS(state.String), + "success": FuncARB(state.Success), + }, + } +} + +func osProcessImmutableMap(proc *os.Process) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "kill": FuncARE(proc.Kill), + "release": FuncARE(proc.Release), + "signal": &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(proc.Signal(syscall.Signal(i1))), nil + }, + }, + "wait": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + state, err := proc.Wait() + if err != nil { + return wrapError(err), nil + } + + return osProcessStateImmutableMap(state), nil + }, + }, + }, + } +} + +func osFindProcess(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + proc, err := os.FindProcess(i1) + if err != nil { + return wrapError(err), nil + } + + return osProcessImmutableMap(proc), nil +} + +func osStartProcess(args ...objects.Object) (objects.Object, error) { + if len(args) != 4 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + argv, err := stringArray(args[1]) + if err != nil { + return nil, err + } + + dir, ok := objects.ToString(args[2]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + env, err := stringArray(args[3]) + if err != nil { + return nil, err + } + + proc, err := os.StartProcess(name, argv, &os.ProcAttr{ + Dir: dir, + Env: env, + }) + if err != nil { + return wrapError(err), nil + } + + return osProcessImmutableMap(proc), nil +} + +func stringArray(o objects.Object) ([]string, error) { + arr, ok := o.(*objects.Array) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + var sarr []string + for _, elem := range arr.Value { + str, ok := elem.(*objects.String) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + sarr = append(sarr, str.Value) + } + + return sarr, nil +} From bff59006d18a1e6a7e661ae76e9c7c94dd286e57 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 18 Jan 2019 02:33:23 -0800 Subject: [PATCH 09/12] add Variable.Error() function --- script/variable.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/script/variable.go b/script/variable.go index bf9189b..bec167a 100644 --- a/script/variable.go +++ b/script/variable.go @@ -1,6 +1,8 @@ package script import ( + "errors" + "github.com/d5/tengo/objects" ) @@ -124,6 +126,17 @@ func (v *Variable) Bytes() []byte { return c } +// Error returns an error if the underlying value is error object. +// If not, this returns nil. +func (v *Variable) Error() error { + err, ok := (*v.value).(*objects.Error) + if ok { + return errors.New(err.String()) + } + + return nil +} + // Object returns an underlying Object of the variable value. // Note that returned Object is a copy of an actual Object used in the script. func (v *Variable) Object() objects.Object { From a8d838ad3e584499d6da9ce5db31a19badc00d28 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 18 Jan 2019 09:19:45 -0800 Subject: [PATCH 10/12] Compile can take custom stdlibs --- cmd/bench/main.go | 2 +- cmd/tengo/main.go | 8 ++++++-- compiler/compiler.go | 25 ++++++++++++++++++++----- compiler/compiler_test.go | 5 ++++- objects/builtins.go | 9 ++++++--- runtime/vm_test.go | 5 ++++- script/script.go | 6 +++++- 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/cmd/bench/main.go b/cmd/bench/main.go index e275608..17a8093 100644 --- a/cmd/bench/main.go +++ b/cmd/bench/main.go @@ -193,7 +193,7 @@ func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) { start := time.Now() - c := compiler.NewCompiler(symTable, nil) + c := compiler.NewCompiler(symTable, nil, nil) if err := c.Compile(file); err != nil { return time.Since(start), nil, err } diff --git a/cmd/tengo/main.go b/cmd/tengo/main.go index 777321c..5ebb054 100644 --- a/cmd/tengo/main.go +++ b/cmd/tengo/main.go @@ -171,7 +171,11 @@ func doRepl(in io.Reader, out io.Writer) { fileSet := source.NewFileSet() globals := make([]*objects.Object, runtime.GlobalsSize) + symbolTable := compiler.NewSymbolTable() + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) + } for { _, _ = fmt.Fprintf(out, replPrompt) @@ -191,7 +195,7 @@ func doRepl(in io.Reader, out io.Writer) { file = addPrints(file) - c := compiler.NewCompiler(symbolTable, nil) + c := compiler.NewCompiler(symbolTable, nil, nil) if err := c.Compile(file); err != nil { _, _ = fmt.Fprintf(out, "Compilation error:\n %s\n", err.Error()) continue @@ -218,7 +222,7 @@ func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { return nil, err } - c := compiler.NewCompiler(nil, nil) + c := compiler.NewCompiler(nil, nil, nil) if err := c.Compile(file); err != nil { return nil, err } diff --git a/compiler/compiler.go b/compiler/compiler.go index ba5a26b..1b3b331 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -20,6 +20,7 @@ type Compiler struct { scopes []CompilationScope scopeIndex int moduleLoader ModuleLoader + stdModules map[string]*objects.ImmutableMap compiledModules map[string]*objects.CompiledModule loops []*Loop loopIndex int @@ -28,17 +29,28 @@ type Compiler struct { } // NewCompiler creates a Compiler. -func NewCompiler(symbolTable *SymbolTable, trace io.Writer) *Compiler { +// User can optionally provide the symbol table if one wants to add or remove +// some global- or builtin- scope symbols. If not (nil), Compile will create +// a new symbol table and use the default builtin functions. Likewise, standard +// modules can be explicitly provided if user wants to add or remove some modules. +// By default, Compile will use all the standard modules otherwise. +func NewCompiler(symbolTable *SymbolTable, stdModules map[string]*objects.ImmutableMap, trace io.Writer) *Compiler { mainScope := CompilationScope{ instructions: make([]byte, 0), } + // symbol table if symbolTable == nil { symbolTable = NewSymbolTable() + + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) + } } - for idx, fn := range objects.Builtins { - symbolTable.DefineBuiltin(idx, fn.Name) + // standard modules + if stdModules == nil { + stdModules = stdlib.Modules } return &Compiler{ @@ -47,6 +59,7 @@ func NewCompiler(symbolTable *SymbolTable, trace io.Writer) *Compiler { scopeIndex: 0, loopIndex: -1, trace: trace, + stdModules: stdModules, compiledModules: make(map[string]*objects.CompiledModule), } } @@ -440,7 +453,7 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(OpCall, len(node.Args)) case *ast.ImportExpr: - stdMod, ok := stdlib.Modules[node.ModuleName] + stdMod, ok := c.stdModules[node.ModuleName] if ok { // standard modules contain only globals with no code. // so no need to compile anything @@ -474,12 +487,14 @@ func (c *Compiler) Bytecode() *Bytecode { } // SetModuleLoader sets or replaces the current module loader. +// Note that the module loader is used for user modules, +// not for the standard modules. func (c *Compiler) SetModuleLoader(moduleLoader ModuleLoader) { c.moduleLoader = moduleLoader } func (c *Compiler) fork(moduleName string, symbolTable *SymbolTable) *Compiler { - child := NewCompiler(symbolTable, c.trace) + child := NewCompiler(symbolTable, c.stdModules, c.trace) child.moduleName = moduleName // name of the module to compile child.parent = c // parent to set to current compiler child.moduleLoader = c.moduleLoader // share module loader diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index c63b053..5dfefe5 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -972,9 +972,12 @@ func traceCompile(input string, symbols map[string]objects.Object) (res *compile for name := range symbols { symTable.Define(name) } + for idx, fn := range objects.Builtins { + symTable.DefineBuiltin(idx, fn.Name) + } tr := &tracer{} - c := compiler.NewCompiler(symTable, tr) + c := compiler.NewCompiler(symTable, nil, tr) parsed, err := p.ParseFile() if err != nil { return diff --git a/objects/builtins.go b/objects/builtins.go index acf2419..fe7085e 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -1,10 +1,13 @@ package objects -// Builtins contains all known builtin functions. -var Builtins = []struct { +// NamedBuiltinFunc is a named builtin function. +type NamedBuiltinFunc struct { Name string Func CallableFunc -}{ +} + +// Builtins contains all default builtin functions. +var Builtins = []NamedBuiltinFunc{ { Name: "print", Func: builtinPrint, diff --git a/runtime/vm_test.go b/runtime/vm_test.go index d14eaef..f3f0722 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -176,9 +176,12 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu sym := symTable.Define(name) globals[sym.Index] = &value } + for idx, fn := range objects.Builtins { + symTable.DefineBuiltin(idx, fn.Name) + } tr := &tracer{} - c := compiler.NewCompiler(symTable, tr) + c := compiler.NewCompiler(symTable, nil, tr) c.SetModuleLoader(func(moduleName string) ([]byte, error) { if src, ok := userModules[moduleName]; ok { return []byte(src), nil diff --git a/script/script.go b/script/script.go index 32933eb..418b3f3 100644 --- a/script/script.go +++ b/script/script.go @@ -63,7 +63,7 @@ func (s *Script) Compile() (*Compiled, error) { return nil, fmt.Errorf("parse error: %s", err.Error()) } - c := compiler.NewCompiler(symbolTable, nil) + c := compiler.NewCompiler(symbolTable, nil, nil) if err := c.Compile(file); err != nil { return nil, err } @@ -94,6 +94,10 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []*ob } symbolTable = compiler.NewSymbolTable() + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) + } + globals = make([]*objects.Object, len(names), len(names)) for idx, name := range names { From 5164d1de9f5d5f50fa013845e019630ccb0ab34e Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 18 Jan 2019 10:17:24 -0800 Subject: [PATCH 11/12] add 'exec' module --- compiler/stdlib/exec.go | 116 ++++++++++++++++++++++++++ compiler/stdlib/func_typedefs.go | 19 +++++ compiler/stdlib/func_typedefs_test.go | 69 +++++++++++++++ compiler/stdlib/stdlib.go | 1 + runtime/vm_module_test.go | 15 ++++ 5 files changed, 220 insertions(+) create mode 100644 compiler/stdlib/exec.go diff --git a/compiler/stdlib/exec.go b/compiler/stdlib/exec.go new file mode 100644 index 0000000..2577d10 --- /dev/null +++ b/compiler/stdlib/exec.go @@ -0,0 +1,116 @@ +package stdlib + +import ( + "os/exec" + + "github.com/d5/tengo/objects" +) + +var execModule = map[string]objects.Object{ + // look_path(file string) => string/error + "look_path": FuncASRSE(exec.LookPath), + // command(name string, args array(string)) => imap(cmd) + "command": &objects.UserFunction{Value: execCommand}, +} + +func execCmdImmutableMap(cmd *exec.Cmd) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // combined_output() => bytes/error + "combined_output": FuncARYE(cmd.CombinedOutput), + // output() => bytes/error + "output": FuncARYE(cmd.Output), + // run() => error + "run": FuncARE(cmd.Run), + // start() => error + "start": FuncARE(cmd.Start), + // wait() => error + "wait": FuncARE(cmd.Wait), + // set_path(path string) + "set_path": &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 + } + + cmd.Path = s1 + + return objects.UndefinedValue, nil + }, + }, + // set_dir(dir string) + "set_dir": &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 + } + + cmd.Dir = s1 + + return objects.UndefinedValue, nil + }, + }, + // set_env(env array(string)) + "set_env": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + envs, err := stringArray(args[0]) + if err != nil { + return nil, err + } + + cmd.Env = envs + + return objects.UndefinedValue, nil + }, + }, + // process() => imap(process) + "process": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return osProcessImmutableMap(cmd.Process), nil + }, + }, + // TODO: implement pipes + //"stderr_pipe": nil, + //"stdin_pipe": nil, + //"stdout_pipe": nil, + }, + } +} + +func execCommand(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + arg, err := stringArray(args[1]) + if err != nil { + return nil, err + } + + res := exec.Command(name, arg...) + + return execCmdImmutableMap(res), nil +} diff --git a/compiler/stdlib/func_typedefs.go b/compiler/stdlib/func_typedefs.go index 022c77f..ab07845 100644 --- a/compiler/stdlib/func_typedefs.go +++ b/compiler/stdlib/func_typedefs.go @@ -95,6 +95,25 @@ func FuncARSE(fn func() (string, error)) *objects.UserFunction { } } +// FuncARYE transform a function of 'func() ([]byte, error)' signature +// into a user function object. +func FuncARYE(fn func() ([]byte, error)) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + return &objects.Bytes{Value: res}, nil + }, + } +} + // FuncARF transform a function of 'func() float64' signature // into a user function object. func FuncARF(fn func() float64) *objects.UserFunction { diff --git a/compiler/stdlib/func_typedefs_test.go b/compiler/stdlib/func_typedefs_test.go index d6baed4..610e132 100644 --- a/compiler/stdlib/func_typedefs_test.go +++ b/compiler/stdlib/func_typedefs_test.go @@ -286,6 +286,75 @@ func TestFuncAFIRB(t *testing.T) { assert.Equal(t, objects.ErrWrongNumArguments, err) } +func TestFuncAIRSsE(t *testing.T) { + uf := stdlib.FuncAIRSsE(func(a int) ([]string, error) { + return []string{"foo", "bar"}, nil + }) + ret, err := uf.Call(&objects.Int{Value: 10}) + assert.NoError(t, err) + assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) + uf = stdlib.FuncAIRSsE(func(a int) ([]string, error) { + return nil, errors.New("some error") + }) + ret, err = uf.Call(&objects.Int{Value: 10}) + 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 TestFuncARB(t *testing.T) { + uf := stdlib.FuncARB(func() bool { return true }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, objects.TrueValue, ret) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncARYE(t *testing.T) { + uf := stdlib.FuncARYE(func() ([]byte, error) { + return []byte("foo bar"), nil + }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.Bytes{Value: []byte("foo bar")}, ret) + uf = stdlib.FuncARYE(func() ([]byte, 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) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncASRIE(t *testing.T) { + uf := stdlib.FuncASRIE(func(a string) (int, error) { return 5, nil }) + ret, err := uf.Call(&objects.String{Value: "foo"}) + assert.NoError(t, err) + assert.Equal(t, &objects.Int{Value: 5}, ret) + uf = stdlib.FuncASRIE(func(a string) (int, error) { return 0, 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) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncAYRIE(t *testing.T) { + uf := stdlib.FuncAYRIE(func(a []byte) (int, error) { return 5, nil }) + ret, err := uf.Call(&objects.Bytes{Value: []byte("foo")}) + assert.NoError(t, err) + assert.Equal(t, &objects.Int{Value: 5}, ret) + uf = stdlib.FuncAYRIE(func(a []byte) (int, error) { return 0, errors.New("some error") }) + ret, err = uf.Call(&objects.Bytes{Value: []byte("foo")}) + 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 array(elements ...objects.Object) *objects.Array { return &objects.Array{Value: elements} } diff --git a/compiler/stdlib/stdlib.go b/compiler/stdlib/stdlib.go index cd24166..3ca5dd3 100644 --- a/compiler/stdlib/stdlib.go +++ b/compiler/stdlib/stdlib.go @@ -6,4 +6,5 @@ import "github.com/d5/tengo/objects" var Modules = map[string]*objects.ImmutableMap{ "math": {Value: mathModule}, "os": {Value: osModule}, + "exec": {Value: execModule}, } diff --git a/runtime/vm_module_test.go b/runtime/vm_module_test.go index 9dc8d49..b63b8b2 100644 --- a/runtime/vm_module_test.go +++ b/runtime/vm_module_test.go @@ -45,6 +45,21 @@ if write_file("./temp", "foobar") { os.remove("./temp") `, "foobar") + // exec.command + expect(t, ` +exec := import("exec") + +echo := func(args) { + cmd := exec.command("echo", args) + if is_error(cmd) { return cmd.value } + output := cmd.output() + if is_error(output) { return output.value } + return output +} + +out = echo(["foo", "bar"]) +`, []byte("foo bar\n")) + // user modules expectWithUserModules(t, `out = import("mod1").bar()`, 5.0, map[string]string{ "mod1": `bar := func() { return 5.0 }`, From b3d48549617a0c005630dd988ceb220eeddc4018 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 18 Jan 2019 10:52:12 -0800 Subject: [PATCH 12/12] update README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 95096b6..677582b 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,13 @@ foo := 2 Basically `import` expression returns all the global variables defined in the module as a Map-like value. One can access the functions or variables defined in the module using `.` selector or `["key"]` indexer, but, module variables are immutable. +Also you can use `import` to load the [standard libraries](https://github.com/d5/tengo/wiki/Standard-Libraries). + +```golang +math := import("math") +a := math.abs(-19.84) // == 19.84 +``` + ## Embedding Tengo in Go