+++ /dev/null
-package vm
-
-import (
- "bufio"
- "encoding/binary"
- "encoding/hex"
- "fmt"
- "math"
- "strconv"
- "strings"
- "unicode"
-
- "github.com/vapor/errors"
-)
-
-// Assemble converts a string like "2 3 ADD 5 NUMEQUAL" into 0x525393559c.
-// The input should not include PUSHDATA (or OP_<num>) ops; those will
-// be inferred.
-// Input may include jump-target labels of the form $foo, which can
-// then be used as JUMP:$foo or JUMPIF:$foo.
-func Assemble(s string) (res []byte, err error) {
- // maps labels to the location each refers to
- locations := make(map[string]uint32)
-
- // maps unresolved uses of labels to the locations that need to be filled in
- unresolved := make(map[string][]int)
-
- handleJump := func(addrStr string, opcode Op) error {
- res = append(res, byte(opcode))
- l := len(res)
-
- var fourBytes [4]byte
- res = append(res, fourBytes[:]...)
-
- if strings.HasPrefix(addrStr, "$") {
- unresolved[addrStr] = append(unresolved[addrStr], l)
- return nil
- }
-
- address, err := strconv.ParseUint(addrStr, 10, 32)
- if err != nil {
- return err
- }
- binary.LittleEndian.PutUint32(res[l:], uint32(address))
- return nil
- }
-
- scanner := bufio.NewScanner(strings.NewReader(s))
- scanner.Split(split)
- for scanner.Scan() {
- token := scanner.Text()
- if info, ok := opsByName[token]; ok {
- if strings.HasPrefix(token, "PUSHDATA") || strings.HasPrefix(token, "JUMP") {
- return nil, errors.Wrap(ErrToken, token)
- }
- res = append(res, byte(info.op))
- } else if strings.HasPrefix(token, "JUMP:") {
- // TODO (Dan): add IF/ELSE/ENDIF and BEGIN/WHILE/REPEAT
- err = handleJump(strings.TrimPrefix(token, "JUMP:"), OP_JUMP)
- if err != nil {
- return nil, err
- }
- } else if strings.HasPrefix(token, "JUMPIF:") {
- err = handleJump(strings.TrimPrefix(token, "JUMPIF:"), OP_JUMPIF)
- if err != nil {
- return nil, err
- }
- } else if strings.HasPrefix(token, "$") {
- if _, seen := locations[token]; seen {
- return nil, fmt.Errorf("label %s redefined", token)
- }
- if len(res) > math.MaxInt32 {
- return nil, fmt.Errorf("program too long")
- }
- locations[token] = uint32(len(res))
- } else if strings.HasPrefix(token, "0x") {
- bytes, err := hex.DecodeString(strings.TrimPrefix(token, "0x"))
- if err != nil {
- return nil, err
- }
- res = append(res, PushdataBytes(bytes)...)
- } else if len(token) >= 2 && token[0] == '\'' && token[len(token)-1] == '\'' {
- bytes := make([]byte, 0, len(token)-2)
- var b int
- for i := 1; i < len(token)-1; i++ {
- if token[i] == '\\' {
- i++
- }
- bytes = append(bytes, token[i])
- b++
- }
- res = append(res, PushdataBytes(bytes)...)
- } else if num, err := strconv.ParseInt(token, 10, 64); err == nil {
- res = append(res, PushdataInt64(num)...)
- } else {
- return nil, errors.Wrap(ErrToken, token)
- }
- }
- err = scanner.Err()
- if err != nil {
- return nil, err
- }
-
- for label, uses := range unresolved {
- location, ok := locations[label]
- if !ok {
- return nil, fmt.Errorf("undefined label %s", label)
- }
- for _, use := range uses {
- binary.LittleEndian.PutUint32(res[use:], location)
- }
- }
-
- return res, nil
-}
-
-func Disassemble(prog []byte) (string, error) {
- var (
- insts []Instruction
-
- // maps program locations (used as jump targets) to a label for each
- labels = make(map[uint32]string)
- )
-
- // first pass: look for jumps
- for i := uint32(0); i < uint32(len(prog)); {
- inst, err := ParseOp(prog, i)
- if err != nil {
- return "", err
- }
- switch inst.Op {
- case OP_JUMP, OP_JUMPIF:
- addr := binary.LittleEndian.Uint32(inst.Data)
- if _, ok := labels[addr]; !ok {
- labelNum := len(labels)
- label := words[labelNum%len(words)]
- if labelNum >= len(words) {
- label += fmt.Sprintf("%d", labelNum/len(words)+1)
- }
- labels[addr] = label
- }
- }
- insts = append(insts, inst)
- i += inst.Len
- }
-
- var (
- loc uint32
- strs []string
- )
-
- for _, inst := range insts {
- if label, ok := labels[loc]; ok {
- strs = append(strs, "$"+label)
- }
-
- var str string
- switch inst.Op {
- case OP_JUMP, OP_JUMPIF:
- addr := binary.LittleEndian.Uint32(inst.Data)
- str = fmt.Sprintf("%s:$%s", inst.Op.String(), labels[addr])
- default:
- if len(inst.Data) > 0 {
- str = fmt.Sprintf("0x%x", inst.Data)
- } else {
- str = inst.Op.String()
- }
- }
- strs = append(strs, str)
-
- loc += inst.Len
- }
-
- if label, ok := labels[loc]; ok {
- strs = append(strs, "$"+label)
- }
-
- return strings.Join(strs, " "), nil
-}
-
-// split is a bufio.SplitFunc for scanning the input to Compile.
-// It starts like bufio.ScanWords but adjusts the return value to
-// account for quoted strings.
-func split(inp []byte, atEOF bool) (advance int, token []byte, err error) {
- advance, token, err = bufio.ScanWords(inp, atEOF)
- if err != nil {
- return
- }
- if len(token) > 1 && token[0] != '\'' {
- return
- }
- var start int
- for ; start < len(inp); start++ {
- if !unicode.IsSpace(rune(inp[start])) {
- break
- }
- }
- if start == len(inp) || inp[start] != '\'' {
- return
- }
- var escape bool
- for i := start + 1; i < len(inp); i++ {
- if escape {
- escape = false
- } else {
- switch inp[i] {
- case '\'':
- advance = i + 1
- token = inp[start:advance]
- return
- case '\\':
- escape = true
- }
- }
- }
- // Reached the end of the input with no closing quote.
- if atEOF {
- return 0, nil, ErrToken
- }
- return 0, nil, nil
-}
-
-var words = []string{
- "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel",
- "india", "juliet", "kilo", "lima", "mike", "november", "oscar", "papa",
- "quebec", "romeo", "sierra", "tango", "uniform", "victor", "whisky", "xray",
- "yankee", "zulu",
-}