cmd/evm: move compiler to evm

This commit is contained in:
Jeffrey Wilcke 2017-02-20 20:40:56 +01:00
parent fb729ece61
commit 24763cf054
No known key found for this signature in database
GPG key ID: 63FF149CD6945F9E
14 changed files with 342 additions and 270 deletions

View file

@ -1 +0,0 @@
push 256

View file

@ -1,8 +0,0 @@
jump @main
push 1
other:
push 0
push 0
mstore
main:
push 1

View file

@ -1,2 +0,0 @@
push "hellooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo"

View file

@ -1,17 +0,0 @@
jump @main
my_label:
push 0
push 1
mstore
push 0
push 5
push 10
log1
stop
main:
push 1
push 1
eq
jumpi @my_label

View file

@ -1,3 +0,0 @@
push 1
push 2
add

View file

@ -1,3 +0,0 @@
push "hello"
push 0
mstore

View file

@ -1,20 +0,0 @@
package main
import "testing"
func lexAll(src string) []item {
ch := lex("test.asm", []byte(src), false)
var tokens []item
for i := range ch {
tokens = append(tokens, i)
}
return tokens
}
func TestComment(t *testing.T) {
tokens := lexAll(";; this is a comment")
if len(tokens) != 2 { // {new line, EOF}
t.Error("expected no tokens")
}
}

55
cmd/evm/compiler.go Normal file
View file

@ -0,0 +1,55 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"errors"
"fmt"
"io/ioutil"
"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
cli "gopkg.in/urfave/cli.v1"
)
var compileCommand = cli.Command{
Action: compileCmd,
Name: "compile",
Usage: "compiles easm source to evm binary",
ArgsUsage: "<file>",
}
func compileCmd(ctx *cli.Context) error {
debug := ctx.GlobalBool(DebugFlag.Name)
if len(ctx.Args().First()) == 0 {
return errors.New("filename required")
}
fn := ctx.Args().First()
src, err := ioutil.ReadFile(fn)
if err != nil {
return err
}
bin, err := compiler.Compile(fn, src, debug)
if err != nil {
return err
}
fmt.Println(bin)
return nil
}

View file

@ -0,0 +1,39 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package compiler
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/core/asm"
)
func Compile(fn string, src []byte, debug bool) (string, error) {
compiler := asm.NewCompiler(debug)
compiler.Feed(asm.Lex(fn, src, debug))
bin, compileErrors := compiler.Compile()
if len(compileErrors) > 0 {
// report errors
for _, err := range compileErrors {
fmt.Printf("%s:%v\n", fn, err)
}
return "", errors.New("compiling failed")
}
return bin, nil
}

View file

@ -18,21 +18,11 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil"
"math/big" "math/big"
"os" "os"
goruntime "runtime"
"time"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm/runtime"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
@ -109,113 +99,10 @@ func init() {
InputFlag, InputFlag,
DisableGasMeteringFlag, DisableGasMeteringFlag,
} }
app.Action = run app.Commands = []cli.Command{
compileCommand,
runCommand,
} }
func run(ctx *cli.Context) error {
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name)))
log.Root().SetHandler(glogger)
var (
db, _ = ethdb.NewMemDatabase()
statedb, _ = state.New(common.Hash{}, db)
address = common.StringToAddress("sender")
sender = vm.AccountRef(address)
)
statedb.CreateAccount(common.StringToAddress("sender"))
logger := vm.NewStructLogger(nil)
tstart := time.Now()
var (
code []byte
ret []byte
err error
)
if ctx.GlobalString(CodeFlag.Name) != "" {
code = common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name))
} else {
var hexcode []byte
if ctx.GlobalString(CodeFileFlag.Name) != "" {
var err error
hexcode, err = ioutil.ReadFile(ctx.GlobalString(CodeFileFlag.Name))
if err != nil {
fmt.Printf("Could not load code from file: %v\n", err)
os.Exit(1)
}
} else {
var err error
hexcode, err = ioutil.ReadAll(os.Stdin)
if err != nil {
fmt.Printf("Could not load code from stdin: %v\n", err)
os.Exit(1)
}
}
code = common.Hex2Bytes(string(bytes.TrimRight(hexcode, "\n")))
}
if ctx.GlobalBool(CreateFlag.Name) {
input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...)
ret, _, err = runtime.Create(input, &runtime.Config{
Origin: sender.Address(),
State: statedb,
GasLimit: ctx.GlobalUint64(GasFlag.Name),
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
Value: utils.GlobalBig(ctx, ValueFlag.Name),
EVMConfig: vm.Config{
Tracer: logger,
Debug: ctx.GlobalBool(DebugFlag.Name),
DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
},
})
} else {
receiverAddress := common.StringToAddress("receiver")
statedb.CreateAccount(receiverAddress)
statedb.SetCode(receiverAddress, code)
ret, err = runtime.Call(receiverAddress, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtime.Config{
Origin: sender.Address(),
State: statedb,
GasLimit: ctx.GlobalUint64(GasFlag.Name),
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
Value: utils.GlobalBig(ctx, ValueFlag.Name),
EVMConfig: vm.Config{
Tracer: logger,
Debug: ctx.GlobalBool(DebugFlag.Name),
DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
},
})
}
vmdone := time.Since(tstart)
if ctx.GlobalBool(DumpFlag.Name) {
statedb.Commit(true)
fmt.Println(string(statedb.Dump()))
}
vm.StdErrFormat(logger.StructLogs())
if ctx.GlobalBool(SysStatFlag.Name) {
var mem goruntime.MemStats
goruntime.ReadMemStats(&mem)
fmt.Printf("vm took %v\n", vmdone)
fmt.Printf(`alloc: %d
tot alloc: %d
no. malloc: %d
heap alloc: %d
heap objs: %d
num gc: %d
`, mem.Alloc, mem.TotalAlloc, mem.Mallocs, mem.HeapAlloc, mem.HeapObjects, mem.NumGC)
}
fmt.Printf("OUT: 0x%x", ret)
if err != nil {
fmt.Printf(" error: %v", err)
}
fmt.Println()
return nil
} }
func main() { func main() {

145
cmd/evm/runner.go Normal file
View file

@ -0,0 +1,145 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"time"
goruntime "runtime"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm/runtime"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
cli "gopkg.in/urfave/cli.v1"
)
var runCommand = cli.Command{
Action: runCmd,
Name: "run",
Usage: "run arbitrary evm binary",
ArgsUsage: "<code>",
Description: `The run command runs arbitrary EVM code.`,
}
func runCmd(ctx *cli.Context) error {
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name)))
log.Root().SetHandler(glogger)
var (
db, _ = ethdb.NewMemDatabase()
statedb, _ = state.New(common.Hash{}, db)
sender = common.StringToAddress("sender")
logger = vm.NewStructLogger(nil)
tstart = time.Now()
)
statedb.CreateAccount(sender)
var (
code []byte
ret []byte
err error
)
if ctx.GlobalString(CodeFlag.Name) != "" {
code = common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name))
} else {
var hexcode []byte
if ctx.GlobalString(CodeFileFlag.Name) != "" {
var err error
hexcode, err = ioutil.ReadFile(ctx.GlobalString(CodeFileFlag.Name))
if err != nil {
fmt.Printf("Could not load code from file: %v\n", err)
os.Exit(1)
}
} else {
var err error
hexcode, err = ioutil.ReadAll(os.Stdin)
if err != nil {
fmt.Printf("Could not load code from stdin: %v\n", err)
os.Exit(1)
}
}
code = common.Hex2Bytes(string(bytes.TrimRight(hexcode, "\n")))
}
if ctx.GlobalBool(CreateFlag.Name) {
input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...)
ret, _, err = runtime.Create(input, &runtime.Config{
Origin: sender,
State: statedb,
GasLimit: ctx.GlobalUint64(GasFlag.Name),
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
Value: utils.GlobalBig(ctx, ValueFlag.Name),
EVMConfig: vm.Config{
Tracer: logger,
Debug: ctx.GlobalBool(DebugFlag.Name),
DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
},
})
} else {
receiver := common.StringToAddress("receiver")
statedb.SetCode(receiver, code)
ret, err = runtime.Call(receiver, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtime.Config{
Origin: sender,
State: statedb,
GasLimit: ctx.GlobalUint64(GasFlag.Name),
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
Value: utils.GlobalBig(ctx, ValueFlag.Name),
EVMConfig: vm.Config{
Tracer: logger,
Debug: ctx.GlobalBool(DebugFlag.Name),
DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
},
})
}
vmdone := time.Since(tstart)
if ctx.GlobalBool(DumpFlag.Name) {
statedb.Commit(true)
fmt.Println(string(statedb.Dump()))
}
vm.StdErrFormat(logger.StructLogs())
if ctx.GlobalBool(SysStatFlag.Name) {
var mem goruntime.MemStats
goruntime.ReadMemStats(&mem)
fmt.Printf("vm took %v\n", vmdone)
fmt.Printf(`alloc: %d
tot alloc: %d
no. malloc: %d
heap alloc: %d
heap objs: %d
num gc: %d
`, mem.Alloc, mem.TotalAlloc, mem.Mallocs, mem.HeapAlloc, mem.HeapObjects, mem.NumGC)
}
fmt.Printf("OUT: 0x%x", ret)
if err != nil {
fmt.Printf(" error: %v", err)
}
fmt.Println()
return nil
}

View file

@ -1,84 +1,32 @@
package main // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package asm
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"math/big" "math/big"
"os" "os"
"path/filepath"
"strings" "strings"
cli "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/logger/glog"
) )
var (
app *cli.App
DebugFlag = cli.BoolFlag{
Name: "debug",
Usage: "outputs lexer and compiler debug output",
}
LexFlag = cli.BoolFlag{
Name: "lex",
Usage: "displays lex token output",
}
)
func init() {
app = cli.NewApp()
app.Name = filepath.Base(os.Args[0])
app.Author = ""
app.Email = ""
app.Version = "0.1"
app.Flags = []cli.Flag{
DebugFlag,
}
app.Action = run
}
func run(ctx *cli.Context) {
var (
debug = ctx.GlobalBool(DebugFlag.Name)
lexDebug = ctx.GlobalBool(LexFlag.Name)
)
if len(ctx.Args()) == 0 {
glog.Exitln("err: <filename> required")
}
fn := ctx.Args().First()
src, err := ioutil.ReadFile(fn)
if err != nil {
glog.Exitln("err:", err)
}
compiler := newCompiler(debug)
compiler.feed(lex(fn, src, lexDebug))
bin, errors := compiler.compile()
if len(errors) > 0 {
// report errors
for _, err := range errors {
fmt.Printf("%s:%v\n", fn, err)
}
os.Exit(1)
}
fmt.Println(bin)
}
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// Compiler contains information about the parsed source // Compiler contains information about the parsed source
// and holds the tokens for the program. // and holds the tokens for the program.
type Compiler struct { type Compiler struct {
@ -93,14 +41,14 @@ type Compiler struct {
} }
// newCompiler returns a new allocated compiler. // newCompiler returns a new allocated compiler.
func newCompiler(debug bool) *Compiler { func NewCompiler(debug bool) *Compiler {
return &Compiler{ return &Compiler{
labels: make(map[string]int), labels: make(map[string]int),
debug: debug, debug: debug,
} }
} }
// 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 // feed is the first pass in the compile stage as it
@ -109,11 +57,11 @@ func newCompiler(debug bool) *Compiler {
// of the jump dests. The labels can than be used in the // of the jump dests. The labels can than be used in the
// second stage to push labels and determine the right // second stage to push labels and determine the right
// position. // position.
func (c *Compiler) feed(ch <-chan token) { func (c *Compiler) Feed(ch <-chan token) {
for i := range ch { for i := range ch {
switch i.typ { switch i.typ {
case number: case number:
num := common.String2Big(i.text).Bytes() num := math.MustParseBig256(i.text).Bytes()
if len(num) == 0 { if len(num) == 0 {
num = []byte{0} num = []byte{0}
} }
@ -132,17 +80,17 @@ func (c *Compiler) feed(ch <-chan token) {
c.tokens = append(c.tokens, i) c.tokens = append(c.tokens, i)
} }
if c.debug { if c.debug {
fmt.Sprintln(os.Stderr, "found", len(c.labels), "labels") fmt.Fprintln(os.Stderr, "found", len(c.labels), "labels")
} }
} }
// compile compiles the current tokens and returns a // Compile compiles the current tokens and returns a
// binary string that can be interpreted by the EVM // binary string that can be interpreted by the EVM
// and an error if it failed. // and an error if it failed.
// //
// compile is the second stage in the compile phase // compile is the second stage in the compile phase
// which compiles the tokens to EVM 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
// the stack has been exhausted. // the stack has been exhausted.
@ -206,7 +154,7 @@ func (c *Compiler) compileLine() error {
// compileNumber compiles the number to bytes // compileNumber compiles the number to bytes
func (c *Compiler) compileNumber(element token) (int, error) { func (c *Compiler) compileNumber(element token) (int, error) {
num := common.String2Big(element.text).Bytes() num := math.MustParseBig256(element.text).Bytes()
if len(num) == 0 { if len(num) == 0 {
num = []byte{0} num = []byte{0}
} }
@ -247,7 +195,7 @@ func (c *Compiler) compileElement(element token) error {
rvalue := c.next() rvalue := c.next()
switch rvalue.typ { switch rvalue.typ {
case number: case number:
value = common.String2Big(rvalue.text).Bytes() value = math.MustParseBig256(rvalue.text).Bytes()
if len(value) == 0 { if len(value) == 0 {
value = []byte{0} value = []byte{0}
} }

36
core/asm/lex_test.go Normal file
View file

@ -0,0 +1,36 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package asm
import "testing"
func lexAll(src string) []token {
ch := Lex("test.asm", []byte(src), false)
var tokens []token
for i := range ch {
tokens = append(tokens, i)
}
return tokens
}
func TestComment(t *testing.T) {
tokens := lexAll(";; this is a comment")
if len(tokens) != 2 { // {new line, EOF}
t.Error("expected no tokens")
}
}

View file

@ -1,4 +1,20 @@
package main // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package asm
import ( import (
"fmt" "fmt"
@ -11,7 +27,7 @@ import (
// stateFn is used through the lifetime of the // stateFn is used through the lifetime of the
// lexer to parse the different values at the // lexer to parse the different values at the
// current state. // current state.
type stateFn func(*Lexer) stateFn type stateFn func(*lexer) stateFn
// token is emitted when the lexer has discovered // token is emitted when the lexer has discovered
// a new parsable token. These are delivered over // a new parsable token. These are delivered over
@ -62,10 +78,10 @@ var stringtokenTypes = []string{
stringValue: "string", 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.
// Tokens are interpreted by the compiler. // Tokens are interpreted by the compiler.
type Lexer struct { type lexer struct {
input string // input contains the source code of the program input string // input contains the source code of the program
tokens chan token // tokens is used to deliver tokens to the listener tokens chan token // tokens is used to deliver tokens to the listener
@ -79,9 +95,9 @@ type Lexer struct {
// lex lexes the program by name with the given source. It returns a // lex lexes the program by name with the given source. It returns a
// channel on which the tokens are delivered. // channel on which the tokens are delivered.
func lex(name string, source []byte, debug bool) <-chan token { func Lex(name string, source []byte, debug bool) <-chan token {
ch := make(chan token) ch := make(chan token)
l := &Lexer{ l := &lexer{
input: string(source), input: string(source),
tokens: ch, tokens: ch,
state: lexLine, state: lexLine,
@ -100,7 +116,7 @@ func lex(name string, source []byte, debug bool) <-chan token {
} }
// next returns the next rune in the program's source. // next returns the next rune in the program's source.
func (l *Lexer) next() (rune rune) { func (l *lexer) next() (rune rune) {
if l.pos >= len(l.input) { if l.pos >= len(l.input) {
l.width = 0 l.width = 0
return 0 return 0
@ -111,24 +127,24 @@ func (l *Lexer) next() (rune rune) {
} }
// backup backsup the last parsed element (multi-character) // backup backsup the last parsed element (multi-character)
func (l *Lexer) backup() { func (l *lexer) backup() {
l.pos -= l.width l.pos -= l.width
} }
// peek returns the next rune but does not advance the seeker // peek returns the next rune but does not advance the seeker
func (l *Lexer) peek() rune { func (l *lexer) peek() rune {
r := l.next() r := l.next()
l.backup() l.backup()
return r return r
} }
// ignore advances the seeker and ignores the value // ignore advances the seeker and ignores the value
func (l *Lexer) ignore() { func (l *lexer) ignore() {
l.start = l.pos l.start = l.pos
} }
// Accepts checks whether the given input matches the next rune // Accepts checks whether the given input matches the next rune
func (l *Lexer) accept(valid string) bool { func (l *lexer) accept(valid string) bool {
if strings.IndexRune(valid, l.next()) >= 0 { if strings.IndexRune(valid, l.next()) >= 0 {
return true return true
} }
@ -140,7 +156,7 @@ func (l *Lexer) accept(valid string) bool {
// acceptRun will continue to advance the seeker until valid // acceptRun will continue to advance the seeker until valid
// can no longer be met. // can no longer be met.
func (l *Lexer) acceptRun(valid string) { func (l *lexer) acceptRun(valid string) {
for strings.IndexRune(valid, l.next()) >= 0 { for strings.IndexRune(valid, l.next()) >= 0 {
} }
l.backup() l.backup()
@ -148,7 +164,7 @@ func (l *Lexer) acceptRun(valid string) {
// acceptRunUntil is the inverse of acceptRun and will continue // acceptRunUntil is the inverse of acceptRun and will continue
// to advance the seeker until the rune has been found. // to advance the seeker until the rune has been found.
func (l *Lexer) acceptRunUntil(until rune) bool { func (l *lexer) acceptRunUntil(until rune) bool {
// Continues running until a rune is found // Continues running until a rune is found
for i := l.next(); strings.IndexRune(string(until), i) == -1; i = l.next() { for i := l.next(); strings.IndexRune(string(until), i) == -1; i = l.next() {
if i == 0 { if i == 0 {
@ -160,12 +176,12 @@ func (l *Lexer) acceptRunUntil(until rune) bool {
} }
// blob returns the current value // blob returns the current value
func (l *Lexer) blob() string { func (l *lexer) blob() string {
return l.input[l.start:l.pos] return l.input[l.start:l.pos]
} }
// Emits a new token on to token channel for processing // Emits a new token on to token channel for processing
func (l *Lexer) emit(t tokenType) { func (l *lexer) emit(t tokenType) {
token := token{t, l.lineno, l.blob()} token := token{t, l.lineno, l.blob()}
if l.debug { if l.debug {
@ -177,7 +193,7 @@ func (l *Lexer) emit(t tokenType) {
} }
// lexLine is state function for lexing lines // lexLine is state function for lexing lines
func lexLine(l *Lexer) stateFn { func lexLine(l *lexer) stateFn {
for { for {
switch r := l.next(); { switch r := l.next(); {
case r == '\n': case r == '\n':
@ -207,7 +223,7 @@ func lexLine(l *Lexer) stateFn {
// lexComment parses the current position until the end // lexComment parses the current position until the end
// 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.ignore() l.ignore()
@ -217,7 +233,7 @@ func lexComment(l *Lexer) stateFn {
// lexLabel parses the current label, emits and returns // lexLabel parses the current label, emits and returns
// 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 + "_") l.acceptRun(Alpha + "_")
l.emit(label) l.emit(label)
@ -228,7 +244,7 @@ func lexLabel(l *Lexer) stateFn {
// lexInsideString lexes the inside of a string until // lexInsideString lexes the inside of a string until
// until the state function finds the closing quote. // until the state function finds the closing quote.
// It returns the lex text state function. // It returns the lex text state function.
func lexInsideString(l *Lexer) stateFn { func lexInsideString(l *lexer) stateFn {
if l.acceptRunUntil('"') { if l.acceptRunUntil('"') {
l.emit(stringValue) l.emit(stringValue)
} }
@ -236,7 +252,7 @@ func lexInsideString(l *Lexer) stateFn {
return lexLine return lexLine
} }
func lexNumber(l *Lexer) stateFn { func lexNumber(l *lexer) stateFn {
acceptance := Numbers acceptance := Numbers
if l.accept("0") && l.accept("xX") { if l.accept("0") && l.accept("xX") {
acceptance = HexadecimalNumbers acceptance = HexadecimalNumbers
@ -248,7 +264,7 @@ func lexNumber(l *Lexer) stateFn {
return lexLine return lexLine
} }
func lexElement(l *Lexer) stateFn { func lexElement(l *lexer) stateFn {
l.acceptRun(Alpha + "_" + Numbers) l.acceptRun(Alpha + "_" + Numbers)
if l.peek() == ':' { if l.peek() == ':' {