mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-05-07 15:07:36 +00:00
vm: replace ishell with urfave/cli
Use github.com/chzyer/readline for readline capabilities (including history and ANSI escape sequences handling).
This commit is contained in:
parent
c4b49a2d52
commit
563c3a4baa
6 changed files with 354 additions and 282 deletions
|
@ -8,6 +8,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -15,9 +16,10 @@ import (
|
|||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/abiosoft/ishell/v2"
|
||||
"github.com/abiosoft/readline"
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/kballard/go-shellquote"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||
|
@ -27,115 +29,128 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
vmKey = "vm"
|
||||
manifestKey = "manifest"
|
||||
boolType = "bool"
|
||||
boolFalse = "false"
|
||||
boolTrue = "true"
|
||||
intType = "int"
|
||||
stringType = "string"
|
||||
exitFunc = "exitFunc"
|
||||
vmKey = "vm"
|
||||
manifestKey = "manifest"
|
||||
exitFuncKey = "exitFunc"
|
||||
readlineInstanceKey = "readlineKey"
|
||||
printLogoKey = "printLogoKey"
|
||||
boolType = "bool"
|
||||
boolFalse = "false"
|
||||
boolTrue = "true"
|
||||
intType = "int"
|
||||
stringType = "string"
|
||||
)
|
||||
|
||||
var commands = []*ishell.Cmd{
|
||||
var commands = []cli.Command{
|
||||
{
|
||||
Name: "exit",
|
||||
Help: "Exit the VM prompt",
|
||||
LongHelp: "Exit the VM prompt",
|
||||
Func: handleExit,
|
||||
Name: "exit",
|
||||
Usage: "Exit the VM prompt",
|
||||
Description: "Exit the VM prompt",
|
||||
Action: handleExit,
|
||||
},
|
||||
{
|
||||
Name: "ip",
|
||||
Help: "Show current instruction",
|
||||
LongHelp: "Show current instruction",
|
||||
Func: handleIP,
|
||||
Name: "ip",
|
||||
Usage: "Show current instruction",
|
||||
Description: "Show current instruction",
|
||||
Action: handleIP,
|
||||
},
|
||||
{
|
||||
Name: "break",
|
||||
Help: "Place a breakpoint",
|
||||
LongHelp: `Usage: break <ip>
|
||||
Name: "break",
|
||||
Usage: "Place a breakpoint",
|
||||
UsageText: `break <ip>`,
|
||||
Description: `break <ip>
|
||||
<ip> is mandatory parameter, example:
|
||||
> break 12`,
|
||||
Func: handleBreak,
|
||||
Action: handleBreak,
|
||||
},
|
||||
{
|
||||
Name: "estack",
|
||||
Help: "Show evaluation stack contents",
|
||||
LongHelp: "Show evaluation stack contents",
|
||||
Func: handleXStack,
|
||||
Name: "estack",
|
||||
Usage: "Show evaluation stack contents",
|
||||
Description: "Show evaluation stack contents",
|
||||
Action: handleXStack,
|
||||
},
|
||||
{
|
||||
Name: "istack",
|
||||
Help: "Show invocation stack contents",
|
||||
LongHelp: "Show invocation stack contents",
|
||||
Func: handleXStack,
|
||||
Name: "istack",
|
||||
Usage: "Show invocation stack contents",
|
||||
Description: "Show invocation stack contents",
|
||||
Action: handleXStack,
|
||||
},
|
||||
{
|
||||
Name: "sslot",
|
||||
Help: "Show static slot contents",
|
||||
LongHelp: "Show static slot contents",
|
||||
Func: handleSlots,
|
||||
Name: "sslot",
|
||||
Usage: "Show static slot contents",
|
||||
Description: "Show static slot contents",
|
||||
Action: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "lslot",
|
||||
Help: "Show local slot contents",
|
||||
LongHelp: "Show local slot contents",
|
||||
Func: handleSlots,
|
||||
Name: "lslot",
|
||||
Usage: "Show local slot contents",
|
||||
Description: "Show local slot contents",
|
||||
Action: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "aslot",
|
||||
Help: "Show arguments slot contents",
|
||||
LongHelp: "Show arguments slot contents",
|
||||
Func: handleSlots,
|
||||
Name: "aslot",
|
||||
Usage: "Show arguments slot contents",
|
||||
Description: "Show arguments slot contents",
|
||||
Action: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "loadnef",
|
||||
Help: "Load a NEF-consistent script into the VM",
|
||||
LongHelp: `Usage: loadnef <file> <manifest>
|
||||
Name: "loadnef",
|
||||
Usage: "Load a NEF-consistent script into the VM",
|
||||
UsageText: `loadnef <file> <manifest>`,
|
||||
Description: `loadnef <file> <manifest>
|
||||
both parameters are mandatory, example:
|
||||
> loadnef /path/to/script.nef /path/to/manifest.json`,
|
||||
Func: handleLoadNEF,
|
||||
Action: handleLoadNEF,
|
||||
},
|
||||
{
|
||||
Name: "loadbase64",
|
||||
Help: "Load a base64-encoded script string into the VM",
|
||||
LongHelp: `Usage: loadbase64 <string>
|
||||
Name: "loadbase64",
|
||||
Usage: "Load a base64-encoded script string into the VM",
|
||||
UsageText: `loadbase64 <string>`,
|
||||
Description: `loadbase64 <string>
|
||||
|
||||
<string> is mandatory parameter, example:
|
||||
> loadbase64 AwAQpdToAAAADBQV9ehtQR1OrVZVhtHtoUHRfoE+agwUzmFvf3Rhfg/EuAVYOvJgKiON9j8TwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I4`,
|
||||
Func: handleLoadBase64,
|
||||
Action: handleLoadBase64,
|
||||
},
|
||||
{
|
||||
Name: "loadhex",
|
||||
Help: "Load a hex-encoded script string into the VM",
|
||||
LongHelp: `Usage: loadhex <string>
|
||||
Name: "loadhex",
|
||||
Usage: "Load a hex-encoded script string into the VM",
|
||||
UsageText: `loadhex <string>`,
|
||||
Description: `loadhex <string>
|
||||
|
||||
<string> is mandatory parameter, example:
|
||||
> loadhex 0c0c48656c6c6f20776f726c6421`,
|
||||
Func: handleLoadHex,
|
||||
Action: handleLoadHex,
|
||||
},
|
||||
{
|
||||
Name: "loadgo",
|
||||
Help: "Compile and load a Go file with the manifest into the VM",
|
||||
LongHelp: `Usage: loadgo <file>
|
||||
Name: "loadgo",
|
||||
Usage: "Compile and load a Go file with the manifest into the VM",
|
||||
UsageText: `loadgo <file>`,
|
||||
Description: `loadgo <file>
|
||||
|
||||
<file> is mandatory parameter, example:
|
||||
> loadgo /path/to/file.go`,
|
||||
Func: handleLoadGo,
|
||||
Action: handleLoadGo,
|
||||
},
|
||||
{
|
||||
Name: "parse",
|
||||
Help: "Parse provided argument and convert it into other possible formats",
|
||||
LongHelp: `Usage: parse <arg>
|
||||
Name: "parse",
|
||||
Usage: "Parse provided argument and convert it into other possible formats",
|
||||
UsageText: `parse <arg>`,
|
||||
Description: `parse <arg>
|
||||
|
||||
<arg> is an argument which is tried to be interpreted as an item of different types
|
||||
and converted to other formats. Strings are escaped and output in quotes.`,
|
||||
Func: handleParse,
|
||||
and converted to other formats. Strings are escaped and output in quotes.`,
|
||||
Action: handleParse,
|
||||
},
|
||||
{
|
||||
Name: "run",
|
||||
Help: "Execute the current loaded script",
|
||||
LongHelp: `Usage: run [<method> [<parameter>...]]
|
||||
Name: "run",
|
||||
Usage: "Execute the current loaded script",
|
||||
UsageText: `run [<method> [<parameter>...]]`,
|
||||
Description: `run [<method> [<parameter>...]]
|
||||
|
||||
<method> is a contract method, specified in manifest (and it
|
||||
can't be 'help' at the moment). It can be '_' which will push parameters
|
||||
|
@ -153,51 +168,52 @@ both parameters are mandatory, example:
|
|||
|
||||
Example:
|
||||
> run put ` + stringType + `:"Something to put"`,
|
||||
Func: handleRun,
|
||||
Action: handleRun,
|
||||
},
|
||||
{
|
||||
Name: "cont",
|
||||
Help: "Continue execution of the current loaded script",
|
||||
LongHelp: "Continue execution of the current loaded script",
|
||||
Func: handleCont,
|
||||
Name: "cont",
|
||||
Usage: "Continue execution of the current loaded script",
|
||||
Description: "Continue execution of the current loaded script",
|
||||
Action: handleCont,
|
||||
},
|
||||
{
|
||||
Name: "step",
|
||||
Help: "Step (n) instruction in the program",
|
||||
LongHelp: `Usage: step [<n>]
|
||||
Name: "step",
|
||||
Usage: "Step (n) instruction in the program",
|
||||
UsageText: `step [<n>]`,
|
||||
Description: `step [<n>]
|
||||
<n> is optional parameter to specify number of instructions to run, example:
|
||||
> step 10`,
|
||||
Func: handleStep,
|
||||
Action: handleStep,
|
||||
},
|
||||
{
|
||||
Name: "stepinto",
|
||||
Help: "Stepinto instruction to take in the debugger",
|
||||
LongHelp: `Usage: stepInto
|
||||
Name: "stepinto",
|
||||
Usage: "Stepinto instruction to take in the debugger",
|
||||
Description: `Usage: stepInto
|
||||
example:
|
||||
> stepinto`,
|
||||
Func: handleStepInto,
|
||||
Action: handleStepInto,
|
||||
},
|
||||
{
|
||||
Name: "stepout",
|
||||
Help: "Stepout instruction to take in the debugger",
|
||||
LongHelp: `Usage: stepOut
|
||||
Name: "stepout",
|
||||
Usage: "Stepout instruction to take in the debugger",
|
||||
Description: `stepOut
|
||||
example:
|
||||
> stepout`,
|
||||
Func: handleStepOut,
|
||||
Action: handleStepOut,
|
||||
},
|
||||
{
|
||||
Name: "stepover",
|
||||
Help: "Stepover instruction to take in the debugger",
|
||||
LongHelp: `Usage: stepOver
|
||||
Name: "stepover",
|
||||
Usage: "Stepover instruction to take in the debugger",
|
||||
Description: `stepOver
|
||||
example:
|
||||
> stepover`,
|
||||
Func: handleStepOver,
|
||||
Action: handleStepOver,
|
||||
},
|
||||
{
|
||||
Name: "ops",
|
||||
Help: "Dump opcodes of the current loaded program",
|
||||
LongHelp: "Dump opcodes of the current loaded program",
|
||||
Func: handleOps,
|
||||
Name: "ops",
|
||||
Usage: "Dump opcodes of the current loaded program",
|
||||
Description: "Dump opcodes of the current loaded program",
|
||||
Action: handleOps,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -210,119 +226,156 @@ var (
|
|||
// VMCLI object for interacting with the VM.
|
||||
type VMCLI struct {
|
||||
vm *vm.VM
|
||||
shell *ishell.Shell
|
||||
// printLogo specifies if logo is printed.
|
||||
printLogo bool
|
||||
shell *cli.App
|
||||
}
|
||||
|
||||
// New returns a new VMCLI object.
|
||||
func New() *VMCLI {
|
||||
return NewWithConfig(true, os.Exit, &readline.Config{
|
||||
Prompt: ">>>",
|
||||
Prompt: "\033[32mNEO-GO-VM >\033[0m ", // green prompt ^^
|
||||
})
|
||||
}
|
||||
|
||||
// NewWithConfig returns new VMCLI instance using provided config.
|
||||
func NewWithConfig(printLogo bool, onExit func(int), c *readline.Config) *VMCLI {
|
||||
func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config) *VMCLI {
|
||||
l, err := readline.NewEx(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctl := cli.NewApp()
|
||||
ctl.Name = "VM CLI"
|
||||
|
||||
// Note: need to set empty `ctl.HelpName` and `ctl.UsageText`, otherwise
|
||||
// `filepath.Base(os.Args[0])` will be used which is `neo-go`.
|
||||
ctl.HelpName = ""
|
||||
ctl.UsageText = ""
|
||||
|
||||
ctl.Writer = l.Stdout()
|
||||
ctl.ErrWriter = l.Stderr()
|
||||
ctl.Version = config.Version
|
||||
ctl.Usage = "Official VM CLI for Neo-Go"
|
||||
|
||||
// Override default error handler in order not to exit on error.
|
||||
ctl.ExitErrHandler = func(context *cli.Context, err error) {}
|
||||
|
||||
ctl.Commands = commands
|
||||
|
||||
vmcli := VMCLI{
|
||||
vm: vm.New(),
|
||||
shell: ishell.NewWithConfig(c),
|
||||
printLogo: printLogo,
|
||||
vm: vm.New(),
|
||||
shell: ctl,
|
||||
}
|
||||
vmcli.shell.Set(vmKey, vmcli.vm)
|
||||
vmcli.shell.Set(manifestKey, new(manifest.Manifest))
|
||||
vmcli.shell.Set(exitFunc, onExit)
|
||||
for _, c := range commands {
|
||||
vmcli.shell.AddCmd(c)
|
||||
|
||||
vmcli.shell.Metadata = map[string]interface{}{
|
||||
vmKey: vmcli.vm,
|
||||
manifestKey: new(manifest.Manifest),
|
||||
exitFuncKey: onExit,
|
||||
readlineInstanceKey: l,
|
||||
printLogoKey: printLogotype,
|
||||
}
|
||||
changePrompt(vmcli.shell, vmcli.vm)
|
||||
changePrompt(vmcli.shell)
|
||||
return &vmcli
|
||||
}
|
||||
|
||||
func getVMFromContext(c *ishell.Context) *vm.VM {
|
||||
return c.Get(vmKey).(*vm.VM)
|
||||
func getExitFuncFromContext(app *cli.App) func(int) {
|
||||
return app.Metadata[exitFuncKey].(func(int))
|
||||
}
|
||||
|
||||
func getManifestFromContext(c *ishell.Context) *manifest.Manifest {
|
||||
return c.Get(manifestKey).(*manifest.Manifest)
|
||||
func getReadlineInstanceFromContext(app *cli.App) *readline.Instance {
|
||||
return app.Metadata[readlineInstanceKey].(*readline.Instance)
|
||||
}
|
||||
|
||||
func setManifestInContext(c *ishell.Context, m *manifest.Manifest) {
|
||||
old := getManifestFromContext(c)
|
||||
func getVMFromContext(app *cli.App) *vm.VM {
|
||||
return app.Metadata[vmKey].(*vm.VM)
|
||||
}
|
||||
|
||||
func getManifestFromContext(app *cli.App) *manifest.Manifest {
|
||||
return app.Metadata[manifestKey].(*manifest.Manifest)
|
||||
}
|
||||
|
||||
func getPrintLogoFromContext(app *cli.App) bool {
|
||||
return app.Metadata[printLogoKey].(bool)
|
||||
}
|
||||
|
||||
func setManifestInContext(app *cli.App, m *manifest.Manifest) {
|
||||
old := getManifestFromContext(app)
|
||||
*old = *m
|
||||
}
|
||||
|
||||
func checkVMIsReady(c *ishell.Context) bool {
|
||||
v := getVMFromContext(c)
|
||||
func checkVMIsReady(app *cli.App) bool {
|
||||
v := getVMFromContext(app)
|
||||
if v == nil || !v.Ready() {
|
||||
c.Err(errors.New("VM is not ready: no program loaded"))
|
||||
writeErr(app.Writer, errors.New("VM is not ready: no program loaded"))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func handleExit(c *ishell.Context) {
|
||||
c.Println("Bye!")
|
||||
c.Get(exitFunc).(func(int))(0)
|
||||
func handleExit(c *cli.Context) error {
|
||||
l := getReadlineInstanceFromContext(c.App)
|
||||
_ = l.Close()
|
||||
exit := getExitFuncFromContext(c.App)
|
||||
fmt.Fprintln(c.App.Writer, "Bye!")
|
||||
exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleIP(c *ishell.Context) {
|
||||
if !checkVMIsReady(c) {
|
||||
return
|
||||
func handleIP(c *cli.Context) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
v := getVMFromContext(c)
|
||||
v := getVMFromContext(c.App)
|
||||
ctx := v.Context()
|
||||
if ctx.NextIP() < ctx.LenInstr() {
|
||||
ip, opcode := v.Context().NextInstr()
|
||||
c.Printf("instruction pointer at %d (%s)\n", ip, opcode)
|
||||
fmt.Fprintf(c.App.Writer, "instruction pointer at %d (%s)\n", ip, opcode)
|
||||
} else {
|
||||
c.Println("execution has finished")
|
||||
fmt.Fprintln(c.App.Writer, "execution has finished")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleBreak(c *ishell.Context) {
|
||||
if !checkVMIsReady(c) {
|
||||
return
|
||||
func handleBreak(c *cli.Context) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
v := getVMFromContext(c)
|
||||
if len(c.Args) != 1 {
|
||||
c.Err(fmt.Errorf("%w: <ip>", ErrMissingParameter))
|
||||
return
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("%w: <ip>", ErrMissingParameter)
|
||||
}
|
||||
n, err := strconv.Atoi(c.Args[0])
|
||||
n, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
c.Err(fmt.Errorf("%w: %v", ErrInvalidParameter, err))
|
||||
return
|
||||
return fmt.Errorf("%w: %s", ErrInvalidParameter, err)
|
||||
}
|
||||
|
||||
v.AddBreakPoint(n)
|
||||
c.Printf("breakpoint added at instruction %d\n", n)
|
||||
fmt.Fprintf(c.App.Writer, "breakpoint added at instruction %d\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleXStack(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
func handleXStack(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
var stackDump string
|
||||
switch c.Cmd.Name {
|
||||
switch c.Command.Name {
|
||||
case "estack":
|
||||
stackDump = v.DumpEStack()
|
||||
case "istack":
|
||||
stackDump = v.DumpIStack()
|
||||
default:
|
||||
c.Err(errors.New("unknown stack"))
|
||||
return
|
||||
return errors.New("unknown stack")
|
||||
}
|
||||
c.Println(stackDump)
|
||||
fmt.Fprintln(c.App.Writer, stackDump)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSlots(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
func handleSlots(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
vmCtx := v.Context()
|
||||
if vmCtx == nil {
|
||||
c.Err(errors.New("no program loaded"))
|
||||
return
|
||||
return errors.New("no program loaded")
|
||||
}
|
||||
var rawSlot string
|
||||
switch c.Cmd.Name {
|
||||
switch c.Command.Name {
|
||||
case "sslot":
|
||||
rawSlot = vmCtx.DumpStaticSlot()
|
||||
case "lslot":
|
||||
|
@ -330,89 +383,87 @@ func handleSlots(c *ishell.Context) {
|
|||
case "aslot":
|
||||
rawSlot = vmCtx.DumpArgumentsSlot()
|
||||
default:
|
||||
c.Err(errors.New("unknown slot"))
|
||||
return
|
||||
return errors.New("unknown slot")
|
||||
}
|
||||
c.Println(rawSlot)
|
||||
fmt.Fprintln(c.App.Writer, rawSlot)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadNEF(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
if len(c.Args) < 2 {
|
||||
c.Err(fmt.Errorf("%w: <file> <manifest>", ErrMissingParameter))
|
||||
return
|
||||
func handleLoadNEF(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("%w: <file> <manifest>", ErrMissingParameter)
|
||||
}
|
||||
if err := v.LoadFileWithFlags(c.Args[0], callflag.All); err != nil {
|
||||
c.Err(err)
|
||||
return
|
||||
if err := v.LoadFileWithFlags(args[0], callflag.All); err != nil {
|
||||
return fmt.Errorf("failed to read nef: %w", err)
|
||||
}
|
||||
m, err := getManifestFromFile(c.Args[1])
|
||||
m, err := getManifestFromFile(args[1])
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
return
|
||||
return fmt.Errorf("failed to read manifest: %w", err)
|
||||
}
|
||||
c.Printf("READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
setManifestInContext(c, m)
|
||||
changePrompt(c, v)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
setManifestInContext(c.App, m)
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadBase64(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
if len(c.Args) < 1 {
|
||||
c.Err(fmt.Errorf("%w: <string>", ErrMissingParameter))
|
||||
return
|
||||
func handleLoadBase64(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <string>", ErrMissingParameter)
|
||||
}
|
||||
b, err := base64.StdEncoding.DecodeString(c.Args[0])
|
||||
b, err := base64.StdEncoding.DecodeString(args[0])
|
||||
if err != nil {
|
||||
c.Err(fmt.Errorf("%w: %v", ErrInvalidParameter, err))
|
||||
return
|
||||
return fmt.Errorf("%w: %s", ErrInvalidParameter, err)
|
||||
}
|
||||
v.LoadWithFlags(b, callflag.All)
|
||||
c.Printf("READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c, v)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadHex(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
if len(c.Args) < 1 {
|
||||
c.Err(fmt.Errorf("%w: <string>", ErrMissingParameter))
|
||||
return
|
||||
func handleLoadHex(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <string>", ErrMissingParameter)
|
||||
}
|
||||
b, err := hex.DecodeString(c.Args[0])
|
||||
b, err := hex.DecodeString(args[0])
|
||||
if err != nil {
|
||||
c.Err(fmt.Errorf("%w: %v", ErrInvalidParameter, err))
|
||||
return
|
||||
return fmt.Errorf("%w: %s", ErrInvalidParameter, err)
|
||||
}
|
||||
v.LoadWithFlags(b, callflag.All)
|
||||
c.Printf("READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c, v)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLoadGo(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
if len(c.Args) < 1 {
|
||||
c.Err(fmt.Errorf("%w: <file>", ErrMissingParameter))
|
||||
return
|
||||
func handleLoadGo(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <file>", ErrMissingParameter)
|
||||
}
|
||||
|
||||
name := strings.TrimSuffix(c.Args[0], ".go")
|
||||
b, di, err := compiler.CompileWithOptions(c.Args[0], nil, &compiler.Options{Name: name})
|
||||
name := strings.TrimSuffix(args[0], ".go")
|
||||
b, di, err := compiler.CompileWithOptions(args[0], nil, &compiler.Options{Name: name})
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
return
|
||||
return fmt.Errorf("failed to compile: %w", err)
|
||||
}
|
||||
|
||||
// Don't perform checks, just load.
|
||||
m, err := di.ConvertToManifest(&compiler.Options{})
|
||||
if err != nil {
|
||||
c.Err(fmt.Errorf("can't create manifest: %w", err))
|
||||
return
|
||||
return fmt.Errorf("can't create manifest: %w", err)
|
||||
}
|
||||
setManifestInContext(c, m)
|
||||
setManifestInContext(c.App, m)
|
||||
|
||||
v.LoadWithFlags(b.Script, callflag.All)
|
||||
c.Printf("READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c, v)
|
||||
fmt.Fprintf(c.App.Writer, "READY: loaded %d instructions\n", v.Context().LenInstr())
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getManifestFromFile(name string) (*manifest.Manifest, error) {
|
||||
|
@ -428,27 +479,26 @@ func getManifestFromFile(name string) (*manifest.Manifest, error) {
|
|||
return &m, nil
|
||||
}
|
||||
|
||||
func handleRun(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
m := getManifestFromContext(c)
|
||||
if len(c.Args) != 0 {
|
||||
func handleRun(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
m := getManifestFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) != 0 {
|
||||
var (
|
||||
params []stackitem.Item
|
||||
offset int
|
||||
err error
|
||||
runCurrent = c.Args[0] != "_"
|
||||
runCurrent = args[0] != "_"
|
||||
)
|
||||
|
||||
params, err = parseArgs(c.Args[1:])
|
||||
params, err = parseArgs(args[1:])
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
if runCurrent {
|
||||
md := m.ABI.GetMethod(c.Args[0], len(params))
|
||||
md := m.ABI.GetMethod(args[0], len(params))
|
||||
if md == nil {
|
||||
c.Err(fmt.Errorf("%w: method not found", ErrInvalidParameter))
|
||||
return
|
||||
return fmt.Errorf("%w: method not found", ErrInvalidParameter)
|
||||
}
|
||||
offset = md.Offset
|
||||
}
|
||||
|
@ -457,8 +507,7 @@ func handleRun(c *ishell.Context) {
|
|||
}
|
||||
if runCurrent {
|
||||
if !v.Ready() {
|
||||
c.Err(fmt.Errorf("no program loaded"))
|
||||
return
|
||||
return errors.New("no program loaded")
|
||||
}
|
||||
v.Context().Jump(offset)
|
||||
if initMD := m.ABI.GetMethod(manifest.MethodInit, 0); initMD != nil {
|
||||
|
@ -466,15 +515,17 @@ func handleRun(c *ishell.Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
runVMWithHandling(c, v)
|
||||
changePrompt(c, v)
|
||||
runVMWithHandling(c)
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
// runVMWithHandling runs VM with handling errors and additional state messages.
|
||||
func runVMWithHandling(c *ishell.Context, v *vm.VM) {
|
||||
func runVMWithHandling(c *cli.Context) {
|
||||
v := getVMFromContext(c.App)
|
||||
err := v.Run()
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
writeErr(c.App.ErrWriter, err)
|
||||
}
|
||||
|
||||
var message string
|
||||
|
@ -493,58 +544,59 @@ func runVMWithHandling(c *ishell.Context, v *vm.VM) {
|
|||
}
|
||||
}
|
||||
if message != "" {
|
||||
c.Println(message)
|
||||
fmt.Fprintln(c.App.Writer, message)
|
||||
}
|
||||
}
|
||||
|
||||
func handleCont(c *ishell.Context) {
|
||||
if !checkVMIsReady(c) {
|
||||
return
|
||||
func handleCont(c *cli.Context) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
v := getVMFromContext(c)
|
||||
runVMWithHandling(c, v)
|
||||
changePrompt(c, v)
|
||||
runVMWithHandling(c)
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleStep(c *ishell.Context) {
|
||||
func handleStep(c *cli.Context) error {
|
||||
var (
|
||||
n = 1
|
||||
err error
|
||||
)
|
||||
|
||||
if !checkVMIsReady(c) {
|
||||
return
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
v := getVMFromContext(c)
|
||||
if len(c.Args) > 0 {
|
||||
n, err = strconv.Atoi(c.Args[0])
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
if len(args) > 0 {
|
||||
n, err = strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
c.Err(fmt.Errorf("%w: %v", ErrInvalidParameter, err))
|
||||
return
|
||||
return fmt.Errorf("%w: %s", ErrInvalidParameter, err)
|
||||
}
|
||||
}
|
||||
v.AddBreakPointRel(n)
|
||||
runVMWithHandling(c, v)
|
||||
changePrompt(c, v)
|
||||
runVMWithHandling(c)
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleStepInto(c *ishell.Context) {
|
||||
handleStepType(c, "into")
|
||||
func handleStepInto(c *cli.Context) error {
|
||||
return handleStepType(c, "into")
|
||||
}
|
||||
|
||||
func handleStepOut(c *ishell.Context) {
|
||||
handleStepType(c, "out")
|
||||
func handleStepOut(c *cli.Context) error {
|
||||
return handleStepType(c, "out")
|
||||
}
|
||||
|
||||
func handleStepOver(c *ishell.Context) {
|
||||
handleStepType(c, "over")
|
||||
func handleStepOver(c *cli.Context) error {
|
||||
return handleStepType(c, "over")
|
||||
}
|
||||
|
||||
func handleStepType(c *ishell.Context, stepType string) {
|
||||
if !checkVMIsReady(c) {
|
||||
return
|
||||
func handleStepType(c *cli.Context, stepType string) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
v := getVMFromContext(c)
|
||||
v := getVMFromContext(c.App)
|
||||
var err error
|
||||
switch stepType {
|
||||
case "into":
|
||||
|
@ -555,47 +607,69 @@ func handleStepType(c *ishell.Context, stepType string) {
|
|||
err = v.StepOver()
|
||||
}
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
} else {
|
||||
handleIP(c)
|
||||
return err
|
||||
}
|
||||
changePrompt(c, v)
|
||||
_ = handleIP(c)
|
||||
changePrompt(c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleOps(c *ishell.Context) {
|
||||
if !checkVMIsReady(c) {
|
||||
return
|
||||
func handleOps(c *cli.Context) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
v := getVMFromContext(c)
|
||||
v := getVMFromContext(c.App)
|
||||
out := bytes.NewBuffer(nil)
|
||||
v.PrintOps(out)
|
||||
c.Println(out.String())
|
||||
fmt.Fprintln(c.App.Writer, out.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func changePrompt(c ishell.Actions, v *vm.VM) {
|
||||
func changePrompt(app *cli.App) {
|
||||
v := getVMFromContext(app)
|
||||
l := getReadlineInstanceFromContext(app)
|
||||
if v.Ready() && v.Context().NextIP() >= 0 && v.Context().NextIP() < v.Context().LenInstr() {
|
||||
c.SetPrompt(fmt.Sprintf("NEO-GO-VM %d > ", v.Context().NextIP()))
|
||||
l.SetPrompt(fmt.Sprintf("\033[32mNEO-GO-VM %d >\033[0m ", v.Context().NextIP()))
|
||||
} else {
|
||||
c.SetPrompt("NEO-GO-VM > ")
|
||||
l.SetPrompt("\033[32mNEO-GO-VM >\033[0m ")
|
||||
}
|
||||
}
|
||||
|
||||
// Run waits for user input from Stdin and executes the passed command.
|
||||
func (c *VMCLI) Run() error {
|
||||
if c.printLogo {
|
||||
printLogo(c.shell)
|
||||
if getPrintLogoFromContext(c.shell) {
|
||||
printLogo(c.shell.Writer)
|
||||
}
|
||||
l := getReadlineInstanceFromContext(c.shell)
|
||||
for {
|
||||
line, err := l.Readline()
|
||||
if err == io.EOF || err == readline.ErrInterrupt {
|
||||
return nil // OK, stop execution.
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read input: %w", err) // Critical error, stop execution.
|
||||
}
|
||||
|
||||
args, err := shellquote.Split(line)
|
||||
if err != nil {
|
||||
writeErr(c.shell.ErrWriter, fmt.Errorf("failed to parse arguments: %w", err))
|
||||
continue // Not a critical error, continue execution.
|
||||
}
|
||||
|
||||
err = c.shell.Run(append([]string{"vm"}, args...))
|
||||
if err != nil {
|
||||
writeErr(c.shell.ErrWriter, err) // Various command/flags parsing errors and execution errors.
|
||||
}
|
||||
}
|
||||
c.shell.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleParse(c *ishell.Context) {
|
||||
res, err := Parse(c.Args)
|
||||
func handleParse(c *cli.Context) error {
|
||||
res, err := Parse(c.Args())
|
||||
if err != nil {
|
||||
c.Err(err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
c.Print(res)
|
||||
fmt.Fprintln(c.App.Writer, res)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse converts it's argument to other formats.
|
||||
|
@ -708,8 +782,12 @@ const logo = `
|
|||
/_/ |_/_____/\____/ \____/\____/ |___/_/ /_/
|
||||
`
|
||||
|
||||
func printLogo(c *ishell.Shell) {
|
||||
c.Print(logo)
|
||||
c.Println()
|
||||
c.Println()
|
||||
func printLogo(w io.Writer) {
|
||||
fmt.Fprintln(w, logo)
|
||||
fmt.Fprintln(w)
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
func writeErr(w io.Writer, err error) {
|
||||
fmt.Fprintf(w, "Error: %s\n", err)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue