go-vise

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

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