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  }