Revert "cmd/evm: fix some issues with the evm run command (#28109)"

This reverts commit 0264a9ad74.
This commit is contained in:
devopsbo3 2023-11-10 12:27:53 -06:00 committed by GitHub
parent 627678beea
commit 2755593ba0
9 changed files with 271 additions and 296 deletions

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -40,7 +41,10 @@ func blockTestCmd(ctx *cli.Context) error {
if len(ctx.Args().First()) == 0 { if len(ctx.Args().First()) == 0 {
return errors.New("path-to-test argument required") return errors.New("path-to-test argument required")
} }
// Configure the go-ethereum logger
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
log.Root().SetHandler(glogger)
var tracer vm.EVMLogger var tracer vm.EVMLogger
// Configure the EVM logger // Configure the EVM logger
if ctx.Bool(MachineFlag.Name) { if ctx.Bool(MachineFlag.Name) {

View file

@ -23,116 +23,107 @@ import (
"os" "os"
"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool" "github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var ( var (
DebugFlag = &cli.BoolFlag{ DebugFlag = &cli.BoolFlag{
Name: "debug", Name: "debug",
Usage: "output full trace logs", Usage: "output full trace logs",
Category: flags.VMCategory, }
MemProfileFlag = &cli.StringFlag{
Name: "memprofile",
Usage: "creates a memory profile at the given path",
}
CPUProfileFlag = &cli.StringFlag{
Name: "cpuprofile",
Usage: "creates a CPU profile at the given path",
} }
StatDumpFlag = &cli.BoolFlag{ StatDumpFlag = &cli.BoolFlag{
Name: "statdump", Name: "statdump",
Usage: "displays stack and heap memory information", Usage: "displays stack and heap memory information",
Category: flags.VMCategory,
} }
CodeFlag = &cli.StringFlag{ CodeFlag = &cli.StringFlag{
Name: "code", Name: "code",
Usage: "EVM code", Usage: "EVM code",
Category: flags.VMCategory,
} }
CodeFileFlag = &cli.StringFlag{ CodeFileFlag = &cli.StringFlag{
Name: "codefile", Name: "codefile",
Usage: "File containing EVM code. If '-' is specified, code is read from stdin ", Usage: "File containing EVM code. If '-' is specified, code is read from stdin ",
Category: flags.VMCategory,
} }
GasFlag = &cli.Uint64Flag{ GasFlag = &cli.Uint64Flag{
Name: "gas", Name: "gas",
Usage: "gas limit for the evm", Usage: "gas limit for the evm",
Value: 10000000000, Value: 10000000000,
Category: flags.VMCategory,
} }
PriceFlag = &flags.BigFlag{ PriceFlag = &flags.BigFlag{
Name: "price", Name: "price",
Usage: "price set for the evm", Usage: "price set for the evm",
Value: new(big.Int), Value: new(big.Int),
Category: flags.VMCategory,
} }
ValueFlag = &flags.BigFlag{ ValueFlag = &flags.BigFlag{
Name: "value", Name: "value",
Usage: "value set for the evm", Usage: "value set for the evm",
Value: new(big.Int), Value: new(big.Int),
Category: flags.VMCategory,
} }
DumpFlag = &cli.BoolFlag{ DumpFlag = &cli.BoolFlag{
Name: "dump", Name: "dump",
Usage: "dumps the state after the run", Usage: "dumps the state after the run",
Category: flags.VMCategory,
} }
InputFlag = &cli.StringFlag{ InputFlag = &cli.StringFlag{
Name: "input", Name: "input",
Usage: "input for the EVM", Usage: "input for the EVM",
Category: flags.VMCategory,
} }
InputFileFlag = &cli.StringFlag{ InputFileFlag = &cli.StringFlag{
Name: "inputfile", Name: "inputfile",
Usage: "file containing input for the EVM", Usage: "file containing input for the EVM",
Category: flags.VMCategory, }
VerbosityFlag = &cli.IntFlag{
Name: "verbosity",
Usage: "sets the verbosity level",
} }
BenchFlag = &cli.BoolFlag{ BenchFlag = &cli.BoolFlag{
Name: "bench", Name: "bench",
Usage: "benchmark the execution", Usage: "benchmark the execution",
Category: flags.VMCategory,
} }
CreateFlag = &cli.BoolFlag{ CreateFlag = &cli.BoolFlag{
Name: "create", Name: "create",
Usage: "indicates the action should be create rather than call", Usage: "indicates the action should be create rather than call",
Category: flags.VMCategory,
} }
GenesisFlag = &cli.StringFlag{ GenesisFlag = &cli.StringFlag{
Name: "prestate", Name: "prestate",
Usage: "JSON file with prestate (genesis) config", Usage: "JSON file with prestate (genesis) config",
Category: flags.VMCategory,
} }
MachineFlag = &cli.BoolFlag{ MachineFlag = &cli.BoolFlag{
Name: "json", Name: "json",
Usage: "output trace logs in machine readable format (json)", Usage: "output trace logs in machine readable format (json)",
Category: flags.VMCategory,
} }
SenderFlag = &cli.StringFlag{ SenderFlag = &cli.StringFlag{
Name: "sender", Name: "sender",
Usage: "The transaction origin", Usage: "The transaction origin",
Category: flags.VMCategory,
} }
ReceiverFlag = &cli.StringFlag{ ReceiverFlag = &cli.StringFlag{
Name: "receiver", Name: "receiver",
Usage: "The transaction receiver (execution context)", Usage: "The transaction receiver (execution context)",
Category: flags.VMCategory,
} }
DisableMemoryFlag = &cli.BoolFlag{ DisableMemoryFlag = &cli.BoolFlag{
Name: "nomemory", Name: "nomemory",
Value: true, Value: true,
Usage: "disable memory output", Usage: "disable memory output",
Category: flags.VMCategory,
} }
DisableStackFlag = &cli.BoolFlag{ DisableStackFlag = &cli.BoolFlag{
Name: "nostack", Name: "nostack",
Usage: "disable stack output", Usage: "disable stack output",
Category: flags.VMCategory,
} }
DisableStorageFlag = &cli.BoolFlag{ DisableStorageFlag = &cli.BoolFlag{
Name: "nostorage", Name: "nostorage",
Usage: "disable storage output", Usage: "disable storage output",
Category: flags.VMCategory,
} }
DisableReturnDataFlag = &cli.BoolFlag{ DisableReturnDataFlag = &cli.BoolFlag{
Name: "noreturndata", Name: "noreturndata",
Value: true, Value: true,
Usage: "enable return data output", Usage: "enable return data output",
Category: flags.VMCategory,
} }
) )
@ -192,38 +183,34 @@ var blockBuilderCommand = &cli.Command{
}, },
} }
// vmFlags contains flags related to running the EVM.
var vmFlags = []cli.Flag{
CodeFlag,
CodeFileFlag,
CreateFlag,
GasFlag,
PriceFlag,
ValueFlag,
InputFlag,
InputFileFlag,
GenesisFlag,
SenderFlag,
ReceiverFlag,
}
// traceFlags contains flags that configure tracing output.
var traceFlags = []cli.Flag{
BenchFlag,
DebugFlag,
DumpFlag,
MachineFlag,
StatDumpFlag,
DisableMemoryFlag,
DisableStackFlag,
DisableStorageFlag,
DisableReturnDataFlag,
}
var app = flags.NewApp("the evm command line interface") var app = flags.NewApp("the evm command line interface")
func init() { func init() {
app.Flags = flags.Merge(vmFlags, traceFlags, debug.Flags) app.Flags = []cli.Flag{
BenchFlag,
CreateFlag,
DebugFlag,
VerbosityFlag,
CodeFlag,
CodeFileFlag,
GasFlag,
PriceFlag,
ValueFlag,
DumpFlag,
InputFlag,
InputFileFlag,
MemProfileFlag,
CPUProfileFlag,
StatDumpFlag,
GenesisFlag,
MachineFlag,
SenderFlag,
ReceiverFlag,
DisableMemoryFlag,
DisableStackFlag,
DisableStorageFlag,
DisableReturnDataFlag,
}
app.Commands = []*cli.Command{ app.Commands = []*cli.Command{
compileCommand, compileCommand,
disasmCommand, disasmCommand,
@ -234,14 +221,6 @@ func init() {
transactionCommand, transactionCommand,
blockBuilderCommand, blockBuilderCommand,
} }
app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)
return debug.Setup(ctx)
}
app.After = func(ctx *cli.Context) error {
debug.Exit()
return nil
}
} }
func main() { func main() {

View file

@ -24,6 +24,7 @@ import (
"math/big" "math/big"
"os" "os"
goruntime "runtime" goruntime "runtime"
"runtime/pprof"
"testing" "testing"
"time" "time"
@ -33,10 +34,12 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/core/vm/runtime"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/triedb/hashdb" "github.com/ethereum/go-ethereum/trie/triedb/hashdb"
@ -49,7 +52,6 @@ var runCommand = &cli.Command{
Usage: "run arbitrary evm binary", Usage: "run arbitrary evm binary",
ArgsUsage: "<code>", ArgsUsage: "<code>",
Description: `The run command runs arbitrary EVM code.`, Description: `The run command runs arbitrary EVM code.`,
Flags: flags.Merge(vmFlags, traceFlags),
} }
// readGenesis will read the given JSON format genesis file and return // readGenesis will read the given JSON format genesis file and return
@ -107,6 +109,9 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) (output []by
} }
func runCmd(ctx *cli.Context) error { func runCmd(ctx *cli.Context) error {
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
log.Root().SetHandler(glogger)
logconfig := &logger.Config{ logconfig := &logger.Config{
EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), EnableMemory: !ctx.Bool(DisableMemoryFlag.Name),
DisableStack: ctx.Bool(DisableStackFlag.Name), DisableStack: ctx.Bool(DisableStackFlag.Name),
@ -116,14 +121,15 @@ func runCmd(ctx *cli.Context) error {
} }
var ( var (
tracer vm.EVMLogger tracer vm.EVMLogger
debugLogger *logger.StructLogger debugLogger *logger.StructLogger
statedb *state.StateDB statedb *state.StateDB
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
sender = common.BytesToAddress([]byte("sender")) sender = common.BytesToAddress([]byte("sender"))
receiver = common.BytesToAddress([]byte("receiver")) receiver = common.BytesToAddress([]byte("receiver"))
preimages = ctx.Bool(DumpFlag.Name) genesisConfig *core.Genesis
blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests preimages = ctx.Bool(DumpFlag.Name)
blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests
) )
if ctx.Bool(MachineFlag.Name) { if ctx.Bool(MachineFlag.Name) {
tracer = logger.NewJSONLogger(logconfig, os.Stdout) tracer = logger.NewJSONLogger(logconfig, os.Stdout)
@ -133,30 +139,30 @@ func runCmd(ctx *cli.Context) error {
} else { } else {
debugLogger = logger.NewStructLogger(logconfig) debugLogger = logger.NewStructLogger(logconfig)
} }
initialGas := ctx.Uint64(GasFlag.Name)
genesisConfig := new(core.Genesis)
genesisConfig.GasLimit = initialGas
if ctx.String(GenesisFlag.Name) != "" { if ctx.String(GenesisFlag.Name) != "" {
genesisConfig = readGenesis(ctx.String(GenesisFlag.Name)) gen := readGenesis(ctx.String(GenesisFlag.Name))
if genesisConfig.GasLimit != 0 { genesisConfig = gen
initialGas = genesisConfig.GasLimit db := rawdb.NewMemoryDatabase()
} triedb := trie.NewDatabase(db, &trie.Config{
Preimages: preimages,
HashDB: hashdb.Defaults,
})
defer triedb.Close()
genesis := gen.MustCommit(db, triedb)
sdb := state.NewDatabaseWithNodeDB(db, triedb)
statedb, _ = state.New(genesis.Root(), sdb, nil)
chainConfig = gen.Config
} else { } else {
genesisConfig.Config = params.AllEthashProtocolChanges db := rawdb.NewMemoryDatabase()
triedb := trie.NewDatabase(db, &trie.Config{
Preimages: preimages,
HashDB: hashdb.Defaults,
})
defer triedb.Close()
sdb := state.NewDatabaseWithNodeDB(db, triedb)
statedb, _ = state.New(types.EmptyRootHash, sdb, nil)
genesisConfig = new(core.Genesis)
} }
db := rawdb.NewMemoryDatabase()
triedb := trie.NewDatabase(db, &trie.Config{
Preimages: preimages,
HashDB: hashdb.Defaults,
})
defer triedb.Close()
genesis := genesisConfig.MustCommit(db, triedb)
sdb := state.NewDatabaseWithNodeDB(db, triedb)
statedb, _ = state.New(genesis.Root(), sdb, nil)
chainConfig = genesisConfig.Config
if ctx.String(SenderFlag.Name) != "" { if ctx.String(SenderFlag.Name) != "" {
sender = common.HexToAddress(ctx.String(SenderFlag.Name)) sender = common.HexToAddress(ctx.String(SenderFlag.Name))
} }
@ -210,6 +216,10 @@ func runCmd(ctx *cli.Context) error {
} }
code = common.Hex2Bytes(bin) code = common.Hex2Bytes(bin)
} }
initialGas := ctx.Uint64(GasFlag.Name)
if genesisConfig.GasLimit != 0 {
initialGas = genesisConfig.GasLimit
}
runtimeConfig := runtime.Config{ runtimeConfig := runtime.Config{
Origin: sender, Origin: sender,
State: statedb, State: statedb,
@ -226,6 +236,19 @@ func runCmd(ctx *cli.Context) error {
}, },
} }
if cpuProfilePath := ctx.String(CPUProfileFlag.Name); cpuProfilePath != "" {
f, err := os.Create(cpuProfilePath)
if err != nil {
fmt.Println("could not create CPU profile: ", err)
os.Exit(1)
}
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Println("could not start CPU profile: ", err)
os.Exit(1)
}
defer pprof.StopCPUProfile()
}
if chainConfig != nil { if chainConfig != nil {
runtimeConfig.ChainConfig = chainConfig runtimeConfig.ChainConfig = chainConfig
} else { } else {
@ -273,6 +296,19 @@ func runCmd(ctx *cli.Context) error {
fmt.Println(string(statedb.Dump(nil))) fmt.Println(string(statedb.Dump(nil)))
} }
if memProfilePath := ctx.String(MemProfileFlag.Name); memProfilePath != "" {
f, err := os.Create(memProfilePath)
if err != nil {
fmt.Println("could not create memory profile: ", err)
os.Exit(1)
}
if err := pprof.WriteHeapProfile(f); err != nil {
fmt.Println("could not write memory profile: ", err)
os.Exit(1)
}
f.Close()
}
if ctx.Bool(DebugFlag.Name) { if ctx.Bool(DebugFlag.Name) {
if debugLogger != nil { if debugLogger != nil {
fmt.Fprintln(os.Stderr, "#### TRACE ####") fmt.Fprintln(os.Stderr, "#### TRACE ####")

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -51,6 +52,11 @@ type StatetestResult struct {
} }
func stateTestCmd(ctx *cli.Context) error { func stateTestCmd(ctx *cli.Context) error {
// Configure the go-ethereum logger
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
log.Root().SetHandler(glogger)
// Configure the EVM logger // Configure the EVM logger
config := &logger.Config{ config := &logger.Config{
EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), EnableMemory: !ctx.Bool(DisableMemoryFlag.Name),

View file

@ -17,8 +17,6 @@
package asm package asm
import ( import (
"encoding/hex"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
@ -32,7 +30,7 @@ import (
// and holds the tokens for the program. // and holds the tokens for the program.
type Compiler struct { type Compiler struct {
tokens []token tokens []token
out []byte binary []interface{}
labels map[string]int labels map[string]int
@ -52,10 +50,12 @@ func NewCompiler(debug bool) *Compiler {
// Feed feeds tokens in to ch and are interpreted by // Feed feeds tokens in to ch and are interpreted by
// the compiler. // the compiler.
// //
// feed is the first pass in the compile stage as it collects the used labels in the // feed is the first pass in the compile stage as it
// program and keeps a program counter which is used to determine the locations of the // collects the used labels in the program and keeps a
// jump dests. The labels can than be used in the second stage to push labels and // program counter which is used to determine the locations
// determine the right position. // of the jump dests. The labels can than be used in the
// second stage to push labels and determine the right
// position.
func (c *Compiler) Feed(ch <-chan token) { func (c *Compiler) Feed(ch <-chan token) {
var prev token var prev token
for i := range ch { for i := range ch {
@ -79,6 +79,7 @@ func (c *Compiler) Feed(ch <-chan token) {
c.pc++ c.pc++
} }
} }
c.tokens = append(c.tokens, i) c.tokens = append(c.tokens, i)
prev = i prev = i
} }
@ -87,11 +88,12 @@ func (c *Compiler) Feed(ch <-chan token) {
} }
} }
// Compile compiles the current tokens and returns a binary string that can be interpreted // Compile compiles the current tokens and returns a
// by the EVM and an error if it failed. // binary string that can be interpreted by the EVM
// and an error if it failed.
// //
// compile is the second stage in the compile phase which compiles the tokens to EVM // compile is the second stage in the compile phase
// instructions. // which compiles the tokens to EVM instructions.
func (c *Compiler) Compile() (string, []error) { func (c *Compiler) Compile() (string, []error) {
var errors []error var errors []error
// continue looping over the tokens until // continue looping over the tokens until
@ -103,8 +105,16 @@ func (c *Compiler) Compile() (string, []error) {
} }
// turn the binary to hex // turn the binary to hex
h := hex.EncodeToString(c.out) var bin strings.Builder
return h, errors for _, v := range c.binary {
switch v := v.(type) {
case vm.OpCode:
bin.WriteString(fmt.Sprintf("%x", []byte{byte(v)}))
case []byte:
bin.WriteString(fmt.Sprintf("%x", v))
}
}
return bin.String(), errors
} }
// next returns the next token and increments the // next returns the next token and increments the
@ -146,114 +156,87 @@ func (c *Compiler) compileLine() error {
return nil return nil
} }
// parseNumber compiles the number to bytes // compileNumber compiles the number to bytes
func parseNumber(tok token) ([]byte, error) { func (c *Compiler) compileNumber(element token) {
if tok.typ != number { num := math.MustParseBig256(element.text).Bytes()
panic("parseNumber of non-number token") if len(num) == 0 {
num = []byte{0}
} }
num, ok := math.ParseBig256(tok.text) c.pushBin(num)
if !ok {
return nil, errors.New("invalid number")
}
bytes := num.Bytes()
if len(bytes) == 0 {
bytes = []byte{0}
}
return bytes, nil
} }
// compileElement compiles the element (push & label or both) // compileElement compiles the element (push & label or both)
// to a binary representation and may error if incorrect statements // to a binary representation and may error if incorrect statements
// where fed. // where fed.
func (c *Compiler) compileElement(element token) error { func (c *Compiler) compileElement(element token) error {
switch { // check for a jump. jumps must be read and compiled
case isJump(element.text): // from right to left.
return c.compileJump(element.text) if isJump(element.text) {
case isPush(element.text): rvalue := c.next()
return c.compilePush() switch rvalue.typ {
default: case number:
c.outputOpcode(toBinary(element.text)) // TODO figure out how to return the error properly
c.compileNumber(rvalue)
case stringValue:
// strings are quoted, remove them.
c.pushBin(rvalue.text[1 : len(rvalue.text)-2])
case label:
c.pushBin(vm.PUSH4)
pos := big.NewInt(int64(c.labels[rvalue.text])).Bytes()
pos = append(make([]byte, 4-len(pos)), pos...)
c.pushBin(pos)
case lineEnd:
c.pos--
default:
return compileErr(rvalue, rvalue.text, "number, string or label")
}
// push the operation
c.pushBin(toBinary(element.text))
return nil return nil
} } else if isPush(element.text) {
} // handle pushes. pushes are read from left to right.
var value []byte
func (c *Compiler) compileJump(jumpType string) error { rvalue := c.next()
rvalue := c.next() switch rvalue.typ {
switch rvalue.typ { case number:
case number: value = math.MustParseBig256(rvalue.text).Bytes()
numBytes, err := parseNumber(rvalue) if len(value) == 0 {
if err != nil { value = []byte{0}
return err }
case stringValue:
value = []byte(rvalue.text[1 : len(rvalue.text)-1])
case label:
value = big.NewInt(int64(c.labels[rvalue.text])).Bytes()
value = append(make([]byte, 4-len(value)), value...)
default:
return compileErr(rvalue, rvalue.text, "number, string or label")
} }
c.outputBytes(numBytes)
case stringValue: if len(value) > 32 {
// strings are quoted, remove them. return fmt.Errorf("%d type error: unsupported string or number with size > 32", rvalue.lineno)
str := rvalue.text[1 : len(rvalue.text)-2]
c.outputBytes([]byte(str))
case label:
c.outputOpcode(vm.PUSH4)
pos := big.NewInt(int64(c.labels[rvalue.text])).Bytes()
pos = append(make([]byte, 4-len(pos)), pos...)
c.outputBytes(pos)
case lineEnd:
// push without argument is supported, it just takes the destination from the stack.
c.pos--
default:
return compileErr(rvalue, rvalue.text, "number, string or label")
}
// push the operation
c.outputOpcode(toBinary(jumpType))
return nil
}
func (c *Compiler) compilePush() error {
// handle pushes. pushes are read from left to right.
var value []byte
rvalue := c.next()
switch rvalue.typ {
case number:
value = math.MustParseBig256(rvalue.text).Bytes()
if len(value) == 0 {
value = []byte{0}
} }
case stringValue:
value = []byte(rvalue.text[1 : len(rvalue.text)-1]) c.pushBin(vm.OpCode(int(vm.PUSH1) - 1 + len(value)))
case label: c.pushBin(value)
value = big.NewInt(int64(c.labels[rvalue.text])).Bytes() } else {
value = append(make([]byte, 4-len(value)), value...) c.pushBin(toBinary(element.text))
default:
return compileErr(rvalue, rvalue.text, "number, string or label")
} }
if len(value) > 32 {
return fmt.Errorf("%d: string or number size > 32 bytes", rvalue.lineno+1)
}
c.outputOpcode(vm.OpCode(int(vm.PUSH1) - 1 + len(value)))
c.outputBytes(value)
return nil return nil
} }
// compileLabel pushes a jumpdest to the binary slice. // compileLabel pushes a jumpdest to the binary slice.
func (c *Compiler) compileLabel() { func (c *Compiler) compileLabel() {
c.outputOpcode(vm.JUMPDEST) c.pushBin(vm.JUMPDEST)
} }
func (c *Compiler) outputOpcode(op vm.OpCode) { // pushBin pushes the value v to the binary stack.
func (c *Compiler) pushBin(v interface{}) {
if c.debug { if c.debug {
fmt.Printf("%d: %v\n", len(c.out), op) fmt.Printf("%d: %v\n", len(c.binary), v)
} }
c.out = append(c.out, byte(op)) c.binary = append(c.binary, v)
}
// output pushes the value v to the binary stack.
func (c *Compiler) outputBytes(b []byte) {
if c.debug {
fmt.Printf("%d: %x\n", len(c.out), b)
}
c.out = append(c.out, b...)
} }
// isPush returns whether the string op is either any of // isPush returns whether the string op is either any of
@ -280,13 +263,13 @@ type compileError struct {
} }
func (err compileError) Error() string { func (err compileError) Error() string {
return fmt.Sprintf("%d: syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want) return fmt.Sprintf("%d syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want)
} }
func compileErr(c token, got, want string) error { func compileErr(c token, got, want string) error {
return compileError{ return compileError{
got: got, got: got,
want: want, want: want,
lineno: c.lineno + 1, lineno: c.lineno,
} }
} }

View file

@ -54,14 +54,6 @@ func TestCompiler(t *testing.T) {
`, `,
output: "6300000006565b", output: "6300000006565b",
}, },
{
input: `
JUMP @label
label: ;; comment
ADD ;; comment
`,
output: "6300000006565b01",
},
} }
for _, test := range tests { for _, test := range tests {
ch := Lex([]byte(test.input), false) ch := Lex([]byte(test.input), false)

View file

@ -72,16 +72,6 @@ func TestLexer(t *testing.T) {
input: "@label123", input: "@label123",
tokens: []token{{typ: lineStart}, {typ: label, text: "label123"}, {typ: eof}}, tokens: []token{{typ: lineStart}, {typ: label, text: "label123"}, {typ: eof}},
}, },
// comment after label
{
input: "@label123 ;; comment",
tokens: []token{{typ: lineStart}, {typ: label, text: "label123"}, {typ: eof}},
},
// comment after instruction
{
input: "push 3 ;; comment\nadd",
tokens: []token{{typ: lineStart}, {typ: element, text: "push"}, {typ: number, text: "3"}, {typ: lineEnd, text: "\n"}, {typ: lineStart, lineno: 1}, {typ: element, lineno: 1, text: "add"}, {typ: eof, lineno: 1}},
},
} }
for _, test := range tests { for _, test := range tests {

View file

@ -42,8 +42,6 @@ type token struct {
// is able to parse and return. // is able to parse and return.
type tokenType int type tokenType int
//go:generate go run golang.org/x/tools/cmd/stringer -type tokenType
const ( const (
eof tokenType = iota // end of file eof tokenType = iota // end of file
lineStart // emitted when a line starts lineStart // emitted when a line starts
@ -54,13 +52,31 @@ const (
labelDef // label definition is emitted when a new label is found labelDef // label definition is emitted when a new label is found
number // number is emitted when a number is found number // number is emitted when a number is found
stringValue // stringValue is emitted when a string has been found stringValue // stringValue is emitted when a string has been found
Numbers = "1234567890" // characters representing any decimal number
HexadecimalNumbers = Numbers + "aAbBcCdDeEfF" // characters representing any hexadecimal
Alpha = "abcdefghijklmnopqrstuwvxyzABCDEFGHIJKLMNOPQRSTUWVXYZ" // characters representing alphanumeric
) )
const ( // String implements stringer
decimalNumbers = "1234567890" // characters representing any decimal number func (it tokenType) String() string {
hexNumbers = decimalNumbers + "aAbBcCdDeEfF" // characters representing any hexadecimal if int(it) > len(stringtokenTypes) {
alpha = "abcdefghijklmnopqrstuwvxyzABCDEFGHIJKLMNOPQRSTUWVXYZ" // characters representing alphanumeric return "invalid"
) }
return stringtokenTypes[it]
}
var stringtokenTypes = []string{
eof: "EOF",
lineStart: "new line",
lineEnd: "end of line",
invalidStatement: "invalid statement",
element: "element",
label: "label",
labelDef: "label definition",
number: "number",
stringValue: "string",
}
// lexer is the basic construct for parsing // lexer is the basic construct for parsing
// source code and turning them in to tokens. // source code and turning them in to tokens.
@ -184,6 +200,7 @@ func lexLine(l *lexer) stateFn {
l.emit(lineEnd) l.emit(lineEnd)
l.ignore() l.ignore()
l.lineno++ l.lineno++
l.emit(lineStart) l.emit(lineStart)
case r == ';' && l.peek() == ';': case r == ';' && l.peek() == ';':
return lexComment return lexComment
@ -208,7 +225,6 @@ func lexLine(l *lexer) stateFn {
// of the line and discards the text. // of the line and discards the text.
func lexComment(l *lexer) stateFn { func lexComment(l *lexer) stateFn {
l.acceptRunUntil('\n') l.acceptRunUntil('\n')
l.backup()
l.ignore() l.ignore()
return lexLine return lexLine
@ -218,7 +234,7 @@ func lexComment(l *lexer) stateFn {
// the lex text state function to advance the parsing // the lex text state function to advance the parsing
// process. // process.
func lexLabel(l *lexer) stateFn { func lexLabel(l *lexer) stateFn {
l.acceptRun(alpha + "_" + decimalNumbers) l.acceptRun(Alpha + "_" + Numbers)
l.emit(label) l.emit(label)
@ -237,9 +253,9 @@ func lexInsideString(l *lexer) stateFn {
} }
func lexNumber(l *lexer) stateFn { func lexNumber(l *lexer) stateFn {
acceptance := decimalNumbers acceptance := Numbers
if l.accept("xX") { if l.accept("xX") {
acceptance = hexNumbers acceptance = HexadecimalNumbers
} }
l.acceptRun(acceptance) l.acceptRun(acceptance)
@ -249,7 +265,7 @@ func lexNumber(l *lexer) stateFn {
} }
func lexElement(l *lexer) stateFn { func lexElement(l *lexer) stateFn {
l.acceptRun(alpha + "_" + decimalNumbers) l.acceptRun(Alpha + "_" + Numbers)
if l.peek() == ':' { if l.peek() == ':' {
l.emit(labelDef) l.emit(labelDef)

View file

@ -1,31 +0,0 @@
// Code generated by "stringer -type tokenType"; DO NOT EDIT.
package asm
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[eof-0]
_ = x[lineStart-1]
_ = x[lineEnd-2]
_ = x[invalidStatement-3]
_ = x[element-4]
_ = x[label-5]
_ = x[labelDef-6]
_ = x[number-7]
_ = x[stringValue-8]
}
const _tokenType_name = "eoflineStartlineEndinvalidStatementelementlabellabelDefnumberstringValue"
var _tokenType_index = [...]uint8{0, 3, 12, 19, 35, 42, 47, 55, 61, 72}
func (i tokenType) String() string {
if i < 0 || i >= tokenType(len(_tokenType_index)-1) {
return "tokenType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _tokenType_name[_tokenType_index[i]:_tokenType_index[i+1]]
}