diff --git a/README.md b/README.md index b7438e9..4884f75 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The VM defines the following opcode symbols: * `RELOAD ` - Execute a code symbol already loaded by `LOAD` and cache the data, constrained to the previously given `size` for the same symbol. * `MAP ` - Expose a code symbol previously loaded by `LOAD` to the rendering client. Roughly corresponds to the `global` directive in Python. * `MOVE ` - Create a new execution frame, invalidating all previous `MAP` calls. More detailed: After a `MOVE` call, a `BACK` call will return to the same execution frame, with the same symbols available, but all `MAP` calls will have to be repeated. +* 'HALT' - Stop execution. The remaining bytecode (typicaly, the routing code for the node) is returned to the invoking function. ### External code diff --git a/go/vm/opcodes.go b/go/vm/opcodes.go index b59d607..c1c0e14 100644 --- a/go/vm/opcodes.go +++ b/go/vm/opcodes.go @@ -6,14 +6,15 @@ import ( const VERSION = 0 const ( - BACK = iota - CATCH - CROAK - LOAD - RELOAD - MAP - MOVE - _MAX + BACK = 0 + CATCH = 1 + CROAK = 2 + LOAD = 3 + RELOAD = 4 + MAP = 5 + MOVE = 6 + HALT = 7 + _MAX = 7 ) func NewLine(instructionList []byte, instruction uint16, args []string, post []byte, szPost []uint8) []byte { diff --git a/go/vm/vm.go b/go/vm/vm.go index 5de4205..15b10a0 100644 --- a/go/vm/vm.go +++ b/go/vm/vm.go @@ -96,6 +96,9 @@ func Run(instruction []byte, st *state.State, rs resource.Resource, ctx context. instruction, err = RunMove(instruction[2:], st, rs, ctx) case BACK: instruction, err = RunBack(instruction[2:], st, rs, ctx) + case HALT: + log.Printf("found HALT, stopping") + return instruction[2:], err default: err = fmt.Errorf("Unhandled state: %v", op) } diff --git a/go/vm/vm_test.go b/go/vm/vm_test.go index 7f7acb3..59bd524 100644 --- a/go/vm/vm_test.go +++ b/go/vm/vm_test.go @@ -50,6 +50,8 @@ func (r *TestResource) GetTemplate(sym string) (string, error) { return "inky pinky {{.baz}} blinky clyde", nil case "three": return "{{.one}} inky pinky {{.three}} blinky clyde {{.two}}", nil + case "_catch": + return "aiee", nil } panic(fmt.Sprintf("unknown symbol %s", sym)) return "", fmt.Errorf("unknown symbol %s", sym) @@ -253,108 +255,112 @@ func TestRunArgInvalid(t *testing.T) { } } -//func TestRunArgInstructions(t *testing.T) { -// st := state.NewState(5) -// rs := TestResource{} -// -// rt := router.NewRouter() -// rt.Add("foo", "bar") -// b := []byte{0x03} -// b = append(b, []byte("foo")...) -// b = append(b, rt.ToBytes()...) -// -// bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) -// bi = NewLine(bi, LOAD, []string{"two"}, nil, []uint8{3}) -// bi = NewLine(bi, MAP, []string{"one"}, nil, nil) -// bi = NewLine(bi, MAP, []string{"two"}, nil, nil) -// var err error -// b, err = Apply(b, bi, &st, &rs, 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) -// } -// loc := st.Where() -// if loc != "bar" { -// t.Errorf("expected where-state bar, got %v", loc) -// } -// m, err := st.Get() -// if err != nil { -// t.Fatal(err) -// } -// _, err = rs.RenderTemplate(loc, m) -// if err == nil { -// t.Fatalf("expected error to generate template") -// } -// _, err = Run(bi, &st, &rs, context.TODO()) -// if err != nil { -// t.Error(err) -// } -// m, err = st.Get() -// if err != nil { -// t.Fatal(err) -// } -// _, err = rs.RenderTemplate(loc, m) -// if err != nil { -// t.Fatal(err) -// } -//} -// -//func TestRunMoveAndBack(t *testing.T) { -// st := state.NewState(5) -// rs := TestResource{} -// rt := router.NewRouter() -// rt.Add("foo", "bar") -// b := []byte{0x03} -// b = append(b, []byte("foo")...) -// //b = append(b, rt.ToBytes()...) -// bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) -// -// var err error -// b, err = Apply(b, bi, &st, &rs, 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) -// } -// -// rt = router.NewRouter() -// rt.Add("foo", "baz") -// b = []byte{0x03} -// b = append(b, []byte("foo")...) -// b = append(b, rt.ToBytes()...) -// bi = NewLine([]byte{}, LOAD, []string{"two"}, nil, []uint8{0}) -// b, err = Apply(b, bi, &st, &rs, 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) -// } -// -// rt = router.NewRouter() -// rt.Add("foo", "_") -// b = []byte{0x03} -// b = append(b, []byte("foo")...) -// //b = append(b, rt.ToBytes()...) -// b, err = Apply(b, rt.ToBytes(), &st, &rs, 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) -// } -// loc := st.Where() -// if loc != "bar" { -// t.Errorf("expected where-string 'bar', got %v", loc) -// } -//} +func TestRunArgInstructions(t *testing.T) { + t.Skip("pending fix for separating router code from executing code") + st := state.NewState(5) + rs := TestResource{} + + rt := router.NewRouter() + rt.Add("foo", "bar") + b := []byte{0x03} + b = append(b, []byte("foo")...) + + bi := NewLine(rt.ToBytes(), LOAD, []string{"one"}, nil, []uint8{0}) + bi = NewLine(bi, LOAD, []string{"two"}, nil, []uint8{3}) + bi = NewLine(bi, MAP, []string{"one"}, nil, nil) + bi = NewLine(bi, MAP, []string{"two"}, nil, nil) + var err error + b, err = Apply(b, bi, &st, &rs, 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) + } + loc := st.Where() + if loc != "bar" { + t.Errorf("expected where-state bar, got %v", loc) + } + m, err := st.Get() + if err != nil { + t.Fatal(err) + } + r, err := rs.RenderTemplate(loc, m) + if err != nil { + t.Fatal(err) //f("expected error to generate template") + } + if r != "aiee" { + t.Fatalf("expected result 'aiee', got '%v'", r) + } + _, err = Run(bi, &st, &rs, context.TODO()) + if err != nil { + t.Error(err) + } + m, err = st.Get() + if err != nil { + t.Fatal(err) + } + _, err = rs.RenderTemplate(loc, m) + if err != nil { + t.Fatal(err) + } +} + +func TestRunMoveAndBack(t *testing.T) { + t.Skip("pending fix for separating router code from executing code") + st := state.NewState(5) + rs := TestResource{} + rt := router.NewRouter() + rt.Add("foo", "bar") + b := []byte{0x03} + b = append(b, []byte("foo")...) + //b = append(b, rt.ToBytes()...) + bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) + + var err error + b, err = Apply(b, bi, &st, &rs, 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) + } + + rt = router.NewRouter() + rt.Add("foo", "baz") + b = []byte{0x03} + b = append(b, []byte("foo")...) + b = append(b, rt.ToBytes()...) + bi = NewLine([]byte{}, LOAD, []string{"two"}, nil, []uint8{0}) + b, err = Apply(b, bi, &st, &rs, 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) + } + + rt = router.NewRouter() + rt.Add("foo", "_") + b = []byte{0x03} + b = append(b, []byte("foo")...) + //b = append(b, rt.ToBytes()...) + b, err = Apply(b, rt.ToBytes(), &st, &rs, 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) + } + loc := st.Where() + if loc != "bar" { + t.Errorf("expected where-string 'bar', got %v", loc) + } +} func TestCatchAndBack(t *testing.T) { st := state.NewState(5) @@ -396,3 +402,24 @@ func TestCatchAndBack(t *testing.T) { t.Error(err) } } + + +func TestHalt(t *testing.T) { + st := state.NewState(5) + rs := TestResource{} + b := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) + b = NewLine(b, HALT, nil, nil, nil) + b = NewLine(b, MOVE, []string{"foo"}, nil, nil) + var err error + b, err = Run(b, &st, &rs, context.TODO()) + if err != nil { + t.Error(err) + } + r := st.Where() + if r == "foo" { + t.Fatalf("Expected where-symbol not to be 'foo'") + } + if !bytes.Equal(b[:2], []byte{0x00, MOVE}) { + t.Fatalf("Expected MOVE instruction, found '%v'", b) + } +}