commit 8cf6dbaec7f7d0f2dbeaf0f14cd66c35cbf1588e
parent 9f848e7012246d654edd6da8c087673719a1ede2
Author: lash <dev@holbrook.no>
Date:   Sun, 23 Apr 2023 09:20:50 +0100
Add mem resource
Diffstat:
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)
 	}
 }