mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-04 19:07:51 +08:00
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:
parent
ce19bca599
commit
1e2ead56e2
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 "),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
Loading…
Reference in New Issue
Block a user