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