Move source files to root dir
This commit is contained in:
404
asm/asm.go
Normal file
404
asm/asm.go
Normal file
@@ -0,0 +1,404 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/participle/v2"
|
||||
"github.com/alecthomas/participle/v2/lexer"
|
||||
|
||||
"git.defalsify.org/festive/vm"
|
||||
)
|
||||
|
||||
|
||||
// Asm assembles bytecode from the festive assembly mini-language.
|
||||
type Asm struct {
|
||||
Instructions []*Instruction `@@*`
|
||||
}
|
||||
|
||||
// Arg holds all parsed argument elements of a single line of assembly code.
|
||||
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?)?`
|
||||
}
|
||||
|
||||
func flush(b *bytes.Buffer, w io.Writer) (int, error) {
|
||||
if w != nil {
|
||||
return w.Write(b.Bytes())
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func parseDescType(b *bytes.Buffer, arg Arg) (int, error) {
|
||||
var rn int
|
||||
var err error
|
||||
|
||||
var selector string
|
||||
if arg.Flag != nil {
|
||||
selector = strconv.FormatUint(uint64(*arg.Flag), 10)
|
||||
} else if arg.Selector != nil {
|
||||
selector = *arg.Selector
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if arg.Sym != nil {
|
||||
n, err := writeSym(b, *arg.Sym)
|
||||
rn += n
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
}
|
||||
|
||||
if selector != "" {
|
||||
n, err := writeSym(b, *arg.Selector)
|
||||
rn += n
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
}
|
||||
|
||||
n, err := writeSym(b, *arg.Desc)
|
||||
rn += n
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
func parseTwoSym(b *bytes.Buffer, arg Arg) (int, error) {
|
||||
var rn int
|
||||
|
||||
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)
|
||||
rn += n
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
|
||||
n, err = writeSym(b, sym)
|
||||
rn += n
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Catch MOUT
|
||||
if a.Desc != nil {
|
||||
n, err := parseDescType(b, a)
|
||||
n_buf += n
|
||||
if err != nil {
|
||||
return n_out, err
|
||||
}
|
||||
return flush(b, w)
|
||||
}
|
||||
|
||||
// Catch
|
||||
if a.Selector != nil {
|
||||
log.Printf("entering twosym for %v", op)
|
||||
n, err := parseTwoSym(b, a)
|
||||
n_buf += n
|
||||
if err != nil {
|
||||
return n_out, err
|
||||
}
|
||||
return flush(b, w)
|
||||
}
|
||||
|
||||
// Catch CATCH, LOAD
|
||||
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)
|
||||
}
|
||||
|
||||
// Catch HALT
|
||||
if a.Sym == nil {
|
||||
return flush(b, w)
|
||||
}
|
||||
|
||||
n, err = writeSym(b, *a.Sym)
|
||||
n_buf += n
|
||||
return flush(b, w)
|
||||
}
|
||||
|
||||
// String implements the String interface.
|
||||
func (a Arg) String() string {
|
||||
s := "[Arg]"
|
||||
if a.Sym != nil {
|
||||
s += " Sym: " + *a.Sym
|
||||
}
|
||||
if a.Size != nil {
|
||||
s += fmt.Sprintf(" Size: %v", *a.Size)
|
||||
}
|
||||
if a.Flag != nil {
|
||||
s += fmt.Sprintf(" Flag: %v", *a.Flag)
|
||||
}
|
||||
if a.Selector != nil {
|
||||
s += " Selector: " + *a.Selector
|
||||
}
|
||||
if a.Desc != nil {
|
||||
s += " Description: " + *a.Desc
|
||||
}
|
||||
|
||||
return fmt.Sprintf(s)
|
||||
}
|
||||
|
||||
// Instruction represents one full line of assembly code.
|
||||
type Instruction struct {
|
||||
OpCode string `@Ident`
|
||||
OpArg Arg `(Whitespace @@)?`
|
||||
Comment string `Comment? EOL`
|
||||
}
|
||||
|
||||
// String implement the String interface.
|
||||
func (i Instruction) String() string {
|
||||
return fmt.Sprintf("%s %s", i.OpCode, i.OpArg)
|
||||
}
|
||||
|
||||
var (
|
||||
asmLexer = lexer.MustSimple([]lexer.SimpleRule{
|
||||
{"Comment", `(?:#)[^\n]*`},
|
||||
{"Ident", `^[A-Z]+`},
|
||||
{"Size", `[0-9]+`},
|
||||
{"Sym", `[a-zA-Z_][a-zA-Z0-9_]*`},
|
||||
{"Whitespace", `[ \t]+`},
|
||||
{"EOL", `[\n\r]+`},
|
||||
{"Quote", `["']`},
|
||||
})
|
||||
asmParser = participle.MustBuild[Asm](
|
||||
participle.Lexer(asmLexer),
|
||||
participle.Elide("Comment", "Whitespace"),
|
||||
)
|
||||
)
|
||||
|
||||
func numSize(n uint32) int {
|
||||
v := math.Log2(float64(n))
|
||||
return int((v / 8) + 1)
|
||||
}
|
||||
|
||||
func writeOpcode(w *bytes.Buffer, op vm.Opcode) (int, error) {
|
||||
bn := [2]byte{}
|
||||
binary.BigEndian.PutUint16(bn[:], uint16(op))
|
||||
n, err := w.Write(bn[:])
|
||||
return n, err
|
||||
}
|
||||
|
||||
func writeSym(w *bytes.Buffer, s string) (int, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
func writeDisplay(w *bytes.Buffer, s string) (int, error) {
|
||||
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)
|
||||
}
|
||||
func writeSize(w *bytes.Buffer, n uint32) (int, error) {
|
||||
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
|
||||
return w.Write(bn[c:])
|
||||
}
|
||||
|
||||
// Batcher handles assembly commands that generates multiple instructions, such as menu navigation commands.
|
||||
type Batcher struct {
|
||||
menuProcessor MenuProcessor
|
||||
inMenu bool
|
||||
}
|
||||
|
||||
// NewBatcher creates a new Batcher objcet.
|
||||
func NewBatcher(mp MenuProcessor) Batcher {
|
||||
return Batcher{
|
||||
menuProcessor: NewMenuProcessor(),
|
||||
}
|
||||
}
|
||||
|
||||
// MenuExit generates the instructions for the batch and writes them to the given io.Writer.
|
||||
func(bt *Batcher) MenuExit(w io.Writer) (int, error) {
|
||||
if !bt.inMenu {
|
||||
return 0, nil
|
||||
}
|
||||
bt.inMenu = false
|
||||
b := bt.menuProcessor.ToLines()
|
||||
return w.Write(b)
|
||||
}
|
||||
|
||||
// MenuAdd adds a new menu instruction to the batcher.
|
||||
func(bt *Batcher) MenuAdd(w io.Writer, code string, arg Arg) (int, error) {
|
||||
bt.inMenu = true
|
||||
var selector string
|
||||
var sym string
|
||||
if arg.Size != nil {
|
||||
selector = strconv.FormatUint(uint64(*arg.Size), 10)
|
||||
} else if arg.Selector != nil {
|
||||
selector = *arg.Selector
|
||||
}
|
||||
|
||||
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)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Exit is a synonym for MenuExit
|
||||
func(bt *Batcher) Exit(w io.Writer) (int, error) {
|
||||
return bt.MenuExit(w)
|
||||
}
|
||||
|
||||
// Parse one or more lines of assembly code, and write assembled bytecode to the provided writer.
|
||||
func Parse(s string, w io.Writer) (int, error) {
|
||||
rd := strings.NewReader(s)
|
||||
ast, err := asmParser.Parse("file", rd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
batch := Batcher{
|
||||
|
||||
}
|
||||
|
||||
var rn int
|
||||
for _, v := range ast.Instructions {
|
||||
log.Printf("parsing line %v: %v", v.OpCode, v.OpArg)
|
||||
op, ok := vm.OpcodeIndex[v.OpCode]
|
||||
if !ok {
|
||||
n, err := batch.MenuAdd(w, v.OpCode, v.OpArg)
|
||||
rn += n
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
} else {
|
||||
n, err := batch.MenuExit(w)
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
rn += n
|
||||
n, err = parseOne(op, v, w)
|
||||
rn += n
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
log.Printf("wrote %v bytes for %v", n, v.OpArg)
|
||||
}
|
||||
}
|
||||
n, err := batch.Exit(w)
|
||||
rn += n
|
||||
if err != nil {
|
||||
return rn, err
|
||||
}
|
||||
rn += n
|
||||
|
||||
return rn, err
|
||||
}
|
||||
249
asm/asm_test.go
Normal file
249
asm/asm_test.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/festive/vm"
|
||||
)
|
||||
|
||||
|
||||
func TestParserInit(t *testing.T) {
|
||||
var b []byte
|
||||
b = vm.NewLine(b, vm.HALT, nil, nil, nil)
|
||||
b = vm.NewLine(b, vm.CATCH, []string{"xyzzy"}, []byte{0x02, 0x9a}, []uint8{1})
|
||||
b = vm.NewLine(b, vm.INCMP, []string{"inky", "pinky"}, nil, nil)
|
||||
b = vm.NewLine(b, vm.LOAD, []string{"foo"}, []byte{42}, nil)
|
||||
b = vm.NewLine(b, vm.MOUT, []string{"bar", "barbarbaz"}, nil, nil)
|
||||
s, err := vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
n, err := Parse(s, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Fatalf("expected 0 byte write count, got %v", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserSized(t *testing.T) {
|
||||
var b []byte
|
||||
b = vm.NewLine(b, vm.LOAD, []string{"foo"}, []byte{42}, nil)
|
||||
s, err := vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
r := bytes.NewBuffer(nil)
|
||||
n, err := Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 8 {
|
||||
t.Fatalf("expected 8 byte write count, got %v", n)
|
||||
}
|
||||
rb := r.Bytes()
|
||||
if !bytes.Equal(rb, []byte{0x00, vm.LOAD, 0x03, 0x66, 0x6f, 0x6f, 0x01, 0x2a}) {
|
||||
t.Fatalf("expected 0x00%x012a, got %v", vm.LOAD, rb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDisplay(t *testing.T) {
|
||||
var b []byte
|
||||
b = vm.NewLine(b, vm.MOUT, []string{"foo", "baz ba zbaz"}, nil, nil)
|
||||
s, err := vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
r := bytes.NewBuffer(nil)
|
||||
n, err := Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 18 {
|
||||
t.Fatalf("expected 18 byte write count, got %v", n)
|
||||
}
|
||||
rb := r.Bytes()
|
||||
expect := []byte{0x00, vm.MOUT, 0x03, 0x66, 0x6f, 0x6f, 0x0b, 0x62, 0x61, 0x7a, 0x20, 0x62, 0x61, 0x20, 0x7a, 0x62, 0x61, 0x7a}
|
||||
if !bytes.Equal(rb, expect) {
|
||||
t.Fatalf("expected %x, got %x", expect, rb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDouble(t *testing.T) {
|
||||
var b []byte
|
||||
b = vm.NewLine(b, vm.INCMP, []string{"bar", "foo"}, nil, nil)
|
||||
s, err := vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
r := bytes.NewBuffer(nil)
|
||||
n, err := Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 10 {
|
||||
t.Fatalf("expected 18 byte write count, got %v", n)
|
||||
}
|
||||
rb := r.Bytes()
|
||||
expect := []byte{0x00, vm.INCMP, 0x03, 0x66, 0x6f, 0x6f, 0x03, 0x62, 0x61, 0x72}
|
||||
if !bytes.Equal(rb, expect) {
|
||||
t.Fatalf("expected %x, got %x", expect, rb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMenu(t *testing.T) {
|
||||
s := `DOWN foobar 00 "inky pinky"
|
||||
UP s1 "tinkywinky"
|
||||
`
|
||||
r := bytes.NewBuffer(nil)
|
||||
n, err := Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Printf("wrote %v bytes", n)
|
||||
|
||||
s = `MOUT foobar 00 "inky pinky"
|
||||
MOUT bazbar s1 "tinky winky"
|
||||
HALT
|
||||
INCMP 00 foobar
|
||||
INCMP s1 bazbar
|
||||
`
|
||||
r_check := bytes.NewBuffer(nil)
|
||||
n, err = Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Printf("wrote %v bytes", n)
|
||||
|
||||
if !bytes.Equal(r_check.Bytes(), r.Bytes()) {
|
||||
fmt.Errorf("expected:\n\t%xgot:\n\t%x\n", r_check, r)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParseSingle(t *testing.T) {
|
||||
var b []byte
|
||||
b = vm.NewLine(b, vm.MAP, []string{"xyzzy"}, nil, nil)
|
||||
s, err := vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
r := bytes.NewBuffer(nil)
|
||||
n, err := Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 8 {
|
||||
t.Fatalf("expected 8 byte write count, got %v", n)
|
||||
}
|
||||
rb := r.Bytes()
|
||||
expect := []byte{0x00, vm.MAP, 0x05, 0x78, 0x79, 0x7a, 0x7a, 0x79}
|
||||
if !bytes.Equal(rb, expect) {
|
||||
t.Fatalf("expected %x, got %x", expect, rb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSig(t *testing.T) {
|
||||
b := vm.NewLine(nil, vm.CATCH, []string{"plugh"}, []byte{0x02, 0x9a}, []uint8{0x2a})
|
||||
s, err := vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
r := bytes.NewBuffer(nil)
|
||||
n, err := Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 12 {
|
||||
t.Fatalf("expected 12 byte write count, got %v", n)
|
||||
}
|
||||
rb := r.Bytes()
|
||||
expect_hex := "000105706c75676802029a01"
|
||||
expect, err := hex.DecodeString(expect_hex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(rb, expect) {
|
||||
t.Fatalf("expected %v, got %x", expect_hex, rb)
|
||||
}
|
||||
|
||||
b = vm.NewLine(nil, vm.CATCH, []string{"plugh"}, []byte{0x01}, []uint8{0x0})
|
||||
s, err = vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
r = bytes.NewBuffer(nil)
|
||||
n, err = Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 11 {
|
||||
t.Fatalf("expected 11 byte write count, got %v", n)
|
||||
}
|
||||
rb = r.Bytes()
|
||||
expect_hex = "000105706c756768010100"
|
||||
expect, err = hex.DecodeString(expect_hex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(rb, expect) {
|
||||
t.Fatalf("expected %v, got %x", expect_hex, rb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNoarg(t *testing.T) {
|
||||
var b []byte
|
||||
b = vm.NewLine(b, vm.HALT, nil, nil, nil)
|
||||
s, err := vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
r := bytes.NewBuffer(nil)
|
||||
n, err := Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 2 {
|
||||
t.Fatalf("expected 8 byte write count, got %v", n)
|
||||
}
|
||||
rb := r.Bytes()
|
||||
expect := []byte{0x00, vm.HALT}
|
||||
if !bytes.Equal(rb, expect) {
|
||||
t.Fatalf("expected %x, got %x", expect, rb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserWriteMultiple(t *testing.T) {
|
||||
var b []byte
|
||||
b = vm.NewLine(b, vm.HALT, nil, nil, nil)
|
||||
b = vm.NewLine(b, vm.CATCH, []string{"xyzzy"}, []byte{0x02, 0x9a}, []uint8{1})
|
||||
b = vm.NewLine(b, vm.INCMP, []string{"pinky", "inky"}, nil, nil)
|
||||
b = vm.NewLine(b, vm.LOAD, []string{"foo"}, []byte{42}, nil)
|
||||
b = vm.NewLine(b, vm.MOUT, []string{"bar", "bar barb az"}, nil, nil)
|
||||
s, err := vm.ToString(b)
|
||||
log.Printf("parsing:\n%s\n", s)
|
||||
|
||||
r := bytes.NewBuffer(nil)
|
||||
n, err := Parse(s, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Printf("result %x", r.Bytes())
|
||||
|
||||
r_expect_hex := "000700010578797a7a7902029a01000804696e6b790570696e6b79000303666f6f012a000a036261720b626172206261726220617a"
|
||||
r_expect, err := hex.DecodeString(r_expect_hex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n_expect := len(r_expect)
|
||||
if n != n_expect {
|
||||
t.Fatalf("expected total %v bytes output, got %v", n_expect, n)
|
||||
}
|
||||
|
||||
rb := r.Bytes()
|
||||
if !bytes.Equal(rb, r_expect) {
|
||||
t.Fatalf("expected result:\n\t%v, got:\n\t%x", r_expect_hex, rb)
|
||||
}
|
||||
|
||||
_, err = vm.ParseAll(rb, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
94
asm/menu.go
Normal file
94
asm/menu.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/festive/vm"
|
||||
)
|
||||
|
||||
// BatchCode defines quasi-opcodes that expand to mulitple individual vm instructions.
|
||||
type BatchCode uint16
|
||||
|
||||
const (
|
||||
_MENU_OFFSET = 256
|
||||
MENU_DOWN = _MENU_OFFSET
|
||||
MENU_UP = _MENU_OFFSET + 1
|
||||
MENU_NEXT = _MENU_OFFSET + 2
|
||||
MENU_PREVIOUS = _MENU_OFFSET + 3
|
||||
)
|
||||
|
||||
var (
|
||||
batchCode = map[string]BatchCode{
|
||||
"DOWN": MENU_DOWN,
|
||||
"UP": MENU_UP,
|
||||
"NEXT": MENU_NEXT,
|
||||
"PREVIOUS": MENU_PREVIOUS,
|
||||
}
|
||||
)
|
||||
|
||||
type menuItem struct {
|
||||
code BatchCode
|
||||
choice string
|
||||
display string
|
||||
target string
|
||||
}
|
||||
|
||||
// MenuProcessor handles code lines with BatchCode quasi-opcodes that control menu generation.
|
||||
//
|
||||
// It creates vm instructions for display of menu and handling of input on either size of a vm.HALT instruction.
|
||||
type MenuProcessor struct {
|
||||
items []menuItem
|
||||
size uint32
|
||||
}
|
||||
|
||||
// NewMenuProcessor creates a new MenuProcessor object.
|
||||
func NewMenuProcessor() MenuProcessor {
|
||||
return MenuProcessor{}
|
||||
}
|
||||
|
||||
// Add a menu batch instruction to be processed.
|
||||
//
|
||||
// Instructions will be rendered in the order in which they have been added.
|
||||
func(mp *MenuProcessor) Add(bop string, choice string, display string, target string) error {
|
||||
bopCode := batchCode[bop]
|
||||
if bopCode == 0 {
|
||||
return fmt.Errorf("unknown menu instruction: %v", bop)
|
||||
}
|
||||
if len(target) > 0 && bopCode != MENU_DOWN {
|
||||
return fmt.Errorf("target is only valid for DOWN")
|
||||
}
|
||||
m := menuItem{
|
||||
code: bopCode,
|
||||
choice: choice,
|
||||
display: display,
|
||||
target: target,
|
||||
}
|
||||
mp.items = append(mp.items, m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToLines returns the generated bytecode from the added menu batch instructions.
|
||||
func (mp *MenuProcessor) ToLines() []byte {
|
||||
preLines := []byte{}
|
||||
postLines := []byte{}
|
||||
|
||||
for _, v := range mp.items {
|
||||
switch v.code {
|
||||
case MENU_UP:
|
||||
preLines = vm.NewLine(preLines, vm.MOUT, []string{v.choice, v.display}, nil, nil)
|
||||
postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, "_"}, nil, nil)
|
||||
case MENU_NEXT:
|
||||
preLines = vm.NewLine(preLines, vm.MNEXT, []string{v.choice, v.display}, nil, nil)
|
||||
postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, ">"}, nil, nil)
|
||||
case MENU_PREVIOUS:
|
||||
preLines = vm.NewLine(preLines, vm.MPREV, []string{v.choice, v.display}, nil, nil)
|
||||
postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, "<"}, nil, nil)
|
||||
default:
|
||||
preLines = vm.NewLine(preLines, vm.MOUT, []string{v.choice, v.display}, nil, nil)
|
||||
postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, v.target}, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
preLines = vm.NewLine(preLines, vm.HALT, nil, nil, nil)
|
||||
return append(preLines, postLines...)
|
||||
}
|
||||
50
asm/menu_test.go
Normal file
50
asm/menu_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/festive/vm"
|
||||
)
|
||||
|
||||
|
||||
func TestMenuInterpreter(t *testing.T) {
|
||||
m := NewMenuProcessor()
|
||||
err := m.Add("DOWN", "0", "inky", "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = m.Add("NEXT", "1", "pinky", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = m.Add("PREVIOUS", "2", "blinky clyde", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = m.Add("UP", "99", "tinky-winky", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = m.Add("BOGUS", "42", "lala poo", "plugh")
|
||||
if err == nil {
|
||||
t.Errorf("expected error on invalid menu item 'BOGUS'")
|
||||
}
|
||||
b := m.ToLines()
|
||||
r, err := vm.ToString(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expect := `MOUT 0 "inky"
|
||||
MNEXT 1 "pinky"
|
||||
MPREV 2 "blinky clyde"
|
||||
MOUT 99 "tinky-winky"
|
||||
HALT
|
||||
INCMP 0 foo
|
||||
INCMP 1 >
|
||||
INCMP 2 <
|
||||
INCMP 99 _
|
||||
`
|
||||
if r != expect {
|
||||
t.Errorf("expected:\n\t%v\ngot:\n\t%v\n", expect, r)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user