asm.go (8693B)
1 package asm 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 "io" 8 "log" 9 "math" 10 "strconv" 11 "strings" 12 13 "github.com/alecthomas/participle/v2" 14 "github.com/alecthomas/participle/v2/lexer" 15 16 "git.defalsify.org/vise.git/vm" 17 ) 18 19 20 // Asm assembles bytecode from the vise assembly mini-language. 21 type Asm struct { 22 Instructions []*Instruction `@@*` 23 } 24 25 // Arg holds all parsed argument elements of a single line of assembly code. 26 type Arg struct { 27 Sym *string `(@Sym Whitespace?)?` 28 Size *uint32 `(@Size Whitespace?)?` 29 Flag *uint8 `(@Size Whitespace?)?` 30 Selector *string `(@Sym Whitespace?)?` 31 Desc *string `(@Sym Whitespace?)?` 32 //Desc *string `(Quote ((@Sym | @Size) @Whitespace?)+ Quote Whitespace?)?` 33 } 34 35 func flush(b *bytes.Buffer, w io.Writer) (int, error) { 36 if w != nil { 37 return w.Write(b.Bytes()) 38 } 39 return 0, nil 40 } 41 42 func parseDescType(b *bytes.Buffer, arg Arg) (int, error) { 43 var rn int 44 var err error 45 46 log.Printf("desctype") 47 var selector string 48 if arg.Flag != nil { 49 selector = strconv.FormatUint(uint64(*arg.Flag), 10) 50 } else if arg.Selector != nil { 51 selector = *arg.Selector 52 } 53 54 var size string 55 if arg.Size != nil { 56 size = strconv.FormatUint(uint64(*arg.Size), 10) 57 n, err := writeSym(b, size) 58 rn += n 59 if err != nil { 60 return rn, err 61 } 62 } 63 64 if arg.Sym != nil { 65 log.Printf(">>>> sym") 66 n, err := writeSym(b, *arg.Sym) 67 rn += n 68 if err != nil { 69 return rn, err 70 } 71 } 72 73 if selector != "" { 74 n, err := writeSym(b, *arg.Selector) 75 rn += n 76 if err != nil { 77 return rn, err 78 } 79 } 80 81 n, err := writeSym(b, *arg.Desc) 82 rn += n 83 if err != nil { 84 return rn, err 85 } 86 87 return rn, nil 88 } 89 90 func parseTwoSym(b *bytes.Buffer, arg Arg) (int, error) { 91 var rn int 92 93 var selector string 94 var sym string 95 if arg.Size != nil { 96 selector = strconv.FormatUint(uint64(*arg.Size), 10) 97 //sym = *arg.Selector 98 sym = *arg.Sym 99 } else if arg.Selector != nil { 100 if *arg.Sym == "*" { 101 sym = *arg.Selector 102 selector = *arg.Sym 103 } else { 104 sym = *arg.Sym 105 selector = *arg.Selector 106 } 107 } 108 109 110 n, err := writeSym(b, sym) 111 rn += n 112 if err != nil { 113 return rn, err 114 } 115 116 n, err = writeSym(b, selector) 117 rn += n 118 if err != nil { 119 return rn, err 120 } 121 return rn, nil 122 } 123 124 func parseTwoSymReverse(b *bytes.Buffer, arg Arg) (int, error) { 125 var rn int 126 127 sym := *arg.Selector 128 selector := *arg.Sym 129 n, err := writeSym(b, selector) 130 rn += n 131 if err != nil { 132 return rn, err 133 } 134 135 n, err = writeSym(b, sym) 136 rn += n 137 if err != nil { 138 return rn, err 139 } 140 141 return rn, nil 142 } 143 144 func parseSig(b *bytes.Buffer, arg Arg) (int, error) { 145 var rn int 146 147 n, err := writeSym(b, *arg.Sym) 148 rn += n 149 if err != nil { 150 return rn, err 151 } 152 153 n, err = writeSize(b, *arg.Size) 154 rn += n 155 if err != nil { 156 return rn, err 157 } 158 159 n, err = b.Write([]byte{uint8(*arg.Flag)}) 160 rn += n 161 if err != nil { 162 return rn, err 163 } 164 165 return rn, nil 166 } 167 168 func parseSized(b *bytes.Buffer, arg Arg) (int, error) { 169 var rn int 170 171 n, err := writeSym(b, *arg.Sym) 172 rn += n 173 if err != nil { 174 return rn, err 175 } 176 177 n, err = writeSize(b, *arg.Size) 178 rn += n 179 if err != nil { 180 return rn, err 181 } 182 183 return rn, nil 184 } 185 186 func parseOne(op vm.Opcode, instruction *Instruction, w io.Writer) (int, error) { 187 a := instruction.OpArg 188 var n_buf int 189 var n_out int 190 191 b := bytes.NewBuffer(nil) 192 193 n, err := writeOpcode(b, op) 194 n_buf += n 195 if err != nil { 196 return n_out, err 197 } 198 199 // Catch Menu batch commands 200 if a.Desc != nil { 201 n, err := parseDescType(b, a) 202 n_buf += n 203 if err != nil { 204 return n_out, err 205 } 206 return flush(b, w) 207 } 208 209 // Catch 210 if a.Selector != nil { 211 log.Printf("have selector %v", instruction) 212 var n int 213 var err error 214 if op == vm.MOUT { 215 n, err = parseTwoSymReverse(b, a) 216 } else { 217 n, err = parseTwoSym(b, a) 218 } 219 n_buf += n 220 if err != nil { 221 return n_out, err 222 } 223 return flush(b, w) 224 } 225 226 // Catch CATCH, LOAD and twosyms with integer-as-string 227 if a.Size != nil { 228 log.Printf("have size %v", instruction) 229 if a.Flag != nil { 230 n, err := parseSig(b, a) 231 n_buf += n 232 if err != nil { 233 return n_out, err 234 } 235 } else if op == vm.LOAD { 236 n, err := parseSized(b, a) 237 n_buf += n 238 if err != nil { 239 return n_out, err 240 } 241 } else { 242 n, err := parseTwoSym(b, a) 243 n_buf += n 244 if err != nil { 245 return n_out, err 246 } 247 248 } 249 return flush(b, w) 250 } 251 252 // Catch HALT 253 if a.Sym == nil { 254 return flush(b, w) 255 } 256 257 n, err = writeSym(b, *a.Sym) 258 n_buf += n 259 return flush(b, w) 260 } 261 262 // String implements the String interface. 263 func (a Arg) String() string { 264 s := "[Arg]" 265 if a.Sym != nil { 266 s += " Sym: " + *a.Sym 267 } 268 if a.Size != nil { 269 s += fmt.Sprintf(" Size: %v", *a.Size) 270 } 271 if a.Flag != nil { 272 s += fmt.Sprintf(" Flag: %v", *a.Flag) 273 } 274 if a.Selector != nil { 275 s += " Selector: " + *a.Selector 276 } 277 if a.Desc != nil { 278 s += " Description: " + *a.Desc 279 } 280 281 return fmt.Sprintf(s) 282 } 283 284 // Instruction represents one full line of assembly code. 285 type Instruction struct { 286 OpCode string `@Ident` 287 OpArg Arg `(Whitespace @@)?` 288 Comment string `Comment? EOL` 289 } 290 291 // String implement the String interface. 292 func (i Instruction) String() string { 293 return fmt.Sprintf("%s %s", i.OpCode, i.OpArg) 294 } 295 296 var ( 297 asmLexer = lexer.MustSimple([]lexer.SimpleRule{ 298 {"Comment", `(?:#)[^\n]*`}, 299 {"Ident", `^[A-Z]+`}, 300 {"Size", `[0-9]+`}, 301 {"Sym", `[a-zA-Z_\*\.\^\<\>][a-zA-Z0-9_]*`}, 302 {"Whitespace", `[ \t]+`}, 303 {"EOL", `[\n\r]+`}, 304 {"Quote", `["']`}, 305 }) 306 asmParser = participle.MustBuild[Asm]( 307 participle.Lexer(asmLexer), 308 participle.Elide("Comment", "Whitespace"), 309 ) 310 ) 311 312 func numSize(n uint32) int { 313 v := math.Log2(float64(n)) 314 return int((v / 8) + 1) 315 } 316 317 func writeOpcode(w *bytes.Buffer, op vm.Opcode) (int, error) { 318 bn := [2]byte{} 319 binary.BigEndian.PutUint16(bn[:], uint16(op)) 320 n, err := w.Write(bn[:]) 321 return n, err 322 } 323 324 func writeSym(w *bytes.Buffer, s string) (int, error) { 325 sz := len(s) 326 if sz > 255 { 327 return 0, fmt.Errorf("string size %v too big", sz) 328 } 329 w.Write([]byte{byte(sz)}) 330 return w.WriteString(s) 331 } 332 333 func writeDisplay(w *bytes.Buffer, s string) (int, error) { 334 s = strings.Trim(s, "\"'") 335 sz := len(s) 336 if sz > 255 { 337 return 0, fmt.Errorf("string size %v too big", sz) 338 } 339 w.Write([]byte{byte(sz)}) 340 return w.WriteString(s) 341 } 342 func writeSize(w *bytes.Buffer, n uint32) (int, error) { 343 if n == 0 { 344 return w.Write([]byte{0x01, 0x00}) 345 } 346 bn := [4]byte{} 347 sz := numSize(n) 348 if sz > 4 { 349 return 0, fmt.Errorf("number size %v too big", sz) 350 } 351 w.Write([]byte{byte(sz)}) 352 binary.BigEndian.PutUint32(bn[:], n) 353 c := 4-sz 354 return w.Write(bn[c:]) 355 } 356 357 // Batcher handles assembly commands that generates multiple instructions, such as menu navigation commands. 358 type Batcher struct { 359 menuProcessor MenuProcessor 360 inMenu bool 361 } 362 363 // NewBatcher creates a new Batcher objcet. 364 func NewBatcher(mp MenuProcessor) Batcher { 365 return Batcher{ 366 menuProcessor: NewMenuProcessor(), 367 } 368 } 369 370 // MenuExit generates the instructions for the batch and writes them to the given io.Writer. 371 func(bt *Batcher) MenuExit(w io.Writer) (int, error) { 372 if !bt.inMenu { 373 return 0, nil 374 } 375 bt.inMenu = false 376 b := bt.menuProcessor.ToLines() 377 return w.Write(b) 378 } 379 380 // MenuAdd adds a new menu instruction to the batcher. 381 func(bt *Batcher) MenuAdd(w io.Writer, code string, arg Arg) (int, error) { 382 bt.inMenu = true 383 var selector string 384 var sym string 385 var display string 386 if arg.Desc != nil { 387 sym = *arg.Sym 388 display = *arg.Desc 389 selector = *arg.Selector 390 } else if arg.Size != nil { 391 if arg.Sym != nil { 392 sym = *arg.Sym 393 } 394 selector = strconv.FormatUint(uint64(*arg.Size), 10) 395 display = *arg.Selector 396 } else { 397 selector = *arg.Sym 398 display = *arg.Selector 399 } 400 log.Printf("menu processor add %v '%v' '%v' '%v'", code, selector, display, sym) 401 err := bt.menuProcessor.Add(code, selector, display, sym) 402 return 0, err 403 } 404 405 // Exit is a synonym for MenuExit 406 func(bt *Batcher) Exit(w io.Writer) (int, error) { 407 return bt.MenuExit(w) 408 } 409 410 // Parse one or more lines of assembly code, and write assembled bytecode to the provided writer. 411 func Parse(s string, w io.Writer) (int, error) { 412 rd := strings.NewReader(s) 413 ast, err := asmParser.Parse("file", rd) 414 if err != nil { 415 return 0, err 416 } 417 418 batch := Batcher{ 419 420 } 421 422 var rn int 423 for _, v := range ast.Instructions { 424 log.Printf("parsing line %v: %v", v.OpCode, v.OpArg) 425 op, ok := vm.OpcodeIndex[v.OpCode] 426 if !ok { 427 n, err := batch.MenuAdd(w, v.OpCode, v.OpArg) 428 rn += n 429 if err != nil { 430 return rn, err 431 } 432 } else { 433 n, err := batch.MenuExit(w) 434 if err != nil { 435 return rn, err 436 } 437 rn += n 438 n, err = parseOne(op, v, w) 439 rn += n 440 if err != nil { 441 return rn, err 442 } 443 log.Printf("wrote %v bytes for %v", n, v.OpArg) 444 } 445 } 446 n, err := batch.Exit(w) 447 rn += n 448 if err != nil { 449 return rn, err 450 } 451 rn += n 452 453 return rn, err 454 }