elvish/eval/builtin-special.go

418 lines
10 KiB
Go
Raw Normal View History

package eval
// Builtin special forms.
import (
"errors"
"fmt"
"os"
"strings"
2016-01-25 01:10:54 +08:00
"github.com/elves/elvish/parse"
)
2016-02-20 07:48:13 +08:00
type compileBuiltin func(*compiler, *parse.Form) OpFunc
var ErrNoDataDir = errors.New("There is no data directory")
2016-01-23 01:05:15 +08:00
var builtinSpecials map[string]compileBuiltin
2016-02-08 06:23:16 +08:00
// BuiltinSpecialNames contains all names of builtin special forms. It is
// useful for the syntax highlighter.
2015-07-06 23:58:51 +08:00
var BuiltinSpecialNames []string
func init() {
// Needed to avoid initialization loop
2016-01-23 01:05:15 +08:00
builtinSpecials = map[string]compileBuiltin{
"del": compileDel,
"fn": compileFn,
"use": compileUse,
"for": compileFor,
"while": compileWhile,
"try": compileTry,
}
2016-02-08 06:23:16 +08:00
for k := range builtinSpecials {
2015-07-06 23:58:51 +08:00
BuiltinSpecialNames = append(BuiltinSpecialNames, k)
}
}
2015-01-22 00:22:55 +08:00
// DelForm = 'del' { VariablePrimary }
2016-02-20 07:48:13 +08:00
func compileDel(cp *compiler, fn *parse.Form) OpFunc {
// Do conventional compiling of all compound expressions, including
// ensuring that variables can be resolved
var names, envNames []string
for _, cn := range fn.Args {
2016-02-20 04:16:23 +08:00
cp.compiling(cn)
qname := mustString(cp, cn, "should be a literal variable name")
explode, ns, name := ParseAndFixVariable(qname)
if explode {
cp.errorf("removing exploded variable makes no sense")
}
switch ns {
case "", "local":
if !cp.thisScope()[name] {
2016-02-20 04:16:23 +08:00
cp.errorf("variable $%s not found on current local scope", name)
}
delete(cp.thisScope(), name)
names = append(names, name)
2016-10-18 22:17:15 +08:00
case "E":
envNames = append(envNames, name)
default:
2016-09-10 06:54:18 +08:00
cp.errorf("can only delete a variable in local: or E:")
}
2015-01-22 00:22:55 +08:00
}
2016-02-12 07:30:36 +08:00
return func(ec *EvalCtx) {
2014-09-26 05:26:56 +08:00
for _, name := range names {
delete(ec.local, name)
}
for _, name := range envNames {
2015-02-26 22:17:05 +08:00
// BUG(xiaq): We rely on the fact that os.Unsetenv always returns
// nil.
os.Unsetenv(name)
}
}
}
// makeFnOp wraps an op such that a return is converted to an ok.
2016-02-08 06:23:16 +08:00
func makeFnOp(op Op) Op {
2016-02-20 07:48:13 +08:00
return Op{func(ec *EvalCtx) {
err := ec.PEval(op)
if err != nil && err.(*Exception).Cause != Return {
2016-01-31 09:11:10 +08:00
// rethrow
2016-02-20 07:48:13 +08:00
throw(err)
2015-07-08 18:31:40 +08:00
}
2016-02-20 07:48:13 +08:00
}, op.Begin, op.End}
2015-07-08 18:31:40 +08:00
}
// FnForm = 'fn' StringPrimary LambdaPrimary
//
2016-01-28 05:24:17 +08:00
// fn f []{foobar} is a shorthand for set '&'f = []{foobar}.
2016-02-20 07:48:13 +08:00
func compileFn(cp *compiler, fn *parse.Form) OpFunc {
2016-01-23 08:24:47 +08:00
if len(fn.Args) == 0 {
2016-02-20 04:16:23 +08:00
end := fn.End()
cp.errorpf(end, end, "should be followed by function name")
}
2016-01-23 08:24:47 +08:00
fnName := mustString(cp, fn.Args[0], "must be a literal string")
varName := FnPrefix + fnName
2016-01-23 08:24:47 +08:00
if len(fn.Args) == 1 {
2016-02-20 04:16:23 +08:00
end := fn.Args[0].End()
cp.errorpf(end, end, "should be followed by a lambda")
}
2016-01-23 08:24:47 +08:00
pn := mustPrimary(cp, fn.Args[1], "should be a lambda")
if pn.Type != parse.Lambda {
2016-02-20 04:16:23 +08:00
cp.compiling(pn)
cp.errorf("should be a lambda")
2016-01-23 08:24:47 +08:00
}
if len(fn.Args) > 2 {
2016-02-20 04:16:23 +08:00
cp.errorpf(fn.Args[2].Begin(), fn.Args[len(fn.Args)-1].End(), "superfluous argument(s)")
}
2016-01-26 06:45:51 +08:00
cp.registerVariableSet(":" + varName)
2016-01-23 08:24:47 +08:00
op := cp.lambda(pn)
2016-02-12 07:30:36 +08:00
return func(ec *EvalCtx) {
// Initialize the function variable with the builtin nop
// function. This step allows the definition of recursive
// functions; the actual function will never be called.
ec.local[varName] = NewPtrVariable(&BuiltinFn{"<shouldn't be called>", nop})
2016-01-29 20:55:14 +08:00
closure := op(ec)[0].(*Closure)
2015-07-08 18:31:40 +08:00
closure.Op = makeFnOp(closure.Op)
ec.local[varName].Set(closure)
}
}
// UseForm = 'use' StringPrimary [ Compound ]
2016-02-20 07:48:13 +08:00
func compileUse(cp *compiler, fn *parse.Form) OpFunc {
var modname string
var filenameOp ValuesOp
2016-02-20 07:48:13 +08:00
var filenameBegin, filenameEnd int
switch len(fn.Args) {
case 0:
2016-02-20 04:16:23 +08:00
end := fn.Head.End()
cp.errorpf(end, end, "lack module name")
case 2:
2016-02-20 07:48:13 +08:00
filenameOp = cp.compoundOp(fn.Args[1])
filenameBegin = fn.Args[1].Begin()
2016-02-20 07:48:13 +08:00
filenameEnd = fn.Args[1].End()
fallthrough
case 1:
modname = mustString(cp, fn.Args[0], "should be a literal module name")
default:
2016-02-20 04:16:23 +08:00
cp.errorpf(fn.Args[2].Begin(), fn.Args[len(fn.Args)-1].End(), "superfluous argument(s)")
}
return func(ec *EvalCtx) {
2016-02-20 07:48:13 +08:00
if filenameOp.Func != nil {
values := filenameOp.Exec(ec)
valuesMust := &muster{ec, "module filename", filenameBegin, filenameEnd, values}
2016-02-22 03:10:33 +08:00
filename := string(valuesMust.mustOneStr())
use(ec, modname, &filename)
} else {
2016-02-22 03:10:33 +08:00
use(ec, modname, nil)
}
}
}
func use(ec *EvalCtx, modname string, pfilename *string) {
if _, ok := ec.Evaler.Modules[modname]; ok {
2016-02-22 03:10:33 +08:00
// Module already loaded.
return
}
// Load the source.
var filename, source string
if pfilename != nil {
filename = *pfilename
var err error
source, err = readFileUTF8(filename)
maybeThrow(err)
} else {
// No filename; defaulting to $datadir/lib/$modname.elv.
if ec.DataDir == "" {
throw(ErrNoDataDir)
}
filename = ec.DataDir + "/lib/" + strings.Replace(modname, ":", "/", -1) + ".elv"
2016-02-22 03:10:33 +08:00
if _, err := os.Stat(filename); os.IsNotExist(err) {
// File does not exist. Try loading from the table of builtin
// modules.
var ok bool
if source, ok = builtinModules[modname]; ok {
// Source is loaded. Do nothing more.
filename = "<builtin module>"
} else {
2016-02-22 03:10:33 +08:00
throw(fmt.Errorf("cannot load %s: %s does not exist", modname, filename))
}
2016-02-22 03:10:33 +08:00
} else {
// File exists. Load it.
source, err = readFileUTF8(filename)
maybeThrow(err)
}
2016-02-22 03:10:33 +08:00
}
n, err := parse.Parse(filename, source)
maybeThrow(err)
// Make an empty namespace.
local := Namespace{}
2016-02-22 03:10:33 +08:00
// TODO(xiaq): Should handle failures when evaluting the module
newEc := &EvalCtx{
2016-10-13 21:51:51 +08:00
ec.Evaler, "module " + modname,
filename, source,
local, Namespace{},
ec.ports, nil,
0, len(source), ec.addTraceback(), false,
2016-02-22 03:10:33 +08:00
}
op, err := newEc.Compile(n, filename, source)
2016-02-22 03:10:33 +08:00
// TODO the err originates in another source, should add appropriate information.
maybeThrow(err)
// Load the namespace before executing. This avoids mutual and self use's to
// result in an infinite recursion.
ec.Evaler.Modules[modname] = local
err = newEc.PEval(op)
if err != nil {
// Unload the namespace.
delete(ec.Modules, modname)
throw(err)
}
}
func compileWhile(cp *compiler, fn *parse.Form) OpFunc {
args := cp.walkArgs(fn)
condNode := args.next()
bodyNode := args.next()
args.mustEnd()
condOp := cp.compoundOp(condNode)
bodyOp := cp.compoundOp(bodyNode)
return func(ec *EvalCtx) {
body := bodyOp.execMustOneFn(ec)
for {
cond := condOp.Exec(ec)
if !allTrue(cond) {
break
}
err := ec.PCall(body, NoArgs, NoOpts)
if err != nil {
exc := err.(*Exception)
if exc.Cause == Continue {
// do nothing
} else if exc.Cause == Break {
continue
} else {
throw(err)
}
}
}
}
}
func compileFor(cp *compiler, fn *parse.Form) OpFunc {
args := cp.walkArgs(fn)
varNode := args.next()
iterNode := args.next()
bodyNode := args.next()
elseNode := args.nextLedBy("else")
args.mustEnd()
varOp, restOp := cp.lvaluesOp(varNode.Indexings[0])
if restOp.Func != nil {
cp.errorpf(restOp.Begin, restOp.End, "rest not allowed")
}
iterOp := cp.compoundOp(iterNode)
bodyOp := cp.compoundOp(bodyNode)
var elseOp ValuesOp
if elseNode != nil {
elseOp = cp.compoundOp(elseNode)
}
return func(ec *EvalCtx) {
variables := varOp.Exec(ec)
if len(variables) != 1 {
ec.errorpf(varOp.Begin, varOp.End, "only one variable allowed")
}
variable := variables[0]
iterables := iterOp.Exec(ec)
if len(iterables) != 1 {
ec.errorpf(iterOp.Begin, iterOp.End, "should be one iterable")
}
iterable, ok := iterables[0].(Iterator)
if !ok {
ec.errorpf(iterOp.Begin, iterOp.End, "should be one iterable")
}
body := bodyOp.execMustOneFn(ec)
elseBody := elseOp.execMustOneFn(ec)
iterated := false
iterable.Iterate(func(v Value) bool {
iterated = true
variable.Set(v)
err := ec.PCall(body, NoArgs, NoOpts)
if err != nil {
exc := err.(*Exception)
if exc.Cause == Continue {
// do nothing
} else if exc.Cause == Break {
return false
} else {
throw(err)
}
}
return true
})
if !iterated && elseBody != nil {
elseBody.Call(ec, NoArgs, NoOpts)
}
}
}
func compileTry(cp *compiler, fn *parse.Form) OpFunc {
Logger.Println("compiling try")
args := cp.walkArgs(fn)
bodyNode := args.next()
Logger.Printf("body is %q", bodyNode.SourceText())
var exceptVarNode *parse.Indexing
var exceptNode *parse.Compound
if args.nextIs("except") {
Logger.Println("except-ing")
n := args.next()
if len(n.Indexings) != 1 {
cp.errorpf(n.Begin(), n.End(), "should be one variable")
}
exceptVarNode = n.Indexings[0]
exceptNode = args.next()
Logger.Printf("except-var = %q, except = %q", exceptVarNode.SourceText(), exceptNode.SourceText())
}
elseNode := args.nextLedBy("else")
finallyNode := args.nextLedBy("finally")
args.mustEnd()
var exceptVarOp LValuesOp
var bodyOp, exceptOp, elseOp, finallyOp ValuesOp
bodyOp = cp.compoundOp(bodyNode)
if exceptVarNode != nil {
var restOp LValuesOp
exceptVarOp, restOp = cp.lvaluesOp(exceptVarNode)
if restOp.Func != nil {
cp.errorpf(restOp.Begin, restOp.End, "may not use @rest in except variable")
}
exceptOp = cp.compoundOp(exceptNode)
}
if elseNode != nil {
elseOp = cp.compoundOp(elseNode)
}
if finallyNode != nil {
finallyOp = cp.compoundOp(finallyNode)
}
return func(ec *EvalCtx) {
body := bodyOp.execMustOneFn(ec)
exceptVar := exceptVarOp.execMustOne(ec)
except := exceptOp.execMustOneFn(ec)
else_ := elseOp.execMustOneFn(ec)
finally := finallyOp.execMustOneFn(ec)
err := ec.PCall(body, NoArgs, NoOpts)
if err != nil {
if except != nil {
exceptVar.Set(err.(*Exception))
err = ec.PCall(except, NoArgs, NoOpts)
}
} else {
if else_ != nil {
err = ec.PCall(else_, NoArgs, NoOpts)
}
}
if finally != nil {
finally.Call(ec, NoArgs, NoOpts)
}
if err != nil {
throw(err)
}
}
}
// execMustOneFn executes the ValuesOp and raises an exception if it does not
// evaluate to exactly one Fn. If the given ValuesOp is empty, it returns nil.
func (op ValuesOp) execMustOneFn(ec *EvalCtx) Fn {
if op.Func == nil {
return nil
}
values := op.Exec(ec)
if len(values) != 1 {
ec.errorpf(op.Begin, op.End, "should be one fn")
}
fn, ok := values[0].(Fn)
if !ok {
ec.errorpf(op.Begin, op.End, "should be one fn")
}
return fn
}
// execMustOne executes the LValuesOp and raises an exception if it does not
// evaluate to exactly one Variable. If the given LValuesOp is empty, it returns
// nil.
func (op LValuesOp) execMustOne(ec *EvalCtx) Variable {
if op.Func == nil {
return nil
}
variables := op.Exec(ec)
if len(variables) != 1 {
ec.errorpf(op.Begin, op.End, "should be one variable")
}
return variables[0]
}