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  }