mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
New eval. Currently broken.
This commit is contained in:
parent
73cfc1e35c
commit
aa7ca8eea1
67
eval/boilerplate.go
Normal file
67
eval/boilerplate.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package eval
|
||||
|
||||
import "github.com/elves/elvish/parse-ng"
|
||||
|
||||
func (cp *compiler) chunks(ns []*parse.Chunk) []valuesOp {
|
||||
ops := make([]valuesOp, len(ns))
|
||||
for i, n := range ns {
|
||||
ops[i] = cp.chunk(n)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func (cp *compiler) pipelines(ns []*parse.Pipeline) []valuesOp {
|
||||
ops := make([]valuesOp, len(ns))
|
||||
for i, n := range ns {
|
||||
ops[i] = cp.pipeline(n)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func (cp *compiler) forms(ns []*parse.Form) []stateUpdatesOp {
|
||||
ops := make([]stateUpdatesOp, len(ns))
|
||||
for i, n := range ns {
|
||||
ops[i] = cp.form(n)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func (cp *compiler) redirs(ns []*parse.Redir) []op {
|
||||
ops := make([]op, len(ns))
|
||||
for i, n := range ns {
|
||||
ops[i] = cp.redir(n)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func (cp *compiler) compounds(ns []*parse.Compound) []valuesOp {
|
||||
ops := make([]valuesOp, len(ns))
|
||||
for i, n := range ns {
|
||||
ops[i] = cp.compound(n)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func (cp *compiler) arrays(ns []*parse.Array) []valuesOp {
|
||||
ops := make([]valuesOp, len(ns))
|
||||
for i, n := range ns {
|
||||
ops[i] = cp.array(n)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func (cp *compiler) indexeds(ns []*parse.Indexed) []valuesOp {
|
||||
ops := make([]valuesOp, len(ns))
|
||||
for i, n := range ns {
|
||||
ops[i] = cp.indexed(n)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func (cp *compiler) primarys(ns []*parse.Primary) []valuesOp {
|
||||
ops := make([]valuesOp, len(ns))
|
||||
for i, n := range ns {
|
||||
ops[i] = cp.primary(n)
|
||||
}
|
||||
return ops
|
||||
}
|
27
eval/boilerplate.py
Executable file
27
eval/boilerplate.py
Executable file
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/python2.7
|
||||
import re
|
||||
|
||||
|
||||
def put_compile_s(out, name, intype, outtype):
|
||||
print >>out, '''
|
||||
func (cp *compiler) {name}s(ns []{intype}) []{outtype} {{
|
||||
ops := make([]{outtype}, len(ns))
|
||||
for i, n := range ns {{
|
||||
ops[i] = cp.{name}(n)
|
||||
}}
|
||||
return ops
|
||||
}}'''.format(name=name, intype=intype, outtype=outtype)
|
||||
|
||||
|
||||
def main():
|
||||
out = open('boilerplate.go', 'w')
|
||||
print >>out, '''package eval
|
||||
import "github.com/elves/elvish/parse-ng"'''
|
||||
for line in file('compile-ng.go'):
|
||||
m = re.match(r'^func \(cp \*compiler\) (\w+)\(\w+ (.+)\) (\w*[oO]p) {$', line)
|
||||
if m:
|
||||
put_compile_s(out, *m.groups())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -11,140 +11,51 @@ import (
|
|||
"github.com/elves/elvish/parse"
|
||||
)
|
||||
|
||||
type exitusOp func(*evalCtx) exitus
|
||||
type builtinSpecialCompile func(*compileCtx, *parse.Form) exitusOp
|
||||
type compileBuiltin func(*compiler, *parse.Form) exitusOp
|
||||
|
||||
type builtinSpecial struct {
|
||||
compile builtinSpecialCompile
|
||||
}
|
||||
|
||||
var builtinSpecials map[string]builtinSpecial
|
||||
var builtinSpecials map[string]compileBuiltin
|
||||
var BuiltinSpecialNames []string
|
||||
|
||||
func init() {
|
||||
// Needed to avoid initialization loop
|
||||
builtinSpecials = map[string]builtinSpecial{
|
||||
"var": builtinSpecial{compileVar},
|
||||
"set": builtinSpecial{compileSet},
|
||||
"del": builtinSpecial{compileDel},
|
||||
builtinSpecials = map[string]compileBuiltin{
|
||||
"var": compileVar,
|
||||
"set": compileSet,
|
||||
"del": compileDel,
|
||||
|
||||
"use": builtinSpecial{compileUse},
|
||||
"use": compileUse,
|
||||
|
||||
"fn": builtinSpecial{compileFn},
|
||||
"if": builtinSpecial{compileIf},
|
||||
|
||||
"static-typeof": builtinSpecial{compileStaticTypeof},
|
||||
"fn": compileFn,
|
||||
// "if": builtinSpecial{compileIf},
|
||||
}
|
||||
for k, _ := range builtinSpecials {
|
||||
BuiltinSpecialNames = append(BuiltinSpecialNames, k)
|
||||
}
|
||||
}
|
||||
|
||||
func mayAssign(tvar, tval Type) bool {
|
||||
if isAny(tval) || isAny(tvar) {
|
||||
return true
|
||||
}
|
||||
// BUG(xiaq): mayAssign uses a wrong way to check the equality of two
|
||||
// interfaces, which happens to work when all the Type instances are empty
|
||||
// structs.
|
||||
return tval == tvar
|
||||
}
|
||||
|
||||
func checkSetType(cc *compileCtx, names []string, values []*parse.Compound, vop valuesOp, p parse.Pos) {
|
||||
n, more := vop.tr.count()
|
||||
if more {
|
||||
if n > len(names) {
|
||||
cc.errorf(p, "number of variables (%d) can never match that of values (%d or more)", len(names), n)
|
||||
}
|
||||
// Only check the variables before the "more" part.
|
||||
names = names[:n]
|
||||
} else if n != len(names) {
|
||||
cc.errorf(p, "number of variables (%d) doesn't match that of values (%d or more)", len(names), n)
|
||||
}
|
||||
|
||||
for i, name := range names {
|
||||
tval := vop.tr[i].t
|
||||
tvar := cc.ResolveVar(splitQualifiedName(name))
|
||||
if !mayAssign(tvar, tval) {
|
||||
cc.errorf(values[i].Pos, "type mismatch: assigning %#v value to %#v variable", tval, tvar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that a CompoundNode contains exactly one PrimaryNode.
|
||||
func ensurePrimary(cc *compileCtx, cn *parse.Compound, msg string) *parse.Primary {
|
||||
if len(cn.Nodes) != 1 || cn.Nodes[0].Right != nil {
|
||||
cc.errorf(cn.Pos, msg)
|
||||
}
|
||||
return cn.Nodes[0].Left
|
||||
}
|
||||
|
||||
// ensureVariableOrStringPrimary ensures that a CompoundNode contains exactly
|
||||
// one PrimaryNode of type VariablePrimary or StringPrimary.
|
||||
func ensureVariableOrStringPrimary(cc *compileCtx, cn *parse.Compound, msg string) (*parse.Primary, string) {
|
||||
pn := ensurePrimary(cc, cn, msg)
|
||||
switch pn.Typ {
|
||||
case parse.VariablePrimary, parse.StringPrimary:
|
||||
return pn, pn.Node.(*parse.String).Text
|
||||
default:
|
||||
cc.errorf(cn.Pos, msg)
|
||||
return nil, ""
|
||||
}
|
||||
}
|
||||
|
||||
// ensureVariablePrimary ensures that a CompoundNode contains exactly one
|
||||
// PrimaryNode of type VariablePrimary.
|
||||
func ensureVariablePrimary(cc *compileCtx, cn *parse.Compound, msg string) (*parse.Primary, string) {
|
||||
pn, text := ensureVariableOrStringPrimary(cc, cn, msg)
|
||||
if pn.Typ != parse.VariablePrimary {
|
||||
cc.errorf(pn.Pos, msg)
|
||||
}
|
||||
return pn, text
|
||||
}
|
||||
|
||||
// ensureStringPrimary ensures that a CompoundNode contains exactly one
|
||||
// PrimaryNode of type VariablePrimary.
|
||||
func ensureStringPrimary(cc *compileCtx, cn *parse.Compound, msg string) (*parse.Primary, string) {
|
||||
pn, text := ensureVariableOrStringPrimary(cc, cn, msg)
|
||||
if pn.Typ != parse.StringPrimary {
|
||||
cc.errorf(pn.Pos, msg)
|
||||
}
|
||||
return pn, text
|
||||
}
|
||||
|
||||
// ensureStartWithVariabl ensures the first compound of the form is a
|
||||
// VariablePrimary. This is merely for better error messages; No actual
|
||||
// processing is done.
|
||||
func ensureStartWithVariable(cc *compileCtx, fn *parse.Form, form string) {
|
||||
func ensureStartWithVariable(cp *compiler, fn *parse.Form, form string) {
|
||||
if len(fn.Args.Nodes) == 0 {
|
||||
cc.errorf(fn.Pos, "expect variable after %s", form)
|
||||
cp.errorf(fn.Pos, "expect variable after %s", form)
|
||||
}
|
||||
ensureVariablePrimary(cc, fn.Args.Nodes[0], "expect variable")
|
||||
ensureVariablePrimary(cp, fn.Args.Nodes[0], "expect variable")
|
||||
}
|
||||
|
||||
// VarForm = 'var' { VarGroup } [ '=' Compound ]
|
||||
// VarGroup = { VariablePrimary } [ StringPrimary ]
|
||||
//
|
||||
// Variables in the same VarGroup has the type specified by the StringPrimary.
|
||||
// Only in the last VarGroup the StringPrimary may be omitted, in which case it
|
||||
// defaults to "any". For instance,
|
||||
//
|
||||
// var $u $v Type1 $x $y Type2 $z = a b c d e
|
||||
//
|
||||
// gives $u and $v type Type1, $x $y type Type2 and $z type Any and
|
||||
// assigns them the values a, b, c, d, e respectively.
|
||||
func compileVar(cc *compileCtx, fn *parse.Form) exitusOp {
|
||||
// VarForm = 'var' { StringPrimary } [ '=' Compound ]
|
||||
func compileVar(cp *compiler, fn *parse.Form) exitusOp {
|
||||
var (
|
||||
names []string
|
||||
types []Type
|
||||
values []*parse.Compound
|
||||
)
|
||||
|
||||
ensureStartWithVariable(cc, fn, "var")
|
||||
ensureStartWithVariable(cp, fn, "var")
|
||||
|
||||
for i, cn := range fn.Args.Nodes {
|
||||
expect := "expect variable, type or equal sign"
|
||||
pn, text := ensureVariableOrStringPrimary(cc, cn, expect)
|
||||
pn, text := ensureVariableOrStringPrimary(cp, cn, expect)
|
||||
if pn.Typ == parse.VariablePrimary {
|
||||
names = append(names, text)
|
||||
} else {
|
||||
|
@ -153,10 +64,10 @@ func compileVar(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
break
|
||||
} else {
|
||||
if t, ok := typenames[text]; !ok {
|
||||
cc.errorf(pn.Pos, "%v is not a valid type name", text)
|
||||
cp.errorf(pn.Pos, "%v is not a valid type name", text)
|
||||
} else {
|
||||
if len(names) == len(types) {
|
||||
cc.errorf(pn.Pos, "duplicate type")
|
||||
cp.errorf(pn.Pos, "duplicate type")
|
||||
}
|
||||
for i := len(types); i < len(names); i++ {
|
||||
types = append(types, t)
|
||||
|
@ -171,13 +82,13 @@ func compileVar(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
}
|
||||
|
||||
for i, name := range names {
|
||||
cc.pushVar(name, types[i])
|
||||
cp.pushVar(name, types[i])
|
||||
}
|
||||
|
||||
var vop valuesOp
|
||||
if values != nil {
|
||||
vop = cc.compounds(values)
|
||||
checkSetType(cc, names, values, vop, fn.Pos)
|
||||
vop = cp.compounds(values)
|
||||
checkSetType(cp, names, values, vop, fn.Pos)
|
||||
}
|
||||
return func(ec *evalCtx) exitus {
|
||||
for i, name := range names {
|
||||
|
@ -191,24 +102,24 @@ func compileVar(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
}
|
||||
|
||||
// SetForm = 'set' { VariablePrimary } '=' { Compound }
|
||||
func compileSet(cc *compileCtx, fn *parse.Form) exitusOp {
|
||||
func compileSet(cp *compiler, fn *parse.Form) exitusOp {
|
||||
var (
|
||||
names []string
|
||||
values []*parse.Compound
|
||||
)
|
||||
|
||||
ensureStartWithVariable(cc, fn, "set")
|
||||
ensureStartWithVariable(cp, fn, "set")
|
||||
|
||||
for i, cn := range fn.Args.Nodes {
|
||||
expect := "expect variable or equal sign"
|
||||
pn, text := ensureVariableOrStringPrimary(cc, cn, expect)
|
||||
pn, text := ensureVariableOrStringPrimary(cp, cn, expect)
|
||||
if pn.Typ == parse.VariablePrimary {
|
||||
ns, name := splitQualifiedName(text)
|
||||
cc.mustResolveVar(ns, name, cn.Pos)
|
||||
cp.mustResolveVar(ns, name, cn.Pos)
|
||||
names = append(names, text)
|
||||
} else {
|
||||
if text != "=" {
|
||||
cc.errorf(pn.Pos, expect)
|
||||
cp.errorf(pn.Pos, expect)
|
||||
}
|
||||
values = fn.Args.Nodes[i+1:]
|
||||
break
|
||||
|
@ -216,8 +127,8 @@ func compileSet(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
}
|
||||
|
||||
var vop valuesOp
|
||||
vop = cc.compounds(values)
|
||||
checkSetType(cc, names, values, vop, fn.Pos)
|
||||
vop = cp.compounds(values)
|
||||
checkSetType(cp, names, values, vop, fn.Pos)
|
||||
|
||||
return func(ec *evalCtx) exitus {
|
||||
return doSet(ec, names, vop.f(ec))
|
||||
|
@ -226,7 +137,6 @@ func compileSet(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
|
||||
var (
|
||||
arityMismatch = newFailure("arity mismatch")
|
||||
typeMismatch = newFailure("type mismatch")
|
||||
)
|
||||
|
||||
func doSet(ec *evalCtx, names []string, values []Value) exitus {
|
||||
|
@ -254,24 +164,24 @@ func doSet(ec *evalCtx, names []string, values []Value) exitus {
|
|||
}
|
||||
|
||||
// DelForm = 'del' { VariablePrimary }
|
||||
func compileDel(cc *compileCtx, fn *parse.Form) exitusOp {
|
||||
func compileDel(cp *compiler, fn *parse.Form) exitusOp {
|
||||
// Do conventional compiling of all compound expressions, including
|
||||
// ensuring that variables can be resolved
|
||||
var names, envNames []string
|
||||
for _, cn := range fn.Args.Nodes {
|
||||
_, qname := ensureVariablePrimary(cc, cn, "expect variable")
|
||||
_, qname := ensureVariablePrimary(cp, cn, "expect variable")
|
||||
ns, name := splitQualifiedName(qname)
|
||||
switch ns {
|
||||
case "", "local":
|
||||
if cc.resolveVarOnThisScope(name) == nil {
|
||||
cc.errorf(cn.Pos, "variable $%s not found on current local scope", name)
|
||||
if cp.resolveVarOnThisScope(name) == nil {
|
||||
cp.errorf(cn.Pos, "variable $%s not found on current local scope", name)
|
||||
}
|
||||
cc.popVar(name)
|
||||
cp.popVar(name)
|
||||
names = append(names, name)
|
||||
case "env":
|
||||
envNames = append(envNames, name)
|
||||
default:
|
||||
cc.errorf(cn.Pos, "can only delete a variable in local: or env: namespace")
|
||||
cp.errorf(cn.Pos, "can only delete a variable in local: or env: namespace")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -296,55 +206,55 @@ func stem(fname string) string {
|
|||
|
||||
// UseForm = 'use' StringPrimary.modname Primary.fname
|
||||
// = 'use' StringPrimary.fname
|
||||
func compileUse(cc *compileCtx, fn *parse.Form) exitusOp {
|
||||
func compileUse(cp *compiler, fn *parse.Form) exitusOp {
|
||||
var fnameNode *parse.Compound
|
||||
var fname, modname string
|
||||
|
||||
switch len(fn.Args.Nodes) {
|
||||
case 0:
|
||||
cc.errorf(fn.Args.Pos, "expect module name or file name")
|
||||
cp.errorf(fn.Args.Pos, "expect module name or file name")
|
||||
case 1, 2:
|
||||
fnameNode = fn.Args.Nodes[0]
|
||||
_, fname = ensureStringPrimary(cc, fnameNode, "expect string literal")
|
||||
_, fname = ensureStringPrimary(cp, fnameNode, "expect string literal")
|
||||
if len(fn.Args.Nodes) == 2 {
|
||||
modnameNode := fn.Args.Nodes[1]
|
||||
_, modname = ensureStringPrimary(
|
||||
cc, modnameNode, "expect string literal")
|
||||
cp, modnameNode, "expect string literal")
|
||||
if modname == "" {
|
||||
cc.errorf(modnameNode.Pos, "module name is empty")
|
||||
cp.errorf(modnameNode.Pos, "module name is empty")
|
||||
}
|
||||
} else {
|
||||
modname = stem(fname)
|
||||
if modname == "" {
|
||||
cc.errorf(fnameNode.Pos, "stem of file name is empty")
|
||||
cp.errorf(fnameNode.Pos, "stem of file name is empty")
|
||||
}
|
||||
}
|
||||
default:
|
||||
cc.errorf(fn.Args.Nodes[2].Pos, "superfluous argument")
|
||||
cp.errorf(fn.Args.Nodes[2].Pos, "superfluous argument")
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(fname, "/"):
|
||||
// Absolute file name, do nothing
|
||||
case strings.HasPrefix(fname, "./") || strings.HasPrefix(fname, "../"):
|
||||
// File name relative to current source
|
||||
fname = path.Clean(path.Join(cc.dir, fname))
|
||||
fname = path.Clean(path.Join(cp.dir, fname))
|
||||
default:
|
||||
// File name relative to data dir
|
||||
fname = path.Clean(path.Join(cc.dataDir, fname))
|
||||
fname = path.Clean(path.Join(cp.dataDir, fname))
|
||||
}
|
||||
src, err := readFileUTF8(fname)
|
||||
if err != nil {
|
||||
cc.errorf(fnameNode.Pos, "cannot read module: %s", err.Error())
|
||||
cp.errorf(fnameNode.Pos, "cannot read module: %s", err.Error())
|
||||
}
|
||||
|
||||
cn, err := parse.Parse(fname, src)
|
||||
if err != nil {
|
||||
// TODO(xiaq): Pretty print
|
||||
cc.errorf(fnameNode.Pos, "cannot parse module: %s", err.Error())
|
||||
cp.errorf(fnameNode.Pos, "cannot parse module: %s", err.Error())
|
||||
}
|
||||
|
||||
newCc := &compileCtx{
|
||||
cc.Compiler,
|
||||
newCc := &compiler{
|
||||
cp.Compiler,
|
||||
fname, src, path.Dir(fname),
|
||||
[]staticNS{staticNS{}}, staticNS{},
|
||||
}
|
||||
|
@ -352,10 +262,10 @@ func compileUse(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
op, err := newCc.compile(cn)
|
||||
if err != nil {
|
||||
// TODO(xiaq): Pretty print
|
||||
cc.errorf(fnameNode.Pos, "cannot compile module: %s", err.Error())
|
||||
cp.errorf(fnameNode.Pos, "cannot compile module: %s", err.Error())
|
||||
}
|
||||
|
||||
cc.mod[modname] = newCc.scopes[0]
|
||||
cp.mod[modname] = newCc.scopes[0]
|
||||
|
||||
return func(ec *evalCtx) exitus {
|
||||
// TODO(xiaq): Should handle failures when evaluting the module
|
||||
|
@ -395,11 +305,11 @@ func makeFnOp(op valuesOp) valuesOp {
|
|||
//
|
||||
// fn f $a $b { put (* $a $b) (/ $a *b) }
|
||||
// var $fn-f = { |$a $b| put (* $a $b) (/ $a $b) }
|
||||
func compileFn(cc *compileCtx, fn *parse.Form) exitusOp {
|
||||
func compileFn(cp *compiler, fn *parse.Form) exitusOp {
|
||||
if len(fn.Args.Nodes) == 0 {
|
||||
cc.errorf(fn.Pos, "expect function name after fn")
|
||||
cp.errorf(fn.Pos, "expect function name after fn")
|
||||
}
|
||||
_, fnName := ensureStringPrimary(cc, fn.Args.Nodes[0], "expect string literal")
|
||||
_, fnName := ensureStringPrimary(cp, fn.Args.Nodes[0], "expect string literal")
|
||||
varName := fnPrefix + fnName
|
||||
|
||||
var closureNode *parse.Closure
|
||||
|
@ -407,18 +317,18 @@ func compileFn(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
|
||||
for i, cn := range fn.Args.Nodes[1:] {
|
||||
expect := "expect variable or closure"
|
||||
pn := ensurePrimary(cc, cn, expect)
|
||||
pn := ensurePrimary(cp, cn, expect)
|
||||
switch pn.Typ {
|
||||
case parse.ClosurePrimary:
|
||||
if i+2 != len(fn.Args.Nodes) {
|
||||
cc.errorf(fn.Args.Nodes[i+2].Pos, "garbage after closure literal")
|
||||
cp.errorf(fn.Args.Nodes[i+2].Pos, "garbage after closure literal")
|
||||
}
|
||||
closureNode = pn.Node.(*parse.Closure)
|
||||
break
|
||||
case parse.VariablePrimary:
|
||||
argNames = append(argNames, cn)
|
||||
default:
|
||||
cc.errorf(pn.Pos, expect)
|
||||
cp.errorf(pn.Pos, expect)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,9 +340,9 @@ func compileFn(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
}
|
||||
}
|
||||
|
||||
op := cc.closure(closureNode)
|
||||
op := cp.closure(closureNode)
|
||||
|
||||
cc.pushVar(varName, callableType{})
|
||||
cp.pushVar(varName, callableType{})
|
||||
|
||||
return func(ec *evalCtx) exitus {
|
||||
closure := op.f(ec)[0].(*closure)
|
||||
|
@ -456,6 +366,7 @@ func maybeStringPrimary(cn *parse.Compound) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
/*
|
||||
type ifBranch struct {
|
||||
condition valuesOp
|
||||
body valuesOp
|
||||
|
@ -466,7 +377,7 @@ type ifBranch struct {
|
|||
//
|
||||
// The condition part of a Branch ends as soon as a Compound of a single
|
||||
// ClosurePrimary is encountered.
|
||||
func compileIf(cc *compileCtx, fn *parse.Form) exitusOp {
|
||||
func compileIf(cp *compiler, fn *parse.Form) exitusOp {
|
||||
compounds := fn.Args.Nodes
|
||||
var branches []*ifBranch
|
||||
|
||||
|
@ -475,20 +386,20 @@ func compileIf(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
for i, cn := range compounds {
|
||||
if closure, ok := maybeClosurePrimary(cn); ok {
|
||||
if i == 0 {
|
||||
cc.errorf(cn.Pos, "expect condition")
|
||||
cp.errorf(cn.Pos, "expect condition")
|
||||
}
|
||||
condition := cc.compounds(conds)
|
||||
condition := cp.compounds(conds)
|
||||
if closure.ArgNames != nil && len(closure.ArgNames.Nodes) > 0 {
|
||||
cc.errorf(closure.ArgNames.Pos, "unexpected arguments")
|
||||
cp.errorf(closure.ArgNames.Pos, "unexpected arguments")
|
||||
}
|
||||
body := cc.closure(closure)
|
||||
body := cp.closure(closure)
|
||||
branches = append(branches, &ifBranch{condition, body})
|
||||
compounds = compounds[i+1:]
|
||||
return
|
||||
}
|
||||
conds = append(conds, cn)
|
||||
}
|
||||
cc.errorf(compounds[len(compounds)-1].Pos, "expect body after this")
|
||||
cp.errorf(compounds[len(compounds)-1].Pos, "expect body after this")
|
||||
}
|
||||
// if branch
|
||||
nextBranch()
|
||||
|
@ -508,18 +419,18 @@ func compileIf(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
s, _ := maybeStringPrimary(compounds[0])
|
||||
if s == "else" {
|
||||
if len(compounds) == 1 {
|
||||
cc.errorf(compounds[0].Pos, "expect body after this")
|
||||
cp.errorf(compounds[0].Pos, "expect body after this")
|
||||
} else if len(compounds) > 2 {
|
||||
cc.errorf(compounds[2].Pos, "trailing garbage")
|
||||
cp.errorf(compounds[2].Pos, "trailing garbage")
|
||||
}
|
||||
body, ok := maybeClosurePrimary(compounds[1])
|
||||
if !ok {
|
||||
cc.errorf(compounds[1].Pos, "expect body")
|
||||
cp.errorf(compounds[1].Pos, "expect body")
|
||||
}
|
||||
branches = append(branches, &ifBranch{
|
||||
literalValue(boolean(true)), cc.closure(body)})
|
||||
literalValue(boolean(true)), cp.closure(body)})
|
||||
} else {
|
||||
cc.errorf(compounds[0].Pos, "trailing garbage")
|
||||
cp.errorf(compounds[0].Pos, "trailing garbage")
|
||||
}
|
||||
}
|
||||
return func(ec *evalCtx) exitus {
|
||||
|
@ -536,19 +447,4 @@ func compileIf(cc *compileCtx, fn *parse.Form) exitusOp {
|
|||
return ok
|
||||
}
|
||||
}
|
||||
|
||||
func compileStaticTypeof(cc *compileCtx, fn *parse.Form) exitusOp {
|
||||
// Do conventional compiling of all compounds, only keeping the static type
|
||||
// information
|
||||
var trs []typeRun
|
||||
for _, cn := range fn.Args.Nodes {
|
||||
trs = append(trs, cc.compound(cn).tr)
|
||||
}
|
||||
return func(ec *evalCtx) exitus {
|
||||
out := ec.ports[1].ch
|
||||
for _, tr := range trs {
|
||||
out <- str(tr.String())
|
||||
}
|
||||
return ok
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
735
eval/compile.go
735
eval/compile.go
|
@ -1,155 +1,463 @@
|
|||
package eval
|
||||
|
||||
//go:generate ./boilerplate.py
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/elves/elvish/errutil"
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/parse-ng"
|
||||
)
|
||||
|
||||
// staticNS is the static type information of a namespace.
|
||||
type staticNS map[string]Type
|
||||
type scope map[string]bool
|
||||
|
||||
// Compiler compiles an Elvish AST into an Op.
|
||||
type Compiler struct {
|
||||
global staticNS
|
||||
builtin staticNS
|
||||
// scopes []staticNS
|
||||
mod map[string]staticNS
|
||||
dataDir string
|
||||
type (
|
||||
op func(*evalCtx)
|
||||
valuesOp func(*evalCtx) []Value
|
||||
stateUpdatesOp func(*evalCtx) <-chan *stateUpdate
|
||||
exitusOp func(*evalCtx) exitus
|
||||
)
|
||||
|
||||
// compiler maintains the set of states needed when compiling a single source
|
||||
// file.
|
||||
type compiler struct {
|
||||
// Used in error messages.
|
||||
name, source string
|
||||
// Lexical scopes.
|
||||
scopes []scope
|
||||
// Variables captured from outer scopes.
|
||||
capture scope
|
||||
// Stored error.
|
||||
error error
|
||||
}
|
||||
|
||||
// compileCtx maintains a Compiler along with mutable states. After creation a
|
||||
// compilerCtx is never modified (but its fields may be), and new instances are
|
||||
// created when needed.
|
||||
type compileCtx struct {
|
||||
*Compiler
|
||||
name, text, dir string
|
||||
|
||||
scopes []staticNS
|
||||
up staticNS
|
||||
func (cp *compiler) thisScope() scope {
|
||||
return cp.scopes[len(cp.scopes)-1]
|
||||
}
|
||||
|
||||
func newCompileCtx(cp *Compiler, name, text, dir string) *compileCtx {
|
||||
return &compileCtx{
|
||||
cp,
|
||||
name, text, dir,
|
||||
[]staticNS{cp.global}, staticNS{},
|
||||
func compile(name, source string, sc scope, n *parse.Chunk) (valuesOp, error) {
|
||||
cp := &compiler{name, source, []scope{sc}, scope{}, nil}
|
||||
op := cp.chunk(n)
|
||||
return op, cp.error
|
||||
}
|
||||
|
||||
func (cp *compiler) chunk(n *parse.Chunk) valuesOp {
|
||||
ops := cp.pipelines(n.Pipelines)
|
||||
|
||||
return func(ec *evalCtx) []Value {
|
||||
for _, op := range ops {
|
||||
s := op(ec)
|
||||
if HasFailure(s) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return []Value{ok}
|
||||
}
|
||||
}
|
||||
|
||||
// NewCompiler returns a new compiler.
|
||||
func NewCompiler(bi staticNS, dataDir string) *Compiler {
|
||||
return &Compiler{staticNS{}, bi, map[string]staticNS{}, dataDir}
|
||||
}
|
||||
const pipelineChanBufferSize = 32
|
||||
|
||||
// Compile compiles a ChunkNode into an Op. The supplied name and text are used
|
||||
// in diagnostic messages.
|
||||
func (cp *Compiler) Compile(name, text, dir string, n *parse.Chunk) (valuesOp, error) {
|
||||
cc := newCompileCtx(cp, name, text, dir)
|
||||
return cc.compile(n)
|
||||
}
|
||||
var noExitus = newFailure("no exitus")
|
||||
|
||||
func (cc *compileCtx) compile(n *parse.Chunk) (op valuesOp, err error) {
|
||||
defer errutil.Catch(&err)
|
||||
return cc.chunk(n), nil
|
||||
}
|
||||
func (cp *compiler) pipeline(n *parse.Pipeline) valuesOp {
|
||||
ops := cp.forms(n.Forms)
|
||||
p := n.Begin
|
||||
|
||||
func (cc *compileCtx) pushScope() {
|
||||
cc.scopes = append(cc.scopes, staticNS{})
|
||||
}
|
||||
|
||||
func (cc *compileCtx) popScope() {
|
||||
cc.scopes[len(cc.scopes)-1] = nil
|
||||
cc.scopes = cc.scopes[:len(cc.scopes)-1]
|
||||
}
|
||||
|
||||
func (cc *compileCtx) pushVar(name string, t Type) {
|
||||
cc.scopes[len(cc.scopes)-1][name] = t
|
||||
}
|
||||
|
||||
func (cc *compileCtx) popVar(name string) {
|
||||
delete(cc.scopes[len(cc.scopes)-1], name)
|
||||
}
|
||||
|
||||
func (cc *compileCtx) errorf(p parse.Pos, format string, args ...interface{}) {
|
||||
errutil.Throw(errutil.NewContextualError(cc.name, "static error", cc.text, int(p), format, args...))
|
||||
}
|
||||
|
||||
// chunk compiles a ChunkNode into an Op.
|
||||
func (cc *compileCtx) chunk(cn *parse.Chunk) valuesOp {
|
||||
ops := make([]valuesOp, len(cn.Nodes))
|
||||
for i, pn := range cn.Nodes {
|
||||
ops[i] = cc.pipeline(pn)
|
||||
return func(ec *evalCtx) []Value {
|
||||
var nextIn *port
|
||||
updates := make([]<-chan *stateUpdate, len(ops))
|
||||
// For each form, create a dedicated evalCtx and run
|
||||
for i, op := range ops {
|
||||
newEc := ec.copy(fmt.Sprintf("form op %v", op))
|
||||
if i > 0 {
|
||||
newEc.ports[0] = nextIn
|
||||
}
|
||||
if i < len(ops)-1 {
|
||||
// Each internal port pair consists of a (byte) pipe pair and a
|
||||
// channel.
|
||||
// os.Pipe sets O_CLOEXEC, which is what we want.
|
||||
reader, writer, e := os.Pipe()
|
||||
if e != nil {
|
||||
ec.errorf(p, "failed to create pipe: %s", e)
|
||||
}
|
||||
ch := make(chan Value, pipelineChanBufferSize)
|
||||
newEc.ports[1] = &port{
|
||||
f: writer, ch: ch, closeF: true, closeCh: true}
|
||||
nextIn = &port{
|
||||
f: reader, ch: ch, closeF: true, closeCh: false}
|
||||
}
|
||||
updates[i] = op(newEc)
|
||||
}
|
||||
// Collect exit values
|
||||
exits := make([]Value, len(ops))
|
||||
for i, update := range updates {
|
||||
ex := noExitus
|
||||
for up := range update {
|
||||
ex = up.Exitus
|
||||
}
|
||||
exits[i] = ex
|
||||
}
|
||||
return exits
|
||||
}
|
||||
return combineChunk(ops)
|
||||
}
|
||||
|
||||
// closure compiles a ClosureNode into a valuesOp.
|
||||
func (cc *compileCtx) closure(cn *parse.Closure) valuesOp {
|
||||
nargs := 0
|
||||
if cn.ArgNames != nil {
|
||||
nargs = len(cn.ArgNames.Nodes)
|
||||
}
|
||||
argNames := make([]string, nargs)
|
||||
func (cp *compiler) form(n *parse.Form) stateUpdatesOp {
|
||||
headOp := cp.compound(n.Head)
|
||||
argOps := cp.compounds(n.Args)
|
||||
// TODO: n.NamedArgs
|
||||
redirOps := cp.redirs(n.Redirs)
|
||||
// TODO: n.ExitusRedir
|
||||
|
||||
if nargs > 0 {
|
||||
// TODO Allow types for arguments. Maybe share code with the var
|
||||
// builtin.
|
||||
for i, cn := range cn.ArgNames.Nodes {
|
||||
_, name := ensureVariablePrimary(cc, cn, "expect variable")
|
||||
argNames[i] = name
|
||||
p := n.Begin
|
||||
// ec here is always a subevaler created in compiler.pipeline, so it can
|
||||
// be safely modified.
|
||||
return func(ec *evalCtx) <-chan *stateUpdate {
|
||||
// head
|
||||
headValues := headOp(ec)
|
||||
headMust := ec.must(headValues, "the head of command", p)
|
||||
headMust.mustLen(1)
|
||||
switch headValues[0].(type) {
|
||||
case str, callable:
|
||||
default:
|
||||
headMust.error("a string or closure", headValues[0].Type().String())
|
||||
}
|
||||
|
||||
// args
|
||||
var args []Value
|
||||
for _, argOp := range argOps {
|
||||
args = append(args, argOp(ec)...)
|
||||
}
|
||||
|
||||
// redirs
|
||||
for _, redirOp := range redirOps {
|
||||
redirOp(ec)
|
||||
}
|
||||
|
||||
return ec.execNonSpecial(headValues[0], args)
|
||||
}
|
||||
}
|
||||
|
||||
func makeFlag(m parse.RedirMode) int {
|
||||
switch m {
|
||||
case parse.Read:
|
||||
return os.O_RDONLY
|
||||
case parse.Write:
|
||||
return os.O_WRONLY | os.O_CREATE
|
||||
case parse.ReadWrite:
|
||||
return os.O_RDWR | os.O_CREATE
|
||||
case parse.Append:
|
||||
return os.O_WRONLY | os.O_CREATE | os.O_APPEND
|
||||
default:
|
||||
// XXX should report parser bug
|
||||
panic("bad RedirMode; parser bug")
|
||||
}
|
||||
}
|
||||
|
||||
const defaultFileRedirPerm = 0644
|
||||
|
||||
// redir compiles a Redir into a op.
|
||||
func (cp *compiler) redir(n *parse.Redir) op {
|
||||
var dstOp valuesOp
|
||||
if n.Dest != nil {
|
||||
dstOp = cp.compound(n.Dest)
|
||||
}
|
||||
p := n.Begin
|
||||
srcOp := cp.compound(n.Source)
|
||||
sourceIsFd := n.SourceIsFd
|
||||
pSrc := n.Source.Begin
|
||||
mode := n.Mode
|
||||
flag := makeFlag(mode)
|
||||
|
||||
return func(ec *evalCtx) {
|
||||
var dst int
|
||||
if dstOp == nil {
|
||||
// use default dst fd
|
||||
switch mode {
|
||||
case parse.Read:
|
||||
dst = 0
|
||||
case parse.Write, parse.ReadWrite, parse.Append:
|
||||
dst = 1
|
||||
default:
|
||||
// XXX should report parser bug
|
||||
panic("bad RedirMode; parser bug")
|
||||
}
|
||||
} else {
|
||||
// dst must be a valid fd
|
||||
dst = ec.must(dstOp(ec), "FD", p).mustOneNonNegativeInt()
|
||||
}
|
||||
|
||||
ec.growPorts(dst + 1)
|
||||
ec.ports[dst].close()
|
||||
|
||||
srcMust := ec.must(srcOp(ec), "redirection source", pSrc)
|
||||
src := string(srcMust.mustOneStr())
|
||||
if sourceIsFd {
|
||||
if src == "-" {
|
||||
// close
|
||||
ec.ports[dst] = &port{}
|
||||
} else {
|
||||
fd := srcMust.zerothMustNonNegativeInt()
|
||||
ec.ports[dst] = ec.ports[fd]
|
||||
if ec.ports[dst] != nil {
|
||||
ec.ports[dst].closeF = false
|
||||
ec.ports[dst].closeCh = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f, err := os.OpenFile(src, flag, defaultFileRedirPerm)
|
||||
if err != nil {
|
||||
ec.errorf(p, "failed to open file %q: %s", src, err)
|
||||
}
|
||||
ec.ports[dst] = &port{
|
||||
f: f, ch: make(chan Value), closeF: true, closeCh: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cc.pushScope()
|
||||
|
||||
for _, name := range argNames {
|
||||
cc.pushVar(name, anyType{})
|
||||
func (cp *compiler) compound(n *parse.Compound) valuesOp {
|
||||
if len(n.Indexeds) == 1 {
|
||||
return cp.indexed(n.Indexeds[0])
|
||||
}
|
||||
|
||||
op := cc.chunk(cn.Chunk)
|
||||
ops := cp.indexeds(n.Indexeds)
|
||||
|
||||
up := cc.up
|
||||
cc.up = staticNS{}
|
||||
cc.popScope()
|
||||
|
||||
// Added variables up on inner closures to cc.up
|
||||
for name, typ := range up {
|
||||
if cc.resolveVarOnThisScope(name) == nil {
|
||||
cc.up[name] = typ
|
||||
return func(ec *evalCtx) []Value {
|
||||
// start with a single "", do Cartesian products one by one
|
||||
vs := []Value{str("")}
|
||||
for _, op := range ops {
|
||||
us := op(ec)
|
||||
if len(us) == 1 {
|
||||
// short path: reuse vs
|
||||
u := us[0]
|
||||
for i := range vs {
|
||||
vs[i] = cat(vs[i], u)
|
||||
}
|
||||
} else {
|
||||
newvs := make([]Value, len(vs)*len(us))
|
||||
for i, v := range vs {
|
||||
for j, u := range us {
|
||||
newvs[i*len(us)+j] = cat(v, u)
|
||||
}
|
||||
}
|
||||
vs = newvs
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
return combineClosure(argNames, op, up)
|
||||
}
|
||||
|
||||
// pipeline compiles a PipelineNode into a valuesOp.
|
||||
func (cc *compileCtx) pipeline(pn *parse.Pipeline) valuesOp {
|
||||
ops := make([]stateUpdatesOp, len(pn.Nodes))
|
||||
|
||||
for i, fn := range pn.Nodes {
|
||||
ops[i] = cc.form(fn)
|
||||
}
|
||||
return combinePipeline(ops, pn.Pos)
|
||||
func cat(lhs, rhs Value) Value {
|
||||
return str(toString(lhs) + toString(rhs))
|
||||
}
|
||||
|
||||
// mustResolveVar calls ResolveVar and calls errorf if the variable is
|
||||
// nonexistent.
|
||||
func (cc *compileCtx) mustResolveVar(ns, name string, p parse.Pos) Type {
|
||||
if t := cc.ResolveVar(ns, name); t != nil {
|
||||
return t
|
||||
func (cp *compiler) array(n *parse.Array) valuesOp {
|
||||
ops := cp.compounds(n.Compounds)
|
||||
|
||||
return func(ec *evalCtx) []Value {
|
||||
// Use number of compound expressions as an estimation of the number
|
||||
// of values
|
||||
vs := make([]Value, 0, len(ops))
|
||||
for _, op := range ops {
|
||||
us := op(ec)
|
||||
vs = append(vs, us...)
|
||||
}
|
||||
return vs
|
||||
}
|
||||
cc.errorf(p, "undefined variable $%s", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveVarOnThisScope returns the type of the named variable on current
|
||||
// scope. When such a variable does not exist, nil is returned.
|
||||
func (cc *compileCtx) resolveVarOnThisScope(name string) Type {
|
||||
return cc.scopes[len(cc.scopes)-1][name]
|
||||
func (cp *compiler) indexed(n *parse.Indexed) valuesOp {
|
||||
if len(n.Indicies) == 0 {
|
||||
return cp.primary(n.Head)
|
||||
}
|
||||
|
||||
headOp := cp.primary(n.Head)
|
||||
indexOps := cp.arrays(n.Indicies)
|
||||
p := n.Begin
|
||||
indexPoses := make([]int, len(n.Indicies))
|
||||
for i, index := range n.Indicies {
|
||||
indexPoses[i] = index.Begin
|
||||
}
|
||||
|
||||
return func(ec *evalCtx) []Value {
|
||||
v := ec.must(headOp(ec), "the indexed value", p).mustOne()
|
||||
for i, indexOp := range indexOps {
|
||||
index := ec.must(indexOp(ec), "the index", p).mustOne()
|
||||
v = evalSubscript(ec, v, index, p, indexPoses[i])
|
||||
}
|
||||
return []Value{v}
|
||||
}
|
||||
}
|
||||
|
||||
func literalValues(v ...Value) valuesOp {
|
||||
return func(e *evalCtx) []Value {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func literalStr(text string) valuesOp {
|
||||
return literalValues(str(text))
|
||||
}
|
||||
|
||||
func variable(qname string, p int) valuesOp {
|
||||
ns, name := splitQualifiedName(qname)
|
||||
return func(ec *evalCtx) []Value {
|
||||
variable := ec.ResolveVar(ns, name)
|
||||
if variable == nil {
|
||||
ec.errorf(p, "variable $%s not found", qname)
|
||||
}
|
||||
return []Value{variable.Get()}
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *compiler) registerVariableAccess(qname string) {
|
||||
ns, name := splitQualifiedName(qname)
|
||||
if ns == "up" || (ns == "" && !cp.thisScope()[name]) {
|
||||
cp.capture[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *compiler) primary(n *parse.Primary) valuesOp {
|
||||
switch n.Type {
|
||||
case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
|
||||
return literalStr(n.Value)
|
||||
case parse.Variable:
|
||||
qname := n.Value[1:]
|
||||
cp.registerVariableAccess(qname)
|
||||
return variable(qname, n.Begin)
|
||||
// case parse.Wildcard:
|
||||
case parse.ExitusCapture:
|
||||
return cp.chunk(n.Chunk)
|
||||
case parse.OutputCapture:
|
||||
return cp.outputCapture(n)
|
||||
case parse.List:
|
||||
op := cp.array(n.List)
|
||||
return func(ec *evalCtx) []Value {
|
||||
t := newTable()
|
||||
t.List = op(ec)
|
||||
return []Value{t}
|
||||
}
|
||||
case parse.Lambda:
|
||||
return cp.lambda(n)
|
||||
case parse.Map:
|
||||
return cp.map_(n)
|
||||
case parse.Braced:
|
||||
return cp.braced(n)
|
||||
default:
|
||||
// XXX: Primary types not yet implemented are just treated as
|
||||
// barewords. Should report parser bug of bad PrimaryType after they
|
||||
// have been implemented.
|
||||
return literalStr(n.SourceText)
|
||||
// panic("bad PrimaryType; parser bug")
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *compiler) outputCapture(n *parse.Primary) valuesOp {
|
||||
op := cp.chunk(n.Chunk)
|
||||
return func(ec *evalCtx) []Value {
|
||||
vs := []Value{}
|
||||
newEc := ec.copy(fmt.Sprintf("channel output capture %v", op))
|
||||
ch := make(chan Value)
|
||||
collected := make(chan bool)
|
||||
newEc.ports[1] = &port{ch: ch}
|
||||
go func() {
|
||||
for v := range ch {
|
||||
vs = append(vs, v)
|
||||
}
|
||||
collected <- true
|
||||
}()
|
||||
// XXX The exitus is discarded.
|
||||
op(newEc)
|
||||
newEc.closePorts()
|
||||
close(ch)
|
||||
<-collected
|
||||
return vs
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *compiler) pushScope() scope {
|
||||
sc := scope{}
|
||||
cp.scopes = append(cp.scopes, sc)
|
||||
return sc
|
||||
}
|
||||
|
||||
func (cp *compiler) popScope() {
|
||||
cp.scopes[len(cp.scopes)-1] = nil
|
||||
cp.scopes = cp.scopes[:len(cp.scopes)-1]
|
||||
}
|
||||
|
||||
func (cp *compiler) lambda(n *parse.Primary) valuesOp {
|
||||
// Collect argument names
|
||||
argNames := make([]string, len(n.List.Compounds))
|
||||
for i, arg := range n.List.Compounds {
|
||||
_, name := mustString(cp, arg, "expect string")
|
||||
argNames[i] = name
|
||||
}
|
||||
|
||||
thisScope := cp.pushScope()
|
||||
for _, argName := range argNames {
|
||||
thisScope[argName] = true
|
||||
}
|
||||
op := cp.chunk(n.Chunk)
|
||||
capture := cp.capture
|
||||
cp.capture = scope{}
|
||||
cp.popScope()
|
||||
|
||||
for name := range capture {
|
||||
cp.registerVariableAccess(name)
|
||||
}
|
||||
|
||||
return func(ec *evalCtx) []Value {
|
||||
evCapture := make(map[string]Variable, len(capture))
|
||||
for name := range capture {
|
||||
evCapture[name] = ec.ResolveVar("", name)
|
||||
}
|
||||
return []Value{newClosure(argNames, op, evCapture)}
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *compiler) map_(n *parse.Primary) valuesOp {
|
||||
nn := len(n.MapPairs)
|
||||
keysOps := make([]valuesOp, nn)
|
||||
valuesOps := make([]valuesOp, nn)
|
||||
poses := make([]int, nn)
|
||||
for i := 0; i < nn; i++ {
|
||||
keysOps[i] = cp.compound(n.MapPairs[i].Key)
|
||||
valuesOps[i] = cp.compound(n.MapPairs[i].Value)
|
||||
poses[i] = n.MapPairs[i].Begin
|
||||
}
|
||||
return func(ec *evalCtx) []Value {
|
||||
t := newTable()
|
||||
for i := 0; i < nn; i++ {
|
||||
keys := keysOps[i](ec)
|
||||
values := valuesOps[i](ec)
|
||||
if len(keys) != len(values) {
|
||||
ec.errorf(poses[i], "%d keys but %d values", len(keys), len(values))
|
||||
}
|
||||
for j, key := range keys {
|
||||
t.Dict[toString(key)] = values[j]
|
||||
}
|
||||
}
|
||||
return []Value{t}
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *compiler) braced(n *parse.Primary) valuesOp {
|
||||
ops := cp.compounds(n.Braced)
|
||||
// TODO: n.IsRange
|
||||
// isRange := n.IsRange
|
||||
// XXX This is now a copy of compiler.array.
|
||||
return func(ec *evalCtx) []Value {
|
||||
// Use number of compound expressions as an estimation of the number
|
||||
// of values
|
||||
vs := make([]Value, 0, len(ops))
|
||||
for _, op := range ops {
|
||||
us := op(ec)
|
||||
vs = append(vs, us...)
|
||||
}
|
||||
return vs
|
||||
}
|
||||
}
|
||||
|
||||
// splitQualifiedName splits a qualified variable name into two parts separated
|
||||
|
@ -162,204 +470,3 @@ func splitQualifiedName(qname string) (string, string) {
|
|||
}
|
||||
return qname[:i], qname[i+1:]
|
||||
}
|
||||
|
||||
// ResolveVar returns the type of a variable with supplied name and on the
|
||||
// supplied namespace. If such a variable is nonexistent, a nil is returned.
|
||||
// When the value to resolve is on an outer current scope, it is added to
|
||||
// cc.up.
|
||||
func (cc *compileCtx) ResolveVar(ns, name string) Type {
|
||||
if ns == "env" {
|
||||
return stringType{}
|
||||
}
|
||||
if mod, ok := cc.mod[ns]; ok {
|
||||
return mod[name]
|
||||
}
|
||||
|
||||
may := func(n string) bool {
|
||||
return ns == "" || ns == n
|
||||
}
|
||||
if may("local") {
|
||||
if t := cc.resolveVarOnThisScope(name); t != nil {
|
||||
return t
|
||||
}
|
||||
}
|
||||
if may("up") {
|
||||
for i := len(cc.scopes) - 2; i >= 0; i-- {
|
||||
if t, ok := cc.scopes[i][name]; ok {
|
||||
cc.up[name] = t
|
||||
return t
|
||||
}
|
||||
}
|
||||
}
|
||||
if may("builtin") {
|
||||
if t, ok := cc.builtin[name]; ok {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveBuiltinSpecial(cmd *parse.Compound) *builtinSpecial {
|
||||
if len(cmd.Nodes) == 1 {
|
||||
sn := cmd.Nodes[0]
|
||||
if sn.Right == nil {
|
||||
pn := sn.Left
|
||||
if pn.Typ == parse.StringPrimary {
|
||||
name := pn.Node.(*parse.String).Text
|
||||
if bi, ok := builtinSpecials[name]; ok {
|
||||
return &bi
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// form compiles a FormNode into a stateUpdatesOp.
|
||||
func (cc *compileCtx) form(fn *parse.Form) stateUpdatesOp {
|
||||
bi := resolveBuiltinSpecial(fn.Command)
|
||||
ports := cc.redirs(fn.Redirs)
|
||||
|
||||
if bi != nil {
|
||||
specialOp := bi.compile(cc, fn)
|
||||
return combineSpecialForm(specialOp, ports, fn.Pos)
|
||||
}
|
||||
cmdOp := cc.compound(fn.Command)
|
||||
argsOp := cc.spaced(fn.Args)
|
||||
return combineNonSpecialForm(cmdOp, argsOp, ports, fn.Pos)
|
||||
}
|
||||
|
||||
// redirs compiles a slice of Redir's into a slice of portOp's. The resulting
|
||||
// slice is indexed by the original fd.
|
||||
func (cc *compileCtx) redirs(rs []parse.Redir) []portOp {
|
||||
var nports uintptr
|
||||
for _, rd := range rs {
|
||||
if nports < rd.Fd()+1 {
|
||||
nports = rd.Fd() + 1
|
||||
}
|
||||
}
|
||||
|
||||
ports := make([]portOp, nports)
|
||||
for _, rd := range rs {
|
||||
ports[rd.Fd()] = cc.redir(rd)
|
||||
}
|
||||
|
||||
return ports
|
||||
}
|
||||
|
||||
const defaultFileRedirPerm = 0644
|
||||
|
||||
// redir compiles a Redir into a portOp.
|
||||
func (cc *compileCtx) redir(r parse.Redir) portOp {
|
||||
switch r := r.(type) {
|
||||
case *parse.CloseRedir:
|
||||
return func(ec *evalCtx) *port {
|
||||
return &port{}
|
||||
}
|
||||
case *parse.FdRedir:
|
||||
oldFd := int(r.OldFd)
|
||||
return func(ec *evalCtx) *port {
|
||||
// Copied ports have shouldClose unmarked to avoid double close on
|
||||
// channels
|
||||
p := *ec.port(oldFd)
|
||||
p.closeF = false
|
||||
p.closeCh = false
|
||||
return &p
|
||||
}
|
||||
case *parse.FilenameRedir:
|
||||
fnameOp := cc.compound(r.Filename)
|
||||
return func(ec *evalCtx) *port {
|
||||
fname := string(ec.mustSingleString(
|
||||
fnameOp.f(ec), "filename", r.Filename.Pos))
|
||||
f, e := os.OpenFile(fname, r.Flag, defaultFileRedirPerm)
|
||||
if e != nil {
|
||||
ec.errorf(r.Pos, "failed to open file %q: %s", fname[0], e)
|
||||
}
|
||||
return &port{
|
||||
f: f, ch: make(chan Value), closeF: true, closeCh: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("bad Redir type")
|
||||
}
|
||||
}
|
||||
|
||||
// compounds compiles a slice of CompoundNode's into a valuesOp. It can
|
||||
// be also used to compile a SpacedNode.
|
||||
func (cc *compileCtx) compounds(tns []*parse.Compound) valuesOp {
|
||||
ops := make([]valuesOp, len(tns))
|
||||
for i, tn := range tns {
|
||||
ops[i] = cc.compound(tn)
|
||||
}
|
||||
return combineSpaced(ops)
|
||||
}
|
||||
|
||||
// spaced compiles a SpacedNode into a valuesOp.
|
||||
func (cc *compileCtx) spaced(ln *parse.Spaced) valuesOp {
|
||||
return cc.compounds(ln.Nodes)
|
||||
}
|
||||
|
||||
// compound compiles a CompoundNode into a valuesOp.
|
||||
func (cc *compileCtx) compound(tn *parse.Compound) valuesOp {
|
||||
var op valuesOp
|
||||
if len(tn.Nodes) == 1 {
|
||||
op = cc.subscript(tn.Nodes[0])
|
||||
} else {
|
||||
ops := make([]valuesOp, len(tn.Nodes))
|
||||
for i, fn := range tn.Nodes {
|
||||
ops[i] = cc.subscript(fn)
|
||||
}
|
||||
op = combineCompound(ops)
|
||||
}
|
||||
if tn.Sigil == parse.NoSigil {
|
||||
return op
|
||||
}
|
||||
cmdOp := makeString(string(tn.Sigil))
|
||||
fop := combineNonSpecialForm(cmdOp, op, nil, tn.Pos)
|
||||
pop := combinePipeline([]stateUpdatesOp{fop}, tn.Pos)
|
||||
return combineChanCapture(pop)
|
||||
}
|
||||
|
||||
// subscript compiles a SubscriptNode into a valuesOp.
|
||||
func (cc *compileCtx) subscript(sn *parse.Subscript) valuesOp {
|
||||
if sn.Right == nil {
|
||||
return cc.primary(sn.Left)
|
||||
}
|
||||
left := cc.primary(sn.Left)
|
||||
right := cc.compound(sn.Right)
|
||||
return combineSubscript(cc, left, right, sn.Left.Pos, sn.Right.Pos)
|
||||
}
|
||||
|
||||
// primary compiles a PrimaryNode into a valuesOp.
|
||||
func (cc *compileCtx) primary(fn *parse.Primary) valuesOp {
|
||||
switch fn.Typ {
|
||||
case parse.StringPrimary:
|
||||
text := fn.Node.(*parse.String).Text
|
||||
return makeString(text)
|
||||
case parse.VariablePrimary:
|
||||
name := fn.Node.(*parse.String).Text
|
||||
return makeVar(cc, name, fn.Pos)
|
||||
case parse.TablePrimary:
|
||||
table := fn.Node.(*parse.Table)
|
||||
list := cc.compounds(table.List)
|
||||
keys := make([]valuesOp, len(table.Dict))
|
||||
values := make([]valuesOp, len(table.Dict))
|
||||
for i, tp := range table.Dict {
|
||||
keys[i] = cc.compound(tp.Key)
|
||||
values[i] = cc.compound(tp.Value)
|
||||
}
|
||||
return combineTable(list, keys, values, fn.Pos)
|
||||
case parse.ClosurePrimary:
|
||||
return cc.closure(fn.Node.(*parse.Closure))
|
||||
case parse.ListPrimary:
|
||||
return cc.spaced(fn.Node.(*parse.Spaced))
|
||||
case parse.ChanCapturePrimary:
|
||||
op := cc.pipeline(fn.Node.(*parse.Pipeline))
|
||||
return combineChanCapture(op)
|
||||
case parse.StatusCapturePrimary:
|
||||
op := cc.pipeline(fn.Node.(*parse.Pipeline))
|
||||
return op
|
||||
default:
|
||||
panic(fmt.Sprintln("bad PrimaryNode type", fn.Typ))
|
||||
}
|
||||
}
|
||||
|
|
45
eval/eval.go
45
eval/eval.go
|
@ -13,7 +13,7 @@ import (
|
|||
"unicode/utf8"
|
||||
|
||||
"github.com/elves/elvish/errutil"
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/parse-ng"
|
||||
"github.com/elves/elvish/store"
|
||||
)
|
||||
|
||||
|
@ -25,7 +25,6 @@ type ns map[string]Variable
|
|||
// Evaler is used to evaluate elvish sources. It maintains runtime context
|
||||
// shared among all evalCtx instances.
|
||||
type Evaler struct {
|
||||
Compiler *Compiler
|
||||
global ns
|
||||
builtin ns
|
||||
mod map[string]ns
|
||||
|
@ -80,7 +79,6 @@ func NewEvaler(st *store.Store, dataDir string) *Evaler {
|
|||
}
|
||||
|
||||
return &Evaler{
|
||||
NewCompiler(makeCompilerScope(builtin), dataDir),
|
||||
ns{}, builtin, map[string]ns{},
|
||||
searchPaths, st,
|
||||
}
|
||||
|
@ -179,23 +177,22 @@ func (ec *evalCtx) growPorts(n int) {
|
|||
copy(ec.ports, ports)
|
||||
}
|
||||
|
||||
// makeCompilerScope extracts the type information from variables.
|
||||
func makeCompilerScope(s ns) staticNS {
|
||||
scope := staticNS{}
|
||||
for name, variable := range s {
|
||||
scope[name] = variable.StaticType()
|
||||
func makeScope(s ns) scope {
|
||||
sc := scope{}
|
||||
for name, _ := range s {
|
||||
sc[name] = true
|
||||
}
|
||||
return scope
|
||||
return sc
|
||||
}
|
||||
|
||||
// Eval evaluates a chunk node n. The supplied name and text are used in
|
||||
// diagnostic messages.
|
||||
func (ev *Evaler) Eval(name, text, dir string, n *parse.Chunk) ([]Value, error) {
|
||||
return ev.evalWithChanOut(name, text, dir, n, nil)
|
||||
func (ev *Evaler) Eval(name, text string, n *parse.Chunk) ([]Value, error) {
|
||||
return ev.evalWithChanOut(name, text, n, nil)
|
||||
}
|
||||
|
||||
func (ev *Evaler) evalWithChanOut(name, text, dir string, n *parse.Chunk, ch chan Value) ([]Value, error) {
|
||||
op, err := ev.Compiler.Compile(name, text, dir, n)
|
||||
func (ev *Evaler) evalWithChanOut(name, text string, n *parse.Chunk, ch chan Value) ([]Value, error) {
|
||||
op, err := compile(name, text, makeScope(ev.global), n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -209,7 +206,7 @@ func (ec *evalCtx) eval(op valuesOp) ([]Value, error) {
|
|||
}
|
||||
|
||||
func (ec *evalCtx) evalWithChanOut(op valuesOp, ch chan Value) (vs []Value, err error) {
|
||||
if op.f == nil {
|
||||
if op == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if ch != nil {
|
||||
|
@ -217,21 +214,21 @@ func (ec *evalCtx) evalWithChanOut(op valuesOp, ch chan Value) (vs []Value, err
|
|||
}
|
||||
defer ec.closePorts()
|
||||
defer errutil.Catch(&err)
|
||||
vs = op.f(ec)
|
||||
vs = op(ec)
|
||||
return vs, nil
|
||||
}
|
||||
|
||||
// errorf stops the ec.eval immediately by panicking with a diagnostic message.
|
||||
// The panic is supposed to be caught by ec.eval.
|
||||
func (ec *evalCtx) errorf(p parse.Pos, format string, args ...interface{}) {
|
||||
func (ec *evalCtx) errorf(p int, format string, args ...interface{}) {
|
||||
errutil.Throw(errutil.NewContextualError(
|
||||
fmt.Sprintf("%s (%s)", ec.name, ec.context), "error",
|
||||
ec.text, int(p), format, args...))
|
||||
ec.text, p, format, args...))
|
||||
}
|
||||
|
||||
// mustSingleString returns a String if that is the only element of vs.
|
||||
// Otherwise it errors.
|
||||
func (ec *evalCtx) mustSingleString(vs []Value, what string, p parse.Pos) str {
|
||||
func (ec *evalCtx) mustSingleString(vs []Value, what string, p int) str {
|
||||
if len(vs) != 1 {
|
||||
ec.errorf(p, "Expect exactly one word for %s, got %d", what, len(vs))
|
||||
}
|
||||
|
@ -242,23 +239,13 @@ func (ec *evalCtx) mustSingleString(vs []Value, what string, p parse.Pos) str {
|
|||
return v
|
||||
}
|
||||
|
||||
func (ec *evalCtx) applyPortOps(ports []portOp) {
|
||||
ec.growPorts(len(ports))
|
||||
|
||||
for i, op := range ports {
|
||||
if op != nil {
|
||||
ec.ports[i] = op(ec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SourceText evaluates a chunk of elvish source.
|
||||
func (ev *Evaler) SourceText(name, src, dir string) ([]Value, error) {
|
||||
n, err := parse.Parse(name, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ev.Eval(name, src, dir, n)
|
||||
return ev.Eval(name, src, n)
|
||||
}
|
||||
|
||||
func readFileUTF8(fname string) (string, error) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/parse-ng"
|
||||
)
|
||||
|
||||
func TestNewEvaler(t *testing.T) {
|
||||
|
@ -17,14 +17,6 @@ func TestNewEvaler(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func mustParse(name, text string) *parse.Chunk {
|
||||
n, e := parse.Parse(name, text)
|
||||
if e != nil {
|
||||
panic("parser error")
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func stringValues(ss ...string) []Value {
|
||||
vs := make([]Value, len(ss))
|
||||
for i, s := range ss {
|
||||
|
@ -57,34 +49,24 @@ var evalTests = []struct {
|
|||
{"/ 1 0", stringValues("+Inf"), false},
|
||||
|
||||
// String literal
|
||||
{"put `such \\\"``literal`", stringValues("such \\\"`literal"), false},
|
||||
{`put 'such \"''literal'`, stringValues(`such \"'literal`), false},
|
||||
{`put "much \n\033[31;1m$cool\033[m"`,
|
||||
stringValues("much \n\033[31;1m$cool\033[m"), false},
|
||||
|
||||
// Compounding list primaries
|
||||
{"put {fi elvi}sh{1 2}",
|
||||
stringValues("fish1", "fish2", "elvish1", "elvish2"), false},
|
||||
{"put {fi,elvi}sh{1.0,1.1}",
|
||||
stringValues("fish1.0", "fish1.1", "elvish1.0", "elvish1.1"), false},
|
||||
|
||||
// Table and subscript
|
||||
{"println [a b c &key value] | feedchan",
|
||||
stringValues("[a b c &key value]"), false},
|
||||
{"put [a b c &key value][2]", stringValues("c"), false},
|
||||
{"put [a b c &key value][key]", stringValues("value"), false},
|
||||
// List, map and indexing
|
||||
{"println [a b c] [&key value] | feedchan",
|
||||
stringValues("[a b c][&key value]"), false},
|
||||
{"put [a b c][2]", stringValues("c"), false},
|
||||
{"put [&key value][key]", stringValues("value"), false},
|
||||
|
||||
// Variable and compounding
|
||||
{"var $x string = `SHELL`\nput `WOW, SUCH `$x`, MUCH COOL`\n",
|
||||
{"set x = `SHELL`\nput `WOW, SUCH `$x`, MUCH COOL`\n",
|
||||
stringValues("WOW, SUCH SHELL, MUCH COOL"), false},
|
||||
|
||||
// var and set
|
||||
{"var $x $y string = SUCH VAR; put $x $y",
|
||||
stringValues("SUCH", "VAR"), false},
|
||||
{"var $x $y string; set $x $y = SUCH SET; put $x $y",
|
||||
stringValues("SUCH", "SET"), false},
|
||||
{"var $x", stringValues(), false},
|
||||
{"var $x string $y", stringValues(), false},
|
||||
{"var $x table; set $x = [lorem ipsum]; put $x[1]",
|
||||
stringValues("ipsum"), false},
|
||||
|
||||
// Channel capture
|
||||
{"put (put lorem ipsum)", stringValues("lorem", "ipsum"), false},
|
||||
|
||||
|
@ -93,47 +75,54 @@ var evalTests = []struct {
|
|||
[]Value{ok, newFailure("1"), newFailure("1")}, false},
|
||||
|
||||
// Closure evaluation
|
||||
{"{ }", stringValues(), false},
|
||||
{"{|$x| put $x} foo", stringValues("foo"), false},
|
||||
{"[]{ }", stringValues(), false},
|
||||
{"[x]{put $x} foo", stringValues("foo"), false},
|
||||
|
||||
// Variable enclosure
|
||||
{"var $x = lorem; { put $x; set $x = ipsum }; put $x",
|
||||
{"set x = lorem; []{ put $x; set x = ipsum }; put $x",
|
||||
stringValues("lorem", "ipsum"), false},
|
||||
// Shadowing
|
||||
{"var $x = ipsum; { var $x = lorem; put $x }; put $x",
|
||||
{"set x = ipsum; []{ var x = lorem; put $x }; put $x",
|
||||
stringValues("lorem", "ipsum"), false},
|
||||
// Shadowing by argument
|
||||
{"var $x = ipsum; { |$x| put $x; set $x = BAD } lorem; put $x",
|
||||
{"set x = ipsum; [x]{ put $x; set x = BAD } lorem; put $x",
|
||||
stringValues("lorem", "ipsum"), false},
|
||||
|
||||
// fn
|
||||
{"fn f $x { put $x ipsum }; f lorem",
|
||||
{"fn f [x]{ put $x ipsum }; f lorem",
|
||||
stringValues("lorem", "ipsum"), false},
|
||||
// if
|
||||
{"if $true { put x }", stringValues("x"), false},
|
||||
{"if $true $false { put x } else if $true { put y }",
|
||||
{"if true; then put x", stringValues("x"), false},
|
||||
{"if true; false; then put x; else put y",
|
||||
stringValues("y"), false},
|
||||
{"if $true $false { put x } else if $false { put y } else { put z }",
|
||||
{"if true; false; then put x; else if false; put y; else put z",
|
||||
stringValues("z"), false},
|
||||
|
||||
// Namespaces
|
||||
// Pseudo-namespaces local: and up:
|
||||
{"var $true = lorem; { var $true = ipsum; put $up:true $local:true $builtin:true }",
|
||||
{"set true = lorem; []{set true = ipsum; put $up:true $local:true $builtin:true}",
|
||||
[]Value{str("lorem"), str("ipsum"), boolean(true)}, false},
|
||||
{"var $x = lorem; { set $up:x = ipsum }; put $x",
|
||||
stringValues("ipsum"), false},
|
||||
{"set x = lorem; []{set up:x = ipsum}; put x", stringValues("ipsum"), false},
|
||||
// Pseudo-namespace env:
|
||||
{"set $env:foo = lorem; put $env:foo", stringValues("lorem"), false},
|
||||
{"del $env:foo; put $env:foo", stringValues(""), false},
|
||||
{"set env:foo = lorem; put $env:foo", stringValues("lorem"), false},
|
||||
{"del env:foo; put $env:foo", stringValues(""), false},
|
||||
}
|
||||
|
||||
func evalAndCollect(texts []string, chsize int) ([]Value, error) {
|
||||
func mustParse(t *testing.T, name, text string) *parse.Chunk {
|
||||
n, err := parse.Parse("<eval_test>", text)
|
||||
if err != nil {
|
||||
t.Fatalf("Parser(%q) error: %s", text, err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func evalAndCollect(t *testing.T, texts []string, chsize int) ([]Value, error) {
|
||||
name := "<eval test>"
|
||||
ev := NewEvaler(nil, ".")
|
||||
outs := []Value{}
|
||||
|
||||
for _, text := range texts {
|
||||
n := mustParse(name, text)
|
||||
n := mustParse(t, name, text)
|
||||
|
||||
out := make(chan Value, chsize)
|
||||
exhausted := make(chan struct{})
|
||||
|
@ -145,7 +134,7 @@ func evalAndCollect(texts []string, chsize int) ([]Value, error) {
|
|||
}()
|
||||
|
||||
// XXX(xiaq): Ignore failures
|
||||
_, err := ev.evalWithChanOut(name, text, ".", n, out)
|
||||
_, err := ev.evalWithChanOut(name, text, n, out)
|
||||
if err != nil {
|
||||
return outs, err
|
||||
}
|
||||
|
@ -157,7 +146,7 @@ func evalAndCollect(texts []string, chsize int) ([]Value, error) {
|
|||
|
||||
func TestEval(t *testing.T) {
|
||||
for _, tt := range evalTests {
|
||||
outs, err := evalAndCollect([]string{tt.text}, len(tt.wanted))
|
||||
outs, err := evalAndCollect(t, []string{tt.text}, len(tt.wanted))
|
||||
|
||||
if tt.wantError {
|
||||
// Test for error, ignore output
|
||||
|
@ -176,6 +165,7 @@ func TestEval(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestMultipleEval(t *testing.T) {
|
||||
outs, err := evalAndCollect([]string{"var $x = `hello`", "put $x"}, 1)
|
||||
wanted := stringValues("hello")
|
||||
|
@ -186,3 +176,4 @@ func TestMultipleEval(t *testing.T) {
|
|||
t.Errorf("eval %q outputs %v, want %v", outs, wanted)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -121,6 +121,10 @@ func (b *builtinFn) Exec(ec *evalCtx, args []Value) <-chan *stateUpdate {
|
|||
return update
|
||||
}
|
||||
|
||||
var (
|
||||
arityMismatch = newFailure("arity mismatch")
|
||||
)
|
||||
|
||||
// Exec executes a closure.
|
||||
func (c *closure) Exec(ec *evalCtx, args []Value) <-chan *stateUpdate {
|
||||
update := make(chan *stateUpdate, 1)
|
||||
|
|
127
eval/must.go
Normal file
127
eval/must.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/elves/elvish/parse-ng"
|
||||
)
|
||||
|
||||
type muster struct {
|
||||
ec *evalCtx
|
||||
what string
|
||||
p int
|
||||
vs []Value
|
||||
}
|
||||
|
||||
func (m *muster) error(want, got string) {
|
||||
m.ec.errorf(m.p, "%s must be %s; got %s", m.what, want, got)
|
||||
}
|
||||
|
||||
func (ec *evalCtx) must(vs []Value, what string, pos int) *muster {
|
||||
return &muster{ec, what, pos, vs}
|
||||
}
|
||||
|
||||
func (m *muster) mustLen(l int) {
|
||||
if len(m.vs) != l {
|
||||
m.ec.errorf(m.p, "%s must be exactly %d value; got %d", m.what, l, len(m.vs))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *muster) mustOne() Value {
|
||||
m.mustLen(1)
|
||||
return m.vs[0]
|
||||
}
|
||||
|
||||
func (m *muster) zerothMustStr() str {
|
||||
v := m.vs[0]
|
||||
s, ok := v.(str)
|
||||
if !ok {
|
||||
m.ec.errorf(m.p, "%s must be a string; got %s (type %s)",
|
||||
m.what, v.Repr(), v.Type())
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (m *muster) mustOneStr() str {
|
||||
m.mustLen(1)
|
||||
return m.zerothMustStr()
|
||||
}
|
||||
|
||||
func (m *muster) zerothMustInt() int {
|
||||
s := m.zerothMustStr()
|
||||
i, err := strconv.Atoi(string(s))
|
||||
if err != nil {
|
||||
m.ec.errorf(m.p, "%s must be an integer; got %s", m.what, s)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (m *muster) mustOneInt() int {
|
||||
m.mustLen(1)
|
||||
return m.zerothMustInt()
|
||||
}
|
||||
|
||||
func (m *muster) zerothMustNonNegativeInt() int {
|
||||
i := m.zerothMustInt()
|
||||
if i < 0 {
|
||||
m.ec.errorf(m.p, "%s must be non-negative; got %d", m.what, i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (m *muster) mustOneNonNegativeInt() int {
|
||||
m.mustLen(1)
|
||||
return m.zerothMustNonNegativeInt()
|
||||
}
|
||||
|
||||
func mustPrimary(cp *compiler, cn *parse.Compound, msg string) *parse.Primary {
|
||||
if len(cn.Indexeds) != 1 || len(cn.Indexeds[0].Indicies) > 0 {
|
||||
// cp.errorf(cn.Begin, msg)
|
||||
}
|
||||
return cn.Indexeds[0].Head
|
||||
}
|
||||
|
||||
func mustVariableOrString(cp *compiler, cn *parse.Compound, msg string) (*parse.Primary, string) {
|
||||
pn := mustPrimary(cp, cn, msg)
|
||||
switch pn.Type {
|
||||
case parse.Variable:
|
||||
return pn, pn.Value[1:]
|
||||
case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted:
|
||||
return pn, pn.Value
|
||||
default:
|
||||
// cp.errorf(cn.Pos, msg)
|
||||
return nil, ""
|
||||
}
|
||||
}
|
||||
|
||||
// mustVariable musts that a Compound contains exactly one Primary of type
|
||||
// Variable.
|
||||
func mustVariablePrimary(cp *compiler, cn *parse.Compound, msg string) (*parse.Primary, string) {
|
||||
pn, text := mustVariableOrString(cp, cn, msg)
|
||||
if pn.Type != parse.Variable {
|
||||
// cp.errorf(pn.Pos, msg)
|
||||
}
|
||||
return pn, text
|
||||
}
|
||||
|
||||
// mustString musts that a Compound contains exactly one Primary of type
|
||||
// Variable.
|
||||
func mustString(cp *compiler, cn *parse.Compound, msg string) (*parse.Primary, string) {
|
||||
pn, text := mustVariableOrString(cp, cn, msg)
|
||||
if pn.Type == parse.Variable {
|
||||
// cp.errorf(pn.Pos, msg)
|
||||
}
|
||||
return pn, text
|
||||
}
|
||||
|
||||
/*
|
||||
// mustStartWithVariabl musts the first compound of the form is a
|
||||
// VariablePrimary. This is merely for better error messages; No actual
|
||||
// processing is done.
|
||||
func mustStartWithVariable(cp *compiler, fn *parse.Form, form string) {
|
||||
if len(fn.Args.Nodes) == 0 {
|
||||
cp.errorf(fn.Pos, "expect variable after %s", form)
|
||||
}
|
||||
mustVariablePrimary(cp, fn.Args.Nodes[0], "expect variable")
|
||||
}
|
||||
*/
|
295
eval/op.go
295
eval/op.go
|
@ -1,295 +0,0 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/elves/elvish/parse"
|
||||
)
|
||||
|
||||
const (
|
||||
pipelineChanBufferSize = 32
|
||||
)
|
||||
|
||||
// Definition of Op and friends and combinators.
|
||||
|
||||
// Op operates on an evalCtx.
|
||||
type Op func(*evalCtx)
|
||||
|
||||
// valuesOp operates on an evalCtx and results in some values.
|
||||
type valuesOp struct {
|
||||
tr typeRun
|
||||
f func(*evalCtx) []Value
|
||||
}
|
||||
|
||||
// portOp operates on an evalCtx and results in a port.
|
||||
type portOp func(*evalCtx) *port
|
||||
|
||||
// stateUpdatesOp operates on an evalCtx and results in a receiving channel
|
||||
// of StateUpdate's.
|
||||
type stateUpdatesOp func(*evalCtx) <-chan *stateUpdate
|
||||
|
||||
func combineChunk(ops []valuesOp) valuesOp {
|
||||
f := func(ec *evalCtx) []Value {
|
||||
for _, op := range ops {
|
||||
s := op.f(ec)
|
||||
if HasFailure(s) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return []Value{ok}
|
||||
}
|
||||
return valuesOp{newHomoTypeRun(&exitusType{}, 1, true), f}
|
||||
}
|
||||
|
||||
func combineClosure(argNames []string, op valuesOp, up map[string]Type) valuesOp {
|
||||
f := func(ec *evalCtx) []Value {
|
||||
evCaptured := make(map[string]Variable, len(up))
|
||||
for name := range up {
|
||||
evCaptured[name] = ec.ResolveVar("", name)
|
||||
}
|
||||
return []Value{newClosure(argNames, op, evCaptured)}
|
||||
}
|
||||
return valuesOp{newFixedTypeRun(callableType{}), f}
|
||||
}
|
||||
|
||||
var noExitus = newFailure("no exitus")
|
||||
|
||||
func combinePipeline(ops []stateUpdatesOp, p parse.Pos) valuesOp {
|
||||
f := func(ec *evalCtx) []Value {
|
||||
var nextIn *port
|
||||
updates := make([]<-chan *stateUpdate, len(ops))
|
||||
// For each form, create a dedicated evalCtx and run
|
||||
for i, op := range ops {
|
||||
newEc := ec.copy(fmt.Sprintf("form op %v", op))
|
||||
if i > 0 {
|
||||
newEc.ports[0] = nextIn
|
||||
}
|
||||
if i < len(ops)-1 {
|
||||
// Each internal port pair consists of a (byte) pipe pair and a
|
||||
// channel.
|
||||
// os.Pipe sets O_CLOEXEC, which is what we want.
|
||||
reader, writer, e := os.Pipe()
|
||||
if e != nil {
|
||||
ec.errorf(p, "failed to create pipe: %s", e)
|
||||
}
|
||||
ch := make(chan Value, pipelineChanBufferSize)
|
||||
newEc.ports[1] = &port{
|
||||
f: writer, ch: ch, closeF: true, closeCh: true}
|
||||
nextIn = &port{
|
||||
f: reader, ch: ch, closeF: true, closeCh: false}
|
||||
}
|
||||
updates[i] = op(newEc)
|
||||
}
|
||||
// Collect exit values
|
||||
exits := make([]Value, len(ops))
|
||||
for i, update := range updates {
|
||||
ex := noExitus
|
||||
for up := range update {
|
||||
ex = up.Exitus
|
||||
}
|
||||
exits[i] = ex
|
||||
}
|
||||
return exits
|
||||
}
|
||||
return valuesOp{newHomoTypeRun(&exitusType{}, len(ops), false), f}
|
||||
}
|
||||
|
||||
func combineSpecialForm(op exitusOp, ports []portOp, p parse.Pos) stateUpdatesOp {
|
||||
// ec here is always a subevaler created in combinePipeline, so it can
|
||||
// be safely modified.
|
||||
return func(ec *evalCtx) <-chan *stateUpdate {
|
||||
ec.applyPortOps(ports)
|
||||
return ec.execSpecial(op)
|
||||
}
|
||||
}
|
||||
|
||||
func combineNonSpecialForm(cmdOp, argsOp valuesOp, ports []portOp, p parse.Pos) stateUpdatesOp {
|
||||
// ec here is always a subevaler created in combinePipeline, so it can
|
||||
// be safely modified.
|
||||
return func(ec *evalCtx) <-chan *stateUpdate {
|
||||
ec.applyPortOps(ports)
|
||||
|
||||
cmd := cmdOp.f(ec)
|
||||
expect := "expect a single string or closure value"
|
||||
if len(cmd) != 1 {
|
||||
ec.errorf(p, expect)
|
||||
}
|
||||
switch cmd[0].(type) {
|
||||
case str, callable:
|
||||
default:
|
||||
ec.errorf(p, expect)
|
||||
}
|
||||
|
||||
args := argsOp.f(ec)
|
||||
return ec.execNonSpecial(cmd[0], args)
|
||||
}
|
||||
}
|
||||
|
||||
func combineSpaced(ops []valuesOp) valuesOp {
|
||||
tr := make(typeRun, 0, len(ops))
|
||||
for _, op := range ops {
|
||||
tr = append(tr, op.tr...)
|
||||
}
|
||||
|
||||
f := func(ec *evalCtx) []Value {
|
||||
// Use number of compound expressions as an estimation of the number
|
||||
// of values
|
||||
vs := make([]Value, 0, len(ops))
|
||||
for _, op := range ops {
|
||||
us := op.f(ec)
|
||||
vs = append(vs, us...)
|
||||
}
|
||||
return vs
|
||||
}
|
||||
return valuesOp{tr, f}
|
||||
}
|
||||
|
||||
func compound(lhs, rhs Value) Value {
|
||||
return str(toString(lhs) + toString(rhs))
|
||||
}
|
||||
|
||||
func combineCompound(ops []valuesOp) valuesOp {
|
||||
// Non-proper compound: just return the sole subscript
|
||||
if len(ops) == 1 {
|
||||
return ops[0]
|
||||
}
|
||||
|
||||
n := 1
|
||||
more := false
|
||||
for _, op := range ops {
|
||||
m, b := op.tr.count()
|
||||
n *= m
|
||||
more = more || b
|
||||
}
|
||||
|
||||
f := func(ec *evalCtx) []Value {
|
||||
vs := []Value{str("")}
|
||||
for _, op := range ops {
|
||||
us := op.f(ec)
|
||||
if len(us) == 1 {
|
||||
u := us[0]
|
||||
for i := range vs {
|
||||
vs[i] = compound(vs[i], u)
|
||||
}
|
||||
} else {
|
||||
// Do a cartesian product
|
||||
newvs := make([]Value, len(vs)*len(us))
|
||||
for i, v := range vs {
|
||||
for j, u := range us {
|
||||
newvs[i*len(us)+j] = compound(v, u)
|
||||
}
|
||||
}
|
||||
vs = newvs
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
return valuesOp{newHomoTypeRun(stringType{}, n, more), f}
|
||||
}
|
||||
|
||||
func literalValue(v ...Value) valuesOp {
|
||||
tr := make(typeRun, len(v))
|
||||
for i := range tr {
|
||||
tr[i].t = v[i].Type()
|
||||
}
|
||||
f := func(e *evalCtx) []Value {
|
||||
return v
|
||||
}
|
||||
return valuesOp{tr, f}
|
||||
}
|
||||
|
||||
func makeString(text string) valuesOp {
|
||||
return literalValue(str(text))
|
||||
}
|
||||
|
||||
func makeVar(cc *compileCtx, qname string, p parse.Pos) valuesOp {
|
||||
ns, name := splitQualifiedName(qname)
|
||||
tr := newFixedTypeRun(cc.mustResolveVar(ns, name, p))
|
||||
f := func(ec *evalCtx) []Value {
|
||||
variable := ec.ResolveVar(ns, name)
|
||||
if variable == nil {
|
||||
ec.errorf(p, "variable $%s not found; the compiler has a bug", name)
|
||||
}
|
||||
return []Value{variable.Get()}
|
||||
}
|
||||
return valuesOp{tr, f}
|
||||
}
|
||||
|
||||
func combineSubscript(cc *compileCtx, left, right valuesOp, lp, rp parse.Pos) valuesOp {
|
||||
if !left.tr.mayCountTo(1) {
|
||||
cc.errorf(lp, "left operand of subscript must be a single value")
|
||||
}
|
||||
var t Type
|
||||
switch left.tr[0].t.(type) {
|
||||
case stringType:
|
||||
t = stringType{}
|
||||
case tableType, anyType:
|
||||
t = anyType{}
|
||||
default:
|
||||
cc.errorf(lp, "left operand of subscript must be of type string, env, table or any")
|
||||
}
|
||||
|
||||
if !right.tr.mayCountTo(1) {
|
||||
cc.errorf(rp, "right operand of subscript must be a single value")
|
||||
}
|
||||
if _, ok := right.tr[0].t.(stringType); !ok {
|
||||
cc.errorf(rp, "right operand of subscript must be of type string")
|
||||
}
|
||||
|
||||
f := func(ec *evalCtx) []Value {
|
||||
l := left.f(ec)
|
||||
if len(l) != 1 {
|
||||
ec.errorf(lp, "left operand of subscript must be a single value")
|
||||
}
|
||||
r := right.f(ec)
|
||||
if len(r) != 1 {
|
||||
ec.errorf(rp, "right operand of subscript must be a single value")
|
||||
}
|
||||
return []Value{evalSubscript(ec, l[0], r[0], lp, rp)}
|
||||
}
|
||||
return valuesOp{newFixedTypeRun(t), f}
|
||||
}
|
||||
|
||||
func combineTable(list valuesOp, keys []valuesOp, values []valuesOp, p parse.Pos) valuesOp {
|
||||
f := func(ec *evalCtx) []Value {
|
||||
t := newTable()
|
||||
t.append(list.f(ec)...)
|
||||
for i, kop := range keys {
|
||||
vop := values[i]
|
||||
ks := kop.f(ec)
|
||||
vs := vop.f(ec)
|
||||
if len(ks) != len(vs) {
|
||||
ec.errorf(p, "Number of keys doesn't match number of values: %d vs. %d", len(ks), len(vs))
|
||||
}
|
||||
for j, k := range ks {
|
||||
t.Dict[toString(k)] = vs[j]
|
||||
}
|
||||
}
|
||||
return []Value{t}
|
||||
}
|
||||
return valuesOp{newFixedTypeRun(tableType{}), f}
|
||||
}
|
||||
|
||||
func combineChanCapture(op valuesOp) valuesOp {
|
||||
tr := typeRun{typeStar{anyType{}, true}}
|
||||
f := func(ec *evalCtx) []Value {
|
||||
vs := []Value{}
|
||||
newEc := ec.copy(fmt.Sprintf("channel output capture %v", op))
|
||||
ch := make(chan Value)
|
||||
collected := make(chan bool)
|
||||
newEc.ports[1] = &port{ch: ch}
|
||||
go func() {
|
||||
for v := range ch {
|
||||
vs = append(vs, v)
|
||||
}
|
||||
collected <- true
|
||||
}()
|
||||
op.f(newEc)
|
||||
newEc.closePorts()
|
||||
close(ch)
|
||||
<-collected
|
||||
return vs
|
||||
}
|
||||
return valuesOp{tr, f}
|
||||
}
|
19
eval/port.go
19
eval/port.go
|
@ -12,15 +12,22 @@ type port struct {
|
|||
closeCh bool
|
||||
}
|
||||
|
||||
func (p *port) close() {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
if p.closeF {
|
||||
p.f.Close()
|
||||
}
|
||||
if p.closeCh {
|
||||
close(p.ch)
|
||||
}
|
||||
}
|
||||
|
||||
// closePorts closes the suitable components of all ports in ec.ports that were
|
||||
// marked marked for closing.
|
||||
func (ec *evalCtx) closePorts() {
|
||||
for _, port := range ec.ports {
|
||||
if port.closeF {
|
||||
port.f.Close()
|
||||
}
|
||||
if port.closeCh {
|
||||
close(port.ch)
|
||||
}
|
||||
port.close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ type callableType struct {
|
|||
}
|
||||
|
||||
func (ct callableType) Default() Value {
|
||||
return newClosure([]string{}, valuesOp{}, map[string]Variable{})
|
||||
return newClosure([]string{}, func(*evalCtx) []Value { return nil }, map[string]Variable{})
|
||||
}
|
||||
|
||||
func (ct callableType) String() string {
|
||||
|
|
|
@ -8,10 +8,8 @@ import (
|
|||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/parse-ng"
|
||||
"github.com/elves/elvish/strutil"
|
||||
)
|
||||
|
||||
|
@ -35,51 +33,8 @@ func (s str) Type() Type {
|
|||
return stringType{}
|
||||
}
|
||||
|
||||
func quote(s string) string {
|
||||
if len(s) == 0 {
|
||||
return "``"
|
||||
}
|
||||
|
||||
printable := true
|
||||
for _, r := range s {
|
||||
if !unicode.IsPrint(r) {
|
||||
printable = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if printable {
|
||||
r0, w0 := utf8.DecodeRuneInString(s)
|
||||
if parse.StartsBare(r0) {
|
||||
barewordPossible := true
|
||||
for _, r := range s[w0:] {
|
||||
if parse.TerminatesBare(r) {
|
||||
barewordPossible = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if barewordPossible {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// Quote with backquote
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteRune('`')
|
||||
for _, r := range s {
|
||||
buf.WriteRune(r)
|
||||
if r == '`' {
|
||||
buf.WriteRune('`')
|
||||
}
|
||||
}
|
||||
buf.WriteRune('`')
|
||||
return buf.String()
|
||||
}
|
||||
// Quote with double quote
|
||||
return strconv.Quote(s)
|
||||
}
|
||||
|
||||
func (s str) Repr() string {
|
||||
return quote(string(s))
|
||||
return parse.Quote(string(s))
|
||||
}
|
||||
|
||||
func (s str) String() string {
|
||||
|
@ -162,7 +117,7 @@ func (e exitus) Repr() string {
|
|||
case Ok:
|
||||
return "$ok"
|
||||
case Failure:
|
||||
return "(failure " + quote(e.Failure) + ")"
|
||||
return "(failure " + parse.Quote(e.Failure) + ")"
|
||||
case Traceback:
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString("(traceback")
|
||||
|
@ -228,7 +183,7 @@ func (t *table) Repr() string {
|
|||
sep = " "
|
||||
}
|
||||
for k, v := range t.Dict {
|
||||
fmt.Fprint(buf, sep, "&", quote(k), " ", v.Repr())
|
||||
fmt.Fprint(buf, sep, "&", parse.Quote(k), " ", v.Repr())
|
||||
sep = " "
|
||||
}
|
||||
buf.WriteRune(']')
|
||||
|
@ -315,7 +270,7 @@ func (r rat) String() string {
|
|||
return r.b.String()
|
||||
}
|
||||
|
||||
func evalSubscript(ec *evalCtx, left, right Value, lp, rp parse.Pos) Value {
|
||||
func evalSubscript(ec *evalCtx, left, right Value, lp, rp int) Value {
|
||||
var sub string
|
||||
if s, ok := right.(str); ok {
|
||||
sub = string(s)
|
||||
|
|
4
main.go
4
main.go
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/elves/elvish/edit"
|
||||
"github.com/elves/elvish/errutil"
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/parse-ng"
|
||||
"github.com/elves/elvish/store"
|
||||
"github.com/elves/elvish/sysutil"
|
||||
)
|
||||
|
@ -106,7 +106,7 @@ func interact() {
|
|||
printError(err)
|
||||
|
||||
if err == nil {
|
||||
vs, err := ev.Eval(name, lr.Line, ".", n)
|
||||
vs, err := ev.Eval(name, lr.Line, n)
|
||||
printError(err)
|
||||
eval.PrintExituses(vs)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user