mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
Improve handling of namespaces.
* The training colons of namespaces are now considered part of the namespace, simplifying the internal API. * Added tests for more complex patterns of nested namespaces that used to fail.
This commit is contained in:
parent
5b93b08206
commit
93c17dc7c2
|
@ -3,7 +3,6 @@ package cliedit
|
|||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/parse"
|
||||
|
@ -32,23 +31,24 @@ func hasCommand(ev *eval.Evaler, cmd string) bool {
|
|||
return isDirOrExecutable(cmd) || hasExternalCommand(cmd)
|
||||
}
|
||||
|
||||
explode, ns, name := eval.ParseVariableRef(cmd)
|
||||
if explode {
|
||||
sigil, qname := eval.SplitVariableRef(cmd)
|
||||
if sigil != "" {
|
||||
// The @ sign is only valid when referring to external commands.
|
||||
return hasExternalCommand(cmd)
|
||||
}
|
||||
|
||||
switch ns {
|
||||
case "e":
|
||||
return hasExternalCommand(name)
|
||||
firstNs, rest := eval.SplitIncompleteQNameFirstNs(qname)
|
||||
switch firstNs {
|
||||
case "e:":
|
||||
return hasExternalCommand(rest)
|
||||
case "":
|
||||
// Unqualified name; try builtin and global.
|
||||
if hasFn(ev.Builtin, name) || hasFn(ev.Global, name) {
|
||||
if hasFn(ev.Builtin, rest) || hasFn(ev.Global, rest) {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
// Qualified name. Find the top-level module first.
|
||||
if hasQualifiedFn(ev, strings.Split(ns, ":"), name) {
|
||||
if hasQualifiedFn(ev, firstNs, rest) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -57,10 +57,10 @@ func hasCommand(ev *eval.Evaler, cmd string) bool {
|
|||
return hasExternalCommand(cmd)
|
||||
}
|
||||
|
||||
func hasQualifiedFn(ev *eval.Evaler, nsParts []string, name string) bool {
|
||||
modVar := ev.Global[nsParts[0]+eval.NsSuffix]
|
||||
func hasQualifiedFn(ev *eval.Evaler, firstNs string, rest string) bool {
|
||||
modVar := ev.Global[firstNs]
|
||||
if modVar == nil {
|
||||
modVar = ev.Builtin[nsParts[0]+eval.NsSuffix]
|
||||
modVar = ev.Builtin[firstNs]
|
||||
if modVar == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -69,8 +69,9 @@ func hasQualifiedFn(ev *eval.Evaler, nsParts []string, name string) bool {
|
|||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, nsPart := range nsParts[1:] {
|
||||
modVar = mod[nsPart+eval.NsSuffix]
|
||||
segs := eval.SplitQNameNsSegs(rest)
|
||||
for _, seg := range segs[:len(segs)-1] {
|
||||
modVar = mod[seg]
|
||||
if modVar == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -79,7 +80,7 @@ func hasQualifiedFn(ev *eval.Evaler, nsParts []string, name string) bool {
|
|||
return false
|
||||
}
|
||||
}
|
||||
return hasFn(mod, name)
|
||||
return hasFn(mod, segs[len(segs)-1])
|
||||
}
|
||||
|
||||
func hasFn(ns eval.Ns, name string) bool {
|
||||
|
|
|
@ -72,17 +72,18 @@ func complFormHeadInner(head string, ev *eval.Evaler, rawCands chan<- rawCandida
|
|||
for special := range eval.IsBuiltinSpecial {
|
||||
got(special)
|
||||
}
|
||||
explode, ns, _ := eval.ParseIncompleteVariableRef(head)
|
||||
if !explode {
|
||||
sigil, qname := eval.SplitVariableRef(head)
|
||||
ns, _ := eval.SplitQNameNsIncomplete(qname)
|
||||
if sigil == "" {
|
||||
logger.Printf("completing commands in ns %q", ns)
|
||||
ev.EachVariableInTop(ns, func(varname string) {
|
||||
switch {
|
||||
case strings.HasSuffix(varname, eval.FnSuffix):
|
||||
got(eval.MakeVariableRef(false, ns, varname[:len(varname)-len(eval.FnSuffix)]))
|
||||
got(ns + varname[:len(varname)-len(eval.FnSuffix)])
|
||||
case strings.HasSuffix(varname, eval.NsSuffix):
|
||||
got(eval.MakeVariableRef(false, ns, varname))
|
||||
got(ns + varname)
|
||||
default:
|
||||
name := eval.MakeVariableRef(false, ns, varname)
|
||||
name := ns + varname
|
||||
rawCands <- &complexCandidate{name, " = ", " = ", ui.Styles{}}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
type variableComplContext struct {
|
||||
complContextCommon
|
||||
ns, nsPart string
|
||||
ns string
|
||||
}
|
||||
|
||||
func (*variableComplContext) name() string { return "variable" }
|
||||
|
@ -17,16 +17,14 @@ func (*variableComplContext) name() string { return "variable" }
|
|||
func findVariableComplContext(n parse.Node, _ pureEvaler) complContext {
|
||||
primary, ok := n.(*parse.Primary)
|
||||
if ok && primary.Type == parse.Variable {
|
||||
explode, nsPart, nameSeed := eval.SplitIncompleteVariableRef(primary.Value)
|
||||
sigil, qname := eval.SplitVariableRef(primary.Value)
|
||||
ns, nameSeed := eval.SplitQNameNsIncomplete(qname)
|
||||
|
||||
// Move past "$", "@" and "<ns>:".
|
||||
begin := primary.Range().From + 1 + len(explode) + len(nsPart)
|
||||
ns := nsPart
|
||||
if len(ns) > 0 {
|
||||
ns = ns[:len(ns)-1]
|
||||
}
|
||||
begin := primary.Range().From + 1 + len(sigil) + len(ns)
|
||||
return &variableComplContext{
|
||||
complContextCommon{nameSeed, parse.Bareword, begin, primary.Range().To},
|
||||
ns, nsPart,
|
||||
ns,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -38,21 +36,20 @@ type evalerScopes interface {
|
|||
}
|
||||
|
||||
func (ctx *variableComplContext) generate(env *complEnv, ch chan<- rawCandidate) error {
|
||||
complVariable(ctx.ns, ctx.nsPart, env.evaler, ch)
|
||||
complVariable(ctx.ns, env.evaler, ch)
|
||||
return nil
|
||||
}
|
||||
|
||||
func complVariable(ctxNs, ctxNsPart string, ev evalerScopes, ch chan<- rawCandidate) {
|
||||
func complVariable(ctxNs string, ev evalerScopes, ch chan<- rawCandidate) {
|
||||
ev.EachVariableInTop(ctxNs, func(varname string) {
|
||||
ch <- noQuoteCandidate(varname)
|
||||
})
|
||||
|
||||
ev.EachNsInTop(func(ns string) {
|
||||
nsPart := ns + ":"
|
||||
// This is to match namespaces that are "nested" under the current
|
||||
// namespace.
|
||||
if hasProperPrefix(nsPart, ctxNsPart) {
|
||||
ch <- noQuoteCandidate(nsPart[len(ctxNsPart):])
|
||||
if hasProperPrefix(ns, ctxNs) {
|
||||
ch <- noQuoteCandidate(ns[len(ctxNs):])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ import (
|
|||
func TestFindVariableComplContext(t *testing.T) {
|
||||
testComplContextFinder(t, "findVariableComplContext", findVariableComplContext, []complContextFinderTest{
|
||||
{"$", &variableComplContext{
|
||||
complContextCommon{"", parse.Bareword, 1, 1}, "", ""}},
|
||||
complContextCommon{"", parse.Bareword, 1, 1}, ""}},
|
||||
{"$a", &variableComplContext{
|
||||
complContextCommon{"a", parse.Bareword, 1, 2}, "", ""}},
|
||||
complContextCommon{"a", parse.Bareword, 1, 2}, ""}},
|
||||
{"$a:", &variableComplContext{
|
||||
complContextCommon{"", parse.Bareword, 3, 3}, "a", "a:"}},
|
||||
complContextCommon{"", parse.Bareword, 3, 3}, "a:"}},
|
||||
{"$a:b", &variableComplContext{
|
||||
complContextCommon{"b", parse.Bareword, 3, 4}, "a", "a:"}},
|
||||
complContextCommon{"b", parse.Bareword, 3, 4}, "a:"}},
|
||||
// Wrong contexts
|
||||
{"", nil},
|
||||
{"echo", nil},
|
||||
|
@ -27,9 +27,9 @@ func TestFindVariableComplContext(t *testing.T) {
|
|||
type testEvalerScopes struct{}
|
||||
|
||||
var testScopes = map[string]map[string]int{
|
||||
"": {"veni": 0, "vidi": 0, "vici": 0},
|
||||
"foo": {"lorem": 0, "ipsum": 0},
|
||||
"foo:bar": {"lorem": 0, "dolor": 0},
|
||||
"": {"veni": 0, "vidi": 0, "vici": 0},
|
||||
"foo:": {"lorem": 0, "ipsum": 0},
|
||||
"foo:bar:": {"lorem": 0, "dolor": 0},
|
||||
}
|
||||
|
||||
func (testEvalerScopes) EachNsInTop(f func(string)) {
|
||||
|
@ -47,38 +47,37 @@ func (testEvalerScopes) EachVariableInTop(ns string, f func(string)) {
|
|||
}
|
||||
|
||||
var complVariableTests = []struct {
|
||||
ns string
|
||||
nsPart string
|
||||
want []rawCandidate
|
||||
ns string
|
||||
want []rawCandidate
|
||||
}{
|
||||
// No namespace: complete variables and namespaces
|
||||
{"", "", []rawCandidate{
|
||||
{"", []rawCandidate{
|
||||
noQuoteCandidate("foo:"), noQuoteCandidate("foo:bar:"),
|
||||
noQuoteCandidate("veni"), noQuoteCandidate("vici"), noQuoteCandidate("vidi"),
|
||||
}},
|
||||
// Nonempty namespace: complete variables in namespace and subnamespaces
|
||||
// (but not variables in subnamespaces)
|
||||
{"foo", "foo:", []rawCandidate{
|
||||
{"foo:", []rawCandidate{
|
||||
noQuoteCandidate("bar:"),
|
||||
noQuoteCandidate("ipsum"), noQuoteCandidate("lorem"),
|
||||
}},
|
||||
// Bad namespace
|
||||
{"bad", "bad:", nil},
|
||||
{"bad:", nil},
|
||||
}
|
||||
|
||||
func TestComplVariable(t *testing.T) {
|
||||
for _, test := range complVariableTests {
|
||||
got := collectComplVariable(test.ns, test.nsPart, testEvalerScopes{})
|
||||
got := collectComplVariable(test.ns, testEvalerScopes{})
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("complVariable(%q, %q, ...) => %v, want %v", test.ns, test.nsPart, got, test.want)
|
||||
t.Errorf("complVariable(%q, ...) => %v, want %v", test.ns, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectComplVariable(ns, nsPart string, ev evalerScopes) []rawCandidate {
|
||||
func collectComplVariable(ns string, ev evalerScopes) []rawCandidate {
|
||||
ch := make(chan rawCandidate)
|
||||
go func() {
|
||||
complVariable(ns, nsPart, ev, ch)
|
||||
complVariable(ns, ev, ch)
|
||||
close(ch)
|
||||
}()
|
||||
var results []rawCandidate
|
||||
|
|
|
@ -25,10 +25,11 @@ func goodFormHead(head string, ed *editor) bool {
|
|||
return util.IsExecutable(head) || isDir(head)
|
||||
} else {
|
||||
ev := ed.evaler
|
||||
explode, ns, name := eval.ParseVariableRef(head)
|
||||
if !explode {
|
||||
sigil, qname := eval.SplitVariableRef(head)
|
||||
ns, name := eval.SplitIncompleteQNameFirstNs(qname)
|
||||
if sigil == "" {
|
||||
switch ns {
|
||||
case "":
|
||||
case "", ":":
|
||||
if ev.Builtin[name+eval.FnSuffix] != nil || ev.Global[name+eval.FnSuffix] != nil {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -70,9 +70,9 @@ func resolve(fm *Frame, head string) string {
|
|||
if special {
|
||||
return "special"
|
||||
}
|
||||
explode, ns, name := ParseVariableRef(head)
|
||||
if !explode && fm.ResolveVar(ns, name+FnSuffix) != nil {
|
||||
return "$" + head + FnSuffix
|
||||
sigil, qname := SplitVariableRef(head)
|
||||
if sigil == "" && fm.ResolveVar(qname+FnSuffix) != nil {
|
||||
return "$" + qname + FnSuffix
|
||||
}
|
||||
return "(external " + parse.Quote(head) + ")"
|
||||
}
|
||||
|
|
|
@ -84,33 +84,34 @@ func compileDel(cp *compiler, fn *parse.Form) effectOpBody {
|
|||
continue
|
||||
}
|
||||
|
||||
explode, ns, name := ParseVariableRef(head.Value)
|
||||
if explode {
|
||||
cp.errorf("arguments to del may be have a leading @")
|
||||
sigil, qname := SplitVariableRef(head.Value)
|
||||
if sigil != "" {
|
||||
cp.errorf("arguments to del may not have a sigils, got %q", sigil)
|
||||
continue
|
||||
}
|
||||
var f effectOpBody
|
||||
if len(indicies) == 0 {
|
||||
ns, name := SplitQNameNsFirst(qname)
|
||||
switch ns {
|
||||
case "", "local":
|
||||
case "", ":", "local:":
|
||||
if !cp.thisScope().has(name) {
|
||||
cp.errorf("no variable $%s in local scope", name)
|
||||
continue
|
||||
}
|
||||
cp.thisScope().del(name)
|
||||
f = delLocalVarOp{name}
|
||||
case "E":
|
||||
case "E:":
|
||||
f = delEnvVarOp{name}
|
||||
default:
|
||||
cp.errorf("only variables in local: or E: can be deleted")
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if !cp.registerVariableGet(ns, name) {
|
||||
if !cp.registerVariableGet(qname) {
|
||||
cp.errorf("no variable $%s", head.Value)
|
||||
continue
|
||||
}
|
||||
f = newDelElementOp(ns, name, head.Range().From, head.Range().To, cp.arrayOps(indicies))
|
||||
f = newDelElementOp(qname, head.Range().From, head.Range().To, cp.arrayOps(indicies))
|
||||
}
|
||||
ops = append(ops, effectOp{f, cn.Range().From, cn.Range().To})
|
||||
}
|
||||
|
@ -130,18 +131,17 @@ func (op delEnvVarOp) invoke(*Frame) error {
|
|||
return os.Unsetenv(op.name)
|
||||
}
|
||||
|
||||
func newDelElementOp(ns, name string, begin, headEnd int, indexOps []valuesOp) effectOpBody {
|
||||
func newDelElementOp(qname string, begin, headEnd int, indexOps []valuesOp) effectOpBody {
|
||||
ends := make([]int, len(indexOps)+1)
|
||||
ends[0] = headEnd
|
||||
for i, op := range indexOps {
|
||||
ends[i+1] = op.end
|
||||
}
|
||||
return &delElemOp{ns, name, indexOps, begin, ends}
|
||||
return &delElemOp{qname, indexOps, begin, ends}
|
||||
}
|
||||
|
||||
type delElemOp struct {
|
||||
ns string
|
||||
name string
|
||||
qname string
|
||||
indexOps []valuesOp
|
||||
begin int
|
||||
ends []int
|
||||
|
@ -159,7 +159,7 @@ func (op *delElemOp) invoke(fm *Frame) error {
|
|||
}
|
||||
indicies = append(indicies, indexValues[0])
|
||||
}
|
||||
err := vars.DelElement(fm.ResolveVar(op.ns, op.name), indicies)
|
||||
err := vars.DelElement(fm.ResolveVar(op.qname), indicies)
|
||||
if err != nil {
|
||||
if level := vars.ElementErrorLevel(err); level >= 0 {
|
||||
return fm.errorpf(op.begin, op.ends[level], "%s", err.Error())
|
||||
|
@ -179,7 +179,7 @@ func compileFn(cp *compiler, fn *parse.Form) effectOpBody {
|
|||
bodyNode := args.nextMustLambda()
|
||||
args.mustEnd()
|
||||
|
||||
cp.registerVariableSetQname(":" + varName)
|
||||
cp.registerVariableSet(":" + varName)
|
||||
op := cp.lambda(bodyNode)
|
||||
|
||||
return fnOp{varName, op}
|
||||
|
|
|
@ -87,6 +87,8 @@ func TestUse(t *testing.T) {
|
|||
// That(`{ use lorem }; put $lorem:name`).ErrorsAny(),
|
||||
|
||||
// use of imported variable is captured in upvalue
|
||||
That(`use lorem; { put $lorem:name }`).Puts("lorem"),
|
||||
That(`{ use lorem; { put $lorem:name } }`).Puts("lorem"),
|
||||
That(`({ use lorem; put { { put $lorem:name } } })`).Puts("lorem"),
|
||||
// use of imported function is also captured in upvalue
|
||||
That(`{ use lorem; { lorem:put-name } }`).Puts("lorem"),
|
||||
|
|
|
@ -175,10 +175,10 @@ func (cp *compiler) form(n *parse.Form) effectOpBody {
|
|||
specialOpFunc = compileForm(cp, n)
|
||||
} else {
|
||||
var headOpFunc valuesOpBody
|
||||
explode, ns, name := ParseVariableRef(headStr)
|
||||
if !explode && cp.registerVariableGet(ns, name+FnSuffix) {
|
||||
sigil, qname := SplitVariableRef(headStr)
|
||||
if sigil == "" && cp.registerVariableGet(qname+FnSuffix) {
|
||||
// $head~ resolves.
|
||||
headOpFunc = variableOp{false, ns, name + FnSuffix}
|
||||
headOpFunc = variableOp{false, qname + FnSuffix}
|
||||
} else {
|
||||
// Fall back to $e:head~.
|
||||
headOpFunc = literalValues(ExternalCmd{headStr})
|
||||
|
|
|
@ -75,17 +75,19 @@ func (cp *compiler) lvaluesMulti(nodes []*parse.Compound) (lvaluesOp, lvaluesOp)
|
|||
}
|
||||
|
||||
func (cp *compiler) lvalueBase(n *parse.Indexing, msg string) (bool, lvaluesOpBody) {
|
||||
qname := cp.literal(n.Head, msg)
|
||||
explode, ns, name := ParseVariableRef(qname)
|
||||
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(ns, name)
|
||||
return explode, varOp{ns, name}
|
||||
cp.registerVariableSet(qname)
|
||||
return explode, varOp{qname}
|
||||
}
|
||||
return explode, cp.lvalueElement(ns, name, n)
|
||||
return explode, cp.lvalueElement(qname, n)
|
||||
}
|
||||
|
||||
func (cp *compiler) lvalueElement(ns, name string, n *parse.Indexing) lvaluesOpBody {
|
||||
cp.registerVariableGet(ns, name)
|
||||
func (cp *compiler) lvalueElement(qname string, n *parse.Indexing) lvaluesOpBody {
|
||||
cp.registerVariableGet(qname)
|
||||
|
||||
begin, end := n.Range().From, n.Range().To
|
||||
ends := make([]int, len(n.Indicies)+1)
|
||||
|
@ -96,7 +98,7 @@ func (cp *compiler) lvalueElement(ns, name string, n *parse.Indexing) lvaluesOpB
|
|||
|
||||
indexOps := cp.arrayOps(n.Indicies)
|
||||
|
||||
return &elemOp{ns, name, indexOps, begin, end, ends}
|
||||
return &elemOp{qname, indexOps, begin, end, ends}
|
||||
}
|
||||
|
||||
type seqLValuesOpBody struct {
|
||||
|
@ -116,26 +118,27 @@ func (op seqLValuesOpBody) invoke(fm *Frame) ([]vars.Var, error) {
|
|||
}
|
||||
|
||||
type varOp struct {
|
||||
ns, name string
|
||||
qname string
|
||||
}
|
||||
|
||||
func (op varOp) invoke(fm *Frame) ([]vars.Var, error) {
|
||||
variable := fm.ResolveVar(op.ns, op.name)
|
||||
variable := fm.ResolveVar(op.qname)
|
||||
if variable == nil {
|
||||
if op.ns == "" || op.ns == "local" {
|
||||
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(op.name, FnSuffix) {
|
||||
if strings.HasSuffix(name, FnSuffix) {
|
||||
val := Callable(nil)
|
||||
variable = vars.FromPtr(&val)
|
||||
} else if strings.HasSuffix(op.name, NsSuffix) {
|
||||
} else if strings.HasSuffix(name, NsSuffix) {
|
||||
val := Ns(nil)
|
||||
variable = vars.FromPtr(&val)
|
||||
} else {
|
||||
variable = vars.FromInit(nil)
|
||||
}
|
||||
fm.local[op.name] = variable
|
||||
fm.local[name] = variable
|
||||
} else {
|
||||
return nil, fmt.Errorf("new variables can only be created in local scope")
|
||||
}
|
||||
|
@ -144,8 +147,7 @@ func (op varOp) invoke(fm *Frame) ([]vars.Var, error) {
|
|||
}
|
||||
|
||||
type elemOp struct {
|
||||
ns string
|
||||
name string
|
||||
qname string
|
||||
indexOps []valuesOp
|
||||
begin int
|
||||
end int
|
||||
|
@ -153,9 +155,9 @@ type elemOp struct {
|
|||
}
|
||||
|
||||
func (op *elemOp) invoke(fm *Frame) ([]vars.Var, error) {
|
||||
variable := fm.ResolveVar(op.ns, op.name)
|
||||
variable := fm.ResolveVar(op.qname)
|
||||
if variable == nil {
|
||||
return nil, fmt.Errorf("variable $%s:%s does not exist, compiler bug", op.ns, op.name)
|
||||
return nil, fmt.Errorf("variable $%s does not exist, compiler bug", op.qname)
|
||||
}
|
||||
|
||||
indicies := make([]interface{}, len(op.indexOps))
|
||||
|
|
|
@ -221,11 +221,11 @@ func (cp *compiler) primary(n *parse.Primary) valuesOpBody {
|
|||
case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
|
||||
return literalStr(n.Value)
|
||||
case parse.Variable:
|
||||
explode, ns, name := ParseVariableRef(n.Value)
|
||||
if !cp.registerVariableGet(ns, name) {
|
||||
cp.errorf("variable $%s not found", n.Value)
|
||||
sigil, qname := SplitVariableRef(n.Value)
|
||||
if !cp.registerVariableGet(qname) {
|
||||
cp.errorf("variable $%s not found", qname)
|
||||
}
|
||||
return &variableOp{explode, ns, name}
|
||||
return &variableOp{sigil != "", qname}
|
||||
case parse.Wildcard:
|
||||
seg, err := wildcardToSegment(n.SourceText())
|
||||
if err != nil {
|
||||
|
@ -257,14 +257,13 @@ func (cp *compiler) primary(n *parse.Primary) valuesOpBody {
|
|||
|
||||
type variableOp struct {
|
||||
explode bool
|
||||
ns string
|
||||
name string
|
||||
qname string
|
||||
}
|
||||
|
||||
func (op variableOp) invoke(fm *Frame) ([]interface{}, error) {
|
||||
variable := fm.ResolveVar(op.ns, op.name)
|
||||
variable := fm.ResolveVar(op.qname)
|
||||
if variable == nil {
|
||||
return nil, fmt.Errorf("variable $%s:%s not found", op.ns, op.name)
|
||||
return nil, fmt.Errorf("variable $%s not found", op.qname)
|
||||
}
|
||||
value := variable.Get()
|
||||
if op.explode {
|
||||
|
@ -390,8 +389,10 @@ func (cp *compiler) lambda(n *parse.Primary) valuesOpBody {
|
|||
// Argument list.
|
||||
argNames = make([]string, len(n.Elements))
|
||||
for i, arg := range n.Elements {
|
||||
qname := mustString(cp, arg, "argument name must be literal string")
|
||||
explode, ns, name := ParseVariableRef(qname)
|
||||
ref := mustString(cp, arg, "argument name must be literal string")
|
||||
sigil, qname := SplitVariableRef(ref)
|
||||
explode := sigil != ""
|
||||
ns, name := SplitQNameNs(qname)
|
||||
if ns != "" {
|
||||
cp.errorpf(arg.Range().From, arg.Range().To, "argument name must be unqualified")
|
||||
}
|
||||
|
@ -414,7 +415,7 @@ func (cp *compiler) lambda(n *parse.Primary) valuesOpBody {
|
|||
optDefaultOps = make([]valuesOp, len(n.MapPairs))
|
||||
for i, opt := range n.MapPairs {
|
||||
qname := mustString(cp, opt.Key, "option name must be literal string")
|
||||
_, ns, name := ParseVariableRef(qname)
|
||||
ns, name := SplitQNameNs(qname)
|
||||
if ns != "" {
|
||||
cp.errorpf(opt.Key.Range().From, opt.Key.Range().To, "option name must be unqualified")
|
||||
}
|
||||
|
@ -449,7 +450,7 @@ func (cp *compiler) lambda(n *parse.Primary) valuesOpBody {
|
|||
cp.popScope()
|
||||
|
||||
for name := range capture {
|
||||
cp.registerVariableGetQname(name)
|
||||
cp.registerVariableGet(name)
|
||||
}
|
||||
|
||||
return &lambdaOp{argNames, restArgName, optNames, optDefaultOps, capture, subop, cp.srcMeta, n.Range().From, n.Range().To}
|
||||
|
@ -470,7 +471,7 @@ type lambdaOp struct {
|
|||
func (op *lambdaOp) invoke(fm *Frame) ([]interface{}, error) {
|
||||
evCapture := make(Ns)
|
||||
for name := range op.capture {
|
||||
evCapture[name] = fm.ResolveVar("", name)
|
||||
evCapture[name] = fm.ResolveVar(":" + name)
|
||||
}
|
||||
optDefaults := make([]interface{}, len(op.optDefaultOps))
|
||||
for i, op := range op.optDefaultOps {
|
||||
|
|
|
@ -49,6 +49,51 @@ func TestCompileValue(t *testing.T) {
|
|||
// Splicing
|
||||
That("x=[elvish rules]; put $@x").Puts("elvish", "rules"),
|
||||
|
||||
// Variable namespace
|
||||
// ------------------
|
||||
|
||||
// Pseudo-namespace local: accesses the local scope.
|
||||
That("x = outer; { local:x = inner; put $local:x }").Puts("inner"),
|
||||
// Pseudo-namespace up: accesses upvalues.
|
||||
That("x = outer; { local:x = inner; put $up:x }").Puts("outer"),
|
||||
// Pseudo-namespace builtin: accesses builtins.
|
||||
That("put $builtin:true").Puts(true),
|
||||
// Unqualified name prefers local: to up:.
|
||||
That("x = outer; { local:x = inner; put $x }").Puts("inner"),
|
||||
// Unqualified name resolves to upvalue if no local name exists.
|
||||
That("x = outer; { put $x }").Puts("outer"),
|
||||
// Unqualified name resolves to builtin if no local name or upvalue
|
||||
// exists.
|
||||
That("put $true").Puts(true),
|
||||
// A name can be explicitly unqualified by having a leading colon.
|
||||
That("x = val; put $:x").Puts("val"),
|
||||
That("put $:true").Puts(true),
|
||||
|
||||
// Pseudo-namespace E: provides read-write access to environment
|
||||
// variables. Colons inside the name are supported.
|
||||
That("set-env a:b VAL; put $E:a:b").Puts("VAL"),
|
||||
That("E:a:b = VAL2; get-env a:b").Puts("VAL2"),
|
||||
|
||||
// Pseudo-namespace e: provides readonly access to external commands.
|
||||
// Only names ending in ~ are resolved, and resolution always succeeds
|
||||
// regardless of whether the command actually exists. Colons inside the
|
||||
// name are supported.
|
||||
That("put $e:a:b~").Puts(ExternalCmd{Name: "a:b"}),
|
||||
|
||||
// A "normal" namespace access indexes the namespace as a variable.
|
||||
That("ns: = (ns [&a= val]); put $ns:a").Puts("val"),
|
||||
// Multi-level namespace access is supported.
|
||||
That("ns: = (ns [&a:= (ns [&b= val])]); put $ns:a:b").Puts("val"),
|
||||
// Multi-level namespace access can have a leading colon to signal that
|
||||
// the first component is unqualified.
|
||||
That("ns: = (ns [&a:= (ns [&b= val])]); put $:ns:a:b").Puts("val"),
|
||||
// Multi-level namespace access can be combined with the local:
|
||||
// pseudo-namespaces.
|
||||
That("ns: = (ns [&a:= (ns [&b= val])]); put $local:ns:a:b").Puts("val"),
|
||||
// Multi-level namespace access can be combined with the up:
|
||||
// pseudo-namespaces.
|
||||
That("ns: = (ns [&a:= (ns [&b= val])]); { put $up:ns:a:b }").Puts("val"),
|
||||
|
||||
// Tilde
|
||||
// -----
|
||||
That("h=$E:HOME; E:HOME=/foo; put ~ ~/src; E:HOME=$h").Puts("/foo", "/foo/src"),
|
||||
|
|
113
eval/compiler.go
113
eval/compiler.go
|
@ -60,56 +60,18 @@ func (cp *compiler) popScope() {
|
|||
cp.scopes = cp.scopes[:len(cp.scopes)-1]
|
||||
}
|
||||
|
||||
func (cp *compiler) registerVariableGetQname(qname string) bool {
|
||||
_, ns, name := ParseVariableRef(qname)
|
||||
return cp.registerVariableGet(ns, name)
|
||||
func (cp *compiler) registerVariableSet(qname string) bool {
|
||||
return cp.registerVariableAccess(qname, true)
|
||||
}
|
||||
|
||||
func (cp *compiler) registerVariableGet(ns, name string) bool {
|
||||
switch ns {
|
||||
case "", "local", "up":
|
||||
// Handled below
|
||||
case "e", "E":
|
||||
return true
|
||||
default:
|
||||
return cp.registerModAccess(ns)
|
||||
}
|
||||
// Find in local scope
|
||||
if ns == "" || ns == "local" {
|
||||
if cp.thisScope().has(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Find in upper scopes
|
||||
if ns == "" || ns == "up" {
|
||||
for i := len(cp.scopes) - 2; i >= 0; i-- {
|
||||
if cp.scopes[i].has(name) {
|
||||
// Existing name: record capture and return.
|
||||
cp.capture.set(name)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find in builtin scope
|
||||
if ns == "" || ns == "builtin" {
|
||||
if cp.builtin.has(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
func (cp *compiler) registerVariableGet(qname string) bool {
|
||||
return cp.registerVariableAccess(qname, false)
|
||||
}
|
||||
|
||||
func (cp *compiler) registerVariableSetQname(qname string) bool {
|
||||
_, ns, name := ParseVariableRef(qname)
|
||||
return cp.registerVariableSet(ns, name)
|
||||
}
|
||||
func (cp *compiler) registerVariableAccess(qname string, set bool) bool {
|
||||
readLocal := func(name string) bool { return cp.thisScope().has(name) }
|
||||
|
||||
func (cp *compiler) registerVariableSet(ns, name string) bool {
|
||||
switch ns {
|
||||
case "local":
|
||||
cp.thisScope().set(name)
|
||||
return true
|
||||
case "up":
|
||||
readUpvalue := func(name string) bool {
|
||||
for i := len(cp.scopes) - 2; i >= 0; i-- {
|
||||
if cp.scopes[i].has(name) {
|
||||
// Existing name: record capture and return.
|
||||
|
@ -118,36 +80,43 @@ func (cp *compiler) registerVariableSet(ns, name string) bool {
|
|||
}
|
||||
}
|
||||
return false
|
||||
case "builtin":
|
||||
cp.errorf("cannot set builtin variable")
|
||||
return false
|
||||
case "":
|
||||
if cp.thisScope().has(name) {
|
||||
// A name on current scope. Do nothing.
|
||||
}
|
||||
|
||||
readBuiltin := func(name string) bool { return cp.builtin.has(name) }
|
||||
|
||||
readNonPseudo := func(name string) bool {
|
||||
return readLocal(name) || readUpvalue(name) || readBuiltin(name)
|
||||
}
|
||||
|
||||
createLocal := func(name string) bool {
|
||||
if set && name != "" && !strings.ContainsRune(name[:len(name)-1], ':') {
|
||||
cp.thisScope().set(name)
|
||||
return true
|
||||
}
|
||||
// Walk up the upper scopes
|
||||
for i := len(cp.scopes) - 2; i >= 0; i-- {
|
||||
if cp.scopes[i].has(name) {
|
||||
// Existing name. Do nothing
|
||||
cp.capture.set(name)
|
||||
return true
|
||||
}
|
||||
}
|
||||
// New name. Register on this scope!
|
||||
cp.thisScope().set(name)
|
||||
return true
|
||||
case "e", "E":
|
||||
// Special namespaces, do nothing
|
||||
return true
|
||||
default:
|
||||
return cp.registerModAccess(ns)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *compiler) registerModAccess(name string) bool {
|
||||
if strings.ContainsRune(name, ':') {
|
||||
name = name[:strings.IndexByte(name, ':')]
|
||||
ns, name := SplitQNameNsFirst(qname) // ns = "", name = "ns:"
|
||||
name1 := name // name1 = "ns:"
|
||||
if name != "" && strings.ContainsRune(name[:len(name)-1], ':') {
|
||||
name1, _ = SplitQNameNsFirst(name)
|
||||
}
|
||||
|
||||
// This switch mirrors the structure of that from (*Frame).ResoleVar.
|
||||
switch ns {
|
||||
case "E:":
|
||||
return true
|
||||
case "e:":
|
||||
return !set && strings.HasSuffix(name, FnSuffix)
|
||||
case "local:":
|
||||
return readLocal(name1) || createLocal(name)
|
||||
case "up:":
|
||||
return readUpvalue(name1)
|
||||
case "builtin:":
|
||||
return readBuiltin(name1)
|
||||
case "", ":":
|
||||
return readNonPseudo(name1) || createLocal(name)
|
||||
default:
|
||||
return readNonPseudo(ns)
|
||||
}
|
||||
return cp.registerVariableGet("", name+NsSuffix)
|
||||
}
|
||||
|
|
|
@ -28,10 +28,6 @@ func TestNumBgJobs(t *testing.T) {
|
|||
|
||||
func TestMiscEval(t *testing.T) {
|
||||
Test(t,
|
||||
// Pseudo-namespaces local: and up:
|
||||
That("x=lorem; { local:x=ipsum; put $up:x $local:x }").Puts(
|
||||
"lorem", "ipsum"),
|
||||
That("x=lorem; { up:x=ipsum; put $x }; put $x").Puts("ipsum", "ipsum"),
|
||||
// Pseudo-namespace E:
|
||||
That("E:FOO=lorem; put $E:FOO").Puts("lorem"),
|
||||
That("del E:FOO; put $E:FOO").Puts(""),
|
||||
|
|
|
@ -72,12 +72,12 @@ func (ev *Evaler) PurelyEvalPrimary(pn *parse.Primary) interface{} {
|
|||
case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
|
||||
return pn.Value
|
||||
case parse.Variable:
|
||||
explode, ns, name := ParseVariableRef(pn.Value)
|
||||
if explode {
|
||||
sigil, qname := SplitVariableRef(pn.Value)
|
||||
if sigil != "" {
|
||||
return nil
|
||||
}
|
||||
ec := NewTopFrame(ev, NewInternalSource("[purely eval]"), nil)
|
||||
variable := ec.ResolveVar(ns, name)
|
||||
variable := ec.ResolveVar(qname)
|
||||
if variable != nil {
|
||||
return variable.Get()
|
||||
}
|
||||
|
|
111
eval/resolve.go
111
eval/resolve.go
|
@ -13,29 +13,29 @@ import (
|
|||
// namespace ns that can be found from the top context.
|
||||
func (ev *evalerScopes) EachVariableInTop(ns string, f func(s string)) {
|
||||
switch ns {
|
||||
case "builtin":
|
||||
case "builtin:":
|
||||
for name := range ev.Builtin {
|
||||
f(name)
|
||||
}
|
||||
case "":
|
||||
case "", ":":
|
||||
for name := range ev.Global {
|
||||
f(name)
|
||||
}
|
||||
for name := range ev.Builtin {
|
||||
f(name)
|
||||
}
|
||||
case "e":
|
||||
case "e:":
|
||||
EachExternal(func(cmd string) {
|
||||
f(cmd + FnSuffix)
|
||||
})
|
||||
case "E":
|
||||
case "E:":
|
||||
for _, s := range os.Environ() {
|
||||
if i := strings.IndexByte(s, '='); i > 0 {
|
||||
f(s[:i])
|
||||
}
|
||||
}
|
||||
default:
|
||||
segs := splitQName(ns + NsSuffix)
|
||||
segs := SplitQNameNsSegs(ns)
|
||||
mod := ev.Global[segs[0]]
|
||||
if mod == nil {
|
||||
mod = ev.Builtin[segs[0]]
|
||||
|
@ -57,90 +57,73 @@ func (ev *evalerScopes) EachVariableInTop(ns string, f func(s string)) {
|
|||
// EachNsInTop calls the passed function for each namespace that can be used
|
||||
// from the top context.
|
||||
func (ev *evalerScopes) EachNsInTop(f func(s string)) {
|
||||
f("builtin")
|
||||
f("e")
|
||||
f("E")
|
||||
f("builtin:")
|
||||
f("e:")
|
||||
f("E:")
|
||||
|
||||
for name := range ev.Global {
|
||||
if strings.HasSuffix(name, NsSuffix) {
|
||||
f(name[:len(name)-len(NsSuffix)])
|
||||
f(name)
|
||||
}
|
||||
}
|
||||
for name := range ev.Builtin {
|
||||
if strings.HasSuffix(name, NsSuffix) {
|
||||
f(name[:len(name)-len(NsSuffix)])
|
||||
f(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveVar resolves a variable. When the variable cannot be found, nil is
|
||||
// returned.
|
||||
func (fm *Frame) ResolveVar(n, name string) vars.Var {
|
||||
if n == "" {
|
||||
return fm.resolveUnqualified(name)
|
||||
}
|
||||
func (fm *Frame) ResolveVar(qname string) vars.Var {
|
||||
ns, name := SplitQNameNsFirst(qname)
|
||||
|
||||
// TODO: Let this function accept the fully qualified name.
|
||||
segs := splitQName(n + ":" + name)
|
||||
|
||||
var ns Ns
|
||||
|
||||
switch segs[0] {
|
||||
case "e:":
|
||||
if len(segs) == 2 && strings.HasSuffix(segs[1], FnSuffix) {
|
||||
return vars.NewReadOnly(ExternalCmd{Name: segs[1][:len(segs[1])-len(FnSuffix)]})
|
||||
}
|
||||
return nil
|
||||
switch ns {
|
||||
case "E:":
|
||||
if len(segs) == 2 {
|
||||
return vars.FromEnv(segs[1])
|
||||
return vars.FromEnv(name)
|
||||
case "e:":
|
||||
if strings.HasSuffix(name, FnSuffix) {
|
||||
return vars.NewReadOnly(ExternalCmd{name[:len(name)-len(FnSuffix)]})
|
||||
}
|
||||
return nil
|
||||
case "local:":
|
||||
ns = fm.local
|
||||
return resolveNested(fm.local, name)
|
||||
case "up:":
|
||||
ns = fm.up
|
||||
return resolveNested(fm.up, name)
|
||||
case "builtin:":
|
||||
ns = fm.Builtin
|
||||
return resolveNested(fm.Builtin, name)
|
||||
case "", ":":
|
||||
return fm.resolveNonPseudo(name)
|
||||
default:
|
||||
v := fm.resolveUnqualified(segs[0])
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
ns = v.Get().(Ns)
|
||||
return fm.resolveNonPseudo(qname)
|
||||
}
|
||||
}
|
||||
|
||||
for _, seg := range segs[1 : len(segs)-1] {
|
||||
v := ns[seg]
|
||||
if v == nil {
|
||||
func (fm *Frame) resolveNonPseudo(name string) vars.Var {
|
||||
if v := resolveNested(fm.local, name); v != nil {
|
||||
return v
|
||||
}
|
||||
if v := resolveNested(fm.up, name); v != nil {
|
||||
return v
|
||||
}
|
||||
return resolveNested(fm.Builtin, name)
|
||||
}
|
||||
|
||||
func resolveNested(ns Ns, name string) vars.Var {
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
segs := SplitQNameNsSegs(name)
|
||||
for _, seg := range segs[:len(segs)-1] {
|
||||
variable := ns[seg]
|
||||
if variable == nil {
|
||||
return nil
|
||||
}
|
||||
ns = v.Get().(Ns)
|
||||
nestedNs, ok := variable.Get().(Ns)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ns = nestedNs
|
||||
}
|
||||
return ns[segs[len(segs)-1]]
|
||||
}
|
||||
|
||||
func splitQName(qname string) []string {
|
||||
i := 0
|
||||
var segs []string
|
||||
for i < len(qname) {
|
||||
j := strings.IndexByte(qname[i:], ':')
|
||||
if j == -1 {
|
||||
segs = append(segs, qname[i:])
|
||||
break
|
||||
}
|
||||
segs = append(segs, qname[i:i+j+1])
|
||||
i += j + 1
|
||||
}
|
||||
return segs
|
||||
}
|
||||
|
||||
func (fm *Frame) resolveUnqualified(name string) vars.Var {
|
||||
if v, ok := fm.local[name]; ok {
|
||||
return v
|
||||
}
|
||||
if v, ok := fm.up[name]; ok {
|
||||
return v
|
||||
}
|
||||
return fm.Builtin[name]
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/tt"
|
||||
)
|
||||
|
||||
var splitQNameTests = tt.Table{
|
||||
tt.Args("a").Rets([]string{"a"}),
|
||||
tt.Args("a:b").Rets([]string{"a:", "b"}),
|
||||
tt.Args("a:b:").Rets([]string{"a:", "b:"}),
|
||||
tt.Args("a:b:c:d").Rets([]string{"a:", "b:", "c:", "d"}),
|
||||
}
|
||||
|
||||
func TestSplitQName(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("splitQName", splitQName), splitQNameTests)
|
||||
}
|
|
@ -2,65 +2,64 @@ package eval
|
|||
|
||||
import "strings"
|
||||
|
||||
// ParseVariableRef parses a variable reference.
|
||||
func ParseVariableRef(text string) (explode bool, ns string, name string) {
|
||||
return parseVariableRef(text, true)
|
||||
}
|
||||
|
||||
// ParseIncompleteVariableRef parses an incomplete variable reference.
|
||||
func ParseIncompleteVariableRef(text string) (explode bool, ns string, name string) {
|
||||
return parseVariableRef(text, false)
|
||||
}
|
||||
|
||||
func parseVariableRef(text string, complete bool) (explode bool, ns string, name string) {
|
||||
explodePart, nsPart, name := splitVariableRef(text, complete)
|
||||
ns = nsPart
|
||||
if len(ns) > 0 {
|
||||
ns = ns[:len(ns)-1]
|
||||
// SplitVariableRef splits a variable reference into the sigil and the
|
||||
// (qualified) name.
|
||||
func SplitVariableRef(ref string) (sigil string, qname string) {
|
||||
if ref == "" {
|
||||
return "", ""
|
||||
}
|
||||
return explodePart != "", ns, name
|
||||
}
|
||||
|
||||
// SplitVariableRef splits a variable reference into three parts: an optional
|
||||
// explode operator (either "" or "@"), a namespace part, and a name part.
|
||||
func SplitVariableRef(text string) (explodePart, nsPart, name string) {
|
||||
return splitVariableRef(text, true)
|
||||
}
|
||||
|
||||
// SplitIncompleteVariableRef splits an incomplete variable reference into three
|
||||
// parts: an optional explode operator (either "" or "@"), a namespace part, and
|
||||
// a name part.
|
||||
func SplitIncompleteVariableRef(text string) (explodePart, nsPart, name string) {
|
||||
return splitVariableRef(text, false)
|
||||
}
|
||||
|
||||
func splitVariableRef(text string, complete bool) (explodePart, nsPart, name string) {
|
||||
if text == "" {
|
||||
return "", "", ""
|
||||
}
|
||||
e, qname := "", text
|
||||
if text[0] == '@' {
|
||||
e = "@"
|
||||
qname = text[1:]
|
||||
switch ref[0] {
|
||||
case '@':
|
||||
// TODO(xiaq): Support % later.
|
||||
return ref[:1], ref[1:]
|
||||
default:
|
||||
return "", ref
|
||||
}
|
||||
}
|
||||
|
||||
// SplitQNameNs splits a qualified variable name into the namespace part and the
|
||||
// name part.
|
||||
func SplitQNameNs(qname string) (ns, name string) {
|
||||
if qname == "" {
|
||||
return e, "", ""
|
||||
return "", ""
|
||||
}
|
||||
i := strings.LastIndexByte(qname, ':')
|
||||
if complete && i == len(qname)-1 {
|
||||
i = strings.LastIndexByte(qname[:len(qname)-1], ':')
|
||||
}
|
||||
return e, qname[:i+1], qname[i+1:]
|
||||
colon := strings.LastIndexByte(qname[:len(qname)-1], ':')
|
||||
// If colon is -1, colon+1 will be 0, rendering an empty ns.
|
||||
return qname[:colon+1], qname[colon+1:]
|
||||
}
|
||||
|
||||
// MakeVariableRef builds a variable reference.
|
||||
func MakeVariableRef(explode bool, ns string, name string) string {
|
||||
prefix := ""
|
||||
if explode {
|
||||
prefix = "@"
|
||||
}
|
||||
if ns != "" {
|
||||
prefix += ns + ":"
|
||||
}
|
||||
return prefix + name
|
||||
// SplitQNameNs splits an incomplete qualified variable name into the namespace
|
||||
// part and the name part.
|
||||
func SplitQNameNsIncomplete(qname string) (ns, name string) {
|
||||
colon := strings.LastIndexByte(qname, ':')
|
||||
// If colon is -1, colon+1 will be 0, rendering an empty ns.
|
||||
return qname[:colon+1], qname[colon+1:]
|
||||
}
|
||||
|
||||
// SplitQNameNs splits a qualified variable name into the first part and the rest.
|
||||
func SplitQNameNsFirst(qname string) (ns, rest string) {
|
||||
colon := strings.IndexByte(qname, ':')
|
||||
if colon == len(qname)-1 {
|
||||
// Unqualified variable ending with colon ($name:).
|
||||
return "", qname
|
||||
}
|
||||
// If colon is -1, colon+1 will be 0, rendering an empty ns.
|
||||
return qname[:colon+1], qname[colon+1:]
|
||||
}
|
||||
|
||||
// SplitIncompleteQNameNsFirst splits an incomplete qualified variable name into
|
||||
// the first part and the rest.
|
||||
func SplitIncompleteQNameFirstNs(qname string) (ns, rest string) {
|
||||
colon := strings.IndexByte(qname, ':')
|
||||
// If colon is -1, colon+1 will be 0, rendering an empty ns.
|
||||
return qname[:colon+1], qname[colon+1:]
|
||||
}
|
||||
|
||||
// SplitQNameNsSegs splits a qualified name into namespace segments.
|
||||
func SplitQNameNsSegs(qname string) []string {
|
||||
segs := strings.SplitAfter(qname, ":")
|
||||
if len(segs) > 0 && segs[len(segs)-1] == "" {
|
||||
segs = segs[:len(segs)-1]
|
||||
}
|
||||
return segs
|
||||
}
|
||||
|
|
79
eval/variable_ref_test.go
Normal file
79
eval/variable_ref_test.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/tt"
|
||||
)
|
||||
|
||||
var Args = tt.Args
|
||||
|
||||
func TestSplitVariableRef(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("SplitVariableRef", SplitVariableRef), tt.Table{
|
||||
Args("").Rets("", ""),
|
||||
Args("x").Rets("", "x"),
|
||||
Args("@x").Rets("@", "x"),
|
||||
Args("a:b").Rets("", "a:b"),
|
||||
Args("@a:b").Rets("@", "a:b"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestSplitQNameNs(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("SplitQNameNs", SplitQNameNs), tt.Table{
|
||||
Args("").Rets("", ""),
|
||||
Args("a").Rets("", "a"),
|
||||
Args("a:").Rets("", "a:"),
|
||||
Args("a:b").Rets("a:", "b"),
|
||||
Args("a:b:").Rets("a:", "b:"),
|
||||
Args("a:b:c").Rets("a:b:", "c"),
|
||||
Args("a:b:c:").Rets("a:b:", "c:"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestSplitQNameNsIncomplete(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("SplitQNameNsIncomplete", SplitQNameNsIncomplete), tt.Table{
|
||||
Args("").Rets("", ""),
|
||||
Args("a").Rets("", "a"),
|
||||
Args("a:").Rets("a:", ""),
|
||||
Args("a:b").Rets("a:", "b"),
|
||||
Args("a:b:").Rets("a:b:", ""),
|
||||
Args("a:b:c").Rets("a:b:", "c"),
|
||||
Args("a:b:c:").Rets("a:b:c:", ""),
|
||||
})
|
||||
}
|
||||
|
||||
func TestSplitQNameNsFirst(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("SplitQNameNsFirst", SplitQNameNsFirst), tt.Table{
|
||||
Args("").Rets("", ""),
|
||||
Args("a").Rets("", "a"),
|
||||
Args("a:").Rets("", "a:"),
|
||||
Args("a:b").Rets("a:", "b"),
|
||||
Args("a:b:").Rets("a:", "b:"),
|
||||
Args("a:b:c").Rets("a:", "b:c"),
|
||||
Args("a:b:c:").Rets("a:", "b:c:"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestSplitIncompleteQNameFirstNs(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("SplitIncompleteQNameFirstNs", SplitIncompleteQNameFirstNs), tt.Table{
|
||||
Args("").Rets("", ""),
|
||||
Args("a").Rets("", "a"),
|
||||
Args("a:").Rets("a:", ""),
|
||||
Args("a:b").Rets("a:", "b"),
|
||||
Args("a:b:").Rets("a:", "b:"),
|
||||
Args("a:b:c").Rets("a:", "b:c"),
|
||||
Args("a:b:c:").Rets("a:", "b:c:"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestSplitQNameNsSegs(t *testing.T) {
|
||||
tt.Test(t, tt.Fn("SplitQNameNsSegs", SplitQNameNsSegs), tt.Table{
|
||||
Args("").Rets([]string{}),
|
||||
Args("a").Rets([]string{"a"}),
|
||||
Args("a:").Rets([]string{"a:"}),
|
||||
Args("a:b").Rets([]string{"a:", "b"}),
|
||||
Args("a:b:").Rets([]string{"a:", "b:"}),
|
||||
Args("a:b:c").Rets([]string{"a:", "b:", "c"}),
|
||||
Args("a:b:c:").Rets([]string{"a:", "b:", "c:"}),
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user