From 147f299de62a5a07c937aae02607362faae20dc1 Mon Sep 17 00:00:00 2001 From: Massimiliano Ghilardi Date: Fri, 12 May 2017 19:27:30 +0200 Subject: [PATCH] classic: support 'evil' assignment i.e. "m, i, m[i] = nil, 1, 2" where m is a map classic: delete(map,key) bugfix: must convert key to appropriate type classic: support assignment "a[i] = j" where a is a pointer to array --- README.md | 11 +++++++--- TrickyGo.md | 8 ++++++- all_test.go | 29 +++++++++++++++++-------- benchmark_test.go | 49 +++++++++++++++++++------------------------ classic/assignment.go | 31 ++++++++++++++++++++------- classic/builtin.go | 8 ++++++- classic/env.go | 2 +- fast/assignment.go | 12 +++++------ fast/identifier.go | 28 +++++++++++++++++++++++-- 9 files changed, 121 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 0de312f4..867d2b5d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ ## gomacro - interactive Go interpreter with macros -gomacro is a fairly complete Go interpreter, implemented in pure Go, -built on top of the go/ast and reflect packages. +gomacro is a fairly complete Go interpreter, implemented in pure Go. It offers both +an interactive REPL and a scripting mode, and does not require a Go toolchain at runtime +(except in one very specific case: import of a non-standard package). + +It has very few dependencies: go/ast, go/types, reflect and, +for goroutines support, golang.org/x/sync/syncmap. Gomacro can be used as: * a standalone executable with interactive Go REPL: just run `gomacro` from your command line or, better, `rlwrap gomacro` - (rlwrap is a wrapper that adds history and line editing to terminal-based programs - available on many platforms) + (rlwrap is a wrapper that adds history and line editing to terminal-based + programs - available on many platforms) Available options: ``` -c, --collect collect declarations and statements, to print them later diff --git a/TrickyGo.md b/TrickyGo.md index 1cdbf008..3a01cfa6 100644 --- a/TrickyGo.md +++ b/TrickyGo.md @@ -2,12 +2,18 @@ A collection of tricky go code ``` // change the meaning of true -true := false +const true = false println(true) ``` ``` // change the meaning of uint +type uint int +println(uint(1)) +``` + +``` +// change the meaning of uint (again) func uint(x int) int { return x + 7 } println(uint(1)) ``` diff --git a/all_test.go b/all_test.go index 2601da4d..664202de 100644 --- a/all_test.go +++ b/all_test.go @@ -93,8 +93,9 @@ func (tc *TestCase) classic(t *testing.T, env *classic.Env) { const sum_source_string = "func sum(n int) int { total := 0; for i := 1; i <= n; i++ { total += i }; return total }" const fibonacci_source_string = "func fibonacci(n int) int { if n <= 2 { return 1 }; return fibonacci(n-1) + fibonacci(n-2) }" const insertion_sort_source_string = `func insertion_sort(v []int) { - for i, n := 1, len(v); i < n; i++ { - for j := i; j > 0 && v[j-1] > v[j]; j-- { + var i, j, n int // optimization: group var declarations + for i, n = 1, len(v); i < n; i++ { + for j = i; j > 0 && v[j-1] > v[j]; j-- { v[j-1], v[j] = v[j], v[j-1] } } @@ -133,6 +134,8 @@ var si = r.Zero(ti).Interface() var zeroValues = []r.Value{} +var nil_map_int_string map[int]string + var testcases = []TestCase{ TestCase{A, "1+1", "1+1", 1 + 1, nil}, TestCase{A, "1+'A'", "1+'A'", 'B', nil}, // rune i.e. int32 should win over untyped constant (or int) @@ -323,8 +326,8 @@ var testcases = []TestCase{ TestCase{A, "function_5", "func swap(x, y int) (int,int) { return y, x }; swap(88,99)", nil, []interface{}{99, 88}}, TestCase{A, "function_6", "i=0; func seti2() { i=2 }; seti2(); i", 2, nil}, TestCase{A, "function_7", "i=0; func setiadd(x, y int) { i=x+y }; setiadd(7,8); i", 15, nil}, - TestCase{A, "function_variadic_1", "func list_args(args ...int) []int { return args }; list_args(1,2,3)", []int{1, 2, 3}, nil}, - TestCase{A, "function_variadic_2", "si := make([]int, 4); si[1]=1; si[2]=2; si[3]=3; list_args(si...)", []int{0, 1, 2, 3}, nil}, + TestCase{A, "function_variadic_1", "func list_args(args ...interface{}) []interface{} { return args }; list_args(1,2,3)", []interface{}{1, 2, 3}, nil}, + TestCase{A, "function_variadic_2", "si := make([]interface{}, 4); si[1]=1; si[2]=2; si[3]=3; list_args(si...)", []interface{}{nil, 1, 2, 3}, nil}, TestCase{A, "fibonacci", fibonacci_source_string + "; fibonacci(13)", 233, nil}, TestCase{A, "function_literal", "adder := func(a,b int) int { return a+b }; adder(-7,-9)", -16, nil}, @@ -335,15 +338,23 @@ var testcases = []TestCase{ TestCase{A, "setplace_deref_3", `func vint_addr() *int { return &vint }; *vint_addr() = 7; vint`, 7, nil}, TestCase{A, "setplace_deref_4", `*vint_addr() %= 4; vint`, 3, nil}, - TestCase{A, "swap", `i=1;j=2; i,j=j,i; list_args(i, j)`, []int{2, 1}, nil}, - TestCase{A, "evil_assignment", `i=0; si[0]=7; si[1]=8; i,si[i]=1,2; list_args(i,si[0],si[1])`, []int{1, 2, 8}, nil}, - TestCase{A, "setmap_1", `m[1]="x"; m[2]="y"; m`, map[int]string{1: "x", 2: "y"}, nil}, TestCase{A, "setmap_2", `m[2]+="z"; m`, map[int]string{1: "x", 2: "yz"}, nil}, TestCase{A, "setmap_3", `mi := make(map[rune]byte); mi['@']+=2; mi`, map[rune]byte{'@': 2}, nil}, - TestCase{A, "setmap_4", `mi['a'] |= 7; mi['a']`, nil, []interface{}{byte(7), true}}, + TestCase{A, "setmap_4", `mi['a'] |= 7; mi`, map[rune]byte{'@': 2, 'a': 7}, nil}, TestCase{A, "getmap_1", `m[1]`, nil, []interface{}{"x", true}}, TestCase{A, "getmap_2", `m1 := m[1]; m1`, "x", nil}, + TestCase{A, "getmap_3", `mi['b']`, nil, []interface{}{byte(0), false}}, + TestCase{A, "getmap_4", `v2 = mi['@']; v2`, byte(2), nil}, + + TestCase{A, "swap", `i=1;j=2; i,j=j,i; list_args(i, j)`, []interface{}{2, 1}, nil}, + TestCase{A, "evil_assignment_1", `i=0; si[0]=7; si[1]=8 + i, si[i] = 1, 2 + list_args(i,si[0],si[1])`, []interface{}{1, 2, 8}, nil}, + TestCase{A, "evil_assignment_2", `i=0; m=make(map[int]string); mcopy:=m; + i, m, m[i] = 1, nil, "foo" + list_args(i,m,mcopy)`, + []interface{}{1, nil_map_int_string, map[int]string{0: "foo"}}, nil}, TestCase{A, "setstruct_1", `pair.A = 'k'; pair.B = "m"; pair`, struct { A rune @@ -385,7 +396,7 @@ var testcases = []TestCase{ TestCase{A, "builtin_make_8", "vs = make([]byte, 5); vs", make([]byte, 5), nil}, TestCase{A, "builtin_copy_1", "copy(vs, v5)", 5, nil}, TestCase{A, "builtin_copy_2", "vs", []byte("8y57r"), nil}, - TestCase{A, "builtin_delete_1", "delete(m,1); m", map[int]string{2: "yz"}, nil}, + TestCase{A, "builtin_delete_1", "delete(mi,64); mi", map[rune]byte{'a': 7}, nil}, TestCase{A, "builtin_real_1", "real(0.5+1.75i)", real(0.5 + 1.75i), nil}, TestCase{A, "builtin_real_2", "var cplx complex64 = 1.5+0.25i; real(cplx)", real(complex64(1.5 + 0.25i)), nil}, TestCase{A, "builtin_imag_1", "imag(0.5+1.75i)", imag(0.5 + 1.75i), nil}, diff --git a/benchmark_test.go b/benchmark_test.go index 625f55ff..e79cfd7d 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -48,7 +48,7 @@ const ( var verbose = false /* - --------- 2016-05-06: results on Intel Core i7 4770 --------------- + --------- 2017-05-06: results on Intel Core i7 4770 --------------- BenchmarkFibonacciCompiler-8 3000000 498 ns/op BenchmarkFibonacciFast-8 100000 14812 ns/op @@ -205,57 +205,52 @@ func BenchmarkFibonacciClosureMaps(b *testing.B) { } } -// ---------------------- iteration: insertion_sort ------------------------ +// ---------------------- arrays: insertion_sort ------------------------ func insertion_sort(v []int) { - for i, n := 1, len(v); i < n; i++ { - for j := i; j > 0 && v[j-1] > v[j]; j-- { + var i, j, n int + for i, n = 1, len(v); i < n; i++ { + for j = i; j > 0 && v[j-1] > v[j]; j-- { v[j-1], v[j] = v[j], v[j-1] } } } -func BenchmarkInsertionSortCompiler(b *testing.B) { - var v []int +var insertion_sort_data = []int{97, 89, 3, 4, 7, 0, 36, 79, 1, 12, 2, 15, 70, 18, 35, 70, 15, 73} - for i := 0; i < b.N; i++ { - v = []int{97, 89, 3, 4, 7, 0, 36, 79, 1, 12, 2, 15, 70, 18, 35, 70, 15, 73} - insertion_sort(v) - } - if verbose { - fmt.Println(v) - } +func BenchmarkInsertionSortCompiler(b *testing.B) { + benchmark_insertion_sort(b, insertion_sort) } func BenchmarkInsertionSortFast(b *testing.B) { ce := fast.New() ce.Eval(insertion_sort_source_string) - // compile the call to fibonacci(fib_n) + // extract the function insertion_sort() insertion_sort := ce.ValueOf("insertion_sort").Interface().(func([]int)) - insertion_sort([]int{3, 2, 1}) - var v []int - for i := 0; i < b.N; i++ { - v = []int{97, 89, 3, 4, 7, 0, 36, 79, 1, 12, 2, 15, 70, 18, 35, 70, 15, 73} - insertion_sort(v) - } - if verbose { - fmt.Println(v) - } + benchmark_insertion_sort(b, insertion_sort) } func BenchmarkInsertionSortClassic(b *testing.B) { env := classic.New() env.Eval(insertion_sort_source_string) - // compile the call to fibonacci(fib_n) + // extract the function insertion_sort() insertion_sort := env.ValueOf("insertion_sort").Interface().(func([]int)) - insertion_sort([]int{3, 2, 1}) - var v []int + benchmark_insertion_sort(b, insertion_sort) +} + +func benchmark_insertion_sort(b *testing.B, insertion_sort func([]int)) { + // call insertion_sort once for warm-up + v := make([]int, len(insertion_sort_data)) + copy(v, insertion_sort_data) + insertion_sort(v) + + b.ResetTimer() for i := 0; i < b.N; i++ { - v = []int{97, 89, 3, 4, 7, 0, 36, 79, 1, 12, 2, 15, 70, 18, 35, 70, 15, 73} + copy(v, insertion_sort_data) insertion_sort(v) } if verbose { diff --git a/classic/assignment.go b/classic/assignment.go index 411fac8a..352d5c80 100644 --- a/classic/assignment.go +++ b/classic/assignment.go @@ -97,7 +97,22 @@ func (env *Env) evalPlace(node ast.Expr) placeType { switch obj.Kind() { case r.Map: + // make a copy of obj and index, to protect against "evil assignment" m, i, m[i] = nil, 1, 2 where m is a map + if obj != Nil && obj.CanSet() { + obj = obj.Convert(obj.Type()) + } + if index != Nil && index.CanSet() { + index = index.Convert(index.Type()) + } return placeType{obj, index} + default: + if obj.Kind() != r.Ptr || obj.Elem().Kind() != r.Array { + env.Errorf("unsupported index operation: %v [ %v ]. not an array, map, slice or string: %v <%v>", + node.X, index, obj, typeOf(obj)) + return placeType{} + } + obj = obj.Elem() + fallthrough case r.Array, r.Slice, r.String: i, ok := env.toInt(index) if !ok { @@ -105,10 +120,6 @@ func (env *Env) evalPlace(node ast.Expr) placeType { return placeType{} } obj = obj.Index(int(i)) - default: - env.Errorf("unsupported index operation: %v [ %v ]. not an array, map, slice or string: %v <%v>", - node.X, index, obj, typeOf(obj)) - return placeType{} } default: obj = env.evalExpr1(node) @@ -130,11 +141,17 @@ func (env *Env) assignPlaces(places []placeType, op token.Token, values []r.Valu // is bugged. It breaks, among others, the common Go idiom to swap two values: a,b = b,a // // More in general, Go guarantees that all assignments happen *as if* - // the rhs values were copied to temporary locations before the assignments. + // the rhs values, and all lhs operands of indexing, dereferencing and struct field access, + // were copied to temporary locations before the assignments. // That's exactly what we must do. for i := 0; i < n; i++ { - v := values[i] - if v != None && v != Nil { + p := &places[i] + v := p.mapkey + if v != Nil && v.CanSet() { + p.mapkey = v.Convert(v.Type()) // r.Value.Convert() makes a copy + } + v = values[i] + if v != Nil && v.CanSet() { values[i] = v.Convert(v.Type()) // r.Value.Convert() makes a copy } } diff --git a/classic/builtin.go b/classic/builtin.go index b25f2cb5..fdda5669 100644 --- a/classic/builtin.go +++ b/classic/builtin.go @@ -85,7 +85,13 @@ func callCopy(dst, src interface{}) int { } func callDelete(m interface{}, key interface{}) { - r.ValueOf(m).SetMapIndex(r.ValueOf(key), Nil) + vmap := r.ValueOf(m) + tkey := vmap.Type().Key() + vkey := r.ValueOf(key) + if key != nil && vkey.Type() != tkey { + vkey = vkey.Convert(tkey) + } + vmap.SetMapIndex(vkey, Nil) } func funcEnv(env *Env, args []r.Value) (r.Value, []r.Value) { diff --git a/classic/env.go b/classic/env.go index 40eac701..47389bcd 100644 --- a/classic/env.go +++ b/classic/env.go @@ -160,7 +160,7 @@ func (env *Env) ValueOf(name string) (value r.Value) { func (env *Env) ReplStdin() { if env.Options&OptShowPrompt != 0 { fmt.Fprint(env.Stdout, `// GOMACRO, an interactive Go interpreter with macros -// Copyright (C) 2016-2017 Massimiliano Ghilardi +// Copyright (C) 2017 Massimiliano Ghilardi // License GPLv3+: GNU GPL version 3 or later // This is free software with ABSOLUTELY NO WARRANTY. // diff --git a/fast/assignment.go b/fast/assignment.go index 606fb34e..57f93b19 100644 --- a/fast/assignment.go +++ b/fast/assignment.go @@ -442,10 +442,10 @@ func cacheFunXV(fun func(env *Env) (r.Value, []r.Value), t r.Type, option CacheO var cache r.Value if option == CacheCopy { setfun = func(env *Env) { - v, _ := fun(env) - if v != base.Nil && v != base.None && v.CanSet() { + cache, _ = fun(env) + if cache != base.Nil && cache.CanSet() { // make a copy. how? Convert() the r.Value to its expected type - cache = v.Convert(t) + cache = cache.Convert(t) } } } else { @@ -464,10 +464,10 @@ func cacheFunX1(fun func(env *Env) r.Value, t r.Type, option CacheOption) (setfu var cache r.Value if option == CacheCopy { setfun = func(env *Env) { - v := fun(env) - if v != base.Nil && v != base.None && v.CanSet() { + cache = fun(env) + if cache != base.Nil && cache.CanSet() { // make a copy. how? Convert() the r.Value to its expected type - cache = v.Convert(t) + cache = cache.Convert(t) } } } else { diff --git a/fast/identifier.go b/fast/identifier.go index 5fb7a63d..6eda83f4 100644 --- a/fast/identifier.go +++ b/fast/identifier.go @@ -72,7 +72,7 @@ func (c *Comp) Symbol(sym *Symbol) *Expr { case ConstBind: return exprLit(sym.Lit, sym) case VarBind, FuncBind: - return c.varOrFuncSymbol(sym) + return c.symbol(sym) case IntBind: return c.intSymbol(sym) default: @@ -81,7 +81,7 @@ func (c *Comp) Symbol(sym *Symbol) *Expr { return nil } -func (c *Comp) varOrFuncSymbol(sym *Symbol) *Expr { +func (c *Comp) symbol(sym *Symbol) *Expr { idx := sym.Desc.Index() upn := sym.Upn var fun I @@ -100,6 +100,14 @@ func (c *Comp) varOrFuncSymbol(sym *Symbol) *Expr { fun = func(env *Env) complex128 { return env.Outer.Outer.Binds[idx].Complex() } + case c.Depth - 1: + fun = func(env *Env) complex128 { + return env.ThreadGlobals.FileEnv.Binds[idx].Complex() + } + case c.Depth: // TopEnv should not contain variables or functions... but no harm + fun = func(env *Env) complex128 { + return env.ThreadGlobals.TopEnv.Binds[idx].Complex() + } default: fun = func(env *Env) complex128 { for i := 3; i < upn; i++ { @@ -122,6 +130,14 @@ func (c *Comp) varOrFuncSymbol(sym *Symbol) *Expr { fun = func(env *Env) string { return env.Outer.Outer.Binds[idx].String() } + case c.Depth - 1: + fun = func(env *Env) string { + return env.ThreadGlobals.FileEnv.Binds[idx].String() + } + case c.Depth: // TopEnv should not contain variables or functions... but no harm + fun = func(env *Env) string { + return env.ThreadGlobals.TopEnv.Binds[idx].String() + } default: fun = func(env *Env) string { for i := 3; i < upn; i++ { @@ -144,6 +160,14 @@ func (c *Comp) varOrFuncSymbol(sym *Symbol) *Expr { fun = func(env *Env) r.Value { return env.Outer.Outer.Binds[idx] } + case c.Depth - 1: + fun = func(env *Env) r.Value { + return env.ThreadGlobals.FileEnv.Binds[idx] + } + case c.Depth: // TopEnv should not contain variables or functions... but no harm + fun = func(env *Env) r.Value { + return env.ThreadGlobals.TopEnv.Binds[idx] + } default: fun = func(env *Env) r.Value { for i := 3; i < upn; i++ {