go-vise

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

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 }