New eval. Currently broken.

This commit is contained in:
Qi Xiao 2016-01-22 18:05:15 +01:00
parent 73cfc1e35c
commit aa7ca8eea1
13 changed files with 790 additions and 917 deletions

67
eval/boilerplate.go Normal file
View 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
View 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()

View File

@ -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
}
}
*/

View File

@ -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))
}
}

View File

@ -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) {

View File

@ -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)
}
}
*/

View File

@ -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
View 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")
}
*/

View File

@ -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}
}

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}