elvish/pkg/eval/frame.go

223 lines
5.5 KiB
Go
Raw Normal View History

2017-12-28 03:59:42 +08:00
package eval
import (
"bufio"
2018-09-28 01:50:53 +08:00
"fmt"
2017-12-28 03:59:42 +08:00
"io"
"os"
"sync"
2019-12-24 04:00:59 +08:00
"github.com/elves/elvish/pkg/diag"
"github.com/elves/elvish/pkg/parse"
"github.com/elves/elvish/pkg/prog"
"github.com/elves/elvish/pkg/strutil"
2017-12-28 03:59:42 +08:00
)
2020-09-11 10:07:19 +08:00
// Frame contains information of the current running function, akin to a call
2017-12-28 03:59:42 +08:00
// frame in native CPU execution. A Frame is only modified during and very
// shortly after creation; new Frame's are "forked" when needed.
type Frame struct {
Evaler *Evaler
srcMeta parse.Source
2017-12-28 03:59:42 +08:00
local, up *Ns
intCh <-chan struct{}
ports []*Port
2017-12-28 03:59:42 +08:00
2020-09-03 12:54:32 +08:00
traceback *StackTrace
2017-12-28 03:59:42 +08:00
background bool
}
// Eval evaluates a piece of code in a copy. If r is not nil, it is added to the
// traceback of the evaluation context. If ns is not nil, it is used in place
// of the current local namespace as the namespace to evaluate the code in. It
// returns the altered local namespace and any parse error, compilation error or
// exception.
func (fm *Frame) Eval(src parse.Source, r diag.Ranger, ns *Ns) (*Ns, error) {
tree, err := parse.ParseWithDeprecation(src, fm.ErrorFile())
if err != nil {
return nil, err
}
local := fm.local
if ns != nil {
local = ns
}
traceback := fm.traceback
if r != nil {
traceback = fm.addTraceback(r)
}
newFm := &Frame{
fm.Evaler, src, local, new(Ns), fm.intCh, fm.ports, traceback, fm.background}
op, err := compile(newFm.Evaler.Builtin().static(), local.static(), tree, fm.ErrorFile())
if err != nil {
return nil, err
}
exc := op.exec(newFm)
return newFm.local, exc
}
// Close releases resources allocated for this frame. It always returns a nil
// error. It may be called only once.
func (fm *Frame) Close() error {
for _, port := range fm.ports {
2021-01-05 12:07:35 +08:00
port.close()
}
return nil
}
2017-12-28 03:59:42 +08:00
// InputChan returns a channel from which input can be read.
func (fm *Frame) InputChan() chan interface{} {
return fm.ports[0].Chan
2017-12-28 03:59:42 +08:00
}
// InputFile returns a file from which input can be read.
func (fm *Frame) InputFile() *os.File {
return fm.ports[0].File
2017-12-28 03:59:42 +08:00
}
// OutputChan returns a channel onto which output can be written.
func (fm *Frame) OutputChan() chan<- interface{} {
return fm.ports[1].Chan
2017-12-28 03:59:42 +08:00
}
// OutputFile returns a file onto which output can be written.
func (fm *Frame) OutputFile() *os.File {
return fm.ports[1].File
2017-12-28 03:59:42 +08:00
}
// ErrorFile returns a file onto which error messages can be written.
func (fm *Frame) ErrorFile() *os.File {
return fm.ports[2].File
}
2017-12-28 03:59:42 +08:00
// IterateInputs calls the passed function for each input element.
func (fm *Frame) IterateInputs(f func(interface{})) {
2017-12-28 03:59:42 +08:00
var w sync.WaitGroup
inputs := make(chan interface{})
2017-12-28 03:59:42 +08:00
w.Add(2)
go func() {
linesToChan(fm.InputFile(), inputs)
2017-12-28 03:59:42 +08:00
w.Done()
}()
go func() {
for v := range fm.ports[0].Chan {
2017-12-28 03:59:42 +08:00
inputs <- v
}
w.Done()
}()
go func() {
w.Wait()
close(inputs)
}()
for v := range inputs {
f(v)
}
}
func linesToChan(r io.Reader, ch chan<- interface{}) {
2017-12-28 03:59:42 +08:00
filein := bufio.NewReader(r)
for {
line, err := filein.ReadString('\n')
if line != "" {
ch <- strutil.ChopLineEnding(line)
2017-12-28 03:59:42 +08:00
}
if err != nil {
if err != io.EOF {
logger.Println("error on reading:", err)
}
break
}
}
}
// fork returns a modified copy of ec. The ports are forked, and the name is
// changed to the given value. Other fields are copied shallowly.
func (fm *Frame) fork(name string) *Frame {
newPorts := make([]*Port, len(fm.ports))
for i, p := range fm.ports {
if p != nil {
2021-01-05 12:07:35 +08:00
newPorts[i] = p.fork()
}
2017-12-28 03:59:42 +08:00
}
return &Frame{
fm.Evaler, fm.srcMeta,
fm.local, fm.up,
fm.intCh, newPorts,
fm.traceback, fm.background,
2017-12-28 03:59:42 +08:00
}
}
// A shorthand for forking a frame and setting the output port.
func (fm *Frame) forkWithOutput(name string, p *Port) *Frame {
newFm := fm.fork(name)
newFm.ports[1] = p
return newFm
}
// CaptureOutput captures the output of a given callback that operates on a Frame.
func (fm *Frame) CaptureOutput(f func(*Frame) error) ([]interface{}, error) {
outPort, collect, err := CapturePort()
if err != nil {
return nil, err
}
err = f(fm.forkWithOutput("[output capture]", outPort))
return collect(), err
2017-12-28 03:59:42 +08:00
}
// PipeOutput calls a callback with output piped to the given output handlers.
func (fm *Frame) PipeOutput(f func(*Frame) error, vCb func(<-chan interface{}), bCb func(*os.File)) error {
outPort, done, err := PipePort(vCb, bCb)
if err != nil {
return err
}
err = f(fm.forkWithOutput("[output pipe]", outPort))
done()
return err
}
2020-09-03 12:54:32 +08:00
func (fm *Frame) addTraceback(r diag.Ranger) *StackTrace {
return &StackTrace{
Head: diag.NewContext(fm.srcMeta.Name, fm.srcMeta.Code, r.Range()),
Next: fm.traceback,
}
}
// Returns an Exception with specified range and cause.
func (fm *Frame) errorp(r diag.Ranger, e error) Exception {
switch e := e.(type) {
case nil:
return nil
case Exception:
return e
default:
return &exception{e, &StackTrace{
2020-09-03 12:54:32 +08:00
Head: diag.NewContext(fm.srcMeta.Name, fm.srcMeta.Code, r.Range()),
Next: fm.traceback,
}}
2017-12-28 03:59:42 +08:00
}
}
// Returns an Exception with specified range and error text.
func (fm *Frame) errorpf(r diag.Ranger, format string, args ...interface{}) Exception {
return fm.errorp(r, fmt.Errorf(format, args...))
2017-12-28 03:59:42 +08:00
}
// Deprecate shows a deprecation message. The message is not shown if the same
// deprecation message has been shown for the same location before.
func (fm *Frame) Deprecate(msg string, ctx *diag.Context) {
if ctx == nil {
2020-09-03 12:54:32 +08:00
ctx = fm.traceback.Head
}
dep := deprecation{ctx.Name, ctx.Ranging, msg}
if prog.ShowDeprecations && fm.Evaler.registerDeprecation(dep) {
err := diag.Error{
Type: "deprecation", Message: dep.message, Context: *ctx}
fm.ErrorFile().WriteString(err.Show("") + "\n")
}
}