diff --git a/eval/boilerplate.go b/eval/boilerplate.go new file mode 100644 index 00000000..20976c95 --- /dev/null +++ b/eval/boilerplate.go @@ -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 +} diff --git a/eval/boilerplate.py b/eval/boilerplate.py new file mode 100755 index 00000000..2bb2944a --- /dev/null +++ b/eval/boilerplate.py @@ -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() diff --git a/eval/builtin_special.go b/eval/builtin_special.go index 02d70e76..43b0ab15 100644 --- a/eval/builtin_special.go +++ b/eval/builtin_special.go @@ -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 - } -} +*/ diff --git a/eval/compile.go b/eval/compile.go index 06db91ae..55f14ac6 100644 --- a/eval/compile.go +++ b/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)) - } -} diff --git a/eval/eval.go b/eval/eval.go index 6d6cbe10..211fa11d 100644 --- a/eval/eval.go +++ b/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) { diff --git a/eval/eval_test.go b/eval/eval_test.go index 97b8ee4a..47c25a68 100644 --- a/eval/eval_test.go +++ b/eval/eval_test.go @@ -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("", 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 := "" 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) } } +*/ diff --git a/eval/exec.go b/eval/exec.go index 4cf709d2..f043d77c 100644 --- a/eval/exec.go +++ b/eval/exec.go @@ -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) diff --git a/eval/must.go b/eval/must.go new file mode 100644 index 00000000..bc0dd56e --- /dev/null +++ b/eval/must.go @@ -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") +} +*/ diff --git a/eval/op.go b/eval/op.go deleted file mode 100644 index c81d8115..00000000 --- a/eval/op.go +++ /dev/null @@ -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} -} diff --git a/eval/port.go b/eval/port.go index 10c659f8..587b9021 100644 --- a/eval/port.go +++ b/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() } } diff --git a/eval/type.go b/eval/type.go index f3346a61..1bb8e256 100644 --- a/eval/type.go +++ b/eval/type.go @@ -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 { diff --git a/eval/value.go b/eval/value.go index 00de0c3f..fed5a012 100644 --- a/eval/value.go +++ b/eval/value.go @@ -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) diff --git a/main.go b/main.go index 4f5dba56..4b5be005 100644 --- a/main.go +++ b/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) }