go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

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 }