1package eval 2 3import ( 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. 20type 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. 44func (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. 71func (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. 81func (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. 89func (fm *Frame) InputChan() chan interface{} { 90 return fm.ports[0].Chan 91} 92 93// InputFile returns a file from which input can be read. 94func (fm *Frame) InputFile() *os.File { 95 return fm.ports[0].File 96} 97 98// ValueOutput returns a handle for writing value outputs. 99func (fm *Frame) ValueOutput() ValueOutput { 100 p := fm.ports[1] 101 return valueOutput{p.Chan, p.sendStop, p.sendError} 102} 103 104// ByteOutput returns a handle for writing byte outputs. 105func (fm *Frame) ByteOutput() ByteOutput { 106 return byteOutput{fm.ports[1].File} 107} 108 109// ErrorFile returns a file onto which error messages can be written. 110func (fm *Frame) ErrorFile() *os.File { 111 return fm.ports[2].File 112} 113 114// IterateInputs calls the passed function for each input element. 115func (fm *Frame) IterateInputs(f func(interface{})) { 116 var wg sync.WaitGroup 117 inputs := make(chan interface{}) 118 119 wg.Add(2) 120 go func() { 121 linesToChan(fm.InputFile(), inputs) 122 wg.Done() 123 }() 124 go func() { 125 for v := range fm.ports[0].Chan { 126 inputs <- v 127 } 128 wg.Done() 129 }() 130 go func() { 131 wg.Wait() 132 close(inputs) 133 }() 134 135 for v := range inputs { 136 f(v) 137 } 138} 139 140func linesToChan(r io.Reader, ch chan<- interface{}) { 141 filein := bufio.NewReader(r) 142 for { 143 line, err := filein.ReadString('\n') 144 if line != "" { 145 ch <- strutil.ChopLineEnding(line) 146 } 147 if err != nil { 148 if err != io.EOF { 149 logger.Println("error on reading:", err) 150 } 151 break 152 } 153 } 154} 155 156// fork returns a modified copy of ec. The ports are forked, and the name is 157// changed to the given value. Other fields are copied shallowly. 158func (fm *Frame) fork(name string) *Frame { 159 newPorts := make([]*Port, len(fm.ports)) 160 for i, p := range fm.ports { 161 if p != nil { 162 newPorts[i] = p.fork() 163 } 164 } 165 return &Frame{ 166 fm.Evaler, fm.srcMeta, 167 fm.local, fm.up, 168 fm.intCh, newPorts, 169 fm.traceback, fm.background, 170 } 171} 172 173// A shorthand for forking a frame and setting the output port. 174func (fm *Frame) forkWithOutput(name string, p *Port) *Frame { 175 newFm := fm.fork(name) 176 newFm.ports[1] = p 177 return newFm 178} 179 180// CaptureOutput captures the output of a given callback that operates on a Frame. 181func (fm *Frame) CaptureOutput(f func(*Frame) error) ([]interface{}, error) { 182 outPort, collect, err := CapturePort() 183 if err != nil { 184 return nil, err 185 } 186 err = f(fm.forkWithOutput("[output capture]", outPort)) 187 return collect(), err 188} 189 190// PipeOutput calls a callback with output piped to the given output handlers. 191func (fm *Frame) PipeOutput(f func(*Frame) error, vCb func(<-chan interface{}), bCb func(*os.File)) error { 192 outPort, done, err := PipePort(vCb, bCb) 193 if err != nil { 194 return err 195 } 196 err = f(fm.forkWithOutput("[output pipe]", outPort)) 197 done() 198 return err 199} 200 201func (fm *Frame) addTraceback(r diag.Ranger) *StackTrace { 202 return &StackTrace{ 203 Head: diag.NewContext(fm.srcMeta.Name, fm.srcMeta.Code, r.Range()), 204 Next: fm.traceback, 205 } 206} 207 208// Returns an Exception with specified range and cause. 209func (fm *Frame) errorp(r diag.Ranger, e error) Exception { 210 switch e := e.(type) { 211 case nil: 212 return nil 213 case Exception: 214 return e 215 default: 216 ctx := diag.NewContext(fm.srcMeta.Name, fm.srcMeta.Code, r) 217 if _, ok := e.(errs.SetReadOnlyVar); ok { 218 e = errs.SetReadOnlyVar{VarName: ctx.RelevantString()} 219 } 220 return &exception{e, &StackTrace{Head: ctx, Next: fm.traceback}} 221 } 222} 223 224// Returns an Exception with specified range and error text. 225func (fm *Frame) errorpf(r diag.Ranger, format string, args ...interface{}) Exception { 226 return fm.errorp(r, fmt.Errorf(format, args...)) 227} 228 229// Deprecate shows a deprecation message. The message is not shown if the same 230// deprecation message has been shown for the same location before. 231func (fm *Frame) Deprecate(msg string, ctx *diag.Context, minLevel int) { 232 if prog.DeprecationLevel < minLevel { 233 return 234 } 235 if ctx == nil { 236 fmt.Fprintf(fm.ErrorFile(), "deprecation: \033[31;1m%s\033[m\n", msg) 237 return 238 } 239 if fm.Evaler.registerDeprecation(deprecation{ctx.Name, ctx.Ranging, msg}) { 240 err := diag.Error{Type: "deprecation", Message: msg, Context: *ctx} 241 fm.ErrorFile().WriteString(err.Show("") + "\n") 242 } 243} 244