mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
4fb04bc27a
This fixes #889.
187 lines
4.9 KiB
Go
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
|
|
}
|