db.go (15766B)
1 package engine 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 11 "git.defalsify.org/vise.git/cache" 12 "git.defalsify.org/vise.git/persist" 13 "git.defalsify.org/vise.git/render" 14 "git.defalsify.org/vise.git/resource" 15 "git.defalsify.org/vise.git/state" 16 "git.defalsify.org/vise.git/vm" 17 ) 18 19 var ( 20 ErrFlushNoExec = errors.New("Attempted flush on unexecuted engine") 21 ) 22 23 type DefaultEngine struct { 24 st *state.State 25 ca cache.Memory 26 vm *vm.Vm 27 rs resource.Resource 28 pe *persist.Persister 29 cfg Config 30 dbg Debug 31 first resource.EntryFunc 32 initd bool 33 exit string 34 exiting bool 35 execd bool 36 regexCount int 37 } 38 39 // NewEngine instantiates the default Engine implementation. 40 func NewEngine(cfg Config, rs resource.Resource) *DefaultEngine { 41 if rs == nil { 42 panic("resource cannot be nil") 43 } 44 en := &DefaultEngine{ 45 rs: rs, 46 cfg: cfg, 47 } 48 if en.cfg.Root == "" { 49 en.cfg.Root = "root" 50 } 51 if en.cfg.EngineDebug { 52 en.cfg.StateDebug = true 53 } 54 return en 55 } 56 57 // WithState is a chainable method that explicitly sets the state object to use for the engine. 58 // 59 // If not set, the state.State object provided by the persist.Persister will be used. 60 // If that is not available, a new instance will be created according to engine.Config. 61 // 62 // Note that engine.Init will fail if state is set both explicitly 63 // and in a provided persist.Persister. 64 func (en *DefaultEngine) WithState(st *state.State) *DefaultEngine { 65 if en.st != nil { 66 panic("state already set") 67 } 68 if st == nil { 69 panic("state argument is nil") 70 } 71 en.st = st 72 return en 73 } 74 75 // WithMemory is a chainable method that explicitly sets the memory object to use for the engine. 76 // 77 // If not set, the cache.Memory object provided by the persist.Persister will be used. 78 // If that is not available, a new instance will be created according to engine.Config. 79 // 80 // Note that engine.Init will fail if memory is set both explicitly 81 // and in a provided persist.Persister. 82 func (en *DefaultEngine) WithMemory(ca cache.Memory) *DefaultEngine { 83 if en.ca != nil { 84 panic("cache already set") 85 } 86 if ca == nil { 87 panic("cache argument is nil") 88 } 89 en.ca = ca 90 return en 91 } 92 93 // WithPersister is a chainable method that sets the persister to use with the engine. 94 // 95 // If the persister is missing state, memory or both, it will inherit them from the engine. 96 func (en *DefaultEngine) WithPersister(pe *persist.Persister) *DefaultEngine { 97 if en.pe != nil { 98 panic("persister already set") 99 } 100 if pe == nil { 101 panic("persister argument is nil") 102 } 103 en.pe = pe 104 return en 105 } 106 107 // WithDebug is a chainable method that sets the debugger to use for the engine. 108 // 109 // If the argument is nil, the default debugger will be used. 110 func (en *DefaultEngine) WithDebug(dbg Debug) *DefaultEngine { 111 if en.dbg != nil { 112 panic("debugger already set") 113 } 114 if dbg == nil { 115 logg.Infof("debug argument was nil, using default debugger") 116 dbg = NewSimpleDebug(os.Stderr) 117 } 118 en.dbg = dbg 119 return en 120 } 121 122 // WithFirst is a chainable method that defines the function that will be run before 123 // control is handed over to the VM bytecode from the current state. 124 // 125 // If this function returns an error, execution will be aborted in engine.Init. 126 func (en *DefaultEngine) WithFirst(fn resource.EntryFunc) *DefaultEngine { 127 if en.first != nil { 128 panic("firstfunc already set") 129 } 130 if fn == nil { 131 panic("firstfunc argument is nil") 132 } 133 en.first = fn 134 return en 135 } 136 137 // AddValidInput defines a regular expressing string to match input against. 138 // 139 // The added regular expression will be evaluated after the builtin match (see 140 // vm/input.go for the actual string details). 141 // 142 // The function may be called more than once. Input will be validated against each 143 // in the sequence they were added. 144 // 145 // When a match is found, remaining regular expressions will be skipped. 146 func (en *DefaultEngine) AddValidInput(re string) error { 147 err := vm.RegisterInputValidator(en.regexCount, re) 148 en.regexCount += 1 149 return err 150 } 151 152 // ensure state is present in engine. 153 func (en *DefaultEngine) ensureState() { 154 if en.st == nil { 155 st := state.NewState(en.cfg.FlagCount) 156 en.st = st 157 en.st.SetLanguage(en.cfg.Language) 158 if en.st.Language != nil { 159 en.st.SetFlag(state.FLAG_LANG) 160 } 161 logg.Debugf("new engine state added", "state", en.st) 162 } else { 163 if en.cfg.Language != "" { 164 if en.st.Language == nil { 165 en.st.SetLanguage(en.cfg.Language) 166 en.st.SetFlag(state.FLAG_LANG) 167 } else { 168 logg.Warnf("language '%s'set in config, but will be ignored because state language has already been set.") 169 } 170 } 171 } 172 } 173 174 // ensure memory is present in engine. 175 func (en *DefaultEngine) ensureMemory() error { 176 cac, ok := en.ca.(*cache.Cache) 177 if cac == nil { 178 ca := cache.NewCache() 179 if en.cfg.CacheSize > 0 { 180 ca = ca.WithCacheSize(en.cfg.CacheSize) 181 } 182 en.ca = ca 183 logg.Debugf("new engine memory added", "memory", en.ca) 184 } else if !ok { 185 return errors.New("memory MUST be *cache.Cache for now. sorry") 186 } 187 188 return nil 189 } 190 191 // retrieve state and memory from perister if present. 192 func (en *DefaultEngine) preparePersist() error { 193 if en.pe == nil { 194 return nil 195 } 196 st := en.pe.GetState() 197 if st != nil { 198 if en.st != nil { 199 return errors.New("state cannot be explicitly set in both persister and engine.") 200 } 201 en.st = st 202 } else { 203 if en.st == nil { 204 logg.Debugf("defer persist state set until state set in engine") 205 } 206 } 207 208 ca := en.pe.GetMemory() 209 cac, ok := ca.(*cache.Cache) 210 if !ok { 211 return errors.New("memory MUST be *cache.Cache for now. sorry") 212 } 213 if cac != nil { 214 logg.Debugf("ca", "ca", cac) 215 if en.ca != nil { 216 return errors.New("cache cannot be explicitly set in both persister and engine.") 217 } 218 en.ca = cac 219 } else { 220 if en.ca == nil { 221 logg.Debugf("defer persist memory set until memory set in engine") 222 } 223 } 224 return nil 225 } 226 227 // synchronize state and memory between engine and persister. 228 func (en *DefaultEngine) ensurePersist() error { 229 if en.pe == nil { 230 return nil 231 } 232 st := en.pe.GetState() 233 if st == nil { 234 st = en.st 235 logg.Debugf("using engine state for persister", "state", st) 236 } else { 237 en.st = st 238 } 239 ca := en.pe.GetMemory() 240 cac, ok := ca.(*cache.Cache) 241 if cac == nil { 242 cac, ok = en.ca.(*cache.Cache) 243 if !ok { 244 return errors.New("memory MUST be *cache.Cache for now. sorry") 245 } 246 logg.Debugf("using engine memory for persister", "memory", cac) 247 } else { 248 en.ca = cac 249 } 250 en.pe = en.pe.WithContent(st, cac) 251 err := en.pe.Load(en.cfg.SessionId) 252 if err != nil { 253 logg.Infof("persister load fail. trying save in case new session", "err", err, "session", en.cfg.SessionId) 254 err = en.pe.Save(en.cfg.SessionId) 255 if err != nil { 256 return err 257 } 258 en.pe = en.pe.WithContent(st, cac) 259 err = en.pe.Load(en.cfg.SessionId) 260 } 261 if en.cfg.StateDebug { 262 en.st.UseDebug() 263 } 264 logg.Tracef("set persister", "st", st, "cac", cac, "session", en.cfg.SessionId, "persister", en.pe) 265 return err 266 } 267 268 // create vm instance. 269 func (en *DefaultEngine) setupVm() { 270 var szr *render.Sizer 271 if en.cfg.OutputSize > 0 { 272 szr = render.NewSizer(en.cfg.OutputSize) 273 } 274 en.vm = vm.NewVm(en.st, en.rs, en.ca, szr) 275 if en.cfg.MenuSeparator != "" { 276 en.vm = en.vm.WithMenuSeparator(en.cfg.MenuSeparator) 277 } 278 } 279 280 func (en *DefaultEngine) empty(ctx context.Context) error { 281 var err error 282 b := bytes.NewBuffer(nil) 283 _, err = en.Flush(ctx, b) 284 if err != nil { 285 return err 286 } 287 logg.DebugCtxf(ctx, "discard", "output", b.Bytes()) 288 return nil 289 } 290 291 // prepare engine for Init run. 292 func (en *DefaultEngine) prepare(ctx context.Context) error { 293 if en.execd { 294 err := en.empty(ctx) 295 if err != nil { 296 return err 297 } 298 } 299 en.execd = false 300 en.exit = "" 301 en.exiting = false 302 if en.initd { 303 return nil 304 } 305 err := en.preparePersist() 306 if err != nil { 307 return err 308 } 309 en.ensureState() 310 err = en.ensureMemory() 311 if err != nil { 312 return err 313 } 314 err = en.ensurePersist() 315 if err != nil { 316 return err 317 } 318 en.setupVm() 319 return nil 320 } 321 322 // execute the first function, if set. 323 func (en *DefaultEngine) runFirst(ctx context.Context) (bool, error) { 324 var err error 325 var r bool 326 if en.first == nil { 327 return true, nil 328 } 329 logg.DebugCtxf(ctx, "start pre-VM check") 330 en.ca.Push() 331 rs := resource.NewMenuResource() 332 rs.AddLocalFunc("_first", en.first) 333 en.st.Down("_first") 334 defer en.ca.Pop() 335 defer en.st.Up() 336 defer en.st.ResetFlag(state.FLAG_TERMINATE) 337 defer en.st.ResetFlag(state.FLAG_DIRTY) 338 pvm := vm.NewVm(en.st, rs, en.ca, nil) 339 b := vm.NewLine(nil, vm.LOAD, []string{"_first"}, []byte{0}, nil) 340 b = vm.NewLine(b, vm.HALT, nil, nil, nil) 341 b, err = pvm.Run(ctx, b) 342 if err != nil { 343 return false, err 344 } 345 if len(b) > 0 { 346 // TODO: typed error 347 err = fmt.Errorf("Pre-VM code cannot have remaining bytecode after execution, had: %x", b) 348 } else { 349 if en.st.MatchFlag(state.FLAG_TERMINATE, true) { 350 en.execd = true 351 en.exit = en.ca.Last() 352 logg.InfoCtxf(ctx, "Pre-VM check says not to continue execution", "state", en.st) 353 } else { 354 r = true 355 } 356 } 357 if err != nil { 358 en.st.Invalidate() 359 en.ca.Invalidate() 360 } 361 logg.DebugCtxf(ctx, "end pre-VM check") 362 return r, err 363 } 364 365 // Finish implements the Engine interface. 366 // 367 // If persister is set, this call will save the state and memory. 368 // 369 // An error will be logged and returned if: 370 // - persistence was attempted and failed (takes precedence) 371 // - resource backend did not close cleanly. 372 func (en *DefaultEngine) Finish(ctx context.Context) error { 373 var perr error 374 if !en.initd { 375 return nil 376 } 377 if en.pe != nil { 378 perr = en.pe.Save(en.cfg.SessionId) 379 } 380 err := en.rs.Close(ctx) 381 if err != nil { 382 logg.Errorf("resource close failed!", "err", err) 383 } 384 if perr != nil { 385 logg.Errorf("persistence failed!", "err", perr) 386 err = perr 387 } 388 if err == nil { 389 logg.Tracef("that's a wrap", "engine", en) 390 } 391 return err 392 } 393 394 func (en *DefaultEngine) setCode(ctx context.Context, code []byte) (bool, error) { 395 var err error 396 397 cont := true 398 en.st.SetCode(code) 399 if len(code) == 0 { 400 logg.InfoCtxf(ctx, "runner finished with no remaining code", "state", en.st) 401 if en.st.MatchFlag(state.FLAG_DIRTY, true) { 402 logg.Debugf("have output for quitting") 403 en.exiting = true 404 en.exit = en.ca.Last() 405 } 406 cont = false 407 } 408 return cont, err 409 } 410 411 // Init implements the Engine interface. 412 // 413 // It loads and executes code for the start node. 414 func (en *DefaultEngine) init(ctx context.Context, input []byte) (bool, error) { 415 cont := true 416 err := en.prepare(ctx) 417 if err != nil { 418 return false, err 419 } 420 421 if en.st.Language != nil { 422 logg.TraceCtxf(ctx, "set language on context", "lang", en.st.Language) 423 ctx = context.WithValue(ctx, "Language", *en.st.Language) 424 } 425 426 if en.initd { 427 logg.DebugCtxf(ctx, "already initialized") 428 return true, nil 429 } 430 431 sym := en.cfg.Root 432 if sym == "" { 433 return false, fmt.Errorf("start sym empty") 434 } 435 436 inSave, _ := en.st.GetInput() 437 err = en.st.SetInput(input) 438 if err != nil { 439 return false, err 440 } 441 442 r, err := en.runFirst(ctx) 443 if err != nil { 444 return false, err 445 } 446 if !r { 447 return false, nil 448 } 449 450 if len(en.st.Code) == 0 { 451 b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil) 452 cont, err = en.setCode(ctx, b) 453 if err != nil { 454 return false, err 455 } 456 } 457 458 err = en.st.SetInput(inSave) 459 if err != nil { 460 return false, err 461 } 462 en.initd = true 463 return cont, nil 464 } 465 466 // Exec implements the Engine interface. 467 // 468 // It processes user input against the current state of the virtual machine environment. 469 // 470 // If successfully executed, output of the last execution is available using the Flush call. 471 // 472 // A bool return valus of false indicates that execution should be terminated. Calling Exec again has undefined effects. 473 // 474 // Fails if: 475 // - input is formally invalid (too long etc) 476 // - no current bytecode is available 477 // - input processing against bytcode failed 478 func (en *DefaultEngine) Exec(ctx context.Context, input []byte) (bool, error) { 479 var err error 480 481 if en.cfg.SessionId != "" { 482 ctx = context.WithValue(ctx, "SessionId", en.cfg.SessionId) 483 } 484 485 cont, err := en.init(ctx, input) 486 if err != nil { 487 return false, err 488 } 489 if !cont { 490 return cont, nil 491 } 492 493 if en.st.Language != nil { 494 ctx = context.WithValue(ctx, "Language", *en.st.Language) 495 } 496 497 if en.cfg.ResetOnEmptyInput { 498 if len(input) == 0 { 499 v, err := en.Reset(ctx, true) 500 if err != nil { 501 return v, err 502 } 503 } 504 } 505 506 if len(input) > 0 { 507 _, err = vm.ValidInput(input) 508 if err != nil { 509 return true, err 510 } 511 } 512 err = en.st.SetInput(input) 513 if err != nil { 514 return false, err 515 } 516 return en.exec(ctx, input) 517 } 518 519 // backend for Exec, after the input validity check 520 func (en *DefaultEngine) exec(ctx context.Context, input []byte) (bool, error) { 521 logg.InfoCtxf(ctx, "new VM execution with input", "input", input) 522 code, err := en.st.GetCode() 523 if err != nil { 524 return false, err 525 } 526 if len(code) == 0 { 527 return false, fmt.Errorf("no code to execute") 528 } 529 530 logg.Debugf("start VM run", "code", code) 531 code, err = en.vm.Run(ctx, code) 532 if err != nil { 533 logg.ErrorCtxf(ctx, "fail VM run with state", "code", en.st.Code, "state", en.st.String(), "vm", en.vm) 534 return false, err 535 } 536 en.execd = true 537 logg.Debugf("end VM run", "code", code, "state", en.st.String(), "vm", en.vm) 538 539 v := en.st.MatchFlag(state.FLAG_TERMINATE, true) 540 if v { 541 if len(code) > 0 { 542 logg.Debugf("terminated with code remaining", "code", code) 543 } 544 return false, err 545 } 546 cont, err := en.setCode(ctx, code) 547 if en.dbg != nil { 548 en.dbg.Break(en.st, en.ca) 549 } 550 return cont, err 551 } 552 553 // Flush implements the Engine interface. 554 // 555 // The method writes the output of the last vm execution to the given writer. 556 // 557 // Fails if 558 // - required data inputs to the template are not available. 559 // - the template for the given node point is note available for retrieval using the resource.Resource implementer. 560 // - the supplied writer fails to process the writes. 561 func (en *DefaultEngine) Flush(ctx context.Context, w io.Writer) (int, error) { 562 var l int 563 if !en.execd { 564 return 0, ErrFlushNoExec 565 } 566 if en.st.Language != nil { 567 ctx = context.WithValue(ctx, "Language", *en.st.Language) 568 } 569 logg.TraceCtxf(ctx, "render with state", "state", en.st) 570 r, err := en.vm.Render(ctx) 571 if err != nil { 572 if len(en.exit) == 0 { 573 return 0, err 574 } 575 } else { 576 if len(r) > 0 { 577 l, err = io.WriteString(w, r) 578 if err != nil { 579 return l, err 580 } 581 } 582 } 583 if len(en.exit) > 0 { 584 logg.TraceCtxf(ctx, "have exit", "exit", en.exit) 585 n, err := io.WriteString(w, en.exit) 586 if err != nil { 587 return l, err 588 } 589 l += n 590 } 591 if en.exiting { 592 _, err = en.reset(ctx) 593 en.exiting = false 594 } 595 596 return l, err 597 } 598 599 // start execution over at top node while keeping current state of client error flags. 600 func (en *DefaultEngine) Reset(ctx context.Context, force bool) (bool, error) { 601 if en.st.Depth() == -1 { 602 logg.TraceCtxf(ctx, "reset on pristine state is a noop") 603 return false, nil 604 } 605 if en.st == nil { 606 return false, fmt.Errorf("reset on engine with nil state") 607 } 608 if force { 609 sym := en.cfg.Root 610 if sym == "" { 611 return false, fmt.Errorf("start sym empty") 612 } 613 b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil) 614 en.st.SetCode(b) 615 } else { 616 b, err := en.st.GetCode() 617 if err != nil { 618 return false, err 619 } 620 if len(b) > 0 { 621 return false, ErrCodeRemaining 622 } 623 } 624 return en.reset(ctx) 625 } 626 627 func (en *DefaultEngine) reset(ctx context.Context) (bool, error) { 628 var err error 629 var isTop bool 630 logg.DebugCtxf(ctx, "entering engine reset", "state", en.st) 631 for !isTop { 632 isTop, err = en.st.Top() 633 if err != nil { 634 return false, err 635 } 636 _, err = en.st.Up() 637 if err != nil { 638 return false, err 639 } 640 en.ca.Pop() 641 } 642 if (en.cfg.ResetRoot) { 643 en.ca.Pop() 644 } 645 en.st.Restart() 646 en.st.ResetFlag(state.FLAG_TERMINATE) 647 en.st.ResetFlag(state.FLAG_DIRTY) 648 en.st.ResetFlag(state.FLAG_LOADFAIL) 649 return false, nil 650 }