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