neo-go/pkg/vm/cli/cli.go

169 lines
3.8 KiB
Go
Raw Normal View History

package cli
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"
"github.com/CityOfZion/neo-go/pkg/vm"
)
// 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, "shows evaluation stack details", false},
"astack": {0, "shows 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},
"resume": {0, "resume the current loaded script", true},
"step": {0, "step (n) instruction in the program (> step 10)", true},
"opcode": {0, "print the opcodes of the current loaded program", true},
}
// VMCLI object for interacting with the VM.
type VMCLI struct {
vm *vm.VM
}
// New returns a new VMCLI object.
func New() *VMCLI {
return &VMCLI{
vm: vm.New(nil),
}
}
func (c *VMCLI) handleCommand(cmd string, args ...string) {
com, ok := commands[cmd]
if !ok {
fmt.Printf("unknown command (%s)\n", cmd)
return
}
if len(args) < com.args {
fmt.Printf("command (%s) takes at least %d arguments\n", cmd, com.args)
return
}
if com.ready && !c.vm.Ready() {
fmt.Println("VM is not ready: no program loaded")
return
}
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()
case "exit":
fmt.Println("Bye!")
os.Exit(0)
case "ip":
ip, opcode := c.vm.Context().CurrInstr()
fmt.Printf("instruction pointer at %d (%s)\n", ip, opcode)
case "break":
n, err := strconv.Atoi(args[0])
if err != nil {
fmt.Printf("argument conversion error: %s\n", err)
return
}
c.vm.AddBreakPoint(n)
fmt.Printf("breakpoint added at instruction %d\n", n)
case "estack", "istack", "astack":
fmt.Println(c.vm.Stack(cmd))
case "load":
if err := c.vm.Load(args[0]); err != nil {
fmt.Println(err)
} else {
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
}
case "run", "resume":
c.vm.Run()
case "step":
var (
n = 1
err error
)
if len(args) > 0 {
n, err = strconv.Atoi(args[0])
if err != nil {
fmt.Printf("argument conversion error: %s\n", err)
return
}
}
c.vm.AddBreakPointRel(n)
c.vm.Run()
case "opcode":
prog := c.vm.Context().Program()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t")
for i := 0; i < len(prog); i++ {
fmt.Fprintf(w, "%d\t0x%2x\t%s\t\n", i, prog[i], vm.Opcode(prog[i]))
}
w.Flush()
}
}
// Run waits for user input from Stdin and executes the passed command.
func (c *VMCLI) Run() error {
printLogo()
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("NEO-GO-VM > ")
input, _ := reader.ReadString('\n')
input = strings.Trim(input, "\n")
if len(input) != 0 {
parts := strings.Split(input, " ")
cmd := parts[0]
args := []string{}
if len(parts) > 1 {
args = parts[1:]
}
c.handleCommand(cmd, args...)
}
}
}
func printLogo() {
logo := `
_ ____________ __________ _ ____ ___
/ | / / ____/ __ \ / ____/ __ \ | | / / |/ /
/ |/ / __/ / / / /_____/ / __/ / / /____| | / / /|_/ /
/ /| / /___/ /_/ /_____/ /_/ / /_/ /_____/ |/ / / / /
/_/ |_/_____/\____/ \____/\____/ |___/_/ /_/
`
fmt.Print(logo)
fmt.Println()
fmt.Println()
}