diff --git a/go/router/router.go b/go/router/router.go index eedea83..4ec7cf3 100644 --- a/go/router/router.go +++ b/go/router/router.go @@ -7,20 +7,17 @@ import ( type Router struct { selectors []string symbols map[string]string - navigable bool } func NewRouter() Router { return Router{ symbols: make(map[string]string), - navigable: true, } } func NewStaticRouter(symbol string) Router { return Router{ symbols: map[string]string{"_": symbol}, - navigable: false, } } @@ -32,6 +29,9 @@ func(r *Router) Add(selector string, symbol string) error { if (l > 255) { return fmt.Errorf("selector too long (is %v, max 255)", l) } + if selector[0] == '_' { + return fmt.Errorf("Invalid selector prefix '_'") + } l = len(symbol) if (l > 255) { return fmt.Errorf("symbol too long (is %v, max 255)", l) diff --git a/go/state/state.go b/go/state/state.go index bf84f23..3ece243 100644 --- a/go/state/state.go +++ b/go/state/state.go @@ -34,6 +34,14 @@ func NewState(bitSize uint64) State { return st } +func(st State) Where() string { + if len(st.ExecPath) == 0 { + return "" + } + l := len(st.ExecPath) + return st.ExecPath[l-1] +} + func(st State) WithCacheSize(cacheSize uint32) State { st.CacheSize = cacheSize return st diff --git a/go/vm/opcodes.go b/go/vm/opcodes.go index 23aed1c..3960338 100644 --- a/go/vm/opcodes.go +++ b/go/vm/opcodes.go @@ -1,13 +1,35 @@ package vm +import ( + "encoding/binary" +) const VERSION = 0 const ( - CATCH = iota + BACK = iota + CATCH CROAK LOAD RELOAD MAP SINK + MOVE _MAX ) + +func NewLine(instructionList []byte, instruction uint16, args []string, post []byte, szPost []uint8) []byte { + b := []byte{0x00, 0x00} + binary.BigEndian.PutUint16(b, instruction) + for _, arg := range args { + b = append(b, uint8(len(arg))) + b = append(b, []byte(arg)...) + } + if post != nil { + b = append(b, uint8(len(post))) + b = append(b, post...) + } + if szPost != nil { + b = append(b, szPost...) + } + return append(instructionList, b...) +} diff --git a/go/vm/vm.go b/go/vm/vm.go index 76e9f3a..d61c925 100644 --- a/go/vm/vm.go +++ b/go/vm/vm.go @@ -13,19 +13,39 @@ import ( type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) +func argFromBytes(input []byte) (string, []byte, error) { + if len(input) == 0 { + return "", input, fmt.Errorf("zero length input") + } + sz := input[0] + out := input[1:1+sz] + return string(out), input[1+sz:], nil +} + func Apply(input []byte, instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { var err error + + arg, input, err := argFromBytes(input) + if err != nil { + return st, input, err + } + + rt := router.FromBytes(input) + sym := rt.Get(arg) + if sym == "" { + sym = rt.Default() + st.PutArg(arg) + } + if sym == "" { + instruction = NewLine([]byte{}, MOVE, []string{"_catch"}, nil , nil) + } else { + instruction = NewLine(instruction, MOVE, []string{sym}, nil, nil) + } + st, instruction, err = Run(instruction, st, rs, ctx) if err != nil { return st, instruction, err } - rt := router.FromBytes(instruction) - - sym := rt.Get(string(input)) - if sym == "" { - sym = rt.Default() - st.PutArg(string(input)) - } return st, instruction, nil } @@ -56,6 +76,9 @@ func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Co case SINK: st, instruction, err = RunSink(instruction[2:], st, rs, ctx) break + case MOVE: + st, instruction, err = RunMove(instruction[2:], st, rs, ctx) + break default: err = fmt.Errorf("Unhandled state: %v", op) } @@ -88,8 +111,9 @@ func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx conte if err != nil { return st, instruction, err } + _ = tail st.Add(head, r, uint32(len(r))) - return st, tail, nil + return st, []byte{}, nil } func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { @@ -98,8 +122,9 @@ func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx conte return st, instruction, err } _ = head + _ = tail st.Reset() - return st, tail, nil + return st, []byte{}, nil } func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { @@ -135,7 +160,12 @@ func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx cont } func RunMove(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { - return st, nil, nil + head, tail, err := instructionSplit(instruction) + if err != nil { + return st, instruction, err + } + st.Down(head) + return st, tail, nil } func refresh(key string, sym []byte, rs resource.Fetcher, ctx context.Context) (string, error) { diff --git a/go/vm/vm_test.go b/go/vm/vm_test.go index 0c1c855..4de5253 100644 --- a/go/vm/vm_test.go +++ b/go/vm/vm_test.go @@ -3,13 +3,13 @@ package vm import ( "bytes" "context" - "encoding/binary" "fmt" "log" "testing" "text/template" "git.defalsify.org/festive/resource" + "git.defalsify.org/festive/router" "git.defalsify.org/festive/state" ) @@ -159,8 +159,8 @@ func TestRunMultiple(t *testing.T) { st := state.NewState(5) rs := TestResource{} b := []byte{} - b = NewTestOp(b, LOAD, []string{"one"}, nil, []uint8{0}) - b = NewTestOp(b, LOAD, []string{"two"}, nil, []uint8{42}) + b = NewLine(b, LOAD, []string{"one"}, nil, []uint8{0}) + b = NewLine(b, LOAD, []string{"two"}, nil, []uint8{42}) st, _, err := Run(b, st, &rs, context.TODO()) if err != nil { t.Error(err) @@ -171,8 +171,8 @@ func TestRunReload(t *testing.T) { st := state.NewState(5) rs := TestResource{} b := []byte{} - b = NewTestOp(b, LOAD, []string{"dyn"}, nil, []uint8{0}) - b = NewTestOp(b, MAP, []string{"dyn"}, nil, nil) + b = NewLine(b, LOAD, []string{"dyn"}, nil, []uint8{0}) + b = NewLine(b, MAP, []string{"dyn"}, nil, nil) st, _, err := Run(b, st, &rs, context.TODO()) if err != nil { t.Error(err) @@ -186,7 +186,7 @@ func TestRunReload(t *testing.T) { } dynVal = "baz" b = []byte{} - b = NewTestOp(b, RELOAD, []string{"dyn"}, nil, nil) + b = NewLine(b, RELOAD, []string{"dyn"}, nil, nil) st, _, err = Run(b, st, &rs, context.TODO()) if err != nil { t.Error(err) @@ -202,19 +202,50 @@ func TestRunReload(t *testing.T) { } -func NewTestOp(instructionList []byte, instruction uint16, args []string, post []byte, szPost []uint8) []byte { - b := []byte{0x00, 0x00} - binary.BigEndian.PutUint16(b, instruction) - for _, arg := range args { - b = append(b, uint8(len(arg))) - b = append(b, []byte(arg)...) +func TestRunArg(t *testing.T) { + st := state.NewState(5) + rt := router.NewRouter() + rt.Add("foo", "bar") + rt.Add("baz", "xyzzy") + b := []byte{0x03} + b = append(b, []byte("baz")...) + b = append(b, rt.ToBytes()...) + var err error + st, b, err = Apply(b, []byte{}, st, nil, context.TODO()) + if err != nil { + t.Error(err) } - if post != nil { - b = append(b, uint8(len(post))) - b = append(b, post...) + l := len(b) + if l != 0 { + t.Errorf("expected empty remainder, got length %v: %v", l, b) } - if szPost != nil { - b = append(b, szPost...) + r := st.Where() + if r != "xyzzy" { + t.Errorf("expected where-state baz, got %v", r) } - return append(instructionList, b...) } + +func TestRunArgInvalid(t *testing.T) { + st := state.NewState(5) + rt := router.NewRouter() + rt.Add("foo", "bar") + rt.Add("baz", "xyzzy") + b := []byte{0x03} + b = append(b, []byte("bar")...) + b = append(b, rt.ToBytes()...) + var err error + st, b, err = Apply(b, []byte{}, st, nil, context.TODO()) + if err != nil { + t.Error(err) + } + l := len(b) + if l != 0 { + t.Errorf("expected empty remainder, got length %v: %v", l, b) + } + r := st.Where() + if r != "_catch" { + t.Errorf("expected where-state _catch, got %v", r) + } + +} +