mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-04 10:57:50 +08:00
9f4a6413c1
This fixes #84.
161 lines
4.6 KiB
Go
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)
|
|
splice, ns, barename := parseVariable(varname)
|
|
|
|
if len(n.Indicies) == 0 {
|
|
return splice, 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 {
|
|
ec.errorf("cannot set $%s", varname)
|
|
}
|
|
}
|
|
return []Variable{variable}
|
|
}
|
|
}
|
|
|
|
p := n.Begin()
|
|
indexOps := cp.arrayOps(n.Indicies)
|
|
|
|
return splice, func(ec *EvalCtx) []Variable {
|
|
variable := ec.ResolveVar(ns, barename)
|
|
if variable == nil {
|
|
ec.errorf("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]}}
|
|
}
|
|
}
|