github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/eval/eval.go (about) 1 // Package eval handles evaluation of nodes and consists the runtime of the 2 // shell. 3 package eval 4 5 //go:generate ./gen-embedded-modules 6 7 import ( 8 "bufio" 9 "bytes" 10 "errors" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "os" 15 "os/signal" 16 "strconv" 17 "strings" 18 "sync" 19 "syscall" 20 "unicode/utf8" 21 22 "github.com/elves/elvish/daemon" 23 "github.com/elves/elvish/daemon/api" 24 "github.com/elves/elvish/parse" 25 "github.com/elves/elvish/sys" 26 "github.com/elves/elvish/util" 27 ) 28 29 var logger = util.GetLogger("[eval] ") 30 31 // FnPrefix is the prefix for the variable names of functions. Defining a 32 // function "foo" is equivalent to setting a variable named FnPrefix + "foo". 33 const FnPrefix = "&" 34 35 // Namespace is a map from name to variables. 36 type Namespace map[string]Variable 37 38 // Evaler is used to evaluate elvish sources. It maintains runtime context 39 // shared among all evalCtx instances. 40 type Evaler struct { 41 Builtin Namespace 42 Global Namespace 43 Modules map[string]Namespace 44 Daemon *api.Client 45 ToSpawn *daemon.Daemon 46 Editor Editor 47 DataDir string 48 intCh chan struct{} 49 } 50 51 // EvalCtx maintains an Evaler along with its runtime context. After creation 52 // an EvalCtx is seldom modified, and new instances are created when needed. 53 type EvalCtx struct { 54 *Evaler 55 name, srcName, src string 56 57 local, up Namespace 58 ports []*Port 59 positionals []Value 60 61 begin, end int 62 traceback *util.SourceContext 63 64 background bool 65 } 66 67 // NewEvaler creates a new Evaler. 68 func NewEvaler(daemon *api.Client, toSpawn *daemon.Daemon, 69 dataDir string, extraModules map[string]Namespace) *Evaler { 70 71 // TODO(xiaq): Create daemon namespace asynchronously. 72 modules := map[string]Namespace{ 73 "daemon": makeDaemonNamespace(daemon), 74 } 75 for name, mod := range extraModules { 76 modules[name] = mod 77 } 78 79 return &Evaler{ 80 Builtin: makeBuiltinNamespace(daemon), 81 Global: Namespace{}, 82 Modules: modules, 83 Daemon: daemon, 84 ToSpawn: toSpawn, 85 Editor: nil, 86 DataDir: dataDir, 87 intCh: nil, 88 } 89 } 90 91 func (ev *Evaler) searchPaths() []string { 92 return ev.Builtin["paths"].(*EnvPathList).get() 93 } 94 95 const ( 96 outChanSize = 32 97 outChanLeader = "▶ " 98 falseIndicator = "✗" 99 initIndent = NoPretty 100 ) 101 102 // NewTopEvalCtx creates a top-level evalCtx. 103 func NewTopEvalCtx(ev *Evaler, name, text string, ports []*Port) *EvalCtx { 104 return &EvalCtx{ 105 ev, "top", 106 name, text, 107 ev.Global, Namespace{}, 108 ports, nil, 109 0, len(text), nil, false, 110 } 111 } 112 113 // fork returns a modified copy of ec. The ports are forked, and the name is 114 // changed to the given value. Other fields are copied shallowly. 115 func (ec *EvalCtx) fork(name string) *EvalCtx { 116 newPorts := make([]*Port, len(ec.ports)) 117 for i, p := range ec.ports { 118 newPorts[i] = p.Fork() 119 } 120 return &EvalCtx{ 121 ec.Evaler, name, 122 ec.srcName, ec.src, 123 ec.local, ec.up, 124 newPorts, ec.positionals, 125 ec.begin, ec.end, ec.traceback, ec.background, 126 } 127 } 128 129 // port returns ec.ports[i] or nil if i is out of range. This makes it possible 130 // to treat ec.ports as if it has an infinite tail of nil's. 131 func (ec *EvalCtx) port(i int) *Port { 132 if i >= len(ec.ports) { 133 return nil 134 } 135 return ec.ports[i] 136 } 137 138 // growPorts makes the size of ec.ports at least n, adding nil's if necessary. 139 func (ec *EvalCtx) growPorts(n int) { 140 if len(ec.ports) >= n { 141 return 142 } 143 ports := ec.ports 144 ec.ports = make([]*Port, n) 145 copy(ec.ports, ports) 146 } 147 148 func makeScope(s Namespace) scope { 149 sc := scope{} 150 for name := range s { 151 sc[name] = true 152 } 153 return sc 154 } 155 156 // eval evaluates a chunk node n. The supplied name and text are used in 157 // diagnostic messages. 158 func (ev *Evaler) eval(op Op, ports []*Port, name, text string) error { 159 ec := NewTopEvalCtx(ev, name, text, ports) 160 return ec.PEval(op) 161 } 162 163 func (ec *EvalCtx) Interrupts() <-chan struct{} { 164 return ec.intCh 165 } 166 167 // Eval sets up the Evaler with standard ports and evaluates an Op. The supplied 168 // name and text are used in diagnostic messages. 169 func (ev *Evaler) Eval(op Op, name, text string) error { 170 inCh := make(chan Value) 171 close(inCh) 172 173 outCh := make(chan Value, outChanSize) 174 outDone := make(chan struct{}) 175 go func() { 176 for v := range outCh { 177 fmt.Println(outChanLeader + v.Repr(initIndent)) 178 } 179 close(outDone) 180 }() 181 defer func() { 182 close(outCh) 183 <-outDone 184 }() 185 186 ports := []*Port{ 187 {File: os.Stdin, Chan: inCh}, 188 {File: os.Stdout, Chan: outCh}, 189 {File: os.Stderr, Chan: BlackholeChan}, 190 } 191 192 return ev.EvalWithPorts(ports, op, name, text) 193 } 194 195 // EvalWithPorts sets up the Evaler with the given ports and evaluates an Op. 196 // The supplied name and text are used in diagnostic messages. 197 func (ev *Evaler) EvalWithPorts(ports []*Port, op Op, name, text string) error { 198 // signal.Ignore(syscall.SIGTTIN) 199 200 // Ignore TTOU. 201 // When a subprocess in its own process group puts itself in the foreground, 202 // the elvish will be in the background. In that case, elvish will move 203 // itself back to the foreground by calling tcsetpgrp. However, whenever a 204 // background process calls tcsetpgrp (or otherwise attempts to modify the 205 // terminal configuration), TTOU will be sent, whose default handler is to 206 // stop the process. When the process lives in an orphaned process group 207 // (most likely for elvish), the call will outright fail. Therefore, for 208 // elvish to be able to move itself back to the foreground, we need to 209 // ignore TTOU. 210 signal.Ignore(syscall.SIGTTOU) 211 stopSigGoroutine := make(chan struct{}) 212 sigGoRoutineDone := make(chan struct{}) 213 // Set up intCh. 214 ev.intCh = make(chan struct{}) 215 sigCh := make(chan os.Signal) 216 signal.Notify(sigCh, syscall.SIGINT, syscall.SIGQUIT) 217 go func() { 218 closedIntCh := false 219 loop: 220 for { 221 select { 222 case <-sigCh: 223 if !closedIntCh { 224 close(ev.intCh) 225 closedIntCh = true 226 } 227 case <-stopSigGoroutine: 228 break loop 229 } 230 } 231 ev.intCh = nil 232 signal.Stop(sigCh) 233 close(sigGoRoutineDone) 234 }() 235 236 err := ev.eval(op, ports, name, text) 237 238 close(stopSigGoroutine) 239 <-sigGoRoutineDone 240 241 // Put myself in foreground, in case some command has put me in background. 242 // XXX Should probably use fd of /dev/tty instead of 0. 243 if sys.IsATTY(0) { 244 err := sys.Tcsetpgrp(0, syscall.Getpgrp()) 245 if err != nil { 246 fmt.Println("failed to put myself in foreground:", err) 247 } 248 } 249 250 // Un-ignore TTOU. 251 signal.Reset(syscall.SIGTTOU) 252 253 return err 254 } 255 256 func summarize(text string) string { 257 // TODO Make a proper summary. 258 if len(text) < 32 { 259 return text 260 } 261 var b bytes.Buffer 262 for i, r := range text { 263 if i+len(string(r)) >= 32 { 264 break 265 } 266 b.WriteRune(r) 267 } 268 return b.String() 269 } 270 271 // Compile compiles elvish code in the global scope. If the error is not nil, it 272 // always has type CompilationError. 273 func (ev *Evaler) Compile(n *parse.Chunk, name, text string) (Op, error) { 274 return compile(makeScope(ev.Builtin), makeScope(ev.Global), n, name, text) 275 } 276 277 // PEval evaluates an op in a protected environment so that calls to errorf are 278 // wrapped in an Error. 279 func (ec *EvalCtx) PEval(op Op) (err error) { 280 defer catch(&err, ec) 281 op.Exec(ec) 282 return nil 283 } 284 285 func (ec *EvalCtx) PCall(f Callable, args []Value, opts map[string]Value) (err error) { 286 defer catch(&err, ec) 287 f.Call(ec, args, opts) 288 return nil 289 } 290 291 func (ec *EvalCtx) PCaptureOutput(f Callable, args []Value, opts map[string]Value) (vs []Value, err error) { 292 // XXX There is no source. 293 return pcaptureOutput(ec, Op{ 294 func(newec *EvalCtx) { f.Call(newec, args, opts) }, -1, -1}) 295 } 296 297 func catch(perr *error, ec *EvalCtx) { 298 // NOTE: We have to duplicate instead of calling util.Catch here, since 299 // recover can only catch a panic when called directly from a deferred 300 // function. 301 r := recover() 302 if r == nil { 303 return 304 } 305 if exc, ok := r.(util.Thrown); ok { 306 err := exc.Error 307 if _, ok := err.(*Exception); !ok { 308 err = ec.makeException(err) 309 } 310 *perr = err 311 } else if r != nil { 312 panic(r) 313 } 314 } 315 316 // makeException turns an error into an Exception by adding traceback. 317 func (ec *EvalCtx) makeException(e error) *Exception { 318 return &Exception{e, ec.addTraceback()} 319 } 320 321 func (ec *EvalCtx) addTraceback() *util.SourceContext { 322 return &util.SourceContext{ 323 Name: ec.srcName, Source: ec.src, 324 Begin: ec.begin, End: ec.end, Next: ec.traceback, 325 } 326 } 327 328 // errorpf stops the ec.eval immediately by panicking with a diagnostic message. 329 // The panic is supposed to be caught by ec.eval. 330 func (ec *EvalCtx) errorpf(begin, end int, format string, args ...interface{}) { 331 ec.begin, ec.end = begin, end 332 throwf(format, args...) 333 } 334 335 // SourceText evaluates a chunk of elvish source. 336 func (ev *Evaler) SourceText(name, src string) error { 337 n, err := parse.Parse(name, src) 338 if err != nil { 339 return err 340 } 341 op, err := ev.Compile(n, name, src) 342 if err != nil { 343 return err 344 } 345 return ev.Eval(op, name, src) 346 } 347 348 func readFileUTF8(fname string) (string, error) { 349 bytes, err := ioutil.ReadFile(fname) 350 if err != nil { 351 return "", err 352 } 353 if !utf8.Valid(bytes) { 354 return "", fmt.Errorf("%s: source is not valid UTF-8", fname) 355 } 356 return string(bytes), nil 357 } 358 359 // Source evaluates the content of a file. 360 func (ev *Evaler) Source(fname string) error { 361 src, err := readFileUTF8(fname) 362 if err != nil { 363 return err 364 } 365 return ev.SourceText(fname, src) 366 } 367 368 // ErrStoreUnconnected is thrown by ResolveVar when a shared: variable needs to 369 // be resolved but the store is not connected. 370 var ErrStoreUnconnected = errors.New("store unconnected") 371 372 // ResolveVar resolves a variable. When the variable cannot be found, nil is 373 // returned. 374 func (ec *EvalCtx) ResolveVar(ns, name string) Variable { 375 switch ns { 376 case "local": 377 return ec.getLocal(name) 378 case "up": 379 return ec.up[name] 380 case "builtin": 381 return ec.Builtin[name] 382 case "": 383 if v := ec.getLocal(name); v != nil { 384 return v 385 } 386 if v, ok := ec.up[name]; ok { 387 return v 388 } 389 return ec.Builtin[name] 390 case "e": 391 if strings.HasPrefix(name, FnPrefix) { 392 return NewRoVariable(ExternalCmd{name[len(FnPrefix):]}) 393 } 394 case "E": 395 return envVariable{name} 396 case "shared": 397 if ec.Daemon == nil { 398 throw(ErrStoreUnconnected) 399 } 400 return sharedVariable{ec.Daemon, name} 401 default: 402 if ns, ok := ec.Modules[ns]; ok { 403 return ns[name] 404 } 405 } 406 return nil 407 } 408 409 // getLocal finds the named local variable. 410 func (ec *EvalCtx) getLocal(name string) Variable { 411 i, err := strconv.Atoi(name) 412 if err == nil { 413 // Logger.Println("positional variable", i) 414 // Logger.Printf("EvalCtx=%p, args=%v", ec, ec.positionals) 415 if i < 0 { 416 i += len(ec.positionals) 417 } 418 if i < 0 || i >= len(ec.positionals) { 419 // Logger.Print("out of range") 420 return nil 421 } 422 // Logger.Print("found") 423 return NewRoVariable(ec.positionals[i]) 424 } 425 return ec.local[name] 426 } 427 428 var ErrMoreThanOneRest = errors.New("more than one @ lvalue") 429 430 // IterateInputs calls the passed function for each input element. 431 func (ec *EvalCtx) IterateInputs(f func(Value)) { 432 var w sync.WaitGroup 433 inputs := make(chan Value) 434 435 w.Add(2) 436 go func() { 437 linesToChan(ec.ports[0].File, inputs) 438 w.Done() 439 }() 440 go func() { 441 for v := range ec.ports[0].Chan { 442 inputs <- v 443 } 444 w.Done() 445 }() 446 go func() { 447 w.Wait() 448 close(inputs) 449 }() 450 451 for v := range inputs { 452 f(v) 453 } 454 } 455 456 func linesToChan(r io.Reader, ch chan<- Value) { 457 filein := bufio.NewReader(r) 458 for { 459 line, err := filein.ReadString('\n') 460 if line != "" { 461 ch <- String(strings.TrimSuffix(line, "\n")) 462 } 463 if err != nil { 464 if err != io.EOF { 465 logger.Println("error on reading:", err) 466 } 467 break 468 } 469 } 470 } 471 472 // OutputChan returns a channel onto which output can be written. 473 func (ec *EvalCtx) OutputChan() chan<- Value { 474 return ec.ports[1].Chan 475 } 476 477 // OutputFile returns a file onto which output can be written. 478 func (ec *EvalCtx) OutputFile() *os.File { 479 return ec.ports[1].File 480 }