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:
Anna Shaleva 2022-02-15 15:55:25 +03:00 committed by AnnaShaleva
parent c4b49a2d52
commit 563c3a4baa
6 changed files with 354 additions and 282 deletions

View file

@ -3,7 +3,7 @@ package vm
import ( import (
"os" "os"
"github.com/abiosoft/readline" "github.com/chzyer/readline"
vmcli "github.com/nspcc-dev/neo-go/pkg/vm/cli" vmcli "github.com/nspcc-dev/neo-go/pkg/vm/cli"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -21,9 +21,6 @@ func NewCommands() []cli.Command {
} }
func startVMPrompt(ctx *cli.Context) error { func startVMPrompt(ctx *cli.Context) error {
p := vmcli.NewWithConfig(true, os.Exit, &readline.Config{ p := vmcli.NewWithConfig(true, os.Exit, &readline.Config{})
Stdout: ctx.App.Writer,
Stderr: ctx.App.ErrWriter,
})
return p.Run() return p.Run()
} }

View file

@ -8,7 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/abiosoft/readline" "github.com/chzyer/readline"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/address"

4
go.mod
View file

@ -1,13 +1,13 @@
module github.com/nspcc-dev/neo-go module github.com/nspcc-dev/neo-go
require ( require (
github.com/abiosoft/ishell/v2 v2.0.2
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db
github.com/btcsuite/btcd v0.22.0-beta github.com/btcsuite/btcd v0.22.0-beta
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru v0.5.4
github.com/holiman/uint256 v1.2.0 github.com/holiman/uint256 v1.2.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mr-tron/base58 v1.2.0 github.com/mr-tron/base58 v1.2.0
github.com/nspcc-dev/dbft v0.0.0-20210721160347-1b03241391ac github.com/nspcc-dev/dbft v0.0.0-20210721160347-1b03241391ac
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22

10
go.sum
View file

@ -6,11 +6,8 @@ github.com/CityOfZion/neo-go v0.70.1-pre.0.20191209120015-fccb0085941e/go.mod h1
github.com/CityOfZion/neo-go v0.70.1-pre.0.20191212173117-32ac01130d4c/go.mod h1:JtlHfeqLywZLswKIKFnAp+yzezY4Dji9qlfQKB2OD/I= github.com/CityOfZion/neo-go v0.70.1-pre.0.20191212173117-32ac01130d4c/go.mod h1:JtlHfeqLywZLswKIKFnAp+yzezY4Dji9qlfQKB2OD/I=
github.com/CityOfZion/neo-go v0.71.1-pre.0.20200129171427-f773ec69fb84/go.mod h1:FLI526IrRWHmcsO+mHsCbj64pJZhwQFTLJZu+A4PGOA= github.com/CityOfZion/neo-go v0.71.1-pre.0.20200129171427-f773ec69fb84/go.mod h1:FLI526IrRWHmcsO+mHsCbj64pJZhwQFTLJZu+A4PGOA=
github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA=
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg= github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
github.com/abiosoft/ishell/v2 v2.0.2 h1:5qVfGiQISaYM8TkbBl7RFO6MddABoXpATrsFbVI+SNo=
github.com/abiosoft/ishell/v2 v2.0.2/go.mod h1:E4oTCXfo6QjoCart0QYa5m9w4S+deXs/P/9jA77A9Bs= github.com/abiosoft/ishell/v2 v2.0.2/go.mod h1:E4oTCXfo6QjoCart0QYa5m9w4S+deXs/P/9jA77A9Bs=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530= github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -47,6 +44,7 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -70,9 +68,7 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M= github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
@ -142,6 +138,8 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -155,12 +153,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=

View file

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

View file

@ -15,7 +15,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/abiosoft/readline" "github.com/chzyer/readline"
"github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
@ -75,6 +75,7 @@ func newTestVMCLIWithLogo(t *testing.T, printLogo bool) *executor {
&readline.Config{ &readline.Config{
Prompt: "", Prompt: "",
Stdin: e.in, Stdin: e.in,
Stderr: e.out,
Stdout: e.out, Stdout: e.out,
FuncIsTerminal: func() bool { FuncIsTerminal: func() bool {
return false return false