github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/core/asm/compiler.go (about) 1 package asm 2 3 import ( 4 "fmt" 5 "math/big" 6 "os" 7 "strings" 8 9 "github.com/quickchainproject/quickchain/common/math" 10 "github.com/quickchainproject/quickchain/core/vm" 11 ) 12 13 // Compiler contains information about the parsed source 14 // and holds the tokens for the program. 15 type Compiler struct { 16 tokens []token 17 binary []interface{} 18 19 labels map[string]int 20 21 pc, pos int 22 23 debug bool 24 } 25 26 // newCompiler returns a new allocated compiler. 27 func NewCompiler(debug bool) *Compiler { 28 return &Compiler{ 29 labels: make(map[string]int), 30 debug: debug, 31 } 32 } 33 34 // Feed feeds tokens in to ch and are interpreted by 35 // the compiler. 36 // 37 // feed is the first pass in the compile stage as it 38 // collect the used labels in the program and keeps a 39 // program counter which is used to determine the locations 40 // of the jump dests. The labels can than be used in the 41 // second stage to push labels and determine the right 42 // position. 43 func (c *Compiler) Feed(ch <-chan token) { 44 for i := range ch { 45 switch i.typ { 46 case number: 47 num := math.MustParseBig256(i.text).Bytes() 48 if len(num) == 0 { 49 num = []byte{0} 50 } 51 c.pc += len(num) 52 case stringValue: 53 c.pc += len(i.text) - 2 54 case element: 55 c.pc++ 56 case labelDef: 57 c.labels[i.text] = c.pc 58 c.pc++ 59 case label: 60 c.pc += 5 61 } 62 63 c.tokens = append(c.tokens, i) 64 } 65 if c.debug { 66 fmt.Fprintln(os.Stderr, "found", len(c.labels), "labels") 67 } 68 } 69 70 // Compile compiles the current tokens and returns a 71 // binary string that can be interpreted by the EVM 72 // and an error if it failed. 73 // 74 // compile is the second stage in the compile phase 75 // which compiles the tokens to EVM instructions. 76 func (c *Compiler) Compile() (string, []error) { 77 var errors []error 78 // continue looping over the tokens until 79 // the stack has been exhausted. 80 for c.pos < len(c.tokens) { 81 if err := c.compileLine(); err != nil { 82 errors = append(errors, err) 83 } 84 } 85 86 // turn the binary to hex 87 var bin string 88 for _, v := range c.binary { 89 switch v := v.(type) { 90 case vm.OpCode: 91 bin += fmt.Sprintf("%x", []byte{byte(v)}) 92 case []byte: 93 bin += fmt.Sprintf("%x", v) 94 } 95 } 96 return bin, errors 97 } 98 99 // next returns the next token and increments the 100 // position. 101 func (c *Compiler) next() token { 102 token := c.tokens[c.pos] 103 c.pos++ 104 return token 105 } 106 107 // compile line compiles a single line instruction e.g. 108 // "push 1", "jump @label". 109 func (c *Compiler) compileLine() error { 110 n := c.next() 111 if n.typ != lineStart { 112 return compileErr(n, n.typ.String(), lineStart.String()) 113 } 114 115 lvalue := c.next() 116 switch lvalue.typ { 117 case eof: 118 return nil 119 case element: 120 if err := c.compileElement(lvalue); err != nil { 121 return err 122 } 123 case labelDef: 124 c.compileLabel() 125 case lineEnd: 126 return nil 127 default: 128 return compileErr(lvalue, lvalue.text, fmt.Sprintf("%v or %v", labelDef, element)) 129 } 130 131 if n := c.next(); n.typ != lineEnd { 132 return compileErr(n, n.text, lineEnd.String()) 133 } 134 135 return nil 136 } 137 138 // compileNumber compiles the number to bytes 139 func (c *Compiler) compileNumber(element token) (int, error) { 140 num := math.MustParseBig256(element.text).Bytes() 141 if len(num) == 0 { 142 num = []byte{0} 143 } 144 c.pushBin(num) 145 return len(num), nil 146 } 147 148 // compileElement compiles the element (push & label or both) 149 // to a binary representation and may error if incorrect statements 150 // where fed. 151 func (c *Compiler) compileElement(element token) error { 152 // check for a jump. jumps must be read and compiled 153 // from right to left. 154 if isJump(element.text) { 155 rvalue := c.next() 156 switch rvalue.typ { 157 case number: 158 // TODO figure out how to return the error properly 159 c.compileNumber(rvalue) 160 case stringValue: 161 // strings are quoted, remove them. 162 c.pushBin(rvalue.text[1 : len(rvalue.text)-2]) 163 case label: 164 c.pushBin(vm.PUSH4) 165 pos := big.NewInt(int64(c.labels[rvalue.text])).Bytes() 166 pos = append(make([]byte, 4-len(pos)), pos...) 167 c.pushBin(pos) 168 default: 169 return compileErr(rvalue, rvalue.text, "number, string or label") 170 } 171 // push the operation 172 c.pushBin(toBinary(element.text)) 173 return nil 174 } else if isPush(element.text) { 175 // handle pushes. pushes are read from left to right. 176 var value []byte 177 178 rvalue := c.next() 179 switch rvalue.typ { 180 case number: 181 value = math.MustParseBig256(rvalue.text).Bytes() 182 if len(value) == 0 { 183 value = []byte{0} 184 } 185 case stringValue: 186 value = []byte(rvalue.text[1 : len(rvalue.text)-1]) 187 case label: 188 value = make([]byte, 4) 189 copy(value, big.NewInt(int64(c.labels[rvalue.text])).Bytes()) 190 default: 191 return compileErr(rvalue, rvalue.text, "number, string or label") 192 } 193 194 if len(value) > 32 { 195 return fmt.Errorf("%d type error: unsupported string or number with size > 32", rvalue.lineno) 196 } 197 198 c.pushBin(vm.OpCode(int(vm.PUSH1) - 1 + len(value))) 199 c.pushBin(value) 200 } else { 201 c.pushBin(toBinary(element.text)) 202 } 203 204 return nil 205 } 206 207 // compileLabel pushes a jumpdest to the binary slice. 208 func (c *Compiler) compileLabel() { 209 c.pushBin(vm.JUMPDEST) 210 } 211 212 // pushBin pushes the value v to the binary stack. 213 func (c *Compiler) pushBin(v interface{}) { 214 if c.debug { 215 fmt.Printf("%d: %v\n", len(c.binary), v) 216 } 217 c.binary = append(c.binary, v) 218 } 219 220 // isPush returns whether the string op is either any of 221 // push(N). 222 func isPush(op string) bool { 223 return strings.ToUpper(op) == "PUSH" 224 } 225 226 // isJump returns whether the string op is jump(i) 227 func isJump(op string) bool { 228 return strings.ToUpper(op) == "JUMPI" || strings.ToUpper(op) == "JUMP" 229 } 230 231 // toBinary converts text to a vm.OpCode 232 func toBinary(text string) vm.OpCode { 233 return vm.StringToOp(strings.ToUpper(text)) 234 } 235 236 type compileError struct { 237 got string 238 want string 239 240 lineno int 241 } 242 243 func (err compileError) Error() string { 244 return fmt.Sprintf("%d syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want) 245 } 246 247 func compileErr(c token, got, want string) error { 248 return compileError{ 249 got: got, 250 want: want, 251 lineno: c.lineno, 252 } 253 }