elvish/eval/builtin_special.go

209 lines
5.3 KiB
Go
Raw Normal View History

package eval
//go:genearte ./builtin_modules.bash
// Builtin special forms.
import (
"fmt"
"os"
2016-01-25 01:10:54 +08:00
"github.com/elves/elvish/parse"
"github.com/elves/elvish/store"
)
2016-02-08 06:23:16 +08:00
type compileBuiltin func(*compiler, *parse.Form) Op
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,
2016-01-23 08:24:47 +08:00
"fn": compileFn,
"use": compileUse,
}
2016-02-08 06:23:16 +08:00
for k := range builtinSpecials {
2015-07-06 23:58:51 +08:00
BuiltinSpecialNames = append(BuiltinSpecialNames, k)
}
}
2016-02-12 07:30:36 +08:00
func doSet(ec *EvalCtx, variables []Variable, values []Value) {
// TODO Support assignment of mismatched arity in some restricted way -
// "optional" and "rest" arguments and the like
if len(variables) != len(values) {
2016-02-08 06:23:16 +08:00
throw(ErrArityMismatch)
}
for i, variable := range variables {
// TODO Prevent overriding builtin variables e.g. $pid $env
variable.Set(values[i])
}
}
2015-01-22 00:22:55 +08:00
// DelForm = 'del' { VariablePrimary }
2016-02-08 06:23:16 +08:00
func compileDel(cp *compiler, fn *parse.Form) Op {
// Do conventional compiling of all compound expressions, including
// ensuring that variables can be resolved
var names, envNames []string
for _, cn := range fn.Args {
qname := mustString(cp, cn, "should be a literal variable name")
splice, ns, name := parseVariable(qname)
if splice {
cp.errorf(cn.Begin(), "removing spliced variable makes no sense")
}
switch ns {
case "", "local":
if !cp.thisScope()[name] {
2016-01-25 02:04:15 +08:00
cp.errorf(cn.Begin(), "variable $%s not found on current local scope", name)
}
delete(cp.thisScope(), name)
names = append(names, name)
case "env":
envNames = append(envNames, name)
default:
2016-01-25 02:04:15 +08:00
cp.errorf(cn.Begin(), "can only delete a variable in local: or env:")
}
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-12 07:30:36 +08:00
return func(ec *EvalCtx) {
2016-02-09 03:27:05 +08:00
ex := ec.PEval(op)
if ex != Return {
2016-01-31 09:11:10 +08:00
// rethrow
throw(ex)
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-08 06:23:16 +08:00
func compileFn(cp *compiler, fn *parse.Form) Op {
2016-01-23 08:24:47 +08:00
if len(fn.Args) == 0 {
2016-01-25 02:04:15 +08:00
cp.errorf(fn.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-01-25 02:04:15 +08:00
cp.errorf(fn.Args[0].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-01-25 02:04:15 +08:00
cp.errorf(pn.Begin(), "should be a lambda")
2016-01-23 08:24:47 +08:00
}
if len(fn.Args) > 2 {
2016-01-25 02:04:15 +08:00
cp.errorf(fn.Args[2].Begin(), "superfluous argument")
}
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 ]
func compileUse(cp *compiler, fn *parse.Form) Op {
var modname string
var filenameOp ValuesOp
var filenameBegin int
switch len(fn.Args) {
case 0:
cp.errorf(fn.Head.End(), "should be module name")
case 2:
filenameOp = cp.compound(fn.Args[1])
filenameBegin = fn.Args[1].Begin()
fallthrough
case 1:
modname = mustString(cp, fn.Args[0], "should be a literal module name")
default:
cp.errorf(fn.Args[2].Begin(), "superfluous argument")
}
return func(ec *EvalCtx) {
if _, ok := ec.Evaler.modules[modname]; ok {
// Module already loaded.
return
}
// Load the source.
var filename, source string
if filenameOp != nil {
// Filename was specified; evaluate it.
values := filenameOp(ec)
valuesMust := &muster{ec, "module filename", filenameBegin, values}
filename = string(valuesMust.mustOneStr())
var err error
source, err = readFileUTF8(filename)
maybeThrow(err)
} else {
// No filename; defaulting to $datadir/$modname.elv.
dataDir, err := store.DataDir()
maybeThrow(err)
2016-02-17 06:24:09 +08:00
filename = dataDir + "/" + modname + ".elv"
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.
2016-02-17 06:24:09 +08:00
filename = "<builtin module>"
} else {
throw(fmt.Errorf("cannot load %s: %s does not exist", modname, filename))
}
} else {
// File exists. Load it.
source, err = readFileUTF8(filename)
maybeThrow(err)
}
}
// TODO(xiaq): Should handle failures when evaluting the module
newEc := &EvalCtx{
ec.Evaler,
filename, source, "module " + modname,
Namespace{}, Namespace{},
ec.ports,
}
n, err := parse.Parse(source)
maybeThrow(err)
op, err := newEc.Compile(n)
// TODO the err originates in another source, should add appropriate information.
maybeThrow(err)
op(newEc)
ec.Evaler.modules[modname] = newEc.local
}
}