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