mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
6a7d99041d
This has always been the documented behavior, but up until this point, "set" actually behaved like the legacy assignment form, which creates the variable if it doesn't exist yet. This fixes the discrepancy. Addresses #645.
205 lines
5.2 KiB
Go
205 lines
5.2 KiB
Go
package eval
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"src.elv.sh/pkg/diag"
|
|
"src.elv.sh/pkg/eval/errs"
|
|
"src.elv.sh/pkg/eval/vals"
|
|
"src.elv.sh/pkg/eval/vars"
|
|
"src.elv.sh/pkg/parse"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// Parsed lvalue.
|
|
type lvalue struct {
|
|
diag.Ranging
|
|
ref *varRef
|
|
indexOps []valuesOp
|
|
ends []int
|
|
}
|
|
|
|
type lvalueFlag uint
|
|
|
|
const (
|
|
setLValue lvalueFlag = 1 << iota
|
|
newLValue
|
|
)
|
|
|
|
func (cp *compiler) parseCompoundLValues(ns []*parse.Compound, f lvalueFlag) 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], f)
|
|
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, f lvalueFlag) lvaluesGroup {
|
|
if n.Head.Type == parse.Braced {
|
|
// Braced list of lvalues may not have indices.
|
|
if len(n.Indices) > 0 {
|
|
cp.errorpf(n, "braced list may not have indices when used as lvalue")
|
|
}
|
|
return cp.parseCompoundLValues(n.Head.Braced, f)
|
|
}
|
|
// A basic lvalue.
|
|
if !parse.ValidLHSVariable(n.Head, true) {
|
|
cp.errorpf(n.Head, "lvalue must be valid literal variable names")
|
|
}
|
|
varUse := n.Head.Value
|
|
sigil, qname := SplitSigil(varUse)
|
|
|
|
var ref *varRef
|
|
if f&setLValue != 0 {
|
|
ref = resolveVarRef(cp, qname, n)
|
|
}
|
|
if ref == nil {
|
|
if f&newLValue == 0 {
|
|
cp.errorpf(n, "cannot find variable $%s", qname)
|
|
}
|
|
if len(n.Indices) > 0 {
|
|
cp.errorpf(n, "name for new variable must not have indices")
|
|
}
|
|
segs := SplitQNameSegs(qname)
|
|
if len(segs) == 1 {
|
|
// Unqualified name - implicit local
|
|
ref = &varRef{localScope, cp.thisScope().add(segs[0]), nil}
|
|
} else if len(segs) == 2 && (segs[0] == "local:" || segs[0] == ":") {
|
|
// Qualified local name
|
|
ref = &varRef{localScope, cp.thisScope().add(segs[1]), nil}
|
|
} else {
|
|
cp.errorpf(n, "cannot create variable $%s; new variables can only be created in the local scope", qname)
|
|
}
|
|
}
|
|
|
|
ends := make([]int, len(n.Indices)+1)
|
|
ends[0] = n.Head.Range().To
|
|
for i, idx := range n.Indices {
|
|
ends[i+1] = idx.Range().To
|
|
}
|
|
lv := lvalue{n.Range(), ref, cp.arrayOps(n.Indices), ends}
|
|
restIndex := -1
|
|
if sigil == "@" {
|
|
restIndex = 0
|
|
}
|
|
// TODO: Support % (and other sigils?) if https://b.elv.sh/584 is implemented for map explosion.
|
|
return lvaluesGroup{[]lvalue{lv}, restIndex}
|
|
}
|
|
|
|
type assignOp struct {
|
|
diag.Ranging
|
|
lhs lvaluesGroup
|
|
rhs valuesOp
|
|
}
|
|
|
|
func (op *assignOp) exec(fm *Frame) Exception {
|
|
variables := make([]vars.Var, len(op.lhs.lvalues))
|
|
for i, lvalue := range op.lhs.lvalues {
|
|
variable, err := derefLValue(fm, lvalue)
|
|
if err != nil {
|
|
return fm.errorp(op, err)
|
|
}
|
|
variables[i] = variable
|
|
}
|
|
|
|
values, exc := op.rhs.exec(fm)
|
|
if exc != nil {
|
|
return exc
|
|
}
|
|
|
|
if op.lhs.rest == -1 {
|
|
if len(variables) != len(values) {
|
|
return fm.errorp(op, 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 fm.errorp(op.lhs.lvalues[i], err)
|
|
}
|
|
}
|
|
} else {
|
|
if len(values) < len(variables)-1 {
|
|
return fm.errorp(op, 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 fm.errorp(op.lhs.lvalues[i], err)
|
|
}
|
|
}
|
|
restOff := len(values) - len(variables)
|
|
err := variables[rest].Set(vals.MakeList(values[rest : rest+restOff+1]...))
|
|
if err != nil {
|
|
return fm.errorp(op.lhs.lvalues[rest], err)
|
|
}
|
|
for i := rest + 1; i < len(variables); i++ {
|
|
err := variables[i].Set(values[i+restOff])
|
|
if err != nil {
|
|
return fm.errorp(op.lhs.lvalues[i], err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NoSuchVariable returns an error representing that a variable can't be found.
|
|
func NoSuchVariable(name string) error {
|
|
return noSuchVariableError{name}
|
|
}
|
|
|
|
type noSuchVariableError struct{ name string }
|
|
|
|
func (er noSuchVariableError) Error() string { return "no variable $" + er.name }
|
|
|
|
func derefLValue(fm *Frame, lv lvalue) (vars.Var, error) {
|
|
variable := deref(fm, lv.ref)
|
|
if variable == nil {
|
|
return nil, NoSuchVariable(fm.srcMeta.Code[lv.From:lv.To])
|
|
}
|
|
if len(lv.indexOps) == 0 {
|
|
return variable, nil
|
|
}
|
|
indices := make([]interface{}, len(lv.indexOps))
|
|
for i, op := range lv.indexOps {
|
|
values, exc := op.exec(fm)
|
|
if exc != nil {
|
|
return nil, exc
|
|
}
|
|
// TODO: Implement multi-indexing.
|
|
if len(values) != 1 {
|
|
return nil, errors.New("multi indexing not implemented")
|
|
}
|
|
indices[i] = values[0]
|
|
}
|
|
elemVar, err := vars.MakeElement(variable, indices)
|
|
if err != nil {
|
|
level := vars.ElementErrorLevel(err)
|
|
if level < 0 {
|
|
return nil, fm.errorp(lv, err)
|
|
}
|
|
return nil, fm.errorp(diag.Ranging{From: lv.From, To: lv.ends[level]}, err)
|
|
}
|
|
return elemVar, nil
|
|
}
|