package eval import ( "errors" "fmt" "strings" "github.com/elves/elvish/pkg/diag" "github.com/elves/elvish/pkg/eval/errs" "github.com/elves/elvish/pkg/eval/vals" "github.com/elves/elvish/pkg/eval/vars" "github.com/elves/elvish/pkg/fsutil" "github.com/elves/elvish/pkg/glob" "github.com/elves/elvish/pkg/parse" ) // An operation that produces values. type valuesOp interface { diag.Ranger exec(*Frame) ([]interface{}, error) } var outputCaptureBufferSize = 16 func (cp *compiler) compoundOp(n *parse.Compound) valuesOp { if len(n.Indexings) == 0 { return literalValues(n, "") } tilde := false indexings := n.Indexings if n.Indexings[0].Head.Type == parse.Tilde { // A lone ~. if len(n.Indexings) == 1 { return loneTildeOp{n.Range()} } tilde = true indexings = indexings[1:] } return compoundOp{n.Range(), tilde, cp.indexingOps(indexings)} } type loneTildeOp struct{ diag.Ranging } func (loneTildeOp) exec(fm *Frame) ([]interface{}, error) { home, err := fsutil.GetHome("") if err != nil { return nil, err } return []interface{}{home}, nil } func (cp *compiler) compoundOps(ns []*parse.Compound) []valuesOp { ops := make([]valuesOp, len(ns)) for i, n := range ns { ops[i] = cp.compoundOp(n) } return ops } type compoundOp struct { diag.Ranging tilde bool subops []valuesOp } func (op compoundOp) exec(fm *Frame) ([]interface{}, error) { // Accumulator. vs, err := op.subops[0].exec(fm) if err != nil { return nil, err } for _, subop := range op.subops[1:] { us, err := subop.exec(fm) if err != nil { return nil, err } vs, err = outerProduct(vs, us, vals.Concat) if err != nil { return nil, fm.errorp(op, err) } } if op.tilde { newvs := make([]interface{}, len(vs)) for i, v := range vs { tilded, err := doTilde(v) if err != nil { return nil, fm.errorp(op, err) } newvs[i] = tilded } vs = newvs } hasGlob := false for _, v := range vs { if _, ok := v.(globPattern); ok { hasGlob = true break } } if hasGlob { newvs := make([]interface{}, 0, len(vs)) for _, v := range vs { if gp, ok := v.(globPattern); ok { results, err := doGlob(gp, fm.Interrupts()) if err != nil { return nil, fm.errorp(op, err) } newvs = append(newvs, results...) } else { newvs = append(newvs, v) } } vs = newvs } return vs, nil } func outerProduct(vs []interface{}, us []interface{}, f func(interface{}, interface{}) (interface{}, error)) ([]interface{}, error) { ws := make([]interface{}, len(vs)*len(us)) nu := len(us) for i, v := range vs { for j, u := range us { var err error ws[i*nu+j], err = f(v, u) if err != nil { return nil, err } } } return ws, nil } // Errors thrown when globbing. var ( ErrBadglobPattern = errors.New("bad globPattern; elvish bug") ErrCannotDetermineUsername = errors.New("cannot determine user name from glob pattern") ) func doTilde(v interface{}) (interface{}, error) { switch v := v.(type) { case string: s := v i := strings.Index(s, "/") var uname, rest string if i == -1 { uname = s } else { uname = s[:i] rest = s[i+1:] } dir, err := fsutil.GetHome(uname) if err != nil { return nil, err } // We do not use path.Join, as it removes trailing slashes. // // TODO(xiaq): Make this correct on Windows. return dir + "/" + rest, nil case globPattern: if len(v.Segments) == 0 { return nil, ErrBadglobPattern } switch seg := v.Segments[0].(type) { case glob.Literal: if len(v.Segments) == 1 { return nil, ErrBadglobPattern } _, isSlash := v.Segments[1].(glob.Slash) if isSlash { // ~username or ~username/xxx. Replace the first segment with // the home directory of the specified user. dir, err := fsutil.GetHome(seg.Data) if err != nil { return nil, err } v.Segments[0] = glob.Literal{Data: dir} return v, nil } case glob.Slash: dir, err := fsutil.GetHome("") if err != nil { return nil, err } v.DirOverride = dir return v, nil } return nil, ErrCannotDetermineUsername default: return nil, fmt.Errorf("tilde doesn't work on value of type %s", vals.Kind(v)) } } func (cp *compiler) arrayOp(n *parse.Array) valuesOp { return seqValuesOp{n.Range(), cp.compoundOps(n.Compounds)} } func (cp *compiler) arrayOps(ns []*parse.Array) []valuesOp { ops := make([]valuesOp, len(ns)) for i, n := range ns { ops[i] = cp.arrayOp(n) } return ops } func (cp *compiler) indexingOp(n *parse.Indexing) valuesOp { if len(n.Indicies) == 0 { return cp.primaryOp(n.Head) } return &indexingOp{n.Range(), cp.primaryOp(n.Head), cp.arrayOps(n.Indicies)} } func (cp *compiler) indexingOps(ns []*parse.Indexing) []valuesOp { ops := make([]valuesOp, len(ns)) for i, n := range ns { ops[i] = cp.indexingOp(n) } return ops } type indexingOp struct { diag.Ranging headOp valuesOp indexOps []valuesOp } func (op *indexingOp) exec(fm *Frame) ([]interface{}, error) { vs, err := op.headOp.exec(fm) if err != nil { return nil, err } for _, indexOp := range op.indexOps { indices, err := indexOp.exec(fm) if err != nil { return nil, err } newvs := make([]interface{}, 0, len(vs)*len(indices)) for _, v := range vs { for _, index := range indices { result, err := vals.Index(v, index) if err != nil { return nil, fm.errorp(op, err) } deprecation := vals.CheckDeprecatedIndex(v, index) if deprecation != "" { fm.Deprecate(deprecation, diag.NewContext(fm.srcMeta.Name, fm.srcMeta.Code, indexOp)) } newvs = append(newvs, result) } } vs = newvs } return vs, nil } func (cp *compiler) primaryOp(n *parse.Primary) valuesOp { switch n.Type { case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted: return literalValues(n, n.Value) case parse.Variable: sigil, qname := SplitSigil(n.Value) ref := resolveVarRef(cp, qname, n) if ref == nil { cp.errorpf(n, "variable $%s not found", qname) } return &variableOp{n.Range(), sigil != "", qname, ref} case parse.Wildcard: seg, err := wildcardToSegment(parse.SourceText(n)) if err != nil { cp.errorpf(n, "%s", err) } vs := []interface{}{ globPattern{Pattern: glob.Pattern{Segments: []glob.Segment{seg}, DirOverride: ""}, Flags: 0, Buts: nil, TypeCb: nil}} return literalValues(n, vs...) case parse.Tilde: cp.errorpf(n, "compiler bug: Tilde not handled in .compound") return literalValues(n, "~") case parse.ExceptionCapture: return exceptionCaptureOp{n.Range(), cp.chunkOp(n.Chunk)} case parse.OutputCapture: return outputCaptureOp{n.Range(), cp.chunkOp(n.Chunk)} case parse.List: return listOp{n.Range(), cp.compoundOps(n.Elements)} case parse.Lambda: return cp.lambda(n) case parse.Map: return mapOp{n.Range(), cp.mapPairs(n.MapPairs)} case parse.Braced: return seqValuesOp{n.Range(), cp.compoundOps(n.Braced)} default: cp.errorpf(n, "bad PrimaryType; parser bug") return literalValues(n, parse.SourceText(n)) } } func (cp *compiler) primaryOps(ns []*parse.Primary) []valuesOp { ops := make([]valuesOp, len(ns)) for i, n := range ns { ops[i] = cp.primaryOp(n) } return ops } type variableOp struct { diag.Ranging explode bool qname string ref *varRef } func (op variableOp) exec(fm *Frame) ([]interface{}, error) { variable := deref(fm, op.ref) if variable == nil { return nil, fm.errorpf(op, "variable $%s not found", op.qname) } value := variable.Get() if op.explode { vs, err := vals.Collect(value) return vs, fm.errorp(op, err) } return []interface{}{value}, nil } type listOp struct { diag.Ranging subops []valuesOp } func (op listOp) exec(fm *Frame) ([]interface{}, error) { list := vals.EmptyList for _, subop := range op.subops { moreValues, err := subop.exec(fm) if err != nil { return nil, err } for _, moreValue := range moreValues { list = list.Cons(moreValue) } } return []interface{}{list}, nil } type exceptionCaptureOp struct { diag.Ranging subop effectOp } func (op exceptionCaptureOp) exec(fm *Frame) ([]interface{}, error) { err := op.subop.exec(fm) if err == nil { return []interface{}{OK}, nil } return []interface{}{err.(*exception)}, nil } type outputCaptureOp struct { diag.Ranging subop effectOp } func (op outputCaptureOp) exec(fm *Frame) ([]interface{}, error) { return fm.CaptureOutput(op.subop.exec) } func (cp *compiler) lambda(n *parse.Primary) valuesOp { // Parse signature. var ( argNames []string restArg int = -1 optNames []string optDefaultOps []valuesOp ) if len(n.Elements) > 0 { // Argument list. argNames = make([]string, len(n.Elements)) for i, arg := range n.Elements { ref := mustString(cp, arg, "argument name must be literal string") sigil, qname := SplitSigil(ref) name, rest := SplitQName(qname) if rest != "" { cp.errorpf(arg, "argument name must be unqualified") } if name == "" { cp.errorpf(arg, "argument name must not be empty") } if sigil == "@" { if restArg != -1 { cp.errorpf(arg, "only one argument may have @") } restArg = i } argNames[i] = name } } if len(n.MapPairs) > 0 { optNames = make([]string, len(n.MapPairs)) optDefaultOps = make([]valuesOp, len(n.MapPairs)) for i, opt := range n.MapPairs { qname := mustString(cp, opt.Key, "option name must be literal string") name, rest := SplitQName(qname) if rest != "" { cp.errorpf(opt.Key, "option name must be unqualified") } if name == "" { cp.errorpf(opt.Key, "option name must not be empty") } optNames[i] = name if opt.Value == nil { cp.errorpf(opt.Key, "option must have default value") } else { optDefaultOps[i] = cp.compoundOp(opt.Value) } } } local, capture := cp.pushScope() for _, argName := range argNames { local.add(argName) } for _, optName := range optNames { local.add(optName) } scopeSizeInit := len(local.names) chunkOp := cp.chunkOp(n.Chunk) newLocal := local.names[scopeSizeInit:] cp.popScope() return &lambdaOp{n.Range(), argNames, restArg, optNames, optDefaultOps, newLocal, capture, chunkOp, cp.srcMeta} } type lambdaOp struct { diag.Ranging argNames []string restArg int optNames []string optDefaultOps []valuesOp newLocal []string capture *staticUpNs subop effectOp srcMeta parse.Source } func (op *lambdaOp) exec(fm *Frame) ([]interface{}, error) { capture := &Ns{ names: op.capture.names, slots: make([]vars.Var, len(op.capture.names))} for i := range op.capture.names { if op.capture.local[i] { capture.slots[i] = fm.local.slots[op.capture.index[i]] } else { capture.slots[i] = fm.up.slots[op.capture.index[i]] } } optDefaults := make([]interface{}, len(op.optDefaultOps)) for i, op := range op.optDefaultOps { defaultValue, err := evalForValue(fm, op, "option default value") if err != nil { return nil, err } optDefaults[i] = defaultValue } return []interface{}{&closure{op.argNames, op.restArg, op.optNames, optDefaults, op.subop, op.newLocal, capture, op.srcMeta, op.Range()}}, nil } type mapOp struct { diag.Ranging pairsOp *mapPairsOp } func (op mapOp) exec(fm *Frame) ([]interface{}, error) { m := vals.EmptyMap err := op.pairsOp.exec(fm, func(k, v interface{}) error { m = m.Assoc(k, v) return nil }) if err != nil { return nil, err } return []interface{}{m}, nil } func (cp *compiler) mapPairs(pairs []*parse.MapPair) *mapPairsOp { npairs := len(pairs) keysOps := make([]valuesOp, npairs) valuesOps := make([]valuesOp, npairs) begins, ends := make([]int, npairs), make([]int, npairs) for i, pair := range pairs { keysOps[i] = cp.compoundOp(pair.Key) if pair.Value == nil { p := pair.Range().To valuesOps[i] = literalValues(diag.PointRanging(p), true) } else { valuesOps[i] = cp.compoundOp(pairs[i].Value) } begins[i], ends[i] = pair.Range().From, pair.Range().To } return &mapPairsOp{keysOps, valuesOps, begins, ends} } type mapPairsOp struct { keysOps []valuesOp valuesOps []valuesOp begins []int ends []int } func (op *mapPairsOp) exec(fm *Frame, f func(k, v interface{}) error) error { for i := range op.keysOps { keys, err := op.keysOps[i].exec(fm) if err != nil { return err } values, err := op.valuesOps[i].exec(fm) if err != nil { return err } if len(keys) != len(values) { return fm.errorpf(diag.Ranging{From: op.begins[i], To: op.ends[i]}, "%d keys but %d values", len(keys), len(values)) } for j, key := range keys { err := f(key, values[j]) if err != nil { return err } } } return nil } type literalValuesOp struct { diag.Ranging values []interface{} } func (op literalValuesOp) exec(*Frame) ([]interface{}, error) { return op.values, nil } func literalValues(r diag.Ranger, vs ...interface{}) valuesOp { return literalValuesOp{r.Range(), vs} } type seqValuesOp struct { diag.Ranging subops []valuesOp } func (op seqValuesOp) exec(fm *Frame) ([]interface{}, error) { var values []interface{} for _, subop := range op.subops { moreValues, err := subop.exec(fm) if err != nil { return nil, err } values = append(values, moreValues...) } return values, nil } func evalForValue(fm *Frame, op valuesOp, what string) (interface{}, error) { values, err := op.exec(fm) if err != nil { return nil, err } if len(values) != 1 { return nil, fm.errorp(op, errs.ArityMismatch{ What: what, ValidLow: 1, ValidHigh: 1, Actual: len(values)}) } return values[0], nil }