mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
576 lines
13 KiB
Go
576 lines
13 KiB
Go
package eval
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/elves/elvish/pkg/eval/vals"
|
|
"github.com/elves/elvish/pkg/eval/vars"
|
|
"github.com/elves/elvish/pkg/parse"
|
|
"github.com/elves/elvish/pkg/util"
|
|
"github.com/xiaq/persistent/hashmap"
|
|
)
|
|
|
|
func (cp *compiler) chunk(n *parse.Chunk) effectOpBody {
|
|
return chunkOp{cp.pipelineOps(n.Pipelines)}
|
|
}
|
|
|
|
type chunkOp struct {
|
|
subops []effectOp
|
|
}
|
|
|
|
func (op chunkOp) invoke(fm *Frame) error {
|
|
for _, subop := range op.subops {
|
|
err := subop.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Check for interrupts after the chunk.
|
|
// We also check for interrupts before each pipeline, so there is no
|
|
// need to check it before the chunk or after each pipeline.
|
|
if fm.IsInterrupted() {
|
|
return ErrInterrupted
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cp *compiler) pipeline(n *parse.Pipeline) effectOpBody {
|
|
return &pipelineOp{n.Background, n.SourceText(), cp.formOps(n.Forms)}
|
|
}
|
|
|
|
type pipelineOp struct {
|
|
bg bool
|
|
source string
|
|
subops []effectOp
|
|
}
|
|
|
|
const pipelineChanBufferSize = 32
|
|
|
|
func (op *pipelineOp) invoke(fm *Frame) error {
|
|
if fm.IsInterrupted() {
|
|
return ErrInterrupted
|
|
}
|
|
|
|
if op.bg {
|
|
fm = fm.fork("background job" + op.source)
|
|
fm.intCh = nil
|
|
fm.background = true
|
|
fm.Evaler.state.addNumBgJobs(1)
|
|
|
|
if fm.Editor != nil {
|
|
// TODO: Redirect output in interactive mode so that the line
|
|
// editor does not get messed up.
|
|
}
|
|
}
|
|
|
|
nforms := len(op.subops)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(nforms)
|
|
errors := make([]*Exception, nforms)
|
|
|
|
var nextIn *Port
|
|
|
|
// For each form, create a dedicated evalCtx and run asynchronously
|
|
for i, op := range op.subops {
|
|
hasChanInput := i > 0
|
|
newFm := fm.fork("[form op]")
|
|
if i > 0 {
|
|
newFm.ports[0] = nextIn
|
|
}
|
|
if i < nforms-1 {
|
|
// Each internal port pair consists of a (byte) pipe pair and a
|
|
// channel.
|
|
// os.Pipe sets O_CLOEXEC, which is what we want.
|
|
reader, writer, e := os.Pipe()
|
|
if e != nil {
|
|
return fmt.Errorf("failed to create pipe: %s", e)
|
|
}
|
|
ch := make(chan interface{}, pipelineChanBufferSize)
|
|
newFm.ports[1] = &Port{
|
|
File: writer, Chan: ch, CloseFile: true, CloseChan: true}
|
|
nextIn = &Port{
|
|
File: reader, Chan: ch, CloseFile: true, CloseChan: false}
|
|
}
|
|
thisOp := op
|
|
thisError := &errors[i]
|
|
go func() {
|
|
err := newFm.eval(thisOp)
|
|
newFm.Close()
|
|
if err != nil {
|
|
*thisError = err.(*Exception)
|
|
}
|
|
wg.Done()
|
|
if hasChanInput {
|
|
// If the command has channel input, drain it. This
|
|
// mitigates the effect of erroneous pipelines like
|
|
// "range 100 | cat"; without draining the pipeline will
|
|
// lock up.
|
|
for range newFm.ports[0].Chan {
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
if op.bg {
|
|
// Background job, wait for form termination asynchronously.
|
|
go func() {
|
|
wg.Wait()
|
|
fm.Evaler.state.addNumBgJobs(-1)
|
|
msg := "job " + op.source + " finished"
|
|
err := ComposeExceptionsFromPipeline(errors)
|
|
if err != nil {
|
|
msg += ", errors = " + err.Error()
|
|
}
|
|
if fm.Evaler.state.getNotifyBgJobSuccess() || err != nil {
|
|
if fm.Editor != nil {
|
|
fm.Editor.Notify("%s", msg)
|
|
} else {
|
|
fm.ports[2].File.WriteString(msg + "\n")
|
|
}
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
wg.Wait()
|
|
return ComposeExceptionsFromPipeline(errors)
|
|
}
|
|
|
|
func (cp *compiler) form(n *parse.Form) effectOpBody {
|
|
var saveVarsOps []lvaluesOp
|
|
var assignmentOps []effectOp
|
|
if len(n.Assignments) > 0 {
|
|
assignmentOps = cp.assignmentOps(n.Assignments)
|
|
if n.Head == nil && n.Vars == nil {
|
|
// Permanent assignment.
|
|
return seqOp{assignmentOps}
|
|
}
|
|
for _, a := range n.Assignments {
|
|
v, r := cp.lvaluesOp(a.Left)
|
|
saveVarsOps = append(saveVarsOps, v, r)
|
|
}
|
|
logger.Println("temporary assignment of", len(n.Assignments), "pairs")
|
|
}
|
|
|
|
// Depending on the type of the form, exactly one of the three below will be
|
|
// set.
|
|
var (
|
|
specialOpFunc effectOpBody
|
|
headOp valuesOp
|
|
spaceyAssignOp effectOp
|
|
)
|
|
|
|
// Forward declaration; needed when compiling assignment forms.
|
|
var argOps []valuesOp
|
|
|
|
if n.Head != nil {
|
|
headStr, ok := oneString(n.Head)
|
|
if ok {
|
|
compileForm, ok := builtinSpecials[headStr]
|
|
if ok {
|
|
// Special form.
|
|
specialOpFunc = compileForm(cp, n)
|
|
} else {
|
|
var headOpFunc valuesOpBody
|
|
sigil, qname := SplitVariableRef(headStr)
|
|
if sigil == "" && cp.registerVariableGet(qname+FnSuffix) {
|
|
// $head~ resolves.
|
|
headOpFunc = variableOp{false, qname + FnSuffix}
|
|
} else {
|
|
// Fall back to $e:head~.
|
|
headOpFunc = literalValues(ExternalCmd{headStr})
|
|
}
|
|
headOp = valuesOp{headOpFunc, n.Head.Range().From, n.Head.Range().To}
|
|
}
|
|
} else {
|
|
// Head exists and is not a literal string. Evaluate as a normal
|
|
// expression.
|
|
headOp = cp.compoundOp(n.Head)
|
|
}
|
|
} else {
|
|
// Assignment form.
|
|
varsOp, restOp := cp.lvaluesMulti(n.Vars)
|
|
// This cannot be replaced with newSeqValuesOp as it depends on the fact
|
|
// that argOps will be changed later.
|
|
argsOp := valuesOp{
|
|
funcValuesOp(func(fm *Frame) ([]interface{}, error) {
|
|
var values []interface{}
|
|
for _, op := range argOps {
|
|
moreValues, err := op.exec(fm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
values = append(values, moreValues...)
|
|
}
|
|
return values, nil
|
|
}), -1, -1}
|
|
if len(argOps) > 0 {
|
|
argsOp.begin = argOps[0].begin
|
|
argsOp.end = argOps[len(argOps)-1].end
|
|
}
|
|
spaceyAssignOp = effectOp{
|
|
&assignmentOp{varsOp, restOp, argsOp},
|
|
n.Range().From, argsOp.end,
|
|
}
|
|
}
|
|
|
|
argOps = cp.compoundOps(n.Args)
|
|
optsOp := cp.mapPairs(n.Opts)
|
|
redirOps := cp.redirOps(n.Redirs)
|
|
// TODO: n.ErrorRedir
|
|
|
|
return &formOp{saveVarsOps, assignmentOps, redirOps, specialOpFunc, headOp, argOps, optsOp, spaceyAssignOp, n.Range().From, n.Range().To}
|
|
}
|
|
|
|
type formOp struct {
|
|
saveVarsOps []lvaluesOp
|
|
assignmentOps []effectOp
|
|
redirOps []effectOp
|
|
specialOpBody effectOpBody
|
|
headOp valuesOp
|
|
argOps []valuesOp
|
|
optsOp valuesOpBody
|
|
spaceyAssignOp effectOp
|
|
begin, end int
|
|
}
|
|
|
|
func (op *formOp) invoke(fm *Frame) (errRet error) {
|
|
// ec here is always a subevaler created in compiler.pipeline, so it can
|
|
// be safely modified.
|
|
|
|
// Temporary assignment.
|
|
if len(op.saveVarsOps) > 0 {
|
|
// There is a temporary assignment.
|
|
// Save variables.
|
|
var saveVars []vars.Var
|
|
var saveVals []interface{}
|
|
for _, op := range op.saveVarsOps {
|
|
moreSaveVars, err := op.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
saveVars = append(saveVars, moreSaveVars...)
|
|
}
|
|
for i, v := range saveVars {
|
|
// XXX(xiaq): If the variable to save is a elemVariable, save
|
|
// the outermost variable instead.
|
|
if u := vars.HeadOfElement(v); u != nil {
|
|
v = u
|
|
saveVars[i] = v
|
|
}
|
|
val := v.Get()
|
|
saveVals = append(saveVals, val)
|
|
logger.Printf("saved %s = %s", v, val)
|
|
}
|
|
// Do assignment.
|
|
for _, subop := range op.assignmentOps {
|
|
err := subop.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Defer variable restoration. Will be executed even if an error
|
|
// occurs when evaling other part of the form.
|
|
defer func() {
|
|
for i, v := range saveVars {
|
|
val := saveVals[i]
|
|
if val == nil {
|
|
// XXX Old value is nonexistent. We should delete the
|
|
// variable. However, since the compiler now doesn't delete
|
|
// it, we don't delete it in the evaler either.
|
|
val = ""
|
|
}
|
|
err := v.Set(val)
|
|
if err != nil {
|
|
errRet = err
|
|
}
|
|
logger.Printf("restored %s = %s", v, val)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// redirs
|
|
for _, redirOp := range op.redirOps {
|
|
err := redirOp.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if op.specialOpBody != nil {
|
|
return op.specialOpBody.invoke(fm)
|
|
}
|
|
var headFn Callable
|
|
var args []interface{}
|
|
if op.headOp.body != nil {
|
|
// head
|
|
headFn, errRet = fm.ExecAndUnwrap("head of command", op.headOp).One().Callable()
|
|
if errRet != nil {
|
|
return errRet
|
|
}
|
|
|
|
// args
|
|
for _, argOp := range op.argOps {
|
|
moreArgs, err := argOp.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
args = append(args, moreArgs...)
|
|
}
|
|
}
|
|
|
|
// opts
|
|
// XXX This conversion should be avoided.
|
|
optValues, err := op.optsOp.invoke(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts := optValues[0].(hashmap.Map)
|
|
convertedOpts := make(map[string]interface{})
|
|
for it := opts.Iterator(); it.HasElem(); it.Next() {
|
|
k, v := it.Elem()
|
|
if ks, ok := k.(string); ok {
|
|
convertedOpts[ks] = v
|
|
} else {
|
|
return fmt.Errorf("Option key must be string, got %s", vals.Kind(k))
|
|
}
|
|
}
|
|
|
|
fm.begin, fm.end = op.begin, op.end
|
|
|
|
if headFn != nil {
|
|
return headFn.Call(fm, args, convertedOpts)
|
|
}
|
|
return op.spaceyAssignOp.exec(fm)
|
|
}
|
|
|
|
func allTrue(vs []interface{}) bool {
|
|
for _, v := range vs {
|
|
if !vals.Bool(v) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (cp *compiler) assignment(n *parse.Assignment) effectOpBody {
|
|
valuesOp := cp.compoundOp(n.Right)
|
|
variablesOp, restOp := cp.lvaluesOp(n.Left)
|
|
return &assignmentOp{variablesOp, restOp, valuesOp}
|
|
}
|
|
|
|
// ErrMoreThanOneRest is returned when the LHS of an assignment contains more
|
|
// than one rest variables.
|
|
var ErrMoreThanOneRest = errors.New("more than one @ lvalue")
|
|
|
|
type assignmentOp struct {
|
|
variablesOp lvaluesOp
|
|
restOp lvaluesOp
|
|
valuesOp valuesOp
|
|
}
|
|
|
|
func (op *assignmentOp) invoke(fm *Frame) (errRet error) {
|
|
variables, err := op.variablesOp.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rest, err := op.restOp.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
values, err := op.valuesOp.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(rest) > 1 {
|
|
return ErrMoreThanOneRest
|
|
}
|
|
if len(rest) == 1 {
|
|
if len(variables) > len(values) {
|
|
return ErrArityMismatch
|
|
}
|
|
} else {
|
|
if len(variables) != len(values) {
|
|
return ErrArityMismatch
|
|
}
|
|
}
|
|
|
|
for i, variable := range variables {
|
|
err := variable.Set(values[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(rest) == 1 {
|
|
err := rest[0].Set(vals.MakeList(values[len(variables):]...))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func fixNilVariables(vs []vars.Var, perr *error) {
|
|
for _, v := range vs {
|
|
if vars.IsBlackhole(v) {
|
|
continue
|
|
}
|
|
if v.Get() == nil {
|
|
err := v.Set("")
|
|
*perr = util.Errors(*perr, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (cp *compiler) literal(n *parse.Primary, msg string) string {
|
|
switch n.Type {
|
|
case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
|
|
return n.Value
|
|
default:
|
|
cp.compiling(n)
|
|
cp.errorf(msg)
|
|
return "" // not reached
|
|
}
|
|
}
|
|
|
|
const defaultFileRedirPerm = 0644
|
|
|
|
// redir compiles a Redir into a op.
|
|
func (cp *compiler) redir(n *parse.Redir) effectOpBody {
|
|
var dstOp valuesOp
|
|
if n.Left != nil {
|
|
dstOp = cp.compoundOp(n.Left)
|
|
}
|
|
flag := makeFlag(n.Mode)
|
|
if flag == -1 {
|
|
// TODO: Record and get redirection sign position
|
|
cp.errorf("bad redirection sign")
|
|
}
|
|
return &redirOp{dstOp, cp.compoundOp(n.Right), n.RightIsFd, n.Mode, flag}
|
|
}
|
|
|
|
func makeFlag(m parse.RedirMode) int {
|
|
switch m {
|
|
case parse.Read:
|
|
return os.O_RDONLY
|
|
case parse.Write:
|
|
return os.O_WRONLY | os.O_CREATE | os.O_TRUNC
|
|
case parse.ReadWrite:
|
|
return os.O_RDWR | os.O_CREATE
|
|
case parse.Append:
|
|
return os.O_WRONLY | os.O_CREATE | os.O_APPEND
|
|
default:
|
|
return -1
|
|
}
|
|
}
|
|
|
|
type redirOp struct {
|
|
dstOp valuesOp
|
|
srcOp valuesOp
|
|
srcIsFd bool
|
|
mode parse.RedirMode
|
|
flag int
|
|
}
|
|
|
|
func (op *redirOp) invoke(fm *Frame) error {
|
|
var dst int
|
|
if op.dstOp.body == nil {
|
|
// use default dst fd
|
|
switch op.mode {
|
|
case parse.Read:
|
|
dst = 0
|
|
case parse.Write, parse.ReadWrite, parse.Append:
|
|
dst = 1
|
|
default:
|
|
return fmt.Errorf("bad RedirMode; parser bug")
|
|
}
|
|
} else {
|
|
var err error
|
|
// dst must be a valid fd
|
|
dst, err = fm.ExecAndUnwrap("Fd", op.dstOp).One().Fd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fm.growPorts(dst + 1)
|
|
// Logger.Printf("closing old port %d of %s", dst, ec.context)
|
|
fm.ports[dst].Close()
|
|
|
|
srcUnwrap := fm.ExecAndUnwrap("redirection source", op.srcOp).One()
|
|
if op.srcIsFd {
|
|
src, err := srcUnwrap.FdOrClose()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if src == -1 {
|
|
// close
|
|
fm.ports[dst] = &Port{}
|
|
} else {
|
|
fm.ports[dst] = fm.ports[src].Fork()
|
|
}
|
|
} else {
|
|
src, err := srcUnwrap.Any()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch src := src.(type) {
|
|
case string:
|
|
f, err := os.OpenFile(src, op.flag, defaultFileRedirPerm)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open file %s: %s", vals.Repr(src, vals.NoPretty), err)
|
|
}
|
|
fm.ports[dst] = &Port{
|
|
File: f, Chan: BlackholeChan,
|
|
CloseFile: true,
|
|
}
|
|
case vals.File:
|
|
fm.ports[dst] = &Port{
|
|
File: src, Chan: BlackholeChan,
|
|
CloseFile: false,
|
|
}
|
|
case vals.Pipe:
|
|
var f *os.File
|
|
switch op.mode {
|
|
case parse.Read:
|
|
f = src.ReadEnd
|
|
case parse.Write:
|
|
f = src.WriteEnd
|
|
default:
|
|
return errors.New("can only use < or > with pipes")
|
|
}
|
|
fm.ports[dst] = &Port{
|
|
File: f, Chan: BlackholeChan,
|
|
CloseFile: false,
|
|
}
|
|
default:
|
|
srcUnwrap.error("string or file", "%s", vals.Kind(src))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type seqOp struct{ subops []effectOp }
|
|
|
|
func (op seqOp) invoke(fm *Frame) error {
|
|
for _, subop := range op.subops {
|
|
err := subop.exec(fm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type funcOp func(*Frame) error
|
|
|
|
func (op funcOp) invoke(fm *Frame) error {
|
|
return op(fm)
|
|
}
|