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