commit 67babc58038077f9bf5767532a24bcd6e2543555
parent d898a995014e183ff41c690021cb1af0a2a9e114
Author: lash <dev@holbrook.no>
Date: Sun, 23 Apr 2023 08:33:34 +0100
Add error display capability in page render
Diffstat:
4 files changed, 141 insertions(+), 5 deletions(-)
diff --git a/render/page.go b/render/page.go
@@ -19,6 +19,8 @@ type Page struct {
menu *Menu // Menu rendererer.
sink *string // Content symbol rendered by dynamic size.
sizer *Sizer // Process size constraints.
+ err error // Error state to prepend to output.
+ errConst error // Use this error for display on all errors.
}
// NewPage creates a new Page object.
@@ -48,6 +50,26 @@ func(pg *Page) WithSizer(sizer *Sizer) *Page {
return pg
}
+// WithError adds an error to prepend to the page output.
+func(pg *Page) WithError(err error) *Page {
+ pg.err = err
+ return pg
+}
+
+func(pg *Page) WithOpaqueError(err error) *Page {
+ pg.errConst = err
+ return pg
+}
+
+func(pg *Page) Error() string {
+ if pg.err != nil {
+ if pg.errConst != nil {
+ return pg.errConst.Error()
+ }
+ }
+ return pg.err.Error()
+}
+
// Usage returns size used by values and menu, and remaining size available
func(pg *Page) Usage() (uint32, uint32, error) {
var l int
@@ -135,6 +157,14 @@ func(pg *Page) RenderTemplate(sym string, values map[string]string, idx uint16,
if err != nil {
return "", err
}
+ if pg.err != nil {
+ Logg.DebugCtxf(ctx, "Prepending error: %s", pg.err)
+ if len(tpl) == 0 {
+ tpl = pg.err.Error()
+ } else {
+ tpl = fmt.Sprintf("%s\n%s", pg.err, tpl)
+ }
+ }
if pg.sizer != nil {
values, err = pg.sizer.GetAt(values, idx)
if err != nil {
diff --git a/vm/input.go b/vm/input.go
@@ -20,6 +20,18 @@ var (
)
+type InvalidInputError struct {
+ input string
+}
+
+func NewInvalidInputError(input string) error {
+ return InvalidInputError{input}
+}
+
+func(e InvalidInputError) Error() string {
+ return "invalid input"
+}
+
// CheckInput validates the given byte string as client input.
func ValidInput(input []byte) error {
if !inputRegex.Match(input) {
diff --git a/vm/runner.go b/vm/runner.go
@@ -10,6 +10,21 @@ import (
"git.defalsify.org/vise.git/state"
)
+type ExternalCodeError struct {
+ sym string
+ err error
+}
+
+func NewExternalCodeError(sym string, err error) error {
+ return ExternalCodeError{sym, err}
+}
+
+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 {
@@ -129,6 +144,7 @@ func(vm *Vm) Run(b []byte, ctx context.Context) ([]byte, error) {
default:
err = fmt.Errorf("Unhandled state: %v", op)
}
+ b, err = vm.RunErrCheck(ctx, b, err)
if err != nil {
return b, err
}
@@ -145,6 +161,24 @@ func(vm *Vm) Run(b []byte, ctx context.Context) ([]byte, error) {
return b, nil
}
+func(vm *Vm) RunErrCheck(ctx context.Context, b []byte, err error) ([]byte, error) {
+ if err == nil {
+ return b, err
+ }
+ vm.pg = vm.pg.WithError(err)
+
+ v, err := vm.st.MatchFlag(state.FLAG_LOADFAIL, false)
+ if err != nil {
+ panic(err)
+ }
+ if !v {
+ return b, err
+ }
+
+ b = NewLine(nil, MOVE, []string{"_catch"}, nil, nil)
+ return b, nil
+}
+
// RunDeadCheck determines whether a state of empty bytecode should result in termination.
//
// If there is remaining bytecode, this method is a noop.
@@ -183,6 +217,8 @@ 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")
+ vm.pg.WithError(cerr)
b = NewLine(nil, MOVE, []string{"_catch"}, nil, nil)
return b, nil
}
@@ -479,13 +515,12 @@ func(vm *Vm) refresh(key string, rs resource.Resource, ctx context.Context) (str
input, _ := vm.st.GetInput()
r, err := fn(key, input, ctx)
if err != nil {
- Logg.TraceCtxf(ctx, "loadfail", "err", err)
var perr error
_, perr = vm.st.SetFlag(state.FLAG_LOADFAIL)
if perr != nil {
panic(err)
}
- return "", err
+ return "", NewExternalCodeError(key, err)
}
for _, flag := range r.FlagSet {
if !state.IsWriteableFlag(flag) {
@@ -513,7 +548,6 @@ func(vm *Vm) refresh(key string, rs resource.Resource, ctx context.Context) (str
if haveLang {
vm.st.SetLanguage(r.Content)
}
-
return r.Content, err
}
diff --git a/vm/runner_test.go b/vm/runner_test.go
@@ -19,6 +19,7 @@ type TestResource struct {
resource.MenuResource
state *state.State
RootCode []byte
+ CatchContent string
}
func getOne(sym string, input []byte, ctx context.Context) (resource.Result, error) {
@@ -46,6 +47,11 @@ func getEcho(sym string, input []byte, ctx context.Context) (resource.Result, er
}, nil
}
+
+func uhOh(sym string, input []byte, ctx context.Context) (resource.Result, error) {
+ return resource.Result{}, fmt.Errorf("uh-oh spaghetti'ohs")
+}
+
func setFlag(sym string, input []byte, ctx context.Context) (resource.Result, error) {
s := fmt.Sprintf("ping")
r := resource.Result{
@@ -86,13 +92,12 @@ func (r TestResource) GetTemplate(sym string, ctx context.Context) (string, erro
case "root":
return "root", nil
case "_catch":
- return "aiee", nil
+ return r.CatchContent, nil
case "ouf":
return "ouch", nil
case "flagCatch":
return "flagiee", nil
}
- panic(fmt.Sprintf("unknown symbol %s", sym))
return "", fmt.Errorf("unknown symbol %s", sym)
}
@@ -112,6 +117,8 @@ func (r TestResource) FuncFor(sym string) (resource.EntryFunc, error) {
return setFlag, nil
case "set_lang":
return set_lang, nil
+ case "aiee":
+ return uhOh, nil
}
return nil, fmt.Errorf("invalid function: '%s'", sym)
}
@@ -660,3 +667,56 @@ func TestSetLang(t *testing.T) {
t.Fatalf("expected language 'nor',, got %s", lang.Code)
}
}
+
+func TestLoadError(t *testing.T) {
+ st := state.NewState(0).WithDebug()
+ rs := TestResource{}
+ ca := cache.NewCache()
+ vm := NewVm(&st, &rs, ca, nil)
+
+ st.Down("root")
+ st.SetInput([]byte{})
+ b := NewLine(nil, LOAD, []string{"aiee"}, []byte{0x01, 0x10}, nil)
+ b = NewLine(b, HALT, nil, nil, nil)
+
+ var err error
+ ctx := context.TODO()
+ b, err = vm.Run(b, ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ r, err := vm.Render(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expect := `[aiee] uh-oh spaghetti'ohs
+0:repent`
+ if r != expect {
+ t.Fatalf("expected: \n\t%s\ngot:\n\t%s", expect, r)
+ }
+
+ rs.CatchContent = "foo"
+
+ st.Up()
+ st.SetInput([]byte{})
+ b = NewLine(nil, LOAD, []string{"aiee"}, []byte{0x01, 0x10}, nil)
+ b = NewLine(b, HALT, nil, nil, nil)
+
+ b, err = vm.Run(b, ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ r, err = vm.Render(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ expect = `[aiee] uh-oh spaghetti'ohs
+foo
+0:repent`
+ if r != expect {
+ t.Fatalf("expected: \n\t%s\ngot:\n\t%s", expect, r)
+ }
+
+}