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  }