13 "github.com/vapor/errors"
16 // Assemble converts a string like "2 3 ADD 5 NUMEQUAL" into 0x525393559c.
17 // The input should not include PUSHDATA (or OP_<num>) ops; those will
19 // Input may include jump-target labels of the form $foo, which can
20 // then be used as JUMP:$foo or JUMPIF:$foo.
21 func Assemble(s string) (res []byte, err error) {
22 // maps labels to the location each refers to
23 locations := make(map[string]uint32)
25 // maps unresolved uses of labels to the locations that need to be filled in
26 unresolved := make(map[string][]int)
28 handleJump := func(addrStr string, opcode Op) error {
29 res = append(res, byte(opcode))
33 res = append(res, fourBytes[:]...)
35 if strings.HasPrefix(addrStr, "$") {
36 unresolved[addrStr] = append(unresolved[addrStr], l)
40 address, err := strconv.ParseUint(addrStr, 10, 32)
44 binary.LittleEndian.PutUint32(res[l:], uint32(address))
48 scanner := bufio.NewScanner(strings.NewReader(s))
51 token := scanner.Text()
52 if info, ok := opsByName[token]; ok {
53 if strings.HasPrefix(token, "PUSHDATA") || strings.HasPrefix(token, "JUMP") {
54 return nil, errors.Wrap(ErrToken, token)
56 res = append(res, byte(info.op))
57 } else if strings.HasPrefix(token, "JUMP:") {
58 // TODO (Dan): add IF/ELSE/ENDIF and BEGIN/WHILE/REPEAT
59 err = handleJump(strings.TrimPrefix(token, "JUMP:"), OP_JUMP)
63 } else if strings.HasPrefix(token, "JUMPIF:") {
64 err = handleJump(strings.TrimPrefix(token, "JUMPIF:"), OP_JUMPIF)
68 } else if strings.HasPrefix(token, "$") {
69 if _, seen := locations[token]; seen {
70 return nil, fmt.Errorf("label %s redefined", token)
72 if len(res) > math.MaxInt32 {
73 return nil, fmt.Errorf("program too long")
75 locations[token] = uint32(len(res))
76 } else if strings.HasPrefix(token, "0x") {
77 bytes, err := hex.DecodeString(strings.TrimPrefix(token, "0x"))
81 res = append(res, PushdataBytes(bytes)...)
82 } else if len(token) >= 2 && token[0] == '\'' && token[len(token)-1] == '\'' {
83 bytes := make([]byte, 0, len(token)-2)
85 for i := 1; i < len(token)-1; i++ {
89 bytes = append(bytes, token[i])
92 res = append(res, PushdataBytes(bytes)...)
93 } else if num, err := strconv.ParseInt(token, 10, 64); err == nil {
94 res = append(res, PushdataInt64(num)...)
96 return nil, errors.Wrap(ErrToken, token)
104 for label, uses := range unresolved {
105 location, ok := locations[label]
107 return nil, fmt.Errorf("undefined label %s", label)
109 for _, use := range uses {
110 binary.LittleEndian.PutUint32(res[use:], location)
117 func Disassemble(prog []byte) (string, error) {
121 // maps program locations (used as jump targets) to a label for each
122 labels = make(map[uint32]string)
125 // first pass: look for jumps
126 for i := uint32(0); i < uint32(len(prog)); {
127 inst, err := ParseOp(prog, i)
132 case OP_JUMP, OP_JUMPIF:
133 addr := binary.LittleEndian.Uint32(inst.Data)
134 if _, ok := labels[addr]; !ok {
135 labelNum := len(labels)
136 label := words[labelNum%len(words)]
137 if labelNum >= len(words) {
138 label += fmt.Sprintf("%d", labelNum/len(words)+1)
143 insts = append(insts, inst)
152 for _, inst := range insts {
153 if label, ok := labels[loc]; ok {
154 strs = append(strs, "$"+label)
159 case OP_JUMP, OP_JUMPIF:
160 addr := binary.LittleEndian.Uint32(inst.Data)
161 str = fmt.Sprintf("%s:$%s", inst.Op.String(), labels[addr])
163 if len(inst.Data) > 0 {
164 str = fmt.Sprintf("0x%x", inst.Data)
166 str = inst.Op.String()
169 strs = append(strs, str)
174 if label, ok := labels[loc]; ok {
175 strs = append(strs, "$"+label)
178 return strings.Join(strs, " "), nil
181 // split is a bufio.SplitFunc for scanning the input to Compile.
182 // It starts like bufio.ScanWords but adjusts the return value to
183 // account for quoted strings.
184 func split(inp []byte, atEOF bool) (advance int, token []byte, err error) {
185 advance, token, err = bufio.ScanWords(inp, atEOF)
189 if len(token) > 1 && token[0] != '\'' {
193 for ; start < len(inp); start++ {
194 if !unicode.IsSpace(rune(inp[start])) {
198 if start == len(inp) || inp[start] != '\'' {
202 for i := start + 1; i < len(inp); i++ {
209 token = inp[start:advance]
216 // Reached the end of the input with no closing quote.
218 return 0, nil, ErrToken
223 var words = []string{
224 "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel",
225 "india", "juliet", "kilo", "lima", "mike", "november", "oscar", "papa",
226 "quebec", "romeo", "sierra", "tango", "uniform", "victor", "whisky", "xray",