elvish/pkg/eval/compile_lvalue.go
2020-01-12 08:23:37 -05:00

187 lines
4.9 KiB
Go

package eval
import (
"errors"
"fmt"
"strings"
"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.errorf("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().From, n.Range().To}
if rest {
return lvaluesOp{}, op
}
return op, lvaluesOp{}
}
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.Range().From, cn.Range().To, "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.Range().From, cn.Range().To, "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().From, restNode.Range().To}
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}, nodes[0].Range().From, 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) {
cp.errorf("variable $%s not found", qname)
}
begin, end := n.Range().From, n.Range().To
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{qname, indexOps, begin, end, 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, name := SplitQNameNs(op.qname)
if ns == "" || ns == ":" || ns == "local:" {
// New variable.
// XXX We depend on the fact that this variable will
// immeidately be set.
if strings.HasSuffix(name, FnSuffix) {
val := Callable(nil)
variable = vars.FromPtr(&val)
} else if strings.HasSuffix(name, NsSuffix) {
val := Ns(nil)
variable = vars.FromPtr(&val)
} else {
variable = vars.FromInit(nil)
}
fm.local[name] = variable
} else {
return nil, fmt.Errorf("new variables can only be created in local scope")
}
}
return []vars.Var{variable}, nil
}
type elemOp struct {
qname string
indexOps []valuesOp
begin int
end int
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)
}
indicies := make([]interface{}, len(op.indexOps))
for i, op := range op.indexOps {
values, err := op.exec(fm)
if err != nil {
return nil, err
}
// TODO: Implement multi-indexing.
if len(values) != 1 {
return nil, errors.New("multi indexing not implemented")
}
indicies[i] = values[0]
}
elemVar, err := vars.MakeElement(variable, indicies)
if err != nil {
level := vars.ElementErrorLevel(err)
if level < 0 {
return nil, fm.errorpf(op.begin, op.end, "%s", err)
}
return nil, fm.errorpf(op.begin, op.ends[level], "%s", err)
}
return []vars.Var{elemVar}, nil
}