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 }