engine.go (5838B)
1 package engine 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 8 "git.defalsify.org/vise.git/cache" 9 "git.defalsify.org/vise.git/render" 10 "git.defalsify.org/vise.git/resource" 11 "git.defalsify.org/vise.git/state" 12 "git.defalsify.org/vise.git/vm" 13 ) 14 15 // EngineIsh defines the interface for execution engines that handle vm initialization and execution, and rendering outputs. 16 type EngineIsh interface { 17 Init(ctx context.Context) (bool, error) 18 Exec(ctx context.Context, input []byte) (bool, error) 19 WriteResult(ctx context.Context, w io.Writer) (int, error) 20 Finish() error 21 } 22 23 // Config globally defines behavior of all components driven by the engine. 24 type Config struct { 25 OutputSize uint32 // Maximum size of output from a single rendered page 26 SessionId string 27 Root string 28 FlagCount uint32 29 CacheSize uint32 30 Language string 31 } 32 33 // Engine is an execution engine that handles top-level errors when running client inputs against code in the bytecode buffer. 34 type Engine struct { 35 st *state.State 36 rs resource.Resource 37 ca cache.Memory 38 vm *vm.Vm 39 root string 40 session string 41 initd bool 42 } 43 44 // NewEngine creates a new Engine 45 func NewEngine(ctx context.Context, cfg Config, st *state.State, rs resource.Resource, ca cache.Memory) Engine { 46 var szr *render.Sizer 47 if cfg.OutputSize > 0 { 48 szr = render.NewSizer(cfg.OutputSize) 49 } 50 ctx = context.WithValue(ctx, "sessionId", cfg.SessionId) 51 engine := Engine{ 52 st: st, 53 rs: rs, 54 ca: ca, 55 vm: vm.NewVm(st, rs, ca, szr), 56 } 57 engine.root = cfg.Root 58 engine.session = cfg.SessionId 59 60 var err error 61 if st.Language == nil { 62 if cfg.Language != "" { 63 err = st.SetLanguage(cfg.Language) 64 if err != nil { 65 panic(err) 66 } 67 Logg.InfoCtxf(ctx, "set language from config", "language", cfg.Language) 68 } 69 } 70 71 if st.Language != nil { 72 st.SetFlag(state.FLAG_LANG) 73 } 74 return engine 75 } 76 77 // Finish implements EngineIsh interface 78 func(en *Engine) Finish() error { 79 Logg.Tracef("that's a wrap", "engine", en) 80 return nil 81 } 82 83 // change root to current state location if non-empty. 84 func(en *Engine) restore() { 85 location, _ := en.st.Where() 86 if len(location) == 0 { 87 return 88 } 89 if en.root != location { 90 Logg.Infof("restoring state: %s", location) 91 en.root = "." 92 } 93 } 94 95 // Init must be explicitly called before using the Engine instance. 96 // 97 // It loads and executes code for the start node. 98 func(en *Engine) Init(ctx context.Context) (bool, error) { 99 en.restore() 100 if en.initd { 101 Logg.DebugCtxf(ctx, "already initialized") 102 return true, nil 103 } 104 105 sym := en.root 106 if sym == "" { 107 return false, fmt.Errorf("start sym empty") 108 } 109 inSave, _ := en.st.GetInput() 110 err := en.st.SetInput([]byte{}) 111 if err != nil { 112 return false, err 113 } 114 b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil) 115 Logg.DebugCtxf(ctx, "start new init VM run", "code", b) 116 b, err = en.vm.Run(ctx, b) 117 if err != nil { 118 return false, err 119 } 120 121 Logg.DebugCtxf(ctx, "end new init VM run", "code", b) 122 en.st.SetCode(b) 123 err = en.st.SetInput(inSave) 124 if err != nil { 125 return false, err 126 } 127 return len(b) > 0, nil 128 } 129 130 // Exec processes user input against the current state of the virtual machine environment. 131 // 132 // If successfully executed, output of the last execution is available using the WriteResult call. 133 // 134 // A bool return valus of false indicates that execution should be terminated. Calling Exec again has undefined effects. 135 // 136 // Fails if: 137 // - input is formally invalid (too long etc) 138 // - no current bytecode is available 139 // - input processing against bytcode failed 140 func (en *Engine) Exec(ctx context.Context, input []byte) (bool, error) { 141 var err error 142 if en.st.Language != nil { 143 ctx = context.WithValue(ctx, "Language", *en.st.Language) 144 } 145 if en.st.Moves == 0 { 146 cont, err := en.Init(ctx) 147 if err != nil { 148 return false, err 149 } 150 return cont, nil 151 } 152 err = vm.ValidInput(input) 153 if err != nil { 154 return true, err 155 } 156 err = en.st.SetInput(input) 157 if err != nil { 158 return false, err 159 } 160 return en.exec(ctx, input) 161 } 162 163 // backend for Exec, after the input validity check 164 func(en *Engine) exec(ctx context.Context, input []byte) (bool, error) { 165 Logg.InfoCtxf(ctx, "new VM execution with input", "input", string(input)) 166 code, err := en.st.GetCode() 167 if err != nil { 168 return false, err 169 } 170 if len(code) == 0 { 171 return false, fmt.Errorf("no code to execute") 172 } 173 174 Logg.Debugf("start new VM run", "code", code) 175 code, err = en.vm.Run(ctx, code) 176 if err != nil { 177 return false, err 178 } 179 Logg.Debugf("end new VM run", "code", code) 180 181 v := en.st.MatchFlag(state.FLAG_TERMINATE, true) 182 if v { 183 if len(code) > 0 { 184 Logg.Debugf("terminated with code remaining", "code", code) 185 } 186 return false, err 187 } 188 189 en.st.SetCode(code) 190 if len(code) == 0 { 191 Logg.Infof("runner finished with no remaining code") 192 _, err = en.reset(ctx) 193 return false, err 194 } 195 return true, nil 196 } 197 198 // WriteResult writes the output of the last vm execution to the given writer. 199 // 200 // Fails if 201 // - required data inputs to the template are not available. 202 // - the template for the given node point is note available for retrieval using the resource.Resource implementer. 203 // - the supplied writer fails to process the writes. 204 func(en *Engine) WriteResult(ctx context.Context, w io.Writer) (int, error) { 205 if en.st.Language != nil { 206 ctx = context.WithValue(ctx, "Language", *en.st.Language) 207 } 208 Logg.TraceCtxf(ctx, "render with state", "state", en.st) 209 r, err := en.vm.Render(ctx) 210 if err != nil { 211 return 0, err 212 } 213 return io.WriteString(w, r) 214 } 215 216 // start execution over at top node while keeping current state of client error flags. 217 func(en *Engine) reset(ctx context.Context) (bool, error) { 218 var err error 219 var isTop bool 220 for !isTop { 221 isTop, err = en.st.Top() 222 if err != nil { 223 return false, err 224 } 225 _, err = en.st.Up() 226 if err != nil { 227 return false, err 228 } 229 en.ca.Pop() 230 } 231 en.st.Restart() 232 en.initd = false 233 return en.Init(ctx) 234 }