add Time type; add is_array, is_immutable_array, is_map, is_immutable_map, is_time, time builtin function

This commit is contained in:
Daniel Kang 2019-01-29 16:01:14 -08:00
parent f752601ff2
commit 378bf510d3
9 changed files with 233 additions and 5 deletions

View file

@ -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...)

View file

@ -84,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"
@ -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},

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_
_* 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

@ -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
}

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

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,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
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)
}