add Time type; add is_array, is_immutable_array, is_map, is_immutable_map, is_time, time builtin function
This commit is contained in:
parent
f752601ff2
commit
378bf510d3
9 changed files with 233 additions and 5 deletions
|
@ -171,6 +171,10 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
|
|||
}
|
||||
case *objects.Error:
|
||||
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...)
|
||||
|
|
|
@ -84,4 +84,5 @@ func init() {
|
|||
gob.Register(&objects.StringIterator{})
|
||||
gob.Register(&objects.MapIterator{})
|
||||
gob.Register(&objects.ArrayIterator{})
|
||||
gob.Register(&objects.Time{})
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package compiler_test
|
|||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/d5/tengo/assert"
|
||||
"github.com/d5/tengo/compiler"
|
||||
|
@ -15,6 +16,7 @@ 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},
|
||||
|
|
|
@ -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_
|
||||
_* 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
|
|
@ -131,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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -3,6 +3,7 @@ package objects
|
|||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ToString will try to convert object o to string value.
|
||||
|
@ -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) {
|
||||
|
|
89
objects/time.go
Normal file
89
objects/time.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue