diff --git a/builtins/builtins.go b/builtins/builtins.go index d6e68a54..24c434f3 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -181,7 +181,7 @@ func String(ctx context.Context, args ...object.Object) object.Object { return object.NewError(err) } return object.NewString(string(arg.Value())) - case *object.BSlice: + case *object.ByteSlice: if err := lim.TrackCost(argCost); err != nil { return object.NewError(err) } @@ -202,12 +202,54 @@ func String(ctx context.Context, args ...object.Object) object.Object { } } -func BSlice(ctx context.Context, args ...object.Object) object.Object { - if err := arg.RequireRange("bslice", 0, 1, args); err != nil { +func FloatSlice(ctx context.Context, args ...object.Object) object.Object { + if err := arg.RequireRange("float_slice", 0, 1, args); err != nil { return err } if len(args) == 0 { - return object.NewBSlice(nil) + return object.NewFloatSlice(nil) + } + arg := args[0] + argCost := arg.Cost() + if err := limits.TrackCost(ctx, argCost); err != nil { + return object.NewError(err) + } + switch arg := arg.(type) { + case *object.FloatSlice: + return arg.Clone() + case *object.Int: + val := arg.Value() + if err := limits.TrackCost(ctx, int(val)-argCost); err != nil { + return object.NewError(err) + } + return object.NewFloatSlice(make([]float64, val)) + case *object.List: + items := arg.Value() + floats := make([]float64, len(items)) + for i, item := range items { + switch item := item.(type) { + case *object.Byte: + floats[i] = float64(item.Value()) + case *object.Int: + floats[i] = float64(item.Value()) + case *object.Float: + floats[i] = item.Value() + default: + return object.Errorf("type error: float_slice() list item unsupported (%s given)", item.Type()) + } + } + return object.NewFloatSlice(floats) + default: + return object.Errorf("type error: float_slice() unsupported argument (%s given)", args[0].Type()) + } +} + +func ByteSlice(ctx context.Context, args ...object.Object) object.Object { + if err := arg.RequireRange("byte_slice", 0, 1, args); err != nil { + return err + } + if len(args) == 0 { + return object.NewByteSlice(nil) } arg := args[0] argCost := arg.Cost() @@ -216,30 +258,33 @@ func BSlice(ctx context.Context, args ...object.Object) object.Object { } switch arg := arg.(type) { case *object.Buffer: - return object.NewBSlice(arg.Value()) - case *object.BSlice: + return object.NewByteSlice(arg.Value()) + case *object.ByteSlice: return arg.Clone() case *object.String: - return object.NewBSlice([]byte(arg.Value())) + return object.NewByteSlice([]byte(arg.Value())) case *object.Int: val := arg.Value() if err := limits.TrackCost(ctx, int(val)-argCost); err != nil { return object.NewError(err) } - return object.NewBSlice(make([]byte, val)) + return object.NewByteSlice(make([]byte, val)) case *object.List: items := arg.Value() bytes := make([]byte, len(items)) for i, item := range items { - intObj, ok := item.(*object.Int) - if !ok { - return object.Errorf("type error: bslice() list argument should contain only ints (got %s)", item.Type()) + switch item := item.(type) { + case *object.Int: + bytes[i] = byte(item.Value()) + case *object.Byte: + bytes[i] = item.Value() + default: + return object.Errorf("type error: byte_slice() list item unsupported (%s given)", item.Type()) } - bytes[i] = byte(intObj.Value()) } - return object.NewBSlice(bytes) + return object.NewByteSlice(bytes) default: - return object.Errorf("type error: bslice() unsupported argument (%s given)", args[0].Type()) + return object.Errorf("type error: byte_slice() unsupported argument (%s given)", args[0].Type()) } } @@ -261,7 +306,7 @@ func Buffer(ctx context.Context, args ...object.Object) object.Object { return object.NewError(err) } return object.NewBufferFromBytes(arg.Value()) - case *object.BSlice: + case *object.ByteSlice: if err := lim.TrackCost(arg.Cost()); err != nil { return object.NewError(err) } @@ -331,7 +376,7 @@ func Any(ctx context.Context, args ...object.Object) object.Object { return object.True } } - case *object.BSlice: + case *object.ByteSlice: for _, val := range arg.Value() { if val != 0 { return object.True @@ -377,7 +422,7 @@ func All(ctx context.Context, args ...object.Object) object.Object { return object.False } } - case *object.BSlice: + case *object.ByteSlice: for _, val := range arg.Value() { if val == 0 { return object.False @@ -437,7 +482,7 @@ func Sorted(ctx context.Context, args ...object.Object) object.Object { items = arg.List().Value() case *object.String: items = arg.Runes() - case *object.BSlice: + case *object.ByteSlice: items = arg.Integers() default: return object.Errorf("type error: sorted() unsupported argument (%s given)", arg.Type()) @@ -463,7 +508,7 @@ func Reversed(ctx context.Context, args ...object.Object) object.Object { return arg.Reversed() case *object.String: return arg.Reversed() - case *object.BSlice: + case *object.ByteSlice: return arg.Reversed() default: return object.Errorf("type error: reversed() unsupported argument (%s given)", arg.Type()) @@ -541,6 +586,30 @@ func Keys(ctx context.Context, args ...object.Object) object.Object { } } +func Byte(ctx context.Context, args ...object.Object) object.Object { + if err := arg.RequireRange("byte", 0, 1, args); err != nil { + return err + } + if len(args) == 0 { + return object.NewByte(0) + } + switch obj := args[0].(type) { + case *object.Int: + return object.NewByte(byte(obj.Value())) + case *object.Byte: + return object.NewByte(obj.Value()) + case *object.Float: + return object.NewByte(byte(obj.Value())) + case *object.String: + if i, err := strconv.ParseInt(obj.Value(), 0, 8); err == nil { + return object.NewByte(byte(i)) + } + return object.Errorf("value error: invalid literal for byte(): %q", obj.Value()) + default: + return object.Errorf("type error: byte() unsupported argument (%s given)", args[0].Type()) + } +} + func Int(ctx context.Context, args ...object.Object) object.Object { if err := arg.RequireRange("int", 0, 1, args); err != nil { return err @@ -551,6 +620,8 @@ func Int(ctx context.Context, args ...object.Object) object.Object { switch obj := args[0].(type) { case *object.Int: return obj + case *object.Byte: + return object.NewInt(int64(obj.Value())) case *object.Float: return object.NewInt(int64(obj.Value())) case *object.String: @@ -558,12 +629,6 @@ func Int(ctx context.Context, args ...object.Object) object.Object { return object.NewInt(i) } return object.Errorf("value error: invalid literal for int(): %q", obj.Value()) - case *object.BSlice: - val := obj.Value() - if len(val) != 1 { - return object.Errorf("value error: bslice must be exactly one byte long") - } - return object.NewInt(int64(val[0])) default: return object.Errorf("type error: int() unsupported argument (%s given)", args[0].Type()) } @@ -579,6 +644,8 @@ func Float(ctx context.Context, args ...object.Object) object.Object { switch obj := args[0].(type) { case *object.Int: return object.NewFloat(float64(obj.Value())) + case *object.Byte: + return object.NewFloat(float64(obj.Value())) case *object.Float: return obj case *object.String: @@ -709,34 +776,36 @@ func CodeObj(ctx context.Context, args ...object.Object) object.Object { func Builtins() map[string]object.Object { return map[string]object.Object{ - "all": object.NewBuiltin("all", All), - "any": object.NewBuiltin("any", Any), - "assert": object.NewBuiltin("assert", Assert), - "bool": object.NewBuiltin("bool", Bool), - "bslice": object.NewBuiltin("bslice", BSlice), - "buffer": object.NewBuiltin("buffer", Buffer), - "call": object.NewBuiltin("call", Call), - "chr": object.NewBuiltin("chr", Chr), - "codeobj": object.NewBuiltin("codeobj", CodeObj), - "decode": object.NewBuiltin("decode", Decode), - "delete": object.NewBuiltin("delete", Delete), - "encode": object.NewBuiltin("encode", Encode), - "error": object.NewBuiltin("error", Error), - "float": object.NewBuiltin("float", Float), - "getattr": object.NewBuiltin("getattr", GetAttr), - "int": object.NewBuiltin("int", Int), - "iter": object.NewBuiltin("iter", Iter), - "keys": object.NewBuiltin("keys", Keys), - "len": object.NewBuiltin("len", Len), - "list": object.NewBuiltin("list", List), - "map": object.NewBuiltin("map", Map), - "ord": object.NewBuiltin("ord", Ord), - "reversed": object.NewBuiltin("reversed", Reversed), - "set": object.NewBuiltin("set", Set), - "sorted": object.NewBuiltin("sorted", Sorted), - "sprintf": object.NewBuiltin("sprintf", Sprintf), - "string": object.NewBuiltin("string", String), - "try": object.NewBuiltin("try", Try), - "type": object.NewBuiltin("type", Type), + "all": object.NewBuiltin("all", All), + "any": object.NewBuiltin("any", Any), + "assert": object.NewBuiltin("assert", Assert), + "bool": object.NewBuiltin("bool", Bool), + "byte": object.NewBuiltin("byte", Byte), + "byte_slice": object.NewBuiltin("byte_slice", ByteSlice), + "buffer": object.NewBuiltin("buffer", Buffer), + "call": object.NewBuiltin("call", Call), + "chr": object.NewBuiltin("chr", Chr), + "codeobj": object.NewBuiltin("codeobj", CodeObj), + "decode": object.NewBuiltin("decode", Decode), + "delete": object.NewBuiltin("delete", Delete), + "encode": object.NewBuiltin("encode", Encode), + "error": object.NewBuiltin("error", Error), + "float": object.NewBuiltin("float", Float), + "float_slice": object.NewBuiltin("float_slice", FloatSlice), + "getattr": object.NewBuiltin("getattr", GetAttr), + "int": object.NewBuiltin("int", Int), + "iter": object.NewBuiltin("iter", Iter), + "keys": object.NewBuiltin("keys", Keys), + "len": object.NewBuiltin("len", Len), + "list": object.NewBuiltin("list", List), + "map": object.NewBuiltin("map", Map), + "ord": object.NewBuiltin("ord", Ord), + "reversed": object.NewBuiltin("reversed", Reversed), + "set": object.NewBuiltin("set", Set), + "sorted": object.NewBuiltin("sorted", Sorted), + "sprintf": object.NewBuiltin("sprintf", Sprintf), + "string": object.NewBuiltin("string", String), + "try": object.NewBuiltin("try", Try), + "type": object.NewBuiltin("type", Type), } } diff --git a/builtins/codecs.go b/builtins/codecs.go index 05276b79..2cf9d9e6 100644 --- a/builtins/codecs.go +++ b/builtins/codecs.go @@ -228,7 +228,7 @@ func decodeBase64(ctx context.Context, obj object.Object) object.Object { if decodeErr != nil { return object.NewError(decodeErr) } - return object.NewBSlice(dst[:count]) + return object.NewByteSlice(dst[:count]) } func decodeBase32(ctx context.Context, obj object.Object) object.Object { @@ -242,7 +242,7 @@ func decodeBase32(ctx context.Context, obj object.Object) object.Object { if decodeErr != nil { return object.NewError(decodeErr) } - return object.NewBSlice(dst[:count]) + return object.NewByteSlice(dst[:count]) } func decodeHex(ctx context.Context, obj object.Object) object.Object { @@ -255,7 +255,7 @@ func decodeHex(ctx context.Context, obj object.Object) object.Object { if decodeErr != nil { return object.NewError(decodeErr) } - return object.NewBSlice(dst[:count]) + return object.NewByteSlice(dst[:count]) } func decodeJSON(ctx context.Context, obj object.Object) object.Object { diff --git a/modules/aws/client.go b/modules/aws/client.go index ad423665..2c4b532c 100644 --- a/modules/aws/client.go +++ b/modules/aws/client.go @@ -39,6 +39,10 @@ func (c *Client) GetAttr(name string) (object.Object, bool) { return NewMethod(methodName, c.client, method), true } +func (c *Client) SetAttr(name string, value object.Object) error { + return fmt.Errorf("attribute error: aws.client object has no attribute %q", name) +} + func (c *Client) Interface() interface{} { return c.client } @@ -67,7 +71,7 @@ func (c *Client) RunOperation(opType op.BinaryOpType, right object.Object) objec } func (c *Client) Cost() int { - return 8 + return 0 } func NewClient(service string, client interface{}) *Client { diff --git a/modules/aws/config.go b/modules/aws/config.go index aadab3a9..a87b9285 100644 --- a/modules/aws/config.go +++ b/modules/aws/config.go @@ -163,6 +163,10 @@ func (c *Config) GetAttr(name string) (object.Object, bool) { return nil, false } +func (c *Config) SetAttr(name string, value object.Object) error { + return fmt.Errorf("attribute error: aws.config object has no attribute %q", name) +} + func (c *Config) Interface() interface{} { return c.cfg } @@ -191,7 +195,7 @@ func (c *Config) RunOperation(opType op.BinaryOpType, right object.Object) objec } func (c *Config) Cost() int { - return 8 + return 0 } func NewConfig(cfg aws.Config) *Config { diff --git a/modules/base64/base64.go b/modules/base64/base64.go index c57d4d03..27b57555 100644 --- a/modules/base64/base64.go +++ b/modules/base64/base64.go @@ -91,7 +91,7 @@ func Decode(ctx context.Context, args ...object.Object) object.Object { if decodeErr != nil { return object.NewError(decodeErr) } - return object.NewBSlice(dst[:count]) + return object.NewByteSlice(dst[:count]) } func URLDecode(ctx context.Context, args ...object.Object) object.Object { @@ -122,7 +122,7 @@ func URLDecode(ctx context.Context, args ...object.Object) object.Object { if decodeErr != nil { return object.NewError(decodeErr) } - return object.NewBSlice(dst[:count]) + return object.NewByteSlice(dst[:count]) } func Module() *object.Module { diff --git a/modules/base64/base64_test.go b/modules/base64/base64_test.go index cfdf28c2..5d0f5cda 100644 --- a/modules/base64/base64_test.go +++ b/modules/base64/base64_test.go @@ -70,8 +70,8 @@ func TestBase64Decode(t *testing.T) { for _, test := range tests { got := Decode(context.Background(), object.NewString(test.input)) ideal, _ := base64.StdEncoding.DecodeString(test.input) - require.Equal(t, object.NewBSlice([]byte(test.want)), got) - require.Equal(t, object.NewBSlice([]byte(ideal)), got) + require.Equal(t, object.NewByteSlice([]byte(test.want)), got) + require.Equal(t, object.NewByteSlice([]byte(ideal)), got) } } @@ -101,15 +101,15 @@ func TestBase64DecodeRaw(t *testing.T) { require.Equal(t, test.wantErr, gotErr.Value().Error()) require.Equal(t, test.wantErr, err.Error()) } else { - require.Equal(t, object.NewBSlice([]byte(test.want)), got) - require.Equal(t, object.NewBSlice([]byte(ideal)), got) + require.Equal(t, object.NewByteSlice([]byte(test.want)), got) + require.Equal(t, object.NewByteSlice([]byte(ideal)), got) } } } func TestBase64URLEncoded(t *testing.T) { - input := object.NewBSlice([]byte{251}) + input := object.NewByteSlice([]byte{251}) got := URLEncode(context.Background(), input) require.Equal(t, object.NewString("-w=="), got) @@ -118,8 +118,8 @@ func TestBase64URLEncoded(t *testing.T) { require.Equal(t, object.NewString("-w"), got) got = URLDecode(context.Background(), object.NewString("-w==")) - require.Equal(t, object.NewBSlice([]byte{251}), got) + require.Equal(t, object.NewByteSlice([]byte{251}), got) got = URLDecode(context.Background(), object.NewString("-w"), object.False) - require.Equal(t, object.NewBSlice([]byte{251}), got) + require.Equal(t, object.NewByteSlice([]byte{251}), got) } diff --git a/modules/bytes/bytes.go b/modules/bytes/bytes.go index 3c4903b1..f98d241a 100644 --- a/modules/bytes/bytes.go +++ b/modules/bytes/bytes.go @@ -7,10 +7,10 @@ import ( "github.com/cloudcmds/tamarin/v2/object" ) -func asBytes(obj object.Object) (*object.BSlice, *object.Error) { - b, ok := obj.(*object.BSlice) +func asBytes(obj object.Object) (*object.ByteSlice, *object.Error) { + b, ok := obj.(*object.ByteSlice) if !ok { - return nil, object.Errorf("type error: expected a bslice (got %v)", obj.Type()) + return nil, object.Errorf("type error: expected a byte_slice (%s given)", obj.Type()) } return b, nil } diff --git a/modules/hash/hash.go b/modules/hash/hash.go index 09dba196..670cd836 100644 --- a/modules/hash/hash.go +++ b/modules/hash/hash.go @@ -29,7 +29,7 @@ func Hash(ctx context.Context, args ...object.Object) object.Object { } } var h hash.Hash - // Hash `data` using the algorithm specified by `alg` and return the result as a BSlice. + // Hash `data` using the algorithm specified by `alg` and return the result as a byte_slice. // Support `alg` values: sha256, sha512, sha1, md5 switch alg { case "sha256": @@ -44,7 +44,7 @@ func Hash(ctx context.Context, args ...object.Object) object.Object { return object.Errorf("type error: hash() algorithm must be one of sha256, sha512, sha1, md5") } h.Write(data) - return object.NewBSlice(h.Sum(nil)) + return object.NewByteSlice(h.Sum(nil)) } diff --git a/modules/image/color.go b/modules/image/color.go deleted file mode 100644 index 9753196f..00000000 --- a/modules/image/color.go +++ /dev/null @@ -1,86 +0,0 @@ -package image - -import ( - "context" - "errors" - "fmt" - "image/color" - - "github.com/cloudcmds/tamarin/v2/object" - "github.com/cloudcmds/tamarin/v2/op" -) - -type Color struct { - c color.Color -} - -func (c *Color) Inspect() string { - return c.String() -} - -func (c *Color) Type() object.Type { - return "image.color" -} - -func (c *Color) Value() color.Color { - return c.c -} - -func (c *Color) GetAttr(name string) (object.Object, bool) { - switch name { - case "rgba": - return object.NewBuiltin("color.rgba", - func(ctx context.Context, args ...object.Object) object.Object { - if len(args) != 0 { - return object.NewArgsError("color.rgba", 0, len(args)) - } - r, g, b, a := c.c.RGBA() - return object.NewList([]object.Object{ - object.NewInt(int64(r)), - object.NewInt(int64(g)), - object.NewInt(int64(b)), - object.NewInt(int64(a)), - }) - }), true - } - return nil, false -} - -func (c *Color) Interface() interface{} { - return c.c -} - -func (c *Color) String() string { - r, g, b, a := c.c.RGBA() - return fmt.Sprintf("color(r=%d g=%d b=%d a=%d)", r, g, b, a) -} - -func (c *Color) Compare(other object.Object) (int, error) { - return 0, errors.New("type error: unable to compare colors") -} - -func (c *Color) Equals(other object.Object) object.Object { - switch other := other.(type) { - case *Color: - if c.c == other.c { - return object.True - } - } - return object.False -} - -func (c *Color) IsTruthy() bool { - return true -} - -func (c *Color) RunOperation(opType op.BinaryOpType, right object.Object) object.Object { - return object.NewError(fmt.Errorf("eval error: unsupported operation for color: %v ", opType)) -} - -func (c *Color) Cost() int { - return 8 -} - -func NewColor(c color.Color) *Color { - return &Color{c: c} -} diff --git a/modules/image/image.go b/modules/image/image.go index 3fceee49..f674dec7 100644 --- a/modules/image/image.go +++ b/modules/image/image.go @@ -60,7 +60,7 @@ func Encode(ctx context.Context, args ...object.Object) object.Object { if err := encoder(writer, img.Value()); err != nil { return object.NewError(err) } - return object.NewBSlice(buf.Bytes()) + return object.NewByteSlice(buf.Bytes()) } func Module() *object.Module { diff --git a/modules/image/image_object.go b/modules/image/image_object.go index d0a31e09..b90a14de 100644 --- a/modules/image/image_object.go +++ b/modules/image/image_object.go @@ -74,12 +74,16 @@ func (img *Image) GetAttr(name string) (object.Object, bool) { if err != nil { return object.Errorf("type error: image.at() expects argument 2 to be an integer") } - return NewColor(img.image.At(int(x), int(y))) + return object.NewColor(img.image.At(int(x), int(y))) }), true } return nil, false } +func (img *Image) SetAttr(name string, value object.Object) error { + return fmt.Errorf("attribute error: image object has no attribute %q", name) +} + func (img *Image) Interface() interface{} { return img.image } diff --git a/modules/os/os.go b/modules/os/os.go index 02e7cbd7..ef816c04 100644 --- a/modules/os/os.go +++ b/modules/os/os.go @@ -229,7 +229,7 @@ func ReadFile(ctx context.Context, args ...object.Object) object.Object { if ioErr != nil { return object.NewError(ioErr) } - return object.NewBSlice(bytes) + return object.NewByteSlice(bytes) } func WriteFile(ctx context.Context, args ...object.Object) object.Object { @@ -242,12 +242,12 @@ func WriteFile(ctx context.Context, args ...object.Object) object.Object { } var data []byte switch arg := args[1].(type) { - case *object.BSlice: + case *object.ByteSlice: data = arg.Value() case *object.String: data = []byte(arg.Value()) default: - return object.NewError(fmt.Errorf("type error: expected bslice or string (got %s)", args[1].Type())) + return object.NewError(fmt.Errorf("type error: expected byte_slice or string (got %s)", args[1].Type())) } perm, err := object.AsInt(args[2]) if err != nil { diff --git a/modules/pgx/pgx_conn.go b/modules/pgx/pgx_conn.go index ec064577..aebe4450 100644 --- a/modules/pgx/pgx_conn.go +++ b/modules/pgx/pgx_conn.go @@ -64,8 +64,12 @@ func (c *PgxConn) GetAttr(name string) (object.Object, bool) { return nil, false } +func (c *PgxConn) SetAttr(name string, value object.Object) error { + return fmt.Errorf("attribute error: pgx.conn object has no attribute %q", name) +} + func (c *PgxConn) RunOperation(opType op.BinaryOpType, right object.Object) object.Object { - return object.NewError(fmt.Errorf("eval error: unsupported operation for pgx_conn: %v", opType)) + return object.NewError(fmt.Errorf("eval error: unsupported operation for pgx.conn: %v", opType)) } func (c *PgxConn) Close() error { diff --git a/object/base.go b/object/base.go new file mode 100644 index 00000000..208ee2d0 --- /dev/null +++ b/object/base.go @@ -0,0 +1,23 @@ +package object + +import ( + "fmt" +) + +type base struct{} + +func (b *base) GetAttr(name string) (Object, bool) { + return nil, false +} + +func (b *base) SetAttr(name string, value Object) error { + return fmt.Errorf("attribute error: object has no attribute %q", name) +} + +func (b *base) IsTruthy() bool { + return true +} + +func (b *base) Cost() int { + return 0 +} diff --git a/object/bool.go b/object/bool.go index 95873984..3182a3a0 100644 --- a/object/bool.go +++ b/object/bool.go @@ -8,7 +8,7 @@ import ( // Bool wraps bool and implements Object and Hashable interface. type Bool struct { - // value holds the boolean value we wrap. + *base value bool } @@ -34,10 +34,6 @@ func (b *Bool) HashKey() HashKey { return HashKey{Type: b.Type(), IntValue: value} } -func (b *Bool) GetAttr(name string) (Object, bool) { - return nil, false -} - func (b *Bool) Interface() interface{} { return b.value } @@ -76,10 +72,6 @@ func (b *Bool) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for bool: %v", opType)) } -func (b *Bool) Cost() int { - return 0 -} - func NewBool(value bool) *Bool { if value { return True diff --git a/object/bool_test.go b/object/bool_test.go new file mode 100644 index 00000000..5cb43614 --- /dev/null +++ b/object/bool_test.go @@ -0,0 +1,18 @@ +package object_test + +import ( + "testing" + + "github.com/cloudcmds/tamarin/v2/object" + "github.com/stretchr/testify/require" +) + +func TestBool(t *testing.T) { + b := object.NewBool(true) + obj, ok := b.GetAttr("foo") + require.False(t, ok) + require.Nil(t, obj) + + // err := b.SetAttr("foo", object.NewInt(int64(1))) + // require.Error(t, err) +} diff --git a/object/bslice_iter.go b/object/bslice_iter.go deleted file mode 100644 index 416b817b..00000000 --- a/object/bslice_iter.go +++ /dev/null @@ -1,116 +0,0 @@ -package object - -import ( - "context" - "fmt" - - "github.com/cloudcmds/tamarin/v2/op" -) - -type BSliceIter struct { - b *BSlice - pos int64 - current Object -} - -func (iter *BSliceIter) Type() Type { - return BSLICE_ITER -} - -func (iter *BSliceIter) Inspect() string { - return fmt.Sprintf("bslice_iter(%s)", iter.b.Inspect()) -} - -func (iter *BSliceIter) String() string { - return iter.Inspect() -} - -func (iter *BSliceIter) Interface() interface{} { - var entries []map[string]interface{} - for { - entry, ok := iter.Next() - if !ok { - break - } - entries = append(entries, entry.Interface().(map[string]interface{})) - } - return entries -} - -func (iter *BSliceIter) Equals(other Object) Object { - switch other := other.(type) { - case *BSliceIter: - return NewBool(iter == other) - default: - return False - } -} - -func (iter *BSliceIter) GetAttr(name string) (Object, bool) { - switch name { - case "next": - return &Builtin{ - name: "bslice_iter.next", - fn: func(ctx context.Context, args ...Object) Object { - if len(args) != 0 { - return NewArgsError("bslice_iter.next", 0, len(args)) - } - value, ok := iter.Next() - if !ok { - return Nil - } - return value - }, - }, true - case "entry": - return &Builtin{ - name: "bslice_iter.entry", - fn: func(ctx context.Context, args ...Object) Object { - if len(args) != 0 { - return NewArgsError("bslice_iter.entry", 0, len(args)) - } - entry, ok := iter.Entry() - if !ok { - return Nil - } - return entry - }, - }, true - } - return nil, false -} - -func (iter *BSliceIter) IsTruthy() bool { - return iter.pos < int64(len(iter.b.value)) -} - -func (iter *BSliceIter) Next() (Object, bool) { - data := iter.b.value - if iter.pos >= int64(len(data)-1) { - iter.current = nil - return nil, false - } - iter.pos++ - value := data[iter.pos] - iter.current = NewBSlice([]byte{value}) - return iter.current, true -} - -func (iter *BSliceIter) Entry() (IteratorEntry, bool) { - if iter.current == nil { - return nil, false - } - return NewEntry(NewInt(iter.pos), iter.current), true -} - -func (iter *BSliceIter) RunOperation(opType op.BinaryOpType, right Object) Object { - return NewError(fmt.Errorf("eval error: unsupported operation for bslice_iter: %v", opType)) -} - -func (iter *BSliceIter) Cost() int { - return 0 -} - -func NewBytesIter(b *BSlice) *BSliceIter { - return &BSliceIter{b: b, pos: -1} -} diff --git a/object/buffer.go b/object/buffer.go index 2db00604..afc56039 100644 --- a/object/buffer.go +++ b/object/buffer.go @@ -8,6 +8,7 @@ import ( ) type Buffer struct { + *base value *bytes.Buffer } @@ -23,10 +24,6 @@ func (b *Buffer) Value() []byte { return b.value.Bytes() } -func (b *Buffer) GetAttr(name string) (Object, bool) { - return nil, false -} - func (b *Buffer) Interface() interface{} { return b.value } @@ -41,7 +38,7 @@ func (b *Buffer) Compare(other Object) (int, error) { return bytes.Compare(b.value.Bytes(), other.value.Bytes()), nil case *String: return bytes.Compare(b.value.Bytes(), []byte(other.Value())), nil - case *BSlice: + case *ByteSlice: return bytes.Compare(b.value.Bytes(), other.Value()), nil default: return 0, fmt.Errorf("type error: cannot compare buffer to type %s", other.Type()) @@ -65,8 +62,8 @@ func (b *Buffer) RunOperation(opType op.BinaryOpType, right Object) Object { return b.runOperationBytes(opType, right) case *String: return b.runOperationString(opType, right) - case *BSlice: - return b.runOperationBSlice(opType, right) + case *ByteSlice: + return b.runOperationByteSlice(opType, right) default: return NewError(fmt.Errorf("eval error: unsupported operation for buffer: %v on type %s", opType, right.Type())) } @@ -84,7 +81,7 @@ func (b *Buffer) runOperationBytes(opType op.BinaryOpType, right *Buffer) Object } } -func (b *Buffer) runOperationBSlice(opType op.BinaryOpType, right *BSlice) Object { +func (b *Buffer) runOperationByteSlice(opType op.BinaryOpType, right *ByteSlice) Object { switch opType { case op.Add: if _, err := b.value.Write(right.value); err != nil { diff --git a/object/builtin.go b/object/builtin.go index 0e9f8c60..b909764f 100644 --- a/object/builtin.go +++ b/object/builtin.go @@ -12,6 +12,8 @@ type BuiltinFunction func(ctx context.Context, args ...Object) Object // Builtin wraps func and implements Object interface. type Builtin struct { + *base + // The function that this object wraps. fn BuiltinFunction @@ -95,18 +97,10 @@ func (b *Builtin) Equals(other Object) Object { return False } -func (b *Builtin) IsTruthy() bool { - return true -} - func (b *Builtin) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for builtin: %v", opType)) } -func (b *Builtin) Cost() int { - return 0 -} - // NewNoopBuiltin creates a builtin function that has no effect. func NewNoopBuiltin(name string, module *Module) *Builtin { b := &Builtin{ diff --git a/object/byte.go b/object/byte.go new file mode 100644 index 00000000..59d99537 --- /dev/null +++ b/object/byte.go @@ -0,0 +1,140 @@ +package object + +import ( + "fmt" + "math" + + "github.com/cloudcmds/tamarin/v2/op" +) + +// Byte wraps byte and implements Object and Hashable interface. +type Byte struct { + *base + value byte +} + +func (b *Byte) Type() Type { + return BYTE +} + +func (b *Byte) Value() byte { + return b.value +} + +func (b *Byte) Inspect() string { + return fmt.Sprintf("%d", b.value) +} + +func (b *Byte) HashKey() HashKey { + return HashKey{Type: b.Type(), IntValue: int64(b.value)} +} + +func (b *Byte) Interface() interface{} { + return b.value +} + +func (b *Byte) String() string { + return fmt.Sprintf("byte(%d)", b.value) +} + +func (b *Byte) Compare(other Object) (int, error) { + typeComp := CompareTypes(b, other) + if typeComp != 0 { + return typeComp, nil + } + otherByte := other.(*Byte) + if b.value == otherByte.value { + return 0, nil + } else if b.value > otherByte.value { + return 1, nil + } + return -1, nil +} + +func (b *Byte) Equals(other Object) Object { + switch other := other.(type) { + case *Byte: + if b.value == other.value { + return True + } + return False + } + return False +} + +func (b *Byte) IsTruthy() bool { + return b.value > 0 +} + +func (b *Byte) RunOperation(opType op.BinaryOpType, right Object) Object { + switch right := right.(type) { + case *Byte: + return b.runOperationByte(opType, right.value) + case *Int: + return b.runOperationInt(opType, right.value) + default: + return NewError(fmt.Errorf("eval error: unsupported operation for byte: %v on type %s", opType, right.Type())) + } +} + +func (b *Byte) runOperationByte(opType op.BinaryOpType, right byte) Object { + switch opType { + case op.Add: + return NewByte(b.value + right) + case op.Subtract: + return NewByte(b.value - right) + case op.Multiply: + return NewByte(b.value * right) + case op.Divide: + return NewByte(b.value / right) + case op.Modulo: + return NewByte(b.value % right) + case op.Xor: + return NewByte(b.value ^ right) + case op.Power: + return NewByte(byte(math.Pow(float64(b.value), float64(right)))) + case op.LShift: + return NewByte(b.value << right) + case op.RShift: + return NewByte(b.value >> right) + case op.BitwiseAnd: + return NewByte(b.value & right) + case op.BitwiseOr: + return NewByte(b.value | right) + default: + return NewError(fmt.Errorf("eval error: unsupported operation for byte: %v on type byte", opType)) + } +} + +func (b *Byte) runOperationInt(opType op.BinaryOpType, right int64) Object { + switch opType { + case op.Add: + return NewInt(int64(b.value) + right) + case op.Subtract: + return NewInt(int64(b.value) - right) + case op.Multiply: + return NewInt(int64(b.value) * right) + case op.Divide: + return NewInt(int64(b.value) / right) + case op.Modulo: + return NewInt(int64(b.value) % right) + case op.Xor: + return NewInt(int64(b.value) ^ right) + case op.Power: + return NewInt(int64(math.Pow(float64(b.value), float64(right)))) + case op.LShift: + return NewInt(int64(b.value) << right) + case op.RShift: + return NewInt(int64(b.value) >> right) + case op.BitwiseAnd: + return NewInt(int64(b.value) & right) + case op.BitwiseOr: + return NewInt(int64(b.value) | right) + default: + return NewError(fmt.Errorf("eval error: unsupported operation for byte: %v on type int", opType)) + } +} + +func NewByte(value byte) *Byte { + return &Byte{value: value} +} diff --git a/object/bslice.go b/object/byte_slice.go similarity index 59% rename from object/bslice.go rename to object/byte_slice.go index 1e17e13f..121bbdb1 100644 --- a/object/bslice.go +++ b/object/byte_slice.go @@ -8,174 +8,175 @@ import ( "github.com/cloudcmds/tamarin/v2/op" ) -type BSlice struct { +type ByteSlice struct { + *base value []byte } -func (b *BSlice) Inspect() string { - return fmt.Sprintf("bslice(%q)", b.value) +func (b *ByteSlice) Inspect() string { + return fmt.Sprintf("byte_slice(%q)", b.value) } -func (b *BSlice) Type() Type { - return BSLICE +func (b *ByteSlice) Type() Type { + return BYTE_SLICE } -func (b *BSlice) Value() []byte { +func (b *ByteSlice) Value() []byte { return b.value } -func (b *BSlice) HashKey() HashKey { +func (b *ByteSlice) HashKey() HashKey { return HashKey{Type: b.Type(), StrValue: string(b.value)} } -func (b *BSlice) GetAttr(name string) (Object, bool) { +func (b *ByteSlice) GetAttr(name string) (Object, bool) { switch name { case "clone": return &Builtin{ - name: "bslice.clone", + name: "byte_slice.clone", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 0 { - return NewArgsError("bslice.clone", 0, len(args)) + return NewArgsError("byte_slice.clone", 0, len(args)) } return b.Clone() }, }, true case "equals": return &Builtin{ - name: "bslice.equals", + name: "byte_slice.equals", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.equals", 1, len(args)) + return NewArgsError("byte_slice.equals", 1, len(args)) } return b.Equals(args[0]) }, }, true case "contains": return &Builtin{ - name: "bslice.contains", + name: "byte_slice.contains", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.contains", 1, len(args)) + return NewArgsError("byte_slice.contains", 1, len(args)) } return b.Contains(args[0]) }, }, true case "contains_any": return &Builtin{ - name: "bslice.contains_any", + name: "byte_slice.contains_any", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.contains_any", 1, len(args)) + return NewArgsError("byte_slice.contains_any", 1, len(args)) } return b.ContainsAny(args[0]) }, }, true case "contains_rune": return &Builtin{ - name: "bslice.contains_rune", + name: "byte_slice.contains_rune", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.contains_rune", 1, len(args)) + return NewArgsError("byte_slice.contains_rune", 1, len(args)) } return b.ContainsRune(args[0]) }, }, true case "count": return &Builtin{ - name: "bslice.count", + name: "byte_slice.count", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.count", 1, len(args)) + return NewArgsError("byte_slice.count", 1, len(args)) } return b.Count(args[0]) }, }, true case "has_prefix": return &Builtin{ - name: "bslice.has_prefix", + name: "byte_slice.has_prefix", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.has_prefix", 1, len(args)) + return NewArgsError("byte_slice.has_prefix", 1, len(args)) } return b.HasPrefix(args[0]) }, }, true case "has_suffix": return &Builtin{ - name: "bslice.has_suffix", + name: "byte_slice.has_suffix", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.has_suffix", 1, len(args)) + return NewArgsError("byte_slice.has_suffix", 1, len(args)) } return b.HasSuffix(args[0]) }, }, true case "index": return &Builtin{ - name: "bslice.index", + name: "byte_slice.index", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.index", 1, len(args)) + return NewArgsError("byte_slice.index", 1, len(args)) } return b.Index(args[0]) }, }, true case "index_any": return &Builtin{ - name: "bslice.index_any", + name: "byte_slice.index_any", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.index_any", 1, len(args)) + return NewArgsError("byte_slice.index_any", 1, len(args)) } return b.IndexAny(args[0]) }, }, true case "index_byte": return &Builtin{ - name: "bslice.index_byte", + name: "byte_slice.index_byte", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.index_byte", 1, len(args)) + return NewArgsError("byte_slice.index_byte", 1, len(args)) } return b.IndexByte(args[0]) }, }, true case "index_rune": return &Builtin{ - name: "bslice.index_rune", + name: "byte_slice.index_rune", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.index_rune", 1, len(args)) + return NewArgsError("byte_slice.index_rune", 1, len(args)) } return b.IndexRune(args[0]) }, }, true case "repeat": return &Builtin{ - name: "bslice.repeat", + name: "byte_slice.repeat", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 1 { - return NewArgsError("bslice.repeat", 1, len(args)) + return NewArgsError("byte_slice.repeat", 1, len(args)) } return b.Repeat(args[0]) }, }, true case "replace": return &Builtin{ - name: "bslice.replace", + name: "byte_slice.replace", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 3 { - return NewArgsError("bslice.replace", 3, len(args)) + return NewArgsError("byte_slice.replace", 3, len(args)) } return b.Replace(args[0], args[1], args[2]) }, }, true case "replace_all": return &Builtin{ - name: "bslice.replace_all", + name: "byte_slice.replace_all", fn: func(ctx context.Context, args ...Object) Object { if len(args) != 2 { - return NewArgsError("bslice.replace_all", 2, len(args)) + return NewArgsError("byte_slice.replace_all", 2, len(args)) } return b.ReplaceAll(args[0], args[1]) }, @@ -184,28 +185,28 @@ func (b *BSlice) GetAttr(name string) (Object, bool) { return nil, false } -func (b *BSlice) Interface() interface{} { +func (b *ByteSlice) Interface() interface{} { return b.value } -func (b *BSlice) String() string { - return fmt.Sprintf("bslice(%v)", b.value) +func (b *ByteSlice) String() string { + return fmt.Sprintf("byte_slice(%v)", b.value) } -func (b *BSlice) Compare(other Object) (int, error) { +func (b *ByteSlice) Compare(other Object) (int, error) { switch other := other.(type) { - case *BSlice: + case *ByteSlice: return bytes.Compare(b.value, other.value), nil case *String: return bytes.Compare(b.value, []byte(other.value)), nil default: - return 0, fmt.Errorf("type error: cannot compare bslice to type %s", other.Type()) + return 0, fmt.Errorf("type error: cannot compare byte_slice to type %s", other.Type()) } } -func (b *BSlice) Equals(other Object) Object { +func (b *ByteSlice) Equals(other Object) Object { switch other := other.(type) { - case *BSlice: + case *ByteSlice: cmp := bytes.Compare(b.value, other.value) if cmp == 0 { return True @@ -221,67 +222,67 @@ func (b *BSlice) Equals(other Object) Object { return False } -func (b *BSlice) IsTruthy() bool { +func (b *ByteSlice) IsTruthy() bool { return len(b.value) > 0 } -func (b *BSlice) RunOperation(opType op.BinaryOpType, right Object) Object { +func (b *ByteSlice) RunOperation(opType op.BinaryOpType, right Object) Object { switch right := right.(type) { - case *BSlice: + case *ByteSlice: return b.runOperationBytes(opType, right) case *String: return b.runOperationString(opType, right) default: - return NewError(fmt.Errorf("eval error: unsupported operation for bslice: %v on type %s", opType, right.Type())) + return NewError(fmt.Errorf("eval error: unsupported operation for byte_slice: %v on type %s", opType, right.Type())) } } -func (b *BSlice) runOperationBytes(opType op.BinaryOpType, right *BSlice) Object { +func (b *ByteSlice) runOperationBytes(opType op.BinaryOpType, right *ByteSlice) Object { switch opType { case op.Add: result := make([]byte, len(b.value)+len(right.value)) copy(result, b.value) copy(result[len(b.value):], right.value) - return NewBSlice(result) + return NewByteSlice(result) default: - return NewError(fmt.Errorf("eval error: unsupported operation for bslice: %v on type %s", opType, right.Type())) + return NewError(fmt.Errorf("eval error: unsupported operation for byte_slice: %v on type %s", opType, right.Type())) } } -func (b *BSlice) runOperationString(opType op.BinaryOpType, right *String) Object { +func (b *ByteSlice) runOperationString(opType op.BinaryOpType, right *String) Object { switch opType { case op.Add: rightBytes := []byte(right.value) result := make([]byte, len(b.value)+len(rightBytes)) copy(result, b.value) copy(result[len(b.value):], rightBytes) - return NewBSlice(result) + return NewByteSlice(result) default: - return NewError(fmt.Errorf("eval error: unsupported operation for bslice: %v on type %s", opType, right.Type())) + return NewError(fmt.Errorf("eval error: unsupported operation for byte_slice: %v on type %s", opType, right.Type())) } } -func (b *BSlice) GetItem(key Object) (Object, *Error) { +func (b *ByteSlice) GetItem(key Object) (Object, *Error) { indexObj, ok := key.(*Int) if !ok { - return nil, Errorf("index error: bslice index must be an int (got %s)", key.Type()) + return nil, Errorf("index error: byte_slice index must be an int (got %s)", key.Type()) } index, err := ResolveIndex(indexObj.value, int64(len(b.value))) if err != nil { return nil, NewError(err) } - return NewBSlice([]byte{b.value[index]}), nil + return NewByte(b.value[index]), nil } -func (b *BSlice) GetSlice(slice Slice) (Object, *Error) { +func (b *ByteSlice) GetSlice(slice Slice) (Object, *Error) { start, stop, err := ResolveIntSlice(slice, int64(len(b.value))) if err != nil { return nil, NewError(err) } - return NewBSlice(b.value[start:stop]), nil + return NewByteSlice(b.value[start:stop]), nil } -func (b *BSlice) SetItem(key, value Object) *Error { +func (b *ByteSlice) SetItem(key, value Object) *Error { indexObj, ok := key.(*Int) if !ok { return Errorf("index error: index must be an int (got %s)", key.Type()) @@ -301,11 +302,11 @@ func (b *BSlice) SetItem(key, value Object) *Error { return nil } -func (b *BSlice) DelItem(key Object) *Error { - return Errorf("type error: cannot delete from bslice") +func (b *ByteSlice) DelItem(key Object) *Error { + return Errorf("type error: cannot delete from byte_slice") } -func (b *BSlice) Contains(obj Object) *Bool { +func (b *ByteSlice) Contains(obj Object) *Bool { data, err := AsInt(obj) if err != nil { return False @@ -316,29 +317,34 @@ func (b *BSlice) Contains(obj Object) *Bool { return NewBool(bytes.Contains(b.value, []byte{byte(data)})) } -func (b *BSlice) Len() *Int { +func (b *ByteSlice) Len() *Int { return NewInt(int64(len(b.value))) } -func (b *BSlice) Iter() Iterator { - return NewBytesIter(b) +func (b *ByteSlice) Iter() Iterator { + return &SliceIter{ + s: b.value, + size: len(b.value), + pos: -1, + converter: &ByteConverter{}, + } } -func (b *BSlice) Clone() *BSlice { +func (b *ByteSlice) Clone() *ByteSlice { value := make([]byte, len(b.value)) copy(value, b.value) - return NewBSlice(value) + return NewByteSlice(value) } -func (b *BSlice) Reversed() *BSlice { +func (b *ByteSlice) Reversed() *ByteSlice { value := make([]byte, len(b.value)) for i := 0; i < len(b.value); i++ { value[i] = b.value[len(b.value)-i-1] } - return NewBSlice(value) + return NewByteSlice(value) } -func (b *BSlice) Integers() []Object { +func (b *ByteSlice) Integers() []Object { result := make([]Object, len(b.value)) for i, v := range b.value { result[i] = NewInt(int64(v)) @@ -346,7 +352,7 @@ func (b *BSlice) Integers() []Object { return result } -func (b *BSlice) ContainsAny(obj Object) Object { +func (b *ByteSlice) ContainsAny(obj Object) Object { chars, err := AsString(obj) if err != nil { return err @@ -354,18 +360,18 @@ func (b *BSlice) ContainsAny(obj Object) Object { return NewBool(bytes.ContainsAny(b.value, chars)) } -func (b *BSlice) ContainsRune(obj Object) Object { +func (b *ByteSlice) ContainsRune(obj Object) Object { s, err := AsString(obj) if err != nil { return err } if len(s) != 1 { - return Errorf("bslice.contains_rune: argument must be a single character") + return Errorf("byte_slice.contains_rune: argument must be a single character") } return NewBool(bytes.ContainsRune(b.value, rune(s[0]))) } -func (b *BSlice) Count(obj Object) Object { +func (b *ByteSlice) Count(obj Object) Object { data, err := AsBytes(obj) if err != nil { return err @@ -373,7 +379,7 @@ func (b *BSlice) Count(obj Object) Object { return NewInt(int64(bytes.Count(b.value, data))) } -func (b *BSlice) HasPrefix(obj Object) Object { +func (b *ByteSlice) HasPrefix(obj Object) Object { data, err := AsBytes(obj) if err != nil { return err @@ -381,7 +387,7 @@ func (b *BSlice) HasPrefix(obj Object) Object { return NewBool(bytes.HasPrefix(b.value, data)) } -func (b *BSlice) HasSuffix(obj Object) Object { +func (b *ByteSlice) HasSuffix(obj Object) Object { data, err := AsBytes(obj) if err != nil { return err @@ -389,7 +395,7 @@ func (b *BSlice) HasSuffix(obj Object) Object { return NewBool(bytes.HasSuffix(b.value, data)) } -func (b *BSlice) Index(obj Object) Object { +func (b *ByteSlice) Index(obj Object) Object { data, err := AsBytes(obj) if err != nil { return err @@ -397,7 +403,7 @@ func (b *BSlice) Index(obj Object) Object { return NewInt(int64(bytes.Index(b.value, data))) } -func (b *BSlice) IndexAny(obj Object) Object { +func (b *ByteSlice) IndexAny(obj Object) Object { chars, err := AsString(obj) if err != nil { return err @@ -405,37 +411,37 @@ func (b *BSlice) IndexAny(obj Object) Object { return NewInt(int64(bytes.IndexAny(b.value, chars))) } -func (b *BSlice) IndexByte(obj Object) Object { +func (b *ByteSlice) IndexByte(obj Object) Object { data, err := AsBytes(obj) if err != nil { return err } if len(data) != 1 { - return Errorf("bslice.index_byte: argument must be a single byte") + return Errorf("byte_slice.index_byte: argument must be a single byte") } return NewInt(int64(bytes.IndexByte(b.value, data[0]))) } -func (b *BSlice) IndexRune(obj Object) Object { +func (b *ByteSlice) IndexRune(obj Object) Object { s, err := AsString(obj) if err != nil { return err } if len(s) != 1 { - return Errorf("bslice.index_rune: argument must be a single character") + return Errorf("byte_slice.index_rune: argument must be a single character") } return NewInt(int64(bytes.IndexRune(b.value, rune(s[0])))) } -func (b *BSlice) Repeat(obj Object) Object { +func (b *ByteSlice) Repeat(obj Object) Object { count, err := AsInt(obj) if err != nil { return err } - return NewBSlice(bytes.Repeat(b.value, int(count))) + return NewByteSlice(bytes.Repeat(b.value, int(count))) } -func (b *BSlice) Replace(old, new, count Object) Object { +func (b *ByteSlice) Replace(old, new, count Object) Object { oldBytes, err := AsBytes(old) if err != nil { return err @@ -448,10 +454,10 @@ func (b *BSlice) Replace(old, new, count Object) Object { if err != nil { return err } - return NewBSlice(bytes.Replace(b.value, oldBytes, newBytes, int(n))) + return NewByteSlice(bytes.Replace(b.value, oldBytes, newBytes, int(n))) } -func (b *BSlice) ReplaceAll(old, new Object) Object { +func (b *ByteSlice) ReplaceAll(old, new Object) Object { oldBytes, err := AsBytes(old) if err != nil { return err @@ -460,13 +466,13 @@ func (b *BSlice) ReplaceAll(old, new Object) Object { if err != nil { return err } - return NewBSlice(bytes.ReplaceAll(b.value, oldBytes, newBytes)) + return NewByteSlice(bytes.ReplaceAll(b.value, oldBytes, newBytes)) } -func (b *BSlice) Cost() int { +func (b *ByteSlice) Cost() int { return len(b.value) } -func NewBSlice(value []byte) *BSlice { - return &BSlice{value: value} +func NewByteSlice(value []byte) *ByteSlice { + return &ByteSlice{value: value} } diff --git a/object/cell.go b/object/cell.go index 1c6f61a4..e6d07e16 100644 --- a/object/cell.go +++ b/object/cell.go @@ -1,9 +1,13 @@ package object -import "fmt" +import ( + "fmt" + + "github.com/cloudcmds/tamarin/v2/op" +) type Cell struct { - *DefaultImpl + *base value *Object } @@ -47,6 +51,10 @@ func (c *Cell) Equals(other Object) Object { return False } +func (c *Cell) RunOperation(opType op.BinaryOpType, right Object) Object { + return NewError(fmt.Errorf("eval error: unsupported operation for cell: %v", opType)) +} + func NewCell(value *Object) *Cell { - return &Cell{DefaultImpl: &DefaultImpl{}, value: value} + return &Cell{value: value} } diff --git a/object/code_proxy.go b/object/code_proxy.go index 2a528ccc..d578bfce 100644 --- a/object/code_proxy.go +++ b/object/code_proxy.go @@ -8,6 +8,7 @@ import ( ) type CodeProxy struct { + *base name string builtins *List code *Code @@ -61,10 +62,6 @@ func (c *CodeProxy) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for code: %v", opType)) } -func (c *CodeProxy) Cost() int { - return 8 -} - func NewCodeProxy(c *Code) *CodeProxy { // Provide some isolation diff --git a/object/color.go b/object/color.go new file mode 100644 index 00000000..b054c3ee --- /dev/null +++ b/object/color.go @@ -0,0 +1,79 @@ +package object + +import ( + "context" + "errors" + "fmt" + "image/color" + + "github.com/cloudcmds/tamarin/v2/op" +) + +type Color struct { + *base + c color.Color +} + +func (c *Color) Inspect() string { + return c.String() +} + +func (c *Color) Type() Type { + return COLOR +} + +func (c *Color) Value() color.Color { + return c.c +} + +func (c *Color) GetAttr(name string) (Object, bool) { + switch name { + case "rgba": + return NewBuiltin("color.rgba", + func(ctx context.Context, args ...Object) Object { + if len(args) != 0 { + return NewArgsError("color.rgba", 0, len(args)) + } + r, g, b, a := c.c.RGBA() + return NewList([]Object{ + NewInt(int64(r)), + NewInt(int64(g)), + NewInt(int64(b)), + NewInt(int64(a)), + }) + }), true + } + return nil, false +} + +func (c *Color) SetAttr(name string, value Object) error { + return fmt.Errorf("attribute error: color object has no attribute %q", name) +} + +func (c *Color) Interface() interface{} { + return c.c +} + +func (c *Color) String() string { + r, g, b, a := c.c.RGBA() + return fmt.Sprintf("color(r=%d g=%d b=%d a=%d)", r, g, b, a) +} + +func (c *Color) Compare(other Object) (int, error) { + return 0, errors.New("type error: unable to compare colors") +} + +func (c *Color) Equals(other Object) Object { + if c == other { + return True + } + return False +} + +func (c *Color) RunOperation(opType op.BinaryOpType, right Object) Object { + return NewError(fmt.Errorf("eval error: unsupported operation for color: %v ", opType)) +} + +func NewColor(c color.Color) *Color { + return &Color{c: c} +} diff --git a/object/default.go b/object/default.go deleted file mode 100644 index 2039f8d3..00000000 --- a/object/default.go +++ /dev/null @@ -1,44 +0,0 @@ -package object - -import ( - "fmt" - - "github.com/cloudcmds/tamarin/v2/op" -) - -type DefaultImpl struct{} - -func (d *DefaultImpl) Type() Type { - panic("not implemented") -} - -func (d *DefaultImpl) Inspect() string { - panic("not implemented") -} - -func (d *DefaultImpl) Interface() interface{} { - return nil -} - -func (d *DefaultImpl) Equals(other Object) Object { - if d == other { - return True - } - return False -} - -func (d *DefaultImpl) GetAttr(name string) (Object, bool) { - return nil, false -} - -func (d *DefaultImpl) IsTruthy() bool { - return true -} - -func (d *DefaultImpl) RunOperation(opType op.BinaryOpType, right Object) Object { - return NewError(fmt.Errorf("eval error: unsupported operation for default: %v", opType)) -} - -func (d *DefaultImpl) Cost() int { - return 0 -} diff --git a/object/error.go b/object/error.go index 709f292c..6843c856 100644 --- a/object/error.go +++ b/object/error.go @@ -8,7 +8,7 @@ import ( // Error wraps a Go error interface and implements Object. type Error struct { - // err is the Go error being wrapped. + *base err error } @@ -61,26 +61,14 @@ func (e *Error) Equals(other Object) Object { } } -func (e *Error) IsTruthy() bool { - return true -} - func (e *Error) Message() *String { return NewString(e.err.Error()) } -func (e *Error) GetAttr(name string) (Object, bool) { - return nil, false -} - func (e *Error) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for error: %v", opType)) } -func (e *Error) Cost() int { - return 0 -} - func Errorf(format string, a ...interface{}) *Error { var args []interface{} for _, arg := range a { diff --git a/object/file.go b/object/file.go index a7548771..ad17a459 100644 --- a/object/file.go +++ b/object/file.go @@ -12,6 +12,7 @@ import ( ) type File struct { + *base ctx context.Context value *os.File once sync.Once @@ -53,7 +54,7 @@ func (f *File) GetAttr(name string) (Object, bool) { return NewArgsError("file.read", 1, len(args)) } switch obj := args[0].(type) { - case *BSlice: + case *ByteSlice: n, ioErr := f.value.Read(obj.Value()) if ioErr != nil && ioErr != io.EOF { return NewError(ioErr) @@ -66,7 +67,7 @@ func (f *File) GetAttr(name string) (Object, bool) { } return NewInt(int64(n)) default: - return Errorf("type error: file.read expects bslice or buffer (%s given)", obj.Type()) + return Errorf("type error: file.read expects byte_slice or buffer (%s given)", obj.Type()) } }), true case "write": @@ -167,10 +168,6 @@ func (f *File) Equals(other Object) Object { return False } -func (f *File) IsTruthy() bool { - return true -} - func (f *File) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for file: %v ", opType)) } diff --git a/object/float.go b/object/float.go index 5fb7ba73..ace13336 100644 --- a/object/float.go +++ b/object/float.go @@ -10,7 +10,7 @@ import ( // Float wraps float64 and implements Object and Hashable interfaces. type Float struct { - // value holds the float64 wrapped by this object. + *base value float64 } @@ -30,10 +30,6 @@ func (f *Float) HashKey() HashKey { return HashKey{Type: f.Type(), FltValue: f.value} } -func (f *Float) GetAttr(name string) (Object, bool) { - return nil, false -} - func (f *Float) Interface() interface{} { return f.value } @@ -95,10 +91,6 @@ func (f *Float) RunOperation(opType op.BinaryOpType, right Object) Object { } } -func (f *Float) Cost() int { - return 8 -} - func (f *Float) runOperationFloat(opType op.BinaryOpType, right float64) Object { switch opType { case op.Add: diff --git a/object/float_slice.go b/object/float_slice.go new file mode 100644 index 00000000..b5548b98 --- /dev/null +++ b/object/float_slice.go @@ -0,0 +1,144 @@ +package object + +import ( + "fmt" + + "github.com/cloudcmds/tamarin/v2/op" +) + +type FloatSlice struct { + *base + value []float64 +} + +func (f *FloatSlice) Inspect() string { + return fmt.Sprintf("float_slice(%v)", f.value) +} + +func (f *FloatSlice) Type() Type { + return FLOAT_SLICE +} + +func (f *FloatSlice) Value() []float64 { + return f.value +} + +func (f *FloatSlice) GetAttr(name string) (Object, bool) { + return nil, false +} + +func (f *FloatSlice) Interface() interface{} { + return f.value +} + +func (f *FloatSlice) String() string { + return f.Inspect() +} + +func (f *FloatSlice) Compare(other Object) (int, error) { + return 0, fmt.Errorf("type error: cannot compare float_slice to type %s", other.Type()) +} + +func (f *FloatSlice) Equals(other Object) Object { + if f == other { + return True + } + return False +} + +func (f *FloatSlice) IsTruthy() bool { + return len(f.value) > 0 +} + +func (f *FloatSlice) RunOperation(opType op.BinaryOpType, right Object) Object { + return NewError(fmt.Errorf("eval error: unsupported operation for float_slice: %v on type %s", opType, right.Type())) +} + +func (f *FloatSlice) Contains(item Object) *Bool { + value, err := AsFloat(item) + if err != nil { + return False + } + for _, v := range f.value { + if v == value { + return True + } + } + return False +} + +func (f *FloatSlice) GetItem(key Object) (Object, *Error) { + indexObj, ok := key.(*Int) + if !ok { + return nil, Errorf("index error: float_slice index must be an int (got %s)", key.Type()) + } + index, err := ResolveIndex(indexObj.value, int64(len(f.value))) + if err != nil { + return nil, NewError(err) + } + return NewFloat(f.value[index]), nil +} + +func (f *FloatSlice) GetSlice(slice Slice) (Object, *Error) { + start, stop, err := ResolveIntSlice(slice, int64(len(f.value))) + if err != nil { + return nil, NewError(err) + } + return NewFloatSlice(f.value[start:stop]), nil +} + +func (f *FloatSlice) SetItem(key, value Object) *Error { + indexObj, ok := key.(*Int) + if !ok { + return Errorf("index error: index must be an int (got %s)", key.Type()) + } + index, err := ResolveIndex(indexObj.value, int64(len(f.value))) + if err != nil { + return NewError(err) + } + floatVal, convErr := AsFloat(value) + if convErr != nil { + return convErr + } + f.value[index] = floatVal + return nil +} + +func (f *FloatSlice) DelItem(key Object) *Error { + return Errorf("type error: cannot delete from float_slice") +} + +func (f *FloatSlice) Len() *Int { + return NewInt(int64(len(f.value))) +} + +func (f *FloatSlice) Iter() Iterator { + return &SliceIter{ + s: f.value, + size: len(f.value), + pos: -1, + converter: &Float64Converter{}, + } +} + +func (f *FloatSlice) Clone() *FloatSlice { + value := make([]float64, len(f.value)) + copy(value, f.value) + return NewFloatSlice(value) +} + +func (f *FloatSlice) Integers() []Object { + result := make([]Object, len(f.value)) + for i, v := range f.value { + result[i] = NewInt(int64(v)) + } + return result +} + +func (f *FloatSlice) Cost() int { + return len(f.value) +} + +func NewFloatSlice(value []float64) *FloatSlice { + return &FloatSlice{value: value} +} diff --git a/object/function.go b/object/function.go index 7cb94827..34b6ae7e 100644 --- a/object/function.go +++ b/object/function.go @@ -10,7 +10,7 @@ import ( // Function is a function that has been compiled to bytecode. type Function struct { - *DefaultImpl + *base name string parameters []string defaults []Object @@ -64,6 +64,21 @@ func (f *Function) String() string { return "func() { ... }" } +func (f *Function) Interface() interface{} { + return nil +} + +func (f *Function) RunOperation(opType op.BinaryOpType, right Object) Object { + return NewError(fmt.Errorf("eval error: unsupported operation for function: %v", opType)) +} + +func (f *Function) Equals(other Object) Object { + if f == other { + return True + } + return False +} + func (f *Function) Instructions() []op.Code { return f.code.Instructions } diff --git a/object/go_field.go b/object/go_field.go index eb91d2fc..955e5b2f 100644 --- a/object/go_field.go +++ b/object/go_field.go @@ -9,6 +9,7 @@ import ( // GoField represents a single field on a Go type that can be read or written. type GoField struct { + *base field reflect.StructField fieldType *GoType name *String @@ -71,10 +72,6 @@ func (f *GoField) RunOperation(opType op.BinaryOpType, right Object) Object { return Errorf("type error: unsupported operation on go_field (%s)", opType) } -func (f *GoField) Cost() int { - return 0 -} - func (f *GoField) Converter() (TypeConverter, bool) { return f.converter, f.converter != nil } diff --git a/object/go_method.go b/object/go_method.go index af227cd1..eca64d46 100644 --- a/object/go_method.go +++ b/object/go_method.go @@ -11,6 +11,7 @@ import ( // GoMethod represents a single method on a Go type. This exposes the method to // Tamarin for reflection and proxying. type GoMethod struct { + *base method reflect.Method inputTypes []*GoType outputTypes []*GoType @@ -103,10 +104,6 @@ func (m *GoMethod) RunOperation(opType op.BinaryOpType, right Object) Object { return Errorf("type error: unsupported operation on go_method (%s)", opType) } -func (m *GoMethod) Cost() int { - return 0 -} - func (m *GoMethod) Name() string { return m.method.Name } diff --git a/object/go_type.go b/object/go_type.go index 5db9df93..183087c0 100644 --- a/object/go_type.go +++ b/object/go_type.go @@ -10,6 +10,7 @@ import ( // GoType represents a single Go type whose methods and fields can be proxied. type GoType struct { + *base typ reflect.Type name *String packagePath *String @@ -69,10 +70,6 @@ func (t *GoType) RunOperation(opType op.BinaryOpType, right Object) Object { return Errorf("type error: unsupported operation on go_type (%s)", opType) } -func (t *GoType) Cost() int { - return 8 -} - func (t *GoType) Name() string { return t.name.value } diff --git a/object/http_response.go b/object/http_response.go index ddf0e9ec..c7112767 100644 --- a/object/http_response.go +++ b/object/http_response.go @@ -13,6 +13,7 @@ import ( ) type HttpResponse struct { + *base resp *http.Response readerLimit int64 once sync.Once @@ -151,10 +152,6 @@ func (r *HttpResponse) Equals(other Object) Object { return NewBool(r.resp == other.(*HttpResponse).resp) } -func (r *HttpResponse) IsTruthy() bool { - return true -} - func (r *HttpResponse) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for http.response: %v", opType)) } diff --git a/object/int.go b/object/int.go index f4585fd8..ca94fdca 100644 --- a/object/int.go +++ b/object/int.go @@ -9,7 +9,7 @@ import ( // Int wraps int64 and implements Object and Hashable interfaces. type Int struct { - // value holds the int64 wrapped by this object. + *base value int64 } @@ -29,10 +29,6 @@ func (i *Int) HashKey() HashKey { return HashKey{Type: i.Type(), IntValue: i.value} } -func (i *Int) GetAttr(name string) (Object, bool) { - return nil, false -} - func (i *Int) Interface() interface{} { return i.value } @@ -141,10 +137,6 @@ func (i *Int) runOperationFloat(opType op.BinaryOpType, right float64) Object { } } -func (i *Int) Cost() int { - return 8 -} - func NewInt(value int64) *Int { if value >= 0 && value < tableSize { return intCache[value] diff --git a/object/iter_entry.go b/object/iter_entry.go index f0b08b7b..2d2f551c 100644 --- a/object/iter_entry.go +++ b/object/iter_entry.go @@ -7,6 +7,7 @@ import ( ) type Entry struct { + *base key Object value Object primary Object @@ -52,10 +53,6 @@ func (e *Entry) GetAttr(name string) (Object, bool) { return nil, false } -func (e *Entry) IsTruthy() bool { - return true -} - func (e *Entry) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for entry: %v", opType)) } @@ -85,10 +82,6 @@ func (e *Entry) WithValueAsPrimary() *Entry { return e } -func (e *Entry) Cost() int { - return 0 -} - func NewEntry(key, value Object) *Entry { return &Entry{key: key, value: value} } diff --git a/object/list.go b/object/list.go index 416ab0eb..2b0b3744 100644 --- a/object/list.go +++ b/object/list.go @@ -11,6 +11,8 @@ import ( // List of objects type List struct { + *base + // items holds the list of objects items []Object diff --git a/object/list_iter.go b/object/list_iter.go index faad9ba9..f3751487 100644 --- a/object/list_iter.go +++ b/object/list_iter.go @@ -8,6 +8,7 @@ import ( ) type ListIter struct { + *base l *List pos int64 current Object @@ -88,10 +89,6 @@ func (iter *ListIter) RunOperation(opType op.BinaryOpType, right Object) Object return NewError(fmt.Errorf("eval error: unsupported operation for list_iter: %v", opType)) } -func (iter *ListIter) Cost() int { - return 0 -} - func (iter *ListIter) Next() (Object, bool) { items := iter.l.items if iter.pos >= int64(len(items)-1) { diff --git a/object/map.go b/object/map.go index d843aa91..bd535635 100644 --- a/object/map.go +++ b/object/map.go @@ -11,6 +11,8 @@ import ( ) type Map struct { + *base + items map[string]Object // Used to avoid the possibility of infinite recursion when inspecting. diff --git a/object/map_iter.go b/object/map_iter.go index e5441a92..9f57ca02 100644 --- a/object/map_iter.go +++ b/object/map_iter.go @@ -8,6 +8,7 @@ import ( ) type MapIter struct { + *base m *Map keys []string pos int64 @@ -112,10 +113,6 @@ func (iter *MapIter) Entry() (IteratorEntry, bool) { return NewEntry(iter.current, value).WithKeyAsPrimary(), true } -func (iter *MapIter) Cost() int { - return 0 -} - func NewMapIter(m *Map) *MapIter { return &MapIter{m: m, keys: m.SortedKeys(), pos: -1} } diff --git a/object/module.go b/object/module.go index 2cb295cb..fe6cc45f 100644 --- a/object/module.go +++ b/object/module.go @@ -7,6 +7,7 @@ import ( ) type Module struct { + *base name string code *Code } @@ -74,10 +75,6 @@ func (m *Module) Compare(other Object) (int, error) { return -1, nil } -func (m *Module) IsTruthy() bool { - return true -} - func (m *Module) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for module: %v", opType)) } @@ -89,10 +86,6 @@ func (m *Module) Equals(other Object) Object { return False } -func (m *Module) Cost() int { - return 0 -} - func NewModule(name string, code *Code) *Module { return &Module{ name: name, diff --git a/object/nil.go b/object/nil.go index 10b37073..d9dc1d6b 100644 --- a/object/nil.go +++ b/object/nil.go @@ -6,7 +6,9 @@ import ( "github.com/cloudcmds/tamarin/v2/op" ) -type NilType struct{} +type NilType struct { + *base +} func (n *NilType) Type() Type { return NIL @@ -20,10 +22,6 @@ func (n *NilType) String() string { return "nil" } -func (n *NilType) GetAttr(name string) (Object, bool) { - return nil, false -} - func (n *NilType) Interface() interface{} { return nil } @@ -50,7 +48,3 @@ func (n *NilType) IsTruthy() bool { func (n *NilType) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for nil: %v", opType)) } - -func (n *NilType) Cost() int { - return 0 -} diff --git a/object/object.go b/object/object.go index 194678f7..65bc17d6 100644 --- a/object/object.go +++ b/object/object.go @@ -26,13 +26,17 @@ const ( BOOL Type = "bool" BUFFER Type = "buffer" BUILTIN Type = "builtin" - BSLICE Type = "bslice" - BSLICE_ITER Type = "bslice_iter" + BYTE_SLICE Type = "byte_slice" + BYTE Type = "byte" CELL Type = "cell" CODE Type = "code" + COLOR Type = "color" + COMPLEX Type = "complex" + COMPLEX_SLICE Type = "complex_slice" ERROR Type = "error" FILE Type = "file" FLOAT Type = "float" + FLOAT_SLICE Type = "float_slice" FUNCTION Type = "function" GO_TYPE Type = "go_type" GO_FIELD Type = "go_field" @@ -52,6 +56,7 @@ const ( RESULT Type = "result" SET Type = "set" SET_ITER Type = "set_iter" + SLICE_ITER Type = "slice_iter" STRING Type = "string" STRING_ITER Type = "string_iter" TIME Type = "time" @@ -81,6 +86,9 @@ type Object interface { // GetAttr returns the attribute with the given name from this object. GetAttr(name string) (Object, bool) + // SetAttr sets the attribute with the given name on this object. + SetAttr(name string, value Object) error + // IsTruthy returns true if the object is considered "truthy". IsTruthy() bool diff --git a/object/partial.go b/object/partial.go index 863e2b0c..41539be9 100644 --- a/object/partial.go +++ b/object/partial.go @@ -9,6 +9,7 @@ import ( // Partial is a partially applied function type Partial struct { + *base fn Object args []Object } @@ -44,22 +45,10 @@ func (p *Partial) Equals(other Object) Object { return False } -func (p *Partial) GetAttr(name string) (Object, bool) { - return nil, false -} - -func (p *Partial) IsTruthy() bool { - return true -} - func (p *Partial) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for nil: %v", opType)) } -func (p *Partial) Cost() int { - return 0 -} - func NewPartial(fn Object, args []Object) *Partial { return &Partial{ fn: fn, diff --git a/object/proxy.go b/object/proxy.go index b65e6019..650d2897 100644 --- a/object/proxy.go +++ b/object/proxy.go @@ -16,22 +16,15 @@ var ( goTypeRegistry = map[reflect.Type]*GoType{} ) -func IsProxyableKind(kind reflect.Kind) bool { - switch kind { - case reflect.Invalid, reflect.UnsafePointer, reflect.Chan, reflect.Array: - return false - default: - return true - } -} - func IsProxyableType(typ reflect.Type) bool { - kind := typ.Kind() - if kind == reflect.Ptr { - // Indirection is allowed only for structs, and only one level + switch typ.Kind() { + case reflect.Interface, reflect.Slice: + return true + case reflect.Ptr: return typ.Elem().Kind() == reflect.Struct + default: + return false } - return IsProxyableKind(kind) } // GoAttribute is an interface to represent an attribute on a Go type. This could @@ -49,6 +42,7 @@ func LookupGoType(obj interface{}) (*GoType, bool) { // Proxy is a Tamarin type that proxies method calls to a wrapped Go struct. // Only the public methods of the Go type are proxied. type Proxy struct { + *base typ *GoType obj interface{} } @@ -62,11 +56,11 @@ func (p *Proxy) Interface() interface{} { } func (p *Proxy) Inspect() string { - return fmt.Sprintf("%v", p.obj) + return p.String() } func (p *Proxy) String() string { - return fmt.Sprintf("proxy(%v)", p.obj) + return fmt.Sprintf("proxy(%s(%v))", reflect.TypeOf(p.obj), p.obj) } func (p *Proxy) GoType() *GoType { @@ -74,6 +68,9 @@ func (p *Proxy) GoType() *GoType { } func (p *Proxy) GetAttr(name string) (Object, bool) { + if name == "__type__" { + return p.typ, true + } attr, found := p.typ.GetAttribute(name) if !found { return nil, false @@ -106,6 +103,39 @@ func (p *Proxy) GetAttr(name string) (Object, bool) { return nil, false } +func (p *Proxy) SetAttr(name string, value Object) error { + attr, found := p.typ.GetAttribute(name) + if !found { + return fmt.Errorf("attribute error: %s has no attribute %s", p.typ.Name(), name) + } + switch attr := attr.(type) { + case *GoField: + conv, ok := attr.Converter() + if !ok { + return fmt.Errorf("type error: no converter for field %s", name) + } + var field reflect.Value + if _, ok := p.typ.IndirectType(); ok { + field = reflect.ValueOf(p.obj).Elem().FieldByName(name) + } else { + field = reflect.ValueOf(p.obj).FieldByName(name) + } + result, err := conv.To(value) + if err != nil { + return err + } + if field.CanSet() { + field.Set(reflect.ValueOf(result)) + return nil + } else { + return fmt.Errorf("type error: cannot set field %s", name) + } + case *GoMethod: + return fmt.Errorf("attribute error: cannot set method %s", name) + } + return fmt.Errorf("attribute error: unknown attribute type") +} + func (p *Proxy) Equals(other Object) Object { if p == other { return True @@ -113,18 +143,10 @@ func (p *Proxy) Equals(other Object) Object { return False } -func (p *Proxy) IsTruthy() bool { - return true -} - func (p *Proxy) RunOperation(opType op.BinaryOpType, right Object) Object { return NewError(fmt.Errorf("eval error: unsupported operation for proxy: %v", opType)) } -func (p *Proxy) Cost() int { - return 8 -} - func (p *Proxy) call(ctx context.Context, m *GoMethod, args ...Object) Object { methodName := fmt.Sprintf("%s.%s", p.typ.Name(), m.name) isVariadic := m.method.Type.IsVariadic() @@ -168,7 +190,7 @@ func (p *Proxy) call(ctx context.Context, m *GoMethod, args ...Object) Object { } } outputCount := len(outputs) - len(m.errorIndices) - if outputCount == 1 { + if outputCount <= 1 { for i, output := range outputs { if !m.IsOutputError(i) { outType := m.outputTypes[i] @@ -204,7 +226,7 @@ func NewProxy(obj interface{}) (*Proxy, error) { // Is this type proxyable? if !IsProxyableType(typ) { - return nil, fmt.Errorf("type error: unsupported argument for go_type (%t given)", typ) + return nil, fmt.Errorf("type error: unable to proxy type (%T given)", obj) } goType, err := NewGoType(typ) diff --git a/object/proxy_test.go b/object/proxy_test.go index 0a85dd02..459d2dc6 100644 --- a/object/proxy_test.go +++ b/object/proxy_test.go @@ -1,8 +1,11 @@ package object_test import ( + "bytes" "context" + "crypto/sha256" "fmt" + "io" "reflect" "strconv" "strings" @@ -45,77 +48,6 @@ func (pt *ProxyService) ParseInt(s string) (int, error) { return strconv.Atoi(s) } -func TestProxy(t *testing.T) { - - // ctx := context.Background() - - // reg, err := object.NewTypeRegistry() - // require.Nil(t, err) - - // _, err = reg.Register(&ProxyService{}) - // require.Nil(t, err) - - // proxyType, found := reg.GetType(&ProxyService{}) - // require.True(t, found) - // methods := proxyType.Methods() - // require.Len(t, methods, 4) - - // sort.Slice(methods, func(i, j int) bool { - // return methods[i].Name() < methods[j].Name() - // }) - - // require.Equal(t, "Flub", methods[0].Name()) - // require.Equal(t, "Increment", methods[1].Name()) - // require.Equal(t, "ParseInt", methods[2].Name()) - // require.Equal(t, "ToUpper", methods[3].Name()) - - // // Create a proxy around an instance of ProxyService - // proxy, err := object.NewProxy(reg, &ProxyService{}) - // require.Nil(t, err) - - // getMethod := func(name string) *object.Builtin { - // method, ok := proxy.GetAttr(name) - // require.True(t, ok) - // return method.(*object.Builtin) - // } - - // flub := getMethod("Flub") - // inc := getMethod("Increment") - // toUpper := getMethod("ToUpper") - // parseInt := getMethod("ParseInt") - - // // Call Flub and check the result - // res := flub.Call(ctx, object.NewMap(map[string]object.Object{ - // "A": object.NewInt(99), - // "B": object.NewString("B"), - // "C": object.NewBool(true), - // })) - // require.Equal(t, "flubbed:99.B.true", res.(*object.String).Value()) - - // // Try calling Increment - // res = inc.Call(ctx, object.NewInt(123)) - // require.Equal(t, int64(124), res.(*object.Int).Value()) - - // // Try calling ToUpper - // res = toUpper.Call(ctx, object.NewString("hello")) - // require.Equal(t, "HELLO", res.(*object.String).Value()) - - // Call ParseInt and check that an Ok result is returned - // res = parseInt.Call(ctx, object.NewString("234")) - // result, ok := res.(*object.Result) - // require.True(t, ok) - // require.True(t, result.IsOk()) - // require.Equal(t, int64(234), result.Unwrap().(*object.Int).Value()) - - // // Call ParseInt with an invalid input and check that an Err result is returned - // res = parseInt.Call(ctx, object.NewString("not-an-int")) - // result, ok = res.(*object.Result) - // require.True(t, ok) - // require.True(t, result.IsErr()) - // require.Equal(t, "strconv.Atoi: parsing \"not-an-int\": invalid syntax", - // result.UnwrapErr().Message().Value()) -} - type proxyTestType1 []string func (p proxyTestType1) Len() int { @@ -254,7 +186,6 @@ func TestProxyTestType2(t *testing.T) { func TestProxyCall(t *testing.T) { proxy, err := object.NewProxy(&proxyTestType2{}) require.Nil(t, err) - fmt.Println(proxy) m, ok := proxy.GetAttr("D") require.True(t, ok) @@ -268,3 +199,133 @@ func TestProxyCall(t *testing.T) { require.Equal(t, object.NewInt(3), result) } + +func TestProxySetGetAttr(t *testing.T) { + + proxy, err := object.NewProxy(&proxyTestType2{}) + require.Nil(t, err) + + // A starts at 0 + value, ok := proxy.GetAttr("A") + require.True(t, ok) + require.Equal(t, object.NewInt(0), value) + + // Set to 42 + require.Nil(t, proxy.SetAttr("A", object.NewInt(42))) + + // Confirm 42 + value, ok = proxy.GetAttr("A") + require.True(t, ok) + require.Equal(t, object.NewInt(42), value) + + // Set to -3 + require.Nil(t, proxy.SetAttr("A", object.NewInt(-3))) + + // Confirm -3 + value, ok = proxy.GetAttr("A") + require.True(t, ok) + require.Equal(t, object.NewInt(-3), value) + +} + +func TestAttemptProxyOnStructValue(t *testing.T) { + // Cannot create a proxy on a struct value. It has to be a pointer. + _, err := object.NewProxy(proxyTestType2{}) + require.NotNil(t, err) + require.Equal(t, "type error: unable to proxy type (object_test.proxyTestType2 given)", err.Error()) +} + +func TestProxyBytesBuffer(t *testing.T) { + + ctx := context.Background() + buf := bytes.NewBuffer([]byte("abc")) + var reader io.Reader = buf + + // Creating a proxy on an interface really means creating a proxy on the + // underlying concrete type. + proxy, err := object.NewProxy(reader) + require.Nil(t, err) + + // Confirm the GoType is actually *bytes.Buffer + goType := proxy.GoType() + require.Equal(t, "*bytes.Buffer", goType.Name()) + + // The proxy should have attributes available for all public attributes + // on *bytes.Buffer + method, ok := proxy.GetAttr("Len") + require.True(t, ok) + + // Confirm we can call a method + lenMethod, ok := method.(*object.Builtin) + require.True(t, ok) + require.Equal(t, object.NewInt(3), lenMethod.Call(ctx)) + + // Write to the buffer and confirm the length changes + buf.WriteString("defg") + require.Equal(t, object.NewInt(7), lenMethod.Call(ctx)) + + // Confirm we can call Bytes() and get a byte_slice back + getBytes, ok := proxy.GetAttr("Bytes") + require.True(t, ok) + bytes := getBytes.(*object.Builtin).Call(ctx) + require.Equal(t, object.NewByteSlice([]byte("abcdefg")), bytes) +} + +func TestProxyMethodError(t *testing.T) { + + // Using the ReadByte method as an example, call it in a situation that will + // have it return an error, then confirm a Tamarin *Error is returned. + + // func (b *Buffer) ReadByte() (byte, error) + // If no byte is available, it returns error io.EOF. + + ctx := context.Background() + buf := bytes.NewBuffer(nil) // empty buffer! + proxy, err := object.NewProxy(buf) + require.Nil(t, err) + + method, ok := proxy.GetAttr("ReadByte") + require.True(t, ok) + + readByte, ok := method.(*object.Builtin) + require.True(t, ok) + + result := readByte.Call(ctx) + errObj, ok := result.(*object.Error) + require.True(t, ok) + require.Equal(t, object.Errorf("EOF"), errObj) +} + +func TestProxyHasher(t *testing.T) { + ctx := context.Background() + h := sha256.New() + + proxy, err := object.NewProxy(h) + require.Nil(t, err) + + method, ok := proxy.GetAttr("Write") + require.True(t, ok) + write, ok := method.(*object.Builtin) + require.True(t, ok) + + method, ok = proxy.GetAttr("Sum") + require.True(t, ok) + sum, ok := method.(*object.Builtin) + require.True(t, ok) + + result := write.Call(ctx, object.NewByteSlice([]byte("abc"))) + require.Equal(t, object.NewInt(3), result) + + result = write.Call(ctx, object.NewByteSlice([]byte("de"))) + require.Equal(t, object.NewInt(2), result) + + result = sum.Call(ctx, object.NewByteSlice(nil)) + byte_slice, ok := result.(*object.ByteSlice) + require.True(t, ok) + + other := sha256.New() + other.Write([]byte("abcde")) + expected := other.Sum(nil) + + require.Equal(t, expected, byte_slice.Value()) +} diff --git a/object/set.go b/object/set.go index 58a9157b..0031d5db 100644 --- a/object/set.go +++ b/object/set.go @@ -10,6 +10,7 @@ import ( ) type Set struct { + *base items map[HashKey]Object } diff --git a/object/set_iter.go b/object/set_iter.go index 1dcaf09b..3d027aad 100644 --- a/object/set_iter.go +++ b/object/set_iter.go @@ -8,6 +8,7 @@ import ( ) type SetIter struct { + *base set *Set keys []HashKey pos int64 @@ -113,10 +114,6 @@ func (iter *SetIter) Entry() (IteratorEntry, bool) { return NewEntry(iter.current, True).WithKeyAsPrimary(), true } -func (iter *SetIter) Cost() int { - return 0 -} - func NewSetIter(set *Set) *SetIter { return &SetIter{set: set, keys: set.Keys(), pos: -1} } diff --git a/object/slice_iter.go b/object/slice_iter.go new file mode 100644 index 00000000..bfba94ea --- /dev/null +++ b/object/slice_iter.go @@ -0,0 +1,131 @@ +package object + +import ( + "context" + "fmt" + "reflect" + + "github.com/cloudcmds/tamarin/v2/op" +) + +type SliceIter struct { + *base + s interface{} + pos int + size int + current Object + converter TypeConverter +} + +func (iter *SliceIter) Type() Type { + return SLICE_ITER +} + +func (iter *SliceIter) Inspect() string { + return fmt.Sprintf("slice_iter(pos=%d size=%d)", iter.pos, iter.size) +} + +func (iter *SliceIter) String() string { + return iter.Inspect() +} + +func (iter *SliceIter) Interface() interface{} { + var entries []map[string]interface{} + for { + entry, ok := iter.Next() + if !ok { + break + } + entries = append(entries, entry.Interface().(map[string]interface{})) + } + return entries +} + +func (iter *SliceIter) Equals(other Object) Object { + if iter == other { + return True + } + return False +} + +func (iter *SliceIter) GetAttr(name string) (Object, bool) { + switch name { + case "next": + return &Builtin{ + name: "slice_iter.next", + fn: func(ctx context.Context, args ...Object) Object { + if len(args) != 0 { + return NewArgsError("slice_iter.next", 0, len(args)) + } + value, ok := iter.Next() + if !ok { + return Nil + } + return value + }, + }, true + case "entry": + return &Builtin{ + name: "slice_iter.entry", + fn: func(ctx context.Context, args ...Object) Object { + if len(args) != 0 { + return NewArgsError("slice_iter.entry", 0, len(args)) + } + entry, ok := iter.Entry() + if !ok { + return Nil + } + return entry + }, + }, true + } + return nil, false +} + +func (iter *SliceIter) IsTruthy() bool { + return iter.pos < iter.size +} + +func (iter *SliceIter) Next() (Object, bool) { + if iter.pos >= iter.size-1 { + iter.current = nil + return nil, false + } + iter.pos++ + value := reflect.ValueOf(iter.s).Index(iter.pos).Interface() + obj, err := iter.converter.From(value) + if err != nil { + // This shouldn't happen, but consider what to do here... + return nil, false + } + iter.current = obj + return iter.current, true +} + +func (iter *SliceIter) Entry() (IteratorEntry, bool) { + if iter.current == nil { + return nil, false + } + return NewEntry(NewInt(int64(iter.pos)), iter.current), true +} + +func (iter *SliceIter) RunOperation(opType op.BinaryOpType, right Object) Object { + return NewError(fmt.Errorf("eval error: unsupported operation for slice_iter: %v", opType)) +} + +func NewSliceIter(s interface{}) (*SliceIter, error) { + typ := reflect.TypeOf(s) + if typ.Kind() != reflect.Slice { + return nil, fmt.Errorf("type error: cannot create slice_iter (%T given)", s) + } + conv, err := NewTypeConverter(typ.Elem()) + if err != nil { + return nil, err + } + return &SliceIter{ + s: s, + size: reflect.ValueOf(s).Len(), + pos: -1, + converter: conv, + }, nil +} diff --git a/object/slice_iter_test.go b/object/slice_iter_test.go new file mode 100644 index 00000000..2c479867 --- /dev/null +++ b/object/slice_iter_test.go @@ -0,0 +1,71 @@ +package object + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSliceIter(t *testing.T) { + + iter, err := NewSliceIter([]int{5, 6}) + require.Nil(t, err) + + require.True(t, iter.IsTruthy()) + require.Equal(t, "slice_iter(pos=-1 size=2)", iter.Inspect()) + + // Call Next to go to position 0 (value 5) + obj, ok := iter.Next() + require.True(t, ok) + require.Equal(t, int64(5), obj.(*Int).value) + + entryObj, ok := iter.Entry() + require.True(t, ok) + entry := entryObj.(*Entry) + require.Equal(t, NewInt(0), entry.Key()) + require.Equal(t, NewInt(5), entry.Value()) + + // Call Next to go to position 1 (value 6) + obj, ok = iter.Next() + require.True(t, ok) + require.Equal(t, int64(6), obj.(*Int).value) + + entryObj, ok = iter.Entry() + require.True(t, ok) + entry = entryObj.(*Entry) + require.Equal(t, NewInt(1), entry.Key()) + require.Equal(t, NewInt(6), entry.Value()) + + // We should be at the end now + _, ok = iter.Next() + require.False(t, ok) +} + +func TestSliceIterStrings(t *testing.T) { + + iter, err := NewSliceIter([]string{"apple", "banana"}) + require.Nil(t, err) + + obj, ok := iter.Next() + require.True(t, ok) + require.Equal(t, "apple", obj.(*String).value) + + entryObj, ok := iter.Entry() + require.True(t, ok) + entry := entryObj.(*Entry) + require.Equal(t, NewInt(0), entry.Key()) + require.Equal(t, NewString("apple"), entry.Value()) + + obj, ok = iter.Next() + require.True(t, ok) + require.Equal(t, "banana", obj.(*String).value) + + entryObj, ok = iter.Entry() + require.True(t, ok) + entry = entryObj.(*Entry) + require.Equal(t, NewInt(1), entry.Key()) + require.Equal(t, NewString("banana"), entry.Value()) + + _, ok = iter.Next() + require.False(t, ok) +} diff --git a/object/string.go b/object/string.go index aa05e5e9..6b8617b6 100644 --- a/object/string.go +++ b/object/string.go @@ -9,6 +9,7 @@ import ( ) type String struct { + *base value string } @@ -422,7 +423,13 @@ func (s *String) Len() *Int { } func (s *String) Iter() Iterator { - return NewStringIter(s) + runes := []rune(s.value) + return &SliceIter{ + s: runes, + size: len(runes), + pos: -1, + converter: &RuneConverter{}, + } } func (s *String) Runes() []Object { diff --git a/object/string_iter.go b/object/string_iter.go deleted file mode 100644 index eab9abbb..00000000 --- a/object/string_iter.go +++ /dev/null @@ -1,115 +0,0 @@ -package object - -import ( - "context" - "fmt" - - "github.com/cloudcmds/tamarin/v2/op" -) - -type StringIter struct { - s *String - runes []rune - pos int64 - current *String -} - -func (iter *StringIter) Type() Type { - return STRING_ITER -} - -func (iter *StringIter) Inspect() string { - return fmt.Sprintf("string_iter(%s)", iter.s.Inspect()) -} - -func (iter *StringIter) String() string { - return iter.Inspect() -} - -func (iter *StringIter) Interface() interface{} { - var entries []map[string]interface{} - for { - entry, ok := iter.Next() - if !ok { - break - } - entries = append(entries, entry.Interface().(map[string]interface{})) - } - return entries -} - -func (iter *StringIter) Equals(other Object) Object { - switch other := other.(type) { - case *StringIter: - return NewBool(iter == other) - default: - return False - } -} - -func (iter *StringIter) GetAttr(name string) (Object, bool) { - switch name { - case "next": - return &Builtin{ - name: "string_iter.next", - fn: func(ctx context.Context, args ...Object) Object { - if len(args) != 0 { - return NewArgsError("string_iter.next", 0, len(args)) - } - value, ok := iter.Next() - if !ok { - return Nil - } - return value - }, - }, true - case "entry": - return &Builtin{ - name: "string_iter.entry", - fn: func(ctx context.Context, args ...Object) Object { - if len(args) != 0 { - return NewArgsError("string_iter.entry", 0, len(args)) - } - entry, ok := iter.Entry() - if !ok { - return Nil - } - return entry - }, - }, true - } - return nil, false -} - -func (iter *StringIter) IsTruthy() bool { - return iter.pos < int64(len(iter.runes)) -} - -func (iter *StringIter) Next() (Object, bool) { - if iter.pos >= int64(len(iter.runes)-1) { - iter.current = nil - return nil, false - } - iter.pos++ - iter.current = NewString(string(iter.runes[iter.pos])) - return iter.current, true -} - -func (iter *StringIter) Entry() (IteratorEntry, bool) { - if iter.current == nil { - return nil, false - } - return NewEntry(NewInt(iter.pos), iter.current), true -} - -func (iter *StringIter) RunOperation(opType op.BinaryOpType, right Object) Object { - return NewError(fmt.Errorf("eval error: unsupported operation for string_iter: %v", opType)) -} - -func (iter *StringIter) Cost() int { - return 1 -} - -func NewStringIter(s *String) *StringIter { - return &StringIter{s: s, runes: []rune(s.value), pos: -1} -} diff --git a/object/time.go b/object/time.go index 5591f3ba..a84a5088 100644 --- a/object/time.go +++ b/object/time.go @@ -9,6 +9,7 @@ import ( ) type Time struct { + *base value time.Time } @@ -129,7 +130,3 @@ func (t *Time) Unix(ctx context.Context, args ...Object) Object { func (t *Time) IsTruthy() bool { return !t.value.IsZero() } - -func (t *Time) Cost() int { - return 8 -} diff --git a/object/typeconv.go b/object/typeconv.go index 135b7d7e..906463f5 100644 --- a/object/typeconv.go +++ b/object/typeconv.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "math" "reflect" "time" @@ -32,14 +31,10 @@ var kindConverters = map[reflect.Kind]TypeConverter{ var typeConverters = map[reflect.Type]TypeConverter{ reflect.TypeOf(time.Time{}): &TimeConverter{}, reflect.TypeOf(bytes.NewBuffer(nil)): &BufferConverter{}, - reflect.TypeOf([]byte{}): &BSliceConverter{}, + reflect.TypeOf([]byte{}): &ByteSliceConverter{}, + reflect.TypeOf(byte(0)): &ByteConverter{}, } -// Outside of those, we want to handle: -// * Array -// * Func -// * Interface (Partial?) - // Kinds do NOT intend to handle for now: // * Chan // * Complex64 @@ -62,7 +57,7 @@ func AsString(obj Object) (string, *Error) { switch obj := obj.(type) { case *String: return obj.value, nil - case *BSlice: + case *ByteSlice: return string(obj.value), nil case *Buffer: return obj.value.String(), nil @@ -72,17 +67,35 @@ func AsString(obj Object) (string, *Error) { } func AsInt(obj Object) (int64, *Error) { - i, ok := obj.(*Int) - if !ok { + switch obj := obj.(type) { + case *Int: + return obj.value, nil + case *Byte: + return int64(obj.value), nil + default: return 0, Errorf("type error: expected an integer (%s given)", obj.Type()) } - return i.value, nil +} + +func AsByte(obj Object) (byte, *Error) { + switch obj := obj.(type) { + case *Int: + return byte(obj.value), nil + case *Byte: + return obj.value, nil + case *Float: + return byte(obj.value), nil + default: + return 0, Errorf("type error: expected a byte (%s given)", obj.Type()) + } } func AsFloat(obj Object) (float64, *Error) { switch obj := obj.(type) { case *Int: return float64(obj.value), nil + case *Byte: + return float64(obj.value), nil case *Float: return obj.value, nil default: @@ -124,7 +137,7 @@ func AsSet(obj Object) (*Set, *Error) { func AsBytes(obj Object) ([]byte, *Error) { switch obj := obj.(type) { - case *BSlice: + case *ByteSlice: return obj.value, nil case *Buffer: return obj.value.Bytes(), nil @@ -137,7 +150,7 @@ func AsBytes(obj Object) ([]byte, *Error) { func AsReader(obj Object) (io.Reader, *Error) { switch obj := obj.(type) { - case *BSlice: + case *ByteSlice: return bytes.NewBuffer(obj.value), nil case *String: return bytes.NewBufferString(obj.value), nil @@ -183,9 +196,9 @@ func FromGoType(obj interface{}) Object { case string: return NewString(obj) case byte: - return NewInt(int64(obj)) + return NewByte(obj) case []byte: - return NewBSlice(obj) + return NewByteSlice(obj) case *bytes.Buffer: return NewBuffer(obj) case bool: @@ -298,6 +311,11 @@ func getTypeConverter(typ reflect.Type) (TypeConverter, error) { if err != nil { return nil, err } + case reflect.Array: + converter, err = newArrayConverter(typ.Elem(), typ.Len()) + if err != nil { + return nil, err + } case reflect.Map: if typ.Key().Kind() == reflect.String { converter, err = newMapConverter(typ.Elem()) @@ -316,7 +334,7 @@ func getTypeConverter(typ reflect.Type) (TypeConverter, error) { converter = &DynamicConverter{} } default: - return nil, fmt.Errorf("type error: unsupported type %s", typ) + return nil, fmt.Errorf("type error: unsupported kind: %q", kind) } return converter, nil } @@ -325,18 +343,72 @@ func getTypeConverter(typ reflect.Type) (TypeConverter, error) { type BoolConverter struct{} func (c *BoolConverter) To(obj Object) (interface{}, error) { - return obj.(*Bool).value, nil + b, ok := obj.(*Bool) + if !ok { + return nil, fmt.Errorf("type error: expected bool (%s given)", obj.Type()) + } + return b.value, nil } func (c *BoolConverter) From(obj interface{}) (Object, error) { return NewBool(obj.(bool)), nil } +// ByteConverter converts between byte and *Byte. +type ByteConverter struct{} + +func (c *ByteConverter) To(obj Object) (interface{}, error) { + switch obj := obj.(type) { + case *Byte: + return obj.value, nil + case *Int: + return byte(obj.value), nil + case *Float: + return byte(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected byte (%s given)", obj.Type()) + } +} + +func (c *ByteConverter) From(obj interface{}) (Object, error) { + return NewByte(obj.(byte)), nil +} + +// RuneConverter converts between rune and *String. +type RuneConverter struct{} + +func (c *RuneConverter) To(obj Object) (interface{}, error) { + switch obj := obj.(type) { + case *String: + if len(obj.value) != 1 { + return nil, fmt.Errorf("type error: expected single rune string (got length %d)", len(obj.value)) + } + return []rune(obj.value)[0], nil + case *Int: + return rune(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected string (%s given)", obj.Type()) + } +} + +func (c *RuneConverter) From(obj interface{}) (Object, error) { + return NewString(string([]rune{obj.(rune)})), nil +} + // IntConverter converts between int and *Int. type IntConverter struct{} func (c *IntConverter) To(obj Object) (interface{}, error) { - return int(obj.(*Int).value), nil + switch obj := obj.(type) { + case *Byte: + return int(obj.value), nil + case *Int: + return int(obj.value), nil + case *Float: + return int(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *IntConverter) From(obj interface{}) (Object, error) { @@ -347,7 +419,16 @@ func (c *IntConverter) From(obj interface{}) (Object, error) { type Int8Converter struct{} func (c *Int8Converter) To(obj Object) (interface{}, error) { - return int8(obj.(*Int).value), nil + switch obj := obj.(type) { + case *Byte: + return int8(obj.value), nil + case *Int: + return int8(obj.value), nil + case *Float: + return int8(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *Int8Converter) From(obj interface{}) (Object, error) { @@ -358,7 +439,16 @@ func (c *Int8Converter) From(obj interface{}) (Object, error) { type Int16Converter struct{} func (c *Int16Converter) To(obj Object) (interface{}, error) { - return int16(obj.(*Int).value), nil + switch obj := obj.(type) { + case *Byte: + return int16(obj.value), nil + case *Int: + return int16(obj.value), nil + case *Float: + return int16(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *Int16Converter) From(obj interface{}) (Object, error) { @@ -369,7 +459,16 @@ func (c *Int16Converter) From(obj interface{}) (Object, error) { type Int32Converter struct{} func (c *Int32Converter) To(obj Object) (interface{}, error) { - return int32(obj.(*Int).value), nil + switch obj := obj.(type) { + case *Byte: + return int32(obj.value), nil + case *Int: + return int32(obj.value), nil + case *Float: + return int32(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *Int32Converter) From(obj interface{}) (Object, error) { @@ -380,7 +479,16 @@ func (c *Int32Converter) From(obj interface{}) (Object, error) { type Int64Converter struct{} func (c *Int64Converter) To(obj Object) (interface{}, error) { - return obj.(*Int).value, nil + switch obj := obj.(type) { + case *Byte: + return int64(obj.value), nil + case *Int: + return int64(obj.value), nil + case *Float: + return int64(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *Int64Converter) From(obj interface{}) (Object, error) { @@ -391,7 +499,16 @@ func (c *Int64Converter) From(obj interface{}) (Object, error) { type UintConverter struct{} func (c *UintConverter) To(obj Object) (interface{}, error) { - return uint(obj.(*Int).value), nil + switch obj := obj.(type) { + case *Byte: + return uint(obj.value), nil + case *Int: + return uint(obj.value), nil + case *Float: + return uint(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *UintConverter) From(obj interface{}) (Object, error) { @@ -402,7 +519,16 @@ func (c *UintConverter) From(obj interface{}) (Object, error) { type Uint8Converter struct{} func (c *Uint8Converter) To(obj Object) (interface{}, error) { - return uint8(obj.(*Int).value), nil + switch obj := obj.(type) { + case *Byte: + return uint8(obj.value), nil + case *Int: + return uint8(obj.value), nil + case *Float: + return uint8(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *Uint8Converter) From(obj interface{}) (Object, error) { @@ -413,7 +539,16 @@ func (c *Uint8Converter) From(obj interface{}) (Object, error) { type Uint16Converter struct{} func (c *Uint16Converter) To(obj Object) (interface{}, error) { - return uint16(obj.(*Int).value), nil + switch obj := obj.(type) { + case *Byte: + return uint16(obj.value), nil + case *Int: + return uint16(obj.value), nil + case *Float: + return uint16(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *Uint16Converter) From(obj interface{}) (Object, error) { @@ -424,7 +559,16 @@ func (c *Uint16Converter) From(obj interface{}) (Object, error) { type Uint32Converter struct{} func (c *Uint32Converter) To(obj Object) (interface{}, error) { - return uint32(obj.(*Int).value), nil + switch obj := obj.(type) { + case *Byte: + return uint32(obj.value), nil + case *Int: + return uint32(obj.value), nil + case *Float: + return uint32(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) + } } func (c *Uint32Converter) From(obj interface{}) (Object, error) { @@ -435,26 +579,36 @@ func (c *Uint32Converter) From(obj interface{}) (Object, error) { type Uint64Converter struct{} func (c *Uint64Converter) To(obj Object) (interface{}, error) { - v := obj.(*Int).value - if v < 0 { - return nil, fmt.Errorf("value error: %d is out of range for uint64", v) + switch obj := obj.(type) { + case *Byte: + return uint64(obj.value), nil + case *Int: + return uint64(obj.value), nil + case *Float: + return uint64(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected int (%s given)", obj.Type()) } - return uint64(obj.(*Int).value), nil } func (c *Uint64Converter) From(obj interface{}) (Object, error) { - v := obj.(uint64) - if v > math.MaxInt64 { - return nil, fmt.Errorf("value error: %d is out of range for int64", v) - } - return NewInt(int64(v)), nil + return NewInt(int64(obj.(uint64))), nil } // Float32Converter converts between float32 and *Float. type Float32Converter struct{} func (c *Float32Converter) To(obj Object) (interface{}, error) { - return float32(obj.(*Float).value), nil + switch obj := obj.(type) { + case *Byte: + return float32(obj.value), nil + case *Int: + return float32(obj.value), nil + case *Float: + return float32(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected float (%s given)", obj.Type()) + } } func (c *Float32Converter) From(obj interface{}) (Object, error) { @@ -465,7 +619,16 @@ func (c *Float32Converter) From(obj interface{}) (Object, error) { type Float64Converter struct{} func (c *Float64Converter) To(obj Object) (interface{}, error) { - return obj.(*Float).value, nil + switch obj := obj.(type) { + case *Byte: + return float64(obj.value), nil + case *Int: + return float64(obj.value), nil + case *Float: + return obj.value, nil + default: + return nil, fmt.Errorf("type error: expected float (%s given)", obj.Type()) + } } func (c *Float64Converter) From(obj interface{}) (Object, error) { @@ -476,29 +639,54 @@ func (c *Float64Converter) From(obj interface{}) (Object, error) { type StringConverter struct{} func (c *StringConverter) To(obj Object) (interface{}, error) { - return obj.(*String).value, nil + switch obj := obj.(type) { + case *ByteSlice: + return string(obj.value), nil + case *Buffer: + return obj.value.String(), nil + case *String: + return obj.value, nil + default: + return nil, fmt.Errorf("type error: expected string (%s given)", obj.Type()) + } } func (c *StringConverter) From(obj interface{}) (Object, error) { return NewString(obj.(string)), nil } -// BSliceConverter converts between []byte and BSlice. -type BSliceConverter struct{} +// ByteSliceConverter converts between []byte and *ByteSlice. +type ByteSliceConverter struct{} -func (c *BSliceConverter) To(obj Object) (interface{}, error) { - return obj.(*BSlice).value, nil +func (c *ByteSliceConverter) To(obj Object) (interface{}, error) { + switch obj := obj.(type) { + case *ByteSlice: + return obj.value, nil + case *Buffer: + return obj.value.Bytes(), nil + case *String: + return []byte(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected bytes (%s given)", obj.Type()) + } } -func (c *BSliceConverter) From(obj interface{}) (Object, error) { - return NewBSlice(obj.([]byte)), nil +func (c *ByteSliceConverter) From(obj interface{}) (Object, error) { + return NewByteSlice(obj.([]byte)), nil } // TimeConverter converts between time.Time and *Time. type TimeConverter struct{} func (c *TimeConverter) To(obj Object) (interface{}, error) { - return obj.(*Time).value, nil + switch obj := obj.(type) { + case *Time: + return obj.value, nil + case *String: + return time.Parse(time.RFC3339, obj.value) + default: + return nil, fmt.Errorf("type error: expected time (%s given)", obj.Type()) + } } func (c *TimeConverter) From(obj interface{}) (Object, error) { @@ -509,7 +697,14 @@ func (c *TimeConverter) From(obj interface{}) (Object, error) { type BufferConverter struct{} func (c *BufferConverter) To(obj Object) (interface{}, error) { - return obj.(*Buffer).value, nil + switch obj := obj.(type) { + case *Buffer: + return obj.value, nil + case *ByteSlice: + return bytes.NewBuffer(obj.value), nil + default: + return nil, fmt.Errorf("type error: expected buffer (%s given)", obj.Type()) + } } func (c *BufferConverter) From(obj interface{}) (Object, error) { @@ -540,7 +735,10 @@ type MapConverter struct { } func (c *MapConverter) To(obj Object) (interface{}, error) { - tMap := obj.(*Map) + tMap, ok := obj.(*Map) + if !ok { + return nil, fmt.Errorf("type error: expected map (%s given)", obj.Type()) + } keyType := reflect.TypeOf("") mapType := reflect.MapOf(keyType, c.valueType) gMap := reflect.MakeMapWithSize(mapType, tMap.Size()) @@ -687,7 +885,10 @@ type SliceConverter struct { } func (c *SliceConverter) To(obj Object) (interface{}, error) { - list := obj.(*List) + list, ok := obj.(*List) + if !ok { + return nil, fmt.Errorf("type error: expected a list (%s given)", obj.Type()) + } slice := reflect.MakeSlice(reflect.SliceOf(c.valueType), 0, len(list.items)) for _, v := range list.items { item, err := c.valueConverter.To(v) @@ -726,11 +927,73 @@ func newSliceConverter(indirectType reflect.Type) (*SliceConverter, error) { }, nil } -// ErrorConverter converts between error and *Error. +// ArrayConverter converts between []T and the Tamarin equivalent of []T. +type ArrayConverter struct { + valueConverter TypeConverter + valueType reflect.Type + len int +} + +func (c *ArrayConverter) To(obj Object) (interface{}, error) { + list, ok := obj.(*List) + if !ok { + return nil, fmt.Errorf("type error: expected a list (%s given)", obj.Type()) + } + array := reflect.New(reflect.ArrayOf(c.len, c.valueType)) + arrayElem := array.Elem() + for i, v := range list.items { + item, err := c.valueConverter.To(v) + if err != nil { + return nil, fmt.Errorf("type error: failed to convert element: %v", err) + } + arrayElem.Index(i).Set(reflect.ValueOf(item)) + } + return arrayElem.Interface(), nil +} + +func (c *ArrayConverter) From(iface interface{}) (Object, error) { + v := reflect.ValueOf(iface) + count := v.Len() + items := make([]Object, 0, count) + for i := 0; i < count; i++ { + item, err := c.valueConverter.From(v.Index(i).Interface()) + if err != nil { + return nil, fmt.Errorf("type error: failed to convert slice element: %v", err) + } + items = append(items, item) + } + return NewList(items), nil +} + +// newArrayConverter creates a TypeConverter for arrays containing the given +// value type, where the items can be converted using the given TypeConverter. +func newArrayConverter(indirectType reflect.Type, length int) (*ArrayConverter, error) { + if length < 0 { + return nil, fmt.Errorf("value error: invalid array length: %d", length) + } + indirectConv, err := createTypeConverter(indirectType) + if err != nil { + return nil, err + } + return &ArrayConverter{ + valueType: indirectType, + valueConverter: indirectConv, + len: length, + }, nil +} + +// ErrorConverter converts between error and *Error or *String. type ErrorConverter struct{} func (c *ErrorConverter) To(obj Object) (interface{}, error) { - return obj.(*Error).err, nil + switch obj := obj.(type) { + case *Error: + return obj.Value(), nil + case *String: + return errors.New(obj.Value()), nil + default: + return nil, fmt.Errorf("type error: expected a string (%s given)", obj.Type()) + } } func (c *ErrorConverter) From(obj interface{}) (Object, error) { diff --git a/object/typeconv_test.go b/object/typeconv_test.go index e228d3fb..51be5983 100644 --- a/object/typeconv_test.go +++ b/object/typeconv_test.go @@ -121,6 +121,28 @@ func TestCreatingPointerViaReflect(t *testing.T) { require.Equal(t, &v, result) } +func TestSetAttributeViaReflect(t *testing.T) { + type test struct { + A int + } + tStruct := test{A: 99} + var tInterface interface{} = tStruct + + if reflect.TypeOf(tInterface).Kind() != reflect.Ptr { + // Create a pointer to the value + tInterfacePointer := reflect.New(reflect.TypeOf(tInterface)) + tInterfacePointer.Elem().Set(reflect.ValueOf(tInterface)) + tInterface = tInterfacePointer.Interface() + } + + // Set the field "A" + value := reflect.ValueOf(tInterface) + value.Elem().FieldByName("A").Set(reflect.ValueOf(100)) + + // Confirm the field was set + require.Equal(t, 100, value.Elem().FieldByName("A").Interface()) +} + func TestSliceConverter(t *testing.T) { c, err := newSliceConverter(reflect.TypeOf(0.0)) require.Nil(t, err) @@ -212,7 +234,7 @@ func TestBufferConverter(t *testing.T) { require.Equal(t, buf, goBuf) } -func TestBSliceConverter(t *testing.T) { +func TestByteSliceConverter(t *testing.T) { buf := []byte("abc") typ := reflect.TypeOf(buf) @@ -222,15 +244,65 @@ func TestBSliceConverter(t *testing.T) { tBuf, err := c.From(buf) require.Nil(t, err) - require.Equal(t, NewBSlice([]byte("abc")), tBuf) + require.Equal(t, NewByteSlice([]byte("abc")), tBuf) - gBuf, err := c.To(NewBSlice([]byte("abc"))) + gBuf, err := c.To(NewByteSlice([]byte("abc"))) require.Nil(t, err) goBuf, ok := gBuf.([]byte) require.True(t, ok) require.Equal(t, buf, goBuf) } +func TestArrayConverterInt(t *testing.T) { + + arr := [4]int{2, 3, 4, 5} + c, err := NewTypeConverter(reflect.TypeOf(arr)) + require.Nil(t, err) + + tList, err := c.From(arr) + require.Nil(t, err) + require.Equal(t, NewList([]Object{ + NewInt(2), + NewInt(3), + NewInt(4), + NewInt(5), + }), tList) + + goValue, err := c.To(NewList([]Object{ + NewInt(-1), + NewInt(-2), + })) + require.Nil(t, err) + + goArray, ok := goValue.([4]int) + require.True(t, ok) + require.Equal(t, [4]int{-1, -2}, goArray) +} + +func TestArrayConverterFloat64(t *testing.T) { + + arr := [2]float64{100, 101} + c, err := NewTypeConverter(reflect.TypeOf(arr)) + require.Nil(t, err) + + tList, err := c.From(arr) + require.Nil(t, err) + require.Equal(t, NewList([]Object{ + NewFloat(100), + NewFloat(101), + }), tList) + + goValue, err := c.To(NewList([]Object{ + NewFloat(-1), + NewFloat(-2), + })) + require.Nil(t, err) + + goArray, ok := goValue.([2]float64) + require.True(t, ok) + require.Equal(t, [2]float64{-1, -2}, goArray) +} + func TestGenericMapConverter(t *testing.T) { m := map[string]interface{}{ diff --git a/parser/parser.go b/parser/parser.go index 7ba338ad..7c24fdf9 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1220,7 +1220,7 @@ func (p *Parser) parseRange() ast.Node { if err := p.nextToken(); err != nil { return nil } - container := p.parseExpression(RANGE) + container := p.parseExpression(PREFIX) if container == nil { p.setTokenError(p.curToken, "invalid range expression") return nil diff --git a/parser/parser_test.go b/parser/parser_test.go index e916cf5b..314fa8f7 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -891,3 +891,32 @@ func TestBadInputs(t *testing.T) { require.Equal(t, tt.expected, err.Error()) } } + +func TestPrecedence(t *testing.T) { + // This confirms the correct precedence of the "range" vs. "call" operators + input := `range byte_slice(99)` + + // Parse the program, which should be 1 statement in length + program, err := Parse(context.Background(), input) + require.Nil(t, err) + require.Len(t, program.Statements(), 1) + stmt := program.First() + + // The top-level of the AST should be a range statement + require.IsType(t, &ast.Range{}, stmt) + rangeStmt := stmt.(*ast.Range) + + // The container of the range statement should be a call expression + require.IsType(t, &ast.Call{}, rangeStmt.Container()) + callStmt := rangeStmt.Container().(*ast.Call) + + // The function of the call expression should be an identifier (byte_slice) + require.IsType(t, &ast.Ident{}, callStmt.Function()) + ident := callStmt.Function().(*ast.Ident) + require.Equal(t, "byte_slice", ident.String()) + + // The argument of the call expression should be an integer + require.IsType(t, &ast.Int{}, callStmt.Arguments()[0]) + intVal := callStmt.Arguments()[0].(*ast.Int) + require.Equal(t, int64(99), intVal.Value()) +} diff --git a/parser/precedence.go b/parser/precedence.go index 5d152259..adc7bf8e 100644 --- a/parser/precedence.go +++ b/parser/precedence.go @@ -20,7 +20,6 @@ const ( PREFIX // -X or !X CALL // myFunction(X) IN // X in Y - RANGE // range X INDEX // array[index], map[key] HIGHEST ) @@ -55,5 +54,5 @@ var precedences = map[token.Type]int{ token.PERIOD: CALL, token.LBRACKET: INDEX, token.IN: IN, - token.RANGE: RANGE, + token.RANGE: PREFIX, } diff --git a/vm/vm_test.go b/vm/vm_test.go index 66315b47..85651a5f 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -242,13 +242,13 @@ func TestForRange7(t *testing.T) { func TestIterator(t *testing.T) { tests := []testCase{ - {`range { 33, 44, 55 }.next()`, object.NewInt(33)}, + {`(range { 33, 44, 55 }).next()`, object.NewInt(33)}, {`i := range { 33, 44, 55 }; i.next(); i.entry().value`, object.True}, {`i := range { 33, 44, 55 }; i.next(); i.entry().key`, object.NewInt(33)}, - {`range [ 33, 44, 55 ].next()`, object.NewInt(33)}, + {`(range [ 33, 44, 55 ]).next()`, object.NewInt(33)}, {`i := range "abcd"; i.next(); i.entry().key`, object.NewInt(0)}, {`i := range "abcd"; i.next(); i.entry().value`, object.NewString("a")}, - {`range { a: 33, b: 44 }.next()`, object.NewString("a")}, + {`(range { a: 33, b: 44 }).next()`, object.NewString("a")}, {`i := range { a: 33, b: 44 }; i.next(); i.entry().key`, object.NewString("a")}, {`i := range { a: 33, b: 44 }; i.next(); i.entry().value`, object.NewInt(33)}, } @@ -804,6 +804,7 @@ func TestLength(t *testing.T) { {`len({"abc": 1})`, object.NewInt(1)}, {`len({"abc"})`, object.NewInt(1)}, {`len("ᛛᛥ")`, object.NewInt(2)}, + {`len(string(byte_slice([0, 1, 2])))`, object.NewInt(3)}, } runTests(t, tests) } @@ -814,6 +815,9 @@ func TestBuiltins(t *testing.T) { {`keys({"a": 1})`, object.NewList([]object.Object{ object.NewString("a"), })}, + {`byte(9)`, object.NewByte(9)}, + {`byte_slice([9])`, object.NewByteSlice([]byte{9})}, + {`float_slice([9])`, object.NewFloatSlice([]float64{9})}, {`type(3.14159)`, object.NewString("float")}, {`type("hi".contains)`, object.NewString("builtin")}, {`sprintf("%d-%d", 1, 2)`, object.NewString("1-2")}, @@ -823,6 +827,8 @@ func TestBuiltins(t *testing.T) { {`string(2.5)`, object.NewString("2.5")}, {`ord("a")`, object.NewInt(97)}, {`chr(97)`, object.NewString("a")}, + {`encode("hi", "hex")`, object.NewString("6869")}, + {`encode("hi", "base64")`, object.NewString("aGk=")}, {`iter("abc").next()`, object.NewString("a")}, {`i := iter("abc"); i.next(); i.entry().key`, object.NewInt(0)}, {`i := iter("abc"); i.next(); i.entry().value`, object.NewString("a")},