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