elvish/pkg/eval/compile_lvalue.go
Qi Xiao af906f1d76 pkg/eval: Change all the *Op types to return Exception instead of error.
Also change the variable name used to keep the Exception returned from "err" to
"exc".

This uncovers several error scenarios that were not returning Exception, and
would result in the absense of stack traces when such errors occur.
2021-01-09 00:25:32 +00:00

182 lines
4.6 KiB
Go

package eval
import (
"errors"
"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"
)
// 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
}
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 indices.
if len(n.Indicies) > 0 {
cp.errorpf(n, "braced list may not have indices when used as lvalue")
}
return cp.parseCompoundLValues(n.Head.Braced)
}
// A basic lvalue.
varUse := cp.literal(n.Head, "lvalue only supports literal variable names")
sigil, qname := SplitSigil(varUse)
var ref *varRef
if len(n.Indicies) == 0 {
ref = resolveVarRef(cp, qname, nil)
if ref == nil {
segs := SplitQNameSegs(qname)
if len(segs) == 1 {
// Unqualified name - implicit local
ref = &varRef{localScope, cp.thisScope().addInner(segs[0]), nil}
} else if len(segs) == 2 && (segs[0] == "local:" || segs[0] == ":") {
// Qualified local name
ref = &varRef{localScope, cp.thisScope().addInner(segs[1]), nil}
} else {
cp.errorpf(n, "cannot create variable $%s; new variables can only be created in the local scope", qname)
}
}
} else {
ref = resolveVarRef(cp, qname, n)
if ref == nil {
cp.errorpf(n, "cannot find variable $%s", 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
}
lv := lvalue{n.Range(), ref, cp.arrayOps(n.Indicies), ends}
restIndex := -1
if sigil == "@" {
restIndex = 0
}
// TODO: Deal with other sigils when they exist.
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, 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, err)
}
}
restOff := len(values) - len(variables)
err := variables[rest].Set(vals.MakeList(values[rest : rest+restOff+1]...))
if err != nil {
return fm.errorp(op, err)
}
for i := rest + 1; i < len(variables); i++ {
err := variables[i].Set(values[i+restOff])
if err != nil {
return fm.errorp(op, err)
}
}
}
return nil
}
func derefLValue(fm *Frame, lv lvalue) (vars.Var, error) {
variable := deref(fm, lv.ref)
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
}