src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/frame.go (about) 1 package eval 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 "sync" 10 11 "src.elv.sh/pkg/diag" 12 "src.elv.sh/pkg/eval/errs" 13 "src.elv.sh/pkg/parse" 14 "src.elv.sh/pkg/prog" 15 "src.elv.sh/pkg/strutil" 16 ) 17 18 // Frame contains information of the current running function, akin to a call 19 // frame in native CPU execution. A Frame is only modified during and very 20 // shortly after creation; new Frame's are "forked" when needed. 21 type Frame struct { 22 Evaler *Evaler 23 24 srcMeta parse.Source 25 26 local, up *Ns 27 defers *[]func(*Frame) Exception 28 29 // The godoc of the context package states: 30 // 31 // > Do not store Contexts inside a struct type; instead, pass a Context 32 // > explicitly to each function that needs it. 33 // 34 // However, that advice is considered by many to be overly aggressive 35 // (https://github.com/golang/go/issues/22602). The Frame struct doesn't fit 36 // the "parameter struct" definition in that discussion, but it is itself is 37 // a "context struct". Storing a Context inside it seems fine. 38 ctx context.Context 39 ports []*Port 40 41 traceback *StackTrace 42 43 background bool 44 } 45 46 // PrepareEval prepares a piece of code for evaluation in a copy of the current 47 // Frame. If r is not nil, it is added to the traceback of the evaluation 48 // context. If ns is not nil, it is used in place of the current local namespace 49 // as the namespace to evaluate the code in. 50 // 51 // If there is any parse error or compilation error, it returns a nil *Ns, nil 52 // function and the error. If there is no parse error or compilation error, it 53 // returns the altered local namespace, function that can be called to actuate 54 // the evaluation, and a nil error. 55 func (fm *Frame) PrepareEval(src parse.Source, r diag.Ranger, ns *Ns) (*Ns, func() Exception, error) { 56 tree, err := parse.Parse(src, parse.Config{WarningWriter: fm.ErrorFile()}) 57 if err != nil { 58 return nil, nil, err 59 } 60 local := fm.local 61 if ns != nil { 62 local = ns 63 } 64 traceback := fm.traceback 65 if r != nil { 66 traceback = fm.addTraceback(r) 67 } 68 newFm := &Frame{ 69 fm.Evaler, src, local, new(Ns), nil, fm.ctx, fm.ports, traceback, fm.background} 70 op, _, err := compile(fm.Evaler.Builtin().static(), local.static(), nil, tree, fm.ErrorFile()) 71 if err != nil { 72 return nil, nil, err 73 } 74 newLocal, exec := op.prepare(newFm) 75 return newLocal, exec, nil 76 } 77 78 // Eval evaluates a piece of code in a copy of the current Frame. It returns the 79 // altered local namespace, and any parse error, compilation error or exception. 80 // 81 // See PrepareEval for a description of the arguments. 82 func (fm *Frame) Eval(src parse.Source, r diag.Ranger, ns *Ns) (*Ns, error) { 83 newLocal, exec, err := fm.PrepareEval(src, r, ns) 84 if err != nil { 85 return nil, err 86 } 87 return newLocal, exec() 88 } 89 90 // Close releases resources allocated for this frame. It always returns a nil 91 // error. It may be called only once. 92 func (fm *Frame) Close() error { 93 for _, port := range fm.ports { 94 port.close() 95 } 96 return nil 97 } 98 99 // InputChan returns a channel from which input can be read. 100 func (fm *Frame) InputChan() chan any { 101 return fm.ports[0].Chan 102 } 103 104 // InputFile returns a file from which input can be read. 105 func (fm *Frame) InputFile() *os.File { 106 return fm.ports[0].File 107 } 108 109 // ValueOutput returns a handle for writing value outputs. 110 func (fm *Frame) ValueOutput() ValueOutput { 111 p := fm.ports[1] 112 return valueOutput{p.Chan, p.sendStop, p.sendError} 113 } 114 115 // ByteOutput returns a handle for writing byte outputs. 116 func (fm *Frame) ByteOutput() ByteOutput { 117 return byteOutput{fm.ports[1].File} 118 } 119 120 // ErrorFile returns a file onto which error messages can be written. 121 func (fm *Frame) ErrorFile() *os.File { 122 return fm.ports[2].File 123 } 124 125 // Port returns port i. If the port doesn't exist, it returns nil 126 // 127 // This is a low-level construct that shouldn't be used for writing output; for 128 // that purpose, use [(*Frame).ValueOutput] and [(*Frame).ByteOutput] instead. 129 func (fm *Frame) Port(i int) *Port { 130 if i >= len(fm.ports) { 131 return nil 132 } 133 return fm.ports[i] 134 } 135 136 // IterateInputs calls the passed function for each input element. 137 func (fm *Frame) IterateInputs(f func(any)) { 138 var wg sync.WaitGroup 139 inputs := make(chan any) 140 141 wg.Add(2) 142 go func() { 143 linesToChan(fm.InputFile(), inputs) 144 wg.Done() 145 }() 146 go func() { 147 for v := range fm.ports[0].Chan { 148 inputs <- v 149 } 150 wg.Done() 151 }() 152 go func() { 153 wg.Wait() 154 close(inputs) 155 }() 156 157 for v := range inputs { 158 f(v) 159 } 160 } 161 162 func linesToChan(r io.Reader, ch chan<- any) { 163 filein := bufio.NewReader(r) 164 for { 165 line, err := filein.ReadString('\n') 166 if line != "" { 167 ch <- strutil.ChopLineEnding(line) 168 } 169 if err != nil { 170 if err != io.EOF { 171 logger.Println("error on reading:", err) 172 } 173 break 174 } 175 } 176 } 177 178 // Context returns a Context associated with the Frame. 179 func (fm *Frame) Context() context.Context { 180 return fm.ctx 181 } 182 183 // Canceled reports whether the Context of the Frame has been canceled. 184 func (fm *Frame) Canceled() bool { 185 select { 186 case <-fm.ctx.Done(): 187 return true 188 default: 189 return false 190 } 191 } 192 193 // Fork returns a modified copy of fm. The ports are forked, and the name is 194 // changed to the given value. Other fields are copied shallowly. 195 func (fm *Frame) Fork(name string) *Frame { 196 newPorts := make([]*Port, len(fm.ports)) 197 for i, p := range fm.ports { 198 if p != nil { 199 newPorts[i] = p.fork() 200 } 201 } 202 return &Frame{ 203 fm.Evaler, fm.srcMeta, 204 fm.local, fm.up, fm.defers, 205 fm.ctx, newPorts, 206 fm.traceback, fm.background, 207 } 208 } 209 210 // A shorthand for forking a frame and setting the output port. 211 func (fm *Frame) forkWithOutput(name string, p *Port) *Frame { 212 newFm := fm.Fork(name) 213 newFm.ports[1] = p 214 return newFm 215 } 216 217 // CaptureOutput captures the output of a given callback that operates on a Frame. 218 func (fm *Frame) CaptureOutput(f func(*Frame) error) ([]any, error) { 219 outPort, collect, err := ValueCapturePort() 220 if err != nil { 221 return nil, err 222 } 223 err = f(fm.forkWithOutput("[output capture]", outPort)) 224 return collect(), err 225 } 226 227 // PipeOutput calls a callback with output piped to the given output handlers. 228 func (fm *Frame) PipeOutput(f func(*Frame) error, vCb func(<-chan any), bCb func(*os.File)) error { 229 outPort, done, err := PipePort(vCb, bCb) 230 if err != nil { 231 return err 232 } 233 err = f(fm.forkWithOutput("[output pipe]", outPort)) 234 done() 235 return err 236 } 237 238 func (fm *Frame) addTraceback(r diag.Ranger) *StackTrace { 239 return &StackTrace{ 240 Head: diag.NewContext(fm.srcMeta.Name, fm.srcMeta.Code, r.Range()), 241 Next: fm.traceback, 242 } 243 } 244 245 // Returns an Exception with specified range and cause. 246 func (fm *Frame) errorp(r diag.Ranger, e error) Exception { 247 switch e := e.(type) { 248 case nil: 249 return nil 250 case Exception: 251 return e 252 default: 253 if _, ok := e.(errs.SetReadOnlyVar); ok { 254 r := r.Range() 255 e = errs.SetReadOnlyVar{VarName: fm.srcMeta.Code[r.From:r.To]} 256 } 257 ctx := diag.NewContext(fm.srcMeta.Name, fm.srcMeta.Code, r) 258 return &exception{e, &StackTrace{Head: ctx, Next: fm.traceback}} 259 } 260 } 261 262 // Returns an Exception with specified range and error text. 263 func (fm *Frame) errorpf(r diag.Ranger, format string, args ...any) Exception { 264 return fm.errorp(r, fmt.Errorf(format, args...)) 265 } 266 267 // Deprecate shows a deprecation message. The message is not shown if the same 268 // deprecation message has been shown for the same location before. 269 func (fm *Frame) Deprecate(msg string, ctx *diag.Context, minLevel int) { 270 if prog.DeprecationLevel < minLevel { 271 return 272 } 273 if ctx == nil { 274 ctx = fm.traceback.Head 275 } 276 if fm.Evaler.registerDeprecation(deprecation{ctx.Name, ctx.Ranging, msg}) { 277 err := diag.Error[deprecationTag]{Message: msg, Context: *ctx} 278 fm.ErrorFile().WriteString(err.Show("") + "\n") 279 } 280 } 281 282 func (fm *Frame) addDefer(f func(*Frame) Exception) { 283 *fm.defers = append(*fm.defers, f) 284 } 285 286 func (fm *Frame) runDefers() Exception { 287 var exc Exception 288 defers := *fm.defers 289 for i := len(defers) - 1; i >= 0; i-- { 290 exc2 := defers[i](fm) 291 // TODO: Combine exc and exc2 if both are not nil 292 if exc2 != nil && exc == nil { 293 exc = exc2 294 } 295 } 296 return exc 297 }