commit 13bf7309e7048723e9a9d7eabcc7b69dc32c672c
parent b8c8802421721c4d1a1d2022669602044de6b20c
Author: lash <dev@holbrook.no>
Date: Wed, 5 Apr 2023 12:06:13 +0100
WIP Add batch menu handler for asm
Diffstat:
3 files changed, 254 insertions(+), 258 deletions(-)
diff --git a/README.md b/README.md
@@ -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.
-* `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
* `NEXT <choice> <display>` include pagination advance
* `PREVIOUS <choice> <display>` include pagination return. If `NEXT` has not been defined this will not be rendered.
diff --git a/go/asm/asm.go b/go/asm/asm.go
@@ -7,6 +7,7 @@ import (
"io"
"log"
"math"
+ "strconv"
"strings"
"github.com/alecthomas/participle/v2"
@@ -20,73 +21,197 @@ type Asm struct {
Instructions []*Instruction `@@*`
}
-type Display struct {
- Sym string `@Sym Whitespace`
- Val string `Quote (@Sym @Whitespace?)+ Quote`
+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(d Display) String() string {
- return fmt.Sprintf("Display: %v %v", d.Sym, d.Val)
+func flush(b *bytes.Buffer, w io.Writer) (int, error) {
+ if w != nil {
+ return w.Write(b.Bytes())
+ }
+ return 0, nil
}
-type Single struct {
- One string `@Sym`
-}
+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
+ }
-func(s Single) String() string {
- return fmt.Sprintf("Single: %v", s.One)
-}
+ 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
+ }
+ }
-type Double struct {
- One string `@Sym Whitespace`
- Two string `@Sym`
+ n, err = writeSym(b, *arg.Desc)
+ rn += n
+ if err != nil {
+ return rn, err
+ }
+
+ return rn, nil
}
-func(d Double) String() string {
- return fmt.Sprintf("Double: %v %v", d.One, d.Two)
+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
}
-type Sized struct {
- Sym string `@Sym Whitespace`
- Size uint32 `@Size`
+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(s Sized) String() string {
- return fmt.Sprintf("Sized: %v %v", s.Sym, s.Size)
+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
}
-type Arg struct {
- ArgDisplay *Display `@@?`
- ArgSized *Sized `@@?`
- ArgFlag *uint8 `@Size?`
- ArgDouble *Double `@@?`
- ArgSingle *Single `@@?`
- ArgNone string `Whitespace? EOL`
+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 {
- if a.ArgDisplay != nil {
- return fmt.Sprintf("%s", a.ArgDisplay)
+ s := "[Arg]"
+ if a.Sym != nil {
+ s += " Sym: " + *a.Sym
}
- if a.ArgFlag != nil {
- return fmt.Sprintf("Flag: %v", *a.ArgFlag)
+ if a.Size != nil {
+ s += fmt.Sprintf(" Size: %v", *a.Size)
}
- if a.ArgSized != nil {
- return fmt.Sprintf("%s", a.ArgSized)
+ if a.Flag != nil {
+ s += fmt.Sprintf(" Flag: %v", *a.Flag)
}
- if a.ArgSingle != nil {
- return fmt.Sprintf("%s", a.ArgSingle)
+ if a.Selector != nil {
+ s += " Selector: " + *a.Selector
}
- if a.ArgDouble != nil {
- return fmt.Sprintf("%s", a.ArgDouble)
+ if a.Desc != nil {
+ s += " Description: " + *a.Desc
}
- return ""
+
+ return fmt.Sprintf(s)
}
type Instruction struct {
OpCode string `@Ident`
- OpArg Arg `@@`
- Comment string `Comment?`
+ OpArg Arg `(Whitespace @@)?`
+ Comment string `Comment? EOL`
}
func (i Instruction) String() string {
@@ -95,13 +220,11 @@ func (i Instruction) String() string {
var (
asmLexer = lexer.MustSimple([]lexer.SimpleRule{
- {"Comment", `(?:#)[^\n]*\n?`},
+ {"Comment", `(?:#)[^\n]*`},
{"Ident", `^[A-Z]+`},
- {"SizeSig", `[0-9]+\s+{?:[0-9]}`},
{"Size", `[0-9]+`},
- {"Sym", `[a-zA-Z_][a-zA-Z0-9_]+`},
+ {"Sym", `[a-zA-Z_][a-zA-Z0-9_]*`},
{"Whitespace", `[ \t]+`},
- {"Discard", `^\s+[\n\r]+$`},
{"EOL", `[\n\r]+`},
{"Quote", `["']`},
})
@@ -116,14 +239,14 @@ func numSize(n uint32) int {
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{}
binary.BigEndian.PutUint16(bn[:], uint16(op))
n, err := w.Write(bn[:])
return n, err
}
-func writeSym(s string, w *bytes.Buffer) (int, error) {
+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)
@@ -132,7 +255,7 @@ func writeSym(s string, w *bytes.Buffer) (int, error) {
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, "\"'")
sz := len(s)
if sz > 255 {
@@ -141,8 +264,7 @@ func writeDisplay(s string, w *bytes.Buffer) (int, error) {
w.Write([]byte{byte(sz)})
return w.WriteString(s)
}
-
-func writeSize(n uint32, w *bytes.Buffer) (int, error) {
+func writeSize(w *bytes.Buffer, n uint32) (int, error) {
bn := [4]byte{}
sz := numSize(n)
if sz > 4 {
@@ -154,175 +276,37 @@ func writeSize(n uint32, w *bytes.Buffer) (int, error) {
return w.Write(bn[c:])
}
-func parseSingle(op vm.Opcode, arg Arg, w io.Writer) (int, error) {
- var rn int
-
- 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) {
- var rn int
-
- 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
+type Batcher struct {
+ menuProcessor MenuProcessor
+ inMenu bool
}
-func parseDouble(op vm.Opcode, arg Arg, w io.Writer) (int, error) {
- var rn int
-
- v := arg.ArgDouble
- if v == nil {
- return 0, nil
+func NewBatcher(mp MenuProcessor) Batcher {
+ return Batcher{
+ menuProcessor: NewMenuProcessor(),
}
-
- 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
- }
-
- 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) {
- var rn int
-
- v := arg.ArgSized
- if v == nil {
+func(b *Batcher) MenuExit(w io.Writer) (int, error) {
+ if !b.inMenu {
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 = 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
+ b.inMenu = false
+ return w.Write(b.menuProcessor.ToLines())
}
-func parseNoarg(op vm.Opcode, arg Arg, w io.Writer) (int, error) {
- var rn int
-
- 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
+func(b *Batcher) MenuAdd(w io.Writer, code string, arg Arg) (int, error) {
+ b.inMenu = true
+ selector := ""
+ if arg.Selector != nil {
+ selector = *arg.Selector
}
- return rn, err
+ err := b.menuProcessor.Add(code, *arg.Sym, selector, *arg.Desc)
+ return 0, 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(b *Batcher) Exit(w io.Writer) (int, error) {
+ return b.MenuExit(w)
}
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
}
+ batch := Batcher{
+
+ }
+
var rn int
for _, v := range ast.Instructions {
log.Printf("parsing line %v: %v", v.OpCode, v.OpArg)
- op := vm.OpcodeIndex[v.OpCode]
- n, err := parseSized(op, v.OpArg, w)
- if err != nil {
- return n, err
- }
- if n > 0 {
+ op, ok := vm.OpcodeIndex[v.OpCode]
+ if !ok {
+ n, err := batch.MenuAdd(w, v.OpCode, v.OpArg)
rn += n
- n, err = parseFlag(op, v.OpArg, w)
if err != nil {
- return n, err
+ return rn, err
+ }
+ } else {
+ n, err := batch.MenuExit(w)
+ if err != nil {
+ return rn, err
}
rn += n
- continue
- }
- n, err = parseDisplay(op, v.OpArg, w)
- if err != nil {
- return n, err
- }
- if n > 0 {
- rn += n
- continue
- }
- 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 {
+ n, err = parseOne(op, v, w)
rn += n
- continue
+ if err != nil {
+ return rn, err
+ }
}
-
}
+ n, err := batch.Exit(w)
+ rn += n
+ if err != nil {
+ return rn, err
+ }
+ rn += n
+
return rn, err
}
diff --git a/go/asm/asm_test.go b/go/asm/asm_test.go
@@ -3,6 +3,7 @@ package asm
import (
"bytes"
"encoding/hex"
+ "fmt"
"log"
"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) {
var b []byte
b = vm.NewLine(b, vm.MAP, []string{"xyzzy"}, nil, nil)
@@ -173,23 +204,21 @@ func TestParserWriteMultiple(t *testing.T) {
if err != nil {
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())
- if n != n_expect {
- t.Fatalf("expected total %v bytes output, got %v", n_expect, n)
- }
+
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 %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)