db.go (14531B)
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/resource" 14 "git.defalsify.org/vise.git/render" 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 } 273 274 func(en *DefaultEngine) empty(ctx context.Context) error { 275 var err error 276 b := bytes.NewBuffer(nil) 277 _, err = en.Flush(ctx, b) 278 if err != nil { 279 return err 280 } 281 logg.WarnCtxf(ctx, "discarding rendered output") 282 logg.DebugCtxf(ctx, "discard", "output", b.Bytes()) 283 return nil 284 } 285 286 // prepare engine for Init run. 287 func(en *DefaultEngine) prepare(ctx context.Context) error { 288 if en.execd { 289 err := en.empty(ctx) 290 if err != nil { 291 return err 292 } 293 } 294 en.execd = false 295 en.exit = "" 296 en.exiting = false 297 if en.initd { 298 return nil 299 } 300 err := en.preparePersist() 301 if err != nil { 302 return err 303 } 304 en.ensureState() 305 err = en.ensureMemory() 306 if err != nil { 307 return err 308 } 309 err = en.ensurePersist() 310 if err != nil { 311 return err 312 } 313 en.setupVm() 314 return nil 315 } 316 317 // execute the first function, if set. 318 func(en *DefaultEngine) runFirst(ctx context.Context) (bool, error) { 319 var err error 320 var r bool 321 if en.first == nil { 322 return true, nil 323 } 324 logg.DebugCtxf(ctx, "start pre-VM check") 325 en.ca.Push() 326 rs := resource.NewMenuResource() 327 rs.AddLocalFunc("_first", en.first) 328 en.st.Down("_first") 329 defer en.ca.Pop() 330 defer en.st.Up() 331 defer en.st.ResetFlag(state.FLAG_TERMINATE) 332 defer en.st.ResetFlag(state.FLAG_DIRTY) 333 pvm := vm.NewVm(en.st, rs, en.ca, nil) 334 b := vm.NewLine(nil, vm.LOAD, []string{"_first"}, []byte{0}, nil) 335 b = vm.NewLine(b, vm.HALT, nil, nil, nil) 336 b, err = pvm.Run(ctx, b) 337 if err != nil { 338 return false, err 339 } 340 if len(b) > 0 { 341 // TODO: typed error 342 err = fmt.Errorf("Pre-VM code cannot have remaining bytecode after execution, had: %x", b) 343 } else { 344 if en.st.MatchFlag(state.FLAG_TERMINATE, true) { 345 en.execd = true 346 en.exit = en.ca.Last() 347 logg.InfoCtxf(ctx, "Pre-VM check says not to continue execution", "state", en.st) 348 } else { 349 r = true 350 } 351 } 352 if err != nil { 353 en.st.Invalidate() 354 en.ca.Invalidate() 355 } 356 logg.DebugCtxf(ctx, "end pre-VM check") 357 return r, err 358 } 359 360 // Finish implements the Engine interface. 361 // 362 // If persister is set, this call will save the state and memory. 363 // 364 // An error will be logged and returned if: 365 // * persistence was attempted and failed (takes precedence) 366 // * resource backend did not close cleanly. 367 func(en *DefaultEngine) Finish() error { 368 var perr error 369 if !en.initd { 370 return nil 371 } 372 if en.pe != nil { 373 perr = en.pe.Save(en.cfg.SessionId) 374 } 375 err := en.rs.Close() 376 if err != nil { 377 logg.Errorf("resource close failed!", "err", err) 378 } 379 if perr != nil { 380 logg.Errorf("persistence failed!", "err", perr) 381 err = perr 382 } 383 if err == nil { 384 logg.Tracef("that's a wrap", "engine", en) 385 } 386 return err 387 } 388 389 func(en *DefaultEngine) setCode(ctx context.Context, code []byte) (bool, error) { 390 var err error 391 392 cont := true 393 en.st.SetCode(code) 394 if len(code) == 0 { 395 logg.InfoCtxf(ctx, "runner finished with no remaining code", "state", en.st) 396 if en.st.MatchFlag(state.FLAG_DIRTY, true) { 397 logg.Debugf("have output for quitting") 398 en.exiting = true 399 en.exit = en.ca.Last() 400 } 401 cont = false 402 } 403 return cont, err 404 } 405 406 // Init implements the Engine interface. 407 // 408 // It loads and executes code for the start node. 409 func(en *DefaultEngine) init(ctx context.Context, input []byte) (bool, error) { 410 cont := true 411 err := en.prepare(ctx) 412 if err != nil { 413 return false, err 414 } 415 416 if en.st.Language != nil { 417 logg.TraceCtxf(ctx, "set language on context", "lang", en.st.Language) 418 ctx = context.WithValue(ctx, "Language", *en.st.Language) 419 } 420 421 if en.initd { 422 logg.DebugCtxf(ctx, "already initialized") 423 return true, nil 424 } 425 426 sym := en.cfg.Root 427 if sym == "" { 428 return false, fmt.Errorf("start sym empty") 429 } 430 431 inSave, _ := en.st.GetInput() 432 err = en.st.SetInput(input) 433 if err != nil { 434 return false, err 435 } 436 437 r, err := en.runFirst(ctx) 438 if err != nil { 439 return false, err 440 } 441 if !r { 442 return false, nil 443 } 444 445 if len(en.st.Code) == 0 { 446 b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil) 447 cont, err = en.setCode(ctx, b) 448 if err != nil { 449 return false, err 450 } 451 } 452 453 err = en.st.SetInput(inSave) 454 if err != nil { 455 return false, err 456 } 457 en.initd = true 458 return cont, nil 459 } 460 461 // Exec implements the Engine interface. 462 // 463 // It processes user input against the current state of the virtual machine environment. 464 // 465 // If successfully executed, output of the last execution is available using the Flush call. 466 // 467 // A bool return valus of false indicates that execution should be terminated. Calling Exec again has undefined effects. 468 // 469 // Fails if: 470 // * input is formally invalid (too long etc) 471 // * no current bytecode is available 472 // * input processing against bytcode failed 473 func (en *DefaultEngine) Exec(ctx context.Context, input []byte) (bool, error) { 474 var err error 475 476 if en.cfg.SessionId != "" { 477 ctx = context.WithValue(ctx, "SessionId", en.cfg.SessionId) 478 } 479 480 cont, err := en.init(ctx, input) 481 if err != nil { 482 return false, err 483 } 484 if !cont { 485 return cont, nil 486 } 487 488 if en.st.Language != nil { 489 ctx = context.WithValue(ctx, "Language", *en.st.Language) 490 } 491 492 if len(input) > 0 { 493 _, err = vm.ValidInput(input) 494 if err != nil { 495 return true, err 496 } 497 } 498 err = en.st.SetInput(input) 499 if err != nil { 500 return false, err 501 } 502 return en.exec(ctx, input) 503 } 504 505 // backend for Exec, after the input validity check 506 func(en *DefaultEngine) exec(ctx context.Context, input []byte) (bool, error) { 507 logg.InfoCtxf(ctx, "new VM execution with input", "input", string(input)) 508 code, err := en.st.GetCode() 509 if err != nil { 510 return false, err 511 } 512 if len(code) == 0 { 513 return false, fmt.Errorf("no code to execute") 514 } 515 516 logg.Debugf("start new VM run", "code", code) 517 code, err = en.vm.Run(ctx, code) 518 if err != nil { 519 return false, err 520 } 521 en.execd = true 522 logg.Debugf("end new VM run", "code", code) 523 524 v := en.st.MatchFlag(state.FLAG_TERMINATE, true) 525 if v { 526 if len(code) > 0 { 527 logg.Debugf("terminated with code remaining", "code", code) 528 } 529 return false, err 530 } 531 cont, err := en.setCode(ctx, code) 532 if en.dbg != nil { 533 en.dbg.Break(en.st, en.ca) 534 } 535 return cont, err 536 } 537 538 // Flush implements the Engine interface. 539 // 540 // The method writes the output of the last vm execution to the given writer. 541 // 542 // Fails if 543 // * required data inputs to the template are not available. 544 // * the template for the given node point is note available for retrieval using the resource.Resource implementer. 545 // * the supplied writer fails to process the writes. 546 func(en *DefaultEngine) Flush(ctx context.Context, w io.Writer) (int, error) { 547 var l int 548 if !en.execd { 549 return 0, ErrFlushNoExec 550 } 551 if en.st.Language != nil { 552 ctx = context.WithValue(ctx, "Language", *en.st.Language) 553 } 554 logg.TraceCtxf(ctx, "render with state", "state", en.st) 555 r, err := en.vm.Render(ctx) 556 if err != nil { 557 if len(en.exit) == 0 { 558 return 0, err 559 } 560 } else { 561 if len(r) > 0 { 562 l, err = io.WriteString(w, r) 563 if err != nil { 564 return l, err 565 } 566 } 567 } 568 if len(en.exit) > 0 { 569 logg.TraceCtxf(ctx, "have exit", "exit", en.exit) 570 n, err := io.WriteString(w, en.exit) 571 if err != nil { 572 return l, err 573 } 574 l += n 575 } 576 if en.exiting { 577 _, err = en.reset(ctx) 578 en.exiting = false 579 } 580 581 return l, err 582 } 583 584 // start execution over at top node while keeping current state of client error flags. 585 func(en *DefaultEngine) reset(ctx context.Context) (bool, error) { 586 var err error 587 var isTop bool 588 for !isTop { 589 isTop, err = en.st.Top() 590 if err != nil { 591 return false, err 592 } 593 _, err = en.st.Up() 594 if err != nil { 595 return false, err 596 } 597 en.ca.Pop() 598 } 599 en.st.Restart() 600 en.st.ResetFlag(state.FLAG_TERMINATE) 601 en.st.ResetFlag(state.FLAG_DIRTY) 602 return false, nil 603 } 604