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)
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.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

View file

@ -15,8 +15,6 @@ import (
// NoError asserts err is not an error.
func NoError(t *testing.T, err error, msg ...interface{}) bool {
t.Helper()
if err == nil {
return true
}
@ -26,8 +24,6 @@ func NoError(t *testing.T, err error, msg ...interface{}) bool {
// Error asserts err is an error.
func Error(t *testing.T, err error, msg ...interface{}) bool {
t.Helper()
if err != nil {
return true
}
@ -37,8 +33,6 @@ func Error(t *testing.T, err error, msg ...interface{}) bool {
// Nil asserts v is nil.
func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
t.Helper()
if v == nil {
return true
}
@ -48,8 +42,6 @@ func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
// True asserts v is true.
func True(t *testing.T, v bool, msg ...interface{}) bool {
t.Helper()
if v {
return true
}
@ -59,8 +51,6 @@ func True(t *testing.T, v bool, msg ...interface{}) bool {
// False asserts vis false.
func False(t *testing.T, v bool, msg ...interface{}) bool {
t.Helper()
if !v {
return true
}
@ -70,8 +60,6 @@ func False(t *testing.T, v bool, msg ...interface{}) bool {
// NotNil asserts v is not nil.
func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
t.Helper()
if v != nil {
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.
func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
t.Helper()
if reflect.TypeOf(expected) == reflect.TypeOf(actual) {
return true
}
@ -92,15 +78,13 @@ func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
// Equal asserts expected and actual are equal.
func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
t.Helper()
if expected == nil {
return Nil(t, actual, "expected nil, but got not nil")
}
if !NotNil(t, actual, "expected not nil, but got nil") {
return false
}
if !IsType(t, expected, actual) {
if !IsType(t, expected, actual, msg...) {
return false
}
@ -123,7 +107,7 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
}
case []byte:
if bytes.Compare(expected, actual.([]byte)) != 0 {
return failExpectedActual(t, expected, actual, msg...)
return failExpectedActual(t, string(expected), string(actual.([]byte)), msg...)
}
case []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...)
}
case []objects.Object:
return equalObjectSlice(t, expected, actual.([]objects.Object))
return equalObjectSlice(t, expected, actual.([]objects.Object), msg...)
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:
return Equal(t, expected.Value, actual.(*objects.Float).Value)
return Equal(t, expected.Value, actual.(*objects.Float).Value, msg...)
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:
return Equal(t, expected.Value, actual.(*objects.Char).Value)
return Equal(t, expected.Value, actual.(*objects.Char).Value, msg...)
case *objects.Bool:
return Equal(t, expected.Value, actual.(*objects.Bool).Value)
if expected != actual {
return failExpectedActual(t, expected, actual, msg...)
}
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:
return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value)
return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value, msg...)
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:
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:
return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value)
return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value, msg...)
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:
return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction))
return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction), msg...)
case *objects.Closure:
return equalClosure(t, expected, actual.(*objects.Closure))
return equalClosure(t, expected, actual.(*objects.Closure), msg...)
case *objects.Undefined:
return true
if expected != actual {
return failExpectedActual(t, expected, actual, msg...)
}
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:
if expected != actual.(error) {
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.
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.Fail()
@ -206,8 +196,6 @@ func Fail(t *testing.T, msg ...interface{}) bool {
}
func failExpectedActual(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
t.Helper()
var addMsg string
if len(msg) > 0 {
addMsg = "\nMessage: " + message(msg...)
@ -256,15 +244,15 @@ func equalSymbol(a, b compiler.Symbol) bool {
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
if !Equal(t, len(expected), len(actual)) {
if !Equal(t, len(expected), len(actual), msg...) {
return false
}
for i := 0; i < len(expected); i++ {
if !Equal(t, expected[i], actual[i]) {
if !Equal(t, expected[i], actual[i], msg...) {
return false
}
}
@ -272,15 +260,15 @@ func equalObjectSlice(t *testing.T, expected, actual []objects.Object) bool {
return true
}
func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bool {
if !Equal(t, len(expected), len(actual)) {
func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool {
if !Equal(t, len(expected), len(actual), msg...) {
return false
}
for key, expectedVal := range expected {
actualVal := actual[key]
if !Equal(t, expectedVal, actualVal) {
if !Equal(t, expectedVal, actualVal, msg...) {
return false
}
}
@ -288,27 +276,27 @@ func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bo
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)
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)
actualT := actual.(*objects.Closure)
if !Equal(t, expectedT.Fn, actualT.Fn) {
if !Equal(t, expectedT.Fn, actualT.Fn, msg...) {
return false
}
if !Equal(t, len(expectedT.Free), len(actualT.Free)) {
if !Equal(t, len(expectedT.Free), len(actualT.Free), msg...) {
return false
}
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
}
}

View file

@ -21,7 +21,16 @@ func (b *Bytecode) Decode(r io.Reader) error {
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.
@ -32,9 +41,32 @@ func (b *Bytecode) Encode(w io.Writer) error {
return err
}
// 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() {
gob.Register(&objects.Int{})
gob.Register(&objects.Float{})
@ -52,4 +84,5 @@ func init() {
gob.Register(&objects.StringIterator{})
gob.Register(&objects.MapIterator{})
gob.Register(&objects.ArrayIterator{})
gob.Register(&objects.Time{})
}

View file

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

View file

@ -43,7 +43,11 @@ func FuncARB(fn func() bool) *objects.UserFunction {
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 &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 &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.
// User function will return 'true' if underlying native function returns nil.
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
// into a user function object.
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
// into a user function object.
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{}
for _, osArg := range res {
arr.Value = append(arr.Value, &objects.String{Value: osArg})
for _, r := range res {
arr.Value = append(arr.Value, &objects.String{Value: r})
}
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 (
"errors"
"strconv"
"strings"
"testing"
"github.com/d5/tengo/assert"
@ -13,7 +15,7 @@ func TestFuncAIR(t *testing.T) {
uf := stdlib.FuncAIR(func(int) {})
ret, err := uf.Call(&objects.Int{Value: 10})
assert.NoError(t, err)
assert.Equal(t, &objects.Undefined{}, ret)
assert.Equal(t, objects.UndefinedValue, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
@ -22,7 +24,7 @@ func TestFuncAR(t *testing.T) {
uf := stdlib.FuncAR(func() {})
ret, err := uf.Call()
assert.NoError(t, err)
assert.Equal(t, &objects.Undefined{}, ret)
assert.Equal(t, objects.UndefinedValue, ret)
ret, err = uf.Call(objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
@ -115,6 +117,15 @@ func TestFuncASRS(t *testing.T) {
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) {
uf := stdlib.FuncASI64RE(func(a string, b int64) error { return nil })
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) {
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) {
@ -227,7 +255,7 @@ func TestFuncAFRB(t *testing.T) {
})
ret, err := uf.Call(&objects.Float{Value: 0.1})
assert.NoError(t, err)
assert.Equal(t, &objects.Bool{Value: true}, ret)
assert.Equal(t, objects.TrueValue, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
ret, err = uf.Call(objects.TrueValue, objects.TrueValue)
@ -247,6 +275,17 @@ func TestFuncAFFRF(t *testing.T) {
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) {
uf := stdlib.FuncAIFRF(func(a int, b float64) float64 {
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})
assert.NoError(t, err)
assert.Equal(t, &objects.Bool{Value: true}, ret)
assert.Equal(t, objects.TrueValue, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
ret, err = uf.Call(objects.TrueValue)
@ -303,6 +342,24 @@ func TestFuncAIRSsE(t *testing.T) {
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) {
uf := stdlib.FuncARB(func() bool { return true })
ret, err := uf.Call()
@ -355,6 +412,42 @@ func TestFuncAYRIE(t *testing.T) {
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 {
return &objects.Array{Value: elements}
}

View file

@ -67,14 +67,8 @@ var mathModule = map[string]objects.Object{
"sqrt": FuncAFRF(math.Sqrt),
"tan": FuncAFRF(math.Tan),
"tanh": FuncAFRF(math.Tanh),
"runct": FuncAFRF(math.Trunc),
"trunc": FuncAFRF(math.Trunc),
"y0": FuncAFRF(math.Y0),
"y1": FuncAFRF(math.Y1),
"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 (
"io"
"os"
"os/exec"
"github.com/d5/tengo/objects"
)
@ -36,82 +37,110 @@ var osModule = map[string]objects.Object{
"seek_set": &objects.Int{Value: int64(io.SeekStart)},
"seek_cur": &objects.Int{Value: int64(io.SeekCurrent)},
"seek_end": &objects.Int{Value: int64(io.SeekEnd)},
// args() => array(string)
"args": &objects.UserFunction{Value: osArgs},
// chdir(dir string) => error
"chdir": FuncASRE(os.Chdir),
// chmod(name string, mode int) => error
"chmod": osFuncASFmRE(os.Chmod),
// chown(name string, uid int, gid int) => error
"chown": FuncASIIRE(os.Chown),
// clearenv()
"clearenv": FuncAR(os.Clearenv),
// environ() => array(string)
"environ": FuncARSs(os.Environ),
// exit(code int)
"exit": FuncAIR(os.Exit),
// expand_env(s string) => string
"expand_env": FuncASRS(os.ExpandEnv),
// getegid() => int
"getegid": FuncARI(os.Getegid),
// getenv(s string) => string
"getenv": FuncASRS(os.Getenv),
// geteuid() => int
"geteuid": FuncARI(os.Geteuid),
// getgid() => int
"getgid": FuncARI(os.Getgid),
// getgroups() => array(string)/error
"getgroups": FuncARIsE(os.Getgroups),
// getpagesize() => int
"getpagesize": FuncARI(os.Getpagesize),
// getpid() => int
"getpid": FuncARI(os.Getpid),
// getppid() => int
"getppid": FuncARI(os.Getppid),
// getuid() => int
"getuid": FuncARI(os.Getuid),
// getwd() => string/error
"getwd": FuncARSE(os.Getwd),
// hostname() => string/error
"hostname": FuncARSE(os.Hostname),
// lchown(name string, uid int, gid int) => error
"lchown": FuncASIIRE(os.Lchown),
// link(oldname string, newname string) => error
"link": FuncASSRE(os.Link),
// lookup_env(key string) => string/false
"lookup_env": &objects.UserFunction{Value: osLookupEnv},
// mkdir(name string, perm int) => error
"mkdir": osFuncASFmRE(os.Mkdir),
// mkdir_all(name string, perm int) => error
"mkdir_all": osFuncASFmRE(os.MkdirAll),
// readlink(name string) => string/error
"readlink": FuncASRSE(os.Readlink),
// remove(name string) => error
"remove": FuncASRE(os.Remove),
// remove_all(name string) => error
"remove_all": FuncASRE(os.RemoveAll),
// rename(oldpath string, newpath string) => error
"rename": FuncASSRE(os.Rename),
// setenv(key string, value string) => error
"setenv": FuncASSRE(os.Setenv),
// symlink(oldname string newname string) => error
"symlink": FuncASSRE(os.Symlink),
// temp_dir() => string
"temp_dir": FuncARS(os.TempDir),
// truncate(name string, size int) => error
"truncate": FuncASI64RE(os.Truncate),
// unsetenv(key string) => error
"unsetenv": FuncASRE(os.Unsetenv),
// create(name string) => imap(file)/error
"create": &objects.UserFunction{Value: osCreate},
// open(name string) => imap(file)/error
"open": &objects.UserFunction{Value: osOpen},
// open_file(name string, flag int, perm int) => imap(file)/error
"open_file": &objects.UserFunction{Value: osOpenFile},
// find_process(pid int) => imap(process)/error
"find_process": &objects.UserFunction{Value: osFindProcess},
// start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
"start_process": &objects.UserFunction{Value: osStartProcess},
"args": &objects.UserFunction{Value: osArgs}, // args() => array(string)
"chdir": FuncASRE(os.Chdir), // chdir(dir string) => error
"chmod": osFuncASFmRE(os.Chmod), // chmod(name string, mode int) => error
"chown": FuncASIIRE(os.Chown), // chown(name string, uid int, gid int) => error
"clearenv": FuncAR(os.Clearenv), // clearenv()
"environ": FuncARSs(os.Environ), // environ() => array(string)
"exit": FuncAIR(os.Exit), // exit(code int)
"expand_env": FuncASRS(os.ExpandEnv), // expand_env(s string) => string
"getegid": FuncARI(os.Getegid), // getegid() => int
"getenv": FuncASRS(os.Getenv), // getenv(s string) => string
"geteuid": FuncARI(os.Geteuid), // geteuid() => int
"getgid": FuncARI(os.Getgid), // getgid() => int
"getgroups": FuncARIsE(os.Getgroups), // getgroups() => array(string)/error
"getpagesize": FuncARI(os.Getpagesize), // getpagesize() => int
"getpid": FuncARI(os.Getpid), // getpid() => int
"getppid": FuncARI(os.Getppid), // getppid() => int
"getuid": FuncARI(os.Getuid), // getuid() => int
"getwd": FuncARSE(os.Getwd), // getwd() => string/error
"hostname": FuncARSE(os.Hostname), // hostname() => string/error
"lchown": FuncASIIRE(os.Lchown), // lchown(name string, uid int, gid int) => error
"link": FuncASSRE(os.Link), // link(oldname string, newname string) => error
"lookup_env": &objects.UserFunction{Value: osLookupEnv}, // lookup_env(key string) => string/false
"mkdir": osFuncASFmRE(os.Mkdir), // mkdir(name string, perm int) => error
"mkdir_all": osFuncASFmRE(os.MkdirAll), // mkdir_all(name string, perm int) => error
"readlink": FuncASRSE(os.Readlink), // readlink(name string) => string/error
"remove": FuncASRE(os.Remove), // remove(name string) => error
"remove_all": FuncASRE(os.RemoveAll), // remove_all(name string) => error
"rename": FuncASSRE(os.Rename), // rename(oldpath string, newpath string) => error
"setenv": FuncASSRE(os.Setenv), // setenv(key string, value string) => error
"symlink": FuncASSRE(os.Symlink), // symlink(oldname string newname string) => error
"temp_dir": FuncARS(os.TempDir), // temp_dir() => string
"truncate": FuncASI64RE(os.Truncate), // truncate(name string, size int) => error
"unsetenv": FuncASRE(os.Unsetenv), // unsetenv(key string) => error
"create": &objects.UserFunction{Value: osCreate}, // create(name string) => imap(file)/error
"open": &objects.UserFunction{Value: osOpen}, // open(name string) => imap(file)/error
"open_file": &objects.UserFunction{Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error
"find_process": &objects.UserFunction{Value: osFindProcess}, // find_process(pid int) => imap(process)/error
"start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
"exec_look_path": FuncASRSE(exec.LookPath), // exec_look_path(file) => string/error
"exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command
}
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 makeOSFile(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 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) {
@ -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) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
@ -178,3 +194,99 @@ func osLookupEnv(args ...objects.Object) (objects.Object, error) {
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"
)
var execModule = map[string]objects.Object{
// look_path(file string) => string/error
"look_path": FuncASRSE(exec.LookPath),
// command(name string, args array(string)) => imap(cmd)
"command": &objects.UserFunction{Value: execCommand},
}
func execCmdImmutableMap(cmd *exec.Cmd) *objects.ImmutableMap {
func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// combined_output() => bytes/error
@ -84,33 +77,9 @@ func execCmdImmutableMap(cmd *exec.Cmd) *objects.ImmutableMap {
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"
)
func osFileImmutableMap(file *os.File) *objects.ImmutableMap {
func makeOSFile(file *os.File) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// chdir() => true/error
@ -66,79 +66,6 @@ func osFileImmutableMap(file *os.File) *objects.ImmutableMap {
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"
)
func osProcessStateImmutableMap(state *os.ProcessState) *objects.ImmutableMap {
func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
"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{
Value: map[string]objects.Object{
"kill": FuncARE(proc.Kill),
@ -48,82 +48,9 @@ func osProcessImmutableMap(proc *os.Process) *objects.ImmutableMap {
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

@ -6,5 +6,6 @@ import "github.com/d5/tengo/objects"
var Modules = map[string]*objects.ImmutableMap{
"math": {Value: mathModule},
"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)
- **Bytes**: byte array (`[]byte` 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)
- **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
- **Undefined**: undefined
## 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**|
|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**|
@ -23,13 +25,15 @@
|Bytes |**X** |string(y)|**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**|
|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**|
|Undefined|**X** |**X**|**X** |false|**X**|**X**|**X**|**X**|**X**|**X**| - |
_* **X**: No conversion; Typed value functions for `script.Variable` will return zero values._
_* strconv: converted using Go's conversion functions from `strconv` package._
_* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_
_* String(): use `Object.String()` function_
_* time.Unix(): use `time.Unix(v, 0)` to convert to Time_
## Object.IsFalsy()
@ -43,7 +47,7 @@ _* IsFalsy(): use [Object.IsFalsy()](#objectisfalsy) function_
- **Bytes**: `len(bytes) == 0`
- **Array**: `len(arr) == 0`
- **Map**: `len(map) == 0`
- **ImmutableMap**: `len(map) == 0`
- **Time**: `Time.IsZero()`
- **Error**: `true` _(Error 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
- `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)
- `time(x)`: tries to convert `x` into time; returns `undefined` if failed
## 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_char(x)`: returns `true` if `x` is char; `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_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")
```
## Variables
## Constants
- `e`: equivalent of Go's `math.E`
- `pi`: equivalent of Go's `math.Pi`
- `phi`: equivalent of Go's `math.Phi`
- `sqrt2`: equivalent of Go's `math.Sqrt2`
- `sqrtE`: equivalent of Go's `math.SqrtE`
- `sprtPi`: equivalent of Go's `math.SqrtPi`
- `sqrtPhi`: equivalent of Go's `math.SqrtPhi`
- `ln2`: equivalent of Go's `math.Ln2`
- `log2E`: equivalent of Go's `math.Log2E`
- `ln10`: equivalent of Go's `math.Ln10`
- `ln10E`: equivalent of Go's `math.Log10E`
- `e`
- `pi`
- `phi`
- `sqrt2`
- `sqrtE`
- `sprtPi`
- `sqrtPhi`
- `ln2`
- `log2E`
- `ln10`
- `ln10E`
## Functions
- `abs(float) => float`: port of Go's `math.Abs` function
- `acos(float) => float`: port of Go's `math.Acos` function
- `acosh(float) => float`: port of Go's `math.Acosh` function
- `asin(float) => float`: port of Go's `math.Asin` function
- `asinh(float) => float`: port of Go's `math.Asinh` function
- `atan(float) => float`: port of Go's `math.Atan` function
- `atan2(float, float) => float`: port of Go's `math.Atan2` function
- `atanh(float) => float`: port of Go's `math.Atanh` function
- `cbrt(float) => float`: port of Go's `math.Cbrt` function
- `ceil(float) => float`: port of Go's `math.Ceil` function
- `copysign(float, float) => float`: port of Go's `math.Copysign` function
- `cos(float) => float`: port of Go's `math.Cos` function
- `cosh(float) => float`: port of Go's `math.Cosh` function
- `dim(float, float) => float`: port of Go's `math.Dim` function
- `erf(float) => float`: port of Go's `math.Erf` function
- `erfc(float) => float`: port of Go's `math.Erfc` function
- `erfcinv(float) => float`: port of Go's `math.Erfcinv` function
- `erfinv(float) => float`: port of Go's `math.Erfinv` function
- `exp(float) => float`: port of Go's `math.Exp` function
- `exp2(float) => float`: port of Go's `math.Exp2` function
- `expm1(float) => float`: port of Go's `math.Expm1` function
- `floor(float) => float`: port of Go's `math.Floor` function
- `gamma(float) => float`: port of Go's `math.Gamma` function
- `hypot(float, float) => float`: port of Go's `math.Hypot` function
- `ilogb(float) => float`: port of Go's `math.Ilogb` function
- `inf(int) => float`: port of Go's `math.Inf` function
- `is_inf(float, int) => float`: port of Go's `math.IsInf` function
- `is_nan(float) => float`: port of Go's `math.IsNaN` function
- `j0(float) => float`: port of Go's `math.J0` function
- `j1(float) => float`: port of Go's `math.J1` function
- `jn(int, float) => float`: port of Go's `math.Jn` function
- `ldexp(float, int) => float`: port of Go's `math.Ldexp` function
- `log(float) => float`: port of Go's `math.Log` function
- `log10(float) => float`: port of Go's `math.Log10` function
- `log1p(float) => float`: port of Go's `math.Log1p` function
- `log2(float) => float`: port of Go's `math.Log2` function
- `logb(float) => float`: port of Go's `math.Logb` function
- `max(float, float) => float`: port of Go's `math.Max` function
- `min(float, float) => float`: port of Go's `math.Min` function
- `mod(float, float) => float`: port of Go's `math.Mod` function
- `nan() => float`: port of Go's `math.NaN` function
- `nextafter(float, float) => float`: port of Go's `math.Nextafter` function
- `pow(float, float) => float`: port of Go's `math.Pow` function
- `pow10(int) => float`: port of Go's `math.Pow10` function
- `remainder(float, float) => float`: port of Go's `math.Remainder` function
- `round(float) => float`: port of Go's `math.Round` function
- `round_to_even(float) => float`: port of Go's `math.RoundToEven` function
- `signbit(float) => float`: port of Go's `math.Signbit` function
- `sin(float) => float`: port of Go's `math.Sin` function
- `sinh(float) => float`: port of Go's `math.Sinh` function
- `sqrt(float) => float`: port of Go's `math.Sqrt` function
- `tan(float) => float`: port of Go's `math.Tan` function
- `tanh(float) => float`: port of Go's `math.Tanh` function
- `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
- `abs(x float) => float`: returns the absolute value of x.
- `acos(x float) => float`: returns the arccosine, in radians, of x.
- `acosh(x float) => float`: returns the inverse hyperbolic cosine of x.
- `asin(x float) => float`: returns the arcsine, in radians, of x.
- `asinh(x float) => float`: returns the inverse hyperbolic sine of x.
- `atan(x float) => float`: returns the arctangent, in radians, of x.
- `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(x float) => float`: returns the inverse hyperbolic tangent of x.
- `cbrt(x float) => float`: returns the cube root of x.
- `ceil(x float) => float`: returns the least integer value greater than or equal to x.
- `copysign(x float, y float) => float`: returns a value with the magnitude of x and the sign of y.
- `cos(x float) => float`: returns the cosine of the radian argument x.
- `cosh(x float) => float`: returns the hyperbolic cosine of x.
- `dim(x float, y float) => float`: returns the maximum of x-y or 0.
- `erf(x float) => float`: returns the error function of x.
- `erfc(x float) => float`: returns the complementary error function of x.
- `exp(x float) => float`: returns e**x, the base-e exponential of x.
- `exp2(x float) => float`: returns 2**x, the base-2 exponential of x.
- `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.
- `floor(x float) => float`: returns the greatest integer value less than or equal to x.
- `gamma(x float) => float`: returns the Gamma function of x.
- `hypot(p float, q float) => float`: returns Sqrt(p * p + q * q), taking care to avoid unnecessary overflow and underflow.
- `ilogb(x float) => float`: returns the binary exponent of x as an integer.
- `inf(sign int) => float`: returns positive infinity if sign >= 0, negative infinity if sign < 0.
- `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.
- `is_nan(f float) => float`: reports whether f is an IEEE 754 ``not-a-number'' value.
- `j0(x float) => float`: returns the order-zero Bessel function of the first kind.
- `j1(x float) => float`: returns the order-one Bessel function of the first kind.
- `jn(n int, x float) => float`: returns the order-n Bessel function of the first kind.
- `ldexp(frac float, exp int) => float`: is the inverse of frexp. It returns frac × 2**exp.
- `log(x float) => float`: returns the natural logarithm of x.
- `log10(x float) => float`: returns the decimal logarithm of x.
- `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.
- `log2(x float) => float`: returns the binary logarithm of x.
- `logb(x float) => float`: returns the binary exponent of x.
- `max(x float, y float) => float`: returns the larger of x or y.
- `min(x float, y float) => float`: returns the smaller of x or y.
- `mod(x float, y float) => float`: returns the floating-point remainder of x/y.
- `nan() => float`: returns an IEEE 754 ``not-a-number'' value.
- `nextafter(x float, y float) => float`: returns the next representable float64 value after x towards y.
- `pow(x float, y float) => float`: returns x**y, the base-x exponential of y.
- `pow10(n int) => float`: returns 10**n, the base-10 exponential of n.
- `remainder(x float, y float) => float`: returns the IEEE 754 floating-point remainder of x/y.
- `signbit(x float) => float`: returns true if x is negative or negative zero.
- `sin(x float) => float`: returns the sine of the radian argument x.
- `sinh(x float) => float`: returns the hyperbolic sine of x.
- `sqrt(x float) => float`: returns the square root of x.
- `tan(x float) => float`: returns the tangent of the radian argument x.
- `tanh(x float) => float`: returns the hyperbolic tangent of x.
- `trunc(x float) => float`: returns the integer value of x.
- `y0(x float) => float`: returns the order-zero Bessel function of the second kind.
- `y1(x float) => float`: returns the order-one Bessel function of the second kind.
- `yn(n int, x float) => float`: returns the order-n Bessel function of the second kind.

View file

@ -4,82 +4,84 @@
os := import("os")
```
## Module Variables
## Constants
- `o_rdonly`: equivalent of Go's `os.O_RDONLY`
- `o_wronly`: equivalent of Go's `os.O_WRONLY`
- `o_rdwr`: equivalent of Go's `os.O_RDWR`
- `o_append`: equivalent of Go's `os.O_APPEND`
- `o_create`: equivalent of Go's `os.O_CREATE`
- `o_excl`: equivalent of Go's `os.O_EXCL`
- `o_sync`: equivalent of Go's `os.O_SYNC`
- `o_trunc`: equivalent of Go's `os.O_TRUNC`
- `mode_dir`: equivalent of Go's `os.ModeDir`
- `mode_append`: equivalent of Go's `os.ModeAppend`
- `mode_exclusive`: equivalent of Go's `os.ModeExclusive`
- `mode_temporary`: equivalent of Go's `os.ModeTemporary`
- `mode_symlink`: equivalent of Go's `os.ModeSymlink`
- `mode_device`: equivalent of Go's `os.ModeDevice`
- `mode_named_pipe`: equivalent of Go's `os.ModeNamedPipe`
- `mode_socket`: equivalent of Go's `os.ModeSocket`
- `mode_setuid`: equivalent of Go's `os.ModeSetuid`
- `mode_setgui`: equivalent of Go's `os.ModeSetgid`
- `mode_char_device`: equivalent of Go's `os.ModeCharDevice`
- `mode_sticky`: equivalent of Go's `os.ModeSticky`
- `mode_irregular`: equivalent of Go's `os.ModeIrregular`
- `mode_type`: equivalent of Go's `os.ModeType`
- `mode_perm`: equivalent of Go's `os.ModePerm`
- `seek_set`: equivalent of Go's `os.SEEK_SET`
- `seek_cur`: equivalent of Go's `os.SEEK_CUR`
- `seek_end`: equivalent of Go's `os.SEEK_END`
- `path_separator`: equivalent of Go's `os.PathSeparator`
- `path_list_separator`: equivalent of Go's `os.PathListSeparator`
- `dev_null`: equivalent of Go's `os.DevNull`
- `o_rdonly`
- `o_wronly`
- `o_rdwr`
- `o_append`
- `o_create`
- `o_excl`
- `o_sync`
- `o_trunc`
- `mode_dir`
- `mode_append`
- `mode_exclusive`
- `mode_temporary`
- `mode_symlink`
- `mode_device`
- `mode_named_pipe`
- `mode_socket`
- `mode_setuid`
- `mode_setgui`
- `mode_char_device`
- `mode_sticky`
- `mode_irregular`
- `mode_type`
- `mode_perm`
- `seek_set`
- `seek_cur`
- `seek_end`
- `path_separator`
- `path_list_separator`
- `dev_null`
## Module Functions
- `args() => array(string)`: returns `os.Args`
- `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
## Functions
## 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
file := os.create("myfile")
@ -87,31 +89,31 @@ file.write_string("some data")
file.close()
```
- `chdir() => true/error`: port of `os.File.Chdir` function
- `chown(uid int, gid int) => true/error`: port of `os.File.Chown` function
- `close() => error`: port of `os.File.Close` function
- `name() => string`: port of `os.File.Name` function
- `readdirnames() => array(string)/error`: port of `os.File.Readdirnames` function
- `sync() => error`: port of `os.File.Sync` function
- `write(bytes) => int/error`: port of `os.File.Write` function
- `write_string(string) => int/error`: port of `os.File.WriteString` function
- `read(bytes) => int/error`: port of `os.File.Read` function
- `chmod(mode int) => error`: port of `os.File.Chmod` function
- `seek(offset int, whence int) => int/error`: port of `os.File.Seek` function
- `chdir() => true/error`: changes the current working directory to the file,
- `chown(uid int, gid int) => true/error`: changes the numeric uid and gid of the named file.
- `close() => error`: closes the File, rendering it unusable for I/O.
- `name() => string`: returns the name of the file as presented to Open.
- `readdirnames(n int) => [string]/error`: reads and returns a slice of names from the directory.
- `sync() => error`: commits the current contents of the file to stable storage.
- `write(bytes) => int/error`: writes len(b) bytes to the File.
- `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`: reads up to len(b) bytes from the File.
- `chmod(mode int) => error`: changes the mode of the file to mode.
- `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
proc := start_process("app", ["arg1", "arg2"], "dir", [])
proc.wait()
```
- `kill() => error`: port of `os.Process.Kill` function
- `release() => error`: port of `os.Process.Release` function
- `signal(signal int) => error`: port of `os.Process.Signal` function
- `wait() => ProcessState/error`: port of `os.Process.Wait` function
- `kill() => error`: causes the Process to exit immediately.
- `release() => error`: releases any resources associated with the process, rendering it unusable in the future.
- `signal(signal int) => error`: sends a signal to the Process.
- `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
proc := start_process("app", ["arg1", "arg2"], "dir", [])
@ -119,7 +121,24 @@ stat := proc.wait()
pid := stat.pid()
```
- `exited() => bool`: port of `os.ProcessState.Exited` function
- `pid() => int`: port of `os.ProcessState.Pid` function
- `string() => string`: port of `os.ProcessState.String` function
- `success() => bool`: port of `os.ProcessState.Success` function
- `exited() => bool`: reports whether the program has exited.
- `pid() => int`: returns the process id of the exited process.
- `string() => string`: returns a string representation of the process.
- `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
_Warning: standard library implementations/interfaces are **experimental** and subject to change in the future release._
- [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md)
- [os](https://github.com/d5/tengo/blob/master/docs/stdlib-os.md)
- [exec](https://github.com/d5/tengo/blob/master/docs/stdlib-exec.md)
- [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): mathematical constants and functions
- [times](https://github.com/d5/tengo/blob/master/docs/stdlib-times.md): time-related functions

View file

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

View file

@ -77,7 +77,11 @@ func builtinBool(args ...Object) (Object, error) {
v, ok := ToBool(args[0])
if ok {
return &Bool{Value: v}, nil
if v {
return TrueValue, nil
}
return FalseValue, nil
}
return UndefinedValue, nil
@ -127,3 +131,25 @@ func builtinBytes(args ...Object) (Object, error) {
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
}
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) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
@ -89,7 +149,7 @@ func builtinIsUndefined(args ...Object) (Object, error) {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Undefined); ok {
if args[0] == UndefinedValue {
return TrueValue, nil
}

View file

@ -56,6 +56,10 @@ var Builtins = []NamedBuiltinFunc{
Name: "bytes",
Func: builtinBytes,
},
{
Name: "time",
Func: builtinTime,
},
{
Name: "is_int",
Func: builtinIsInt,
@ -80,6 +84,26 @@ var Builtins = []NamedBuiltinFunc{
Name: "is_bytes",
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",
Func: builtinIsError,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,16 +2,18 @@ package runtime_test
import (
"testing"
"github.com/d5/tengo/objects"
)
func TestIf(t *testing.T) {
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 (1) { out = 10 }`, 10)
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 }`, 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 }`, 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"}} }
expectWithSymbols(t, `out = dict["a"]`, "foo", 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()})
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"}} }
expectWithSymbols(t, `out = arr["one"]`, 0, 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[1]`, "two", SYM{"arr": strArr()})
expectErrorWithSymbols(t, `out = arr[-1]`, SYM{"arr": strArr()})

View file

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

View file

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

View file

@ -2,12 +2,14 @@ package runtime_test
import (
"testing"
"github.com/d5/tengo/objects"
)
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.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, `
a := {

View file

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

View file

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

View file

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

View file

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

View file

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