Merge pull request #58 from d5/stringstime

'text' and 'times' modules
This commit is contained in:
Daniel Kang 2019-01-29 23:50:21 -08:00 committed by GitHub
commit fdc52a0f83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 3146 additions and 670 deletions

View file

@ -71,7 +71,7 @@ _* See [here](https://github.com/d5/tengobench) for commands/codes used_
- [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md) - [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md)
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md) - [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
- [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) - [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md)
- [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) _(experimental)_ - [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md)
## Roadmap ## Roadmap

View file

@ -15,8 +15,6 @@ import (
// NoError asserts err is not an error. // NoError asserts err is not an error.
func NoError(t *testing.T, err error, msg ...interface{}) bool { func NoError(t *testing.T, err error, msg ...interface{}) bool {
t.Helper()
if err == nil { if err == nil {
return true return true
} }
@ -26,8 +24,6 @@ func NoError(t *testing.T, err error, msg ...interface{}) bool {
// Error asserts err is an error. // Error asserts err is an error.
func Error(t *testing.T, err error, msg ...interface{}) bool { func Error(t *testing.T, err error, msg ...interface{}) bool {
t.Helper()
if err != nil { if err != nil {
return true return true
} }
@ -37,8 +33,6 @@ func Error(t *testing.T, err error, msg ...interface{}) bool {
// Nil asserts v is nil. // Nil asserts v is nil.
func Nil(t *testing.T, v interface{}, msg ...interface{}) bool { func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
t.Helper()
if v == nil { if v == nil {
return true return true
} }
@ -48,8 +42,6 @@ func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
// True asserts v is true. // True asserts v is true.
func True(t *testing.T, v bool, msg ...interface{}) bool { func True(t *testing.T, v bool, msg ...interface{}) bool {
t.Helper()
if v { if v {
return true return true
} }
@ -59,8 +51,6 @@ func True(t *testing.T, v bool, msg ...interface{}) bool {
// False asserts vis false. // False asserts vis false.
func False(t *testing.T, v bool, msg ...interface{}) bool { func False(t *testing.T, v bool, msg ...interface{}) bool {
t.Helper()
if !v { if !v {
return true return true
} }
@ -70,8 +60,6 @@ func False(t *testing.T, v bool, msg ...interface{}) bool {
// NotNil asserts v is not nil. // NotNil asserts v is not nil.
func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool { func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
t.Helper()
if v != nil { if v != nil {
return true return true
} }
@ -81,8 +69,6 @@ func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
// IsType asserts expected and actual are of the same type. // IsType asserts expected and actual are of the same type.
func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
t.Helper()
if reflect.TypeOf(expected) == reflect.TypeOf(actual) { if reflect.TypeOf(expected) == reflect.TypeOf(actual) {
return true return true
} }
@ -92,15 +78,13 @@ func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
// Equal asserts expected and actual are equal. // Equal asserts expected and actual are equal.
func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
t.Helper()
if expected == nil { if expected == nil {
return Nil(t, actual, "expected nil, but got not nil") return Nil(t, actual, "expected nil, but got not nil")
} }
if !NotNil(t, actual, "expected not nil, but got nil") { if !NotNil(t, actual, "expected not nil, but got nil") {
return false return false
} }
if !IsType(t, expected, actual) { if !IsType(t, expected, actual, msg...) {
return false return false
} }
@ -123,7 +107,7 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
} }
case []byte: case []byte:
if bytes.Compare(expected, actual.([]byte)) != 0 { if bytes.Compare(expected, actual.([]byte)) != 0 {
return failExpectedActual(t, expected, actual, msg...) return failExpectedActual(t, string(expected), string(actual.([]byte)), msg...)
} }
case []int: case []int:
if !equalIntSlice(expected, actual.([]int)) { if !equalIntSlice(expected, actual.([]int)) {
@ -150,39 +134,47 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
return failExpectedActual(t, expected, actual, msg...) return failExpectedActual(t, expected, actual, msg...)
} }
case []objects.Object: case []objects.Object:
return equalObjectSlice(t, expected, actual.([]objects.Object)) return equalObjectSlice(t, expected, actual.([]objects.Object), msg...)
case *objects.Int: case *objects.Int:
return Equal(t, expected.Value, actual.(*objects.Int).Value) return Equal(t, expected.Value, actual.(*objects.Int).Value, msg...)
case *objects.Float: case *objects.Float:
return Equal(t, expected.Value, actual.(*objects.Float).Value) return Equal(t, expected.Value, actual.(*objects.Float).Value, msg...)
case *objects.String: case *objects.String:
return Equal(t, expected.Value, actual.(*objects.String).Value) return Equal(t, expected.Value, actual.(*objects.String).Value, msg...)
case *objects.Char: case *objects.Char:
return Equal(t, expected.Value, actual.(*objects.Char).Value) return Equal(t, expected.Value, actual.(*objects.Char).Value, msg...)
case *objects.Bool: case *objects.Bool:
return Equal(t, expected.Value, actual.(*objects.Bool).Value) if expected != actual {
return failExpectedActual(t, expected, actual, msg...)
}
case *objects.ReturnValue: case *objects.ReturnValue:
return Equal(t, expected.Value, actual.(objects.ReturnValue).Value) return Equal(t, expected.Value, actual.(objects.ReturnValue).Value, msg...)
case *objects.Array: case *objects.Array:
return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value) return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value, msg...)
case *objects.ImmutableArray: case *objects.ImmutableArray:
return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value) return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value, msg...)
case *objects.Bytes: case *objects.Bytes:
if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 { if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 {
return failExpectedActual(t, expected.Value, actual.(*objects.Bytes).Value, msg...) return failExpectedActual(t, string(expected.Value), string(actual.(*objects.Bytes).Value), msg...)
} }
case *objects.Map: case *objects.Map:
return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value) return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value, msg...)
case *objects.ImmutableMap: case *objects.ImmutableMap:
return equalObjectMap(t, expected.Value, actual.(*objects.ImmutableMap).Value) return equalObjectMap(t, expected.Value, actual.(*objects.ImmutableMap).Value, msg...)
case *objects.CompiledFunction: case *objects.CompiledFunction:
return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction)) return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction), msg...)
case *objects.Closure: case *objects.Closure:
return equalClosure(t, expected, actual.(*objects.Closure)) return equalClosure(t, expected, actual.(*objects.Closure), msg...)
case *objects.Undefined: case *objects.Undefined:
return true if expected != actual {
return failExpectedActual(t, expected, actual, msg...)
}
case *objects.Error: case *objects.Error:
return Equal(t, expected.Value, actual.(*objects.Error).Value) return Equal(t, expected.Value, actual.(*objects.Error).Value, msg...)
case objects.Object:
if !expected.Equals(actual.(objects.Object)) {
return failExpectedActual(t, expected, actual, msg...)
}
case error: case error:
if expected != actual.(error) { if expected != actual.(error) {
return failExpectedActual(t, expected, actual, msg...) return failExpectedActual(t, expected, actual, msg...)
@ -196,8 +188,6 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
// Fail marks the function as having failed but continues execution. // Fail marks the function as having failed but continues execution.
func Fail(t *testing.T, msg ...interface{}) bool { func Fail(t *testing.T, msg ...interface{}) bool {
t.Helper()
t.Logf("\nError trace:\n\t%s\n%s", strings.Join(errorTrace(), "\n\t"), message(msg...)) t.Logf("\nError trace:\n\t%s\n%s", strings.Join(errorTrace(), "\n\t"), message(msg...))
t.Fail() t.Fail()
@ -206,8 +196,6 @@ func Fail(t *testing.T, msg ...interface{}) bool {
} }
func failExpectedActual(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { func failExpectedActual(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
t.Helper()
var addMsg string var addMsg string
if len(msg) > 0 { if len(msg) > 0 {
addMsg = "\nMessage: " + message(msg...) addMsg = "\nMessage: " + message(msg...)
@ -256,15 +244,15 @@ func equalSymbol(a, b compiler.Symbol) bool {
a.Scope == b.Scope a.Scope == b.Scope
} }
func equalObjectSlice(t *testing.T, expected, actual []objects.Object) bool { func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...interface{}) bool {
// TODO: this test does not differentiate nil vs empty slice // TODO: this test does not differentiate nil vs empty slice
if !Equal(t, len(expected), len(actual)) { if !Equal(t, len(expected), len(actual), msg...) {
return false return false
} }
for i := 0; i < len(expected); i++ { for i := 0; i < len(expected); i++ {
if !Equal(t, expected[i], actual[i]) { if !Equal(t, expected[i], actual[i], msg...) {
return false return false
} }
} }
@ -272,15 +260,15 @@ func equalObjectSlice(t *testing.T, expected, actual []objects.Object) bool {
return true return true
} }
func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bool { func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool {
if !Equal(t, len(expected), len(actual)) { if !Equal(t, len(expected), len(actual), msg...) {
return false return false
} }
for key, expectedVal := range expected { for key, expectedVal := range expected {
actualVal := actual[key] actualVal := actual[key]
if !Equal(t, expectedVal, actualVal) { if !Equal(t, expectedVal, actualVal, msg...) {
return false return false
} }
} }
@ -288,27 +276,27 @@ func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bo
return true return true
} }
func equalCompiledFunction(t *testing.T, expected, actual objects.Object) bool { func equalCompiledFunction(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool {
expectedT := expected.(*objects.CompiledFunction) expectedT := expected.(*objects.CompiledFunction)
actualT := actual.(*objects.CompiledFunction) actualT := actual.(*objects.CompiledFunction)
return Equal(t, expectedT.Instructions, actualT.Instructions) return Equal(t, expectedT.Instructions, actualT.Instructions, msg...)
} }
func equalClosure(t *testing.T, expected, actual objects.Object) bool { func equalClosure(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool {
expectedT := expected.(*objects.Closure) expectedT := expected.(*objects.Closure)
actualT := actual.(*objects.Closure) actualT := actual.(*objects.Closure)
if !Equal(t, expectedT.Fn, actualT.Fn) { if !Equal(t, expectedT.Fn, actualT.Fn, msg...) {
return false return false
} }
if !Equal(t, len(expectedT.Free), len(actualT.Free)) { if !Equal(t, len(expectedT.Free), len(actualT.Free), msg...) {
return false return false
} }
for i := 0; i < len(expectedT.Free); i++ { for i := 0; i < len(expectedT.Free); i++ {
if !Equal(t, *expectedT.Free[i], *actualT.Free[i]) { if !Equal(t, *expectedT.Free[i], *actualT.Free[i], msg...) {
return false return false
} }
} }

View file

@ -21,7 +21,16 @@ func (b *Bytecode) Decode(r io.Reader) error {
return err return err
} }
return dec.Decode(&b.Constants) if err := dec.Decode(&b.Constants); err != nil {
return err
}
// replace Bool and Undefined with known value
for i, v := range b.Constants {
b.Constants[i] = cleanupObjects(v)
}
return nil
} }
// Encode writes Bytecode data to the writer. // Encode writes Bytecode data to the writer.
@ -32,9 +41,32 @@ func (b *Bytecode) Encode(w io.Writer) error {
return err return err
} }
// constants
return enc.Encode(b.Constants) return enc.Encode(b.Constants)
} }
func cleanupObjects(o objects.Object) objects.Object {
switch o := o.(type) {
case *objects.Bool:
if o.IsFalsy() {
return objects.FalseValue
}
return objects.TrueValue
case *objects.Undefined:
return objects.UndefinedValue
case *objects.Array:
for i, v := range o.Value {
o.Value[i] = cleanupObjects(v)
}
case *objects.Map:
for k, v := range o.Value {
o.Value[k] = cleanupObjects(v)
}
}
return o
}
func init() { func init() {
gob.Register(&objects.Int{}) gob.Register(&objects.Int{})
gob.Register(&objects.Float{}) gob.Register(&objects.Float{})
@ -52,4 +84,5 @@ func init() {
gob.Register(&objects.StringIterator{}) gob.Register(&objects.StringIterator{})
gob.Register(&objects.MapIterator{}) gob.Register(&objects.MapIterator{})
gob.Register(&objects.ArrayIterator{}) gob.Register(&objects.ArrayIterator{})
gob.Register(&objects.Time{})
} }

View file

@ -3,6 +3,7 @@ package compiler_test
import ( import (
"bytes" "bytes"
"testing" "testing"
"time"
"github.com/d5/tengo/assert" "github.com/d5/tengo/assert"
"github.com/d5/tengo/compiler" "github.com/d5/tengo/compiler"
@ -14,16 +15,20 @@ func TestBytecode(t *testing.T) {
testBytecodeSerialization(t, bytecode( testBytecodeSerialization(t, bytecode(
concat(), objectsArray( concat(), objectsArray(
objects.UndefinedValue,
&objects.Time{Value: time.Now()},
&objects.Array{ &objects.Array{
Value: objectsArray( Value: objectsArray(
&objects.Int{Value: 12}, &objects.Int{Value: 12},
&objects.String{Value: "foo"}, &objects.String{Value: "foo"},
&objects.Bool{Value: true}, objects.TrueValue,
objects.FalseValue,
&objects.Float{Value: 93.11}, &objects.Float{Value: 93.11},
&objects.Char{Value: 'x'}, &objects.Char{Value: 'x'},
objects.UndefinedValue,
), ),
}, },
&objects.Bool{Value: false}, objects.FalseValue,
&objects.Char{Value: 'y'}, &objects.Char{Value: 'y'},
&objects.Float{Value: 93.11}, &objects.Float{Value: 93.11},
compiledFunction(1, 0, compiledFunction(1, 0,
@ -36,11 +41,12 @@ func TestBytecode(t *testing.T) {
&objects.Map{ &objects.Map{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
"a": &objects.Float{Value: -93.1}, "a": &objects.Float{Value: -93.1},
"b": &objects.Bool{Value: false}, "b": objects.FalseValue,
"c": objects.UndefinedValue,
}, },
}, },
&objects.String{Value: "bar"}, &objects.String{Value: "bar"},
&objects.Undefined{}))) objects.UndefinedValue)))
testBytecodeSerialization(t, bytecode( testBytecodeSerialization(t, bytecode(
concat( concat(

View file

@ -43,7 +43,11 @@ func FuncARB(fn func() bool) *objects.UserFunction {
return nil, objects.ErrWrongNumArguments return nil, objects.ErrWrongNumArguments
} }
return &objects.Bool{Value: fn()}, nil if fn() {
return objects.TrueValue, nil
}
return objects.FalseValue, nil
}, },
} }
} }
@ -340,7 +344,11 @@ func FuncAFIRB(fn func(float64, int) bool) *objects.UserFunction {
return nil, objects.ErrInvalidTypeConversion return nil, objects.ErrInvalidTypeConversion
} }
return &objects.Bool{Value: fn(f1, i2)}, nil if fn(f1, i2) {
return objects.TrueValue, nil
}
return objects.FalseValue, nil
}, },
} }
} }
@ -359,7 +367,11 @@ func FuncAFRB(fn func(float64) bool) *objects.UserFunction {
return nil, objects.ErrInvalidTypeConversion return nil, objects.ErrInvalidTypeConversion
} }
return &objects.Bool{Value: fn(f1)}, nil if fn(f1) {
return objects.TrueValue, nil
}
return objects.FalseValue, nil
}, },
} }
} }
@ -383,6 +395,31 @@ func FuncASRS(fn func(string) string) *objects.UserFunction {
} }
} }
// FuncASRSs transform a function of 'func(string) []string' signature into a user function object.
func FuncASRSs(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
}
res := fn(s1)
arr := &objects.Array{}
for _, osArg := range res {
arr.Value = append(arr.Value, &objects.String{Value: osArg})
}
return arr, nil
},
}
}
// FuncASRSE transform a function of 'func(string) (string, error)' signature into a user function object. // 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. // User function will return 'true' if underlying native function returns nil.
func FuncASRSE(fn func(string) (string, error)) *objects.UserFunction { func FuncASRSE(fn func(string) (string, error)) *objects.UserFunction {
@ -450,6 +487,171 @@ func FuncASSRE(fn func(string, string) error) *objects.UserFunction {
} }
} }
// FuncASSRSs transform a function of 'func(string, string) []string' signature into a user function object.
func FuncASSRSs(fn func(string, string) []string) *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
}
arr := &objects.Array{}
for _, res := range fn(s1, s2) {
arr.Value = append(arr.Value, &objects.String{Value: res})
}
return arr, nil
},
}
}
// FuncASSIRSs transform a function of 'func(string, string, int) []string' signature into a user function object.
func FuncASSIRSs(fn func(string, string, int) []string) *objects.UserFunction {
return &objects.UserFunction{
Value: func(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
}
s2, ok := objects.ToString(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i3, ok := objects.ToInt(args[2])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
arr := &objects.Array{}
for _, res := range fn(s1, s2, i3) {
arr.Value = append(arr.Value, &objects.String{Value: res})
}
return arr, nil
},
}
}
// FuncASSRI transform a function of 'func(string, string) int' signature into a user function object.
func FuncASSRI(fn func(string, string) int) *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 &objects.Int{Value: int64(fn(s1, s2))}, nil
},
}
}
// FuncASSRS transform a function of 'func(string, string) string' signature into a user function object.
func FuncASSRS(fn func(string, string) string) *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 &objects.String{Value: fn(s1, s2)}, nil
},
}
}
// FuncASSRB transform a function of 'func(string, string) bool' signature into a user function object.
func FuncASSRB(fn func(string, string) bool) *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
}
if fn(s1, s2) {
return objects.TrueValue, nil
}
return objects.FalseValue, nil
},
}
}
// FuncASsSRS transform a function of 'func([]string, string) string' signature into a user function object.
func FuncASsSRS(fn func([]string, string) string) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (objects.Object, error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
var ss1 []string
arr, ok := args[0].(*objects.Array)
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
for _, a := range arr.Value {
as, ok := objects.ToString(a)
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
ss1 = append(ss1, as)
}
s2, ok := objects.ToString(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
return &objects.String{Value: fn(ss1, s2)}, nil
},
}
}
// FuncASI64RE transform a function of 'func(string, int64) error' signature // FuncASI64RE transform a function of 'func(string, int64) error' signature
// into a user function object. // into a user function object.
func FuncASI64RE(fn func(string, int64) error) *objects.UserFunction { func FuncASI64RE(fn func(string, int64) error) *objects.UserFunction {
@ -498,6 +700,30 @@ func FuncAIIRE(fn func(int, int) error) *objects.UserFunction {
} }
} }
// FuncASIRS transform a function of 'func(string, int) string' signature
// into a user function object.
func FuncASIRS(fn func(string, int) string) *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.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
return &objects.String{Value: fn(s1, i2)}, nil
},
}
}
// FuncASIIRE transform a function of 'func(string, int, int) error' signature // FuncASIIRE transform a function of 'func(string, int, int) error' signature
// into a user function object. // into a user function object.
func FuncASIIRE(fn func(string, int, int) error) *objects.UserFunction { func FuncASIIRE(fn func(string, int, int) error) *objects.UserFunction {
@ -595,11 +821,30 @@ func FuncAIRSsE(fn func(int) ([]string, error)) *objects.UserFunction {
} }
arr := &objects.Array{} arr := &objects.Array{}
for _, osArg := range res { for _, r := range res {
arr.Value = append(arr.Value, &objects.String{Value: osArg}) arr.Value = append(arr.Value, &objects.String{Value: r})
} }
return arr, nil return arr, nil
}, },
} }
} }
// FuncAIRS transform a function of 'func(int) string' signature
// into a user function object.
func FuncAIRS(fn func(int) string) *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
}
return &objects.String{Value: fn(i1)}, nil
},
}
}

View file

@ -2,6 +2,8 @@ package stdlib_test
import ( import (
"errors" "errors"
"strconv"
"strings"
"testing" "testing"
"github.com/d5/tengo/assert" "github.com/d5/tengo/assert"
@ -13,7 +15,7 @@ func TestFuncAIR(t *testing.T) {
uf := stdlib.FuncAIR(func(int) {}) uf := stdlib.FuncAIR(func(int) {})
ret, err := uf.Call(&objects.Int{Value: 10}) ret, err := uf.Call(&objects.Int{Value: 10})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Undefined{}, ret) assert.Equal(t, objects.UndefinedValue, ret)
ret, err = uf.Call() ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -22,7 +24,7 @@ func TestFuncAR(t *testing.T) {
uf := stdlib.FuncAR(func() {}) uf := stdlib.FuncAR(func() {})
ret, err := uf.Call() ret, err := uf.Call()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Undefined{}, ret) assert.Equal(t, objects.UndefinedValue, ret)
ret, err = uf.Call(objects.TrueValue) ret, err = uf.Call(objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -115,6 +117,15 @@ func TestFuncASRS(t *testing.T) {
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASRSs(t *testing.T) {
uf := stdlib.FuncASRSs(func(a string) []string { return []string{a} })
ret, err := uf.Call(&objects.String{Value: "foo"})
assert.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}), ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncASI64RE(t *testing.T) { func TestFuncASI64RE(t *testing.T) {
uf := stdlib.FuncASI64RE(func(a string, b int64) error { return nil }) uf := stdlib.FuncASI64RE(func(a string, b int64) error { return nil })
ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}) ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5})
@ -168,7 +179,24 @@ func TestFuncASRSE(t *testing.T) {
} }
func TestFuncASSRE(t *testing.T) { func TestFuncASSRE(t *testing.T) {
uf := stdlib.FuncASSRE(func(a, b string) error { return nil })
ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"})
assert.NoError(t, err)
uf = stdlib.FuncASSRE(func(a, b string) error { return errors.New("some error") })
ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"})
assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
ret, err = uf.Call(&objects.String{Value: "foo"})
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncASsRS(t *testing.T) {
uf := stdlib.FuncASsSRS(func(a []string, b string) string { return strings.Join(a, b) })
ret, err := uf.Call(array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), &objects.String{Value: " "})
assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo bar"}, ret)
ret, err = uf.Call(&objects.String{Value: "foo"})
assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARF(t *testing.T) { func TestFuncARF(t *testing.T) {
@ -227,7 +255,7 @@ func TestFuncAFRB(t *testing.T) {
}) })
ret, err := uf.Call(&objects.Float{Value: 0.1}) ret, err := uf.Call(&objects.Float{Value: 0.1})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Bool{Value: true}, ret) assert.Equal(t, objects.TrueValue, ret)
ret, err = uf.Call() ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
ret, err = uf.Call(objects.TrueValue, objects.TrueValue) ret, err = uf.Call(objects.TrueValue, objects.TrueValue)
@ -247,6 +275,17 @@ func TestFuncAFFRF(t *testing.T) {
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASIRS(t *testing.T) {
uf := stdlib.FuncASIRS(func(a string, b int) string { return strings.Repeat(a, b) })
ret, err := uf.Call(&objects.String{Value: "ab"}, &objects.Int{Value: 2})
assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "abab"}, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
ret, err = uf.Call(objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncAIFRF(t *testing.T) { func TestFuncAIFRF(t *testing.T) {
uf := stdlib.FuncAIFRF(func(a int, b float64) float64 { uf := stdlib.FuncAIFRF(func(a int, b float64) float64 {
return float64(a) + b return float64(a) + b
@ -279,7 +318,7 @@ func TestFuncAFIRB(t *testing.T) {
}) })
ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20}) ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Bool{Value: true}, ret) assert.Equal(t, objects.TrueValue, ret)
ret, err = uf.Call() ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
ret, err = uf.Call(objects.TrueValue) ret, err = uf.Call(objects.TrueValue)
@ -303,6 +342,24 @@ func TestFuncAIRSsE(t *testing.T) {
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASSRSs(t *testing.T) {
uf := stdlib.FuncASSRSs(func(a, b string) []string { return []string{a, b} })
ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"})
assert.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncASSIRSs(t *testing.T) {
uf := stdlib.FuncASSIRSs(func(a, b string, c int) []string { return []string{a, b, strconv.Itoa(c)} })
ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.Int{Value: 5})
assert.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.String{Value: "5"}), ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncARB(t *testing.T) { func TestFuncARB(t *testing.T) {
uf := stdlib.FuncARB(func() bool { return true }) uf := stdlib.FuncARB(func() bool { return true })
ret, err := uf.Call() ret, err := uf.Call()
@ -355,6 +412,42 @@ func TestFuncAYRIE(t *testing.T) {
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASSRI(t *testing.T) {
uf := stdlib.FuncASSRI(func(a, b string) int { return len(a) + len(b) })
ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"})
assert.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 6}, ret)
ret, err = uf.Call(&objects.String{Value: "foo"})
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncASSRS(t *testing.T) {
uf := stdlib.FuncASSRS(func(a, b string) string { return a + b })
ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"})
assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foobar"}, ret)
ret, err = uf.Call(&objects.String{Value: "foo"})
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncASSRB(t *testing.T) {
uf := stdlib.FuncASSRB(func(a, b string) bool { return len(a) > len(b) })
ret, err := uf.Call(&objects.String{Value: "123"}, &objects.String{Value: "12"})
assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret)
ret, err = uf.Call(&objects.String{Value: "foo"})
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncAIRS(t *testing.T) {
uf := stdlib.FuncAIRS(func(a int) string { return strconv.Itoa(a) })
ret, err := uf.Call(&objects.Int{Value: 55})
assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "55"}, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func array(elements ...objects.Object) *objects.Array { func array(elements ...objects.Object) *objects.Array {
return &objects.Array{Value: elements} return &objects.Array{Value: elements}
} }

View file

@ -67,14 +67,8 @@ var mathModule = map[string]objects.Object{
"sqrt": FuncAFRF(math.Sqrt), "sqrt": FuncAFRF(math.Sqrt),
"tan": FuncAFRF(math.Tan), "tan": FuncAFRF(math.Tan),
"tanh": FuncAFRF(math.Tanh), "tanh": FuncAFRF(math.Tanh),
"runct": FuncAFRF(math.Trunc), "trunc": FuncAFRF(math.Trunc),
"y0": FuncAFRF(math.Y0), "y0": FuncAFRF(math.Y0),
"y1": FuncAFRF(math.Y1), "y1": FuncAFRF(math.Y1),
"yn": FuncAIFRF(math.Yn), "yn": FuncAIFRF(math.Yn),
// TODO: functions that have multiple returns
// Should these be tuple assignment? Or Map return?
//"frexp": nil,
//"lgamma": nil,
//"modf": nil,
//"sincos": nil,
} }

View file

@ -3,6 +3,7 @@ package stdlib
import ( import (
"io" "io"
"os" "os"
"os/exec"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
@ -36,82 +37,110 @@ var osModule = map[string]objects.Object{
"seek_set": &objects.Int{Value: int64(io.SeekStart)}, "seek_set": &objects.Int{Value: int64(io.SeekStart)},
"seek_cur": &objects.Int{Value: int64(io.SeekCurrent)}, "seek_cur": &objects.Int{Value: int64(io.SeekCurrent)},
"seek_end": &objects.Int{Value: int64(io.SeekEnd)}, "seek_end": &objects.Int{Value: int64(io.SeekEnd)},
// args() => array(string) "args": &objects.UserFunction{Value: osArgs}, // args() => array(string)
"args": &objects.UserFunction{Value: osArgs}, "chdir": FuncASRE(os.Chdir), // chdir(dir string) => error
// chdir(dir string) => error "chmod": osFuncASFmRE(os.Chmod), // chmod(name string, mode int) => error
"chdir": FuncASRE(os.Chdir), "chown": FuncASIIRE(os.Chown), // chown(name string, uid int, gid int) => error
// chmod(name string, mode int) => error "clearenv": FuncAR(os.Clearenv), // clearenv()
"chmod": osFuncASFmRE(os.Chmod), "environ": FuncARSs(os.Environ), // environ() => array(string)
// chown(name string, uid int, gid int) => error "exit": FuncAIR(os.Exit), // exit(code int)
"chown": FuncASIIRE(os.Chown), "expand_env": FuncASRS(os.ExpandEnv), // expand_env(s string) => string
// clearenv() "getegid": FuncARI(os.Getegid), // getegid() => int
"clearenv": FuncAR(os.Clearenv), "getenv": FuncASRS(os.Getenv), // getenv(s string) => string
// environ() => array(string) "geteuid": FuncARI(os.Geteuid), // geteuid() => int
"environ": FuncARSs(os.Environ), "getgid": FuncARI(os.Getgid), // getgid() => int
// exit(code int) "getgroups": FuncARIsE(os.Getgroups), // getgroups() => array(string)/error
"exit": FuncAIR(os.Exit), "getpagesize": FuncARI(os.Getpagesize), // getpagesize() => int
// expand_env(s string) => string "getpid": FuncARI(os.Getpid), // getpid() => int
"expand_env": FuncASRS(os.ExpandEnv), "getppid": FuncARI(os.Getppid), // getppid() => int
// getegid() => int "getuid": FuncARI(os.Getuid), // getuid() => int
"getegid": FuncARI(os.Getegid), "getwd": FuncARSE(os.Getwd), // getwd() => string/error
// getenv(s string) => string "hostname": FuncARSE(os.Hostname), // hostname() => string/error
"getenv": FuncASRS(os.Getenv), "lchown": FuncASIIRE(os.Lchown), // lchown(name string, uid int, gid int) => error
// geteuid() => int "link": FuncASSRE(os.Link), // link(oldname string, newname string) => error
"geteuid": FuncARI(os.Geteuid), "lookup_env": &objects.UserFunction{Value: osLookupEnv}, // lookup_env(key string) => string/false
// getgid() => int "mkdir": osFuncASFmRE(os.Mkdir), // mkdir(name string, perm int) => error
"getgid": FuncARI(os.Getgid), "mkdir_all": osFuncASFmRE(os.MkdirAll), // mkdir_all(name string, perm int) => error
// getgroups() => array(string)/error "readlink": FuncASRSE(os.Readlink), // readlink(name string) => string/error
"getgroups": FuncARIsE(os.Getgroups), "remove": FuncASRE(os.Remove), // remove(name string) => error
// getpagesize() => int "remove_all": FuncASRE(os.RemoveAll), // remove_all(name string) => error
"getpagesize": FuncARI(os.Getpagesize), "rename": FuncASSRE(os.Rename), // rename(oldpath string, newpath string) => error
// getpid() => int "setenv": FuncASSRE(os.Setenv), // setenv(key string, value string) => error
"getpid": FuncARI(os.Getpid), "symlink": FuncASSRE(os.Symlink), // symlink(oldname string newname string) => error
// getppid() => int "temp_dir": FuncARS(os.TempDir), // temp_dir() => string
"getppid": FuncARI(os.Getppid), "truncate": FuncASI64RE(os.Truncate), // truncate(name string, size int) => error
// getuid() => int "unsetenv": FuncASRE(os.Unsetenv), // unsetenv(key string) => error
"getuid": FuncARI(os.Getuid), "create": &objects.UserFunction{Value: osCreate}, // create(name string) => imap(file)/error
// getwd() => string/error "open": &objects.UserFunction{Value: osOpen}, // open(name string) => imap(file)/error
"getwd": FuncARSE(os.Getwd), "open_file": &objects.UserFunction{Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error
// hostname() => string/error "find_process": &objects.UserFunction{Value: osFindProcess}, // find_process(pid int) => imap(process)/error
"hostname": FuncARSE(os.Hostname), "start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
// lchown(name string, uid int, gid int) => error "exec_look_path": FuncASRSE(exec.LookPath), // exec_look_path(file) => string/error
"lchown": FuncASIIRE(os.Lchown), "exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command
// link(oldname string, newname string) => error }
"link": FuncASSRE(os.Link),
// lookup_env(key string) => string/false func osCreate(args ...objects.Object) (objects.Object, error) {
"lookup_env": &objects.UserFunction{Value: osLookupEnv}, if len(args) != 1 {
// mkdir(name string, perm int) => error return nil, objects.ErrWrongNumArguments
"mkdir": osFuncASFmRE(os.Mkdir), }
// mkdir_all(name string, perm int) => error
"mkdir_all": osFuncASFmRE(os.MkdirAll), s1, ok := objects.ToString(args[0])
// readlink(name string) => string/error if !ok {
"readlink": FuncASRSE(os.Readlink), return nil, objects.ErrInvalidTypeConversion
// remove(name string) => error }
"remove": FuncASRE(os.Remove),
// remove_all(name string) => error res, err := os.Create(s1)
"remove_all": FuncASRE(os.RemoveAll), if err != nil {
// rename(oldpath string, newpath string) => error return wrapError(err), nil
"rename": FuncASSRE(os.Rename), }
// setenv(key string, value string) => error
"setenv": FuncASSRE(os.Setenv), return makeOSFile(res), nil
// symlink(oldname string newname string) => error }
"symlink": FuncASSRE(os.Symlink),
// temp_dir() => string func osOpen(args ...objects.Object) (objects.Object, error) {
"temp_dir": FuncARS(os.TempDir), if len(args) != 1 {
// truncate(name string, size int) => error return nil, objects.ErrWrongNumArguments
"truncate": FuncASI64RE(os.Truncate), }
// unsetenv(key string) => error
"unsetenv": FuncASRE(os.Unsetenv), s1, ok := objects.ToString(args[0])
// create(name string) => imap(file)/error if !ok {
"create": &objects.UserFunction{Value: osCreate}, return nil, objects.ErrInvalidTypeConversion
// open(name string) => imap(file)/error }
"open": &objects.UserFunction{Value: osOpen},
// open_file(name string, flag int, perm int) => imap(file)/error res, err := os.Open(s1)
"open_file": &objects.UserFunction{Value: osOpenFile}, if err != nil {
// find_process(pid int) => imap(process)/error return wrapError(err), nil
"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}, return makeOSFile(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 makeOSFile(res), nil
} }
func osArgs(args ...objects.Object) (objects.Object, error) { func osArgs(args ...objects.Object) (objects.Object, error) {
@ -148,19 +177,6 @@ func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction {
} }
} }
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) { func osLookupEnv(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, objects.ErrWrongNumArguments return nil, objects.ErrWrongNumArguments
@ -178,3 +194,99 @@ func osLookupEnv(args ...objects.Object) (objects.Object, error) {
return &objects.String{Value: res}, nil return &objects.String{Value: res}, nil
} }
func osExec(args ...objects.Object) (objects.Object, error) {
if len(args) == 0 {
return nil, objects.ErrWrongNumArguments
}
name, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
var execArgs []string
for _, arg := range args[1:] {
execArg, ok := objects.ToString(arg)
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
execArgs = append(execArgs, execArg)
}
return makeOSExecCommand(exec.Command(name, execArgs...)), 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 makeOSProcess(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 makeOSProcess(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
}

View file

@ -6,14 +6,7 @@ import (
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
var execModule = map[string]objects.Object{ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
// 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{ return &objects.ImmutableMap{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
// combined_output() => bytes/error // combined_output() => bytes/error
@ -84,33 +77,9 @@ func execCmdImmutableMap(cmd *exec.Cmd) *objects.ImmutableMap {
return nil, objects.ErrWrongNumArguments return nil, objects.ErrWrongNumArguments
} }
return osProcessImmutableMap(cmd.Process), nil return makeOSProcess(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
}

View file

@ -6,7 +6,7 @@ import (
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
func osFileImmutableMap(file *os.File) *objects.ImmutableMap { func makeOSFile(file *os.File) *objects.ImmutableMap {
return &objects.ImmutableMap{ return &objects.ImmutableMap{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
// chdir() => true/error // chdir() => true/error
@ -66,79 +66,6 @@ func osFileImmutableMap(file *os.File) *objects.ImmutableMap {
return &objects.Int{Value: res}, nil return &objects.Int{Value: res}, nil
}, },
}, },
// TODO: implement more functions
//"fd": nil,
//"read_at": nil,
//"readdir": nil,
//"set_deadline": nil,
//"set_read_deadline": nil,
//"set_write_deadline": nil,
//"stat": nil,
//"write_at": nil,
}, },
} }
} }
func osCreate(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := os.Create(s1)
if err != nil {
return wrapError(err), nil
}
return osFileImmutableMap(res), nil
}
func osOpen(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := os.Open(s1)
if err != nil {
return wrapError(err), nil
}
return osFileImmutableMap(res), nil
}
func osOpenFile(args ...objects.Object) (objects.Object, error) {
if len(args) != 3 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i2, ok := objects.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
i3, ok := objects.ToInt(args[2])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
res, err := os.OpenFile(s1, i2, os.FileMode(i3))
if err != nil {
return wrapError(err), nil
}
return osFileImmutableMap(res), nil
}

View file

@ -7,7 +7,7 @@ import (
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
func osProcessStateImmutableMap(state *os.ProcessState) *objects.ImmutableMap { func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap {
return &objects.ImmutableMap{ return &objects.ImmutableMap{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
"exited": FuncARB(state.Exited), "exited": FuncARB(state.Exited),
@ -18,7 +18,7 @@ func osProcessStateImmutableMap(state *os.ProcessState) *objects.ImmutableMap {
} }
} }
func osProcessImmutableMap(proc *os.Process) *objects.ImmutableMap { func makeOSProcess(proc *os.Process) *objects.ImmutableMap {
return &objects.ImmutableMap{ return &objects.ImmutableMap{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
"kill": FuncARE(proc.Kill), "kill": FuncARE(proc.Kill),
@ -48,82 +48,9 @@ func osProcessImmutableMap(proc *os.Process) *objects.ImmutableMap {
return wrapError(err), nil return wrapError(err), nil
} }
return osProcessStateImmutableMap(state), nil return makeOSProcessState(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
}

View file

@ -4,7 +4,8 @@ import "github.com/d5/tengo/objects"
// Modules contain the standard modules. // Modules contain the standard modules.
var Modules = map[string]*objects.ImmutableMap{ var Modules = map[string]*objects.ImmutableMap{
"math": {Value: mathModule}, "math": {Value: mathModule},
"os": {Value: osModule}, "os": {Value: osModule},
"exec": {Value: execModule}, "text": {Value: textModule},
"times": {Value: timesModule},
} }

View file

@ -0,0 +1,128 @@
package stdlib_test
import (
"fmt"
"testing"
"time"
"github.com/d5/tengo/assert"
"github.com/d5/tengo/compiler/stdlib"
"github.com/d5/tengo/objects"
)
type ARR = []interface{}
type MAP = map[string]interface{}
type IARR []interface{}
type IMAP map[string]interface{}
type callres struct {
t *testing.T
o objects.Object
e error
}
func (c callres) call(funcName string, args ...interface{}) callres {
if c.e != nil {
return c
}
imap, ok := c.o.(*objects.ImmutableMap)
if !ok {
return c
}
m, ok := imap.Value[funcName]
if !ok {
return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)}
}
f, ok := m.(*objects.UserFunction)
if !ok {
return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)}
}
var oargs []objects.Object
for _, v := range args {
oargs = append(oargs, object(v))
}
res, err := f.Value(oargs...)
return callres{t: c.t, o: res, e: err}
}
func (c callres) expect(expected interface{}, msgAndArgs ...interface{}) bool {
return assert.NoError(c.t, c.e, msgAndArgs...) &&
assert.Equal(c.t, object(expected), c.o, msgAndArgs...)
}
func (c callres) expectError() bool {
return assert.Error(c.t, c.e)
}
func module(t *testing.T, moduleName string) callres {
mod, ok := stdlib.Modules[moduleName]
if !ok {
return callres{t: t, e: fmt.Errorf("module not found: %s", moduleName)}
}
return callres{t: t, o: mod}
}
func object(v interface{}) objects.Object {
switch v := v.(type) {
case objects.Object:
return v
case string:
return &objects.String{Value: v}
case int64:
return &objects.Int{Value: v}
case int: // for convenience
return &objects.Int{Value: int64(v)}
case bool:
if v {
return objects.TrueValue
}
return objects.FalseValue
case rune:
return &objects.Char{Value: v}
case byte: // for convenience
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 {
objs[k] = object(v)
}
return &objects.Map{Value: objs}
case ARR:
var objs []objects.Object
for _, e := range v {
objs = append(objs, object(e))
}
return &objects.Array{Value: objs}
case IMAP:
objs := make(map[string]objects.Object)
for k, v := range v {
objs[k] = object(v)
}
return &objects.ImmutableMap{Value: objs}
case IARR:
var objs []objects.Object
for _, e := range v {
objs = append(objs, object(e))
}
return &objects.ImmutableArray{Value: objs}
case time.Time:
return &objects.Time{Value: v}
}
panic(fmt.Errorf("unknown type: %T", v))
}

469
compiler/stdlib/text.go Normal file
View file

@ -0,0 +1,469 @@
package stdlib
import (
"regexp"
"strconv"
"strings"
"github.com/d5/tengo/objects"
)
var textModule = map[string]objects.Object{
"re_match": &objects.UserFunction{Value: textREMatch}, // re_match(pattern, text) => bool/error
"re_find": &objects.UserFunction{Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined
"re_replace": &objects.UserFunction{Value: textREReplace}, // re_replace(pattern, text, repl) => string/error
"re_split": &objects.UserFunction{Value: textRESplit}, // re_split(pattern, text, count) => [string]/error
"re_compile": &objects.UserFunction{Value: textRECompile}, // re_compile(pattern) => Regexp/error
"compare": FuncASSRI(strings.Compare), // compare(a, b) => int
"contains": FuncASSRB(strings.Contains), // contains(s, substr) => bool
"contains_any": FuncASSRB(strings.ContainsAny), // contains_any(s, chars) => bool
"count": FuncASSRI(strings.Count), // count(s, substr) => int
"equal_fold": FuncASSRB(strings.EqualFold), // "equal_fold(s, t) => bool
"fields": FuncASRSs(strings.Fields), // fields(s) => [string]
"has_prefix": FuncASSRB(strings.HasPrefix), // has_prefix(s, prefix) => bool
"has_suffix": FuncASSRB(strings.HasSuffix), // has_suffix(s, suffix) => bool
"index": FuncASSRI(strings.Index), // index(s, substr) => int
"index_any": FuncASSRI(strings.IndexAny), // index_any(s, chars) => int
"join": FuncASsSRS(strings.Join), // join(arr, sep) => string
"last_index": FuncASSRI(strings.LastIndex), // last_index(s, substr) => int
"last_index_any": FuncASSRI(strings.LastIndexAny), // last_index_any(s, chars) => int
"repeat": FuncASIRS(strings.Repeat), // repeat(s, count) => string
"replace": &objects.UserFunction{Value: textReplace}, // replace(s, old, new, n) => string
"split": FuncASSRSs(strings.Split), // split(s, sep) => [string]
"split_after": FuncASSRSs(strings.SplitAfter), // split_after(s, sep) => [string]
"split_after_n": FuncASSIRSs(strings.SplitAfterN), // split_after_n(s, sep, n) => [string]
"split_n": FuncASSIRSs(strings.SplitN), // split_n(s, sep, n) => [string]
"title": FuncASRS(strings.Title), // title(s) => string
"to_lower": FuncASRS(strings.ToLower), // to_lower(s) => string
"to_title": FuncASRS(strings.ToTitle), // to_title(s) => string
"to_upper": FuncASRS(strings.ToUpper), // to_upper(s) => string
"trim_left": FuncASSRS(strings.TrimLeft), // trim_left(s, cutset) => string
"trim_prefix": FuncASSRS(strings.TrimPrefix), // trim_prefix(s, prefix) => string
"trim_right": FuncASSRS(strings.TrimRight), // trim_right(s, cutset) => string
"trim_space": FuncASRS(strings.TrimSpace), // trim_space(s) => string
"trim_suffix": FuncASSRS(strings.TrimSuffix), // trim_suffix(s, suffix) => string
"atoi": FuncASRIE(strconv.Atoi), // atoi(str) => int/error
"format_bool": &objects.UserFunction{Value: textFormatBool}, // format_bool(b) => string
"format_float": &objects.UserFunction{Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string
"format_int": &objects.UserFunction{Value: textFormatInt}, // format_int(i, base) => string
"itoa": FuncAIRS(strconv.Itoa), // itoa(i) => string
"parse_bool": &objects.UserFunction{Value: textParseBool}, // parse_bool(str) => bool/error
"parse_float": &objects.UserFunction{Value: textParseFloat}, // parse_float(str, bits) => float/error
"parse_int": &objects.UserFunction{Value: textParseInt}, // parse_int(str, base, bits) => int/error
"quote": FuncASRS(strconv.Quote), // quote(str) => string
"unquote": FuncASRSE(strconv.Unquote), // unquote(str) => string/error
}
func textREMatch(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
matched, err := regexp.MatchString(s1, s2)
if err != nil {
ret = wrapError(err)
return
}
if matched {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func textREFind(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs != 2 && numArgs != 3 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
re, err := regexp.Compile(s1)
if err != nil {
ret = wrapError(err)
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
if numArgs < 3 {
m := re.FindStringSubmatchIndex(s2)
if m == nil {
ret = objects.UndefinedValue
return
}
arr := &objects.Array{}
for i := 0; i < len(m); i += 2 {
arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
"text": &objects.String{Value: s2[m[i]:m[i+1]]},
"begin": &objects.Int{Value: int64(m[i])},
"end": &objects.Int{Value: int64(m[i+1])},
}})
}
ret = &objects.Array{Value: []objects.Object{arr}}
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
m := re.FindAllStringSubmatchIndex(s2, i3)
if m == nil {
ret = objects.UndefinedValue
return
}
arr := &objects.Array{}
for _, m := range m {
subMatch := &objects.Array{}
for i := 0; i < len(m); i += 2 {
subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
"text": &objects.String{Value: s2[m[i]:m[i+1]]},
"begin": &objects.Int{Value: int64(m[i])},
"end": &objects.Int{Value: int64(m[i+1])},
}})
}
arr.Value = append(arr.Value, subMatch)
}
ret = arr
return
}
func textREReplace(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 3 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s3, ok := objects.ToString(args[2])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
re, err := regexp.Compile(s1)
if err != nil {
ret = wrapError(err)
} else {
ret = &objects.String{Value: re.ReplaceAllString(s2, s3)}
}
return
}
func textRESplit(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs != 2 && numArgs != 3 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
var i3 = -1
if numArgs > 2 {
i3, ok = objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
}
re, err := regexp.Compile(s1)
if err != nil {
ret = wrapError(err)
return
}
arr := &objects.Array{}
for _, s := range re.Split(s2, i3) {
arr.Value = append(arr.Value, &objects.String{Value: s})
}
ret = arr
return
}
func textRECompile(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
re, err := regexp.Compile(s1)
if err != nil {
ret = wrapError(err)
} else {
ret = makeTextRegexp(re)
}
return
}
func textReplace(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 4 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s3, ok := objects.ToString(args[2])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i4, ok := objects.ToInt(args[3])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)}
return
}
func textFormatBool(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
b1, ok := args[0].(*objects.Bool)
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
if b1 == objects.TrueValue {
ret = &objects.String{Value: "true"}
} else {
ret = &objects.String{Value: "false"}
}
return
}
func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 4 {
err = objects.ErrWrongNumArguments
return
}
f1, ok := args[0].(*objects.Float)
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i4, ok := objects.ToInt(args[3])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)}
return
}
func textFormatInt(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := args[0].(*objects.Int)
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: strconv.FormatInt(i1.Value, i2)}
return
}
func textParseBool(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := args[0].(*objects.String)
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
parsed, err := strconv.ParseBool(s1.Value)
if err != nil {
ret = wrapError(err)
return
}
if parsed {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func textParseFloat(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := args[0].(*objects.String)
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
parsed, err := strconv.ParseFloat(s1.Value, i2)
if err != nil {
ret = wrapError(err)
return
}
ret = &objects.Float{Value: parsed}
return
}
func textParseInt(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 3 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := args[0].(*objects.String)
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
parsed, err := strconv.ParseInt(s1.Value, i2, i3)
if err != nil {
ret = wrapError(err)
return
}
ret = &objects.Int{Value: parsed}
return
}

View file

@ -0,0 +1,167 @@
package stdlib
import (
"regexp"
"github.com/d5/tengo/objects"
)
func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// match(text) => bool
"match": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
if re.MatchString(s1) {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
},
},
// find(text) => array(array({text:,begin:,end:}))/undefined
// find(text, maxCount) => array(array({text:,begin:,end:}))/undefined
"find": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs != 1 && numArgs != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
if numArgs == 1 {
m := re.FindStringSubmatchIndex(s1)
if m == nil {
ret = objects.UndefinedValue
return
}
arr := &objects.Array{}
for i := 0; i < len(m); i += 2 {
arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
"text": &objects.String{Value: s1[m[i]:m[i+1]]},
"begin": &objects.Int{Value: int64(m[i])},
"end": &objects.Int{Value: int64(m[i+1])},
}})
}
ret = &objects.Array{Value: []objects.Object{arr}}
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
m := re.FindAllStringSubmatchIndex(s1, i2)
if m == nil {
ret = objects.UndefinedValue
return
}
arr := &objects.Array{}
for _, m := range m {
subMatch := &objects.Array{}
for i := 0; i < len(m); i += 2 {
subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
"text": &objects.String{Value: s1[m[i]:m[i+1]]},
"begin": &objects.Int{Value: int64(m[i])},
"end": &objects.Int{Value: int64(m[i+1])},
}})
}
arr.Value = append(arr.Value, subMatch)
}
ret = arr
return
},
},
// replace(src, repl) => string
"replace": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: re.ReplaceAllString(s1, s2)}
return
},
},
// split(text) => array(string)
// split(text, maxCount) => array(string)
"split": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs != 1 && numArgs != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
var i2 = -1
if numArgs > 1 {
i2, ok = objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
}
arr := &objects.Array{}
for _, s := range re.Split(s1, i2) {
arr.Value = append(arr.Value, &objects.String{Value: s})
}
ret = arr
return
},
},
},
}
}

View file

@ -0,0 +1,200 @@
package stdlib_test
import (
"testing"
"github.com/d5/tengo/objects"
)
func TestTextRE(t *testing.T) {
// re_match(pattern, text)
for _, d := range []struct {
pattern string
text string
expected interface{}
}{
{"abc", "", false},
{"abc", "abc", true},
{"a", "abc", true},
{"b", "abc", true},
{"^a", "abc", true},
{"^b", "abc", false},
} {
module(t, "text").call("re_match", d.pattern, d.text).expect(d.expected, "pattern: %q, src: %q", d.pattern, d.text)
module(t, "text").call("re_compile", d.pattern).call("match", d.text).expect(d.expected, "patter: %q, src: %q", d.pattern, d.text)
}
// re_find(pattern, text)
for _, d := range []struct {
pattern string
text string
expected interface{}
}{
{"a(b)", "", objects.UndefinedValue},
{"a(b)", "ab", ARR{
ARR{
IMAP{"text": "ab", "begin": 0, "end": 2},
IMAP{"text": "b", "begin": 1, "end": 2},
},
}},
{"a(bc)d", "abcdefgabcd", ARR{
ARR{
IMAP{"text": "abcd", "begin": 0, "end": 4},
IMAP{"text": "bc", "begin": 1, "end": 3},
},
}},
{"(a)b(c)d", "abcdefgabcd", ARR{
ARR{
IMAP{"text": "abcd", "begin": 0, "end": 4},
IMAP{"text": "a", "begin": 0, "end": 1},
IMAP{"text": "c", "begin": 2, "end": 3},
},
}},
} {
module(t, "text").call("re_find", d.pattern, d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
module(t, "text").call("re_compile", d.pattern).call("find", d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
}
// re_find(pattern, text, count))
for _, d := range []struct {
pattern string
text string
count int
expected interface{}
}{
{"a(b)", "", -1, objects.UndefinedValue},
{"a(b)", "ab", -1, ARR{
ARR{
IMAP{"text": "ab", "begin": 0, "end": 2},
IMAP{"text": "b", "begin": 1, "end": 2},
},
}},
{"a(bc)d", "abcdefgabcd", -1, ARR{
ARR{
IMAP{"text": "abcd", "begin": 0, "end": 4},
IMAP{"text": "bc", "begin": 1, "end": 3},
},
ARR{
IMAP{"text": "abcd", "begin": 7, "end": 11},
IMAP{"text": "bc", "begin": 8, "end": 10},
},
}},
{"(a)b(c)d", "abcdefgabcd", -1, ARR{
ARR{
IMAP{"text": "abcd", "begin": 0, "end": 4},
IMAP{"text": "a", "begin": 0, "end": 1},
IMAP{"text": "c", "begin": 2, "end": 3},
},
ARR{
IMAP{"text": "abcd", "begin": 7, "end": 11},
IMAP{"text": "a", "begin": 7, "end": 8},
IMAP{"text": "c", "begin": 9, "end": 10},
},
}},
{"(a)b(c)d", "abcdefgabcd", 0, objects.UndefinedValue},
{"(a)b(c)d", "abcdefgabcd", 1, ARR{
ARR{
IMAP{"text": "abcd", "begin": 0, "end": 4},
IMAP{"text": "a", "begin": 0, "end": 1},
IMAP{"text": "c", "begin": 2, "end": 3},
},
}},
} {
module(t, "text").call("re_find", d.pattern, d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
module(t, "text").call("re_compile", d.pattern).call("find", d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
}
// re_replace(pattern, text, repl)
for _, d := range []struct {
pattern string
text string
repl string
expected interface{}
}{
{"a", "", "b", ""},
{"a", "a", "b", "b"},
{"a", "acac", "b", "bcbc"},
{"a", "acac", "123", "123c123c"},
{"ac", "acac", "99", "9999"},
{"ac$", "acac", "foo", "acfoo"},
} {
module(t, "text").call("re_replace", d.pattern, d.text, d.repl).expect(d.expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl)
module(t, "text").call("re_compile", d.pattern).call("replace", d.text, d.repl).expect(d.expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl)
}
// re_split(pattern, text)
for _, d := range []struct {
pattern string
text string
expected interface{}
}{
{"a", "", ARR{""}},
{"a", "abcabc", ARR{"", "bc", "bc"}},
{"ab", "abcabc", ARR{"", "c", "c"}},
{"^a", "abcabc", ARR{"", "bcabc"}},
} {
module(t, "text").call("re_split", d.pattern, d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
module(t, "text").call("re_compile", d.pattern).call("split", d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
}
// re_split(pattern, text, count))
for _, d := range []struct {
pattern string
text string
count int
expected interface{}
}{
{"a", "", -1, ARR{""}},
{"a", "abcabc", -1, ARR{"", "bc", "bc"}},
{"ab", "abcabc", -1, ARR{"", "c", "c"}},
{"^a", "abcabc", -1, ARR{"", "bcabc"}},
{"a", "abcabc", 0, ARR{}},
{"a", "abcabc", 1, ARR{"abcabc"}},
{"a", "abcabc", 2, ARR{"", "bcabc"}},
{"a", "abcabc", 3, ARR{"", "bc", "bc"}},
{"b", "abcabc", 1, ARR{"abcabc"}},
{"b", "abcabc", 2, ARR{"a", "cabc"}},
{"b", "abcabc", 3, ARR{"a", "ca", "c"}},
} {
module(t, "text").call("re_split", d.pattern, d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
module(t, "text").call("re_compile", d.pattern).call("split", d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
}
}
func TestText(t *testing.T) {
module(t, "text").call("compare", "", "").expect(0)
module(t, "text").call("compare", "", "a").expect(-1)
module(t, "text").call("compare", "a", "").expect(1)
module(t, "text").call("compare", "a", "a").expect(0)
module(t, "text").call("compare", "a", "b").expect(-1)
module(t, "text").call("compare", "b", "a").expect(1)
module(t, "text").call("compare", "abcde", "abcde").expect(0)
module(t, "text").call("compare", "abcde", "abcdf").expect(-1)
module(t, "text").call("compare", "abcdf", "abcde").expect(1)
module(t, "text").call("contains", "", "").expect(true)
module(t, "text").call("contains", "", "a").expect(false)
module(t, "text").call("contains", "a", "").expect(true)
module(t, "text").call("contains", "a", "a").expect(true)
module(t, "text").call("contains", "abcde", "a").expect(true)
module(t, "text").call("contains", "abcde", "abcde").expect(true)
module(t, "text").call("contains", "abc", "abcde").expect(false)
module(t, "text").call("contains", "ab cd", "bc").expect(false)
module(t, "text").call("replace", "", "", "", -1).expect("")
module(t, "text").call("replace", "abcd", "a", "x", -1).expect("xbcd")
module(t, "text").call("replace", "aaaa", "a", "x", -1).expect("xxxx")
module(t, "text").call("replace", "aaaa", "a", "x", 0).expect("aaaa")
module(t, "text").call("replace", "aaaa", "a", "x", 2).expect("xxaa")
module(t, "text").call("replace", "abcd", "bc", "x", -1).expect("axd")
module(t, "text").call("format_bool", true).expect("true")
module(t, "text").call("format_bool", false).expect("false")
module(t, "text").call("format_float", -19.84, 'f', -1, 64).expect("-19.84")
module(t, "text").call("format_int", -1984, 10).expect("-1984")
module(t, "text").call("format_int", 1984, 8).expect("3700")
module(t, "text").call("parse_bool", "true").expect(true)
module(t, "text").call("parse_bool", "0").expect(false)
module(t, "text").call("parse_float", "-19.84", 64).expect(-19.84)
module(t, "text").call("parse_int", "-1984", 10, 64).expect(-1984)
}

782
compiler/stdlib/times.go Normal file
View file

@ -0,0 +1,782 @@
package stdlib
import (
"time"
"github.com/d5/tengo/objects"
)
var timesModule = map[string]objects.Object{
"format_ansic": &objects.String{Value: time.ANSIC},
"format_unix_date": &objects.String{Value: time.UnixDate},
"format_ruby_date": &objects.String{Value: time.RubyDate},
"format_rfc822": &objects.String{Value: time.RFC822},
"format_rfc822z": &objects.String{Value: time.RFC822Z},
"format_rfc850": &objects.String{Value: time.RFC850},
"format_rfc1123": &objects.String{Value: time.RFC1123},
"format_rfc1123z": &objects.String{Value: time.RFC1123Z},
"format_rfc3339": &objects.String{Value: time.RFC3339},
"format_rfc3339_nano": &objects.String{Value: time.RFC3339Nano},
"format_kitchen": &objects.String{Value: time.Kitchen},
"format_stamp": &objects.String{Value: time.Stamp},
"format_stamp_milli": &objects.String{Value: time.StampMilli},
"format_stamp_micro": &objects.String{Value: time.StampMicro},
"format_stamp_nano": &objects.String{Value: time.StampNano},
"nanosecond": &objects.Int{Value: int64(time.Nanosecond)},
"microsecond": &objects.Int{Value: int64(time.Microsecond)},
"millisecond": &objects.Int{Value: int64(time.Millisecond)},
"second": &objects.Int{Value: int64(time.Second)},
"minute": &objects.Int{Value: int64(time.Minute)},
"hour": &objects.Int{Value: int64(time.Hour)},
"january": &objects.Int{Value: int64(time.January)},
"february": &objects.Int{Value: int64(time.February)},
"march": &objects.Int{Value: int64(time.March)},
"april": &objects.Int{Value: int64(time.April)},
"may": &objects.Int{Value: int64(time.May)},
"june": &objects.Int{Value: int64(time.June)},
"july": &objects.Int{Value: int64(time.July)},
"august": &objects.Int{Value: int64(time.August)},
"september": &objects.Int{Value: int64(time.September)},
"october": &objects.Int{Value: int64(time.October)},
"november": &objects.Int{Value: int64(time.November)},
"december": &objects.Int{Value: int64(time.December)},
"sleep": &objects.UserFunction{Value: timesSleep}, // sleep(int)
"parse_duration": &objects.UserFunction{Value: timesParseDuration}, // parse_duration(str) => int
"since": &objects.UserFunction{Value: timesSince}, // since(time) => int
"until": &objects.UserFunction{Value: timesUntil}, // until(time) => int
"duration_hours": &objects.UserFunction{Value: timesDurationHours}, // duration_hours(int) => float
"duration_minutes": &objects.UserFunction{Value: timesDurationMinutes}, // duration_minutes(int) => float
"duration_nanoseconds": &objects.UserFunction{Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int
"duration_seconds": &objects.UserFunction{Value: timesDurationSeconds}, // duration_seconds(int) => float
"duration_string": &objects.UserFunction{Value: timesDurationString}, // duration_string(int) => string
"month_string": &objects.UserFunction{Value: timesMonthString}, // month_string(int) => string
"date": &objects.UserFunction{Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time
"now": &objects.UserFunction{Value: timesNow}, // now() => time
"parse": &objects.UserFunction{Value: timesParse}, // parse(format, str) => time
"unix": &objects.UserFunction{Value: timesUnix}, // unix(sec, nsec) => time
"add": &objects.UserFunction{Value: timesAdd}, // add(time, int) => time
"add_date": &objects.UserFunction{Value: timesAddDate}, // add_date(time, years, months, days) => time
"sub": &objects.UserFunction{Value: timesSub}, // sub(t time, u time) => int
"after": &objects.UserFunction{Value: timesAfter}, // after(t time, u time) => bool
"before": &objects.UserFunction{Value: timesBefore}, // before(t time, u time) => bool
"time_year": &objects.UserFunction{Value: timesTimeYear}, // time_year(time) => int
"time_month": &objects.UserFunction{Value: timesTimeMonth}, // time_month(time) => int
"time_day": &objects.UserFunction{Value: timesTimeDay}, // time_day(time) => int
"time_weekday": &objects.UserFunction{Value: timesTimeWeekday}, // time_weekday(time) => int
"time_hour": &objects.UserFunction{Value: timesTimeHour}, // time_hour(time) => int
"time_minute": &objects.UserFunction{Value: timesTimeMinute}, // time_minute(time) => int
"time_second": &objects.UserFunction{Value: timesTimeSecond}, // time_second(time) => int
"time_nanosecond": &objects.UserFunction{Value: timesTimeNanosecond}, // time_nanosecond(time) => int
"time_unix": &objects.UserFunction{Value: timesTimeUnix}, // time_unix(time) => int
"time_unix_nano": &objects.UserFunction{Value: timesTimeUnixNano}, // time_unix_nano(time) => int
"time_format": &objects.UserFunction{Value: timesTimeFormat}, // time_format(time, format) => string
"time_location": &objects.UserFunction{Value: timesTimeLocation}, // time_location(time) => string
"time_string": &objects.UserFunction{Value: timesTimeString}, // time_string(time) => string
"is_zero": &objects.UserFunction{Value: timesIsZero}, // is_zero(time) => bool
"to_local": &objects.UserFunction{Value: timesToLocal}, // to_local(time) => time
"to_utc": &objects.UserFunction{Value: timesToUTC}, // to_utc(time) => time
}
func timesSleep(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
time.Sleep(time.Duration(i1))
ret = objects.UndefinedValue
return
}
func timesParseDuration(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
dur, err := time.ParseDuration(s1)
if err != nil {
ret = wrapError(err)
return
}
ret = &objects.Int{Value: int64(dur)}
return
}
func timesSince(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(time.Since(t1))}
return
}
func timesUntil(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(time.Until(t1))}
return
}
func timesDurationHours(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Float{Value: time.Duration(i1).Hours()}
return
}
func timesDurationMinutes(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Float{Value: time.Duration(i1).Minutes()}
return
}
func timesDurationNanoseconds(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: time.Duration(i1).Nanoseconds()}
return
}
func timesDurationSeconds(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Float{Value: time.Duration(i1).Seconds()}
return
}
func timesDurationString(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: time.Duration(i1).String()}
return
}
func timesMonthString(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: time.Month(i1).String()}
return
}
func timesDate(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 7 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i4, ok := objects.ToInt(args[3])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i5, ok := objects.ToInt(args[4])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i6, ok := objects.ToInt(args[5])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i7, ok := objects.ToInt(args[6])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Time{Value: time.Date(i1, time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location())}
return
}
func timesNow(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
err = objects.ErrWrongNumArguments
return
}
ret = &objects.Time{Value: time.Now()}
return
}
func timesParse(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
s1, ok := objects.ToString(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
parsed, err := time.Parse(s1, s2)
if err != nil {
ret = wrapError(err)
return
}
ret = &objects.Time{Value: parsed}
return
}
func timesUnix(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
i1, ok := objects.ToInt64(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i2, ok := objects.ToInt64(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Time{Value: time.Unix(i1, i2)}
return
}
func timesAdd(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i2, ok := objects.ToInt64(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Time{Value: t1.Add(time.Duration(i2))}
return
}
func timesSub(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
t2, ok := objects.ToTime(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Sub(t2))}
return
}
func timesAddDate(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 4 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i2, ok := objects.ToInt(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i3, ok := objects.ToInt(args[2])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
i4, ok := objects.ToInt(args[3])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Time{Value: t1.AddDate(i2, i3, i4)}
return
}
func timesAfter(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
t2, ok := objects.ToTime(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
if t1.After(t2) {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func timesBefore(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
t2, ok := objects.ToTime(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
if t1.Before(t2) {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func timesTimeYear(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Year())}
return
}
func timesTimeMonth(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Month())}
return
}
func timesTimeDay(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Day())}
return
}
func timesTimeWeekday(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Weekday())}
return
}
func timesTimeHour(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Hour())}
return
}
func timesTimeMinute(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Minute())}
return
}
func timesTimeSecond(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Second())}
return
}
func timesTimeNanosecond(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Nanosecond())}
return
}
func timesTimeUnix(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.Unix())}
return
}
func timesTimeUnixNano(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Int{Value: int64(t1.UnixNano())}
return
}
func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 2 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
s2, ok := objects.ToString(args[1])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: t1.Format(s2)}
return
}
func timesIsZero(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
if t1.IsZero() {
ret = objects.TrueValue
} else {
ret = objects.FalseValue
}
return
}
func timesToLocal(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Time{Value: t1.Local()}
return
}
func timesToUTC(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.Time{Value: t1.UTC()}
return
}
func timesTimeLocation(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: t1.Location().String()}
return
}
func timesTimeString(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
err = objects.ErrWrongNumArguments
return
}
t1, ok := objects.ToTime(args[0])
if !ok {
err = objects.ErrInvalidTypeConversion
return
}
ret = &objects.String{Value: t1.String()}
return
}

View file

@ -0,0 +1,62 @@
package stdlib_test
import (
"testing"
"time"
"github.com/d5/tengo/assert"
"github.com/d5/tengo/objects"
)
func TestTimes(t *testing.T) {
time1 := time.Date(1982, 9, 28, 19, 21, 44, 999, time.Now().Location())
time2 := time.Now()
module(t, "times").call("sleep", 1).expect(objects.UndefinedValue)
assert.True(t, module(t, "times").call("since", time.Now().Add(-time.Hour)).o.(*objects.Int).Value > 3600000000000)
assert.True(t, module(t, "times").call("until", time.Now().Add(time.Hour)).o.(*objects.Int).Value < 3600000000000)
module(t, "times").call("parse_duration", "1ns").expect(1)
module(t, "times").call("parse_duration", "1ms").expect(1000000)
module(t, "times").call("parse_duration", "1h").expect(3600000000000)
module(t, "times").call("duration_hours", 1800000000000).expect(0.5)
module(t, "times").call("duration_minutes", 1800000000000).expect(30.0)
module(t, "times").call("duration_nanoseconds", 100).expect(100)
module(t, "times").call("duration_seconds", 1000000).expect(0.001)
module(t, "times").call("duration_string", 1800000000000).expect("30m0s")
module(t, "times").call("month_string", 1).expect("January")
module(t, "times").call("month_string", 12).expect("December")
module(t, "times").call("date", 1982, 9, 28, 19, 21, 44, 999).expect(time1)
assert.True(t, module(t, "times").call("now").o.(*objects.Time).Value.Sub(time.Now()).Nanoseconds() < 100000000) // within 100ms
parsed, _ := time.Parse(time.RFC3339, "1982-09-28T19:21:44+07:00")
module(t, "times").call("parse", time.RFC3339, "1982-09-28T19:21:44+07:00").expect(parsed)
module(t, "times").call("unix", 1234325, 94493).expect(time.Unix(1234325, 94493))
module(t, "times").call("add", time2, 3600000000000).expect(time2.Add(time.Duration(3600000000000)))
module(t, "times").call("sub", time2, time2.Add(-time.Hour)).expect(3600000000000)
module(t, "times").call("add_date", time2, 1, 2, 3).expect(time2.AddDate(1, 2, 3))
module(t, "times").call("after", time2, time2.Add(time.Hour)).expect(false)
module(t, "times").call("after", time2, time2.Add(-time.Hour)).expect(true)
module(t, "times").call("before", time2, time2.Add(time.Hour)).expect(true)
module(t, "times").call("before", time2, time2.Add(-time.Hour)).expect(false)
module(t, "times").call("time_year", time1).expect(time1.Year())
module(t, "times").call("time_month", time1).expect(int(time1.Month()))
module(t, "times").call("time_day", time1).expect(time1.Day())
module(t, "times").call("time_hour", time1).expect(time1.Hour())
module(t, "times").call("time_minute", time1).expect(time1.Minute())
module(t, "times").call("time_second", time1).expect(time1.Second())
module(t, "times").call("time_nanosecond", time1).expect(time1.Nanosecond())
module(t, "times").call("time_unix", time1).expect(time1.Unix())
module(t, "times").call("time_unix_nano", time1).expect(time1.UnixNano())
module(t, "times").call("time_format", time1, time.RFC3339).expect(time1.Format(time.RFC3339))
module(t, "times").call("is_zero", time1).expect(false)
module(t, "times").call("is_zero", time.Time{}).expect(true)
module(t, "times").call("to_local", time1).expect(time1.Local())
module(t, "times").call("to_utc", time1).expect(time1.UTC())
module(t, "times").call("time_location", time1).expect(time1.Location().String())
module(t, "times").call("time_string", time1).expect(time1.String())
}

View file

@ -7,15 +7,17 @@
- **Char**: character (`rune` in Go) - **Char**: character (`rune` in Go)
- **Bytes**: byte array (`[]byte` in Go) - **Bytes**: byte array (`[]byte` in Go)
- **Array**: objects array (`[]Object` in Go) - **Array**: objects array (`[]Object` in Go)
- **ImmutableArray**: immutable object array (`[]Object` in Go)
- **Map**: objects map with string keys (`map[string]Object` in Go) - **Map**: objects map with string keys (`map[string]Object` in Go)
- **ImmutableMap**: immutable object map with string keys (`map[string]Object` in Go) - **ImmutableMap**: immutable object map with string keys (`map[string]Object` in Go)
- **Time**: time (`time.Time` in Go)
- **Error**: an error with underlying Object value of any type - **Error**: an error with underlying Object value of any type
- **Undefined**: undefined - **Undefined**: undefined
## Type Conversion/Coercion Table ## Type Conversion/Coercion Table
|src\dst |Int |String |Float |Bool |Char |Bytes |Array |Map |IMap|Error |Undefined| |src\dst |Int |String |Float |Bool |Char |Bytes |Array |Map |Time |Error |Undefined|
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|Int | - |_strconv_ |float64(v)|!IsFalsy()| rune(v)|**X**|**X**|**X**|**X**|**X**|**X**| |Int | - |_strconv_ |float64(v)|!IsFalsy()| rune(v)|**X**|**X**|**X**|_time.Unix()_|**X**|**X**|
|String |_strconv_| - |_strconv_|!IsFalsy()|**X**|[]byte(s)|**X**|**X**|**X**|**X**|**X**| |String |_strconv_| - |_strconv_|!IsFalsy()|**X**|[]byte(s)|**X**|**X**|**X**|**X**|**X**|
|Float |int64(f) |_strconv_ | - |!IsFalsy()|**X**|**X**|**X**|**X**|**X**|**X**|**X**| |Float |int64(f) |_strconv_ | - |!IsFalsy()|**X**|**X**|**X**|**X**|**X**|**X**|**X**|
|Bool |1 / 0 |"true" / "false"|**X** | - |**X**|**X**|**X**|**X**|**X**|**X**|**X**| |Bool |1 / 0 |"true" / "false"|**X** | - |**X**|**X**|**X**|**X**|**X**|**X**|**X**|
@ -23,13 +25,15 @@
|Bytes |**X** |string(y)|**X** |!IsFalsy()|**X**| - |**X**|**X**|**X**|**X**|**X**| |Bytes |**X** |string(y)|**X** |!IsFalsy()|**X**| - |**X**|**X**|**X**|**X**|**X**|
|Array |**X** |"[...]" |**X** |!IsFalsy()|**X**|**X**| - |**X**|**X**|**X**|**X**| |Array |**X** |"[...]" |**X** |!IsFalsy()|**X**|**X**| - |**X**|**X**|**X**|**X**|
|Map |**X** |"{...}" |**X** |!IsFalsy()|**X**|**X**|**X**| - |**X**|**X**|**X**| |Map |**X** |"{...}" |**X** |!IsFalsy()|**X**|**X**|**X**| - |**X**|**X**|**X**|
|IMap |**X** |"{...}" |**X** |!IsFalsy()|**X**|**X**|**X**|**X**| - |**X**|**X**| |Time |**X** |String() |**X** |!IsFalsy()|**X**|**X**|**X**|**X**| - |**X**|**X**|
|Error |**X** |"error: ..." |**X** |false|**X**|**X**|**X**|**X**|**X**| - |**X**| |Error |**X** |"error: ..." |**X** |false|**X**|**X**|**X**|**X**|**X**| - |**X**|
|Undefined|**X** |**X**|**X** |false|**X**|**X**|**X**|**X**|**X**|**X**| - | |Undefined|**X** |**X**|**X** |false|**X**|**X**|**X**|**X**|**X**|**X**| - |
_* **X**: No conversion; Typed value functions for `script.Variable` will return zero values._ _* **X**: No conversion; Typed value functions for `script.Variable` will return zero values._
_* strconv: converted using Go's conversion functions from `strconv` package._ _* strconv: converted using Go's conversion functions from `strconv` package._
_* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_ _* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_
_* String(): use `Object.String()` function_
_* time.Unix(): use `time.Unix(v, 0)` to convert to Time_
## Object.IsFalsy() ## Object.IsFalsy()
@ -43,7 +47,7 @@ _* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_
- **Bytes**: `len(bytes) == 0` - **Bytes**: `len(bytes) == 0`
- **Array**: `len(arr) == 0` - **Array**: `len(arr) == 0`
- **Map**: `len(map) == 0` - **Map**: `len(map) == 0`
- **ImmutableMap**: `len(map) == 0` - **Time**: `Time.IsZero()`
- **Error**: `true` _(Error is always falsy)_ - **Error**: `true` _(Error is always falsy)_
- **Undefined**: `true` _(Undefined is always falsy)_ - **Undefined**: `true` _(Undefined is always falsy)_
@ -56,6 +60,7 @@ _* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_
- `char(x)`: tries to convert `x` into char; returns `undefined` if failed - `char(x)`: tries to convert `x` into char; returns `undefined` if failed
- `bytes(x)`: tries to convert `x` into bytes; returns `undefined` if failed - `bytes(x)`: tries to convert `x` into bytes; returns `undefined` if failed
- `bytes(N)`: as a special case this will create a Bytes variable with the given size `N` (only if `N` is int) - `bytes(N)`: as a special case this will create a Bytes variable with the given size `N` (only if `N` is int)
- `time(x)`: tries to convert `x` into time; returns `undefined` if failed
## Type Checking Builtin Functions ## Type Checking Builtin Functions
@ -65,5 +70,10 @@ _* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_
- `is_float(x)`: returns `true` if `x` is float; `false` otherwise - `is_float(x)`: returns `true` if `x` is float; `false` otherwise
- `is_char(x)`: returns `true` if `x` is char; `false` otherwise - `is_char(x)`: returns `true` if `x` is char; `false` otherwise
- `is_bytes(x)`: returns `true` if `x` is bytes; `false` otherwise - `is_bytes(x)`: returns `true` if `x` is bytes; `false` otherwise
- `is_array(x)`: return `true` if `x` is array; `false` otherwise
- `is_immutable_array(x)`: return `true` if `x` is immutable array; `false` otherwise
- `is_map(x)`: return `true` if `x` is map; `false` otherwise
- `is_immutable_map(x)`: return `true` if `x` is immutable map; `false` otherwise
- `is_time(x)`: return `true` if `x` is time; `false` otherwise
- `is_error(x)`: returns `true` if `x` is error; `false` otherwise - `is_error(x)`: returns `true` if `x` is error; `false` otherwise
- `is_undefined(x)`: returns `true` if `x` is undefined; `false` otherwise - `is_undefined(x)`: returns `true` if `x` is undefined; `false` otherwise

View file

@ -1,28 +0,0 @@
# Module - "exec"
```golang
exec := import("exec")
```
## Module Functions
- `look_path(file string) => string/error`: port of `exec.LookPath` function
- `command(name string, args array(string)) => `Cmd/error`: port of `exec.Command` function
## Cmd Functions
```golang
cmd := exec.command("echo", ["foo", "bar"])
output := cmd.output()
```
- `combined_output() => bytes/error`: port of `exec.Cmd.CombinedOutput` function
- `output() => bytes/error`: port of `exec.Cmd.Output` function
- `combined_output() => bytes/error`: port of `exec.Cmd.CombinedOutput` function
- `run() => error`: port of `exec.Cmd.Run` function
- `start() => error`: port of `exec.Cmd.Start` function
- `wait() => error`: port of `exec.Cmd.Wait` function
- `set_path(path string)`: sets `Path` of `exec.Cmd`
- `set_dir(dir string)`: sets `Dir` of `exec.Cmd`
- `set_env(env array(string))`: sets `Env` of `exec.Cmd`
- `process() => Process`: returns Process (`Process` of `exec.Cmd`)

View file

@ -4,76 +4,72 @@
math := import("math") math := import("math")
``` ```
## Variables ## Constants
- `e`: equivalent of Go's `math.E` - `e`
- `pi`: equivalent of Go's `math.Pi` - `pi`
- `phi`: equivalent of Go's `math.Phi` - `phi`
- `sqrt2`: equivalent of Go's `math.Sqrt2` - `sqrt2`
- `sqrtE`: equivalent of Go's `math.SqrtE` - `sqrtE`
- `sprtPi`: equivalent of Go's `math.SqrtPi` - `sprtPi`
- `sqrtPhi`: equivalent of Go's `math.SqrtPhi` - `sqrtPhi`
- `ln2`: equivalent of Go's `math.Ln2` - `ln2`
- `log2E`: equivalent of Go's `math.Log2E` - `log2E`
- `ln10`: equivalent of Go's `math.Ln10` - `ln10`
- `ln10E`: equivalent of Go's `math.Log10E` - `ln10E`
## Functions ## Functions
- `abs(float) => float`: port of Go's `math.Abs` function - `abs(x float) => float`: returns the absolute value of x.
- `acos(float) => float`: port of Go's `math.Acos` function - `acos(x float) => float`: returns the arccosine, in radians, of x.
- `acosh(float) => float`: port of Go's `math.Acosh` function - `acosh(x float) => float`: returns the inverse hyperbolic cosine of x.
- `asin(float) => float`: port of Go's `math.Asin` function - `asin(x float) => float`: returns the arcsine, in radians, of x.
- `asinh(float) => float`: port of Go's `math.Asinh` function - `asinh(x float) => float`: returns the inverse hyperbolic sine of x.
- `atan(float) => float`: port of Go's `math.Atan` function - `atan(x float) => float`: returns the arctangent, in radians, of x.
- `atan2(float, float) => float`: port of Go's `math.Atan2` function - `atan2(y float, xfloat) => float`: returns the arc tangent of y/x, using the signs of the two to determine the quadrant of the return value.
- `atanh(float) => float`: port of Go's `math.Atanh` function - `atanh(x float) => float`: returns the inverse hyperbolic tangent of x.
- `cbrt(float) => float`: port of Go's `math.Cbrt` function - `cbrt(x float) => float`: returns the cube root of x.
- `ceil(float) => float`: port of Go's `math.Ceil` function - `ceil(x float) => float`: returns the least integer value greater than or equal to x.
- `copysign(float, float) => float`: port of Go's `math.Copysign` function - `copysign(x float, y float) => float`: returns a value with the magnitude of x and the sign of y.
- `cos(float) => float`: port of Go's `math.Cos` function - `cos(x float) => float`: returns the cosine of the radian argument x.
- `cosh(float) => float`: port of Go's `math.Cosh` function - `cosh(x float) => float`: returns the hyperbolic cosine of x.
- `dim(float, float) => float`: port of Go's `math.Dim` function - `dim(x float, y float) => float`: returns the maximum of x-y or 0.
- `erf(float) => float`: port of Go's `math.Erf` function - `erf(x float) => float`: returns the error function of x.
- `erfc(float) => float`: port of Go's `math.Erfc` function - `erfc(x float) => float`: returns the complementary error function of x.
- `erfcinv(float) => float`: port of Go's `math.Erfcinv` function - `exp(x float) => float`: returns e**x, the base-e exponential of x.
- `erfinv(float) => float`: port of Go's `math.Erfinv` function - `exp2(x float) => float`: returns 2**x, the base-2 exponential of x.
- `exp(float) => float`: port of Go's `math.Exp` function - `expm1(x float) => float`: returns e**x - 1, the base-e exponential of x minus 1. It is more accurate than Exp(x) - 1 when x is near zero.
- `exp2(float) => float`: port of Go's `math.Exp2` function - `floor(x float) => float`: returns the greatest integer value less than or equal to x.
- `expm1(float) => float`: port of Go's `math.Expm1` function - `gamma(x float) => float`: returns the Gamma function of x.
- `floor(float) => float`: port of Go's `math.Floor` function - `hypot(p float, q float) => float`: returns Sqrt(p * p + q * q), taking care to avoid unnecessary overflow and underflow.
- `gamma(float) => float`: port of Go's `math.Gamma` function - `ilogb(x float) => float`: returns the binary exponent of x as an integer.
- `hypot(float, float) => float`: port of Go's `math.Hypot` function - `inf(sign int) => float`: returns positive infinity if sign >= 0, negative infinity if sign < 0.
- `ilogb(float) => float`: port of Go's `math.Ilogb` function - `is_inf(f float, sign int) => float`: reports whether f is an infinity, according to sign. If sign > 0, IsInf reports whether f is positive infinity. If sign < 0, IsInf reports whether f is negative infinity. If sign == 0, IsInf reports whether f is either infinity.
- `inf(int) => float`: port of Go's `math.Inf` function - `is_nan(f float) => float`: reports whether f is an IEEE 754 ``not-a-number'' value.
- `is_inf(float, int) => float`: port of Go's `math.IsInf` function - `j0(x float) => float`: returns the order-zero Bessel function of the first kind.
- `is_nan(float) => float`: port of Go's `math.IsNaN` function - `j1(x float) => float`: returns the order-one Bessel function of the first kind.
- `j0(float) => float`: port of Go's `math.J0` function - `jn(n int, x float) => float`: returns the order-n Bessel function of the first kind.
- `j1(float) => float`: port of Go's `math.J1` function - `ldexp(frac float, exp int) => float`: is the inverse of frexp. It returns frac × 2**exp.
- `jn(int, float) => float`: port of Go's `math.Jn` function - `log(x float) => float`: returns the natural logarithm of x.
- `ldexp(float, int) => float`: port of Go's `math.Ldexp` function - `log10(x float) => float`: returns the decimal logarithm of x.
- `log(float) => float`: port of Go's `math.Log` function - `log1p(x float) => float`: returns the natural logarithm of 1 plus its argument x. It is more accurate than Log(1 + x) when x is near zero.
- `log10(float) => float`: port of Go's `math.Log10` function - `log2(x float) => float`: returns the binary logarithm of x.
- `log1p(float) => float`: port of Go's `math.Log1p` function - `logb(x float) => float`: returns the binary exponent of x.
- `log2(float) => float`: port of Go's `math.Log2` function - `max(x float, y float) => float`: returns the larger of x or y.
- `logb(float) => float`: port of Go's `math.Logb` function - `min(x float, y float) => float`: returns the smaller of x or y.
- `max(float, float) => float`: port of Go's `math.Max` function - `mod(x float, y float) => float`: returns the floating-point remainder of x/y.
- `min(float, float) => float`: port of Go's `math.Min` function - `nan() => float`: returns an IEEE 754 ``not-a-number'' value.
- `mod(float, float) => float`: port of Go's `math.Mod` function - `nextafter(x float, y float) => float`: returns the next representable float64 value after x towards y.
- `nan() => float`: port of Go's `math.NaN` function - `pow(x float, y float) => float`: returns x**y, the base-x exponential of y.
- `nextafter(float, float) => float`: port of Go's `math.Nextafter` function - `pow10(n int) => float`: returns 10**n, the base-10 exponential of n.
- `pow(float, float) => float`: port of Go's `math.Pow` function - `remainder(x float, y float) => float`: returns the IEEE 754 floating-point remainder of x/y.
- `pow10(int) => float`: port of Go's `math.Pow10` function - `signbit(x float) => float`: returns true if x is negative or negative zero.
- `remainder(float, float) => float`: port of Go's `math.Remainder` function - `sin(x float) => float`: returns the sine of the radian argument x.
- `round(float) => float`: port of Go's `math.Round` function - `sinh(x float) => float`: returns the hyperbolic sine of x.
- `round_to_even(float) => float`: port of Go's `math.RoundToEven` function - `sqrt(x float) => float`: returns the square root of x.
- `signbit(float) => float`: port of Go's `math.Signbit` function - `tan(x float) => float`: returns the tangent of the radian argument x.
- `sin(float) => float`: port of Go's `math.Sin` function - `tanh(x float) => float`: returns the hyperbolic tangent of x.
- `sinh(float) => float`: port of Go's `math.Sinh` function - `trunc(x float) => float`: returns the integer value of x.
- `sqrt(float) => float`: port of Go's `math.Sqrt` function - `y0(x float) => float`: returns the order-zero Bessel function of the second kind.
- `tan(float) => float`: port of Go's `math.Tan` function - `y1(x float) => float`: returns the order-one Bessel function of the second kind.
- `tanh(float) => float`: port of Go's `math.Tanh` function - `yn(n int, x float) => float`: returns the order-n Bessel function of the second kind.
- `runct(float) => float`: port of Go's `math.Trunc` function
- `y0(float) => float`: port of Go's `math.Y0` function
- `y1(float) => float`: port of Go's `math.Y1` function
- `yn(int, float) => float`: port of Go's `math.Yn` function

View file

@ -4,82 +4,84 @@
os := import("os") os := import("os")
``` ```
## Module Variables ## Constants
- `o_rdonly`: equivalent of Go's `os.O_RDONLY` - `o_rdonly`
- `o_wronly`: equivalent of Go's `os.O_WRONLY` - `o_wronly`
- `o_rdwr`: equivalent of Go's `os.O_RDWR` - `o_rdwr`
- `o_append`: equivalent of Go's `os.O_APPEND` - `o_append`
- `o_create`: equivalent of Go's `os.O_CREATE` - `o_create`
- `o_excl`: equivalent of Go's `os.O_EXCL` - `o_excl`
- `o_sync`: equivalent of Go's `os.O_SYNC` - `o_sync`
- `o_trunc`: equivalent of Go's `os.O_TRUNC` - `o_trunc`
- `mode_dir`: equivalent of Go's `os.ModeDir` - `mode_dir`
- `mode_append`: equivalent of Go's `os.ModeAppend` - `mode_append`
- `mode_exclusive`: equivalent of Go's `os.ModeExclusive` - `mode_exclusive`
- `mode_temporary`: equivalent of Go's `os.ModeTemporary` - `mode_temporary`
- `mode_symlink`: equivalent of Go's `os.ModeSymlink` - `mode_symlink`
- `mode_device`: equivalent of Go's `os.ModeDevice` - `mode_device`
- `mode_named_pipe`: equivalent of Go's `os.ModeNamedPipe` - `mode_named_pipe`
- `mode_socket`: equivalent of Go's `os.ModeSocket` - `mode_socket`
- `mode_setuid`: equivalent of Go's `os.ModeSetuid` - `mode_setuid`
- `mode_setgui`: equivalent of Go's `os.ModeSetgid` - `mode_setgui`
- `mode_char_device`: equivalent of Go's `os.ModeCharDevice` - `mode_char_device`
- `mode_sticky`: equivalent of Go's `os.ModeSticky` - `mode_sticky`
- `mode_irregular`: equivalent of Go's `os.ModeIrregular` - `mode_irregular`
- `mode_type`: equivalent of Go's `os.ModeType` - `mode_type`
- `mode_perm`: equivalent of Go's `os.ModePerm` - `mode_perm`
- `seek_set`: equivalent of Go's `os.SEEK_SET` - `seek_set`
- `seek_cur`: equivalent of Go's `os.SEEK_CUR` - `seek_cur`
- `seek_end`: equivalent of Go's `os.SEEK_END` - `seek_end`
- `path_separator`: equivalent of Go's `os.PathSeparator` - `path_separator`
- `path_list_separator`: equivalent of Go's `os.PathListSeparator` - `path_list_separator`
- `dev_null`: equivalent of Go's `os.DevNull` - `dev_null`
## Module Functions
- `args() => array(string)`: returns `os.Args` ## Functions
- `chdir(dir string) => error`: port of `os.Chdir` function
- `chmod(name string, mode int) => error `: port of Go's `os.Chmod` function
- `chown(name string, uid int, gid int) => error `: port of Go's `os.Chown` function
- `clearenv() `: port of Go's `os.Clearenv` function
- `environ() => array(string) `: port of Go's `os.Environ` function
- `executable() => string/error`: port of Go's `os.Executable()` function
- `exit(code int) `: port of Go's `os.Exit` function
- `expand_env(s string) => string `: port of Go's `os.ExpandEnv` function
- `getegid() => int `: port of Go's `os.Getegid` function
- `getenv(s string) => string `: port of Go's `os.Getenv` function
- `geteuid() => int `: port of Go's `os.Geteuid` function
- `getgid() => int `: port of Go's `os.Getgid` function
- `getgroups() => array(string)/error `: port of Go's `os.Getgroups` function
- `getpagesize() => int `: port of Go's `os.Getpagesize` function
- `getpid() => int `: port of Go's `os.Getpid` function
- `getppid() => int `: port of Go's `os.Getppid` function
- `getuid() => int `: port of Go's `os.Getuid` function
- `getwd() => string/error `: port of Go's `os.Getwd` function
- `hostname() => string/error `: port of Go's `os.Hostname` function
- `lchown(name string, uid int, gid int) => error `: port of Go's `os.Lchown` function
- `link(oldname string, newname string) => error `: port of Go's `os.Link` function
- `lookup_env(key string) => string/false`: port of Go's `os,LookupEnv` function
- `mkdir(name string, perm int) => error `: port of Go's `os.Mkdir` function
- `mkdir_all(name string, perm int) => error `: port of Go's `os.MkdirAll` function
- `readlink(name string) => string/error `: port of Go's `os.Readlink` function
- `remove(name string) => error `: port of Go's `os.Remove` function
- `remove_all(name string) => error `: port of Go's `os.RemoveAll` function
- `rename(oldpath string, newpath string) => error `: port of Go's `os.Rename` function
- `setenv(key string, value string) => error `: port of Go's `os.Setenv` function
- `symlink(oldname string newname string) => error `: port of Go's `os.Symlink` function
- `temp_dir() => string `: port of Go's `os.TempDir` function
- `truncate(name string, size int) => error `: port of Go's `os.Truncate` function
- `unsetenv(key string) => error `: port of Go's `os.Unsetenv` function
- `user_cache_dir() => string/error `: port of Go's `os.UserCacheDir` function
- `create(name string) => File/error`: port of Go's `os.Create` function
- `open(name string) => File/error`: port of Go's `os.Open` function
- `open_file(name string, flag int, perm int) => File/error`: port of Go's `os.OpenFile` function
- `find_process(pid int) => Process/error`: port of Go's `os.FindProcess` function
- `start_process(name string, argv array(string), dir string, env array(string)) => Process/error`: port of Go's `os.StartProcess` function
## File Functions - `args() => [string]`: returns command-line arguments, starting with the program name.
- `chdir(dir string) => error`: changes the current working directory to the named directory.
- `chmod(name string, mode int) => error `: changes the mode of the named file to mode.
- `chown(name string, uid int, gid int) => error `: changes the numeric uid and gid of the named file.
- `clearenv()`: deletes all environment variables.
- `environ() => [string] `: returns a copy of strings representing the environment.
- `exit(code int) `: causes the current program to exit with the given status code.
- `expand_env(s string) => string `: replaces ${var} or $var in the string according to the values of the current environment variables.
- `getegid() => int `: returns the numeric effective group id of the caller.
- `getenv(key string) => string `: retrieves the value of the environment variable named by the key.
- `geteuid() => int `: returns the numeric effective user id of the caller.
- `getgid() => int `: returns the numeric group id of the caller.
- `getgroups() => [int]/error `: returns a list of the numeric ids of groups that the caller belongs to.
- `getpagesize() => int `: returns the underlying system's memory page size.
- `getpid() => int `: returns the process id of the caller.
- `getppid() => int `: returns the process id of the caller's parent.
- `getuid() => int `: returns the numeric user id of the caller.
- `getwd() => string/error `: returns a rooted path name corresponding to the current directory.
- `hostname() => string/error `: returns the host name reported by the kernel.
- `lchown(name string, uid int, gid int) => error `: changes the numeric uid and gid of the named file.
- `link(oldname string, newname string) => error `: creates newname as a hard link to the oldname file.
- `lookup_env(key string) => string/false`: retrieves the value of the environment variable named by the key.
- `mkdir(name string, perm int) => error `: creates a new directory with the specified name and permission bits (before umask).
- `mkdir_all(name string, perm int) => error `: creates a directory named path, along with any necessary parents, and returns nil, or else returns an error.
- `readlink(name string) => string/error `: returns the destination of the named symbolic link.
- `remove(name string) => error `: removes the named file or (empty) directory.
- `remove_all(name string) => error `: removes path and any children it contains.
- `rename(oldpath string, newpath string) => error `: renames (moves) oldpath to newpath.
- `setenv(key string, value string) => error `: sets the value of the environment variable named by the key.
- `symlink(oldname string newname string) => error `: creates newname as a symbolic link to oldname.
- `temp_dir() => string `: returns the default directory to use for temporary files.
- `truncate(name string, size int) => error `: changes the size of the named file.
- `unsetenv(key string) => error `: unsets a single environment variable.
- `create(name string) => File/error`: creates the named file with mode 0666 (before umask), truncating it if it already exists.
- `open(name string) => File/error`: opens the named file for reading. If successful, methods on the returned file can be used for reading; the associated file descriptor has mode O_RDONLY.
- `open_file(name string, flag int, perm int) => File/error`: is the generalized open call; most users will use Open or Create instead. It opens the named file with specified flag (O_RDONLY etc.) and perm (before umask), if applicable.
- `find_process(pid int) => Process/error`: looks for a running process by its pid.
- `start_process(name string, argv [string], dir string, env [string]) => Process/error`: starts a new process with the program, arguments and attributes specified by name, argv and attr. The argv slice will become os.Args in the new process, so it normally starts with the program name.
- `exec_look_path(file string) => string/error`: searches for an executable named file in the directories named by the PATH environment variable.
- `exec(name string, args...) => Command/error`: returns the Command to execute the named program with the given arguments.
## File
```golang ```golang
file := os.create("myfile") file := os.create("myfile")
@ -87,31 +89,31 @@ file.write_string("some data")
file.close() file.close()
``` ```
- `chdir() => true/error`: port of `os.File.Chdir` function - `chdir() => true/error`: changes the current working directory to the file,
- `chown(uid int, gid int) => true/error`: port of `os.File.Chown` function - `chown(uid int, gid int) => true/error`: changes the numeric uid and gid of the named file.
- `close() => error`: port of `os.File.Close` function - `close() => error`: closes the File, rendering it unusable for I/O.
- `name() => string`: port of `os.File.Name` function - `name() => string`: returns the name of the file as presented to Open.
- `readdirnames() => array(string)/error`: port of `os.File.Readdirnames` function - `readdirnames(n int) => [string]/error`: reads and returns a slice of names from the directory.
- `sync() => error`: port of `os.File.Sync` function - `sync() => error`: commits the current contents of the file to stable storage.
- `write(bytes) => int/error`: port of `os.File.Write` function - `write(bytes) => int/error`: writes len(b) bytes to the File.
- `write_string(string) => int/error`: port of `os.File.WriteString` function - `write_string(string) => int/error`: is like 'write', but writes the contents of string s rather than a slice of bytes.
- `read(bytes) => int/error`: port of `os.File.Read` function - `read(bytes) => int/error`: reads up to len(b) bytes from the File.
- `chmod(mode int) => error`: port of `os.File.Chmod` function - `chmod(mode int) => error`: changes the mode of the file to mode.
- `seek(offset int, whence int) => int/error`: port of `os.File.Seek` function - `seek(offset int, whence int) => int/error`: sets the offset for the next Read or Write on file to offset, interpreted according to whence: 0 means relative to the origin of the file, 1 means relative to the current offset, and 2 means relative to the end.
## Process Functions ## Process
```golang ```golang
proc := start_process("app", ["arg1", "arg2"], "dir", []) proc := start_process("app", ["arg1", "arg2"], "dir", [])
proc.wait() proc.wait()
``` ```
- `kill() => error`: port of `os.Process.Kill` function - `kill() => error`: causes the Process to exit immediately.
- `release() => error`: port of `os.Process.Release` function - `release() => error`: releases any resources associated with the process, rendering it unusable in the future.
- `signal(signal int) => error`: port of `os.Process.Signal` function - `signal(signal int) => error`: sends a signal to the Process.
- `wait() => ProcessState/error`: port of `os.Process.Wait` function - `wait() => ProcessState/error`: waits for the Process to exit, and then returns a ProcessState describing its status and an error, if any.
## ProcessState Functions ## ProcessState
```golang ```golang
proc := start_process("app", ["arg1", "arg2"], "dir", []) proc := start_process("app", ["arg1", "arg2"], "dir", [])
@ -119,7 +121,24 @@ stat := proc.wait()
pid := stat.pid() pid := stat.pid()
``` ```
- `exited() => bool`: port of `os.ProcessState.Exited` function - `exited() => bool`: reports whether the program has exited.
- `pid() => int`: port of `os.ProcessState.Pid` function - `pid() => int`: returns the process id of the exited process.
- `string() => string`: port of `os.ProcessState.String` function - `string() => string`: returns a string representation of the process.
- `success() => bool`: port of `os.ProcessState.Success` function - `success() => bool`: reports whether the program exited successfully, such as with exit status 0 on Unix.
```golang
cmd := exec.command("echo", ["foo", "bar"])
output := cmd.output()
```
## Command
- `combined_output() => bytes/error`: runs the command and returns its combined standard output and standard error.
- `output() => bytes/error`: runs the command and returns its standard output.
- `run() => error`: starts the specified command and waits for it to complete.
- `start() => error`: starts the specified command but does not wait for it to complete.
- `wait() => error`: waits for the command to exit and waits for any copying to stdin or copying from stdout or stderr to complete.
- `set_path(path string)`: sets the path of the command to run.
- `set_dir(dir string)`: sets the working directory of the process.
- `set_env(env [string])`: sets the environment of the process.
- `process() => Process`: returns the underlying process, once started.

58
docs/stdlib-text.md Normal file
View file

@ -0,0 +1,58 @@
# Module - "text"
```golang
text := import("text")
```
## Functions
- `re_match(pattern string, text string) => bool/error`: reports whether the string s contains any match of the regular expression pattern.
- `re_find(pattern string, text string, count int) => [[{text: string, begin: int, end: int}]]/undefined`: returns an array holding all matches, each of which is an array of map object that contains matching text, begin and end (exclusive) index.
- `re_replace(pattern string, text string, repl string) => string/error`: returns a copy of src, replacing matches of the pattern with the replacement string repl.
- `re_split(pattern string, text string, count int) => [string]/error`: slices s into substrings separated by the expression and returns a slice of the substrings between those expression matches.
- `re_compile(pattern string) => Regexp/error`: parses a regular expression and returns, if successful, a Regexp object that can be used to match against text.
- `compare(a string, b string) => int`: returns an integer comparing two strings lexicographically. The result will be 0 if a==b, -1 if a < b, and +1 if a > b.
- `contains(s string, substr string) => bool`: reports whether substr is within s.
- `contains_any(s string, chars string) => bool`: reports whether any Unicode code points in chars are within s.
- `count(s string, substr string) => int`: counts the number of non-overlapping instances of substr in s.
- `equal_fold(s string, t string) => bool`: reports whether s and t, interpreted as UTF-8 strings,
- `fields(s string) => [string]`: splits the string s around each instance of one or more consecutive white space characters, as defined by unicode.IsSpace, returning a slice of substrings of s or an empty slice if s contains only white space.
- `has_prefix(s string, prefix string) => bool`: tests whether the string s begins with prefix.
- `has_suffix(s string, suffix string) => bool`: tests whether the string s ends with suffix.
- `index(s string, substr string) => int`: returns the index of the first instance of substr in s, or -1 if substr is not present in s.
- `index_any(s string, chars string) => int`: returns the index of the first instance of any Unicode code point from chars in s, or -1 if no Unicode code point from chars is present in s.
- `join(arr string, sep string) => string`: concatenates the elements of a to create a single string. The separator string sep is placed between elements in the resulting string.
- `last_index(s string, substr string) => int`: returns the index of the last instance of substr in s, or -1 if substr is not present in s.
- `last_index_any(s string, chars string) => int`: returns the index of the last instance of any Unicode code point from chars in s, or -1 if no Unicode code point from chars is present in s.
- `repeat(s string, count int) => string`: returns a new string consisting of count copies of the string s.
- `replace(s string, old string, new string, n int) => string`: returns a copy of the string s with the first n non-overlapping instances of old replaced by new.
- `split(s string, sep string) => [string]`: slices s into all substrings separated by sep and returns a slice of the substrings between those separators.
- `split_after(s string, sep string) => [string]`: slices s into all substrings after each instance of sep and returns a slice of those substrings.
- `split_after_n(s string, sep string, n int) => [string]`: slices s into substrings after each instance of sep and returns a slice of those substrings.
- `split_n(s string, sep string, n int) => [string]`: slices s into substrings separated by sep and returns a slice of the substrings between those separators.
- `title(s string) => string`: returns a copy of the string s with all Unicode letters that begin words mapped to their title case.
- `to_lower(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their lower case.
- `to_title(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their title case.
- `to_upper(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their upper case.
- `trim_left(s string, cutset string) => string`: returns a slice of the string s with all leading Unicode code points contained in cutset removed.
- `trim_prefix(s string, prefix string) => string`: returns s without the provided leading prefix string.
- `trim_right(s string, cutset string) => string`: returns a slice of the string s, with all trailing Unicode code points contained in cutset removed.
- `trim_space(s string) => string`: returns a slice of the string s, with all leading and trailing white space removed, as defined by Unicode.
- `trim_suffix(s string, suffix string) => string`: returns s without the provided trailing suffix string.
- `atoi(str string) => int/error`: returns the result of ParseInt(s, 10, 0) converted to type int.
- `format_bool(b bool) => string`: returns "true" or "false" according to the value of b.
- `format_float(f float, fmt string, prec int, bits int) => string`: converts the floating-point number f to a string, according to the format fmt and precision prec.
- `format_int(i int, base int) => string`: returns the string representation of i in the given base, for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' for digit values >= 10.
- `itoa(i int) => string`: is shorthand for format_int(i, 10).
- `parse_bool(s string) => bool/error`: returns the boolean value represented by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error.
- `parse_float(s string, bits int) => float/error`: converts the string s to a floating-point number with the precision specified by bitSize: 32 for float32, or 64 for float64. When bitSize=32, the result still has type float64, but it will be convertible to float32 without changing its value.
- `parse_int(s string, base int, bits int) => int/error`: interprets a string s in the given base (0, 2 to 36) and bit size (0 to 64) and returns the corresponding value i.
- `quote(s string) => string`: returns a double-quoted Go string literal representing s. The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for control characters and non-printable characters as defined by IsPrint.
- `unquote(s string) => string/error`: interprets s as a single-quoted, double-quoted, or backquoted Go string literal, returning the string value that s quotes. (If s is single-quoted, it would be a Go character literal; Unquote returns the corresponding one-character string.)
## Regexp
- `match(text string) => bool`: reports whether the string s contains any match of the regular expression pattern.
- `find(text string, count int) => [[{text: string, begin: int, end: int}]]/undefined`: returns an array holding all matches, each of which is an array of map object that contains matching text, begin and end (exclusive) index.
- `replace(src string, repl string) => string`: returns a copy of src, replacing matches of the pattern with the replacement string repl.
- `split(text string, count int) => [string]`: slices s into substrings separated by the expression and returns a slice of the substrings between those expression matches.

79
docs/stdlib-times.md Normal file
View file

@ -0,0 +1,79 @@
# Module - "times"
```golang
times := import("times")
```
## Constants
- `format_ansic`: time format "Mon Jan _2 15:04:05 2006"
- `format_unix_date`: time format "Mon Jan _2 15:04:05 MST 2006"
- `format_ruby_date`: time format "Mon Jan 02 15:04:05 -0700 2006"
- `format_rfc822`: time format "02 Jan 06 15:04 MST"
- `format_rfc822z`: time format "02 Jan 06 15:04 -0700"
- `format_rfc850`: time format "Monday, 02-Jan-06 15:04:05 MST"
- `format_rfc1123`: time format "Mon, 02 Jan 2006 15:04:05 MST"
- `format_rfc1123z`: time format "Mon, 02 Jan 2006 15:04:05 -0700"
- `format_rfc3339`: time format "2006-01-02T15:04:05Z07:00"
- `format_rfc3339_nano`: time format "2006-01-02T15:04:05.999999999Z07:00"
- `format_kitchen`: time format "3:04PM"
- `format_stamp`: time format "Jan _2 15:04:05"
- `format_stamp_milli`: time format "Jan _2 15:04:05.000"
- `format_stamp_micro`: time format "Jan _2 15:04:05.000000"
- `format_stamp_nano`: time format "Jan _2 15:04:05.000000000"
- `nanosecond`
- `microsecond`
- `millisecond`
- `second`
- `minute`
- `hour`
- `january`
- `february`
- `march`
- `april`
- `may`
- `june`
- `july`
- `august`
- `september`
- `october`
- `november`
- `december`
## Functions
- `sleep(duration int)`: pauses the current goroutine for at least the duration d. A negative or zero duration causes Sleep to return immediately.
- `parse_duration(s string) => int`: parses a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
- `since(t time) => int`: returns the time elapsed since t.
- `until(t time) => int`: returns the duration until t.
- `duration_hours(duration int) => float`: returns the duration as a floating point number of hours.
- `duration_minutes(duration int) => float`: returns the duration as a floating point number of minutes.
- `duration_nanoseconds(duration int) => int`: returns the duration as an integer of nanoseconds.
- `duration_seconds(duration int) => float`: returns the duration as a floating point number of seconds.
- `duration_string(duration int) => string`: returns a string representation of duration.
- `month_string(month int) => string`: returns the English name of the month ("January", "February", ...).
- `date(year int, month int, day int, hour int, min int, sec int, nsec int) => time`: returns the Time corresponding to "yyyy-mm-dd hh:mm:ss + nsec nanoseconds". Current location is used.
- `now() => time`: returns the current local time.
- `parse(format string, s string) => time`: parses a formatted string and returns the time value it represents. The layout defines the format by showing how the reference time, defined to be "Mon Jan 2 15:04:05 -0700 MST 2006" would be interpreted if it were the value; it serves as an example of the input format. The same interpretation will then be made to the input string.
- `unix(sec int, nsec int) => time`: returns the local Time corresponding to the given Unix time, sec seconds and nsec nanoseconds since January 1, 1970 UTC.
- `add(t time, duration int) => time`: returns the time t+d.
- `add_date(t time, years int, months int, days int) => time`: returns the time corresponding to adding the given number of years, months, and days to t. For example, AddDate(-1, 2, 3) applied to January 1, 2011 returns March 4, 2010.
- `sub(t time, u time) => int`: returns the duration t-u.
- `after(t time, u time) => bool`: reports whether the time instant t is after u.
- `before(t time, u time) => bool`: reports whether the time instant t is before u.
- `time_year(t time) => int`: returns the year in which t occurs.
- `time_month(t time) => int`: returns the month of the year specified by t.
- `time_day(t time) => int`: returns the day of the month specified by t.
- `time_weekday(t time) => int`: returns the day of the week specified by t.
- `time_hour(t time) => int`: returns the hour within the day specified by t, in the range [0, 23].
- `time_minute(t time) => int`: returns the minute offset within the hour specified by t, in the range [0, 59].
- `time_second(t time) => int`: returns the second offset within the minute specified by t, in the range [0, 59].
- `time_nanosecond(t time) => int`: returns the nanosecond offset within the second specified by t, in the range [0, 999999999].
- `time_unix(t time) => int`: returns t as a Unix time, the number of seconds elapsed since January 1, 1970 UTC. The result does not depend on the location associated with t.
- `time_unix_nano(t time) => int`: returns t as a Unix time, the number of nanoseconds elapsed since January 1, 1970 UTC. The result is undefined if the Unix time in nanoseconds cannot be represented by an int64 (a date before the year 1678 or after 2262). Note that this means the result of calling UnixNano on the zero Time is undefined. The result does not depend on the location associated with t.
- `time_format(t time, format) => string`: returns a textual representation of the time value formatted according to layout, which defines the format by showing how the reference time, defined to be "Mon Jan 2 15:04:05 -0700 MST 2006" would be displayed if it were the value; it serves as an example of the desired output. The same display rules will then be applied to the time value.
- `time_location(t time) => string`: returns the time zone name associated with t.
- `time_string(t time) => string`: returns the time formatted using the format string "2006-01-02 15:04:05.999999999 -0700 MST".
- `is_zero(t time) => bool`: reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC.
- `to_local(t time) => time`: returns t with the location set to local time.
- `to_utc(t time) => time`: returns t with the location set to UTC.

View file

@ -1,7 +1,6 @@
# Standard Library # Standard Library
_Warning: standard library implementations/interfaces are **experimental** and subject to change in the future release._ - [os](https://github.com/d5/tengo/blob/master/docs/stdlib-os.md): platform-independent interface to operating system functionality.
- [text](https://github.com/d5/tengo/blob/master/docs/stdlib-text.md): regular expressions, string conversion, and manipulation
- [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md) - [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md): mathematical constants and functions
- [os](https://github.com/d5/tengo/blob/master/docs/stdlib-os.md) - [times](https://github.com/d5/tengo/blob/master/docs/stdlib-times.md): time-related functions
- [exec](https://github.com/d5/tengo/blob/master/docs/stdlib-exec.md)

View file

@ -6,11 +6,12 @@ import (
// Bool represents a boolean value. // Bool represents a boolean value.
type Bool struct { type Bool struct {
Value bool // this is intentionally non-public to force using objects.TrueValue and FalseValue always
value bool
} }
func (o *Bool) String() string { func (o *Bool) String() string {
if o.Value { if o.value {
return "true" return "true"
} }
@ -30,22 +31,34 @@ func (o *Bool) BinaryOp(op token.Token, rhs Object) (Object, error) {
// Copy returns a copy of the type. // Copy returns a copy of the type.
func (o *Bool) Copy() Object { func (o *Bool) Copy() Object {
v := Bool{Value: o.Value} return o
return &v
} }
// IsFalsy returns true if the value of the type is falsy. // IsFalsy returns true if the value of the type is falsy.
func (o *Bool) IsFalsy() bool { func (o *Bool) IsFalsy() bool {
return !o.Value return !o.value
} }
// Equals returns true if the value of the type // Equals returns true if the value of the type
// is equal to the value of another object. // is equal to the value of another object.
func (o *Bool) Equals(x Object) bool { func (o *Bool) Equals(x Object) bool {
t, ok := x.(*Bool) return o == x
if !ok { }
return false
// GobDecode decodes bool value from input bytes.
func (o *Bool) GobDecode(b []byte) (err error) {
o.value = b[0] == 1
return
}
// GobEncode encodes bool values into bytes.
func (o *Bool) GobEncode() (b []byte, err error) {
if o.value {
b = []byte{1}
} else {
b = []byte{0}
} }
return o.Value == t.Value return
} }

View file

@ -77,7 +77,11 @@ func builtinBool(args ...Object) (Object, error) {
v, ok := ToBool(args[0]) v, ok := ToBool(args[0])
if ok { if ok {
return &Bool{Value: v}, nil if v {
return TrueValue, nil
}
return FalseValue, nil
} }
return UndefinedValue, nil return UndefinedValue, nil
@ -127,3 +131,25 @@ func builtinBytes(args ...Object) (Object, error) {
return UndefinedValue, nil return UndefinedValue, nil
} }
func builtinTime(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Time); ok {
return args[0], nil
}
v, ok := ToTime(args[0])
if ok {
return &Time{Value: v}, nil
}
if argsLen == 2 {
return args[1], nil
}
return UndefinedValue, nil
}

View file

@ -72,6 +72,66 @@ func builtinIsBytes(args ...Object) (Object, error) {
return FalseValue, nil return FalseValue, nil
} }
func builtinIsArray(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Array); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsImmutableArray(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*ImmutableArray); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsMap(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Map); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsImmutableMap(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*ImmutableMap); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsTime(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Time); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsError(args ...Object) (Object, error) { func builtinIsError(args ...Object) (Object, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, ErrWrongNumArguments return nil, ErrWrongNumArguments
@ -89,7 +149,7 @@ func builtinIsUndefined(args ...Object) (Object, error) {
return nil, ErrWrongNumArguments return nil, ErrWrongNumArguments
} }
if _, ok := args[0].(*Undefined); ok { if args[0] == UndefinedValue {
return TrueValue, nil return TrueValue, nil
} }

View file

@ -56,6 +56,10 @@ var Builtins = []NamedBuiltinFunc{
Name: "bytes", Name: "bytes",
Func: builtinBytes, Func: builtinBytes,
}, },
{
Name: "time",
Func: builtinTime,
},
{ {
Name: "is_int", Name: "is_int",
Func: builtinIsInt, Func: builtinIsInt,
@ -80,6 +84,26 @@ var Builtins = []NamedBuiltinFunc{
Name: "is_bytes", Name: "is_bytes",
Func: builtinIsBytes, Func: builtinIsBytes,
}, },
{
Name: "is_array",
Func: builtinIsArray,
},
{
Name: "is_immutable_array",
Func: builtinIsImmutableArray,
},
{
Name: "is_map",
Func: builtinIsMap,
},
{
Name: "is_immutable_map",
Func: builtinIsImmutableMap,
},
{
Name: "is_time",
Func: builtinIsTime,
},
{ {
Name: "is_error", Name: "is_error",
Func: builtinIsError, Func: builtinIsError,

View file

@ -3,11 +3,12 @@ package objects
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"time"
) )
// ToString will try to convert object o to string value. // ToString will try to convert object o to string value.
func ToString(o Object) (v string, ok bool) { func ToString(o Object) (v string, ok bool) {
if _, isUndefined := o.(*Undefined); isUndefined { if o == UndefinedValue {
//ok = false //ok = false
return return
} }
@ -36,7 +37,7 @@ func ToInt(o Object) (v int, ok bool) {
v = int(o.Value) v = int(o.Value)
ok = true ok = true
case *Bool: case *Bool:
if o.Value { if o == TrueValue {
v = 1 v = 1
} }
ok = true ok = true
@ -65,7 +66,7 @@ func ToInt64(o Object) (v int64, ok bool) {
v = int64(o.Value) v = int64(o.Value)
ok = true ok = true
case *Bool: case *Bool:
if o.Value { if o == TrueValue {
v = 1 v = 1
} }
ok = true ok = true
@ -140,6 +141,21 @@ func ToByteSlice(o Object) (v []byte, ok bool) {
return return
} }
// ToTime will try to convert object o to time.Time value.
func ToTime(o Object) (v time.Time, ok bool) {
switch o := o.(type) {
case *Time:
v = o.Value
ok = true
case *Int:
v = time.Unix(o.Value, 0)
ok = true
}
//ok = false
return
}
// objectToInterface attempts to convert an object o to an interface{} value // objectToInterface attempts to convert an object o to an interface{} value
func objectToInterface(o Object) (res interface{}) { func objectToInterface(o Object) (res interface{}) {
switch o := o.(type) { switch o := o.(type) {
@ -150,7 +166,7 @@ func objectToInterface(o Object) (res interface{}) {
case *Float: case *Float:
res = o.Value res = o.Value
case *Bool: case *Bool:
res = o.Value res = o == TrueValue
case *Char: case *Char:
res = o.Value res = o.Value
case *Bytes: case *Bytes:
@ -176,7 +192,7 @@ func objectToInterface(o Object) (res interface{}) {
func FromInterface(v interface{}) (Object, error) { func FromInterface(v interface{}) (Object, error) {
switch v := v.(type) { switch v := v.(type) {
case nil: case nil:
return &Undefined{}, nil return UndefinedValue, nil
case string: case string:
return &String{Value: v}, nil return &String{Value: v}, nil
case int64: case int64:
@ -184,7 +200,10 @@ func FromInterface(v interface{}) (Object, error) {
case int: case int:
return &Int{Value: int64(v)}, nil return &Int{Value: int64(v)}, nil
case bool: case bool:
return &Bool{Value: v}, nil if v {
return TrueValue, nil
}
return FalseValue, nil
case rune: case rune:
return &Char{Value: v}, nil return &Char{Value: v}, nil
case byte: case byte:
@ -220,6 +239,8 @@ func FromInterface(v interface{}) (Object, error) {
arr[i] = vo arr[i] = vo
} }
return &Array{Value: arr}, nil return &Array{Value: arr}, nil
case time.Time:
return &Time{Value: v}, nil
case Object: case Object:
return v, nil return v, nil
} }

View file

@ -41,28 +41,28 @@ func TestFloat_BinaryOp(t *testing.T) {
// float < float // float < float
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := float64(-2); r <= 2.1; r += 0.4 { for r := float64(-2); r <= 2.1; r += 0.4 {
testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Float{Value: r}, &objects.Bool{Value: l < r}) testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Float{Value: r}, boolValue(l < r))
} }
} }
// float > float // float > float
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := float64(-2); r <= 2.1; r += 0.4 { for r := float64(-2); r <= 2.1; r += 0.4 {
testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Float{Value: r}, &objects.Bool{Value: l > r}) testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Float{Value: r}, boolValue(l > r))
} }
} }
// float <= float // float <= float
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := float64(-2); r <= 2.1; r += 0.4 { for r := float64(-2); r <= 2.1; r += 0.4 {
testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Float{Value: r}, &objects.Bool{Value: l <= r}) testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Float{Value: r}, boolValue(l <= r))
} }
} }
// float >= float // float >= float
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := float64(-2); r <= 2.1; r += 0.4 { for r := float64(-2); r <= 2.1; r += 0.4 {
testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Float{Value: r}, &objects.Bool{Value: l >= r}) testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Float{Value: r}, boolValue(l >= r))
} }
} }
@ -99,28 +99,28 @@ func TestFloat_BinaryOp(t *testing.T) {
// float < int // float < int
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := int64(-2); r <= 2; r++ { for r := int64(-2); r <= 2; r++ {
testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Int{Value: r}, &objects.Bool{Value: l < float64(r)}) testBinaryOp(t, &objects.Float{Value: l}, token.Less, &objects.Int{Value: r}, boolValue(l < float64(r)))
} }
} }
// float > int // float > int
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := int64(-2); r <= 2; r++ { for r := int64(-2); r <= 2; r++ {
testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Int{Value: r}, &objects.Bool{Value: l > float64(r)}) testBinaryOp(t, &objects.Float{Value: l}, token.Greater, &objects.Int{Value: r}, boolValue(l > float64(r)))
} }
} }
// float <= int // float <= int
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := int64(-2); r <= 2; r++ { for r := int64(-2); r <= 2; r++ {
testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Int{Value: r}, &objects.Bool{Value: l <= float64(r)}) testBinaryOp(t, &objects.Float{Value: l}, token.LessEq, &objects.Int{Value: r}, boolValue(l <= float64(r)))
} }
} }
// float >= int // float >= int
for l := float64(-2); l <= 2.1; l += 0.4 { for l := float64(-2); l <= 2.1; l += 0.4 {
for r := int64(-2); r <= 2; r++ { for r := int64(-2); r <= 2; r++ {
testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Int{Value: r}, &objects.Bool{Value: l >= float64(r)}) testBinaryOp(t, &objects.Float{Value: l}, token.GreaterEq, &objects.Int{Value: r}, boolValue(l >= float64(r)))
} }
} }
} }

View file

@ -114,28 +114,28 @@ func TestInt_BinaryOp(t *testing.T) {
// int < int // int < int
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := int64(-2); r <= 2; r++ { for r := int64(-2); r <= 2; r++ {
testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Int{Value: r}, &objects.Bool{Value: l < r}) testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Int{Value: r}, boolValue(l < r))
} }
} }
// int > int // int > int
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := int64(-2); r <= 2; r++ { for r := int64(-2); r <= 2; r++ {
testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Int{Value: r}, &objects.Bool{Value: l > r}) testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Int{Value: r}, boolValue(l > r))
} }
} }
// int <= int // int <= int
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := int64(-2); r <= 2; r++ { for r := int64(-2); r <= 2; r++ {
testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Int{Value: r}, &objects.Bool{Value: l <= r}) testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Int{Value: r}, boolValue(l <= r))
} }
} }
// int >= int // int >= int
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := int64(-2); r <= 2; r++ { for r := int64(-2); r <= 2; r++ {
testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Int{Value: r}, &objects.Bool{Value: l >= r}) testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Int{Value: r}, boolValue(l >= r))
} }
} }
@ -172,28 +172,28 @@ func TestInt_BinaryOp(t *testing.T) {
// int < float // int < float
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := float64(-2); r <= 2.1; r += 0.5 { for r := float64(-2); r <= 2.1; r += 0.5 {
testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Float{Value: r}, &objects.Bool{Value: float64(l) < r}) testBinaryOp(t, &objects.Int{Value: l}, token.Less, &objects.Float{Value: r}, boolValue(float64(l) < r))
} }
} }
// int > float // int > float
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := float64(-2); r <= 2.1; r += 0.5 { for r := float64(-2); r <= 2.1; r += 0.5 {
testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Float{Value: r}, &objects.Bool{Value: float64(l) > r}) testBinaryOp(t, &objects.Int{Value: l}, token.Greater, &objects.Float{Value: r}, boolValue(float64(l) > r))
} }
} }
// int <= float // int <= float
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := float64(-2); r <= 2.1; r += 0.5 { for r := float64(-2); r <= 2.1; r += 0.5 {
testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Float{Value: r}, &objects.Bool{Value: float64(l) <= r}) testBinaryOp(t, &objects.Int{Value: l}, token.LessEq, &objects.Float{Value: r}, boolValue(float64(l) <= r))
} }
} }
// int >= float // int >= float
for l := int64(-2); l <= 2; l++ { for l := int64(-2); l <= 2; l++ {
for r := float64(-2); r <= 2.1; r += 0.5 { for r := float64(-2); r <= 2.1; r += 0.5 {
testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Float{Value: r}, &objects.Bool{Value: float64(l) >= r}) testBinaryOp(t, &objects.Int{Value: l}, token.GreaterEq, &objects.Float{Value: r}, boolValue(float64(l) >= r))
} }
} }
} }

View file

@ -2,10 +2,10 @@ package objects
var ( var (
// TrueValue represents a true value. // TrueValue represents a true value.
TrueValue Object = &Bool{Value: true} TrueValue Object = &Bool{value: true}
// FalseValue represents a false value. // FalseValue represents a false value.
FalseValue Object = &Bool{Value: false} FalseValue Object = &Bool{value: false}
// UndefinedValue represents an undefined value. // UndefinedValue represents an undefined value.
UndefinedValue Object = &Undefined{} UndefinedValue Object = &Undefined{}

View file

@ -15,3 +15,11 @@ func testBinaryOp(t *testing.T, lhs objects.Object, op token.Token, rhs objects.
return assert.NoError(t, err) && assert.Equal(t, expected, actual) return assert.NoError(t, err) && assert.Equal(t, expected, actual)
} }
func boolValue(b bool) objects.Object {
if b {
return objects.TrueValue
}
return objects.FalseValue
}

89
objects/time.go Normal file
View file

@ -0,0 +1,89 @@
package objects
import (
"time"
"github.com/d5/tengo/compiler/token"
)
// Time represents a time value.
type Time struct {
Value time.Time
}
func (o *Time) String() string {
return o.Value.String()
}
// TypeName returns the name of the type.
func (o *Time) TypeName() string {
return "time"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (o *Time) BinaryOp(op token.Token, rhs Object) (Object, error) {
switch rhs := rhs.(type) {
case *Int:
switch op {
case token.Add: // time + int => time
if rhs.Value == 0 {
return o, nil
}
return &Time{Value: o.Value.Add(time.Duration(rhs.Value))}, nil
case token.Sub: // time - int => time
if rhs.Value == 0 {
return o, nil
}
return &Time{Value: o.Value.Add(time.Duration(-rhs.Value))}, nil
}
case *Time:
switch op {
case token.Sub: // time - time => int (duration)
return &Int{Value: int64(o.Value.Sub(rhs.Value))}, nil
case token.Less: // time < time => bool
if o.Value.Before(rhs.Value) {
return TrueValue, nil
}
return FalseValue, nil
case token.Greater:
if o.Value.After(rhs.Value) {
return TrueValue, nil
}
return FalseValue, nil
case token.LessEq:
if o.Value.Equal(rhs.Value) || o.Value.Before(rhs.Value) {
return TrueValue, nil
}
return FalseValue, nil
case token.GreaterEq:
if o.Value.Equal(rhs.Value) || o.Value.After(rhs.Value) {
return TrueValue, nil
}
return FalseValue, nil
}
}
return nil, ErrInvalidOperator
}
// Copy returns a copy of the type.
func (o *Time) Copy() Object {
return &Time{Value: o.Value}
}
// IsFalsy returns true if the value of the type is falsy.
func (o *Time) IsFalsy() bool {
return o.Value.IsZero()
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (o *Time) Equals(x Object) bool {
t, ok := x.(*Time)
if !ok {
return false
}
return o.Value.Equal(t.Value)
}

View file

@ -22,7 +22,7 @@ func (o *Undefined) BinaryOp(op token.Token, rhs Object) (Object, error) {
// Copy returns a copy of the type. // Copy returns a copy of the type.
func (o *Undefined) Copy() Object { func (o *Undefined) Copy() Object {
return &Undefined{} return o
} }
// IsFalsy returns true if the value of the type is falsy. // IsFalsy returns true if the value of the type is falsy.
@ -33,7 +33,5 @@ func (o *Undefined) IsFalsy() bool {
// Equals returns true if the value of the type // Equals returns true if the value of the type
// is equal to the value of another object. // is equal to the value of another object.
func (o *Undefined) Equals(x Object) bool { func (o *Undefined) Equals(x Object) bool {
_, ok := x.(*Undefined) return o == x
return ok
} }

View file

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

View file

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

View file

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

View file

@ -2,16 +2,18 @@ package runtime_test
import ( import (
"testing" "testing"
"github.com/d5/tengo/objects"
) )
func TestIf(t *testing.T) { func TestIf(t *testing.T) {
expect(t, `if (true) { out = 10 }`, 10) expect(t, `if (true) { out = 10 }`, 10)
expect(t, `if (false) { out = 10 }`, undefined()) expect(t, `if (false) { out = 10 }`, objects.UndefinedValue)
expect(t, `if (false) { out = 10 } else { out = 20 }`, 20) expect(t, `if (false) { out = 10 } else { out = 20 }`, 20)
expect(t, `if (1) { out = 10 }`, 10) expect(t, `if (1) { out = 10 }`, 10)
expect(t, `if (0) { out = 10 } else { out = 20 }`, 20) expect(t, `if (0) { out = 10 } else { out = 20 }`, 20)
expect(t, `if (1 < 2) { out = 10 }`, 10) expect(t, `if (1 < 2) { out = 10 }`, 10)
expect(t, `if (1 > 2) { out = 10 }`, undefined()) expect(t, `if (1 > 2) { out = 10 }`, objects.UndefinedValue)
expect(t, `if (1 < 2) { out = 10 } else { out = 20 }`, 10) expect(t, `if (1 < 2) { out = 10 } else { out = 20 }`, 10)
expect(t, `if (1 > 2) { out = 10 } else { out = 20 }`, 20) expect(t, `if (1 > 2) { out = 10 } else { out = 20 }`, 20)

View file

@ -159,7 +159,7 @@ func TestIndexable(t *testing.T) {
dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} } dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} }
expectWithSymbols(t, `out = dict["a"]`, "foo", SYM{"dict": dict()}) expectWithSymbols(t, `out = dict["a"]`, "foo", SYM{"dict": dict()})
expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()}) expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()})
expectWithSymbols(t, `out = dict["x"]`, undefined(), SYM{"dict": dict()}) expectWithSymbols(t, `out = dict["x"]`, objects.UndefinedValue, SYM{"dict": dict()})
expectErrorWithSymbols(t, `out = dict[0]`, SYM{"dict": dict()}) expectErrorWithSymbols(t, `out = dict[0]`, SYM{"dict": dict()})
strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} }
@ -173,7 +173,7 @@ func TestIndexable(t *testing.T) {
strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr["three"]`, 2, SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr["three"]`, 2, SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr["four"]`, undefined(), SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr["four"]`, objects.UndefinedValue, SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()})
expectErrorWithSymbols(t, `out = arr[-1]`, SYM{"arr": strArr()}) expectErrorWithSymbols(t, `out = arr[-1]`, SYM{"arr": strArr()})

View file

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

View file

@ -47,17 +47,11 @@ os.remove("./temp")
// exec.command // exec.command
expect(t, ` expect(t, `
exec := import("exec") os := import("os")
cmd := os.exec("echo", "foo", "bar")
echo := func(args) { if !is_error(cmd) {
cmd := exec.command("echo", args) out = cmd.output()
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")) `, []byte("foo bar\n"))
// user modules // user modules

View file

@ -2,12 +2,14 @@ package runtime_test
import ( import (
"testing" "testing"
"github.com/d5/tengo/objects"
) )
func TestSelector(t *testing.T) { func TestSelector(t *testing.T) {
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k1`, 5) expect(t, `a := {k1: 5, k2: "foo"}; out = a.k1`, 5)
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, "foo") expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, "foo")
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, undefined()) expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, objects.UndefinedValue)
expect(t, ` expect(t, `
a := { a := {

View file

@ -133,7 +133,10 @@ func toObject(v interface{}) objects.Object {
case int: // for convenience case int: // for convenience
return &objects.Int{Value: int64(v)} return &objects.Int{Value: int64(v)}
case bool: case bool:
return &objects.Bool{Value: v} if v {
return objects.TrueValue
}
return objects.FalseValue
case rune: case rune:
return &objects.Char{Value: v} return &objects.Char{Value: v}
case byte: // for convenience case byte: // for convenience
@ -326,7 +329,7 @@ func objectZeroCopy(o objects.Object) objects.Object {
case *objects.Map: case *objects.Map:
return &objects.Map{} return &objects.Map{}
case *objects.Undefined: case *objects.Undefined:
return &objects.Undefined{} return objects.UndefinedValue
case *objects.Error: case *objects.Error:
return &objects.Error{} return &objects.Error{}
case *objects.Bytes: case *objects.Bytes:
@ -341,7 +344,3 @@ func objectZeroCopy(o objects.Object) objects.Object {
panic(fmt.Errorf("unknown object type: %s", o.TypeName())) panic(fmt.Errorf("unknown object type: %s", o.TypeName()))
} }
} }
func undefined() *objects.Undefined {
return &objects.Undefined{}
}

View file

@ -1,9 +1,13 @@
package runtime_test package runtime_test
import "testing" import (
"testing"
"github.com/d5/tengo/objects"
)
func TestUndefined(t *testing.T) { func TestUndefined(t *testing.T) {
expect(t, `out = undefined`, undefined()) expect(t, `out = undefined`, objects.UndefinedValue)
expect(t, `out = undefined == undefined`, true) expect(t, `out = undefined == undefined`, true)
expect(t, `out = undefined == 1`, false) expect(t, `out = undefined == 1`, false)
expect(t, `out = 1 == undefined`, false) expect(t, `out = 1 == undefined`, false)

View file

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

View file

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

View file

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

View file

@ -54,13 +54,12 @@ func TestVariable(t *testing.T) {
FloatValue: 0, FloatValue: 0,
BoolValue: true, BoolValue: true,
StringValue: "true", StringValue: "true",
Object: &objects.Bool{Value: true}, Object: objects.TrueValue,
}, },
{ {
Name: "d", Name: "d",
Value: nil, Value: nil,
ValueType: "undefined", ValueType: "undefined",
StringValue: "",
Object: objects.UndefinedValue, Object: objects.UndefinedValue,
IsUndefined: true, IsUndefined: true,
}, },