elvish/eval/compile_lvalue_op.go
Qi Xiao 7efd489df3 splice -> explode
The author just found out that he had a terrible misunderstanding of the
word "splice".
2016-10-13 14:12:14 +08:00

161 lines
4.6 KiB
Go

package eval
import (
"errors"
"github.com/elves/elvish/parse"
)
// VariablesOp is an operation on an EvalCtx that produce Variable's.
type LValuesOp struct {
Func LValuesOpFunc
Begin, End int
}
type LValuesOpFunc func(*EvalCtx) []Variable
func (op LValuesOp) Exec(ec *EvalCtx) []Variable {
// Empty value is considered to generate no lvalues.
if op.Func == nil {
return []Variable{}
}
ec.begin, ec.end = op.Begin, op.End
return op.Func(ec)
}
// 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. The braced list
if len(n.Indicies) > 0 {
cp.errorf("may not have indicies")
}
opFuncs := make([]LValuesOpFunc, len(n.Head.Braced))
var restNode *parse.Indexing
var restOpFunc LValuesOpFunc
// Compile each spec inside the brace.
lvalueNodes := n.Head.Braced
fixedEnd := 0
for i, cn := range lvalueNodes {
if len(cn.Indexings) != 1 {
cp.errorpf(cn.Begin(), cn.End(), "must be an lvalue")
}
var rest bool
rest, opFuncs[i] = cp.lvaluesOne(cn.Indexings[0], "must be an lvalue ")
// Only the last one may a rest part.
if rest {
if i == len(n.Head.Braced)-1 {
restNode = cn.Indexings[0]
restOpFunc = opFuncs[i]
} else {
cp.errorpf(cn.Begin(), cn.End(), "only the last lvalue may have @")
}
} else {
fixedEnd = cn.End()
}
}
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.Begin(), restNode.End()}
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{func(ec *EvalCtx) []Variable {
var variables []Variable
for _, opFunc := range opFuncs {
variables = append(variables, opFunc(ec)...)
}
return variables
}, lvalueNodes[0].Begin(), fixedEnd}
}
return op, restOp
}
rest, opFunc := cp.lvaluesOne(n, "must be an lvalue or a braced list of those")
op := LValuesOp{opFunc, n.Begin(), n.End()}
if rest {
return LValuesOp{}, op
} else {
return op, LValuesOp{}
}
}
func (cp *compiler) lvaluesOne(n *parse.Indexing, msg string) (bool, LValuesOpFunc) {
varname := cp.literal(n.Head, msg)
cp.registerVariableSet(varname)
explode, ns, barename := ParseAndFixVariable(varname)
if len(n.Indicies) == 0 {
return explode, func(ec *EvalCtx) []Variable {
variable := ec.ResolveVar(ns, barename)
if variable == nil {
if ns == "" || ns == "local" {
// New variable.
// XXX We depend on the fact that this variable will
// immeidately be set.
variable = NewPtrVariable(nil)
ec.local[barename] = variable
} else if mod, ok := ec.Modules[ns]; ok {
variable = NewPtrVariable(nil)
mod[barename] = variable
} else {
throwf("cannot set $%s", varname)
}
}
return []Variable{variable}
}
}
p := n.Begin()
indexOps := cp.arrayOps(n.Indicies)
return explode, func(ec *EvalCtx) []Variable {
variable := ec.ResolveVar(ns, barename)
if variable == nil {
throwf("variable $%s does not exisit, compiler bug", varname)
}
// Indexing. Do Index up to the last but one index.
value := variable.Get()
n := len(indexOps)
// TODO set location information according.
for _, op := range indexOps[:n-1] {
indexer := mustIndexer(value, ec)
indicies := op.Exec(ec)
values := indexer.Index(indicies)
if len(values) != 1 {
throw(errors.New("multi indexing not implemented"))
}
value = values[0]
}
// Now this must be an IndexSetter.
indexSetter, ok := value.(IndexSetter)
if !ok {
// XXX the indicated end location will fall on or after the opening
// bracket of the last index, instead of exactly on the penultimate
// index.
ec.errorpf(p, indexOps[n-1].Begin, "cannot be indexed for setting (value is %s, type %s)", value.Repr(NoPretty), value.Kind())
}
// XXX Duplicate code.
indicies := indexOps[n-1].Exec(ec)
if len(indicies) != 1 {
ec.errorpf(indexOps[n-1].Begin, indexOps[n-1].End, "index must eval to a single Value (got %v)", indicies)
}
return []Variable{elemVariable{indexSetter, indicies[0]}}
}
}