github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/cmds/core/elvish/eval/eval.go (about) 1 // Package eval handles evaluation of parsed Elvish code and provides runtime 2 // facilities. 3 package eval 4 5 import ( 6 "fmt" 7 "os" 8 "os/signal" 9 "strings" 10 "syscall" 11 12 "github.com/u-root/u-root/cmds/core/elvish/eval/bundled" 13 "github.com/u-root/u-root/cmds/core/elvish/eval/vals" 14 "github.com/u-root/u-root/cmds/core/elvish/eval/vars" 15 "github.com/u-root/u-root/cmds/core/elvish/hashmap" 16 "github.com/u-root/u-root/cmds/core/elvish/parse" 17 "github.com/u-root/u-root/cmds/core/elvish/sys" 18 "github.com/u-root/u-root/cmds/core/elvish/util" 19 "github.com/u-root/u-root/cmds/core/elvish/vector" 20 ) 21 22 var logger = util.GetLogger("[eval] ") 23 24 const ( 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 35 const ( 36 defaultValuePrefix = "▶ " 37 initIndent = vals.NoPretty 38 ) 39 40 // Evaler is used to evaluate elvish sources. It maintains runtime context 41 // shared among all evalCtx instances. 42 type Evaler struct { 43 evalerScopes 44 valuePrefix string 45 beforeChdir []func(string) 46 afterChdir []func(string) 47 modules map[string]Ns 48 // bundled modules 49 bundled map[string]string 50 Editor Editor 51 libDir string 52 intCh chan struct{} 53 } 54 55 type evalerScopes struct { 56 Global Ns 57 Builtin Ns 58 } 59 60 // NewEvaler creates a new Evaler. 61 func NewEvaler() *Evaler { 62 builtin := builtinNs.Clone() 63 64 ev := &Evaler{ 65 valuePrefix: defaultValuePrefix, 66 evalerScopes: evalerScopes{ 67 Global: make(Ns), 68 Builtin: builtin, 69 }, 70 modules: map[string]Ns{ 71 "builtin": builtin, 72 }, 73 bundled: bundled.Get(), 74 Editor: nil, 75 intCh: nil, 76 } 77 78 beforeChdirElvish, afterChdirElvish := vector.Empty, vector.Empty 79 ev.beforeChdir = append(ev.beforeChdir, 80 adaptChdirHook("before-chdir", ev, &beforeChdirElvish)) 81 ev.afterChdir = append(ev.afterChdir, 82 adaptChdirHook("after-chdir", ev, &afterChdirElvish)) 83 builtin["before-chdir"] = vars.FromPtr(&beforeChdirElvish) 84 builtin["after-chdir"] = vars.FromPtr(&afterChdirElvish) 85 86 builtin["value-out-indicator"] = vars.FromPtr(&ev.valuePrefix) 87 builtin["pwd"] = PwdVariable{ev} 88 89 return ev 90 } 91 92 func adaptChdirHook(name string, ev *Evaler, pfns *vector.Vector) func(string) { 93 return func(path string) { 94 stdPorts := newStdPorts(os.Stdin, os.Stdout, os.Stderr, ev.valuePrefix) 95 defer stdPorts.close() 96 for it := (*pfns).Iterator(); it.HasElem(); it.Next() { 97 fn, ok := it.Elem().(Callable) 98 if !ok { 99 fmt.Fprintln(os.Stderr, name, "hook must be callable") 100 continue 101 } 102 fm := NewTopFrame(ev, 103 NewInternalSource("["+name+" hook]"), stdPorts.ports[:]) 104 err := fm.Call(fn, []interface{}{path}, NoOpts) 105 if err != nil { 106 // TODO: Stack trace 107 fmt.Fprintln(os.Stderr, err) 108 } 109 } 110 } 111 } 112 113 // Close releases resources allocated when creating this Evaler. Currently this 114 // does nothing and always returns a nil error. 115 func (ev *Evaler) Close() error { 116 return nil 117 } 118 119 // AddBeforeChdir adds a function to run before changing directory. 120 func (ev *Evaler) AddBeforeChdir(f func(string)) { 121 ev.beforeChdir = append(ev.beforeChdir, f) 122 } 123 124 // AddAfterChdir adds a function to run after changing directory. 125 func (ev *Evaler) AddAfterChdir(f func(string)) { 126 ev.afterChdir = append(ev.afterChdir, f) 127 } 128 129 // InstallModule installs a module to the Evaler so that it can be used with 130 // "use $name" from script. 131 func (ev *Evaler) InstallModule(name string, mod Ns) { 132 ev.modules[name] = mod 133 } 134 135 // InstallBundled installs a bundled module to the Evaler. 136 func (ev *Evaler) InstallBundled(name, src string) { 137 ev.bundled[name] = src 138 } 139 140 // SetArgs sets the $args builtin variable. 141 func (ev *Evaler) SetArgs(args []string) { 142 v := vector.Empty 143 for _, arg := range args { 144 v = v.Cons(arg) 145 } 146 ev.Builtin["args"] = vars.NewRo(v) 147 } 148 149 // SetLibDir sets the library directory, in which external modules are to be 150 // found. 151 func (ev *Evaler) SetLibDir(libDir string) { 152 ev.libDir = libDir 153 } 154 155 func searchPaths() []string { 156 return strings.Split(os.Getenv("PATH"), ":") 157 } 158 159 // growPorts makes the size of ec.ports at least n, adding nil's if necessary. 160 func (fm *Frame) growPorts(n int) { 161 if len(fm.ports) >= n { 162 return 163 } 164 ports := fm.ports 165 fm.ports = make([]*Port, n) 166 copy(fm.ports, ports) 167 } 168 169 // EvalWithStdPorts sets up the Evaler with standard ports and evaluates an Op. 170 // The supplied name and text are used in diagnostic messages. 171 func (ev *Evaler) EvalWithStdPorts(op Op, src *Source) error { 172 stdPorts := newStdPorts(os.Stdin, os.Stdout, os.Stderr, ev.valuePrefix) 173 defer stdPorts.close() 174 return ev.Eval(op, stdPorts.ports[:], src) 175 } 176 177 // Eval sets up the Evaler with the given ports and evaluates an Op. 178 // The supplied name and text are used in diagnostic messages. 179 func (ev *Evaler) Eval(op Op, ports []*Port, src *Source) error { 180 // Ignore TTOU. 181 // 182 // When a subprocess in its own process group puts itself in the foreground, 183 // Elvish will be put in the background. When the code finishes execution, 184 // Elvish will attempt to move itself back to the foreground by calling 185 // tcsetpgrp. However, whenever a background process calls tcsetpgrp (or 186 // otherwise attempts to modify the terminal configuration), TTOU will be 187 // sent, whose default handler is to stop the process. Or, if the process 188 // lives in an orphaned process group (which is often the case for Elvish), 189 // the call will outright fail. Therefore, for Elvish to be able to move 190 // itself back to the foreground later, we need to ignore TTOU now. 191 ignoreTTOU() 192 defer unignoreTTOU() 193 194 stopSigGoroutine := make(chan struct{}) 195 sigGoRoutineDone := make(chan struct{}) 196 // Set up intCh. 197 ev.intCh = make(chan struct{}) 198 sigCh := make(chan os.Signal) 199 signal.Notify(sigCh, syscall.SIGINT, syscall.SIGQUIT) 200 go func() { 201 closedIntCh := false 202 loop: 203 for { 204 select { 205 case <-sigCh: 206 if !closedIntCh { 207 close(ev.intCh) 208 closedIntCh = true 209 } 210 case <-stopSigGoroutine: 211 break loop 212 } 213 } 214 ev.intCh = nil 215 signal.Stop(sigCh) 216 close(sigGoRoutineDone) 217 }() 218 219 err := ev.eval(op, ports, src) 220 221 close(stopSigGoroutine) 222 <-sigGoRoutineDone 223 224 // Put myself in foreground, in case some command has put me in background. 225 // XXX Should probably use fd of /dev/tty instead of 0. 226 if sys.IsATTY(os.Stdin) { 227 err := putSelfInFg() 228 if err != nil { 229 fmt.Println("failed to put myself in foreground:", err) 230 } 231 } 232 233 return err 234 } 235 236 // eval evaluates a chunk node n. The supplied name and text are used in 237 // diagnostic messages. 238 func (ev *Evaler) eval(op Op, ports []*Port, src *Source) error { 239 ec := NewTopFrame(ev, src, ports) 240 return ec.Eval(op) 241 } 242 243 // Compile compiles Elvish code in the global scope. If the error is not nil, it 244 // always has type CompilationError. 245 func (ev *Evaler) Compile(n *parse.Chunk, src *Source) (Op, error) { 246 return compile(ev.Builtin.static(), ev.Global.static(), n, src) 247 } 248 249 // EvalSource evaluates a chunk of Elvish source. 250 func (ev *Evaler) EvalSource(src *Source) error { 251 n, err := parse.Parse(src.name, src.code) 252 if err != nil { 253 return err 254 } 255 op, err := ev.Compile(n, src) 256 if err != nil { 257 return err 258 } 259 return ev.EvalWithStdPorts(op, src) 260 } 261 262 // SourceRC evaluates a rc.elv file. It evaluates the file in the global 263 // namespace. If the file defines a $-exports- map variable, the variable is 264 // removed and its content is poured into the global namespace, not overriding 265 // existing variables. 266 func (ev *Evaler) SourceRC(src *Source) error { 267 n, err := parse.Parse(src.name, src.code) 268 if err != nil { 269 return err 270 } 271 op, err := ev.Compile(n, src) 272 if err != nil { 273 return err 274 } 275 errEval := ev.EvalWithStdPorts(op, src) 276 var errExports error 277 if ev.Global.HasName("-exports-") { 278 exports := ev.Global.PopName("-exports-").Get() 279 switch exports := exports.(type) { 280 case hashmap.Map: 281 for it := exports.Iterator(); it.HasElem(); it.Next() { 282 k, v := it.Elem() 283 if name, ok := k.(string); ok { 284 if !ev.Global.HasName(name) { 285 ev.Global.Add(name, vars.NewAnyWithInit(v)) 286 } 287 } else { 288 errKey := fmt.Errorf("non-string key in $-exports-: %s", 289 vals.Repr(k, vals.NoPretty)) 290 errExports = util.Errors(errExports, errKey) 291 } 292 } 293 default: 294 errExports = fmt.Errorf( 295 "$-exports- should be a map, got %s", vals.Kind(exports)) 296 } 297 } 298 return util.Errors(errEval, errExports) 299 }