Implement the use builtin.

This addresses #135.
This commit is contained in:
Qi Xiao 2016-02-16 23:14:53 +01:00
parent ef1112d53f
commit c37a51c2ed
5 changed files with 121 additions and 88 deletions

3
eval/acme.elv Normal file
View File

@ -0,0 +1,3 @@
fn acme {
echo 'So this works.'
}

18
eval/builtin_modules.bash Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
out=./builtin_modules.go
{
echo "package eval"
echo "var builtinModules = map[string]string{"
for f in *.elv; do
echo -n "\"${f%.elv}\": \`"
cat $f | sed 's/`/``/g'
echo '`,'
done
echo "}"
} > $out
gofmt -w $out

8
eval/builtin_modules.go Normal file
View File

@ -0,0 +1,8 @@
package eval
var builtinModules = map[string]string{
"acme": `fn acme {
echo 'So this works.'
}
`,
}

View File

@ -1,11 +1,15 @@
package eval
//go:genearte ./builtin_modules.bash
// Builtin special forms.
import (
"fmt"
"os"
"github.com/elves/elvish/parse"
"github.com/elves/elvish/store"
)
type compileBuiltin func(*compiler, *parse.Form) Op
@ -21,7 +25,7 @@ func init() {
builtinSpecials = map[string]compileBuiltin{
"del": compileDel,
"fn": compileFn,
//"use": compileUse,
"use": compileUse,
}
for k := range builtinSpecials {
BuiltinSpecialNames = append(BuiltinSpecialNames, k)
@ -78,91 +82,6 @@ func compileDel(cp *compiler, fn *parse.Form) Op {
}
}
/*
func stem(fname string) string {
base := path.Base(fname)
ext := path.Ext(base)
return base[0 : len(base)-len(ext)]
}
// UseForm = 'use' StringPrimary.modname Primary.fname
// = 'use' StringPrimary.fname
func compileUse(cp *compiler, fn *parse.Form) op {
var fnameNode *parse.Compound
var fname, modname string
switch len(fn.Args.Nodes) {
case 0:
cp.errorf(fn.Args.Pos, "expect module name or file name")
case 1, 2:
fnameNode = fn.Args.Nodes[0]
_, fname = ensureStringPrimary(cp, fnameNode, "expect string literal")
if len(fn.Args.Nodes) == 2 {
modnameNode := fn.Args.Nodes[1]
_, modname = ensureStringPrimary(
cp, modnameNode, "expect string literal")
if modname == "" {
cp.errorf(modnameNode.Pos, "module name is empty")
}
} else {
modname = stem(fname)
if modname == "" {
cp.errorf(fnameNode.Pos, "stem of file name is empty")
}
}
default:
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(cp.dir, fname))
default:
// File name relative to data dir
fname = path.Clean(path.Join(cp.dataDir, fname))
}
src, err := readFileUTF8(fname)
if err != nil {
cp.errorf(fnameNode.Pos, "cannot read module: %s", err.Error())
}
cn, err := parse.Parse(fname, src)
if err != nil {
// TODO(xiaq): Pretty print
cp.errorf(fnameNode.Pos, "cannot parse module: %s", err.Error())
}
newCc := &compiler{
cp.Compiler,
fname, src, path.Dir(fname),
[]staticNS{staticNS{}}, staticNS{},
}
op, err := newCc.compile(cn)
if err != nil {
// TODO(xiaq): Pretty print
cp.errorf(fnameNode.Pos, "cannot compile module: %s", err.Error())
}
cp.mod[modname] = newCc.scopes[0]
return func(ec *evalCtx) exitus {
// TODO(xiaq): Should handle failures when evaluting the module
newEc := &evalCtx{
ec.Evaler,
fname, src, "module " + modname,
ns{}, ns{},
ec.ports,
}
op.f(newEc)
ec.mod[modname] = newEc.local
return ok
}
}
*/
// makeFnOp wraps an op such that a return is converted to an ok.
func makeFnOp(op Op) Op {
return func(ec *EvalCtx) {
@ -208,3 +127,81 @@ func compileFn(cp *compiler, fn *parse.Form) 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)
filename = dataDir + modname + ".elv"
if _, err := os.Stat(filename); os.IsNotExist(err) {
// File does not exist. Try loading from the table of builtin
// modules.
filename = "<builtin module>"
var ok bool
if source, ok = builtinModules[modname]; ok {
// Source is loaded. Do nothing more.
} 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(filename, source, n)
maybeThrow(err)
op(newEc)
ec.Evaler.modules[modname] = newEc.local
}
}

View File

@ -15,10 +15,17 @@ var ErrEmptyHOME = errors.New("environment variable HOME is empty")
// necessary. It returns the path to the data directory (never with a
// trailing slash) and possible error.
func EnsureDataDir() (string, error) {
ddir, err := DataDir()
if err != nil {
return "", err
}
return ddir, os.MkdirAll(ddir, 0700)
}
func DataDir() (string, error) {
home, err := util.GetHome("")
if err != nil {
return "", err
}
ddir := home + "/.elvish"
return ddir, os.MkdirAll(ddir, 0700)
return home + "/.elvish", nil
}