From 19498da4914b93281bf9d51b64872fd212b7c30f Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Thu, 24 Jan 2019 19:31:17 -0800 Subject: [PATCH 1/4] adding immutable value (incomplete) --- compiler/ast/immutable_expr.go | 29 +++++++++++++++++++++++++++++ compiler/compiler.go | 7 +++++++ compiler/opcodes.go | 3 +++ compiler/parser/parser.go | 24 +++++++++++++++++++++++- compiler/token/tokens.go | 2 ++ docs/tutorial.md | 19 ++++++++++++++++++- runtime/vm.go | 3 +++ 7 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 compiler/ast/immutable_expr.go diff --git a/compiler/ast/immutable_expr.go b/compiler/ast/immutable_expr.go new file mode 100644 index 0000000..f9843b5 --- /dev/null +++ b/compiler/ast/immutable_expr.go @@ -0,0 +1,29 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" +) + +// ImmutableExpr represents an immutable expression +type ImmutableExpr struct { + Expr Expr + ErrorPos source.Pos + LParen source.Pos + RParen source.Pos +} + +func (e *ImmutableExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ImmutableExpr) Pos() source.Pos { + return e.ErrorPos +} + +// End returns the position of first character immediately after the node. +func (e *ImmutableExpr) End() source.Pos { + return e.RParen +} + +func (e *ImmutableExpr) String() string { + return "immutable(" + e.Expr.String() + ")" +} diff --git a/compiler/compiler.go b/compiler/compiler.go index 1b3b331..c39ae8d 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -473,6 +473,13 @@ func (c *Compiler) Compile(node ast.Node) error { } c.emit(OpError) + + case *ast.ImmutableExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + + c.emit(OpImmutable) } return nil diff --git a/compiler/opcodes.go b/compiler/opcodes.go index 0fd92eb..eaa4892 100644 --- a/compiler/opcodes.go +++ b/compiler/opcodes.go @@ -35,6 +35,7 @@ const ( OpArray // Array object OpMap // Map object OpError // Error object + OpImmutable // Immutable object OpIndex // Index operation OpSliceIndex // Slice operation OpCall // Call function @@ -94,6 +95,7 @@ var OpcodeNames = [...]string{ OpArray: "ARR", OpMap: "MAP", OpError: "ERROR", + OpImmutable: "IMMUT", OpIndex: "INDEX", OpSliceIndex: "SLICE", OpCall: "CALL", @@ -150,6 +152,7 @@ var OpcodeOperands = [...][]int{ OpArray: {2}, OpMap: {2}, OpError: {}, + OpImmutable: {}, OpIndex: {}, OpSliceIndex: {}, OpCall: {1}, diff --git a/compiler/parser/parser.go b/compiler/parser/parser.go index ae19d12..c24fc26 100644 --- a/compiler/parser/parser.go +++ b/compiler/parser/parser.go @@ -361,6 +361,9 @@ func (p *Parser) parseOperand() ast.Expr { case token.Error: // error expression return p.parseErrorExpr() + + case token.Immutable: // immutable expression + return p.parseImmutableExpr() } pos := p.pos @@ -483,6 +486,25 @@ func (p *Parser) parseErrorExpr() ast.Expr { return expr } +func (p *Parser) parseImmutableExpr() ast.Expr { + pos := p.pos + + p.next() + + lparen := p.expect(token.LParen) + value := p.parseExpr() + rparen := p.expect(token.RParen) + + expr := &ast.ImmutableExpr{ + ErrorPos: pos, + Expr: value, + LParen: lparen, + RParen: rparen, + } + + return expr +} + func (p *Parser) parseFuncType() *ast.FuncType { if p.trace { defer un(trace(p, "FuncType")) @@ -572,7 +594,7 @@ func (p *Parser) parseStmt() (stmt ast.Stmt) { switch p.token { case // simple statements - token.Func, token.Error, token.Ident, token.Int, token.Float, token.Char, token.String, token.True, token.False, + token.Func, token.Error, token.Immutable, token.Ident, token.Int, token.Float, token.Char, token.String, token.True, token.False, token.Undefined, token.Import, token.LParen, token.LBrace, token.LBrack, token.Add, token.Sub, token.Mul, token.And, token.Xor, token.Not: s := p.parseSimpleStmt(false) diff --git a/compiler/token/tokens.go b/compiler/token/tokens.go index 1721d23..5e37c75 100644 --- a/compiler/token/tokens.go +++ b/compiler/token/tokens.go @@ -74,6 +74,7 @@ const ( For Func Error + Immutable If Return Switch @@ -149,6 +150,7 @@ var tokens = [...]string{ For: "for", Func: "func", Error: "error", + Immutable: "immutable", If: "if", Return: "return", Switch: "switch", diff --git a/docs/tutorial.md b/docs/tutorial.md index 5993442..03e3c58 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -150,9 +150,26 @@ for k, v in {k1: 1, k2: 2} { // map: key and value } ``` +## Immutable Values + +A value can be marked as immutable using `immutable` expression. + +```golang +a := immutable([1, 2, 3]) // 'a' is immutable +b = a[1] // b == 2 +a[0] = 5 // runtime error + +c := immutable({d: [1, 2, 3]}) +c.d[1] = 10 // runtime error as 'c.d' is also immutable + +e := {f: a} // 'a' is immutable but 'e' is not +e.g = 20 // valid; e == {f: a, g: 20} +e.a[1] = 5 // runtime error as 'e.a' is immutable +``` + ## Errors -An error object is created using `error` function-like keyword. An error can have any types of value and the underlying value of the error can be accessed using `.value` selector. +An error object is created using `error` expression. An error can contain value of any types, and, the underlying value can be read using `.value` selector. ```golang err1 := error("oops") // error with string value diff --git a/runtime/vm.go b/runtime/vm.go index d16288b..559d592 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -562,6 +562,9 @@ func (v *VM) Run() error { v.stack[v.sp] = &err v.sp++ + case compiler.OpImmutable: + // TODO: implement here + case compiler.OpIndex: index := v.stack[v.sp-1] left := v.stack[v.sp-2] From 85001be9b8fb1acd0678e02efc6d77e31560dc3c Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 25 Jan 2019 14:54:58 -0800 Subject: [PATCH 2/4] implement immutable array and map --- assert/assert.go | 26 +++---- compiler/bytecode.go | 1 - docs/tutorial.md | 35 +++++++--- objects/array.go | 13 ++-- objects/immautable_array.go | 109 ++++++++++++++++++++++++++++++ objects/immutable_map.go | 17 +++-- objects/immutable_map_iterator.go | 62 ----------------- objects/map.go | 13 ++-- runtime/vm.go | 52 +++++++++++--- runtime/vm_immutable_test.go | 48 +++++++++++++ runtime/vm_test.go | 20 ++++++ 11 files changed, 284 insertions(+), 112 deletions(-) create mode 100644 objects/immautable_array.go delete mode 100644 objects/immutable_map_iterator.go create mode 100644 runtime/vm_immutable_test.go diff --git a/assert/assert.go b/assert/assert.go index 29efa82..bb9cb06 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -164,13 +164,17 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool case *objects.ReturnValue: return Equal(t, expected.Value, actual.(objects.ReturnValue).Value) case *objects.Array: - return equalArray(t, expected, actual.(*objects.Array)) + return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value) + case *objects.ImmutableArray: + return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value) case *objects.Bytes: if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 { return failExpectedActual(t, expected.Value, actual.(*objects.Bytes).Value, msg...) } case *objects.Map: - return equalMap(t, expected, actual.(*objects.Map)) + return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value) + case *objects.ImmutableMap: + return equalObjectMap(t, expected.Value, actual.(*objects.ImmutableMap).Value) case *objects.CompiledFunction: return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction)) case *objects.Closure: @@ -252,13 +256,6 @@ func equalSymbol(a, b compiler.Symbol) bool { a.Scope == b.Scope } -func equalArray(t *testing.T, expected, actual objects.Object) bool { - expectedT := expected.(*objects.Array).Value - actualT := actual.(*objects.Array).Value - - return equalObjectSlice(t, expectedT, actualT) -} - func equalObjectSlice(t *testing.T, expected, actual []objects.Object) bool { // TODO: this test does not differentiate nil vs empty slice @@ -275,16 +272,13 @@ func equalObjectSlice(t *testing.T, expected, actual []objects.Object) bool { return true } -func equalMap(t *testing.T, expected, actual objects.Object) bool { - expectedT := expected.(*objects.Map).Value - actualT := actual.(*objects.Map).Value - - if !Equal(t, len(expectedT), len(actualT)) { +func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bool { + if !Equal(t, len(expected), len(actual)) { return false } - for key, expectedVal := range expectedT { - actualVal := actualT[key] + for key, expectedVal := range expected { + actualVal := actual[key] if !Equal(t, expectedVal, actualVal) { return false diff --git a/compiler/bytecode.go b/compiler/bytecode.go index a0f10ba..9d13d0b 100644 --- a/compiler/bytecode.go +++ b/compiler/bytecode.go @@ -50,6 +50,5 @@ func init() { gob.Register(&objects.Bytes{}) gob.Register(&objects.StringIterator{}) gob.Register(&objects.MapIterator{}) - gob.Register(&objects.ImmutableMapIterator{}) gob.Register(&objects.ArrayIterator{}) } diff --git a/docs/tutorial.md b/docs/tutorial.md index 03e3c58..f3999d5 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -152,19 +152,36 @@ for k, v in {k1: 1, k2: 2} { // map: key and value ## Immutable Values -A value can be marked as immutable using `immutable` expression. +Basically, all values of the primitive types (Int, Float, String, Bytes, Char, Bool) are immutable. ```golang -a := immutable([1, 2, 3]) // 'a' is immutable -b = a[1] // b == 2 -a[0] = 5 // runtime error +s := "12345" +s[1] = 'b' // error: String is immutable +s = "foo" // ok: this is not mutating the value + // but updating reference 's' with another String value +``` -c := immutable({d: [1, 2, 3]}) -c.d[1] = 10 // runtime error as 'c.d' is also immutable +The composite types (Array, Map) are mutable by default, but, you can make them immutable using `immutable` expression. -e := {f: a} // 'a' is immutable but 'e' is not -e.g = 20 // valid; e == {f: a, g: 20} -e.a[1] = 5 // runtime error as 'e.a' is immutable +```golang +a := [1, 2, 3] +a[1] = "foo" // ok: array is mutable + +b := immutable([1, 2, 3]) +b[1] = "foo" // error: 'b' references to an immutable array. +b = "foo" // ok: this is not mutating the value of array + // but updating reference 'b' with different value +``` + +Not that, if you copy (using `copy` builtin function) an immutable value, it will return a "mutable" copy. Also, immutability is not applied to the individual elements of the array or map value, unless they are explicitly made immutable. + +```golang +a := immutable({b: 4, c: [1, 2, 3]}) +a.b = 5 // error +a.c[1] = 5 // ok: because 'a.c' is not immutable + +a = immutable({b: 4, c: immutable([1, 2, 3])}) +a.c[1] = 5 // error ``` ## Errors diff --git a/objects/array.go b/objects/array.go index e716709..20de2e1 100644 --- a/objects/array.go +++ b/objects/array.go @@ -60,17 +60,22 @@ func (o *Array) IsFalsy() bool { // Equals returns true if the value of the type // is equal to the value of another object. func (o *Array) Equals(x Object) bool { - t, ok := x.(*Array) - if !ok { + var xVal []Object + switch x := x.(type) { + case *Array: + xVal = x.Value + case *ImmutableArray: + xVal = x.Value + default: return false } - if len(o.Value) != len(t.Value) { + if len(o.Value) != len(xVal) { return false } for i, e := range o.Value { - if !e.Equals(t.Value[i]) { + if !e.Equals(xVal[i]) { return false } } diff --git a/objects/immautable_array.go b/objects/immautable_array.go new file mode 100644 index 0000000..36767ae --- /dev/null +++ b/objects/immautable_array.go @@ -0,0 +1,109 @@ +package objects + +import ( + "fmt" + "strings" + + "github.com/d5/tengo/compiler/token" +) + +// ImmutableArray represents an immutable array of objects. +type ImmutableArray struct { + Value []Object +} + +// TypeName returns the name of the type. +func (o *ImmutableArray) TypeName() string { + return "immutable-array" +} + +func (o *ImmutableArray) String() string { + var elements []string + for _, e := range o.Value { + elements = append(elements, e.String()) + } + + return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *ImmutableArray) BinaryOp(op token.Token, rhs Object) (Object, error) { + if rhs, ok := rhs.(*ImmutableArray); ok { + switch op { + case token.Add: + return &Array{Value: append(o.Value, rhs.Value...)}, nil + } + } + + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *ImmutableArray) Copy() Object { + var c []Object + for _, elem := range o.Value { + c = append(c, elem.Copy()) + } + + return &Array{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ImmutableArray) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *ImmutableArray) Equals(x Object) bool { + var xVal []Object + switch x := x.(type) { + case *Array: + xVal = x.Value + case *ImmutableArray: + xVal = x.Value + default: + return false + } + + if len(o.Value) != len(xVal) { + return false + } + + for i, e := range o.Value { + if !e.Equals(xVal[i]) { + return false + } + } + + return true +} + +// IndexGet returns an element at a given index. +func (o *ImmutableArray) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + + idxVal := int(intIdx.Value) + + if idxVal < 0 || idxVal >= len(o.Value) { + err = ErrIndexOutOfBounds + return + } + + res = o.Value[idxVal] + + return +} + +// Iterate creates an array iterator. +func (o *ImmutableArray) Iterate() Iterator { + return &ArrayIterator{ + v: o.Value, + l: len(o.Value), + } +} diff --git a/objects/immutable_map.go b/objects/immutable_map.go index 60c60c1..38ae670 100644 --- a/objects/immutable_map.go +++ b/objects/immutable_map.go @@ -39,7 +39,7 @@ func (o *ImmutableMap) Copy() Object { c[k] = v.Copy() } - return &ImmutableMap{Value: c} + return &Map{Value: c} } // IsFalsy returns true if the value of the type is falsy. @@ -66,17 +66,22 @@ func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) { // Equals returns true if the value of the type // is equal to the value of another object. func (o *ImmutableMap) Equals(x Object) bool { - t, ok := x.(*ImmutableMap) - if !ok { + var xVal map[string]Object + switch x := x.(type) { + case *Map: + xVal = x.Value + case *ImmutableMap: + xVal = x.Value + default: return false } - if len(o.Value) != len(t.Value) { + if len(o.Value) != len(xVal) { return false } for k, v := range o.Value { - tv := t.Value[k] + tv := xVal[k] if !v.Equals(tv) { return false } @@ -92,7 +97,7 @@ func (o *ImmutableMap) Iterate() Iterator { keys = append(keys, k) } - return &ImmutableMapIterator{ + return &MapIterator{ v: o.Value, k: keys, l: len(keys), diff --git a/objects/immutable_map_iterator.go b/objects/immutable_map_iterator.go deleted file mode 100644 index 9937706..0000000 --- a/objects/immutable_map_iterator.go +++ /dev/null @@ -1,62 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// ImmutableMapIterator represents an iterator for the immutable map. -type ImmutableMapIterator struct { - v map[string]Object - k []string - i int - l int -} - -// TypeName returns the name of the type. -func (i *ImmutableMapIterator) TypeName() string { - return "module-iterator" -} - -func (i *ImmutableMapIterator) String() string { - return "" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (i *ImmutableMapIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// IsFalsy returns true if the value of the type is falsy. -func (i *ImmutableMapIterator) IsFalsy() bool { - return true -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (i *ImmutableMapIterator) Equals(Object) bool { - return false -} - -// Copy returns a copy of the type. -func (i *ImmutableMapIterator) Copy() Object { - return &ImmutableMapIterator{v: i.v, k: i.k, i: i.i, l: i.l} -} - -// Next returns true if there are more elements to iterate. -func (i *ImmutableMapIterator) Next() bool { - i.i++ - return i.i <= i.l -} - -// Key returns the key or index value of the current element. -func (i *ImmutableMapIterator) Key() Object { - k := i.k[i.i-1] - - return &String{Value: k} -} - -// Value returns the value of the current element. -func (i *ImmutableMapIterator) Value() Object { - k := i.k[i.i-1] - - return i.v[k] -} diff --git a/objects/map.go b/objects/map.go index 66dc0ad..44d6801 100644 --- a/objects/map.go +++ b/objects/map.go @@ -50,17 +50,22 @@ func (o *Map) IsFalsy() bool { // Equals returns true if the value of the type // is equal to the value of another object. func (o *Map) Equals(x Object) bool { - t, ok := x.(*Map) - if !ok { + var xVal map[string]Object + switch x := x.(type) { + case *Map: + xVal = x.Value + case *ImmutableMap: + xVal = x.Value + default: return false } - if len(o.Value) != len(t.Value) { + if len(o.Value) != len(xVal) { return false } for k, v := range o.Value { - tv := t.Value[k] + tv := xVal[k] if !v.Equals(tv) { return false } diff --git a/runtime/vm.go b/runtime/vm.go index 559d592..47164ed 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -299,7 +299,7 @@ func (v *VM) Run() error { return ErrStackOverflow } - if (*right).Equals(*left) { + if (*left).Equals(*right) { v.stack[v.sp] = truePtr } else { v.stack[v.sp] = falsePtr @@ -315,7 +315,7 @@ func (v *VM) Run() error { return ErrStackOverflow } - if (*right).Equals(*left) { + if (*left).Equals(*right) { v.stack[v.sp] = falsePtr } else { v.stack[v.sp] = truePtr @@ -549,21 +549,28 @@ func (v *VM) Run() error { case compiler.OpError: value := v.stack[v.sp-1] - v.sp-- var err objects.Object = &objects.Error{ Value: *value, } - if v.sp >= StackSize { - return ErrStackOverflow - } - - v.stack[v.sp] = &err - v.sp++ + v.stack[v.sp-1] = &err case compiler.OpImmutable: - // TODO: implement here + value := v.stack[v.sp-1] + + switch value := (*value).(type) { + case *objects.Array: + var immutableArray objects.Object = &objects.ImmutableArray{ + Value: value.Value, + } + v.stack[v.sp-1] = &immutableArray + case *objects.Map: + var immutableMap objects.Object = &objects.ImmutableMap{ + Value: value.Value, + } + v.stack[v.sp-1] = &immutableMap + } case compiler.OpIndex: index := v.stack[v.sp-1] @@ -656,6 +663,31 @@ func (v *VM) Run() error { v.stack[v.sp] = &val v.sp++ + case *objects.ImmutableArray: + numElements := int64(len(left.Value)) + + if lowIdx < 0 || lowIdx >= numElements { + return fmt.Errorf("index out of bounds: %d", lowIdx) + } + if highIdx < 0 { + highIdx = numElements + } else if highIdx < 0 || highIdx > numElements { + return fmt.Errorf("index out of bounds: %d", highIdx) + } + + if lowIdx > highIdx { + return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + } + + if v.sp >= StackSize { + return ErrStackOverflow + } + + var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} + + v.stack[v.sp] = &val + v.sp++ + case *objects.String: numElements := int64(len(left.Value)) diff --git a/runtime/vm_immutable_test.go b/runtime/vm_immutable_test.go new file mode 100644 index 0000000..248f0cf --- /dev/null +++ b/runtime/vm_immutable_test.go @@ -0,0 +1,48 @@ +package runtime_test + +import "testing" + +func TestImmutable(t *testing.T) { + // primitive types are already immutable values + // immutable expression has no effects. + expect(t, `a := immutable(1); out = a`, 1) + expect(t, `a := 5; b := immutable(a); out = b`, 5) + expect(t, `a := immutable(1); a = 5; out = a`, 5) + + // array + expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`) + expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`) + expect(t, `a := immutable(["foo", [1,2,3]]); a[1][1] = "bar"; out = a`, IARR{"foo", ARR{1, "bar", 3}}) + expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`) + expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`) + expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = b`, ARR{1, 5, 3}) + expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = a`, IARR{1, 2, 3}) + expect(t, `out = immutable([1,2,3]) == [1,2,3]`, true) + expect(t, `out = immutable([1,2,3]) == immutable([1,2,3])`, true) + expect(t, `out = [1,2,3] == immutable([1,2,3])`, true) + expect(t, `out = immutable([1,2,3]) == [1,2]`, false) + expect(t, `out = immutable([1,2,3]) == immutable([1,2])`, false) + expect(t, `out = [1,2,3] == immutable([1,2])`, false) + expect(t, `out = immutable([1, 2, 3, 4])[1]`, 2) + expect(t, `out = immutable([1, 2, 3, 4])[1:3]`, ARR{2, 3}) + expect(t, `a := immutable([1,2,3]); a = 5; out = a`, 5) + + // map + expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`) + expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`) + expect(t, `a := immutable({b: 1, c: [1,2,3]}); a.c[1] = "bar"; out = a`, IMAP{"b": 1, "c": ARR{1, "bar", 3}}) + expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`) + expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`) + expect(t, `out = immutable({a:1,b:2}) == {a:1,b:2}`, true) + expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:2})`, true) + expect(t, `out = {a:1,b:2} == immutable({a:1,b:2})`, true) + expect(t, `out = immutable({a:1,b:2}) == {a:1,b:3}`, false) + expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:3})`, false) + expect(t, `out = {a:1,b:2} == immutable({a:1,b:3})`, false) + expect(t, `out = immutable({a:1,b:2}).b`, 2) + expect(t, `out = immutable({a:1,b:2})["b"]`, 2) + expect(t, `a := immutable({a:1,b:2}); a = 5; out = 5`, 5) + + expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, 5) + expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`) +} diff --git a/runtime/vm_test.go b/runtime/vm_test.go index f4e4bc5..6a11697 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -20,6 +20,8 @@ const ( testOut = "out" ) +type IARR []interface{} +type IMAP map[string]interface{} type MAP = map[string]interface{} type ARR = []interface{} type SYM = map[string]objects.Object @@ -154,6 +156,20 @@ func toObject(v interface{}) objects.Object { } return &objects.Array{Value: objs} + case IMAP: + objs := make(map[string]objects.Object) + for k, v := range v { + objs[k] = toObject(v) + } + + return &objects.ImmutableMap{Value: objs} + case IARR: + var objs []objects.Object + for _, e := range v { + objs = append(objs, toObject(e)) + } + + return &objects.ImmutableArray{Value: objs} } panic(fmt.Errorf("unknown type: %T", v)) @@ -315,6 +331,10 @@ func objectZeroCopy(o objects.Object) objects.Object { return &objects.Error{} case *objects.Bytes: return &objects.Bytes{} + case *objects.ImmutableArray: + return &objects.ImmutableArray{} + case *objects.ImmutableMap: + return &objects.ImmutableMap{} case nil: panic("nil") default: From fa6699f7ce68bde207b0b118570e03bbebbcc33c Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 25 Jan 2019 15:03:32 -0800 Subject: [PATCH 3/4] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87d5675..cc71e9c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ s := sum("", [1, 2, 3]) // == "123" - Simple and highly readable [Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md) - Dynamic typing with type coercion - Higher-order functions and closures - - Immutable values _(v1)_ + - Immutable values - Garbage collection - [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md) and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.md) - Compiler/runtime written in native Go _(no external deps or cgo)_ From 4fac7b40134c995468e0e8abf1c908e49102d25c Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Fri, 25 Jan 2019 15:08:17 -0800 Subject: [PATCH 4/4] register immutable array to gob --- compiler/bytecode.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/bytecode.go b/compiler/bytecode.go index 9d13d0b..6e87585 100644 --- a/compiler/bytecode.go +++ b/compiler/bytecode.go @@ -42,11 +42,12 @@ func init() { gob.Register(&objects.Bool{}) gob.Register(&objects.Char{}) gob.Register(&objects.Array{}) + gob.Register(&objects.ImmutableArray{}) gob.Register(&objects.Map{}) + gob.Register(&objects.ImmutableMap{}) gob.Register(&objects.CompiledFunction{}) gob.Register(&objects.Undefined{}) gob.Register(&objects.Error{}) - gob.Register(&objects.ImmutableMap{}) gob.Register(&objects.Bytes{}) gob.Register(&objects.StringIterator{}) gob.Register(&objects.MapIterator{})