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