eval: Introduce an Op type that combines effectOp and Source.

This commit is contained in:
Qi Xiao 2018-10-10 19:43:42 +08:00
parent 21299a200c
commit cdd5576760
9 changed files with 38 additions and 23 deletions

View File

@ -101,7 +101,7 @@ func source(fm *Frame, fname string) error {
if err != nil {
return err
}
return fm.Eval(op)
return fm.EvalOp(op)
}
func readFileUTF8(fname string) (string, error) {

View File

@ -314,7 +314,7 @@ func loadModule(fm *Frame, name string) (Ns, error) {
// Load the namespace before executing. This prevent circular "use"es from
// resulting in an infinite recursion.
fm.Evaler.modules[name] = modGlobal
err = newFm.Eval(op)
err = newFm.EvalOp(op)
if err != nil {
// Unload the namespace.
delete(fm.modules, name)

View File

@ -25,10 +25,10 @@ type compiler struct {
srcMeta *Source
}
func compile(b, g staticNs, n *parse.Chunk, src *Source) (op effectOp, err error) {
func compile(b, g staticNs, n *parse.Chunk, src *Source) (op Op, err error) {
cp := &compiler{b, []staticNs{g}, make(staticNs), 0, 0, src}
defer util.Catch(&err)
return cp.chunkOp(n), nil
return Op{cp.chunkOp(n), src}, nil
}
func (cp *compiler) compiling(n parse.Node) {

View File

@ -203,11 +203,11 @@ func (fm *Frame) growPorts(n int) {
// be used to call functions with stdPorts. Make the Evaler initialize a
// stdPorts on construction, instead of in this function, so that NewTopFrame
// does not require the caller to supply the ports.
func (ev *Evaler) EvalWithStdPorts(op effectOp, src *Source) error {
func (ev *Evaler) EvalWithStdPorts(op Op) error {
stdPorts := newStdPorts(
os.Stdin, os.Stdout, os.Stderr, ev.state.getValuePrefix())
defer stdPorts.close()
return ev.Eval(op, stdPorts.ports[:], src)
return ev.Eval(op, stdPorts.ports[:])
}
// Eval sets up the Evaler with the given ports and evaluates an Op. The
@ -216,7 +216,7 @@ func (ev *Evaler) EvalWithStdPorts(op effectOp, src *Source) error {
// TODO(xiaq): This method only differs from eval in that it sets up intCh for
// relaying interrupts and puts Elvish in the foreground afterwards. Factor out
// those logics.
func (ev *Evaler) Eval(op effectOp, ports []*Port, src *Source) error {
func (ev *Evaler) Eval(op Op, ports []*Port) error {
// Set up intCh.
stopSigGoroutine := make(chan struct{})
sigGoRoutineDone := make(chan struct{})
@ -242,7 +242,7 @@ func (ev *Evaler) Eval(op effectOp, ports []*Port, src *Source) error {
close(sigGoRoutineDone)
}()
err := ev.eval(op, ports, src)
err := ev.eval(op, ports)
close(stopSigGoroutine)
<-sigGoRoutineDone
@ -261,17 +261,14 @@ func (ev *Evaler) Eval(op effectOp, ports []*Port, src *Source) error {
// eval evaluates a chunk node n. The supplied name and text are used in
// diagnostic messages.
func (ev *Evaler) eval(op effectOp, ports []*Port, src *Source) error {
ec := NewTopFrame(ev, src, ports)
return ec.Eval(op)
func (ev *Evaler) eval(op Op, ports []*Port) error {
ec := NewTopFrame(ev, op.src, ports)
return ec.Eval(op.inner)
}
// Compile compiles Elvish code in the global scope. If the error is not nil, it
// always has type CompilationError.
//
// TODO: Return a wrapper around effectOp that includes the Source passed in, so
// that the Source struct does not need to be supplied again when calling Eval.
func (ev *Evaler) Compile(n *parse.Chunk, src *Source) (effectOp, error) {
func (ev *Evaler) Compile(n *parse.Chunk, src *Source) (Op, error) {
return compile(ev.Builtin.static(), ev.Global.static(), n, src)
}
@ -285,5 +282,5 @@ func (ev *Evaler) EvalSource(src *Source) error {
if err != nil {
return err
}
return ev.EvalWithStdPorts(op, src)
return ev.EvalWithStdPorts(op)
}

View File

@ -125,7 +125,18 @@ func (fm *Frame) fork(name string) *Frame {
}
}
// Eval evaluates an op. It does so in a protected environment so that
// EvalOp evaluates an Op. It is like Eval except that it sets fm.srcMeta
// temporarily to op.src during the evaluation.
func (fm *Frame) EvalOp(op Op) error {
oldSrc := fm.srcMeta
fm.srcMeta = op.src
defer func() {
fm.srcMeta = oldSrc
}()
return fm.Eval(op.inner)
}
// Eval evaluates an effectOp. It does so in a protected environment so that
// exceptions thrown are wrapped in an Error.
func (fm *Frame) Eval(op effectOp) (err error) {
defer catch(&err, fm)

View File

@ -2,7 +2,14 @@ package eval
import "github.com/elves/elvish/eval/vars"
// An operation on an Frame that produces a side effect.
// Op represents an operation on a Frame. It is the result of compiling a piece
// of source.
type Op struct {
inner effectOp
src *Source
}
// An operation on a Frame that produces a side effect.
type effectOp struct {
body opBody
begin, end int
@ -36,7 +43,7 @@ func (op valuesOp) exec(fm *Frame) ([]interface{}, error) {
return op.body.invoke(fm)
}
// An operation on an Frame that produce Variable's.
// An operation on a Frame that produce Variable's.
type lvaluesOp struct {
body lvaluesOpBody
begin, end int

View File

@ -180,7 +180,7 @@ func evalAndCollect(t *testing.T, ev *Evaler, texts []string, chsize int) ([]int
{File: os.Stderr, Chan: BlackholeChan},
}
ex = ev.eval(op, ports, src)
ex = ev.eval(op, ports)
close(outCh)
<-outDone
}
@ -192,7 +192,7 @@ func evalAndCollect(t *testing.T, ev *Evaler, texts []string, chsize int) ([]int
return outs, bytesOut, ex
}
func mustParseAndCompile(t *testing.T, ev *Evaler, src *Source) effectOp {
func mustParseAndCompile(t *testing.T, ev *Evaler, src *Source) Op {
n, err := parse.Parse(src.name, src.code)
if err != nil {
t.Fatalf("Parse(%q) error: %s", src.code, err)

View File

@ -49,7 +49,7 @@ func script(ev *eval.Evaler, args []string, cmd, compileOnly bool) error {
return nil
}
return ev.EvalWithStdPorts(op, src)
return ev.EvalWithStdPorts(op)
}
var errSourceNotUTF8 = errors.New("source is not UTF-8")

View File

@ -117,7 +117,7 @@ func evalAndCollect(ev *eval.Evaler, code string) (
{File: outFile, Chan: outChan},
{File: errFile, Chan: eval.BlackholeChan},
}
err = ev.Eval(op, ports, src)
err = ev.Eval(op, ports)
outFile.Close()
close(outChan)