runner.go (12515B)
1 package vm 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "git.defalsify.org/vise.git/cache" 9 "git.defalsify.org/vise.git/render" 10 "git.defalsify.org/vise.git/resource" 11 "git.defalsify.org/vise.git/state" 12 ) 13 14 // ExternalCodeError indicates an error that occurred when resolving an external code symbol (LOAD, RELOAD). 15 type ExternalCodeError struct { 16 sym string 17 code int 18 err error 19 } 20 21 // NewExternalCodeError creates a new ExternalCodeError. 22 func NewExternalCodeError(sym string, err error) *ExternalCodeError { 23 return &ExternalCodeError{ 24 sym: sym, 25 err: err, 26 } 27 } 28 29 func(e *ExternalCodeError) WithCode(code int) *ExternalCodeError { 30 e.code = code 31 return e 32 } 33 34 // Error implements the Error interface. 35 func(e ExternalCodeError) Error() string { 36 logg.Errorf("external code error: %v", e.err) 37 return fmt.Sprintf("error %v:%v", e.sym, e.code) 38 } 39 40 // Vm holds sub-components mutated by the vm execution. 41 // TODO: Renderer should be passed to avoid proxy methods not strictly related to vm operation 42 type Vm struct { 43 st *state.State // Navigation and error states. 44 rs resource.Resource // Retrieves content, code, and templates for symbols. 45 ca cache.Memory // Loaded content. 46 mn *render.Menu // Menu component of page. 47 sizer *render.Sizer // Apply size constraints to output. 48 pg *render.Page // Render outputs with menues to size constraints. 49 } 50 51 // NewVm creates a new Vm. 52 func NewVm(st *state.State, rs resource.Resource, ca cache.Memory, sizer *render.Sizer) *Vm { 53 vmi := &Vm{ 54 st: st, 55 rs: rs, 56 ca: ca, 57 pg: render.NewPage(ca, rs), 58 sizer: sizer, 59 } 60 vmi.Reset() 61 logg.Infof("vm created with state", "state", st, "renderer", vmi.pg) 62 return vmi 63 } 64 65 // Reset re-initializes sub-components for output rendering. 66 func(vmi *Vm) Reset() { 67 vmi.mn = render.NewMenu() 68 vmi.pg.Reset() 69 vmi.pg = vmi.pg.WithMenu(vmi.mn) 70 if vmi.sizer != nil { 71 vmi.pg = vmi.pg.WithSizer(vmi.sizer) 72 } 73 } 74 75 // Run extracts individual op codes and arguments and executes them. 76 // 77 // Each step may update the state. 78 // 79 // On error, the remaining instructions will be returned. State will not be rolled back. 80 func(vm *Vm) Run(ctx context.Context, b []byte) ([]byte, error) { 81 logg.Tracef("new vm run") 82 running := true 83 for running { 84 r := vm.st.MatchFlag(state.FLAG_TERMINATE, true) 85 if r { 86 logg.InfoCtxf(ctx, "terminate set! bailing") 87 return []byte{}, nil 88 } 89 90 _ = vm.st.ResetFlag(state.FLAG_TERMINATE) 91 92 change := vm.st.ResetFlag(state.FLAG_LANG) 93 if change { 94 if vm.st.Language != nil { 95 ctx = context.WithValue(ctx, "Language", *vm.st.Language) 96 } 97 } 98 99 waitChange := vm.st.ResetFlag(state.FLAG_WAIT) 100 if waitChange { 101 vm.st.ResetFlag(state.FLAG_INMATCH) 102 vm.pg.Reset() 103 vm.mn.Reset() 104 } 105 106 _ = vm.st.SetFlag(state.FLAG_DIRTY) 107 op, bb, err := opSplit(b) 108 if err != nil { 109 return b, err 110 } 111 b = bb 112 logg.DebugCtxf(ctx, "execute code", "opcode", op, "op", OpcodeString[op], "code", b) 113 logg.DebugCtxf(ctx, "", "state", vm.st) 114 switch op { 115 case CATCH: 116 b, err = vm.runCatch(ctx, b) 117 case CROAK: 118 b, err = vm.runCroak(ctx, b) 119 case LOAD: 120 b, err = vm.runLoad(ctx, b) 121 case RELOAD: 122 b, err = vm.runReload(ctx, b) 123 case MAP: 124 b, err = vm.runMap(ctx, b) 125 case MOVE: 126 b, err = vm.runMove(ctx, b) 127 case INCMP: 128 b, err = vm.runInCmp(ctx, b) 129 case MSINK: 130 b, err = vm.runMSink(ctx, b) 131 case MOUT: 132 b, err = vm.runMOut(ctx, b) 133 case MNEXT: 134 b, err = vm.runMNext(ctx, b) 135 case MPREV: 136 b, err = vm.runMPrev(ctx, b) 137 case HALT: 138 b, err = vm.runHalt(ctx, b) 139 return b, err 140 default: 141 err = fmt.Errorf("Unhandled state: %v", op) 142 } 143 b, err = vm.runErrCheck(ctx, b, err) 144 if err != nil { 145 return b, err 146 } 147 if len(b) == 0 { 148 b, err = vm.runDeadCheck(ctx, b) 149 if err != nil { 150 return b, err 151 } 152 } 153 if len(b) == 0 { 154 return []byte{}, nil 155 } 156 } 157 return b, nil 158 } 159 160 // handles errors that should not be deferred to the client. 161 func(vm *Vm) runErrCheck(ctx context.Context, b []byte, err error) ([]byte, error) { 162 if err == nil { 163 return b, err 164 } 165 vm.pg = vm.pg.WithError(err) 166 167 v := vm.st.MatchFlag(state.FLAG_LOADFAIL, true) 168 if !v { 169 return b, err 170 } 171 172 b = NewLine(nil, MOVE, []string{"_catch"}, nil, nil) 173 return b, nil 174 } 175 176 // determines whether a state of empty bytecode should result in termination. 177 // 178 // If there is remaining bytecode, this method is a noop. 179 // 180 // If input has not been matched, a default invalid input page should be generated aswell as a possiblity of return to last screen (or exit). 181 // 182 // If the termination flag has been set but not yet handled, execution is allowed to terminate. 183 func(vm *Vm) runDeadCheck(ctx context.Context, b []byte) ([]byte, error) { 184 if len(b) > 0 { 185 return b, nil 186 } 187 r := vm.st.MatchFlag(state.FLAG_READIN, false) 188 if r { 189 logg.DebugCtxf(ctx, "Not processing input. Setting terminate") 190 vm.st.SetFlag(state.FLAG_TERMINATE) 191 return b, nil 192 } 193 r = vm.st.MatchFlag(state.FLAG_TERMINATE, true) 194 if r { 195 logg.TraceCtxf(ctx, "Terminate found!!") 196 return b, nil 197 } 198 199 logg.TraceCtxf(ctx, "no code remaining but not terminating") 200 location, _ := vm.st.Where() 201 if location == "" { 202 return b, fmt.Errorf("dead runner with no current location") 203 } else if location == "_catch" { 204 return b, fmt.Errorf("unexpected catch endless loop detected for state: %s", vm.st) 205 } 206 207 input, err := vm.st.GetInput() 208 if err != nil { 209 input = []byte("(no input)") 210 } 211 cerr := NewInvalidInputError(string(input)) 212 vm.pg.WithError(cerr) 213 b = NewLine(nil, MOVE, []string{"_catch"}, nil, nil) 214 return b, nil 215 } 216 217 // executes the MAP opcode 218 func(vm *Vm) runMap(ctx context.Context, b []byte) ([]byte, error) { 219 sym, b, err := ParseMap(b) 220 err = vm.pg.Map(sym) 221 return b, err 222 } 223 224 // executes the CATCH opcode 225 func(vm *Vm) runCatch(ctx context.Context, b []byte) ([]byte, error) { 226 sym, sig, mode, b, err := ParseCatch(b) 227 if err != nil { 228 return b, err 229 } 230 r := vm.st.MatchFlag(sig, mode) 231 if r { 232 actualSym, _, err := applyTarget([]byte(sym), vm.st, vm.ca, ctx) 233 if err != nil { 234 return b, err 235 } 236 logg.InfoCtxf(ctx, "catch!", "flag", sig, "sym", sym, "target", actualSym, "mode", mode) 237 sym = actualSym 238 bh, err := vm.rs.GetCode(ctx, sym) 239 if err != nil { 240 return b, err 241 } 242 b = bh 243 } 244 return b, nil 245 } 246 247 // executes the CROAK opcode 248 func(vm *Vm) runCroak(ctx context.Context, b []byte) ([]byte, error) { 249 sig, mode, b, err := ParseCroak(b) 250 if err != nil { 251 return b, err 252 } 253 r := vm.st.MatchFlag(sig, mode) 254 if r { 255 logg.InfoCtxf(ctx, "croak! purging and moving to top", "signal", sig) 256 vm.Reset() 257 vm.ca.Reset() 258 b = []byte{} 259 } 260 return b, nil 261 } 262 263 // executes the LOAD opcode 264 func(vm *Vm) runLoad(ctx context.Context, b []byte) ([]byte, error) { 265 sym, sz, b, err := ParseLoad(b) 266 if err != nil { 267 return b, err 268 } 269 _, err = vm.ca.Get(sym) 270 if err == nil { 271 logg.DebugCtxf(ctx, "skip already loaded symbol", "symbol", sym) 272 return b, nil 273 } 274 r, err := vm.refresh(sym, vm.rs, ctx) 275 if err != nil { 276 return b, err 277 } 278 err = vm.ca.Add(sym, r, uint16(sz)) 279 if err != nil { 280 if err == cache.ErrDup { 281 logg.DebugCtxf(ctx, "Ignoring load request on frame that has symbol already loaded", "sym", sym) 282 err = nil 283 } 284 } 285 return b, err 286 } 287 288 // executes the RELOAD opcode 289 func(vm *Vm) runReload(ctx context.Context, b []byte) ([]byte, error) { 290 sym, b, err := ParseReload(b) 291 if err != nil { 292 return b, err 293 } 294 295 r, err := vm.refresh(sym, vm.rs, ctx) 296 if err != nil { 297 return b, err 298 } 299 vm.ca.Update(sym, r) 300 if vm.pg != nil { 301 err := vm.pg.Map(sym) 302 if err != nil { 303 return b, err 304 } 305 } 306 return b, nil 307 } 308 309 // executes the MOVE opcode 310 func(vm *Vm) runMove(ctx context.Context, b []byte) ([]byte, error) { 311 sym, b, err := ParseMove(b) 312 if err != nil { 313 return b, err 314 } 315 sym, _, err = applyTarget([]byte(sym), vm.st, vm.ca, ctx) 316 if err != nil { 317 return b, err 318 } 319 code, err := vm.rs.GetCode(ctx, sym) 320 if err != nil { 321 return b, err 322 } 323 logg.DebugCtxf(ctx, "loaded code", "sym", sym, "code", code) 324 b = append(b, code...) 325 vm.Reset() 326 return b, nil 327 } 328 329 // executes the INCMP opcode 330 // TODO: document state transition table and simplify flow 331 func(vm *Vm) runInCmp(ctx context.Context, b []byte) ([]byte, error) { 332 sym, target, b, err := ParseInCmp(b) 333 if err != nil { 334 return b, err 335 } 336 337 reading := vm.st.GetFlag(state.FLAG_READIN) 338 have := vm.st.GetFlag(state.FLAG_INMATCH) 339 if err != nil { 340 panic(err) 341 } 342 if have { 343 if reading { 344 logg.DebugCtxf(ctx, "ignoring input - already have match", "input", sym) 345 return b, nil 346 } 347 } else { 348 vm.st.SetFlag(state.FLAG_READIN) 349 } 350 input, err := vm.st.GetInput() 351 if err != nil { 352 return b, err 353 } 354 logg.TraceCtxf(ctx, "testing sym", "sym", sym, "input", input) 355 356 if !have && target == "*" { 357 logg.DebugCtxf(ctx, "input wildcard match", "input", input, "next", sym) 358 } else { 359 if target != string(input) { 360 return b, nil 361 } 362 logg.InfoCtxf(ctx, "input match", "input", input, "next", sym) 363 } 364 vm.st.SetFlag(state.FLAG_INMATCH) 365 vm.st.ResetFlag(state.FLAG_READIN) 366 367 newSym, _, err := applyTarget([]byte(sym), vm.st, vm.ca, ctx) 368 369 //_, ok := err.(*state.IndexError) 370 //if ok { 371 if errors.Is(err, state.IndexError) { 372 vm.st.SetFlag(state.FLAG_READIN) 373 return b, nil 374 } else if err != nil { 375 return b, err 376 } 377 378 sym = newSym 379 380 vm.Reset() 381 382 code, err := vm.rs.GetCode(ctx, sym) 383 if err != nil { 384 return b, err 385 } 386 logg.DebugCtxf(ctx, "loaded additional code", "next", sym, "code", code) 387 b = append(b, code...) 388 return b, err 389 } 390 391 // executes the HALT opcode 392 func(vm *Vm) runHalt(ctx context.Context, b []byte) ([]byte, error) { 393 var err error 394 b, err = ParseHalt(b) 395 if err != nil { 396 return b, err 397 } 398 logg.DebugCtxf(ctx, "found HALT, stopping") 399 400 vm.st.SetFlag(state.FLAG_WAIT) 401 return b, nil 402 } 403 404 // executes the MSIZE opcode 405 func(vm *Vm) runMSink(ctx context.Context, b []byte) ([]byte, error) { 406 b, err := ParseMSink(b) 407 mcfg := vm.mn.GetBrowseConfig() 408 vm.mn = vm.mn.WithSink().WithBrowseConfig(mcfg).WithPages() 409 //vm.pg.WithMenu(vm.mn) 410 return b, err 411 } 412 413 // executes the MOUT opcode 414 func(vm *Vm) runMOut(ctx context.Context, b []byte) ([]byte, error) { 415 title, choice, b, err := ParseMOut(b) 416 if err != nil { 417 return b, err 418 } 419 err = vm.mn.Put(choice, title) 420 return b, err 421 } 422 423 // executes the MNEXT opcode 424 func(vm *Vm) runMNext(ctx context.Context, b []byte) ([]byte, error) { 425 display, selector, b, err := ParseMNext(b) 426 if err != nil { 427 return b, err 428 } 429 cfg := vm.mn.GetBrowseConfig() 430 cfg.NextSelector = selector 431 cfg.NextTitle = display 432 cfg.NextAvailable = true 433 vm.mn = vm.mn.WithBrowseConfig(cfg) 434 return b, nil 435 } 436 437 // executes the MPREV opcode 438 func(vm *Vm) runMPrev(ctx context.Context, b []byte) ([]byte, error) { 439 display, selector, b, err := ParseMPrev(b) 440 if err != nil { 441 return b, err 442 } 443 cfg := vm.mn.GetBrowseConfig() 444 cfg.PreviousSelector = selector 445 cfg.PreviousTitle = display 446 cfg.PreviousAvailable = true 447 vm.mn = vm.mn.WithBrowseConfig(cfg) 448 return b, nil 449 } 450 451 // Render wraps output rendering, and handles error when attempting to browse beyond the rendered page count. 452 func(vm *Vm) Render(ctx context.Context) (string, error) { 453 changed := vm.st.ResetFlag(state.FLAG_DIRTY) 454 if !changed { 455 return "", nil 456 } 457 sym, idx := vm.st.Where() 458 if sym == "" { 459 return "", nil 460 } 461 r, err := vm.pg.Render(ctx, sym, idx) 462 var ok bool 463 _, ok = err.(*render.BrowseError) 464 if ok { 465 vm.Reset() 466 b := NewLine(nil, MOVE, []string{"_catch"}, nil, nil) 467 vm.Run(ctx, b) 468 sym, idx := vm.st.Where() 469 r, err = vm.pg.Render(ctx, sym, idx) 470 } 471 if err != nil { 472 return "", err 473 } 474 return r, nil 475 } 476 477 // retrieve and cache data for key 478 func(vm *Vm) refresh(key string, rs resource.Resource, ctx context.Context) (string, error) { 479 var err error 480 481 fn, err := rs.FuncFor(ctx, key) 482 if err != nil { 483 return "", err 484 } 485 if fn == nil { 486 return "", fmt.Errorf("no retrieve function for external symbol %v", key) 487 } 488 input, _ := vm.st.GetInput() 489 r, err := fn(ctx, key, input) 490 if err != nil { 491 logg.Errorf("external function load fail", "key", key, "error", err) 492 _ = vm.st.SetFlag(state.FLAG_LOADFAIL) 493 return "", NewExternalCodeError(key, err).WithCode(r.Status) 494 } 495 for _, flag := range r.FlagReset { 496 if !state.IsWriteableFlag(flag) { 497 continue 498 } 499 vm.st.ResetFlag(flag) 500 } 501 for _, flag := range r.FlagSet { 502 if !state.IsWriteableFlag(flag) { 503 continue 504 } 505 vm.st.SetFlag(flag) 506 } 507 508 haveLang := vm.st.MatchFlag(state.FLAG_LANG, true) 509 if haveLang { 510 vm.st.SetLanguage(r.Content) 511 } 512 513 return r.Content, err 514 }