1// Package eval handles evaluation of parsed Elvish code and provides runtime 2// facilities. 3package eval 4 5import ( 6 "fmt" 7 "io" 8 "os" 9 "strconv" 10 "sync" 11 12 "src.elv.sh/pkg/daemon/daemondefs" 13 "src.elv.sh/pkg/diag" 14 "src.elv.sh/pkg/env" 15 "src.elv.sh/pkg/eval/vals" 16 "src.elv.sh/pkg/eval/vars" 17 "src.elv.sh/pkg/logutil" 18 "src.elv.sh/pkg/parse" 19 "src.elv.sh/pkg/persistent/vector" 20) 21 22var logger = logutil.GetLogger("[eval] ") 23 24const ( 25 // FnSuffix is the suffix for the variable names of functions. Defining a 26 // function "foo" is equivalent to setting a variable named "foo~", and vice 27 // versa. 28 FnSuffix = "~" 29 // NsSuffix is the suffix for the variable names of namespaces. Defining a 30 // namespace foo is equivalent to setting a variable named "foo:", and vice 31 // versa. 32 NsSuffix = ":" 33) 34 35const ( 36 defaultValuePrefix = "▶ " 37 defaultNotifyBgJobSuccess = true 38 initIndent = vals.NoPretty 39) 40 41// Evaler provides methods for evaluating code, and maintains state that is 42// persisted between evaluation of different pieces of code. An Evaler is safe 43// to use concurrently. 44type Evaler struct { 45 // The following fields must only be set before the Evaler is used to 46 // evaluate any code; mutating them afterwards may cause race conditions. 47 48 // Command-line arguments, exposed as $args. 49 Args vals.List 50 // Hooks to run before exit or exec. 51 BeforeExit []func() 52 // Chdir hooks, exposed indirectly as $before-chdir and $after-chdir. 53 BeforeChdir, AfterChdir []func(string) 54 // TODO: Remove after the dir-history command is removed. 55 DaemonClient daemondefs.Client 56 // Directories to search libraries. 57 LibDirs []string 58 // Source code of internal bundled modules indexed by use specs. 59 BundledModules map[string]string 60 // Callback to notify the success or failure of background jobs. Must not be 61 // mutated once the Evaler is used to evaluate any code. 62 BgJobNotify func(string) 63 64 mu sync.RWMutex 65 // Mutations to fields below must be guarded by mutex. 66 // 67 // Note that this is *not* a GIL; most state mutations when executing Elvish 68 // code is localized and do not need to hold this mutex. 69 // 70 // TODO: Actually guard all mutations by this mutex. 71 72 global, builtin *Ns 73 74 deprecations deprecationRegistry 75 76 // Internal modules are indexed by use specs. External modules are indexed by 77 // absolute paths. 78 modules map[string]*Ns 79 80 // Various states and configs exposed to Elvish code. 81 // 82 // The prefix to prepend to value outputs when writing them to terminal, 83 // exposed as $value-out-prefix. 84 valuePrefix string 85 // Whether to notify the success of background jobs, exposed as 86 // $notify-bg-job-sucess. 87 notifyBgJobSuccess bool 88 // The current number of background jobs, exposed as $num-bg-jobs. 89 numBgJobs int 90} 91 92//elvdoc:var after-chdir 93// 94// A list of functions to run after changing directory. These functions are always 95// called with directory to change it, which might be a relative path. The 96// following example also shows `$before-chdir`: 97// 98// ```elvish-transcript 99// ~> before-chdir = [{|dir| echo "Going to change to "$dir", pwd is "$pwd }] 100// ~> after-chdir = [{|dir| echo "Changed to "$dir", pwd is "$pwd }] 101// ~> cd /usr 102// Going to change to /usr, pwd is /Users/xiaq 103// Changed to /usr, pwd is /usr 104// /usr> cd local 105// Going to change to local, pwd is /usr 106// Changed to local, pwd is /usr/local 107// /usr/local> 108// ``` 109// 110// @cf before-chdir 111 112//elvdoc:var before-chdir 113// 114// A list of functions to run before changing directory. These functions are always 115// called with the new working directory. 116// 117// @cf after-chdir 118 119//elvdoc:var num-bg-jobs 120// 121// Number of background jobs. 122 123//elvdoc:var notify-bg-job-success 124// 125// Whether to notify success of background jobs, defaulting to `$true`. 126// 127// Failures of background jobs are always notified. 128 129//elvdoc:var value-out-indicator 130// 131// A string put before value outputs (such as those of of `put`). Defaults to 132// `'▶ '`. Example: 133// 134// ```elvish-transcript 135// ~> put lorem ipsum 136// ▶ lorem 137// ▶ ipsum 138// ~> value-out-indicator = 'val> ' 139// ~> put lorem ipsum 140// val> lorem 141// val> ipsum 142// ``` 143// 144// Note that you almost always want some trailing whitespace for readability. 145 146// NewEvaler creates a new Evaler. 147func NewEvaler() *Evaler { 148 builtin := builtinNs.Ns() 149 beforeChdirElvish, afterChdirElvish := vector.Empty, vector.Empty 150 151 ev := &Evaler{ 152 global: new(Ns), 153 builtin: builtin, 154 155 deprecations: newDeprecationRegistry(), 156 157 modules: make(map[string]*Ns), 158 BundledModules: make(map[string]string), 159 160 valuePrefix: defaultValuePrefix, 161 notifyBgJobSuccess: defaultNotifyBgJobSuccess, 162 numBgJobs: 0, 163 Args: vals.EmptyList, 164 } 165 166 ev.BeforeChdir = []func(string){ 167 adaptChdirHook("before-chdir", ev, &beforeChdirElvish)} 168 ev.AfterChdir = []func(string){ 169 adaptChdirHook("after-chdir", ev, &afterChdirElvish)} 170 171 ev.ExtendBuiltin(BuildNs(). 172 AddVar("pwd", NewPwdVar(ev)). 173 AddVar("before-chdir", vars.FromPtr(&beforeChdirElvish)). 174 AddVar("after-chdir", vars.FromPtr(&afterChdirElvish)). 175 AddVar("value-out-indicator", 176 vars.FromPtrWithMutex(&ev.valuePrefix, &ev.mu)). 177 AddVar("notify-bg-job-success", 178 vars.FromPtrWithMutex(&ev.notifyBgJobSuccess, &ev.mu)). 179 AddVar("num-bg-jobs", 180 vars.FromGet(func() interface{} { return strconv.Itoa(ev.getNumBgJobs()) })). 181 AddVar("args", vars.FromGet(func() interface{} { return ev.Args }))) 182 183 // Install the "builtin" module after extension is complete. 184 ev.modules["builtin"] = ev.builtin 185 186 return ev 187} 188 189func adaptChdirHook(name string, ev *Evaler, pfns *vector.Vector) func(string) { 190 return func(path string) { 191 ports, cleanup := PortsFromStdFiles(ev.ValuePrefix()) 192 defer cleanup() 193 callCfg := CallCfg{Args: []interface{}{path}, From: "[hook " + name + "]"} 194 evalCfg := EvalCfg{Ports: ports[:]} 195 for it := (*pfns).Iterator(); it.HasElem(); it.Next() { 196 fn, ok := it.Elem().(Callable) 197 if !ok { 198 fmt.Fprintln(os.Stderr, name, "hook must be callable") 199 continue 200 } 201 err := ev.Call(fn, callCfg, evalCfg) 202 if err != nil { 203 // TODO: Stack trace 204 fmt.Fprintln(os.Stderr, err) 205 } 206 } 207 } 208} 209 210// Access methods. 211 212// Global returns the global Ns. 213func (ev *Evaler) Global() *Ns { 214 ev.mu.RLock() 215 defer ev.mu.RUnlock() 216 return ev.global 217} 218 219// ExtendGlobal extends the global namespace with the given namespace. 220func (ev *Evaler) ExtendGlobal(ns Nser) { 221 ev.mu.Lock() 222 defer ev.mu.Unlock() 223 ev.global = CombineNs(ev.global, ns.Ns()) 224} 225 226// Builtin returns the builtin Ns. 227func (ev *Evaler) Builtin() *Ns { 228 ev.mu.RLock() 229 defer ev.mu.RUnlock() 230 return ev.builtin 231} 232 233// ExtendBuiltin extends the builtin namespace with the given namespace. 234func (ev *Evaler) ExtendBuiltin(ns Nser) { 235 ev.mu.Lock() 236 defer ev.mu.Unlock() 237 ev.builtin = CombineNs(ev.builtin, ns.Ns()) 238} 239 240func (ev *Evaler) registerDeprecation(d deprecation) bool { 241 ev.mu.Lock() 242 defer ev.mu.Unlock() 243 return ev.deprecations.register(d) 244} 245 246// AddModule add an internal module so that it can be used with "use $name" from 247// script. 248func (ev *Evaler) AddModule(name string, mod *Ns) { 249 ev.mu.Lock() 250 defer ev.mu.Unlock() 251 ev.modules[name] = mod 252} 253 254// ValuePrefix returns the prefix to prepend to value outputs when writing them 255// to terminal. 256func (ev *Evaler) ValuePrefix() string { 257 ev.mu.RLock() 258 defer ev.mu.RUnlock() 259 return ev.valuePrefix 260} 261 262func (ev *Evaler) getNotifyBgJobSuccess() bool { 263 ev.mu.RLock() 264 defer ev.mu.RUnlock() 265 return ev.notifyBgJobSuccess 266} 267 268func (ev *Evaler) getNumBgJobs() int { 269 ev.mu.RLock() 270 defer ev.mu.RUnlock() 271 return ev.numBgJobs 272} 273 274func (ev *Evaler) addNumBgJobs(delta int) { 275 ev.mu.Lock() 276 defer ev.mu.Unlock() 277 ev.numBgJobs += delta 278} 279 280// SetArgs sets the value of the $args variable to a list of strings, built from 281// the given slice. This method must be called before the Evaler is used to 282// evaluate any code. 283func (ev *Evaler) SetArgs(args []string) { 284 ev.Args = listOfStrings(args) 285} 286 287// AddBeforeChdir adds a function to run before changing directory. This method 288// must be called before the Evaler is used to evaluate any code. 289func (ev *Evaler) AddBeforeChdir(f func(string)) { 290 ev.BeforeChdir = append(ev.BeforeChdir, f) 291} 292 293// AddAfterChdir adds a function to run after changing directory. This method 294// must be called before the Evaler is used to evaluate any code. 295func (ev *Evaler) AddAfterChdir(f func(string)) { 296 ev.AfterChdir = append(ev.AfterChdir, f) 297} 298 299// AddBeforeExit adds a function to run before the Elvish process exits or gets 300// replaced (via "exec" on UNIX). This method must be called before the Evaler 301// is used to evaluate any code. 302func (ev *Evaler) AddBeforeExit(f func()) { 303 ev.BeforeExit = append(ev.BeforeExit, f) 304} 305 306// Chdir changes the current directory, and updates $E:PWD on success 307// 308// It runs the functions in beforeChdir immediately before changing the 309// directory, and the functions in afterChdir immediately after (if chdir was 310// successful). It returns nil as long as the directory changing part succeeds. 311func (ev *Evaler) Chdir(path string) error { 312 for _, hook := range ev.BeforeChdir { 313 hook(path) 314 } 315 316 err := os.Chdir(path) 317 if err != nil { 318 return err 319 } 320 321 for _, hook := range ev.AfterChdir { 322 hook(path) 323 } 324 325 pwd, err := os.Getwd() 326 if err != nil { 327 logger.Println("getwd after cd:", err) 328 return nil 329 } 330 os.Setenv(env.PWD, pwd) 331 332 return nil 333} 334 335// EvalCfg keeps configuration for the (*Evaler).Eval method. 336type EvalCfg struct { 337 // Ports to use in evaluation. The first 3 elements, if not specified 338 // (either being nil or Ports containing fewer than 3 elements), 339 // will be filled with DummyInputPort, DummyOutputPort and 340 // DummyOutputPort respectively. 341 Ports []*Port 342 // Callback to get a channel of interrupt signals and a function to call 343 // when the channel is no longer needed. 344 Interrupt func() (<-chan struct{}, func()) 345 // Whether the Eval method should try to put the Elvish in the foreground 346 // after the code is executed. 347 PutInFg bool 348 // If not nil, used the given global namespace, instead of Evaler's own. 349 Global *Ns 350} 351 352func (cfg *EvalCfg) fillDefaults() { 353 if len(cfg.Ports) < 3 { 354 cfg.Ports = append(cfg.Ports, make([]*Port, 3-len(cfg.Ports))...) 355 } 356 if cfg.Ports[0] == nil { 357 cfg.Ports[0] = DummyInputPort 358 } 359 if cfg.Ports[1] == nil { 360 cfg.Ports[1] = DummyOutputPort 361 } 362 if cfg.Ports[2] == nil { 363 cfg.Ports[2] = DummyOutputPort 364 } 365} 366 367// Eval evaluates a piece of source code with the given configuration. The 368// returned error may be a parse error, compilation error or exception. 369func (ev *Evaler) Eval(src parse.Source, cfg EvalCfg) error { 370 cfg.fillDefaults() 371 errFile := cfg.Ports[2].File 372 373 tree, err := parse.Parse(src, parse.Config{WarningWriter: errFile}) 374 if err != nil { 375 return err 376 } 377 378 ev.mu.Lock() 379 b := ev.builtin 380 defaultGlobal := cfg.Global == nil 381 if defaultGlobal { 382 // If cfg.Global is nil, use the Evaler's default global, and also 383 // mutate the default global. 384 cfg.Global = ev.global 385 // Continue to hold the mutex; it will be released when ev.global gets 386 // mutated. 387 } else { 388 ev.mu.Unlock() 389 } 390 391 op, err := compile(b.static(), cfg.Global.static(), tree, errFile) 392 if err != nil { 393 if defaultGlobal { 394 ev.mu.Unlock() 395 } 396 return err 397 } 398 399 fm, cleanup := ev.prepareFrame(src, cfg) 400 defer cleanup() 401 402 newLocal, exec := op.prepare(fm) 403 if defaultGlobal { 404 ev.global = newLocal 405 ev.mu.Unlock() 406 } 407 408 return exec() 409} 410 411// CallCfg keeps configuration for the (*Evaler).Call method. 412type CallCfg struct { 413 // Arguments to pass to the the function. 414 Args []interface{} 415 // Options to pass to the function. 416 Opts map[string]interface{} 417 // The name of the internal source that is calling the function. 418 From string 419} 420 421func (cfg *CallCfg) fillDefaults() { 422 if cfg.Opts == nil { 423 cfg.Opts = NoOpts 424 } 425 if cfg.From == "" { 426 cfg.From = "[internal]" 427 } 428} 429 430// Call calls a given function. 431func (ev *Evaler) Call(f Callable, callCfg CallCfg, evalCfg EvalCfg) error { 432 callCfg.fillDefaults() 433 evalCfg.fillDefaults() 434 if evalCfg.Global == nil { 435 evalCfg.Global = ev.Global() 436 } 437 fm, cleanup := ev.prepareFrame(parse.Source{Name: callCfg.From}, evalCfg) 438 defer cleanup() 439 return f.Call(fm, callCfg.Args, callCfg.Opts) 440} 441 442func (ev *Evaler) prepareFrame(src parse.Source, cfg EvalCfg) (*Frame, func()) { 443 var intCh <-chan struct{} 444 var intChCleanup func() 445 if cfg.Interrupt != nil { 446 intCh, intChCleanup = cfg.Interrupt() 447 } 448 449 ports := fillDefaultDummyPorts(cfg.Ports) 450 451 fm := &Frame{ev, src, cfg.Global, new(Ns), intCh, ports, nil, false} 452 return fm, func() { 453 if intChCleanup != nil { 454 intChCleanup() 455 } 456 if cfg.PutInFg { 457 err := putSelfInFg() 458 if err != nil { 459 fmt.Fprintln(ports[2].File, 460 "failed to put myself in foreground:", err) 461 } 462 } 463 } 464} 465 466func fillDefaultDummyPorts(ports []*Port) []*Port { 467 growPorts(&ports, 3) 468 if ports[0] == nil { 469 ports[0] = DummyInputPort 470 } 471 if ports[1] == nil { 472 ports[1] = DummyOutputPort 473 } 474 if ports[2] == nil { 475 ports[2] = DummyOutputPort 476 } 477 return ports 478} 479 480// Check checks the given source code for any parse error and compilation error. 481// It always tries to compile the code even if there is a parse error; both 482// return values may be non-nil. If w is not nil, deprecation messages are 483// written to it. 484func (ev *Evaler) Check(src parse.Source, w io.Writer) (*parse.Error, *diag.Error) { 485 tree, parseErr := parse.Parse(src, parse.Config{WarningWriter: w}) 486 return parse.GetError(parseErr), ev.CheckTree(tree, w) 487} 488 489// CheckTree checks the given parsed source tree for compilation errors. If w is 490// not nil, deprecation messages are written to it. 491func (ev *Evaler) CheckTree(tree parse.Tree, w io.Writer) *diag.Error { 492 _, compileErr := ev.compile(tree, ev.Global(), w) 493 return GetCompilationError(compileErr) 494} 495 496// Compiles a parsed tree. 497func (ev *Evaler) compile(tree parse.Tree, g *Ns, w io.Writer) (nsOp, error) { 498 return compile(ev.Builtin().static(), g.static(), tree, w) 499} 500