Skip to content

Commit

Permalink
classic: support 'evil' assignment i.e. "m, i, m[i] = nil, 1, 2" wher…
Browse files Browse the repository at this point in the history
…e 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
  • Loading branch information
cosmos72 committed May 12, 2017
1 parent 522d158 commit 147f299
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 57 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 7 additions & 1 deletion TrickyGo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
```
Expand Down
29 changes: 20 additions & 9 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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},

Expand All @@ -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
Expand Down Expand Up @@ -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},
Expand Down
49 changes: 22 additions & 27 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
31 changes: 24 additions & 7 deletions classic/assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,29 @@ 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 {
env.Errorf("invalid index, expecting an int: %v <%v>", index, typeOf(index))
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)
Expand All @@ -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
}
}
Expand Down
8 changes: 7 additions & 1 deletion classic/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion classic/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/cosmos72/gomacro>
// Copyright (C) 2016-2017 Massimiliano Ghilardi
// Copyright (C) 2017 Massimiliano Ghilardi
// License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
// This is free software with ABSOLUTELY NO WARRANTY.
//
Expand Down
12 changes: 6 additions & 6 deletions fast/assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
28 changes: 26 additions & 2 deletions fast/identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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++ {
Expand All @@ -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++ {
Expand All @@ -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++ {
Expand Down

0 comments on commit 147f299

Please sign in to comment.