package tengo import ( "bytes" "fmt" "math" "strconv" "strings" "time" "github.com/d5/tengo/v2/parser" "github.com/d5/tengo/v2/token" ) var ( // TrueValue represents a true value. TrueValue Object = &Bool{value: true} // FalseValue represents a false value. FalseValue Object = &Bool{value: false} // UndefinedValue represents an undefined value. UndefinedValue Object = &Undefined{} ) // Object represents an object in the VM. type Object interface { // TypeName should return the name of the type. TypeName() string // String should return a string representation of the type's value. String() string // BinaryOp should return another object that is the result of a given // binary operator and a right-hand side object. If BinaryOp returns an // error, the VM will treat it as a run-time error. BinaryOp(op token.Token, rhs Object) (Object, error) // IsFalsy should return true if the value of the type should be considered // as falsy. IsFalsy() bool // Equals should return true if the value of the type should be considered // as equal to the value of another object. Equals(another Object) bool // Copy should return a copy of the type (and its value). Copy function // will be used for copy() builtin function which is expected to deep-copy // the values generally. Copy() Object // IndexGet should take an index Object and return a result Object or an // error for indexable objects. Indexable is an object that can take an // index and return an object. If error is returned, the runtime will treat // it as a run-time error and ignore returned value. If Object is not // indexable, ErrNotIndexable should be returned as error. If nil is // returned as value, it will be converted to UndefinedToken value by the // runtime. IndexGet(index Object) (value Object, err error) // IndexSet should take an index Object and a value Object for index // assignable objects. Index assignable is an object that can take an index // and a value on the left-hand side of the assignment statement. If Object // is not index assignable, ErrNotIndexAssignable should be returned as // error. If an error is returned, it will be treated as a run-time error. IndexSet(index, value Object) error // Iterate should return an Iterator for the type. Iterate() Iterator // CanIterate should return whether the Object can be Iterated. CanIterate() bool // Call should take an arbitrary number of arguments and returns a return // value and/or an error, which the VM will consider as a run-time error. Call(args ...Object) (ret Object, err error) // CanCall should return whether the Object can be Called. CanCall() bool } // ObjectImpl represents a default Object Implementation. To defined a new // value type, one can embed ObjectImpl in their type declarations to avoid // implementing all non-significant methods. TypeName() and String() methods // still need to be implemented. type ObjectImpl struct { } // TypeName returns the name of the type. func (o *ObjectImpl) TypeName() string { panic(ErrNotImplemented) } func (o *ObjectImpl) String() string { panic(ErrNotImplemented) } // BinaryOp returns another object that is the result of a given binary // operator and a right-hand side object. func (o *ObjectImpl) BinaryOp(_ token.Token, _ Object) (Object, error) { return nil, ErrInvalidOperator } // Copy returns a copy of the type. func (o *ObjectImpl) Copy() Object { return nil } // IsFalsy returns true if the value of the type is falsy. func (o *ObjectImpl) IsFalsy() bool { return false } // Equals returns true if the value of the type is equal to the value of // another object. func (o *ObjectImpl) Equals(x Object) bool { return o == x } // IndexGet returns an element at a given index. func (o *ObjectImpl) IndexGet(_ Object) (res Object, err error) { return nil, ErrNotIndexable } // IndexSet sets an element at a given index. func (o *ObjectImpl) IndexSet(_, _ Object) (err error) { return ErrNotIndexAssignable } // Iterate returns an iterator. func (o *ObjectImpl) Iterate() Iterator { return nil } // CanIterate returns whether the Object can be Iterated. func (o *ObjectImpl) CanIterate() bool { return false } // Call takes an arbitrary number of arguments and returns a return value // and/or an error. func (o *ObjectImpl) Call(_ ...Object) (ret Object, err error) { return nil, nil } // CanCall returns whether the Object can be Called. func (o *ObjectImpl) CanCall() bool { return false } // Array represents an array of objects. type Array struct { ObjectImpl Value []Object } // TypeName returns the name of the type. func (o *Array) TypeName() string { return "array" } func (o *Array) String() string { var elements []string for _, e := range o.Value { elements = append(elements, e.String()) } return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) } // BinaryOp returns another object that is the result of a given binary // operator and a right-hand side object. func (o *Array) BinaryOp(op token.Token, rhs Object) (Object, error) { if rhs, ok := rhs.(*Array); ok { switch op { case token.Add: if len(rhs.Value) == 0 { return o, nil } return &Array{Value: append(o.Value, rhs.Value...)}, nil } } return nil, ErrInvalidOperator } // Copy returns a copy of the type. func (o *Array) Copy() Object { var c []Object for _, elem := range o.Value { c = append(c, elem.Copy()) } return &Array{Value: c} } // IsFalsy returns true if the value of the type is falsy. func (o *Array) IsFalsy() bool { return len(o.Value) == 0 } // Equals returns true if the value of the type is equal to the value of // another object. func (o *Array) Equals(x Object) bool { var xVal []Object switch x := x.(type) { case *Array: xVal = x.Value case *ImmutableArray: xVal = x.Value default: return false } if len(o.Value) != len(xVal) { return false } for i, e := range o.Value { if !e.Equals(xVal[i]) { return false } } return true } // IndexGet returns an element at a given index. func (o *Array) IndexGet(index Object) (res Object, err error) { intIdx, ok := index.(*Int) if !ok { err = ErrInvalidIndexType return } idxVal := int(intIdx.Value) if idxVal < 0 || idxVal >= len(o.Value) { res = UndefinedValue return } res = o.Value[idxVal] return } // IndexSet sets an element at a given index. func (o *Array) IndexSet(index, value Object) (err error) { intIdx, ok := ToInt(index) if !ok { err = ErrInvalidIndexType return } if intIdx < 0 || intIdx >= len(o.Value) { err = ErrIndexOutOfBounds return } o.Value[intIdx] = value return nil } // Iterate creates an array iterator. func (o *Array) Iterate() Iterator { return &ArrayIterator{ v: o.Value, l: len(o.Value), } } // CanIterate returns whether the Object can be Iterated. func (o *Array) CanIterate() bool { return true } // Bool represents a boolean value. type Bool struct { ObjectImpl // this is intentionally non-public to force using objects.TrueValue and // FalseValue always value bool } func (o *Bool) String() string { if o.value { return "true" } return "false" } // TypeName returns the name of the type. func (o *Bool) TypeName() string { return "bool" } // Copy returns a copy of the type. func (o *Bool) Copy() Object { return o } // IsFalsy returns true if the value of the type is falsy. func (o *Bool) IsFalsy() bool { 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 { return o == x } // 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 } // BuiltinFunction represents a builtin function. type BuiltinFunction struct { ObjectImpl Name string Value CallableFunc } // TypeName returns the name of the type. func (o *BuiltinFunction) TypeName() string { return "builtin-function:" + o.Name } func (o *BuiltinFunction) String() string { return "<builtin-function>" } // Copy returns a copy of the type. func (o *BuiltinFunction) Copy() Object { return &BuiltinFunction{Value: o.Value} } // Equals returns true if the value of the type is equal to the value of // another object. func (o *BuiltinFunction) Equals(_ Object) bool { return false } // Call executes a builtin function. func (o *BuiltinFunction) Call(args ...Object) (Object, error) { return o.Value(args...) } // CanCall returns whether the Object can be Called. func (o *BuiltinFunction) CanCall() bool { return true } // BuiltinModule is an importable module that's written in Go. type BuiltinModule struct { Attrs map[string]Object } // Import returns an immutable map for the module. func (m *BuiltinModule) Import(moduleName string) (interface{}, error) { return m.AsImmutableMap(moduleName), nil } // AsImmutableMap converts builtin module into an immutable map. func (m *BuiltinModule) AsImmutableMap(moduleName string) *ImmutableMap { attrs := make(map[string]Object, len(m.Attrs)) for k, v := range m.Attrs { attrs[k] = v.Copy() } attrs["__module_name__"] = &String{Value: moduleName} return &ImmutableMap{Value: attrs} } // Bytes represents a byte array. type Bytes struct { ObjectImpl Value []byte } func (o *Bytes) String() string { return string(o.Value) } // TypeName returns the name of the type. func (o *Bytes) TypeName() string { return "bytes" } // BinaryOp returns another object that is the result of a given binary // operator and a right-hand side object. func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) { switch op { case token.Add: switch rhs := rhs.(type) { case *Bytes: if len(o.Value)+len(rhs.Value) > MaxBytesLen { return nil, ErrBytesLimit } return &Bytes{Value: append(o.Value, rhs.Value...)}, nil } } return nil, ErrInvalidOperator } // Copy returns a copy of the type. func (o *Bytes) Copy() Object { return &Bytes{Value: append([]byte{}, o.Value...)} } // IsFalsy returns true if the value of the type is falsy. func (o *Bytes) IsFalsy() bool { return len(o.Value) == 0 } // Equals returns true if the value of the type is equal to the value of // another object. func (o *Bytes) Equals(x Object) bool { t, ok := x.(*Bytes) if !ok { return false } return bytes.Equal(o.Value, t.Value) } // IndexGet returns an element (as Int) at a given index. func (o *Bytes) IndexGet(index Object) (res Object, err error) { intIdx, ok := index.(*Int) if !ok { err = ErrInvalidIndexType return } idxVal := int(intIdx.Value) if idxVal < 0 || idxVal >= len(o.Value) { res = UndefinedValue return } res = &Int{Value: int64(o.Value[idxVal])} return } // Iterate creates a bytes iterator. func (o *Bytes) Iterate() Iterator { return &BytesIterator{ v: o.Value, l: len(o.Value), } } // CanIterate returns whether the Object can be Iterated. func (o *Bytes) CanIterate() bool { return true } // Char represents a character value. type Char struct { ObjectImpl Value rune } func (o *Char) String() string { return string(o.Value) } // TypeName returns the name of the type. func (o *Char) TypeName() string { return "char" } // BinaryOp returns another object that is the result of a given binary // operator and a right-hand side object. func (o *Char) BinaryOp(op token.Token, rhs Object) (Object, error) { switch rhs := rhs.(type) { case *Char: switch op { case token.Add: r := o.Value + rhs.Value if r == o.Value { return o, nil } return &Char{Value: r}, nil case token.Sub: r := o.Value - rhs.Value if r == o.Value { return o, nil } return &Char{Value: r}, nil case token.Less: if o.Value < rhs.Value { return TrueValue, nil } return FalseValue, nil case token.Greater: if o.Value > rhs.Value { return TrueValue, nil } return FalseValue, nil case token.LessEq: if o.Value <= rhs.Value { return TrueValue, nil } return FalseValue, nil case token.GreaterEq: if o.Value >= rhs.Value { return TrueValue, nil } return FalseValue, nil } case *Int: switch op { case token.Add: r := o.Value + rune(rhs.Value) if r == o.Value { return o, nil } return &Char{Value: r}, nil case token.Sub: r := o.Value - rune(rhs.Value) if r == o.Value { return o, nil } return &Char{Value: r}, nil case token.Less: if int64(o.Value) < rhs.Value { return TrueValue, nil } return FalseValue, nil case token.Greater: if int64(o.Value) > rhs.Value { return TrueValue, nil } return FalseValue, nil case token.LessEq: if int64(o.Value) <= rhs.Value { return TrueValue, nil } return FalseValue, nil case token.GreaterEq: if int64(o.Value) >= rhs.Value { return TrueValue, nil } return FalseValue, nil } } return nil, ErrInvalidOperator } // Copy returns a copy of the type. func (o *Char) Copy() Object { return &Char{Value: o.Value} } // IsFalsy returns true if the value of the type is falsy. func (o *Char) IsFalsy() bool { return o.Value == 0 } // Equals returns true if the value of the type is equal to the value of // another object. func (o *Char) Equals(x Object) bool { t, ok := x.(*Char) if !ok { return false } return o.Value == t.Value } // CompiledFunction represents a compiled function. type CompiledFunction struct { ObjectImpl Instructions []byte NumLocals int // number of local variables (including function parameters) NumParameters int VarArgs bool SourceMap map[int]parser.Pos Free []*ObjectPtr } // TypeName returns the name of the type. func (o *CompiledFunction) TypeName() string { return "compiled-function" } func (o *CompiledFunction) String() string { return "<compiled-function>" } // Copy returns a copy of the type. func (o *CompiledFunction) Copy() Object { return &CompiledFunction{ Instructions: append([]byte{}, o.Instructions...), NumLocals: o.NumLocals, NumParameters: o.NumParameters, VarArgs: o.VarArgs, Free: append([]*ObjectPtr{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers } } // Equals returns true if the value of the type is equal to the value of // another object. func (o *CompiledFunction) Equals(_ Object) bool { return false } // SourcePos returns the source position of the instruction at ip. func (o *CompiledFunction) SourcePos(ip int) parser.Pos { for ip >= 0 { if p, ok := o.SourceMap[ip]; ok { return p } ip-- } return parser.NoPos } // CanCall returns whether the Object can be Called. func (o *CompiledFunction) CanCall() bool { return true } // Error represents an error value. type Error struct { ObjectImpl Value Object } // TypeName returns the name of the type. func (o *Error) TypeName() string { return "error" } func (o *Error) String() string { if o.Value != nil { return fmt.Sprintf("error: %s", o.Value.String()) } return "error" } // IsFalsy returns true if the value of the type is falsy. func (o *Error) IsFalsy() bool { return true // error is always false. } // Copy returns a copy of the type. func (o *Error) Copy() Object { return &Error{Value: o.Value.Copy()} } // Equals returns true if the value of the type is equal to the value of // another object. func (o *Error) Equals(x Object) bool { return o == x // pointer equality } // IndexGet returns an element at a given index. func (o *Error) IndexGet(index Object) (res Object, err error) { if strIdx, _ := ToString(index); strIdx != "value" { err = ErrInvalidIndexOnError return } res = o.Value return } // Float represents a floating point number value. type Float struct { ObjectImpl Value float64 } func (o *Float) String() string { return strconv.FormatFloat(o.Value, 'f', -1, 64) } // TypeName returns the name of the type. func (o *Float) TypeName() string { return "float" } // BinaryOp returns another object that is the result of a given binary // operator and a right-hand side object. func (o *Float) BinaryOp(op token.Token, rhs Object) (Object, error) { switch rhs := rhs.(type) { case *Float: switch op { case token.Add: r := o.Value + rhs.Value if r == o.Value { return o, nil } return &Float{Value: r}, nil case token.Sub: r := o.Value - rhs.Value if r == o.Value { return o, nil } return &Float{Value: r}, nil case token.Mul: r := o.Value * rhs.Value if r == o.Value { return o, nil } return &Float{Value: r}, nil case token.Quo: r := o.Value / rhs.Value if r == o.Value { return o, nil } return &Float{Value: r}, nil case token.Less: if o.Value < rhs.Value { return TrueValue, nil } return FalseValue, nil case token.Greater: if o.Value > rhs.Value { return TrueValue, nil } return FalseValue, nil case token.LessEq: if o.Value <= rhs.Value { return TrueValue, nil } return FalseValue, nil case token.GreaterEq: if o.Value >= rhs.Value { return TrueValue, nil } return FalseValue, nil } case *Int: switch op { case token.Add: r := o.Value + float64(rhs.Value) if r == o.Value { return o, nil } return &Float{Value: r}, nil case token.Sub: r := o.Value - float64(rhs.Value) if r == o.Value { return o, nil } return &Float{Value: r}, nil case token.Mul: r := o.Value * float64(rhs.Value) if r == o.Value { return o, nil } return &Float{Value: r}, nil case token.Quo: r := o.Value / float64(rhs.Value) if r == o.Value { return o, nil } return &Float{Value: r}, nil case token.Less: if o.Value < float64(rhs.Value) { return TrueValue, nil } return FalseValue, nil case token.Greater: if o.Value > float64(rhs.Value) { return TrueValue, nil } return FalseValue, nil case token.LessEq: if o.Value <= float64(rhs.Value) { return TrueValue, nil } return FalseValue, nil case token.GreaterEq: if o.Value >= float64(rhs.Value) { return TrueValue, nil } return FalseValue, nil } } return nil, ErrInvalidOperator } // Copy returns a copy of the type. func (o *Float) Copy() Object { return &Float{Value: o.Value} } // IsFalsy returns true if the value of the type is falsy. func (o *Float) IsFalsy() bool { return math.IsNaN(o.Value) } // Equals returns true if the value of the type is equal to the value of // another object. func (o *Float) Equals(x Object) bool { t, ok := x.(*Float) if !ok { return false } return o.Value == t.Value } // ImmutableArray represents an immutable array of objects. type ImmutableArray struct { ObjectImpl Value []Object } // TypeName returns the name of the type. func (o *ImmutableArray) TypeName() string { return "immutable-array" } func (o *ImmutableArray) String() string { var elements []string for _, e := range o.Value { elements = append(elements, e.String()) } return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) } // BinaryOp returns another object that is the result of a given binary // operator and a right-hand side object. func (o *ImmutableArray) BinaryOp(op token.Token, rhs Object) (Object, error) { if rhs, ok := rhs.(*ImmutableArray); ok { switch op { case token.Add: return &Array{Value: append(o.Value, rhs.Value...)}, nil } } return nil, ErrInvalidOperator } // Copy returns a copy of the type. func (o *ImmutableArray) Copy() Object { var c []Object for _, elem := range o.Value { c = append(c, elem.Copy()) } return &Array{Value: c} } // IsFalsy returns true if the value of the type is falsy. func (o *ImmutableArray) IsFalsy() bool { return len(o.Value) == 0 } // Equals returns true if the value of the type is equal to the value of // another object. func (o *ImmutableArray) Equals(x Object) bool { var xVal []Object switch x := x.(type) { case *Array: xVal = x.Value case *ImmutableArray: xVal = x.Value default: return false } if len(o.Value) != len(xVal) { return false } for i, e := range o.Value { if !e.Equals(xVal[i]) { return false } } return true } // IndexGet returns an element at a given index. func (o *ImmutableArray) IndexGet(index Object) (res Object, err error) { intIdx, ok := index.(*Int) if !ok { err = ErrInvalidIndexType return } idxVal := int(intIdx.Value) if idxVal < 0 || idxVal >= len(o.Value) { res = UndefinedValue return } res = o.Value[idxVal] return } // Iterate creates an array iterator. func (o *ImmutableArray) Iterate() Iterator { return &ArrayIterator{ v: o.Value, l: len(o.Value), } } // CanIterate returns whether the Object can be Iterated. func (o *ImmutableArray) CanIterate() bool { return true } // ImmutableMap represents an immutable map object. type ImmutableMap struct { ObjectImpl Value map[string]Object } // TypeName returns the name of the type. func (o *ImmutableMap) TypeName() string { return "immutable-map" } func (o *ImmutableMap) String() string { var pairs []string for k, v := range o.Value { pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) } return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) } // Copy returns a copy of the type. func (o *ImmutableMap) Copy() Object { c := make(map[string]Object) for k, v := range o.Value { c[k] = v.Copy() } return &Map{Value: c} } // IsFalsy returns true if the value of the type is falsy. func (o *ImmutableMap) IsFalsy() bool { return len(o.Value) == 0 } // IndexGet returns the value for the given key. func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) { strIdx, ok := ToString(index) if !ok { err = ErrInvalidIndexType return } res, ok = o.Value[strIdx] if !ok { res = UndefinedValue } return } // Equals returns true if the value of the type is equal to the value of // another object. func (o *ImmutableMap) Equals(x Object) bool { var xVal map[string]Object switch x := x.(type) { case *Map: xVal = x.Value case *ImmutableMap: xVal = x.Value default: return false } if len(o.Value) != len(xVal) { return false } for k, v := range o.Value { tv := xVal[k] if !v.Equals(tv) { return false } } return true } // Iterate creates an immutable map iterator. func (o *ImmutableMap) Iterate() Iterator { var keys []string for k := range o.Value { keys = append(keys, k) } return &MapIterator{ v: o.Value, k: keys, l: len(keys), } } // CanIterate returns whether the Object can be Iterated. func (o *ImmutableMap) CanIterate() bool { return true } // Int represents an integer value. type Int struct { ObjectImpl Value int64 } func (o *Int) String() string { return strconv.FormatInt(o.Value, 10) } // TypeName returns the name of the type. func (o *Int) TypeName() string { return "int" } // BinaryOp returns another object that is the result of a given binary // operator and a right-hand side object. func (o *Int) BinaryOp(op token.Token, rhs Object) (Object, error) { switch rhs := rhs.(type) { case *Int: switch op { case token.Add: r := o.Value + rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Sub: r := o.Value - rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Mul: r := o.Value * rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Quo: r := o.Value / rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Rem: r := o.Value % rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.And: r := o.Value & rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Or: r := o.Value | rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Xor: r := o.Value ^ rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.AndNot: r := o.Value &^ rhs.Value if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Shl: r := o.Value << uint64(rhs.Value) if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Shr: r := o.Value >> uint64(rhs.Value) if r == o.Value { return o, nil } return &Int{Value: r}, nil case token.Less: if o.Value < rhs.Value { return TrueValue, nil } return FalseValue, nil case token.Greater: if o.Value > rhs.Value { return TrueValue, nil } return FalseValue, nil case token.LessEq: if o.Value <= rhs.Value { return TrueValue, nil } return FalseValue, nil case token.GreaterEq: if o.Value >= rhs.Value { return TrueValue, nil } return FalseValue, nil } case *Float: switch op { case token.Add: return &Float{Value: float64(o.Value) + rhs.Value}, nil case token.Sub: return &Float{Value: float64(o.Value) - rhs.Value}, nil case token.Mul: return &Float{Value: float64(o.Value) * rhs.Value}, nil case token.Quo: return &Float{Value: float64(o.Value) / rhs.Value}, nil case token.Less: if float64(o.Value) < rhs.Value { return TrueValue, nil } return FalseValue, nil case token.Greater: if float64(o.Value) > rhs.Value { return TrueValue, nil } return FalseValue, nil case token.LessEq: if float64(o.Value) <= rhs.Value { return TrueValue, nil } return FalseValue, nil case token.GreaterEq: if float64(o.Value) >= rhs.Value { return TrueValue, nil } return FalseValue, nil } case *Char: switch op { case token.Add: return &Char{Value: rune(o.Value) + rhs.Value}, nil case token.Sub: return &Char{Value: rune(o.Value) - rhs.Value}, nil case token.Less: if o.Value < int64(rhs.Value) { return TrueValue, nil } return FalseValue, nil case token.Greater: if o.Value > int64(rhs.Value) { return TrueValue, nil } return FalseValue, nil case token.LessEq: if o.Value <= int64(rhs.Value) { return TrueValue, nil } return FalseValue, nil case token.GreaterEq: if o.Value >= int64(rhs.Value) { return TrueValue, nil } return FalseValue, nil } } return nil, ErrInvalidOperator } // Copy returns a copy of the type. func (o *Int) Copy() Object { return &Int{Value: o.Value} } // IsFalsy returns true if the value of the type is falsy. func (o *Int) IsFalsy() bool { return o.Value == 0 } // Equals returns true if the value of the type is equal to the value of // another object. func (o *Int) Equals(x Object) bool { t, ok := x.(*Int) if !ok { return false } return o.Value == t.Value } // Map represents a map of objects. type Map struct { ObjectImpl Value map[string]Object } // TypeName returns the name of the type. func (o *Map) TypeName() string { return "map" } func (o *Map) String() string { var pairs []string for k, v := range o.Value { pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) } return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) } // Copy returns a copy of the type. func (o *Map) Copy() Object { c := make(map[string]Object) for k, v := range o.Value { c[k] = v.Copy() } return &Map{Value: c} } // IsFalsy returns true if the value of the type is falsy. func (o *Map) IsFalsy() bool { return len(o.Value) == 0 } // Equals returns true if the value of the type is equal to the value of // another object. func (o *Map) Equals(x Object) bool { var xVal map[string]Object switch x := x.(type) { case *Map: xVal = x.Value case *ImmutableMap: xVal = x.Value default: return false } if len(o.Value) != len(xVal) { return false } for k, v := range o.Value { tv := xVal[k] if !v.Equals(tv) { return false } } return true } // IndexGet returns the value for the given key. func (o *Map) IndexGet(index Object) (res Object, err error) { strIdx, ok := ToString(index) if !ok { err = ErrInvalidIndexType return } res, ok = o.Value[strIdx] if !ok { res = UndefinedValue } return } // IndexSet sets the value for the given key. func (o *Map) IndexSet(index, value Object) (err error) { strIdx, ok := ToString(index) if !ok { err = ErrInvalidIndexType return } o.Value[strIdx] = value return nil } // Iterate creates a map iterator. func (o *Map) Iterate() Iterator { var keys []string for k := range o.Value { keys = append(keys, k) } return &MapIterator{ v: o.Value, k: keys, l: len(keys), } } // CanIterate returns whether the Object can be Iterated. func (o *Map) CanIterate() bool { return true } // ObjectPtr represents a free variable. type ObjectPtr struct { ObjectImpl Value *Object } func (o *ObjectPtr) String() string { return "free-var" } // TypeName returns the name of the type. func (o *ObjectPtr) TypeName() string { return "<free-var>" } // Copy returns a copy of the type. func (o *ObjectPtr) Copy() Object { return o } // IsFalsy returns true if the value of the type is falsy. func (o *ObjectPtr) IsFalsy() bool { return o.Value == nil } // Equals returns true if the value of the type is equal to the value of // another object. func (o *ObjectPtr) Equals(x Object) bool { return o == x } // String represents a string value. type String struct { ObjectImpl Value string runeStr []rune } // TypeName returns the name of the type. func (o *String) TypeName() string { return "string" } func (o *String) String() string { return strconv.Quote(o.Value) } // BinaryOp returns another object that is the result of a given binary // operator and a right-hand side object. func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) { switch op { case token.Add: switch rhs := rhs.(type) { case *String: if len(o.Value)+len(rhs.Value) > MaxStringLen { return nil, ErrStringLimit } return &String{Value: o.Value + rhs.Value}, nil default: rhsStr := rhs.String() if len(o.Value)+len(rhsStr) > MaxStringLen { return nil, ErrStringLimit } return &String{Value: o.Value + rhsStr}, nil } } return nil, ErrInvalidOperator } // IsFalsy returns true if the value of the type is falsy. func (o *String) IsFalsy() bool { return len(o.Value) == 0 } // Copy returns a copy of the type. func (o *String) Copy() Object { return &String{Value: o.Value} } // Equals returns true if the value of the type is equal to the value of // another object. func (o *String) Equals(x Object) bool { t, ok := x.(*String) if !ok { return false } return o.Value == t.Value } // IndexGet returns a character at a given index. func (o *String) IndexGet(index Object) (res Object, err error) { intIdx, ok := index.(*Int) if !ok { err = ErrInvalidIndexType return } idxVal := int(intIdx.Value) if o.runeStr == nil { o.runeStr = []rune(o.Value) } if idxVal < 0 || idxVal >= len(o.runeStr) { res = UndefinedValue return } res = &Char{Value: o.runeStr[idxVal]} return } // Iterate creates a string iterator. func (o *String) Iterate() Iterator { if o.runeStr == nil { o.runeStr = []rune(o.Value) } return &StringIterator{ v: o.runeStr, l: len(o.runeStr), } } // CanIterate returns whether the Object can be Iterated. func (o *String) CanIterate() bool { return true } // Time represents a time value. type Time struct { ObjectImpl 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) } // Undefined represents an undefined value. type Undefined struct { ObjectImpl } // TypeName returns the name of the type. func (o *Undefined) TypeName() string { return "undefined" } func (o *Undefined) String() string { return "<undefined>" } // Copy returns a copy of the type. func (o *Undefined) Copy() Object { return o } // IsFalsy returns true if the value of the type is falsy. func (o *Undefined) IsFalsy() bool { return true } // Equals returns true if the value of the type is equal to the value of // another object. func (o *Undefined) Equals(x Object) bool { return o == x } // IndexGet returns an element at a given index. func (o *Undefined) IndexGet(_ Object) (Object, error) { return UndefinedValue, nil } // Iterate creates a map iterator. func (o *Undefined) Iterate() Iterator { return o } // CanIterate returns whether the Object can be Iterated. func (o *Undefined) CanIterate() bool { return true } // Next returns true if there are more elements to iterate. func (o *Undefined) Next() bool { return false } // Key returns the key or index value of the current element. func (o *Undefined) Key() Object { return o } // Value returns the value of the current element. func (o *Undefined) Value() Object { return o } // UserFunction represents a user function. type UserFunction struct { ObjectImpl Name string Value CallableFunc EncodingID string } // TypeName returns the name of the type. func (o *UserFunction) TypeName() string { return "user-function:" + o.Name } func (o *UserFunction) String() string { return "<user-function>" } // Copy returns a copy of the type. func (o *UserFunction) Copy() Object { return &UserFunction{Value: o.Value} } // Equals returns true if the value of the type is equal to the value of // another object. func (o *UserFunction) Equals(_ Object) bool { return false } // Call invokes a user function. func (o *UserFunction) Call(args ...Object) (Object, error) { return o.Value(args...) } // CanCall returns whether the Object can be Called. func (o *UserFunction) CanCall() bool { return true }