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