eval.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. // Package eval handles evaluation of parsed Elvish code and provides runtime
  2. // facilities.
  3. package eval
  4. import (
  5. "fmt"
  6. "io"
  7. "os"
  8. "strconv"
  9. "sync"
  10. "src.elv.sh/pkg/diag"
  11. "src.elv.sh/pkg/env"
  12. "src.elv.sh/pkg/eval/vals"
  13. "src.elv.sh/pkg/eval/vars"
  14. "src.elv.sh/pkg/logutil"
  15. "src.elv.sh/pkg/parse"
  16. )
  17. var logger = logutil.GetLogger("[eval] ")
  18. const (
  19. // FnSuffix is the suffix for the variable names of functions. Defining a
  20. // function "foo" is equivalent to setting a variable named "foo~", and vice
  21. // versa.
  22. FnSuffix = "~"
  23. // NsSuffix is the suffix for the variable names of namespaces. Defining a
  24. // namespace foo is equivalent to setting a variable named "foo:", and vice
  25. // versa.
  26. NsSuffix = ":"
  27. )
  28. const (
  29. defaultValuePrefix = "▶ "
  30. defaultNotifyBgJobSuccess = true
  31. )
  32. // Evaler provides methods for evaluating code, and maintains state that is
  33. // persisted between evaluation of different pieces of code. An Evaler is safe
  34. // to use concurrently.
  35. type Evaler struct {
  36. // The following fields must only be set before the Evaler is used to
  37. // evaluate any code; mutating them afterwards may cause race conditions.
  38. // Command-line arguments, exposed as $args.
  39. Args vals.List
  40. // Hooks to run before exit or exec.
  41. PreExitHooks []func()
  42. // Chdir hooks, exposed indirectly as $before-chdir and $after-chdir.
  43. BeforeChdir, AfterChdir []func(string)
  44. // Directories to search libraries.
  45. LibDirs []string
  46. // Source code of internal bundled modules indexed by use specs.
  47. BundledModules map[string]string
  48. // Callback to notify the success or failure of background jobs. Must not be
  49. // mutated once the Evaler is used to evaluate any code.
  50. BgJobNotify func(string)
  51. // Path to the rc file, and path to the rc file actually evaluated. These
  52. // are not used by the Evaler itself right now; they are here so that they
  53. // can be exposed to the runtime: module.
  54. RcPath, EffectiveRcPath string
  55. mu sync.RWMutex
  56. // Mutations to fields below must be guarded by mutex.
  57. //
  58. // Note that this is *not* a GIL; most state mutations when executing Elvish
  59. // code is localized and do not need to hold this mutex.
  60. //
  61. // TODO: Actually guard all mutations by this mutex.
  62. global, builtin *Ns
  63. deprecations deprecationRegistry
  64. // Internal modules are indexed by use specs. External modules are indexed by
  65. // absolute paths.
  66. modules map[string]*Ns
  67. // Various states and configs exposed to Elvish code.
  68. //
  69. // The prefix to prepend to value outputs when writing them to terminal,
  70. // exposed as $value-out-prefix.
  71. valuePrefix string
  72. // Whether to notify the success of background jobs, exposed as
  73. // $notify-bg-job-sucess.
  74. notifyBgJobSuccess bool
  75. // The current number of background jobs, exposed as $num-bg-jobs.
  76. numBgJobs int
  77. }
  78. // NewEvaler creates a new Evaler.
  79. func NewEvaler() *Evaler {
  80. builtin := builtinNs.Ns()
  81. beforeChdirElvish, afterChdirElvish := vals.EmptyList, vals.EmptyList
  82. ev := &Evaler{
  83. global: new(Ns),
  84. builtin: builtin,
  85. deprecations: newDeprecationRegistry(),
  86. modules: make(map[string]*Ns),
  87. BundledModules: make(map[string]string),
  88. valuePrefix: defaultValuePrefix,
  89. notifyBgJobSuccess: defaultNotifyBgJobSuccess,
  90. numBgJobs: 0,
  91. Args: vals.EmptyList,
  92. }
  93. ev.BeforeChdir = []func(string){
  94. adaptChdirHook("before-chdir", ev, &beforeChdirElvish)}
  95. ev.AfterChdir = []func(string){
  96. adaptChdirHook("after-chdir", ev, &afterChdirElvish)}
  97. ev.ExtendBuiltin(BuildNs().
  98. AddVar("pwd", NewPwdVar(ev)).
  99. AddVar("before-chdir", vars.FromPtr(&beforeChdirElvish)).
  100. AddVar("after-chdir", vars.FromPtr(&afterChdirElvish)).
  101. AddVar("value-out-indicator",
  102. vars.FromPtrWithMutex(&ev.valuePrefix, &ev.mu)).
  103. AddVar("notify-bg-job-success",
  104. vars.FromPtrWithMutex(&ev.notifyBgJobSuccess, &ev.mu)).
  105. AddVar("num-bg-jobs",
  106. vars.FromGet(func() any { return strconv.Itoa(ev.getNumBgJobs()) })).
  107. AddVar("args", vars.FromGet(func() any { return ev.Args })))
  108. // Install the "builtin" module after extension is complete.
  109. ev.modules["builtin"] = ev.builtin
  110. return ev
  111. }
  112. func adaptChdirHook(name string, ev *Evaler, pfns *vals.List) func(string) {
  113. return func(path string) {
  114. ports, cleanup := PortsFromStdFiles(ev.ValuePrefix())
  115. defer cleanup()
  116. callCfg := CallCfg{Args: []any{path}, From: "[hook " + name + "]"}
  117. evalCfg := EvalCfg{Ports: ports[:]}
  118. for it := (*pfns).Iterator(); it.HasElem(); it.Next() {
  119. fn, ok := it.Elem().(Callable)
  120. if !ok {
  121. fmt.Fprintln(os.Stderr, name, "hook must be callable")
  122. continue
  123. }
  124. err := ev.Call(fn, callCfg, evalCfg)
  125. if err != nil {
  126. // TODO: Stack trace
  127. fmt.Fprintln(os.Stderr, err)
  128. }
  129. }
  130. }
  131. }
  132. // PreExit runs all pre-exit hooks.
  133. func (ev *Evaler) PreExit() {
  134. for _, hook := range ev.PreExitHooks {
  135. hook()
  136. }
  137. }
  138. // Access methods.
  139. // Global returns the global Ns.
  140. func (ev *Evaler) Global() *Ns {
  141. ev.mu.RLock()
  142. defer ev.mu.RUnlock()
  143. return ev.global
  144. }
  145. // ExtendGlobal extends the global namespace with the given namespace.
  146. func (ev *Evaler) ExtendGlobal(ns Nser) {
  147. ev.mu.Lock()
  148. defer ev.mu.Unlock()
  149. ev.global = CombineNs(ev.global, ns.Ns())
  150. }
  151. // Builtin returns the builtin Ns.
  152. func (ev *Evaler) Builtin() *Ns {
  153. ev.mu.RLock()
  154. defer ev.mu.RUnlock()
  155. return ev.builtin
  156. }
  157. // ExtendBuiltin extends the builtin namespace with the given namespace.
  158. func (ev *Evaler) ExtendBuiltin(ns Nser) {
  159. ev.mu.Lock()
  160. defer ev.mu.Unlock()
  161. ev.builtin = CombineNs(ev.builtin, ns.Ns())
  162. }
  163. // ReplaceBuiltin replaces the builtin namespace. It should only be used in
  164. // tests.
  165. func (ev *Evaler) ReplaceBuiltin(ns *Ns) {
  166. ev.mu.Lock()
  167. defer ev.mu.Unlock()
  168. ev.builtin = ns
  169. }
  170. func (ev *Evaler) registerDeprecation(d deprecation) bool {
  171. ev.mu.Lock()
  172. defer ev.mu.Unlock()
  173. return ev.deprecations.register(d)
  174. }
  175. // AddModule add an internal module so that it can be used with "use $name" from
  176. // script.
  177. func (ev *Evaler) AddModule(name string, mod *Ns) {
  178. ev.mu.Lock()
  179. defer ev.mu.Unlock()
  180. ev.modules[name] = mod
  181. }
  182. // ValuePrefix returns the prefix to prepend to value outputs when writing them
  183. // to terminal.
  184. func (ev *Evaler) ValuePrefix() string {
  185. ev.mu.RLock()
  186. defer ev.mu.RUnlock()
  187. return ev.valuePrefix
  188. }
  189. func (ev *Evaler) getNotifyBgJobSuccess() bool {
  190. ev.mu.RLock()
  191. defer ev.mu.RUnlock()
  192. return ev.notifyBgJobSuccess
  193. }
  194. func (ev *Evaler) getNumBgJobs() int {
  195. ev.mu.RLock()
  196. defer ev.mu.RUnlock()
  197. return ev.numBgJobs
  198. }
  199. func (ev *Evaler) addNumBgJobs(delta int) {
  200. ev.mu.Lock()
  201. defer ev.mu.Unlock()
  202. ev.numBgJobs += delta
  203. }
  204. // Chdir changes the current directory, and updates $E:PWD on success
  205. //
  206. // It runs the functions in beforeChdir immediately before changing the
  207. // directory, and the functions in afterChdir immediately after (if chdir was
  208. // successful). It returns nil as long as the directory changing part succeeds.
  209. func (ev *Evaler) Chdir(path string) error {
  210. for _, hook := range ev.BeforeChdir {
  211. hook(path)
  212. }
  213. err := os.Chdir(path)
  214. if err != nil {
  215. return err
  216. }
  217. for _, hook := range ev.AfterChdir {
  218. hook(path)
  219. }
  220. pwd, err := os.Getwd()
  221. if err != nil {
  222. logger.Println("getwd after cd:", err)
  223. return nil
  224. }
  225. os.Setenv(env.PWD, pwd)
  226. return nil
  227. }
  228. // EvalCfg keeps configuration for the (*Evaler).Eval method.
  229. type EvalCfg struct {
  230. // Ports to use in evaluation. The first 3 elements, if not specified
  231. // (either being nil or Ports containing fewer than 3 elements),
  232. // will be filled with DummyInputPort, DummyOutputPort and
  233. // DummyOutputPort respectively.
  234. Ports []*Port
  235. // Callback to get a channel of interrupt signals and a function to call
  236. // when the channel is no longer needed.
  237. Interrupt func() (<-chan struct{}, func())
  238. // Whether the Eval method should try to put the Elvish in the foreground
  239. // after the code is executed.
  240. PutInFg bool
  241. // If not nil, used the given global namespace, instead of Evaler's own.
  242. Global *Ns
  243. }
  244. func (cfg *EvalCfg) fillDefaults() {
  245. if len(cfg.Ports) < 3 {
  246. cfg.Ports = append(cfg.Ports, make([]*Port, 3-len(cfg.Ports))...)
  247. }
  248. if cfg.Ports[0] == nil {
  249. cfg.Ports[0] = DummyInputPort
  250. }
  251. if cfg.Ports[1] == nil {
  252. cfg.Ports[1] = DummyOutputPort
  253. }
  254. if cfg.Ports[2] == nil {
  255. cfg.Ports[2] = DummyOutputPort
  256. }
  257. }
  258. // Eval evaluates a piece of source code with the given configuration. The
  259. // returned error may be a parse error, compilation error or exception.
  260. func (ev *Evaler) Eval(src parse.Source, cfg EvalCfg) error {
  261. cfg.fillDefaults()
  262. errFile := cfg.Ports[2].File
  263. tree, err := parse.Parse(src, parse.Config{WarningWriter: errFile})
  264. if err != nil {
  265. return err
  266. }
  267. ev.mu.Lock()
  268. b := ev.builtin
  269. defaultGlobal := cfg.Global == nil
  270. if defaultGlobal {
  271. // If cfg.Global is nil, use the Evaler's default global, and also
  272. // mutate the default global.
  273. cfg.Global = ev.global
  274. // Continue to hold the mutex; it will be released when ev.global gets
  275. // mutated.
  276. } else {
  277. ev.mu.Unlock()
  278. }
  279. op, err := compile(b.static(), cfg.Global.static(), tree, errFile)
  280. if err != nil {
  281. if defaultGlobal {
  282. ev.mu.Unlock()
  283. }
  284. return err
  285. }
  286. fm, cleanup := ev.prepareFrame(src, cfg)
  287. defer cleanup()
  288. newLocal, exec := op.prepare(fm)
  289. if defaultGlobal {
  290. ev.global = newLocal
  291. ev.mu.Unlock()
  292. }
  293. return exec()
  294. }
  295. // CallCfg keeps configuration for the (*Evaler).Call method.
  296. type CallCfg struct {
  297. // Arguments to pass to the function.
  298. Args []any
  299. // Options to pass to the function.
  300. Opts map[string]any
  301. // The name of the internal source that is calling the function.
  302. From string
  303. }
  304. func (cfg *CallCfg) fillDefaults() {
  305. if cfg.Opts == nil {
  306. cfg.Opts = NoOpts
  307. }
  308. if cfg.From == "" {
  309. cfg.From = "[internal]"
  310. }
  311. }
  312. // Call calls a given function.
  313. func (ev *Evaler) Call(f Callable, callCfg CallCfg, evalCfg EvalCfg) error {
  314. callCfg.fillDefaults()
  315. evalCfg.fillDefaults()
  316. if evalCfg.Global == nil {
  317. evalCfg.Global = ev.Global()
  318. }
  319. fm, cleanup := ev.prepareFrame(parse.Source{Name: callCfg.From}, evalCfg)
  320. defer cleanup()
  321. return f.Call(fm, callCfg.Args, callCfg.Opts)
  322. }
  323. func (ev *Evaler) prepareFrame(src parse.Source, cfg EvalCfg) (*Frame, func()) {
  324. var intCh <-chan struct{}
  325. var intChCleanup func()
  326. if cfg.Interrupt != nil {
  327. intCh, intChCleanup = cfg.Interrupt()
  328. }
  329. ports := fillDefaultDummyPorts(cfg.Ports)
  330. fm := &Frame{ev, src, cfg.Global, new(Ns), nil, intCh, ports, nil, false}
  331. return fm, func() {
  332. if intChCleanup != nil {
  333. intChCleanup()
  334. }
  335. if cfg.PutInFg {
  336. err := putSelfInFg()
  337. if err != nil {
  338. fmt.Fprintln(ports[2].File,
  339. "failed to put myself in foreground:", err)
  340. }
  341. }
  342. }
  343. }
  344. func fillDefaultDummyPorts(ports []*Port) []*Port {
  345. growPorts(&ports, 3)
  346. if ports[0] == nil {
  347. ports[0] = DummyInputPort
  348. }
  349. if ports[1] == nil {
  350. ports[1] = DummyOutputPort
  351. }
  352. if ports[2] == nil {
  353. ports[2] = DummyOutputPort
  354. }
  355. return ports
  356. }
  357. // Check checks the given source code for any parse error and compilation error.
  358. // It always tries to compile the code even if there is a parse error; both
  359. // return values may be non-nil. If w is not nil, deprecation messages are
  360. // written to it.
  361. func (ev *Evaler) Check(src parse.Source, w io.Writer) (*parse.Error, *diag.Error) {
  362. tree, parseErr := parse.Parse(src, parse.Config{WarningWriter: w})
  363. return parse.GetError(parseErr), ev.CheckTree(tree, w)
  364. }
  365. // CheckTree checks the given parsed source tree for compilation errors. If w is
  366. // not nil, deprecation messages are written to it.
  367. func (ev *Evaler) CheckTree(tree parse.Tree, w io.Writer) *diag.Error {
  368. _, compileErr := ev.compile(tree, ev.Global(), w)
  369. return GetCompilationError(compileErr)
  370. }
  371. // Compiles a parsed tree.
  372. func (ev *Evaler) compile(tree parse.Tree, g *Ns, w io.Writer) (nsOp, error) {
  373. return compile(ev.Builtin().static(), g.static(), tree, w)
  374. }