--- /dev/null
+package vm
+
+import (
+ "bufio"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "math"
+ "strconv"
+ "strings"
+ "unicode"
+
+ "github.com/bytom/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",
+}