VM and compiler update (#63)

* renamed test folders and fixed bug where wrong jump labels would be exectuted for rewrite.

* Added support for Osize (len(string)) and factored out the array tests

* Added current instruction number to VM prompt if program is loaded.

* added support for unary expressions.

* updated README of and sorted the help commands

* updated readme of the compiler

* bumped version -> 0.39.0
This commit is contained in:
Anthony De Meulemeester 2018-04-04 21:41:19 +02:00 committed by GitHub
parent 83e467e527
commit 941bd7e728
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 475 additions and 292 deletions

View file

@ -1 +1 @@
0.38.1 0.39.0

View file

@ -52,27 +52,29 @@ More information about standalone installation coming soon.
NEO-GO-VM > help NEO-GO-VM > help
COMMAND USAGE COMMAND USAGE
step step (n) instruction in the program (> step 10)
ops show the opcodes of the current loaded program
ip show the current instruction
estack show evaluation stack details
astack show alt stack details astack show alt stack details
istack show invocation stack details
run execute the current loaded script
cont continue execution of the current loaded script
help show available commands
exit exit the VM prompt
break place a breakpoint (> break 1) break place a breakpoint (> break 1)
load load a script into the VM (> load /path/to/script.avm) cont continue execution of the current loaded script
estack show evaluation stack details
exit exit the VM prompt
help show available commands
ip show the current instruction
istack show invocation stack details
loadavm load an avm script into the VM (> load /path/to/script.avm)
loadgo compile and load a .go file into the VM (> load /path/to/file.go)
loadhex load a hex string into the VM (> loadhex 006166 )
ops show the opcodes of the current loaded program
run execute the current loaded script
step step (n) instruction in the program (> step 10)
``` ```
### Loading in your script ### Loading in your script
To load a script into the VM: To load an avm script into the VM:
``` ```
NEO-GO-VM > load ../contract.avm NEO-GO-VM > loadavm ../contract.avm
READY READY: loaded 36 instructions
``` ```
Run the script: Run the script:
@ -87,6 +89,28 @@ NEO-GO-VM > run
] ]
``` ```
You can also directly compile and load `.go` files:
```
NEO-GO-VM > loadgo ../contract.go
READY: loaded 36 instructions
```
To make it even more complete, you can directly load hex strings into the VM:
```
NEO-GO-VM > loadhex 54c56b006c766b00527ac46c766b00c391640b006203005a616c756662030000616c7566
READY: loaded 36 instructions
NEO-GO-VM > run
[
{
"value": 10,
"type": "BigInteger"
}
]
```
### Debugging ### Debugging
The `neo-go-vm` provides a debugger to inspect your program in-depth. The `neo-go-vm` provides a debugger to inspect your program in-depth.
@ -95,6 +119,7 @@ Step 4 instructions.
``` ```
NEO-GO-VM > step 4 NEO-GO-VM > step 4
at breakpoint 4 (Opush4) at breakpoint 4 (Opush4)
NEO-GO-VM 4 >
``` ```
Using just `step` will execute 1 instruction at a time. Using just `step` will execute 1 instruction at a time.
@ -102,6 +127,7 @@ Using just `step` will execute 1 instruction at a time.
``` ```
NEO-GO-VM > step NEO-GO-VM > step
instruction pointer at 5 (Odup) instruction pointer at 5 (Odup)
NEO-GO-VM 5 >
``` ```
To place breakpoints: To place breakpoints:
@ -109,14 +135,15 @@ To place breakpoints:
``` ```
NEO-GO-VM > break 10 NEO-GO-VM > break 10
breakpoint added at instruction 10 breakpoint added at instruction 10
NEO-GO-VM > resume NEO-GO-VM > cont
at breakpoint 10 (Osetitem) at breakpoint 10 (Osetitem)
NEO-GO-VM 10 > cont
``` ```
Inspecting the stack: Inspecting the evaluation stack:
``` ```
NEO-GO-VM > stack NEO-GO-VM > estack
[ [
{ {
"value": [ "value": [
@ -137,4 +164,7 @@ NEO-GO-VM > stack
] ]
``` ```
And a lot more features coming next weeks.. There are more stacks that you can inspect.
- `astack` alt stack
- `istack` invocation stack

View file

@ -2,40 +2,45 @@ package cli
import ( import (
"bufio" "bufio"
"bytes"
"encoding/hex"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"sort"
"strconv" "strconv"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/CityOfZion/neo-go/pkg/vm" "github.com/CityOfZion/neo-go/pkg/vm"
"github.com/CityOfZion/neo-go/pkg/vm/compiler"
) )
// command describes a VM command. // command describes a VM command.
type command struct { type command struct {
// number of minimun arguments the command needs. // number of minimun arguments the command needs.
args int args int
// description of the command. // description of the command.
usage string usage string
// whether the VM needs to be "ready" to execute this command. // whether the VM needs to be "ready" to execute this command.
ready bool ready bool
} }
var commands = map[string]command{ var commands = map[string]command{
"help": {0, "show available commands", false}, "help": {0, "show available commands", false},
"exit": {0, "exit the VM prompt", false}, "exit": {0, "exit the VM prompt", false},
"ip": {0, "show the current instruction", true}, "ip": {0, "show the current instruction", true},
"break": {1, "place a breakpoint (> break 1)", true}, "break": {1, "place a breakpoint (> break 1)", true},
"estack": {0, "show evaluation stack details", false}, "estack": {0, "show evaluation stack details", false},
"astack": {0, "show alt stack details", false}, "astack": {0, "show alt stack details", false},
"istack": {0, "show invocation stack details", false}, "istack": {0, "show invocation stack details", false},
"load": {1, "load a script into the VM (> load /path/to/script.avm)", false}, "loadavm": {1, "load an avm script into the VM (> load /path/to/script.avm)", false},
"run": {0, "execute the current loaded script", true}, "loadhex": {1, "load a hex string into the VM (> loadhex 006166 )", false},
"cont": {0, "continue execution of the current loaded script", true}, "loadgo": {1, "compile and load a .go file into the VM (> load /path/to/file.go)", false},
"step": {0, "step (n) instruction in the program (> step 10)", true}, "run": {0, "execute the current loaded script", true},
"ops": {0, "show the opcodes of the current loaded program", true}, "cont": {0, "continue execution of the current loaded script", true},
"step": {0, "step (n) instruction in the program (> step 10)", true},
"ops": {0, "show the opcodes of the current loaded program", true},
} }
// VMCLI object for interacting with the VM. // VMCLI object for interacting with the VM.
@ -67,14 +72,7 @@ func (c *VMCLI) handleCommand(cmd string, args ...string) {
switch cmd { switch cmd {
case "help": case "help":
fmt.Println() printHelp()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(w, "COMMAND\tUSAGE")
for name, details := range commands {
fmt.Fprintf(w, "%s\t%s\n", name, details.usage)
}
w.Flush()
fmt.Println()
case "exit": case "exit":
fmt.Println("Bye!") fmt.Println("Bye!")
@ -97,13 +95,36 @@ func (c *VMCLI) handleCommand(cmd string, args ...string) {
case "estack", "istack", "astack": case "estack", "istack", "astack":
fmt.Println(c.vm.Stack(cmd)) fmt.Println(c.vm.Stack(cmd))
case "load": case "loadavm":
if err := c.vm.LoadFile(args[0]); err != nil { if err := c.vm.LoadFile(args[0]); err != nil {
fmt.Println(err) fmt.Println(err)
} else { } else {
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr()) fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
} }
case "loadhex":
b, err := hex.DecodeString(args[0])
if err != nil {
fmt.Println(err)
return
}
c.vm.Load(b)
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
case "loadgo":
fb, err := ioutil.ReadFile(args[0])
if err != nil {
fmt.Println(err)
return
}
b, err := compiler.Compile(bytes.NewReader(fb), &compiler.Options{})
if err != nil {
fmt.Println(err)
return
}
c.vm.Load(b)
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
case "run", "cont": case "run", "cont":
c.vm.Run() c.vm.Run()
@ -132,7 +153,11 @@ func (c *VMCLI) Run() error {
printLogo() printLogo()
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
for { for {
fmt.Print("NEO-GO-VM > ") if c.vm.Ready() && c.vm.Context().IP()-1 >= 0 {
fmt.Printf("NEO-GO-VM %d > ", c.vm.Context().IP()-1)
} else {
fmt.Print("NEO-GO-VM > ")
}
input, _ := reader.ReadString('\n') input, _ := reader.ReadString('\n')
input = strings.Trim(input, "\n") input = strings.Trim(input, "\n")
if len(input) != 0 { if len(input) != 0 {
@ -147,6 +172,25 @@ func (c *VMCLI) Run() error {
} }
} }
func printHelp() {
names := make([]string, len(commands))
i := 0
for name, _ := range commands {
names[i] = name
i++
}
sort.Strings(names)
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(w, "COMMAND\tUSAGE")
for _, name := range names {
fmt.Fprintf(w, "%s\t%s\n", name, commands[name].usage)
}
w.Flush()
fmt.Println()
}
func printLogo() { func printLogo() {
logo := ` logo := `
_ ____________ __________ _ ____ ___ _ ____________ __________ _ ____ ___

View file

@ -15,19 +15,19 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
- basic if statements - basic if statements
- binary expressions - binary expressions
- return statements - return statements
- for loops
- imports - imports
### Go builtins ### Go builtins
- len - len
- append
### VM API (interop layer) ### VM API (interop layer)
- storage - storage
- runtime - runtime
## Not yet implemented ## Not yet implemented
- for loops
- range - range
- builtin (append)
- some parts of the interop layer (VM API) - some parts of the interop layer (VM API)
## Not supported ## Not supported

View file

@ -192,3 +192,7 @@ func isNoRetSyscall(name string) bool {
} }
return false return false
} }
func isStringType(t types.Type) bool {
return t.String() == "string"
}

View file

@ -380,15 +380,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.CallExpr: case *ast.CallExpr:
var ( var (
f *funcScope f *funcScope
ok bool ok bool
numArgs = len(n.Args) numArgs = len(n.Args)
isBuiltin = isBuiltin(n.Fun)
) )
switch fun := n.Fun.(type) { switch fun := n.Fun.(type) {
case *ast.Ident: case *ast.Ident:
f, ok = c.funcs[fun.Name] f, ok = c.funcs[fun.Name]
if !ok && !isBuiltin(n.Fun) { if !ok && !isBuiltin {
log.Fatalf("could not resolve function %s", fun.Name) log.Fatalf("could not resolve function %s", fun.Name)
} }
case *ast.SelectorExpr: case *ast.SelectorExpr:
@ -410,12 +411,15 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
for _, arg := range n.Args { for _, arg := range n.Args {
ast.Walk(c, arg) ast.Walk(c, arg)
} }
if numArgs == 2 { // Do not swap for builtin functions.
emitOpcode(c.prog, vm.Oswap) if !isBuiltin {
} if numArgs == 2 {
if numArgs == 3 { emitOpcode(c.prog, vm.Oswap)
emitInt(c.prog, 2) }
emitOpcode(c.prog, vm.Oxswap) if numArgs == 3 {
emitInt(c.prog, 2)
emitOpcode(c.prog, vm.Oxswap)
}
} }
// c# compiler adds a NOP (0x61) before every function call. Dont think its relevant // c# compiler adds a NOP (0x61) before every function call. Dont think its relevant
@ -424,7 +428,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
emitOpcode(c.prog, vm.Onop) emitOpcode(c.prog, vm.Onop)
// Check builtin first to avoid nil pointer on funcScope! // Check builtin first to avoid nil pointer on funcScope!
if isBuiltin(n.Fun) { if isBuiltin {
// Use the ident to check, builtins are not in func scopes. // Use the ident to check, builtins are not in func scopes.
// We can be sure builtins are of type *ast.Ident. // We can be sure builtins are of type *ast.Ident.
c.convertBuiltin(n.Fun.(*ast.Ident).Name, n) c.convertBuiltin(n.Fun.(*ast.Ident).Name, n)
@ -456,7 +460,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
return nil return nil
case *ast.UnaryExpr: case *ast.UnaryExpr:
// TODO(@anthdm) ast.Walk(c, n.X)
c.convertToken(n.Op)
return nil
case *ast.IncDecStmt: case *ast.IncDecStmt:
ast.Walk(c, n.X) ast.Walk(c, n.X)
@ -528,9 +534,15 @@ func (c *codegen) convertSyscall(name string) {
func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) { func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) {
switch name { switch name {
case "len": case "len":
emitOpcode(c.prog, vm.Oarraysize) arg := expr.Args[0]
typ := c.typeInfo.Types[arg].Type
if isStringType(typ) {
emitOpcode(c.prog, vm.Osize)
} else {
emitOpcode(c.prog, vm.Oarraysize)
}
case "append": case "append":
log.Fatal("builtin (append) not yet implemented.") emitOpcode(c.prog, vm.Oappend)
} }
} }
@ -621,6 +633,8 @@ func (c *codegen) convertToken(tok token.Token) {
emitOpcode(c.prog, vm.Odec) emitOpcode(c.prog, vm.Odec)
case token.INC: case token.INC:
emitOpcode(c.prog, vm.Oinc) emitOpcode(c.prog, vm.Oinc)
case token.NOT:
emitOpcode(c.prog, vm.Onot)
default: default:
log.Fatalf("compiler could not convert token: %s", tok) log.Fatalf("compiler could not convert token: %s", tok)
} }
@ -702,7 +716,7 @@ func (c *codegen) writeJumps() {
switch vm.Opcode(op) { switch vm.Opcode(op) {
case vm.Ojmp, vm.Ojmpifnot, vm.Ojmpif, vm.Ocall: case vm.Ojmp, vm.Ojmpifnot, vm.Ojmpif, vm.Ocall:
index := int16(binary.LittleEndian.Uint16(b[j : j+2])) index := int16(binary.LittleEndian.Uint16(b[j : j+2]))
if int(index) > len(c.l) { if int(index) > len(c.l) || int(index) < 0 {
continue continue
} }
offset := uint16(c.l[index] - i) offset := uint16(c.l[index] - i)

View file

@ -1,57 +0,0 @@
package compiler
var boolTestCases = []testCase{
{
"bool assign",
`
package foo
func Main() bool {
x := true
return x
}
`,
"52c56b516c766b00527ac46203006c766b00c3616c7566",
},
{
"bool compare",
`
package foo
func Main() int {
x := true
if x {
return 10
}
return 0
}
`,
"54c56b516c766b00527ac46c766b00c3640b006203005a616c756662030000616c7566",
},
{
"bool compare verbose",
`
package foo
func Main() int {
x := true
if x == true {
return 10
}
return 0
}
`,
"54c56b516c766b00527ac46c766b00c3519c640b006203005a616c756662030000616c7566",
},
// {
// "bool invert (unary expr)",
// `
// package foo
// func Main() int {
// x := true
// if !x {
// return 10
// }
// return 0
// }
// `,
// "54c56b516c766b00527ac46c766b00c3630b006203005a616c756662030000616c7566",
// },
}

View file

@ -1,15 +0,0 @@
package compiler
var stringTestCases = []testCase{
{
"simple string",
`
package testcase
func Main() string {
x := "NEO"
return x
}
`,
"52c56b034e454f6c766b00527ac46203006c766b00c3616c7566",
},
}

View file

@ -28,10 +28,13 @@ func NewContext(b []byte) *Context {
// Next return the next instruction to execute. // Next return the next instruction to execute.
func (c *Context) Next() Opcode { func (c *Context) Next() Opcode {
c.ip++ c.ip++
if c.ip >= len(c.prog) {
return Oret
}
return Opcode(c.prog[c.ip]) return Opcode(c.prog[c.ip])
} }
// IP returns the absosulute instruction without taking 0 into account. // IP returns the absolute instruction without taking 0 into account.
// If that program starts the ip = 0 but IP() will return 1, cause its // If that program starts the ip = 0 but IP() will return 1, cause its
// the first instruction. // the first instruction.
func (c *Context) IP() int { func (c *Context) IP() int {

View file

@ -72,6 +72,9 @@ func (e *Element) BigInt() *big.Int {
// Bool attempts to get the underlying value of the element as a boolean. // Bool attempts to get the underlying value of the element as a boolean.
// Will panic if the assertion failed which will be catched by the VM. // Will panic if the assertion failed which will be catched by the VM.
func (e *Element) Bool() bool { func (e *Element) Bool() bool {
if v, ok := e.value.Value().(*big.Int); ok {
return v.Int64() == 1
}
return e.value.Value().(bool) return e.value.Value().(bool)
} }

View file

@ -1,117 +0,0 @@
package vm_test
import (
"math/big"
"github.com/CityOfZion/neo-go/pkg/vm"
)
var arrayTestCases = []testCase{
{
"assign int array",
`
package foo
func Main() []int {
x := []int{1, 2, 3}
return x
}
`,
[]vm.StackItem{
vm.NewBigIntegerItem(1),
vm.NewBigIntegerItem(2),
vm.NewBigIntegerItem(3),
},
},
{
"assign string array",
`
package foo
func Main() []string {
x := []string{"foo", "bar", "foobar"}
return x
}
`,
[]vm.StackItem{
vm.NewByteArrayItem([]byte("foo")),
vm.NewByteArrayItem([]byte("bar")),
vm.NewByteArrayItem([]byte("foobar")),
},
},
{
"array item assign",
`
package foo
func Main() int {
x := []int{0, 1, 2}
y := x[0]
return y
}
`,
big.NewInt(0),
},
{
"array item return",
`
package foo
func Main() int {
x := []int{0, 1, 2}
return x[1]
}
`,
big.NewInt(1),
},
{
"array item in bin expr",
`
package foo
func Main() int {
x := []int{0, 1, 2}
return x[1] + 10
}
`,
big.NewInt(11),
},
{
"array item ident",
`
package foo
func Main() int {
x := 1
y := []int{0, 1, 2}
return y[x]
}
`,
big.NewInt(1),
},
{
"array item index with binExpr",
`
package foo
func Main() int {
x := 1
y := []int{0, 1, 2}
return y[x + 1]
}
`,
big.NewInt(2),
},
{
"array item struct",
`
package foo
type Bar struct {
arr []int
}
func Main() int {
b := Bar{
arr: []int{0, 1, 2},
}
x := b.arr[2]
return x + 2
}
`,
big.NewInt(4),
},
}

View file

@ -1,36 +0,0 @@
package vm_test
import (
"math/big"
"testing"
)
func TestClassicForLoop(t *testing.T) {
src := `
package foo
func Main() int {
x := 0
for i := 0; i < 10; i++ {
x = i
}
return x
}
`
eval(t, src, big.NewInt(9))
}
// TODO: This could be a nasty bug. Output of the VM is 65695.
// Only happens above 100000, could be binary read issue.
//func TestForLoopBigIter(t *testing.T) {
// src := `
// package foo
// func Main() int {
// x := 0
// for i := 0; i < 100000; i++ {
// x = i
// }
// return x
// }
// `
// eval(t, src, big.NewInt(99999))
//}

306
pkg/vm/tests/for_test.go Normal file
View file

@ -0,0 +1,306 @@
package vm_test
import (
"math/big"
"testing"
"github.com/CityOfZion/neo-go/pkg/vm"
)
func TestArrayFieldInStruct(t *testing.T) {
src := `
package foo
type Bar struct {
arr []int
}
func Main() int {
b := Bar{
arr: []int{0, 1, 2},
}
x := b.arr[2]
return x + 2
}
`
eval(t, src, big.NewInt(4))
}
func TestArrayItemGetIndexBinaryExpr(t *testing.T) {
src := `
package foo
func Main() int {
x := 1
y := []int{0, 1, 2}
return y[x + 1]
}
`
eval(t, src, big.NewInt(2))
}
func TestArrayItemGetIndexIdent(t *testing.T) {
src := `
package foo
func Main() int {
x := 1
y := []int{0, 1, 2}
return y[x]
}
`
eval(t, src, big.NewInt(1))
}
func TestArrayItemBinExpr(t *testing.T) {
src := `
package foo
func Main() int {
x := []int{0, 1, 2}
return x[1] + 10
}
`
eval(t, src, big.NewInt(11))
}
func TestArrayItemReturn(t *testing.T) {
src := `
package foo
func Main() int {
arr := []int{0, 1, 2}
return arr[1]
}
`
eval(t, src, big.NewInt(1))
}
func TestArrayItemAssign(t *testing.T) {
src := `
package foo
func Main() int {
arr := []int{1, 2, 3}
y := arr[0]
return y
}
`
eval(t, src, big.NewInt(1))
}
func TestStringArray(t *testing.T) {
src := `
package foo
func Main() []string {
x := []string{"foo", "bar", "foobar"}
return x
}
`
eval(t, src, []vm.StackItem{
vm.NewByteArrayItem([]byte("foo")),
vm.NewByteArrayItem([]byte("bar")),
vm.NewByteArrayItem([]byte("foobar")),
})
}
func TestIntArray(t *testing.T) {
src := `
package foo
func Main() []int {
arr := []int{1, 2, 3}
return arr
}
`
eval(t, src, []vm.StackItem{
vm.NewBigIntegerItem(1),
vm.NewBigIntegerItem(2),
vm.NewBigIntegerItem(3),
})
}
func TestArrayLen(t *testing.T) {
src := `
package foo
func Main() int {
arr := []int{0, 1, 2}
return len(arr)
}
`
eval(t, src, big.NewInt(3))
}
func TestStringLen(t *testing.T) {
src := `
package foo
func Main() int {
str := "this is medium sized string"
return len(str)
}
`
eval(t, src, big.NewInt(27))
}
func TestSimpleString(t *testing.T) {
src := `
package foo
func Main() string {
x := "NEO"
return x
}
`
eval(t, src, vm.NewByteArrayItem([]byte("NEO")).Value())
}
func TestBoolAssign(t *testing.T) {
src := `
package foo
func Main() bool {
x := true
return x
}
`
eval(t, src, big.NewInt(1))
}
func TestBoolCompare(t *testing.T) {
src := `
package foo
func Main() int {
x := true
if x {
return 10
}
return 0
}
`
eval(t, src, big.NewInt(10))
}
func TestBoolCompareVerbose(t *testing.T) {
src := `
package foo
func Main() int {
x := true
if x == true {
return 10
}
return 0
}
`
eval(t, src, big.NewInt(10))
}
func TestUnaryExpr(t *testing.T) {
src := `
package foo
func Main() bool {
x := false
return !x
}
`
eval(t, src, true)
}
func TestIfUnaryInvertPass(t *testing.T) {
src := `
package foo
func Main() int {
x := false
if !x {
return 10
}
return 0
}
`
eval(t, src, big.NewInt(10))
}
func TestIfUnaryInvert(t *testing.T) {
src := `
package foo
func Main() int {
x := true
if !x {
return 10
}
return 0
}
`
eval(t, src, big.NewInt(0))
}
func TestAppendString(t *testing.T) {
src := `
package foo
func Main() string {
arr := []string{"a", "b", "c"}
arr = append(arr, "d")
return arr[3]
}
`
eval(t, src, vm.NewByteArrayItem([]byte("d")).Value())
}
func TestAppendInt(t *testing.T) {
src := `
package foo
func Main() int {
arr := []int{0, 1, 2}
arr = append(arr, 3)
return arr[3]
}
`
eval(t, src, big.NewInt(3))
}
func TestClassicForLoop(t *testing.T) {
src := `
package foo
func Main() int {
x := 0
for i := 0; i < 10; i++ {
x = i
}
return x
}
`
eval(t, src, big.NewInt(9))
}
func TestInc(t *testing.T) {
src := `
package foo
func Main() int {
x := 0
x++
return x
}
`
eval(t, src, big.NewInt(1))
}
func TestDec(t *testing.T) {
src := `
package foo
func Main() int {
x := 2
x--
return x
}
`
eval(t, src, big.NewInt(1))
}
// TODO: This could be a nasty bug. Output of the VM is 65695.
// Only happens above 100000, could be binary read issue.
//func TestForLoopBigIter(t *testing.T) {
// src := `
// package foo
// func Main() int {
// x := 0
// for i := 0; i < 100000; i++ {
// x = i
// }
// return x
// }
// `
// eval(t, src, big.NewInt(99999))
//}

View file

@ -33,7 +33,6 @@ func TestVMAndCompilerCases(t *testing.T) {
testCases := []testCase{} testCases := []testCase{}
testCases = append(testCases, numericTestCases...) testCases = append(testCases, numericTestCases...)
testCases = append(testCases, assignTestCases...) testCases = append(testCases, assignTestCases...)
testCases = append(testCases, arrayTestCases...)
testCases = append(testCases, binaryExprTestCases...) testCases = append(testCases, binaryExprTestCases...)
testCases = append(testCases, structTestCases...) testCases = append(testCases, structTestCases...)

View file

@ -186,10 +186,6 @@ func (v *VM) Run() {
func (v *VM) Step() { func (v *VM) Step() {
ctx := v.Context() ctx := v.Context()
op := ctx.Next() op := ctx.Next()
if ctx.ip >= len(ctx.prog) {
op = Oret
}
v.execute(ctx, op) v.execute(ctx, op)
// re-peek the context as it could been changed during execution. // re-peek the context as it could been changed during execution.
@ -460,8 +456,8 @@ func (v *VM) execute(ctx *Context, op Opcode) {
v.estack.PushVal(x.Abs(x)) v.estack.PushVal(x.Abs(x))
case Onot: case Onot:
x := v.estack.Pop().BigInt() x := v.estack.Pop().Bool()
v.estack.PushVal(x.Not(x)) v.estack.PushVal(!x)
case Onz: case Onz:
panic("todo NZ") panic("todo NZ")
@ -486,6 +482,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
case *ArrayItem, *StructItem: case *ArrayItem, *StructItem:
arr := t.Value().([]StackItem) arr := t.Value().([]StackItem)
arr = append(arr, itemElem.value) arr = append(arr, itemElem.value)
v.estack.PushVal(arr)
default: default:
panic("APPEND: not of underlying type Array") panic("APPEND: not of underlying type Array")
} }
@ -558,6 +555,14 @@ func (v *VM) execute(ctx *Context, op Opcode) {
} }
v.estack.PushVal(len(arr)) v.estack.PushVal(len(arr))
case Osize:
elem := v.estack.Pop()
arr, ok := elem.value.Value().([]uint8)
if !ok {
panic("SIZE: item not of type []uint8")
}
v.estack.PushVal(len(arr))
case Ojmp, Ojmpif, Ojmpifnot: case Ojmp, Ojmpif, Ojmpifnot:
var ( var (
rOffset = int16(ctx.readUint16()) rOffset = int16(ctx.readUint16())