pkg/eval: Rewrite the lvalue parsing code.

Also lift the restriction on rest variables and rest arguments - they may now
appear anywhere, as long as there is only one.
This commit is contained in:
Qi Xiao 2020-08-20 13:15:00 +01:00
parent ce19bca599
commit 1e2ead56e2
12 changed files with 258 additions and 292 deletions

View File

@ -27,6 +27,9 @@ New features in the language:
- Slice indicies can now use `..` for left-closed, right-open ranges, and
`..=` for closed ranges.
- Rest variables and rest arguments are no longer restricted to the last
variable.
New features in the standard library:
- A new `eval` command supports evaluating a dynamic piece of code in a

View File

@ -487,10 +487,7 @@ func compileFor(cp *compiler, fn *parse.Form) effectOpBody {
elseNode := args.nextMustLambdaIfAfter("else")
args.mustEnd()
varOp, restOp := cp.lvaluesOp(varNode.Indexings[0])
if restOp.body != nil {
cp.errorpf(restOp, "rest not allowed")
}
lvalue := cp.compileOneLValue(varNode)
iterOp := cp.compoundOp(iterNode)
bodyOp := cp.primaryOp(bodyNode)
@ -499,18 +496,18 @@ func compileFor(cp *compiler, fn *parse.Form) effectOpBody {
elseOp = cp.primaryOp(elseNode)
}
return &forOp{varOp, iterOp, bodyOp, elseOp}
return &forOp{lvalue, iterOp, bodyOp, elseOp}
}
type forOp struct {
varOp lvaluesOp
lvalue lvalue
iterOp valuesOp
bodyOp valuesOp
elseOp valuesOp
}
func (op *forOp) invoke(fm *Frame) error {
variable, err := evalForVar(fm, op.varOp, "iterator")
variable, err := getVar(fm, op.lvalue)
if err != nil {
return err
}
@ -563,14 +560,14 @@ func compileTry(cp *compiler, fn *parse.Form) effectOpBody {
args := cp.walkArgs(fn)
bodyNode := args.nextMustLambda()
logger.Printf("body is %q", parse.SourceText(bodyNode))
var exceptVarNode *parse.Indexing
var exceptVarNode *parse.Compound
var exceptNode *parse.Primary
if args.nextIs("except") {
logger.Println("except-ing")
n := args.peek()
// Is this a variable?
if len(n.Indexings) == 1 && n.Indexings[0].Head.Type == parse.Bareword {
exceptVarNode = n.Indexings[0]
exceptVarNode = n
args.next()
}
exceptNode = args.nextMustLambda()
@ -579,15 +576,11 @@ func compileTry(cp *compiler, fn *parse.Form) effectOpBody {
finallyNode := args.nextMustLambdaIfAfter("finally")
args.mustEnd()
var exceptVarOp lvaluesOp
var exceptVar lvalue
var bodyOp, exceptOp, elseOp, finallyOp valuesOp
bodyOp = cp.primaryOp(bodyNode)
if exceptVarNode != nil {
var restOp lvaluesOp
exceptVarOp, restOp = cp.lvaluesOp(exceptVarNode)
if restOp.body != nil {
cp.errorpf(restOp, "may not use @rest in except variable")
}
exceptVar = cp.compileOneLValue(exceptVarNode)
}
if exceptNode != nil {
exceptOp = cp.primaryOp(exceptNode)
@ -599,28 +592,32 @@ func compileTry(cp *compiler, fn *parse.Form) effectOpBody {
finallyOp = cp.primaryOp(finallyNode)
}
return &tryOp{bodyOp, exceptVarOp, exceptOp, elseOp, finallyOp}
return &tryOp{bodyOp, exceptVar, exceptOp, elseOp, finallyOp}
}
type tryOp struct {
bodyOp valuesOp
exceptVarOp lvaluesOp
exceptOp valuesOp
elseOp valuesOp
finallyOp valuesOp
bodyOp valuesOp
exceptVar lvalue
exceptOp valuesOp
elseOp valuesOp
finallyOp valuesOp
}
func (op *tryOp) invoke(fm *Frame) error {
body := op.bodyOp.execlambdaOp(fm)
exceptVar, err := op.exceptVarOp.execMustOne(fm)
if err != nil {
return err
var exceptVar vars.Var
if op.exceptVar.qname != "" {
var err error
exceptVar, err = getVar(fm, op.exceptVar)
if err != nil {
return err
}
}
except := op.exceptOp.execlambdaOp(fm)
elseFn := op.elseOp.execlambdaOp(fm)
finally := op.finallyOp.execlambdaOp(fm)
err = body.Call(fm.fork("try body"), NoArgs, NoOpts)
err := body.Call(fm.fork("try body"), NoArgs, NoOpts)
if err != nil {
if except != nil {
if exceptVar != nil {
@ -647,6 +644,21 @@ func (op *tryOp) invoke(fm *Frame) error {
return err
}
func (cp *compiler) compileOneLValue(n *parse.Compound) lvalue {
if len(n.Indexings) != 1 {
cp.errorpf(n, "must be valid lvalue")
}
lvalues := cp.parseIndexingLValue(n.Indexings[0])
cp.registerLValues(lvalues)
if lvalues.rest != -1 {
cp.errorpf(lvalues.lvalues[lvalues.rest], "rest variable not allowed")
}
if len(lvalues.lvalues) != 1 {
cp.errorpf(n, "must be exactly one lvalue")
}
return lvalues.lvalues[0]
}
// execLambdaOp executes a ValuesOp that is known to yield a lambda and returns
// the lambda. If the ValuesOp is empty, it returns a nil.
func (op valuesOp) execlambdaOp(fm *Frame) Callable {

View File

@ -82,11 +82,7 @@ func TestBuiltinSpecial(t *testing.T) {
// continue
That("for x [a b] { put $x; continue; put $x; }").Puts("a", "b"),
// More than one iterator.
That("for {x,y} [] { }").Throws(
errs.ArityMismatch{
What: "iterator",
ValidLow: 1, ValidHigh: 1, Actual: 2},
"x,y"),
That("for {x,y} [] { }").DoesNotCompile(),
// Invalid for loop lvalue. You can't use a var in a namespace other
// than the special "local:" namespace as the lvalue in a for loop.
That("for no-such-namespace:x [a b] { }").ThrowsMessage("new variables can only be created in local scope"),

View File

@ -2,6 +2,7 @@ package eval
import (
"fmt"
"strconv"
"unsafe"
"github.com/elves/elvish/pkg/eval/errs"
@ -15,8 +16,8 @@ import (
// identity.
type Closure struct {
ArgNames []string
// The name for the rest argument. If empty, the function has fixed arity.
RestArg string
// The index of the rest argument. -1 if there is no rest argument.
RestArg int
OptNames []string
OptDefaults []interface{}
Op effectOp
@ -58,11 +59,11 @@ func listOfStrings(ss []string) vals.List {
// Call calls a closure.
func (c *Closure) Call(fm *Frame, args []interface{}, opts map[string]interface{}) error {
if c.RestArg != "" {
if len(args) < len(c.ArgNames) {
if c.RestArg != -1 {
if len(args) < len(c.ArgNames)-1 {
return errs.ArityMismatch{
What: "arguments here",
ValidLow: len(c.ArgNames), ValidHigh: -1, Actual: len(args)}
ValidLow: len(c.ArgNames) - 1, ValidHigh: -1, Actual: len(args)}
}
} else {
if len(args) != len(c.ArgNames) {
@ -83,14 +84,22 @@ func (c *Closure) Call(fm *Frame, args []interface{}, opts map[string]interface{
fm.up[name] = variable
}
// Populate local scope with arguments, possibly a rest argument, and
// options.
// Populate local scope with arguments and options.
fm.local = make(Ns)
for i, name := range c.ArgNames {
fm.local[name] = vars.FromInit(args[i])
}
if c.RestArg != "" {
fm.local[c.RestArg] = vars.FromInit(vals.MakeList(args[len(c.ArgNames):]...))
if c.RestArg == -1 {
for i, name := range c.ArgNames {
fm.local[name] = vars.FromInit(args[i])
}
} else {
for i := 0; i < c.RestArg; i++ {
fm.local[c.ArgNames[i]] = vars.FromInit(args[i])
}
restOff := len(args) - len(c.ArgNames)
fm.local[c.ArgNames[c.RestArg]] = vars.FromInit(
vals.MakeList(args[c.RestArg : c.RestArg+restOff+1]...))
for i := c.RestArg + 1; i < len(c.ArgNames); i++ {
fm.local[c.ArgNames[i]] = vars.FromInit(args[i+restOff])
}
}
optUsed := make(map[string]struct{})
for i, name := range c.OptNames {
@ -120,7 +129,7 @@ type closureFields struct{ c *Closure }
func (closureFields) IsStructMap() {}
func (cf closureFields) ArgNames() vals.List { return listOfStrings(cf.c.ArgNames) }
func (cf closureFields) RestArg() string { return cf.c.RestArg }
func (cf closureFields) RestArg() string { return strconv.Itoa(cf.c.RestArg) }
func (cf closureFields) OptNames() vals.List { return listOfStrings(cf.c.OptNames) }
func (cf closureFields) Src() parse.Source { return cf.c.SrcMeta }

View File

@ -31,7 +31,7 @@ func TestClosure(t *testing.T) {
That("[]{ } &k=v").ThrowsAny(),
That("all [a b]{ }[arg-names]").Puts("a", "b"),
That("put [@r]{ }[rest-arg]").Puts("r"),
That("put [@r]{ }[rest-arg]").Puts("0"),
That("all [&opt=def]{ }[opt-names]").Puts("opt"),
That("all [&opt=def]{ }[opt-defaults]").Puts("def"),
That("put { body }[body]").Puts(" body "),

View File

@ -183,7 +183,7 @@ func (op *pipelineOp) invoke(fm *Frame) error {
}
func (cp *compiler) formOp(n *parse.Form) effectOp {
var saveVarsOps []lvaluesOp
var tempLValues []lvalue
var assignmentOps []effectOp
if len(n.Assignments) > 0 {
assignmentOps = cp.assignmentOps(n.Assignments)
@ -192,8 +192,9 @@ func (cp *compiler) formOp(n *parse.Form) effectOp {
return makeEffectOp(n, seqOp{assignmentOps})
}
for _, a := range n.Assignments {
v, r := cp.lvaluesOp(a.Left)
saveVarsOps = append(saveVarsOps, v, r)
lvalues := cp.parseIndexingLValue(a.Left)
cp.registerLValues(lvalues)
tempLValues = append(tempLValues, lvalues.lvalues...)
}
logger.Println("temporary assignment of", len(n.Assignments), "pairs")
}
@ -236,26 +237,25 @@ func (cp *compiler) formOp(n *parse.Form) effectOp {
argOps = cp.compoundOps(n.Args)
} else {
// Assignment form.
varsOp, restOp := cp.lvaluesMulti(n.Vars)
lhs := cp.parseCompoundLValues(n.Vars)
cp.registerLValues(lhs)
argOps = cp.compoundOps(n.Args)
valuesOp := valuesOp{body: seqValuesOp{argOps}}
rhs := valuesOp{body: seqValuesOp{argOps}}
if len(argOps) > 0 {
valuesOp.From = argOps[0].From
valuesOp.To = argOps[len(argOps)-1].To
rhs.From = argOps[0].From
rhs.To = argOps[len(argOps)-1].To
} else {
valuesOp.From = n.Range().To
valuesOp.To = n.Range().To
}
spaceyAssignOp = effectOp{
&assignmentOp{varsOp, restOp, valuesOp}, n.Range(),
rhs.From = n.Range().To
rhs.To = n.Range().To
}
spaceyAssignOp = makeEffectOp(n, &assignOp{lhs, rhs})
}
optsOp := cp.mapPairs(n.Opts)
redirOps := cp.redirOps(n.Redirs)
// TODO: n.ErrorRedir
return makeEffectOp(n, &formOp{n.Range(), saveVarsOps, assignmentOps, redirOps, specialOpFunc, headOp, argOps, optsOp, spaceyAssignOp})
return makeEffectOp(n, &formOp{n.Range(), tempLValues, assignmentOps, redirOps, specialOpFunc, headOp, argOps, optsOp, spaceyAssignOp})
}
func (cp *compiler) formOps(ns []*parse.Form) []effectOp {
@ -268,7 +268,7 @@ func (cp *compiler) formOps(ns []*parse.Form) []effectOp {
type formOp struct {
diag.Ranging
saveVarsOps []lvaluesOp
tempLValues []lvalue
assignmentOps []effectOp
redirOps []effectOp
specialOpBody effectOpBody
@ -283,17 +283,17 @@ func (op *formOp) invoke(fm *Frame) (errRet error) {
// be safely modified.
// Temporary assignment.
if len(op.saveVarsOps) > 0 {
if len(op.tempLValues) > 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)
for _, lv := range op.tempLValues {
variable, err := getVar(fm, lv)
if err != nil {
return err
}
saveVars = append(saveVars, moreSaveVars...)
saveVars = append(saveVars, variable)
}
for i, v := range saveVars {
// TODO(xiaq): If the variable to save is a elemVariable, save
@ -423,9 +423,10 @@ func allTrue(vs []interface{}) bool {
}
func (cp *compiler) assignmentOp(n *parse.Assignment) effectOp {
valuesOp := cp.compoundOp(n.Right)
variablesOp, restOp := cp.lvaluesOp(n.Left)
return makeEffectOp(n, &assignmentOp{variablesOp, restOp, valuesOp})
lhs := cp.parseIndexingLValue(n.Left)
cp.registerLValues(lhs)
rhs := cp.compoundOp(n.Right)
return makeEffectOp(n, &assignOp{lhs, rhs})
}
func (cp *compiler) assignmentOps(ns []*parse.Assignment) []effectOp {
@ -436,64 +437,6 @@ func (cp *compiler) assignmentOps(ns []*parse.Assignment) []effectOp {
return ops
}
// 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(values) < len(variables) {
return errs.ArityMismatch{
What: "assignment right-hand-side",
ValidLow: len(variables), ValidHigh: -1, Actual: len(values)}
}
} else {
if len(variables) != len(values) {
return errs.ArityMismatch{
What: "assignment right-hand-side",
ValidLow: len(variables), ValidHigh: len(variables), Actual: len(values)}
}
}
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 (cp *compiler) literal(n *parse.Primary, msg string) string {
switch n.Type {
case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:

View File

@ -75,6 +75,9 @@ func TestCompileEffect(t *testing.T) {
That("a = foo; put $a").Puts("foo"),
That("a b = foo bar; put $a $b").Puts("foo", "bar"),
That("a @b = 2 3 foo; put $a $b").Puts("2", vals.MakeList("3", "foo")),
That("a @b c = 1 2 3 4; put $a $b $c").
Puts("1", vals.MakeList("2", "3"), "4"),
That("a @b c = 1 2; put $a $b $c").Puts("1", vals.EmptyList, "2"),
That("@a = ; put $a").Puts(vals.EmptyList),
// List element assignment

View File

@ -5,151 +5,160 @@ import (
"fmt"
"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/parse"
)
// lvaluesOp compiles lvalues, returning the fixed part and, optionally a rest
// part.
//
// In the AST an lvalue is either an Indexing node where the head is a string
// literal, or a braced list of such Indexing nodes. The last Indexing node may
// be prefixed by @, in which case they become the rest part. For instance, in
// {a[x],b,@c[z]}, "a[x],b" is the fixed part and "c[z]" is the rest part.
func (cp *compiler) lvaluesOp(n *parse.Indexing) (lvaluesOp, lvaluesOp) {
if n.Head.Type == parse.Braced {
// Braced list of variable specs, possibly with indicies.
if len(n.Indicies) > 0 {
cp.errorpf(n, "may not have indicies")
}
return cp.lvaluesMulti(n.Head.Braced)
}
rest, opFunc := cp.lvalueBase(n, "must be an lvalue or a braced list of those")
op := lvaluesOp{opFunc, n.Range()}
if rest {
return lvaluesOp{}, op
}
return op, lvaluesOp{}
// Parsed group of lvalues.
type lvaluesGroup struct {
lvalues []lvalue
// Index of the rest variable within lvalues. If there is no rest variable,
// the index is -1.
rest int
}
func (cp *compiler) lvaluesMulti(nodes []*parse.Compound) (lvaluesOp, lvaluesOp) {
opFuncs := make([]lvaluesOpBody, len(nodes))
var restNode *parse.Indexing
var restOpFunc lvaluesOpBody
// Compile each spec inside the brace.
fixedEnd := 0
for i, cn := range nodes {
if len(cn.Indexings) != 1 {
cp.errorpf(cn, "must be an lvalue")
}
var rest bool
rest, opFuncs[i] = cp.lvalueBase(cn.Indexings[0], "must be an lvalue ")
// Only the last one may a rest part.
if rest {
if i == len(nodes)-1 {
restNode = cn.Indexings[0]
restOpFunc = opFuncs[i]
} else {
cp.errorpf(cn, "only the last lvalue may have @")
}
} else {
fixedEnd = cn.Range().To
}
}
var restOp lvaluesOp
// If there is a rest part, make LValuesOp for it and remove it from opFuncs.
if restOpFunc != nil {
restOp = lvaluesOp{restOpFunc, restNode.Range()}
opFuncs = opFuncs[:len(opFuncs)-1]
}
var op lvaluesOp
// If there is still anything left in opFuncs, make LValuesOp for the fixed part.
if len(opFuncs) > 0 {
op = lvaluesOp{seqLValuesOpBody{opFuncs}, diag.Ranging{From: nodes[0].Range().From, To: fixedEnd}}
}
return op, restOp
}
func (cp *compiler) lvalueBase(n *parse.Indexing, msg string) (bool, lvaluesOpBody) {
ref := cp.literal(n.Head, msg)
sigil, qname := SplitVariableRef(ref)
// TODO: Deal with other sigils too
explode := sigil != ""
if len(n.Indicies) == 0 {
cp.registerVariableSet(qname)
return explode, varOp{qname}
}
return explode, cp.lvalueElement(qname, n)
}
func (cp *compiler) lvalueElement(qname string, n *parse.Indexing) lvaluesOpBody {
if !cp.registerVariableGet(qname, nil) {
cp.errorpf(n, "variable $%s not found", qname)
}
ends := make([]int, len(n.Indicies)+1)
ends[0] = n.Head.Range().To
for i, idx := range n.Indicies {
ends[i+1] = idx.Range().To
}
indexOps := cp.arrayOps(n.Indicies)
return &elemOp{n.Range(), qname, indexOps, ends}
}
type seqLValuesOpBody struct {
ops []lvaluesOpBody
}
func (op seqLValuesOpBody) invoke(fm *Frame) ([]vars.Var, error) {
var variables []vars.Var
for _, op := range op.ops {
moreVariables, err := op.invoke(fm)
if err != nil {
return nil, err
}
variables = append(variables, moreVariables...)
}
return variables, nil
}
type varOp struct {
qname string
}
func (op varOp) invoke(fm *Frame) ([]vars.Var, error) {
variable := fm.ResolveVar(op.qname)
if variable == nil {
ns, _ := SplitQNameNs(op.qname)
if ns == "" || ns == ":" || ns == "local:" {
// This should have been created as part of pipelineOp.
return nil, errors.New("compiler bug: new local variable not created in pipeline")
}
return nil, fmt.Errorf("new variables can only be created in local scope")
}
return []vars.Var{variable}, nil
}
type elemOp struct {
// Parsed lvalue.
type lvalue struct {
diag.Ranging
qname string
indexOps []valuesOp
ends []int
}
func (op *elemOp) invoke(fm *Frame) ([]vars.Var, error) {
variable := fm.ResolveVar(op.qname)
if variable == nil {
return nil, fmt.Errorf("variable $%s does not exist, compiler bug", op.qname)
func (cp *compiler) parseCompoundLValues(ns []*parse.Compound) lvaluesGroup {
g := lvaluesGroup{nil, -1}
for _, n := range ns {
if len(n.Indexings) != 1 {
cp.errorpf(n, "lvalue may not be composite expressions")
}
more := cp.parseIndexingLValue(n.Indexings[0])
if more.rest == -1 {
g.lvalues = append(g.lvalues, more.lvalues...)
} else if g.rest != -1 {
cp.errorpf(n, "at most one rest variable is allowed")
} else {
g.rest = len(g.lvalues) + more.rest
g.lvalues = append(g.lvalues, more.lvalues...)
}
}
return g
}
func (cp *compiler) parseIndexingLValue(n *parse.Indexing) lvaluesGroup {
if n.Head.Type == parse.Braced {
// Braced list of lvalues may not have indicies.
if len(n.Indicies) > 0 {
cp.errorpf(n, "braced list may not have indicies when used as lvalue")
}
return cp.parseCompoundLValues(n.Head.Braced)
}
// A basic lvalue.
ref := cp.literal(n.Head, "lvalue only supports literal variable names")
sigil, qname := SplitVariableRef(ref)
ends := make([]int, len(n.Indicies)+1)
ends[0] = n.Head.Range().To
for i, idx := range n.Indicies {
ends[i+1] = idx.Range().To
}
lv := lvalue{n.Range(), qname, cp.arrayOps(n.Indicies), ends}
restIndex := -1
if sigil == "@" {
restIndex = 0
}
// TODO: Deal with other sigils when they exist.
return lvaluesGroup{[]lvalue{lv}, restIndex}
}
func (cp *compiler) registerLValues(lhs lvaluesGroup) {
for _, lv := range lhs.lvalues {
if len(lv.indexOps) == 0 {
cp.registerVariableSet(lv.qname)
} else {
ok := cp.registerVariableGet(lv.qname, lv)
if !ok {
cp.errorpf(lv, "variable $%s not found", lv.qname)
}
}
}
}
type assignOp struct {
lhs lvaluesGroup
rhs valuesOp
}
func (op *assignOp) invoke(fm *Frame) error {
variables := make([]vars.Var, len(op.lhs.lvalues))
for i, lvalue := range op.lhs.lvalues {
variable, err := getVar(fm, lvalue)
if err != nil {
return err
}
variables[i] = variable
}
indicies := make([]interface{}, len(op.indexOps))
for i, op := range op.indexOps {
values, err := op.rhs.exec(fm)
if err != nil {
return err
}
if op.lhs.rest == -1 {
if len(variables) != len(values) {
return errs.ArityMismatch{
What: "assignment right-hand-side",
ValidLow: len(variables), ValidHigh: len(variables), Actual: len(values)}
}
for i, variable := range variables {
err := variable.Set(values[i])
if err != nil {
return err
}
}
} else {
if len(values) < len(variables)-1 {
return errs.ArityMismatch{
What: "assignment right-hand-side",
ValidLow: len(variables) - 1, ValidHigh: -1, Actual: len(values)}
}
rest := op.lhs.rest
for i := 0; i < rest; i++ {
err := variables[i].Set(values[i])
if err != nil {
return err
}
}
restOff := len(values) - len(variables)
err := variables[rest].Set(vals.MakeList(values[rest : rest+restOff+1]...))
if err != nil {
return err
}
for i := rest + 1; i < len(variables); i++ {
err := variables[i].Set(values[i+restOff])
if err != nil {
return err
}
}
}
return nil
}
func getVar(fm *Frame, lv lvalue) (vars.Var, error) {
variable := fm.ResolveVar(lv.qname)
if variable == nil {
ns, _ := SplitQNameNs(lv.qname)
if ns == "" || ns == ":" || ns == "local:" {
// This should have been created as part of pipelineOp.
return nil, errors.New("compiler bug: new local variable not created in pipeline")
}
return nil, fmt.Errorf("new variables can only be created in local scope")
}
if len(lv.indexOps) == 0 {
return variable, nil
}
indicies := make([]interface{}, len(lv.indexOps))
for i, op := range lv.indexOps {
values, err := op.exec(fm)
if err != nil {
return nil, err
@ -164,9 +173,9 @@ func (op *elemOp) invoke(fm *Frame) ([]vars.Var, error) {
if err != nil {
level := vars.ElementErrorLevel(err)
if level < 0 {
return nil, fm.errorp(op, err)
return nil, fm.errorp(lv, err)
}
return nil, fm.errorp(diag.Ranging{From: op.From, To: op.ends[level]}, err)
return nil, fm.errorp(diag.Ranging{From: lv.From, To: lv.ends[level]}, err)
}
return []vars.Var{elemVar}, nil
return elemVar, nil
}

View File

@ -416,7 +416,7 @@ func (cp *compiler) lambda(n *parse.Primary) valuesOpBody {
// Parse signature.
var (
argNames []string
restArgName string
restArg int = -1
optNames []string
optDefaultOps []valuesOp
)
@ -426,7 +426,6 @@ func (cp *compiler) lambda(n *parse.Primary) valuesOpBody {
for i, arg := range n.Elements {
ref := mustString(cp, arg, "argument name must be literal string")
sigil, qname := SplitVariableRef(ref)
explode := sigil != ""
ns, name := SplitQNameNs(qname)
if ns != "" {
cp.errorpf(arg, "argument name must be unqualified")
@ -434,15 +433,13 @@ func (cp *compiler) lambda(n *parse.Primary) valuesOpBody {
if name == "" {
cp.errorpf(arg, "argument name must not be empty")
}
if explode {
if i != len(n.Elements)-1 {
cp.errorpf(arg, "only the last argument may have @")
if sigil == "@" {
if restArg != -1 {
cp.errorpf(arg, "only one argument may have @")
}
restArgName = name
argNames = argNames[:i]
} else {
argNames[i] = name
restArg = i
}
argNames[i] = name
}
}
if len(n.MapPairs) > 0 {
@ -470,9 +467,6 @@ func (cp *compiler) lambda(n *parse.Primary) valuesOpBody {
for _, argName := range argNames {
thisScope.set(argName)
}
if restArgName != "" {
thisScope.set(restArgName)
}
for _, optName := range optNames {
thisScope.set(optName)
}
@ -490,12 +484,12 @@ func (cp *compiler) lambda(n *parse.Primary) valuesOpBody {
cp.registerVariableGet(name, nil)
}
return &lambdaOp{argNames, restArgName, optNames, optDefaultOps, capture, scopeOp, cp.srcMeta, n.Range().From, n.Range().To}
return &lambdaOp{argNames, restArg, optNames, optDefaultOps, capture, scopeOp, cp.srcMeta, n.Range().From, n.Range().To}
}
type lambdaOp struct {
argNames []string
restArgName string
restArg int
optNames []string
optDefaultOps []valuesOp
capture staticNs
@ -518,7 +512,7 @@ func (op *lambdaOp) invoke(fm *Frame) ([]interface{}, error) {
}
optDefaults[i] = defaultValue
}
return []interface{}{&Closure{op.argNames, op.restArgName, op.optNames, optDefaults, op.subop, evCapture, op.srcMeta, op.defBegin, op.defEnd}}, nil
return []interface{}{&Closure{op.argNames, op.restArg, op.optNames, optDefaults, op.subop, evCapture, op.srcMeta, op.defBegin, op.defEnd}}, nil
}
func (cp *compiler) map_(n *parse.Primary) valuesOpBody {

View File

@ -163,6 +163,8 @@ func TestCompileValue(t *testing.T) {
// Rest argument.
That("[x @xs]{ put $x $xs } a b c").Puts("a", vals.MakeList("b", "c")),
That("[a @b c]{ put $a $b $c } a b c d").
Puts("a", vals.MakeList("b", "c"), "d"),
// Options.
That("[a &k=v]{ put $a $k } foo &k=bar").Puts("foo", "bar"),
// Option default value.
@ -173,8 +175,6 @@ func TestCompileValue(t *testing.T) {
// Argument name must not be empty.
That("['']{ }").DoesNotCompile(),
That("[@]{ }").DoesNotCompile(),
// Only the last argument may be prefixed with @.
That("[@a b]{ }").DoesNotCompile(),
// Option name must be unqualified.
That("[&a:b=1]{ }").DoesNotCompile(),
// Option name must not be empty.

View File

@ -2,7 +2,6 @@ package eval
import (
"github.com/elves/elvish/pkg/eval/errs"
"github.com/elves/elvish/pkg/eval/vars"
)
func evalForValue(fm *Frame, op valuesOp, what string) (interface{}, error) {
@ -16,15 +15,3 @@ func evalForValue(fm *Frame, op valuesOp, what string) (interface{}, error) {
}
return values[0], nil
}
func evalForVar(fm *Frame, op lvaluesOp, what string) (vars.Var, error) {
lvalues, err := op.exec(fm)
if err != nil {
return nil, err
}
if len(lvalues) != 1 {
return nil, fm.errorp(op, errs.ArityMismatch{
What: what, ValidLow: 1, ValidHigh: 1, Actual: len(lvalues)})
}
return lvalues[0], nil
}

View File

@ -341,9 +341,9 @@ get all its element values. This is called **exploding** the variable:
(This notation is restricted to exploding variables. To explode arbitrary
values, use the builtin [all](builtin.html#all) command.)
When assigning variables, if you prefix the name of the last variable with `@`,
it gets assigned a list containing all remaining values. That variable is called
a **rest variable**. Example:
When assigning variables, you may prefix the name of one variable with `@`, it
gets assigned a list containing all remaining values. That variable is called a
**rest variable**. Example:
```elvish-transcript
~> a b @rest = 1 2 3 4 5 6 7
@ -351,6 +351,11 @@ a **rest variable**. Example:
▶ 1
▶ 2
▶ [3 4 5 6 7]
~> a @rest b = 1 2 3 4 5 6 7
~> put $a $rest $b
▶ 1
▶ [2 3 4 5 6]
▶ 7
```
Schematically this is a reverse operation to variable explosion, which is why
@ -557,8 +562,8 @@ signature as a list, followed by a lambda without signature:
<closure 0xc42004a480>
```
Like in the left hand of assignments, if you prefix the last argument with `@`,
it becomes a **rest argument**, and its value is a list containing all the
Like in the left hand of assignments, if you prefix one of the arguments with
`@`, it becomes a **rest argument**, and its value is a list containing all the
remaining arguments:
```elvish-transcript
@ -569,6 +574,11 @@ remaining arguments:
~> $f lorem ipsum dolar sit
▶ lorem
▶ [ipsum dolar sit]
~> f = [a @rest b]{ put $a $rest $b }
~> $f lorem ipsum dolar sit
▶ lorem
▶ [ipsum dolar]
▶ sit
```
You can also declare options in the signature. The syntax is `&name=default`