vise/asm/asm.go

405 lines
7.6 KiB
Go
Raw Permalink Normal View History

2023-04-04 11:32:39 +02:00
package asm
import (
2023-04-04 21:32:40 +02:00
"bytes"
"encoding/binary"
2023-04-04 11:32:39 +02:00
"fmt"
"io"
2023-04-05 09:59:24 +02:00
"log"
2023-04-04 21:32:40 +02:00
"math"
2023-04-05 13:06:13 +02:00
"strconv"
2023-04-04 11:32:39 +02:00
"strings"
"github.com/alecthomas/participle/v2"
"github.com/alecthomas/participle/v2/lexer"
2023-04-04 11:38:01 +02:00
"git.defalsify.org/festive/vm"
2023-04-04 11:32:39 +02:00
)
2023-04-04 11:38:01 +02:00
2023-04-12 19:04:36 +02:00
// Asm assembles bytecode from the festive assembly mini-language.
2023-04-04 11:32:39 +02:00
type Asm struct {
Instructions []*Instruction `@@*`
}
2023-04-12 19:04:36 +02:00
// Arg holds all parsed argument elements of a single line of assembly code.
2023-04-05 13:06:13 +02:00
type Arg struct {
Sym *string `(@Sym Whitespace?)?`
Size *uint32 `(@Size Whitespace?)?`
Flag *uint8 `(@Size Whitespace?)?`
Selector *string `(@Sym Whitespace?)?`
Desc *string `(Quote ((@Sym | @Size) @Whitespace?)+ Quote Whitespace?)?`
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
func flush(b *bytes.Buffer, w io.Writer) (int, error) {
if w != nil {
return w.Write(b.Bytes())
}
return 0, nil
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
func parseDescType(b *bytes.Buffer, arg Arg) (int, error) {
var rn int
var err error
2023-04-06 09:38:30 +02:00
2023-04-05 13:06:13 +02:00
var selector string
if arg.Flag != nil {
selector = strconv.FormatUint(uint64(*arg.Flag), 10)
} else if arg.Selector != nil {
selector = *arg.Selector
}
2023-04-04 11:32:39 +02:00
2023-04-06 09:38:30 +02:00
var size string
if arg.Size != nil {
size = strconv.FormatUint(uint64(*arg.Size), 10)
n, err := writeSym(b, size)
rn += n
if err != nil {
return rn, err
}
2023-04-05 13:06:13 +02:00
}
2023-04-06 09:38:30 +02:00
if arg.Sym != nil {
2023-04-05 13:06:13 +02:00
n, err := writeSym(b, *arg.Sym)
rn += n
if err != nil {
return rn, err
}
}
2023-04-04 11:32:39 +02:00
2023-04-06 09:38:30 +02:00
if selector != "" {
n, err := writeSym(b, *arg.Selector)
rn += n
if err != nil {
return rn, err
}
}
n, err := writeSym(b, *arg.Desc)
2023-04-05 13:06:13 +02:00
rn += n
if err != nil {
return rn, err
}
return rn, nil
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
func parseTwoSym(b *bytes.Buffer, arg Arg) (int, error) {
var rn int
2023-04-06 09:38:30 +02:00
var selector string
var sym string
if arg.Size != nil {
selector = strconv.FormatUint(uint64(*arg.Size), 10)
sym = *arg.Selector
} else if arg.Selector != nil {
sym = *arg.Sym
selector = *arg.Selector
}
n, err := writeSym(b, selector)
2023-04-05 13:06:13 +02:00
rn += n
if err != nil {
return rn, err
}
2023-04-06 09:38:30 +02:00
n, err = writeSym(b, sym)
2023-04-05 13:06:13 +02:00
rn += n
if err != nil {
return rn, err
}
return rn, nil
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
func parseSig(b *bytes.Buffer, arg Arg) (int, error) {
var rn int
n, err := writeSym(b, *arg.Sym)
rn += n
if err != nil {
return rn, err
}
n, err = writeSize(b, *arg.Size)
rn += n
if err != nil {
return rn, err
}
n, err = b.Write([]byte{uint8(*arg.Flag)})
rn += n
if err != nil {
return rn, err
}
return rn, nil
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
func parseSized(b *bytes.Buffer, arg Arg) (int, error) {
var rn int
n, err := writeSym(b, *arg.Sym)
rn += n
if err != nil {
return rn, err
}
n, err = writeSize(b, *arg.Size)
rn += n
if err != nil {
return rn, err
}
return rn, nil
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
func parseOne(op vm.Opcode, instruction *Instruction, w io.Writer) (int, error) {
a := instruction.OpArg
var n_buf int
var n_out int
b := bytes.NewBuffer(nil)
n, err := writeOpcode(b, op)
n_buf += n
if err != nil {
return n_out, err
}
2023-04-06 09:38:30 +02:00
// Catch MOUT
2023-04-05 13:06:13 +02:00
if a.Desc != nil {
n, err := parseDescType(b, a)
n_buf += n
if err != nil {
return n_out, err
}
return flush(b, w)
}
2023-04-06 09:38:30 +02:00
// Catch
2023-04-05 13:06:13 +02:00
if a.Selector != nil {
2023-04-06 09:38:30 +02:00
log.Printf("entering twosym for %v", op)
2023-04-05 13:06:13 +02:00
n, err := parseTwoSym(b, a)
n_buf += n
if err != nil {
return n_out, err
}
return flush(b, w)
}
2023-04-06 09:38:30 +02:00
// Catch CATCH, LOAD
2023-04-05 13:06:13 +02:00
if a.Size != nil {
if a.Flag != nil {
n, err := parseSig(b, a)
n_buf += n
if err != nil {
return n_out, err
}
} else {
n, err := parseSized(b, a)
n_buf += n
if err != nil {
return n_out, err
}
}
return flush(b, w)
}
2023-04-06 09:38:30 +02:00
// Catch HALT
if a.Sym == nil {
return flush(b, w)
}
2023-04-05 13:06:13 +02:00
n, err = writeSym(b, *a.Sym)
n_buf += n
return flush(b, w)
2023-04-04 11:32:39 +02:00
}
2023-04-12 19:04:36 +02:00
// String implements the String interface.
2023-04-04 11:32:39 +02:00
func (a Arg) String() string {
2023-04-05 13:06:13 +02:00
s := "[Arg]"
if a.Sym != nil {
s += " Sym: " + *a.Sym
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
if a.Size != nil {
s += fmt.Sprintf(" Size: %v", *a.Size)
2023-04-05 09:59:24 +02:00
}
2023-04-05 13:06:13 +02:00
if a.Flag != nil {
s += fmt.Sprintf(" Flag: %v", *a.Flag)
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
if a.Selector != nil {
s += " Selector: " + *a.Selector
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
if a.Desc != nil {
s += " Description: " + *a.Desc
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
return fmt.Sprintf(s)
2023-04-04 11:32:39 +02:00
}
2023-04-12 19:04:36 +02:00
// Instruction represents one full line of assembly code.
2023-04-04 11:32:39 +02:00
type Instruction struct {
OpCode string `@Ident`
2023-04-05 13:06:13 +02:00
OpArg Arg `(Whitespace @@)?`
Comment string `Comment? EOL`
2023-04-04 11:32:39 +02:00
}
2023-04-12 19:04:36 +02:00
// String implement the String interface.
2023-04-04 11:32:39 +02:00
func (i Instruction) String() string {
return fmt.Sprintf("%s %s", i.OpCode, i.OpArg)
}
var (
asmLexer = lexer.MustSimple([]lexer.SimpleRule{
2023-04-05 13:06:13 +02:00
{"Comment", `(?:#)[^\n]*`},
2023-04-04 11:32:39 +02:00
{"Ident", `^[A-Z]+`},
{"Size", `[0-9]+`},
2023-04-05 13:06:13 +02:00
{"Sym", `[a-zA-Z_][a-zA-Z0-9_]*`},
2023-04-05 09:59:24 +02:00
{"Whitespace", `[ \t]+`},
{"EOL", `[\n\r]+`},
2023-04-04 11:32:39 +02:00
{"Quote", `["']`},
})
asmParser = participle.MustBuild[Asm](
participle.Lexer(asmLexer),
participle.Elide("Comment", "Whitespace"),
)
)
2023-04-04 21:32:40 +02:00
func numSize(n uint32) int {
v := math.Log2(float64(n))
2023-04-06 09:38:30 +02:00
return int((v / 8) + 1)
2023-04-04 21:32:40 +02:00
}
2023-04-05 13:06:13 +02:00
func writeOpcode(w *bytes.Buffer, op vm.Opcode) (int, error) {
2023-04-04 21:32:40 +02:00
bn := [2]byte{}
binary.BigEndian.PutUint16(bn[:], uint16(op))
n, err := w.Write(bn[:])
return n, err
}
2023-04-05 13:06:13 +02:00
func writeSym(w *bytes.Buffer, s string) (int, error) {
2023-04-04 21:32:40 +02:00
sz := len(s)
if sz > 255 {
return 0, fmt.Errorf("string size %v too big", sz)
}
w.Write([]byte{byte(sz)})
return w.WriteString(s)
}
2023-04-05 13:06:13 +02:00
func writeDisplay(w *bytes.Buffer, s string) (int, error) {
2023-04-04 22:02:17 +02:00
s = strings.Trim(s, "\"'")
sz := len(s)
if sz > 255 {
return 0, fmt.Errorf("string size %v too big", sz)
}
w.Write([]byte{byte(sz)})
return w.WriteString(s)
}
2023-04-05 13:06:13 +02:00
func writeSize(w *bytes.Buffer, n uint32) (int, error) {
2023-04-04 21:32:40 +02:00
bn := [4]byte{}
sz := numSize(n)
if sz > 4 {
return 0, fmt.Errorf("number size %v too big", sz)
}
w.Write([]byte{byte(sz)})
binary.BigEndian.PutUint32(bn[:], n)
c := 4-sz
2023-04-05 09:59:24 +02:00
return w.Write(bn[c:])
2023-04-04 21:32:40 +02:00
}
2023-04-12 19:04:36 +02:00
// Batcher handles assembly commands that generates multiple instructions, such as menu navigation commands.
2023-04-05 13:06:13 +02:00
type Batcher struct {
menuProcessor MenuProcessor
inMenu bool
2023-04-04 22:02:17 +02:00
}
2023-04-12 19:04:36 +02:00
// NewBatcher creates a new Batcher objcet.
2023-04-05 13:06:13 +02:00
func NewBatcher(mp MenuProcessor) Batcher {
return Batcher{
menuProcessor: NewMenuProcessor(),
2023-04-05 09:12:13 +02:00
}
}
2023-04-12 19:04:36 +02:00
// MenuExit generates the instructions for the batch and writes them to the given io.Writer.
2023-04-06 09:38:30 +02:00
func(bt *Batcher) MenuExit(w io.Writer) (int, error) {
if !bt.inMenu {
2023-04-04 21:32:40 +02:00
return 0, nil
}
2023-04-06 09:38:30 +02:00
bt.inMenu = false
b := bt.menuProcessor.ToLines()
return w.Write(b)
2023-04-04 21:32:40 +02:00
}
2023-04-12 19:04:36 +02:00
// MenuAdd adds a new menu instruction to the batcher.
2023-04-06 09:38:30 +02:00
func(bt *Batcher) MenuAdd(w io.Writer, code string, arg Arg) (int, error) {
bt.inMenu = true
var selector string
var sym string
2023-04-06 09:38:30 +02:00
if arg.Size != nil {
selector = strconv.FormatUint(uint64(*arg.Size), 10)
} else if arg.Selector != nil {
2023-04-05 13:06:13 +02:00
selector = *arg.Selector
2023-04-05 09:59:24 +02:00
}
if selector == "" {
selector = *arg.Sym
} else if arg.Sym != nil {
sym = *arg.Sym
}
log.Printf("menu processor add %v '%v' '%v' '%v'", code, selector, *arg.Desc, sym)
err := bt.menuProcessor.Add(code, selector, *arg.Desc, sym)
2023-04-05 13:06:13 +02:00
return 0, err
2023-04-05 09:59:24 +02:00
}
2023-04-12 19:04:36 +02:00
// Exit is a synonym for MenuExit
2023-04-06 09:38:30 +02:00
func(bt *Batcher) Exit(w io.Writer) (int, error) {
return bt.MenuExit(w)
2023-04-05 09:21:46 +02:00
}
2023-04-12 19:04:36 +02:00
// Parse one or more lines of assembly code, and write assembled bytecode to the provided writer.
2023-04-04 11:32:39 +02:00
func Parse(s string, w io.Writer) (int, error) {
rd := strings.NewReader(s)
ast, err := asmParser.Parse("file", rd)
2023-04-05 09:38:07 +02:00
if err != nil {
return 0, err
}
2023-04-04 21:32:40 +02:00
2023-04-05 13:06:13 +02:00
batch := Batcher{
}
2023-04-05 09:38:07 +02:00
var rn int
2023-04-04 21:32:40 +02:00
for _, v := range ast.Instructions {
2023-04-05 09:59:24 +02:00
log.Printf("parsing line %v: %v", v.OpCode, v.OpArg)
2023-04-05 13:06:13 +02:00
op, ok := vm.OpcodeIndex[v.OpCode]
if !ok {
n, err := batch.MenuAdd(w, v.OpCode, v.OpArg)
2023-04-04 21:32:40 +02:00
rn += n
2023-04-05 12:22:13 +02:00
if err != nil {
2023-04-05 13:06:13 +02:00
return rn, err
}
} else {
n, err := batch.MenuExit(w)
if err != nil {
return rn, err
2023-04-05 12:22:13 +02:00
}
2023-04-04 22:02:17 +02:00
rn += n
2023-04-05 13:06:13 +02:00
n, err = parseOne(op, v, w)
2023-04-05 09:21:46 +02:00
rn += n
2023-04-05 13:06:13 +02:00
if err != nil {
return rn, err
}
2023-04-06 09:38:30 +02:00
log.Printf("wrote %v bytes for %v", n, v.OpArg)
2023-04-05 09:21:46 +02:00
}
2023-04-04 11:32:39 +02:00
}
2023-04-05 13:06:13 +02:00
n, err := batch.Exit(w)
rn += n
if err != nil {
return rn, err
}
rn += n
2023-04-04 21:32:40 +02:00
return rn, err
2023-04-04 11:32:39 +02:00
}