mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-22 09:29:38 +00:00
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:
parent
83e467e527
commit
941bd7e728
19 changed files with 475 additions and 292 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.38.1
|
||||
0.39.0
|
||||
|
|
|
@ -52,27 +52,29 @@ More information about standalone installation coming soon.
|
|||
NEO-GO-VM > help
|
||||
|
||||
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
|
||||
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)
|
||||
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
|
||||
|
||||
To load a script into the VM:
|
||||
To load an avm script into the VM:
|
||||
|
||||
```
|
||||
NEO-GO-VM > load ../contract.avm
|
||||
READY
|
||||
NEO-GO-VM > loadavm ../contract.avm
|
||||
READY: loaded 36 instructions
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
at breakpoint 4 (Opush4)
|
||||
NEO-GO-VM 4 >
|
||||
```
|
||||
|
||||
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
|
||||
instruction pointer at 5 (Odup)
|
||||
NEO-GO-VM 5 >
|
||||
```
|
||||
|
||||
To place breakpoints:
|
||||
|
@ -109,14 +135,15 @@ To place breakpoints:
|
|||
```
|
||||
NEO-GO-VM > break 10
|
||||
breakpoint added at instruction 10
|
||||
NEO-GO-VM > resume
|
||||
NEO-GO-VM > cont
|
||||
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": [
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -2,40 +2,45 @@ package cli
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm/compiler"
|
||||
)
|
||||
|
||||
// command describes a VM command.
|
||||
type command struct {
|
||||
// number of minimun arguments the command needs.
|
||||
args int
|
||||
|
||||
// description of the command.
|
||||
usage string
|
||||
|
||||
// whether the VM needs to be "ready" to execute this command.
|
||||
ready bool
|
||||
}
|
||||
|
||||
var commands = map[string]command{
|
||||
"help": {0, "show available commands", false},
|
||||
"exit": {0, "exit the VM prompt", false},
|
||||
"ip": {0, "show the current instruction", true},
|
||||
"break": {1, "place a breakpoint (> break 1)", true},
|
||||
"estack": {0, "show evaluation stack details", false},
|
||||
"astack": {0, "show alt stack details", false},
|
||||
"istack": {0, "show invocation stack details", false},
|
||||
"load": {1, "load a script into the VM (> load /path/to/script.avm)", false},
|
||||
"run": {0, "execute the current loaded script", 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},
|
||||
"help": {0, "show available commands", false},
|
||||
"exit": {0, "exit the VM prompt", false},
|
||||
"ip": {0, "show the current instruction", true},
|
||||
"break": {1, "place a breakpoint (> break 1)", true},
|
||||
"estack": {0, "show evaluation stack details", false},
|
||||
"astack": {0, "show alt stack details", false},
|
||||
"istack": {0, "show invocation stack details", false},
|
||||
"loadavm": {1, "load an avm script into the VM (> load /path/to/script.avm)", false},
|
||||
"loadhex": {1, "load a hex string into the VM (> loadhex 006166 )", false},
|
||||
"loadgo": {1, "compile and load a .go file into the VM (> load /path/to/file.go)", false},
|
||||
"run": {0, "execute the current loaded script", 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.
|
||||
|
@ -67,14 +72,7 @@ func (c *VMCLI) handleCommand(cmd string, args ...string) {
|
|||
|
||||
switch cmd {
|
||||
case "help":
|
||||
fmt.Println()
|
||||
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()
|
||||
printHelp()
|
||||
|
||||
case "exit":
|
||||
fmt.Println("Bye!")
|
||||
|
@ -97,13 +95,36 @@ func (c *VMCLI) handleCommand(cmd string, args ...string) {
|
|||
case "estack", "istack", "astack":
|
||||
fmt.Println(c.vm.Stack(cmd))
|
||||
|
||||
case "load":
|
||||
case "loadavm":
|
||||
if err := c.vm.LoadFile(args[0]); err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
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":
|
||||
c.vm.Run()
|
||||
|
||||
|
@ -132,7 +153,11 @@ func (c *VMCLI) Run() error {
|
|||
printLogo()
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
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 = strings.Trim(input, "\n")
|
||||
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() {
|
||||
logo := `
|
||||
_ ____________ __________ _ ____ ___
|
||||
|
|
|
@ -15,19 +15,19 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
|
|||
- basic if statements
|
||||
- binary expressions
|
||||
- return statements
|
||||
- for loops
|
||||
- imports
|
||||
|
||||
### Go builtins
|
||||
- len
|
||||
- append
|
||||
|
||||
### VM API (interop layer)
|
||||
- storage
|
||||
- runtime
|
||||
|
||||
## Not yet implemented
|
||||
- for loops
|
||||
- range
|
||||
- builtin (append)
|
||||
- some parts of the interop layer (VM API)
|
||||
|
||||
## Not supported
|
||||
|
|
|
@ -192,3 +192,7 @@ func isNoRetSyscall(name string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isStringType(t types.Type) bool {
|
||||
return t.String() == "string"
|
||||
}
|
||||
|
|
|
@ -380,15 +380,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
|
||||
case *ast.CallExpr:
|
||||
var (
|
||||
f *funcScope
|
||||
ok bool
|
||||
numArgs = len(n.Args)
|
||||
f *funcScope
|
||||
ok bool
|
||||
numArgs = len(n.Args)
|
||||
isBuiltin = isBuiltin(n.Fun)
|
||||
)
|
||||
|
||||
switch fun := n.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
f, ok = c.funcs[fun.Name]
|
||||
if !ok && !isBuiltin(n.Fun) {
|
||||
if !ok && !isBuiltin {
|
||||
log.Fatalf("could not resolve function %s", fun.Name)
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
|
@ -410,12 +411,15 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
for _, arg := range n.Args {
|
||||
ast.Walk(c, arg)
|
||||
}
|
||||
if numArgs == 2 {
|
||||
emitOpcode(c.prog, vm.Oswap)
|
||||
}
|
||||
if numArgs == 3 {
|
||||
emitInt(c.prog, 2)
|
||||
emitOpcode(c.prog, vm.Oxswap)
|
||||
// Do not swap for builtin functions.
|
||||
if !isBuiltin {
|
||||
if numArgs == 2 {
|
||||
emitOpcode(c.prog, vm.Oswap)
|
||||
}
|
||||
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
|
||||
|
@ -424,7 +428,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
emitOpcode(c.prog, vm.Onop)
|
||||
|
||||
// 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.
|
||||
// We can be sure builtins are of type *ast.Ident.
|
||||
c.convertBuiltin(n.Fun.(*ast.Ident).Name, n)
|
||||
|
@ -456,7 +460,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
return nil
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
// TODO(@anthdm)
|
||||
ast.Walk(c, n.X)
|
||||
c.convertToken(n.Op)
|
||||
return nil
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
ast.Walk(c, n.X)
|
||||
|
@ -528,9 +534,15 @@ func (c *codegen) convertSyscall(name string) {
|
|||
func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) {
|
||||
switch name {
|
||||
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":
|
||||
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)
|
||||
case token.INC:
|
||||
emitOpcode(c.prog, vm.Oinc)
|
||||
case token.NOT:
|
||||
emitOpcode(c.prog, vm.Onot)
|
||||
default:
|
||||
log.Fatalf("compiler could not convert token: %s", tok)
|
||||
}
|
||||
|
@ -702,7 +716,7 @@ func (c *codegen) writeJumps() {
|
|||
switch vm.Opcode(op) {
|
||||
case vm.Ojmp, vm.Ojmpifnot, vm.Ojmpif, vm.Ocall:
|
||||
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
|
||||
}
|
||||
offset := uint16(c.l[index] - i)
|
||||
|
|
|
@ -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",
|
||||
// },
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package compiler
|
||||
|
||||
var stringTestCases = []testCase{
|
||||
{
|
||||
"simple string",
|
||||
`
|
||||
package testcase
|
||||
func Main() string {
|
||||
x := "NEO"
|
||||
return x
|
||||
}
|
||||
`,
|
||||
"52c56b034e454f6c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
}
|
|
@ -28,10 +28,13 @@ func NewContext(b []byte) *Context {
|
|||
// Next return the next instruction to execute.
|
||||
func (c *Context) Next() Opcode {
|
||||
c.ip++
|
||||
if c.ip >= len(c.prog) {
|
||||
return Oret
|
||||
}
|
||||
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
|
||||
// the first instruction.
|
||||
func (c *Context) IP() int {
|
||||
|
|
|
@ -72,6 +72,9 @@ func (e *Element) BigInt() *big.Int {
|
|||
// 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.
|
||||
func (e *Element) Bool() bool {
|
||||
if v, ok := e.value.Value().(*big.Int); ok {
|
||||
return v.Int64() == 1
|
||||
}
|
||||
return e.value.Value().(bool)
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
}
|
|
@ -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
306
pkg/vm/tests/for_test.go
Normal 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))
|
||||
//}
|
|
@ -33,7 +33,6 @@ func TestVMAndCompilerCases(t *testing.T) {
|
|||
testCases := []testCase{}
|
||||
testCases = append(testCases, numericTestCases...)
|
||||
testCases = append(testCases, assignTestCases...)
|
||||
testCases = append(testCases, arrayTestCases...)
|
||||
testCases = append(testCases, binaryExprTestCases...)
|
||||
testCases = append(testCases, structTestCases...)
|
||||
|
17
pkg/vm/vm.go
17
pkg/vm/vm.go
|
@ -186,10 +186,6 @@ func (v *VM) Run() {
|
|||
func (v *VM) Step() {
|
||||
ctx := v.Context()
|
||||
op := ctx.Next()
|
||||
if ctx.ip >= len(ctx.prog) {
|
||||
op = Oret
|
||||
}
|
||||
|
||||
v.execute(ctx, op)
|
||||
|
||||
// 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))
|
||||
|
||||
case Onot:
|
||||
x := v.estack.Pop().BigInt()
|
||||
v.estack.PushVal(x.Not(x))
|
||||
x := v.estack.Pop().Bool()
|
||||
v.estack.PushVal(!x)
|
||||
|
||||
case Onz:
|
||||
panic("todo NZ")
|
||||
|
@ -486,6 +482,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
|
|||
case *ArrayItem, *StructItem:
|
||||
arr := t.Value().([]StackItem)
|
||||
arr = append(arr, itemElem.value)
|
||||
v.estack.PushVal(arr)
|
||||
default:
|
||||
panic("APPEND: not of underlying type Array")
|
||||
}
|
||||
|
@ -558,6 +555,14 @@ func (v *VM) execute(ctx *Context, op Opcode) {
|
|||
}
|
||||
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:
|
||||
var (
|
||||
rOffset = int16(ctx.readUint16())
|
||||
|
|
Loading…
Reference in a new issue