WIP Add batch menu handler for asm

This commit is contained in:
lash 2023-04-05 12:06:13 +01:00
parent b8c8802421
commit 13bf7309e7
Signed by untrusted user who does not match committer: lash
GPG Key ID: 21D2E7BB88C2A746
3 changed files with 267 additions and 271 deletions

View File

@ -69,7 +69,7 @@ A menu has both a display and a input processing part. They are on either side o
To assist with menu creation, a few batch operation symbols have been made available for use with the assembly language. To assist with menu creation, a few batch operation symbols have been made available for use with the assembly language.
* `DOWN <choice> <display> <symbol>` descend to next frame * `DOWN <symbol> <choice> <display>` descend to next frame and move to `symbol`
* `UP <choice> <display>` return to the previous frame * `UP <choice> <display>` return to the previous frame
* `NEXT <choice> <display>` include pagination advance * `NEXT <choice> <display>` include pagination advance
* `PREVIOUS <choice> <display>` include pagination return. If `NEXT` has not been defined this will not be rendered. * `PREVIOUS <choice> <display>` include pagination return. If `NEXT` has not been defined this will not be rendered.

View File

@ -7,6 +7,7 @@ import (
"io" "io"
"log" "log"
"math" "math"
"strconv"
"strings" "strings"
"github.com/alecthomas/participle/v2" "github.com/alecthomas/participle/v2"
@ -20,73 +21,197 @@ type Asm struct {
Instructions []*Instruction `@@*` Instructions []*Instruction `@@*`
} }
type Display struct {
Sym string `@Sym Whitespace`
Val string `Quote (@Sym @Whitespace?)+ Quote`
}
func(d Display) String() string {
return fmt.Sprintf("Display: %v %v", d.Sym, d.Val)
}
type Single struct {
One string `@Sym`
}
func(s Single) String() string {
return fmt.Sprintf("Single: %v", s.One)
}
type Double struct {
One string `@Sym Whitespace`
Two string `@Sym`
}
func(d Double) String() string {
return fmt.Sprintf("Double: %v %v", d.One, d.Two)
}
type Sized struct {
Sym string `@Sym Whitespace`
Size uint32 `@Size`
}
func(s Sized) String() string {
return fmt.Sprintf("Sized: %v %v", s.Sym, s.Size)
}
type Arg struct { type Arg struct {
ArgDisplay *Display `@@?` Sym *string `(@Sym Whitespace?)?`
ArgSized *Sized `@@?` Size *uint32 `(@Size Whitespace?)?`
ArgFlag *uint8 `@Size?` Flag *uint8 `(@Size Whitespace?)?`
ArgDouble *Double `@@?` Selector *string `(@Sym Whitespace?)?`
ArgSingle *Single `@@?` Desc *string `(Quote ((@Sym | @Size) @Whitespace?)+ Quote Whitespace?)?`
ArgNone string `Whitespace? EOL` }
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
}
n, err := writeSym(b, *arg.Sym)
rn += n
if err != nil {
return rn, err
}
if selector != "" {
n, err := writeSym(b, *arg.Sym)
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
n, err := writeSym(b, *arg.Sym)
rn += n
if err != nil {
return rn, err
}
n, err = writeSym(b, *arg.Selector)
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
}
if a.Sym == nil {
return flush(b, w)
}
if a.Desc != nil {
n, err := parseDescType(b, a)
n_buf += n
if err != nil {
return n_out, err
}
return flush(b, w)
}
if a.Selector != nil {
n, err := parseTwoSym(b, a)
n_buf += n
if err != nil {
return n_out, err
}
return flush(b, w)
}
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)
}
n, err = writeSym(b, *a.Sym)
n_buf += n
return flush(b, w)
} }
func (a Arg) String() string { func (a Arg) String() string {
if a.ArgDisplay != nil { s := "[Arg]"
return fmt.Sprintf("%s", a.ArgDisplay) if a.Sym != nil {
s += " Sym: " + *a.Sym
} }
if a.ArgFlag != nil { if a.Size != nil {
return fmt.Sprintf("Flag: %v", *a.ArgFlag) s += fmt.Sprintf(" Size: %v", *a.Size)
} }
if a.ArgSized != nil { if a.Flag != nil {
return fmt.Sprintf("%s", a.ArgSized) s += fmt.Sprintf(" Flag: %v", *a.Flag)
} }
if a.ArgSingle != nil { if a.Selector != nil {
return fmt.Sprintf("%s", a.ArgSingle) s += " Selector: " + *a.Selector
} }
if a.ArgDouble != nil { if a.Desc != nil {
return fmt.Sprintf("%s", a.ArgDouble) s += " Description: " + *a.Desc
} }
return ""
return fmt.Sprintf(s)
} }
type Instruction struct { type Instruction struct {
OpCode string `@Ident` OpCode string `@Ident`
OpArg Arg `@@` OpArg Arg `(Whitespace @@)?`
Comment string `Comment?` Comment string `Comment? EOL`
} }
func (i Instruction) String() string { func (i Instruction) String() string {
@ -95,13 +220,11 @@ func (i Instruction) String() string {
var ( var (
asmLexer = lexer.MustSimple([]lexer.SimpleRule{ asmLexer = lexer.MustSimple([]lexer.SimpleRule{
{"Comment", `(?:#)[^\n]*\n?`}, {"Comment", `(?:#)[^\n]*`},
{"Ident", `^[A-Z]+`}, {"Ident", `^[A-Z]+`},
{"SizeSig", `[0-9]+\s+{?:[0-9]}`},
{"Size", `[0-9]+`}, {"Size", `[0-9]+`},
{"Sym", `[a-zA-Z_][a-zA-Z0-9_]+`}, {"Sym", `[a-zA-Z_][a-zA-Z0-9_]*`},
{"Whitespace", `[ \t]+`}, {"Whitespace", `[ \t]+`},
{"Discard", `^\s+[\n\r]+$`},
{"EOL", `[\n\r]+`}, {"EOL", `[\n\r]+`},
{"Quote", `["']`}, {"Quote", `["']`},
}) })
@ -116,14 +239,14 @@ func numSize(n uint32) int {
return int(((v - 1) / 8) + 1) return int(((v - 1) / 8) + 1)
} }
func writeOpcode(op vm.Opcode, w *bytes.Buffer) (int, error) { func writeOpcode(w *bytes.Buffer, op vm.Opcode) (int, error) {
bn := [2]byte{} bn := [2]byte{}
binary.BigEndian.PutUint16(bn[:], uint16(op)) binary.BigEndian.PutUint16(bn[:], uint16(op))
n, err := w.Write(bn[:]) n, err := w.Write(bn[:])
return n, err return n, err
} }
func writeSym(s string, w *bytes.Buffer) (int, error) { func writeSym(w *bytes.Buffer, s string) (int, error) {
sz := len(s) sz := len(s)
if sz > 255 { if sz > 255 {
return 0, fmt.Errorf("string size %v too big", sz) return 0, fmt.Errorf("string size %v too big", sz)
@ -132,7 +255,7 @@ func writeSym(s string, w *bytes.Buffer) (int, error) {
return w.WriteString(s) return w.WriteString(s)
} }
func writeDisplay(s string, w *bytes.Buffer) (int, error) { func writeDisplay(w *bytes.Buffer, s string) (int, error) {
s = strings.Trim(s, "\"'") s = strings.Trim(s, "\"'")
sz := len(s) sz := len(s)
if sz > 255 { if sz > 255 {
@ -141,8 +264,7 @@ func writeDisplay(s string, w *bytes.Buffer) (int, error) {
w.Write([]byte{byte(sz)}) w.Write([]byte{byte(sz)})
return w.WriteString(s) return w.WriteString(s)
} }
func writeSize(w *bytes.Buffer, n uint32) (int, error) {
func writeSize(n uint32, w *bytes.Buffer) (int, error) {
bn := [4]byte{} bn := [4]byte{}
sz := numSize(n) sz := numSize(n)
if sz > 4 { if sz > 4 {
@ -154,175 +276,37 @@ func writeSize(n uint32, w *bytes.Buffer) (int, error) {
return w.Write(bn[c:]) return w.Write(bn[c:])
} }
func parseSingle(op vm.Opcode, arg Arg, w io.Writer) (int, error) { type Batcher struct {
var rn int menuProcessor MenuProcessor
inMenu bool
v := arg.ArgSingle
if v == nil {
return 0, nil
}
b := bytes.NewBuffer(nil)
n, err := writeOpcode(op, b)
rn += n
if err != nil {
return rn, err
}
n, err = writeSym(v.One, b)
rn += n
if err != nil {
return rn, err
}
if w != nil {
rn, err = w.Write(b.Bytes())
} else {
rn = 0
}
return rn, err
} }
func parseDisplay(op vm.Opcode, arg Arg, w io.Writer) (int, error) { func NewBatcher(mp MenuProcessor) Batcher {
var rn int return Batcher{
menuProcessor: NewMenuProcessor(),
v := arg.ArgDisplay
if v == nil {
return 0, nil
} }
b := bytes.NewBuffer(nil)
n, err := writeOpcode(op, b)
rn += n
if err != nil {
return rn, err
}
n, err = writeSym(v.Sym, b)
rn += n
if err != nil {
return rn, err
}
n, err = writeDisplay(v.Val, b)
rn += n
if err != nil {
return rn, err
}
if w != nil {
rn, err = w.Write(b.Bytes())
} else {
rn = 0
}
return rn, err
} }
func parseDouble(op vm.Opcode, arg Arg, w io.Writer) (int, error) { func(b *Batcher) MenuExit(w io.Writer) (int, error) {
var rn int if !b.inMenu {
v := arg.ArgDouble
if v == nil {
return 0, nil return 0, nil
} }
b.inMenu = false
b := bytes.NewBuffer(nil) return w.Write(b.menuProcessor.ToLines())
n, err := writeOpcode(op, b)
rn += n
if err != nil {
return rn, err
}
n, err = writeSym(v.One, b)
rn += n
if err != nil {
return rn, err
}
n, err = writeSym(v.Two, b)
rn += n
if err != nil {
return rn, err
}
if w != nil {
rn, err = w.Write(b.Bytes())
} else {
rn = 0
}
return rn, err
} }
func parseSized(op vm.Opcode, arg Arg, w io.Writer) (int, error) { func(b *Batcher) MenuAdd(w io.Writer, code string, arg Arg) (int, error) {
var rn int b.inMenu = true
selector := ""
v := arg.ArgSized if arg.Selector != nil {
if v == nil { selector = *arg.Selector
return 0, nil
} }
err := b.menuProcessor.Add(code, *arg.Sym, selector, *arg.Desc)
b := bytes.NewBuffer(nil) return 0, err
n, err := writeOpcode(op, b)
rn += n
if err != nil {
return rn, err
}
n, err = writeSym(v.Sym, b)
rn += n
if err != nil {
return rn, err
}
n, err = writeSize(v.Size, b)
rn += n
if err != nil {
return rn, err
}
if w != nil {
rn, err = w.Write(b.Bytes())
} else {
rn = 0
}
return rn, err
} }
func parseNoarg(op vm.Opcode, arg Arg, w io.Writer) (int, error) { func(b *Batcher) Exit(w io.Writer) (int, error) {
var rn int return b.MenuExit(w)
b := bytes.NewBuffer(nil)
n, err := writeOpcode(op, b)
rn += n
if err != nil {
return rn, err
}
if w != nil {
rn, err = w.Write(b.Bytes())
} else {
rn = 0
}
return rn, err
}
func parseFlag(op vm.Opcode, arg Arg, w io.Writer) (int, error) {
var rn int
var err error
v := arg.ArgFlag
if v == nil {
return 0, nil
}
if w != nil {
rn, err = w.Write([]byte{*v})
} else {
rn = 0
}
return rn, err
} }
func Parse(s string, w io.Writer) (int, error) { func Parse(s string, w io.Writer) (int, error) {
@ -332,56 +316,39 @@ func Parse(s string, w io.Writer) (int, error) {
return 0, err return 0, err
} }
batch := Batcher{
}
var rn int var rn int
for _, v := range ast.Instructions { for _, v := range ast.Instructions {
log.Printf("parsing line %v: %v", v.OpCode, v.OpArg) log.Printf("parsing line %v: %v", v.OpCode, v.OpArg)
op := vm.OpcodeIndex[v.OpCode] op, ok := vm.OpcodeIndex[v.OpCode]
n, err := parseSized(op, v.OpArg, w) if !ok {
if err != nil { n, err := batch.MenuAdd(w, v.OpCode, v.OpArg)
return n, err
}
if n > 0 {
rn += n rn += n
n, err = parseFlag(op, v.OpArg, w)
if err != nil { if err != nil {
return n, err return rn, err
}
} else {
n, err := batch.MenuExit(w)
if err != nil {
return rn, err
} }
rn += n rn += n
continue n, err = parseOne(op, v, w)
}
n, err = parseDisplay(op, v.OpArg, w)
if err != nil {
return n, err
}
if n > 0 {
rn += n rn += n
continue if err != nil {
return rn, err
}
} }
n, err = parseDouble(op, v.OpArg, w)
if err != nil {
return n, err
}
if n > 0 {
rn += n
continue
}
n, err = parseSingle(op, v.OpArg, w)
if err != nil {
return n, err
}
if n > 0 {
rn += n
continue
}
n, err = parseNoarg(op, v.OpArg, w)
if err != nil {
return n, err
}
if n > 0 {
rn += n
continue
}
} }
n, err := batch.Exit(w)
rn += n
if err != nil {
return rn, err
}
rn += n
return rn, err return rn, err
} }

View File

@ -3,6 +3,7 @@ package asm
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt"
"log" "log"
"testing" "testing"
@ -91,6 +92,36 @@ func TestParseDouble(t *testing.T) {
} }
} }
func TestParseMenu(t *testing.T) {
s := `DOWN foobar 00 "inky pinky"
UP bazbar 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) { func TestParseSingle(t *testing.T) {
var b []byte var b []byte
b = vm.NewLine(b, vm.MAP, []string{"xyzzy"}, nil, nil) b = vm.NewLine(b, vm.MAP, []string{"xyzzy"}, nil, nil)
@ -173,23 +204,21 @@ func TestParserWriteMultiple(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
n_expect := 2 // halt
n_expect += 2 + 6 + 3 + 1 // catch
n_expect += 2 + 5 + 6 // incmp
n_expect += 2 + 4 + 2 // load
n_expect += 2 + 4 + 12 // mout
log.Printf("result %x", r.Bytes()) log.Printf("result %x", r.Bytes())
if n != n_expect {
t.Fatalf("expected total %v bytes output, got %v", n_expect, n)
}
r_expect_hex := "000700010578797a7a7902029a01000804696e6b790570696e6b79000303666f6f012a000a036261720b626172206261726220617a" r_expect_hex := "000700010578797a7a7902029a01000804696e6b790570696e6b79000303666f6f012a000a036261720b626172206261726220617a"
r_expect, err := hex.DecodeString(r_expect_hex) r_expect, err := hex.DecodeString(r_expect_hex)
if err != nil { if err != nil {
t.Fatal(err) 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() rb := r.Bytes()
if !bytes.Equal(rb, r_expect) { if !bytes.Equal(rb, r_expect) {
t.Fatalf("expected result %v, got %x", r_expect_hex, rb) t.Fatalf("expected result:\n\t%v, got:\n\t%x", r_expect_hex, rb)
} }
_, err = vm.ParseAll(rb, nil) _, err = vm.ParseAll(rb, nil)