go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

commit 8cf6dbaec7f7d0f2dbeaf0f14cd66c35cbf1588e
parent 9f848e7012246d654edd6da8c087673719a1ede2
Author: lash <dev@holbrook.no>
Date:   Sun, 23 Apr 2023 09:20:50 +0100

Add mem resource

Diffstat:
Mrender/page.go | 6+++++-
Mrender/page_test.go | 18++++++++++++++++++
Mresource/fs.go | 2+-
Aresource/mem.go | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aresource/mem_test.go | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mresource/resource.go | 6+++---
Mvm/input.go | 5++++-
Mvm/runner.go | 11++++++++---
Mvm/runner_test.go | 19+++++++++++++++----
9 files changed, 191 insertions(+), 13 deletions(-)

diff --git a/render/page.go b/render/page.go @@ -56,12 +56,16 @@ func(pg *Page) WithError(err error) *Page { return pg } +// WithOpaqueError sets an error which will be used for display regardless of which error was set using WithError func(pg *Page) WithOpaqueError(err error) *Page { pg.errConst = err return pg } -func(pg *Page) Error() string { +// Error implements error interface. +// +// This error is used internally by the renderer, and is not intended for direct use. +func(pg *Page) ErrorString() string { if pg.err != nil { if pg.errConst != nil { return pg.errConst.Error() diff --git a/render/page_test.go b/render/page_test.go @@ -1,9 +1,11 @@ package render import ( + "fmt" "testing" "git.defalsify.org/vise.git/cache" + "git.defalsify.org/vise.git/resource" ) @@ -113,3 +115,19 @@ func TestStateMapSink(t *testing.T) { t.Error(err) } } + +func TestWithError(t *testing.T) { + ca := cache.NewCache() + rs := resource.NewMemResource() + pg := NewPage(ca, rs) + ca.Push() + + mn := NewMenu().WithOutputSize(32) + err := mn.Put("0", "aiee") + if err != nil { + t.Fatal(err) + } + err = fmt.Errorf("my humps") + pg = pg.WithMenu(mn).WithError(err) + +} diff --git a/resource/fs.go b/resource/fs.go @@ -19,7 +19,7 @@ type FsResource struct { fns map[string]EntryFunc } -func NewFsResource(path string) (FsResource) { +func NewFsResource(path string) FsResource { absPath, err := filepath.Abs(path) if err != nil { panic(err) diff --git a/resource/mem.go b/resource/mem.go @@ -0,0 +1,62 @@ +package resource + +import ( + "context" + "fmt" +) + +type MemResource struct { + MenuResource + templates map[string]string + bytecodes map[string][]byte + funcs map[string]EntryFunc +} + +func NewMemResource() MemResource { + mr := MemResource{ + templates: make(map[string]string), + bytecodes: make(map[string][]byte), + funcs: make(map[string]EntryFunc), + } + mr.WithCodeGetter(mr.getCode) + mr.WithTemplateGetter(mr.getTemplate) + mr.WithEntryFuncGetter(mr.getFunc) + return mr +} + +func(mr MemResource) getTemplate(sym string, ctx context.Context) (string, error) { + r, ok := mr.templates[sym] + if !ok { + return "", fmt.Errorf("unknown template symbol: %s", sym) + } + return r, nil +} + +func(mr MemResource) getCode(sym string) ([]byte, error) { + r, ok := mr.bytecodes[sym] + if !ok { + return nil, fmt.Errorf("unknown bytecode: %s", sym) + } + return r, nil +} + +func(mr MemResource) getFunc(sym string) (EntryFunc, error) { + r, ok := mr.funcs[sym] + if !ok { + return nil, fmt.Errorf("unknown entry func: %s", sym) + } + return r, nil +} + +func(mr *MemResource) AddTemplate(sym string, tpl string) { + mr.templates[sym] = tpl +} + + +func(mr *MemResource) AddEntryFunc(sym string, fn EntryFunc) { + mr.funcs[sym] = fn +} + +func(mr *MemResource) AddBytecode(sym string, code []byte) { + mr.bytecodes[sym] = code +} diff --git a/resource/mem_test.go b/resource/mem_test.go @@ -0,0 +1,75 @@ +package resource + +import ( + "bytes" + "context" + "fmt" + "testing" +) + +func testEntry(sym string, input []byte, ctx context.Context) (Result, error) { + return Result{ + Content: fmt.Sprintf("%sbar", input), + }, nil +} + +func TestMemResourceTemplate(t *testing.T) { + rs := NewMemResource() + rs.AddTemplate("foo", "bar") + + ctx := context.TODO() + r, err := rs.GetTemplate("foo", ctx) + if err != nil { + t.Fatal(err) + } + if r != "bar" { + fmt.Errorf("expected 'bar', got %s", r) + } + + _, err = rs.GetTemplate("bar", ctx) + if err == nil { + t.Fatalf("expected error") + } +} + +func TestMemResourceCode(t *testing.T) { + rs := NewMemResource() + rs.AddBytecode("foo", []byte("bar")) + + r, err := rs.GetCode("foo") + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(r, []byte("bar")) { + fmt.Errorf("expected 'bar', got %x", r) + } + + _, err = rs.GetCode("bar") + if err == nil { + t.Fatalf("expected error") + } +} + +func TestMemResourceEntry(t *testing.T) { + rs := NewMemResource() + rs.AddEntryFunc("foo", testEntry) + + fn, err := rs.FuncFor("foo") + if err != nil { + t.Fatal(err) + } + + ctx := context.TODO() + r, err := fn("foo", []byte("xyzzy"), ctx) + if err != nil { + t.Fatal(err) + } + if r.Content != "foobar" { + fmt.Errorf("expected 'foobar', got %x", r.Content) + } + + _, err = rs.FuncFor("bar") + if err == nil { + t.Fatalf("expected error") + } +} diff --git a/resource/resource.go b/resource/resource.go @@ -58,16 +58,16 @@ func(m *MenuResource) WithTemplateGetter(templateGetter TemplateFunc) *MenuResou } // FuncFor implements Resource interface -func(m *MenuResource) FuncFor(sym string) (EntryFunc, error) { +func(m MenuResource) FuncFor(sym string) (EntryFunc, error) { return m.funcFunc(sym) } // GetCode implements Resource interface -func(m *MenuResource) GetCode(sym string) ([]byte, error) { +func(m MenuResource) GetCode(sym string) ([]byte, error) { return m.codeFunc(sym) } // GetTemplate implements Resource interface -func(m *MenuResource) GetTemplate(sym string, ctx context.Context) (string, error) { +func(m MenuResource) GetTemplate(sym string, ctx context.Context) (string, error) { return m.templateFunc(sym, ctx) } diff --git a/vm/input.go b/vm/input.go @@ -20,16 +20,19 @@ var ( ) +// InvalidInputError indicates client input that was unhandled by the bytecode (INCMP fallthrough) type InvalidInputError struct { input string } +// NewInvalidInputError creates a new InvalidInputError func NewInvalidInputError(input string) error { return InvalidInputError{input} } +// Error implements error interface. func(e InvalidInputError) Error() string { - return "invalid input" + return fmt.Sprintf("invalid input: '%s'", e.input) } // CheckInput validates the given byte string as client input. diff --git a/vm/runner.go b/vm/runner.go @@ -10,21 +10,22 @@ import ( "git.defalsify.org/vise.git/state" ) +// ExternalCodeError indicates an error that occurred when resolving an external code symbol (LOAD, RELOAD). type ExternalCodeError struct { sym string err error } +// NewExternalCodeError creates a new ExternalCodeError. func NewExternalCodeError(sym string, err error) error { return ExternalCodeError{sym, err} } +// Error implements error interface func(e ExternalCodeError) Error() string { return fmt.Sprintf("[%s] %v", e.sym, e.err) } - - // Vm holds sub-components mutated by the vm execution. // TODO: Renderer should be passed to avoid proxy methods not strictly related to vm operation type Vm struct { @@ -217,7 +218,11 @@ func(vm *Vm) RunDeadCheck(b []byte, ctx context.Context) ([]byte, error) { if location == "" { return b, fmt.Errorf("dead runner with no current location") } - cerr := fmt.Errorf("invalid input") + input, err := vm.st.GetInput() + if err != nil { + input = []byte("(no input)") + } + cerr := NewInvalidInputError(string(input)) vm.pg.WithError(cerr) b = NewLine(nil, MOVE, []string{"_catch"}, nil, nil) return b, nil diff --git a/vm/runner_test.go b/vm/runner_test.go @@ -363,13 +363,24 @@ func TestRunArgInvalid(t *testing.T) { st.Down("root") b := NewLine(nil, INCMP, []string{"bar", "baz"}, nil, nil) - b, err = vm.Run(b, context.TODO()) + ctx := context.TODO() + b, err = vm.Run(b, ctx) if err != nil { t.Fatal(err) } - r, _ := st.Where() - if r != "_catch" { - t.Fatalf("expected where-state _catch, got %v", r) + location, _ := st.Where() + if location != "_catch" { + t.Fatalf("expected where-state _catch, got %v", location) + } + + r, err := vm.Render(ctx) + if err != nil { + t.Fatal(err) + } + expect := `invalid input: 'foo' +0:repent` + if r != expect { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s", expect, r) } }