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