commit f6e1d2bacc24b239e78a1a4b666fb2f7940c12ee
parent aefebf278a9b85e7774d1051fc21619527373f5c
Author: lash <dev@holbrook.no>
Date: Fri, 31 Mar 2023 17:27:10 +0100
Add router
Diffstat:
4 files changed, 181 insertions(+), 0 deletions(-)
diff --git a/draft.txt b/draft.txt
@@ -105,11 +105,14 @@ PREV <selector> <display>
GOTO <selector> <display> <symbol>
EXIT <selector> <display>
COND <selector> <display> <symbol> <[!]state>
+VAL <symbol>
kept in session as a router object:
SELECTORHASH|SYMBOLHASH
+Selectorhash 0x0 is VAL
+
---
diff --git a/go/router/router.go b/go/router/router.go
@@ -0,0 +1,80 @@
+package router
+
+import (
+ "fmt"
+)
+
+type Router struct {
+ selectors []string
+ symbols map[string]string
+}
+
+func NewRouter() Router {
+ return Router{
+ symbols: make(map[string]string),
+ }
+}
+
+func(r *Router) Add(selector string, symbol string) error {
+ if r.symbols[selector] != "" {
+ return fmt.Errorf("selector %v already set to symbol %v", selector, symbol)
+ }
+ l := len(selector)
+ if (l > 255) {
+ return fmt.Errorf("selector too long (is %v, max 255)", l)
+ }
+ l = len(symbol)
+ if (l > 255) {
+ return fmt.Errorf("symbol too long (is %v, max 255)", l)
+ }
+ r.selectors = append(r.selectors, selector)
+ r.symbols[selector] = symbol
+ return nil
+}
+
+func(r *Router) Next() []byte {
+ if len(r.selectors) == 0 {
+ return []byte{}
+ }
+ k := r.selectors[0]
+ r.selectors = r.selectors[1:]
+ v := r.symbols[k]
+ if len(r.selectors) == 0 {
+ r.symbols = nil
+ } else {
+ delete(r.symbols, k)
+ }
+ lk := len(k)
+ lv := len(v)
+ b := []byte{uint8(lk)}
+ b = append(b, k...)
+ b = append(b, uint8(lv))
+ b = append(b, v...)
+ return b
+}
+
+func(r *Router) ToBytes() []byte {
+ b := []byte{}
+ for true {
+ v := r.Next()
+ if len(v) == 0 {
+ break
+ }
+ b = append(b, v...)
+ }
+ return b
+}
+
+func FromBytes(b []byte) Router {
+ rb := NewRouter()
+ for len(b) > 0 {
+ l := b[0]
+ k := b[1:1+l]
+ b = b[1+l:]
+ l = b[0]
+ v := b[1:1+l]
+ b = b[1+l:]
+ rb.Add(string(k), string(v))
+ }
+ return rb
+}
diff --git a/go/router/router_test.go b/go/router/router_test.go
@@ -0,0 +1,89 @@
+package router
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestRouter(t *testing.T) {
+ r := NewRouter()
+ err := r.Add("foo", "bar")
+ if err != nil {
+ t.Error(err)
+ }
+ err = r.Add("baz", "barbarbar")
+ if err != nil {
+ t.Error(err)
+ }
+ err = r.Add("foo", "xyzzy")
+ if err == nil {
+ t.Errorf("expected error for duplicate key foo")
+ }
+}
+
+func TestRouterOut(t *testing.T) {
+ rt := NewRouter()
+ err := rt.Add("foo", "inky")
+ if err != nil {
+ t.Error(err)
+ }
+ err = rt.Add("barbar", "pinky")
+ if err != nil {
+ t.Error(err)
+ }
+ err = rt.Add("bazbazbaz", "blinky")
+ if err != nil {
+ t.Error(err)
+ }
+ rb := []byte{}
+ r := rt.Next()
+ expect := append([]byte{0x3}, []byte("foo")...)
+ expect = append(expect, 4)
+ expect = append(expect, []byte("inky")...)
+ if !bytes.Equal(r, expect) {
+ t.Errorf("expected %v, got %v", expect, r)
+ }
+ rb = append(rb, r...)
+
+ r = rt.Next()
+ expect = append([]byte{0x6}, []byte("barbar")...)
+ expect = append(expect, 5)
+ expect = append(expect, []byte("pinky")...)
+ if !bytes.Equal(r, expect) {
+ t.Errorf("expected %v, got %v", expect, r)
+ }
+ rb = append(rb, r...)
+
+ r = rt.Next()
+ expect = append([]byte{0x9}, []byte("bazbazbaz")...)
+ expect = append(expect, 6)
+ expect = append(expect, []byte("blinky")...)
+ if !bytes.Equal(r, expect) {
+ t.Errorf("expected %v, got %v", expect, r)
+ }
+ rb = append(rb, r...)
+}
+
+func TestSerialize(t *testing.T) {
+ rt := NewRouter()
+ err := rt.Add("foo", "inky")
+ if err != nil {
+ t.Error(err)
+ }
+ err = rt.Add("barbar", "pinky")
+ if err != nil {
+ t.Error(err)
+ }
+ err = rt.Add("bazbazbaz", "blinky")
+ if err != nil {
+ t.Error(err)
+ }
+
+ // Serialize and deserialize.
+ ra := rt.ToBytes()
+ rt = FromBytes(ra)
+ rb := rt.ToBytes()
+ if !bytes.Equal(ra, rb) {
+ t.Errorf("expected %v, got %v", ra, rb)
+ }
+}
diff --git a/go/vm/vm.go b/go/vm/vm.go
@@ -12,6 +12,15 @@ import (
type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error)
+func Apply(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+ var err error
+ st, instruction, err = Run(instruction, st, rs, ctx)
+ if err != nil {
+ return st, instruction, err
+ }
+ return st, instruction, nil
+}
+
func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
var err error
for len(instruction) > 0 {