OSDN Git Service

833c50bf9eabb9616446dc44125a16645a25509c
[bytom/vapor.git] / protocol / vm / assemble.go
1 package vm
2
3 import (
4         "bufio"
5         "encoding/binary"
6         "encoding/hex"
7         "fmt"
8         "math"
9         "strconv"
10         "strings"
11         "unicode"
12
13         "github.com/vapor/errors"
14 )
15
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
18 // be inferred.
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)
24
25         // maps unresolved uses of labels to the locations that need to be filled in
26         unresolved := make(map[string][]int)
27
28         handleJump := func(addrStr string, opcode Op) error {
29                 res = append(res, byte(opcode))
30                 l := len(res)
31
32                 var fourBytes [4]byte
33                 res = append(res, fourBytes[:]...)
34
35                 if strings.HasPrefix(addrStr, "$") {
36                         unresolved[addrStr] = append(unresolved[addrStr], l)
37                         return nil
38                 }
39
40                 address, err := strconv.ParseUint(addrStr, 10, 32)
41                 if err != nil {
42                         return err
43                 }
44                 binary.LittleEndian.PutUint32(res[l:], uint32(address))
45                 return nil
46         }
47
48         scanner := bufio.NewScanner(strings.NewReader(s))
49         scanner.Split(split)
50         for scanner.Scan() {
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)
55                         }
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)
60                         if err != nil {
61                                 return nil, err
62                         }
63                 } else if strings.HasPrefix(token, "JUMPIF:") {
64                         err = handleJump(strings.TrimPrefix(token, "JUMPIF:"), OP_JUMPIF)
65                         if err != nil {
66                                 return nil, err
67                         }
68                 } else if strings.HasPrefix(token, "$") {
69                         if _, seen := locations[token]; seen {
70                                 return nil, fmt.Errorf("label %s redefined", token)
71                         }
72                         if len(res) > math.MaxInt32 {
73                                 return nil, fmt.Errorf("program too long")
74                         }
75                         locations[token] = uint32(len(res))
76                 } else if strings.HasPrefix(token, "0x") {
77                         bytes, err := hex.DecodeString(strings.TrimPrefix(token, "0x"))
78                         if err != nil {
79                                 return nil, err
80                         }
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)
84                         var b int
85                         for i := 1; i < len(token)-1; i++ {
86                                 if token[i] == '\\' {
87                                         i++
88                                 }
89                                 bytes = append(bytes, token[i])
90                                 b++
91                         }
92                         res = append(res, PushdataBytes(bytes)...)
93                 } else if num, err := strconv.ParseInt(token, 10, 64); err == nil {
94                         res = append(res, PushdataInt64(num)...)
95                 } else {
96                         return nil, errors.Wrap(ErrToken, token)
97                 }
98         }
99         err = scanner.Err()
100         if err != nil {
101                 return nil, err
102         }
103
104         for label, uses := range unresolved {
105                 location, ok := locations[label]
106                 if !ok {
107                         return nil, fmt.Errorf("undefined label %s", label)
108                 }
109                 for _, use := range uses {
110                         binary.LittleEndian.PutUint32(res[use:], location)
111                 }
112         }
113
114         return res, nil
115 }
116
117 func Disassemble(prog []byte) (string, error) {
118         var (
119                 insts []Instruction
120
121                 // maps program locations (used as jump targets) to a label for each
122                 labels = make(map[uint32]string)
123         )
124
125         // first pass: look for jumps
126         for i := uint32(0); i < uint32(len(prog)); {
127                 inst, err := ParseOp(prog, i)
128                 if err != nil {
129                         return "", err
130                 }
131                 switch inst.Op {
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)
139                                 }
140                                 labels[addr] = label
141                         }
142                 }
143                 insts = append(insts, inst)
144                 i += inst.Len
145         }
146
147         var (
148                 loc  uint32
149                 strs []string
150         )
151
152         for _, inst := range insts {
153                 if label, ok := labels[loc]; ok {
154                         strs = append(strs, "$"+label)
155                 }
156
157                 var str string
158                 switch inst.Op {
159                 case OP_JUMP, OP_JUMPIF:
160                         addr := binary.LittleEndian.Uint32(inst.Data)
161                         str = fmt.Sprintf("%s:$%s", inst.Op.String(), labels[addr])
162                 default:
163                         if len(inst.Data) > 0 {
164                                 str = fmt.Sprintf("0x%x", inst.Data)
165                         } else {
166                                 str = inst.Op.String()
167                         }
168                 }
169                 strs = append(strs, str)
170
171                 loc += inst.Len
172         }
173
174         if label, ok := labels[loc]; ok {
175                 strs = append(strs, "$"+label)
176         }
177
178         return strings.Join(strs, " "), nil
179 }
180
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)
186         if err != nil {
187                 return
188         }
189         if len(token) > 1 && token[0] != '\'' {
190                 return
191         }
192         var start int
193         for ; start < len(inp); start++ {
194                 if !unicode.IsSpace(rune(inp[start])) {
195                         break
196                 }
197         }
198         if start == len(inp) || inp[start] != '\'' {
199                 return
200         }
201         var escape bool
202         for i := start + 1; i < len(inp); i++ {
203                 if escape {
204                         escape = false
205                 } else {
206                         switch inp[i] {
207                         case '\'':
208                                 advance = i + 1
209                                 token = inp[start:advance]
210                                 return
211                         case '\\':
212                                 escape = true
213                         }
214                 }
215         }
216         // Reached the end of the input with no closing quote.
217         if atEOF {
218                 return 0, nil, ErrToken
219         }
220         return 0, nil, nil
221 }
222
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",
227         "yankee", "zulu",
228 }