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