github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/eval/builtin_special.go (about)

     1  package eval
     2  
     3  //go:generate ./builtin_modules.bash
     4  
     5  // Builtin special forms.
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  
    11  	"github.com/elves/elvish/parse"
    12  	"github.com/elves/elvish/store"
    13  )
    14  
    15  type compileBuiltin func(*compiler, *parse.Form) OpFunc
    16  
    17  var builtinSpecials map[string]compileBuiltin
    18  
    19  // BuiltinSpecialNames contains all names of builtin special forms. It is
    20  // useful for the syntax highlighter.
    21  var BuiltinSpecialNames []string
    22  
    23  func init() {
    24  	// Needed to avoid initialization loop
    25  	builtinSpecials = map[string]compileBuiltin{
    26  		"del": compileDel,
    27  		"fn":  compileFn,
    28  		"use": compileUse,
    29  	}
    30  	for k := range builtinSpecials {
    31  		BuiltinSpecialNames = append(BuiltinSpecialNames, k)
    32  	}
    33  }
    34  
    35  // DelForm = 'del' { VariablePrimary }
    36  func compileDel(cp *compiler, fn *parse.Form) OpFunc {
    37  	// Do conventional compiling of all compound expressions, including
    38  	// ensuring that variables can be resolved
    39  	var names, envNames []string
    40  	for _, cn := range fn.Args {
    41  		cp.compiling(cn)
    42  		qname := mustString(cp, cn, "should be a literal variable name")
    43  		splice, ns, name := ParseVariable(qname)
    44  		if splice {
    45  			cp.errorf("removing spliced variable makes no sense")
    46  		}
    47  		switch ns {
    48  		case "", "local":
    49  			if !cp.thisScope()[name] {
    50  				cp.errorf("variable $%s not found on current local scope", name)
    51  			}
    52  			delete(cp.thisScope(), name)
    53  			names = append(names, name)
    54  		case "env":
    55  			envNames = append(envNames, name)
    56  		default:
    57  			cp.errorf("can only delete a variable in local: or env:")
    58  		}
    59  
    60  	}
    61  	return func(ec *EvalCtx) {
    62  		for _, name := range names {
    63  			delete(ec.local, name)
    64  		}
    65  		for _, name := range envNames {
    66  			// BUG(xiaq): We rely on the fact that os.Unsetenv always returns
    67  			// nil.
    68  			os.Unsetenv(name)
    69  		}
    70  	}
    71  }
    72  
    73  // makeFnOp wraps an op such that a return is converted to an ok.
    74  func makeFnOp(op Op) Op {
    75  	return Op{func(ec *EvalCtx) {
    76  		err := ec.PEval(op)
    77  		if err != nil && err != Return {
    78  			// rethrow
    79  			throw(err)
    80  		}
    81  	}, op.Begin, op.End}
    82  }
    83  
    84  // FnForm = 'fn' StringPrimary LambdaPrimary
    85  //
    86  // fn f []{foobar} is a shorthand for set '&'f = []{foobar}.
    87  func compileFn(cp *compiler, fn *parse.Form) OpFunc {
    88  	if len(fn.Args) == 0 {
    89  		end := fn.End()
    90  		cp.errorpf(end, end, "should be followed by function name")
    91  	}
    92  	fnName := mustString(cp, fn.Args[0], "must be a literal string")
    93  	varName := FnPrefix + fnName
    94  
    95  	if len(fn.Args) == 1 {
    96  		end := fn.Args[0].End()
    97  		cp.errorpf(end, end, "should be followed by a lambda")
    98  	}
    99  	pn := mustPrimary(cp, fn.Args[1], "should be a lambda")
   100  	if pn.Type != parse.Lambda {
   101  		cp.compiling(pn)
   102  		cp.errorf("should be a lambda")
   103  	}
   104  	if len(fn.Args) > 2 {
   105  		cp.errorpf(fn.Args[2].Begin(), fn.Args[len(fn.Args)-1].End(), "superfluous argument(s)")
   106  	}
   107  
   108  	cp.registerVariableSet(":" + varName)
   109  	op := cp.lambda(pn)
   110  
   111  	return func(ec *EvalCtx) {
   112  		// Initialize the function variable with the builtin nop
   113  		// function. This step allows the definition of recursive
   114  		// functions; the actual function will never be called.
   115  		ec.local[varName] = NewPtrVariable(&BuiltinFn{"<shouldn't be called>", nop})
   116  		closure := op(ec)[0].(*Closure)
   117  		closure.Op = makeFnOp(closure.Op)
   118  		ec.local[varName].Set(closure)
   119  	}
   120  }
   121  
   122  // UseForm = 'use' StringPrimary [ Compound ]
   123  func compileUse(cp *compiler, fn *parse.Form) OpFunc {
   124  	var modname string
   125  	var filenameOp ValuesOp
   126  	var filenameBegin, filenameEnd int
   127  
   128  	switch len(fn.Args) {
   129  	case 0:
   130  		end := fn.Head.End()
   131  		cp.errorpf(end, end, "lack module name")
   132  	case 2:
   133  		filenameOp = cp.compoundOp(fn.Args[1])
   134  		filenameBegin = fn.Args[1].Begin()
   135  		filenameEnd = fn.Args[1].End()
   136  		fallthrough
   137  	case 1:
   138  		modname = mustString(cp, fn.Args[0], "should be a literal module name")
   139  	default:
   140  		cp.errorpf(fn.Args[2].Begin(), fn.Args[len(fn.Args)-1].End(), "superfluous argument(s)")
   141  	}
   142  
   143  	return func(ec *EvalCtx) {
   144  		if filenameOp.Func != nil {
   145  			values := filenameOp.Exec(ec)
   146  			valuesMust := &muster{ec, "module filename", filenameBegin, filenameEnd, values}
   147  			filename := string(valuesMust.mustOneStr())
   148  			use(ec, modname, &filename)
   149  		} else {
   150  			use(ec, modname, nil)
   151  		}
   152  	}
   153  }
   154  
   155  func use(ec *EvalCtx, modname string, pfilename *string) {
   156  	if _, ok := ec.Evaler.Modules[modname]; ok {
   157  		// Module already loaded.
   158  		return
   159  	}
   160  
   161  	// Load the source.
   162  	var filename, source string
   163  
   164  	if pfilename != nil {
   165  		filename = *pfilename
   166  		var err error
   167  		source, err = readFileUTF8(filename)
   168  		maybeThrow(err)
   169  	} else {
   170  		// No filename; defaulting to $datadir/$modname.elv.
   171  		dataDir, err := store.DataDir()
   172  		maybeThrow(err)
   173  		filename = dataDir + "/" + modname + ".elv"
   174  		if _, err := os.Stat(filename); os.IsNotExist(err) {
   175  			// File does not exist. Try loading from the table of builtin
   176  			// modules.
   177  			var ok bool
   178  			if source, ok = builtinModules[modname]; ok {
   179  				// Source is loaded. Do nothing more.
   180  				filename = "<builtin module>"
   181  			} else {
   182  				throw(fmt.Errorf("cannot load %s: %s does not exist", modname, filename))
   183  			}
   184  		} else {
   185  			// File exists. Load it.
   186  			source, err = readFileUTF8(filename)
   187  			maybeThrow(err)
   188  		}
   189  	}
   190  
   191  	// TODO(xiaq): Should handle failures when evaluting the module
   192  	newEc := &EvalCtx{
   193  		ec.Evaler,
   194  		filename, source, "module " + modname,
   195  		Namespace{}, Namespace{},
   196  		ec.ports, nil, true,
   197  		0, len(source),
   198  	}
   199  
   200  	n, err := parse.Parse(source)
   201  	maybeThrow(err)
   202  
   203  	op, err := newEc.Compile(n)
   204  	// TODO the err originates in another source, should add appropriate information.
   205  	maybeThrow(err)
   206  
   207  	op.Exec(newEc)
   208  
   209  	ec.Evaler.Modules[modname] = newEc.local
   210  }