OILS / core / shell.py View on Github | oilshell.org

1147 lines, 733 significant
1"""
2core/shell.py -- Entry point for the shell interpreter.
3"""
4from __future__ import print_function
5
6from errno import ENOENT
7import time as time_
8
9from _devbuild.gen import arg_types
10from _devbuild.gen.option_asdl import option_i, builtin_i
11from _devbuild.gen.runtime_asdl import scope_e
12from _devbuild.gen.syntax_asdl import (loc, source, source_t, IntParamBox,
13 debug_frame, debug_frame_t)
14from _devbuild.gen.value_asdl import (value, value_e)
15from core import alloc
16from core import comp_ui
17from core import dev
18from core import error
19from core import executor
20from core import completion
21from core import main_loop
22from core import optview
23from core import pyos
24from core import process
25from core import pyutil
26from core import state
27from display import ui
28from core import util
29from core import vm
30
31from frontend import args
32from frontend import flag_def # side effect: flags are defined!
33
34unused1 = flag_def
35from frontend import flag_util
36from frontend import location
37from frontend import reader
38from frontend import parse_lib
39
40from builtin import assign_osh
41from builtin import bracket_osh
42from builtin import completion_osh
43from builtin import completion_ysh
44from builtin import dirs_osh
45from builtin import error_ysh
46from builtin import hay_ysh
47from builtin import io_osh
48from builtin import io_ysh
49from builtin import json_ysh
50from builtin import meta_osh
51from builtin import misc_osh
52from builtin import module_ysh
53from builtin import printf_osh
54from builtin import process_osh
55from builtin import pure_osh
56from builtin import pure_ysh
57from builtin import readline_osh
58from builtin import read_osh
59from builtin import trap_osh
60
61from builtin import func_eggex
62from builtin import func_hay
63from builtin import func_misc
64
65from builtin import method_dict
66from builtin import method_io
67from builtin import method_list
68from builtin import method_other
69from builtin import method_str
70
71from osh import cmd_eval
72from osh import glob_
73from osh import history
74from osh import prompt
75from osh import sh_expr_eval
76from osh import split
77from osh import word_eval
78
79from mycpp import mops
80from mycpp import mylib
81from mycpp.mylib import print_stderr, log
82from pylib import os_path
83from tools import deps
84from tools import fmt
85from tools import ysh_ify
86from ysh import expr_eval
87
88unused2 = log
89
90import libc
91import posix_ as posix
92
93from typing import List, Dict, Optional, TYPE_CHECKING, cast
94if TYPE_CHECKING:
95 from frontend.py_readline import Readline
96
97if mylib.PYTHON:
98 try:
99 from _devbuild.gen import help_meta # type: ignore
100 except ImportError:
101 help_meta = None
102
103
104def _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup):
105 # type: (cmd_eval.CommandEvaluator, completion_osh.Complete, completion.Lookup) -> None
106
107 # register builtins and words
108 complete_builtin.Run(cmd_eval.MakeBuiltinArgv(['-E', '-A', 'command']))
109 # register path completion
110 # Add -o filenames? Or should that be automatic?
111 complete_builtin.Run(cmd_eval.MakeBuiltinArgv(['-D', '-A', 'file']))
112
113
114def _CompletionDemo(comp_lookup):
115 # type: (completion.Lookup) -> None
116
117 # Something for fun, to show off. Also: test that you don't repeatedly hit
118 # the file system / network / coprocess.
119 A1 = completion.TestAction(['foo.py', 'foo', 'bar.py'], 0.0)
120 l = [] # type: List[str]
121 for i in xrange(0, 5):
122 l.append('m%d' % i)
123
124 A2 = completion.TestAction(l, 0.1)
125 C1 = completion.UserSpec([A1, A2], [], [], completion.DefaultPredicate(),
126 '', '')
127 comp_lookup.RegisterName('slowc', {}, C1)
128
129
130def SourceStartupFile(
131 fd_state, # type: process.FdState
132 rc_path, # type: str
133 lang, # type: str
134 parse_ctx, # type: parse_lib.ParseContext
135 cmd_ev, # type: cmd_eval.CommandEvaluator
136 errfmt, # type: ui.ErrorFormatter
137):
138 # type: (...) -> None
139
140 # Right now this is called when the shell is interactive. (Maybe it should
141 # be called on login_shel too.)
142 #
143 # Terms:
144 # - interactive shell: Roughly speaking, no args or -c, and isatty() is true
145 # for stdin and stdout.
146 # - login shell: Started from the top level, e.g. from init or ssh.
147 #
148 # We're not going to copy everything bash does because it's too complex, but
149 # for reference:
150 # https://www.gnu.org/software/bash/manual/bash.html#Bash-Startup-Files
151 # Bash also has --login.
152
153 try:
154 f = fd_state.Open(rc_path)
155 except (IOError, OSError) as e:
156 # TODO: Could warn about nonexistent explicit --rcfile?
157 if e.errno != ENOENT:
158 raise # Goes to top level. Handle this better?
159 return
160
161 arena = parse_ctx.arena
162 rc_line_reader = reader.FileLineReader(f, arena)
163 rc_c_parser = parse_ctx.MakeOshParser(rc_line_reader)
164
165 with alloc.ctx_SourceCode(arena, source.SourcedFile(rc_path, loc.Missing)):
166 # TODO: handle status, e.g. 2 for ParseError
167 unused = main_loop.Batch(cmd_ev, rc_c_parser, errfmt)
168
169 f.close()
170
171
172class ShellOptHook(state.OptHook):
173
174 def __init__(self, readline):
175 # type: (Optional[Readline]) -> None
176 self.readline = readline
177
178 def OnChange(self, opt0_array, opt_name, b):
179 # type: (List[bool], str, bool) -> bool
180 """This method is called whenever an option is changed.
181
182 Returns success or failure.
183 """
184 if opt_name == 'vi' or opt_name == 'emacs':
185 # TODO: Replace with a hook? Just like setting LANG= can have a hook.
186 if self.readline:
187 self.readline.parse_and_bind("set editing-mode " + opt_name)
188 else:
189 print_stderr(
190 "Warning: Can't set option %r because shell wasn't compiled with GNU readline"
191 % opt_name)
192 return False
193
194 # Invert: they are mutually exclusive!
195 if opt_name == 'vi':
196 opt0_array[option_i.emacs] = not b
197 elif opt_name == 'emacs':
198 opt0_array[option_i.vi] = not b
199
200 return True
201
202
203def _SetGlobalFunc(mem, name, func):
204 # type: (state.Mem, str, vm._Callable) -> None
205 assert isinstance(func, vm._Callable), func
206
207 # Note: no location info for builtin functions?
208 mem.SetNamed(location.LName(name), value.BuiltinFunc(func),
209 scope_e.GlobalOnly)
210
211
212def InitAssignmentBuiltins(
213 mem, # type: state.Mem
214 procs, # type: state.Procs
215 exec_opts, # type: optview.Exec
216 errfmt, # type: ui.ErrorFormatter
217):
218 # type: (...) -> Dict[int, vm._AssignBuiltin]
219
220 assign_b = {} # type: Dict[int, vm._AssignBuiltin]
221
222 new_var = assign_osh.NewVar(mem, procs, exec_opts, errfmt)
223 assign_b[builtin_i.declare] = new_var
224 assign_b[builtin_i.typeset] = new_var
225 assign_b[builtin_i.local] = new_var
226
227 assign_b[builtin_i.export_] = assign_osh.Export(mem, errfmt)
228 assign_b[builtin_i.readonly] = assign_osh.Readonly(mem, errfmt)
229
230 return assign_b
231
232
233class ShellFiles(object):
234
235 def __init__(self, lang, home_dir, mem, flag):
236 # type: (str, str, state.Mem, arg_types.main) -> None
237 assert lang in ('osh', 'ysh'), lang
238 self.lang = lang
239 self.home_dir = home_dir
240 self.mem = mem
241 self.flag = flag
242
243 def _HistVar(self):
244 # type: () -> str
245 return 'HISTFILE' if self.lang == 'osh' else 'YSH_HISTFILE'
246
247 def _DefaultHistoryFile(self):
248 # type: () -> str
249 return os_path.join(self.home_dir,
250 '.local/share/oils/%s_history' % self.lang)
251
252 def InitAfterLoadingEnv(self):
253 # type: () -> None
254
255 hist_var = self._HistVar()
256 if self.mem.GetValue(hist_var).tag() == value_e.Undef:
257 # Note: if the directory doesn't exist, GNU readline ignores
258 state.SetGlobalString(self.mem, hist_var,
259 self._DefaultHistoryFile())
260
261 def HistoryFile(self):
262 # type: () -> Optional[str]
263 # TODO: In non-strict mode we should try to cast the HISTFILE value to a
264 # string following bash's rules
265
266 UP_val = self.mem.GetValue(self._HistVar())
267 if UP_val.tag() == value_e.Str:
268 val = cast(value.Str, UP_val)
269 return val.s
270 else:
271 # Note: if HISTFILE is an array, bash will return ${HISTFILE[0]}
272 return None
273 #return self._DefaultHistoryFile()
274
275 # TODO: can we recover line information here?
276 # might be useful to show where HISTFILE was set
277 #raise error.Strict("$HISTFILE should only ever be a string", loc.Missing)
278
279
280def Main(
281 lang, # type: str
282 arg_r, # type: args.Reader
283 environ, # type: Dict[str, str]
284 login_shell, # type: bool
285 loader, # type: pyutil._ResourceLoader
286 readline, # type: Optional[Readline]
287):
288 # type: (...) -> int
289 """The full shell lifecycle. Used by bin/osh and bin/ysh.
290
291 Args:
292 lang: 'osh' or 'ysh'
293 login_shell: Was - on argv[0]?
294 loader: to get help, version, grammar, etc.
295 readline: optional GNU readline
296 """
297 # Differences between osh and ysh:
298 # - oshrc vs yshrc
299 # - shopt -s ysh:all
300 # - Prompt
301 # - --help
302
303 argv0 = arg_r.Peek()
304 assert argv0 is not None
305 arg_r.Next()
306
307 assert lang in ('osh', 'ysh'), lang
308
309 try:
310 attrs = flag_util.ParseMore('main', arg_r)
311 except error.Usage as e:
312 print_stderr('%s usage error: %s' % (lang, e.msg))
313 return 2
314 flag = arg_types.main(attrs.attrs)
315
316 arena = alloc.Arena()
317 errfmt = ui.ErrorFormatter()
318
319 if flag.help:
320 util.HelpFlag(loader, '%s-usage' % lang, mylib.Stdout())
321 return 0
322 if flag.version:
323 util.VersionFlag(loader, mylib.Stdout())
324 return 0
325
326 if flag.tool == 'cat-em':
327 paths = arg_r.Rest()
328
329 status = 0
330 for p in paths:
331 try:
332 contents = loader.Get(p)
333 print(contents)
334 except (OSError, IOError):
335 print_stderr("cat-em: %r not found" % p)
336 status = 1
337 return status
338
339 debug_stack = [] # type: List[debug_frame_t]
340 if arg_r.AtEnd():
341 dollar0 = argv0
342 else:
343 dollar0 = arg_r.Peek() # the script name, or the arg after -c
344
345 frame0 = debug_frame.Main(dollar0)
346 debug_stack.append(frame0)
347
348 script_name = arg_r.Peek() # type: Optional[str]
349 arg_r.Next()
350 mem = state.Mem(dollar0, arg_r.Rest(), arena, debug_stack)
351
352 opt_hook = ShellOptHook(readline)
353 # Note: only MutableOpts needs mem, so it's not a true circular dep.
354 parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook)
355 mem.exec_opts = exec_opts # circular dep
356 mutable_opts.Init()
357
358 version_str = pyutil.GetVersion(loader)
359 state.InitMem(mem, environ, version_str)
360
361 if attrs.show_options: # special case: sh -o
362 mutable_opts.ShowOptions([])
363 return 0
364
365 # Set these BEFORE processing flags, so they can be overridden.
366 if lang == 'ysh':
367 mutable_opts.SetAnyOption('ysh:all', True)
368
369 pure_osh.SetOptionsFromFlags(mutable_opts, attrs.opt_changes,
370 attrs.shopt_changes)
371
372 # feedback between runtime and parser
373 aliases = {} # type: Dict[str, str]
374
375 ysh_grammar = pyutil.LoadYshGrammar(loader)
376
377 if flag.do_lossless and not exec_opts.noexec():
378 raise error.Usage('--one-pass-parse requires noexec (-n)', loc.Missing)
379
380 # Tools always use one pass parse
381 # Note: osh --tool syntax-tree is like osh -n --one-pass-parse
382 do_lossless = True if len(flag.tool) else flag.do_lossless
383
384 parse_ctx = parse_lib.ParseContext(arena,
385 parse_opts,
386 aliases,
387 ysh_grammar,
388 do_lossless=do_lossless)
389
390 # Three ParseContext instances SHARE aliases.
391 comp_arena = alloc.Arena()
392 comp_arena.PushSource(source.Unused('completion'))
393 trail1 = parse_lib.Trail()
394 # do_lossless needs to be turned on to complete inside backticks. TODO:
395 # fix the issue where ` gets erased because it's not part of
396 # set_completer_delims().
397 comp_ctx = parse_lib.ParseContext(comp_arena,
398 parse_opts,
399 aliases,
400 ysh_grammar,
401 do_lossless=True)
402 comp_ctx.Init_Trail(trail1)
403
404 hist_arena = alloc.Arena()
405 hist_arena.PushSource(source.Unused('history'))
406 trail2 = parse_lib.Trail()
407 hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases,
408 ysh_grammar)
409 hist_ctx.Init_Trail(trail2)
410
411 # Deps helps manages dependencies. These dependencies are circular:
412 # - cmd_ev and word_ev, arith_ev -- for command sub, arith sub
413 # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 ))
414 # - cmd_ev and builtins (which execute code, like eval)
415 # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P
416 cmd_deps = cmd_eval.Deps()
417 cmd_deps.mutable_opts = mutable_opts
418
419 job_control = process.JobControl()
420 job_list = process.JobList()
421 fd_state = process.FdState(errfmt, job_control, job_list, mem, None, None,
422 exec_opts)
423
424 my_pid = posix.getpid()
425
426 debug_path = ''
427 debug_dir = environ.get('OILS_DEBUG_DIR')
428 if flag.debug_file is not None:
429 # --debug-file takes precedence over OSH_DEBUG_DIR
430 debug_path = flag.debug_file
431 elif debug_dir is not None:
432 debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid)
433
434 if len(debug_path):
435 # This will be created as an empty file if it doesn't exist, or it could be
436 # a pipe.
437 try:
438 debug_f = util.DebugFile(
439 fd_state.OpenForWrite(debug_path)) # type: util._DebugFile
440 except (IOError, OSError) as e:
441 print_stderr("%s: Couldn't open %r: %s" %
442 (lang, debug_path, posix.strerror(e.errno)))
443 return 2
444 else:
445 debug_f = util.NullDebugFile()
446
447 if flag.xtrace_to_debug_file:
448 trace_f = debug_f
449 else:
450 trace_f = util.DebugFile(mylib.Stderr())
451
452 trace_dir = environ.get('OILS_TRACE_DIR', '')
453 dumps = environ.get('OILS_TRACE_DUMPS', '')
454 streams = environ.get('OILS_TRACE_STREAMS', '')
455 multi_trace = dev.MultiTracer(my_pid, trace_dir, dumps, streams, fd_state)
456
457 tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, trace_f,
458 multi_trace)
459 fd_state.tracer = tracer # circular dep
460
461 signal_safe = pyos.InitSignalSafe()
462 trap_state = trap_osh.TrapState(signal_safe)
463
464 waiter = process.Waiter(job_list, exec_opts, signal_safe, tracer)
465 fd_state.waiter = waiter
466
467 cmd_deps.debug_f = debug_f
468
469 now = time_.time()
470 iso_stamp = time_.strftime("%Y-%m-%d %H:%M:%S", time_.localtime(now))
471
472 argv_buf = mylib.BufWriter()
473 dev.PrintShellArgv(arg_r.argv, argv_buf)
474
475 debug_f.writeln('%s [%d] Oils started with argv %s' %
476 (iso_stamp, my_pid, argv_buf.getvalue()))
477 if len(debug_path):
478 debug_f.writeln('Writing logs to %r' % debug_path)
479
480 interp = environ.get('OILS_HIJACK_SHEBANG', '')
481 search_path = state.SearchPath(mem)
482 ext_prog = process.ExternalProgram(interp, fd_state, errfmt, debug_f)
483
484 splitter = split.SplitContext(mem)
485 # TODO: This is instantiation is duplicated in osh/word_eval.py
486 globber = glob_.Globber(exec_opts)
487
488 # This could just be OILS_TRACE_DUMPS='crash:argv0'
489 crash_dump_dir = environ.get('OILS_CRASH_DUMP_DIR', '')
490 cmd_deps.dumper = dev.CrashDumper(crash_dump_dir, fd_state)
491
492 comp_lookup = completion.Lookup()
493
494 # Various Global State objects to work around readline interfaces
495 compopt_state = completion.OptionState()
496
497 comp_ui_state = comp_ui.State()
498 prompt_state = comp_ui.PromptState()
499
500 # The login program is supposed to set $HOME
501 # https://superuser.com/questions/271925/where-is-the-home-environment-variable-set
502 # state.InitMem(mem) must happen first
503 tilde_ev = word_eval.TildeEvaluator(mem, exec_opts)
504 home_dir = tilde_ev.GetMyHomeDir()
505 if home_dir is None:
506 # TODO: print errno from getpwuid()
507 print_stderr("%s: Failed to get home dir from $HOME or getpwuid()" %
508 lang)
509 return 1
510
511 sh_files = ShellFiles(lang, home_dir, mem, flag)
512 sh_files.InitAfterLoadingEnv()
513
514 #
515 # Executor and Evaluators (are circularly dependent)
516 #
517
518 # Global proc namespace. Funcs are defined in the common variable
519 # namespace.
520 procs = state.Procs(mem) # type: state.Procs
521
522 builtins = {} # type: Dict[int, vm._Builtin]
523
524 # e.g. s->startswith()
525 methods = {} # type: Dict[int, Dict[str, vm._Callable]]
526
527 hay_state = hay_ysh.HayState()
528
529 shell_ex = executor.ShellExecutor(mem, exec_opts, mutable_opts, procs,
530 hay_state, builtins, search_path,
531 ext_prog, waiter, tracer, job_control,
532 job_list, fd_state, trap_state, errfmt)
533
534 arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, mutable_opts,
535 parse_ctx, errfmt)
536 bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, mutable_opts,
537 parse_ctx, errfmt)
538 expr_ev = expr_eval.ExprEvaluator(mem, mutable_opts, methods, splitter,
539 errfmt)
540 word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, mutable_opts,
541 tilde_ev, splitter, errfmt)
542
543 assign_b = InitAssignmentBuiltins(mem, procs, exec_opts, errfmt)
544 cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_b,
545 arena, cmd_deps, trap_state,
546 signal_safe)
547
548 # PromptEvaluator rendering is needed in non-interactive shells for @P.
549 prompt_ev = prompt.Evaluator(lang, version_str, parse_ctx, mem)
550 global_io = value.IO(cmd_ev, prompt_ev)
551 global_guts = value.Guts(None)
552
553 # Wire up circular dependencies.
554 vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex,
555 prompt_ev, global_io, tracer)
556
557 unsafe_arith = sh_expr_eval.UnsafeArith(mem, exec_opts, mutable_opts,
558 parse_ctx, arith_ev, errfmt)
559 vm.InitUnsafeArith(mem, word_ev, unsafe_arith)
560
561 #
562 # Initialize Built-in Procs
563 #
564
565 b = builtins # short alias for initialization
566
567 if mylib.PYTHON:
568 if help_meta:
569 help_data = help_meta.TopicMetadata()
570 else:
571 help_data = {} # minimal build
572 else:
573 help_data = help_meta.TopicMetadata()
574 b[builtin_i.help] = misc_osh.Help(lang, loader, help_data, errfmt)
575
576 # Interpreter state
577 b[builtin_i.set] = pure_osh.Set(mutable_opts, mem)
578 b[builtin_i.shopt] = pure_osh.Shopt(mutable_opts, cmd_ev)
579
580 b[builtin_i.hash] = pure_osh.Hash(search_path) # not really pure
581 b[builtin_i.trap] = trap_osh.Trap(trap_state, parse_ctx, tracer, errfmt)
582
583 b[builtin_i.shvar] = pure_ysh.Shvar(mem, search_path, cmd_ev)
584 b[builtin_i.ctx] = pure_ysh.Ctx(mem, cmd_ev)
585 b[builtin_i.push_registers] = pure_ysh.PushRegisters(mem, cmd_ev)
586
587 # Hay
588 b[builtin_i.hay] = hay_ysh.Hay(hay_state, mutable_opts, mem, cmd_ev)
589 b[builtin_i.haynode] = hay_ysh.HayNode_(hay_state, mem, cmd_ev)
590
591 # Interpreter introspection
592 b[builtin_i.type] = meta_osh.Type(procs, aliases, search_path, errfmt)
593 b[builtin_i.builtin] = meta_osh.Builtin(shell_ex, errfmt)
594 b[builtin_i.command] = meta_osh.Command(shell_ex, procs, aliases,
595 search_path)
596 # Part of YSH, but similar to builtin/command
597 b[builtin_i.runproc] = meta_osh.RunProc(shell_ex, procs, errfmt)
598
599 # Meta builtins
600 source_builtin = meta_osh.Source(parse_ctx, search_path, cmd_ev, fd_state,
601 tracer, errfmt, loader)
602 b[builtin_i.source] = source_builtin
603 b[builtin_i.dot] = source_builtin
604 b[builtin_i.eval] = meta_osh.Eval(parse_ctx, exec_opts, cmd_ev, tracer,
605 errfmt)
606
607 # Module builtins
608 guards = {} # type: Dict[str, bool]
609 b[builtin_i.source_guard] = module_ysh.SourceGuard(guards, exec_opts,
610 errfmt)
611 b[builtin_i.is_main] = module_ysh.IsMain(mem)
612 b[builtin_i.use] = module_ysh.Use(mem, errfmt)
613
614 # Errors
615 b[builtin_i.error] = error_ysh.Error()
616 b[builtin_i.failed] = error_ysh.Failed(mem)
617 b[builtin_i.boolstatus] = error_ysh.BoolStatus(shell_ex, errfmt)
618 b[builtin_i.try_] = error_ysh.Try(mutable_opts, mem, cmd_ev, shell_ex,
619 errfmt)
620 b[builtin_i.assert_] = error_ysh.Assert(expr_ev, errfmt)
621
622 # Pure builtins
623 true_ = pure_osh.Boolean(0)
624 b[builtin_i.colon] = true_ # a "special" builtin
625 b[builtin_i.true_] = true_
626 b[builtin_i.false_] = pure_osh.Boolean(1)
627
628 b[builtin_i.alias] = pure_osh.Alias(aliases, errfmt)
629 b[builtin_i.unalias] = pure_osh.UnAlias(aliases, errfmt)
630
631 b[builtin_i.getopts] = pure_osh.GetOpts(mem, errfmt)
632
633 b[builtin_i.shift] = assign_osh.Shift(mem)
634 b[builtin_i.unset] = assign_osh.Unset(mem, procs, unsafe_arith, errfmt)
635
636 b[builtin_i.append] = pure_ysh.Append(mem, errfmt)
637
638 # test / [ differ by need_right_bracket
639 b[builtin_i.test] = bracket_osh.Test(False, exec_opts, mem, errfmt)
640 b[builtin_i.bracket] = bracket_osh.Test(True, exec_opts, mem, errfmt)
641
642 # Output
643 b[builtin_i.echo] = io_osh.Echo(exec_opts)
644 b[builtin_i.printf] = printf_osh.Printf(mem, parse_ctx, unsafe_arith,
645 errfmt)
646 b[builtin_i.write] = io_ysh.Write(mem, errfmt)
647 b[builtin_i.fopen] = io_ysh.Fopen(mem, cmd_ev)
648
649 # (pp output format isn't stable)
650 b[builtin_i.pp] = io_ysh.Pp(expr_ev, mem, errfmt, procs, arena)
651
652 # Input
653 b[builtin_i.cat] = io_osh.Cat() # for $(<file)
654 b[builtin_i.read] = read_osh.Read(splitter, mem, parse_ctx, cmd_ev, errfmt)
655
656 mapfile = io_osh.MapFile(mem, errfmt, cmd_ev)
657 b[builtin_i.mapfile] = mapfile
658 b[builtin_i.readarray] = mapfile
659
660 # Dirs
661 dir_stack = dirs_osh.DirStack()
662 b[builtin_i.cd] = dirs_osh.Cd(mem, dir_stack, cmd_ev, errfmt)
663 b[builtin_i.pushd] = dirs_osh.Pushd(mem, dir_stack, errfmt)
664 b[builtin_i.popd] = dirs_osh.Popd(mem, dir_stack, errfmt)
665 b[builtin_i.dirs] = dirs_osh.Dirs(mem, dir_stack, errfmt)
666 b[builtin_i.pwd] = dirs_osh.Pwd(mem, errfmt)
667
668 b[builtin_i.times] = misc_osh.Times()
669
670 b[builtin_i.json] = json_ysh.Json(mem, errfmt, False)
671 b[builtin_i.json8] = json_ysh.Json(mem, errfmt, True)
672
673 ### Process builtins
674 b[builtin_i.exec_] = process_osh.Exec(mem, ext_prog, fd_state, search_path,
675 errfmt)
676 b[builtin_i.umask] = process_osh.Umask()
677 b[builtin_i.ulimit] = process_osh.Ulimit()
678 b[builtin_i.wait] = process_osh.Wait(waiter, job_list, mem, tracer, errfmt)
679
680 b[builtin_i.jobs] = process_osh.Jobs(job_list)
681 b[builtin_i.fg] = process_osh.Fg(job_control, job_list, waiter)
682 b[builtin_i.bg] = process_osh.Bg(job_list)
683
684 # Could be in process_ysh
685 b[builtin_i.fork] = process_osh.Fork(shell_ex)
686 b[builtin_i.forkwait] = process_osh.ForkWait(shell_ex)
687
688 # Interactive builtins depend on readline
689 b[builtin_i.bind] = readline_osh.Bind(readline, errfmt)
690 b[builtin_i.history] = readline_osh.History(readline, sh_files, errfmt,
691 mylib.Stdout())
692
693 # Completion
694 spec_builder = completion_osh.SpecBuilder(cmd_ev, parse_ctx, word_ev,
695 splitter, comp_lookup, help_data,
696 errfmt)
697 complete_builtin = completion_osh.Complete(spec_builder, comp_lookup)
698 b[builtin_i.complete] = complete_builtin
699 b[builtin_i.compgen] = completion_osh.CompGen(spec_builder)
700 b[builtin_i.compopt] = completion_osh.CompOpt(compopt_state, errfmt)
701 b[builtin_i.compadjust] = completion_osh.CompAdjust(mem)
702
703 comp_ev = word_eval.CompletionWordEvaluator(mem, exec_opts, mutable_opts,
704 tilde_ev, splitter, errfmt)
705
706 comp_ev.arith_ev = arith_ev
707 comp_ev.expr_ev = expr_ev
708 comp_ev.prompt_ev = prompt_ev
709 comp_ev.CheckCircularDeps()
710
711 root_comp = completion.RootCompleter(comp_ev, mem, comp_lookup,
712 compopt_state, comp_ui_state,
713 comp_ctx, debug_f)
714 b[builtin_i.compexport] = completion_ysh.CompExport(root_comp)
715
716 #
717 # Initialize Builtin-in Methods
718 #
719
720 methods[value_e.Str] = {
721 'startsWith': method_str.HasAffix(method_str.START),
722 'endsWith': method_str.HasAffix(method_str.END),
723 'trim': method_str.Trim(method_str.START | method_str.END),
724 'trimStart': method_str.Trim(method_str.START),
725 'trimEnd': method_str.Trim(method_str.END),
726 'upper': method_str.Upper(),
727 'lower': method_str.Lower(),
728
729 # finds a substring, optional position to start at
730 'find': None,
731
732 # replace substring, OR an eggex
733 # takes count=3, the max number of replacements to do.
734 'replace': method_str.Replace(mem, expr_ev),
735
736 # Like Python's re.search, except we put it on the string object
737 # It's more consistent with Str->find(substring, pos=0)
738 # It returns value.Match() rather than an integer
739 'search': method_str.SearchMatch(method_str.SEARCH),
740
741 # like Python's re.match()
742 'leftMatch': method_str.SearchMatch(method_str.LEFT_MATCH),
743
744 # like Python's re.fullmatch(), not sure if we really need it
745 'fullMatch': None,
746 }
747 methods[value_e.Dict] = {
748 'get': None, # doesn't raise an error
749 'erase': None, # ensures it doesn't exist
750 'keys': method_dict.Keys(),
751 'values': method_dict.Values(),
752
753 # I think items() isn't as necessary because dicts are ordered?
754 # YSH code shouldn't use the List of Lists representation.
755
756 # could be d->tally() or d->increment(), but inc() is short
757 #
758 # call d->inc('mycounter')
759 # call d->inc('mycounter', 3)
760 'inc': None,
761
762 # call d->accum('mygroup', 'value')
763 'accum': None,
764 }
765 methods[value_e.List] = {
766 'reverse': method_list.Reverse(),
767 'append': method_list.Append(),
768 'extend': method_list.Extend(),
769 'pop': method_list.Pop(),
770 'insert': None, # insert object before index
771 'remove': None, # insert object before index
772 'indexOf': method_list.IndexOf(), # return first index of value, or -1
773 # Python list() has index(), which raises ValueError
774 # But this is consistent with Str->find(), and doesn't
775 # use exceptions
776 'join': func_misc.Join(), # both a method and a func
777 }
778
779 methods[value_e.Match] = {
780 'group': func_eggex.MatchMethod(func_eggex.G, expr_ev),
781 'start': func_eggex.MatchMethod(func_eggex.S, None),
782 'end': func_eggex.MatchMethod(func_eggex.E, None),
783 }
784
785 methods[value_e.IO] = {
786 # io->eval(myblock) is the functional version of eval (myblock)
787 # Should we also have expr->eval() instead of evalExpr?
788 'eval': method_io.Eval(),
789
790 # identical to command sub
791 'captureStdout': method_io.CaptureStdout(),
792 'promptVal': method_io.PromptVal(),
793 'time': method_io.Time(),
794 'strftime': method_io.Strftime(),
795 }
796
797 methods[value_e.Place] = {
798 # instead of setplace keyword
799 'setValue': method_other.SetValue(mem),
800 }
801
802 methods[value_e.Command] = {
803 # var x = ^(echo hi)
804 # Export source code and line number
805 # Useful for test frameworks and so forth
806 'export': None,
807 }
808
809 #
810 # Initialize Built-in Funcs
811 #
812
813 parse_hay = func_hay.ParseHay(fd_state, parse_ctx, errfmt)
814 eval_hay = func_hay.EvalHay(hay_state, mutable_opts, mem, cmd_ev)
815 hay_func = func_hay.HayFunc(hay_state)
816
817 _SetGlobalFunc(mem, 'parseHay', parse_hay)
818 _SetGlobalFunc(mem, 'evalHay', eval_hay)
819 _SetGlobalFunc(mem, '_hay', hay_func)
820
821 _SetGlobalFunc(mem, 'len', func_misc.Len())
822 _SetGlobalFunc(mem, 'type', func_misc.Type())
823
824 g = func_eggex.MatchFunc(func_eggex.G, expr_ev, mem)
825 _SetGlobalFunc(mem, '_group', g)
826 _SetGlobalFunc(mem, '_match', g) # TODO: remove this backward compat alias
827 _SetGlobalFunc(mem, '_start', func_eggex.MatchFunc(func_eggex.S, None,
828 mem))
829 _SetGlobalFunc(mem, '_end', func_eggex.MatchFunc(func_eggex.E, None, mem))
830
831 _SetGlobalFunc(mem, 'evalExpr', func_misc.EvalExpr(expr_ev))
832
833 # type conversions
834 _SetGlobalFunc(mem, 'bool', func_misc.Bool())
835 _SetGlobalFunc(mem, 'int', func_misc.Int())
836 _SetGlobalFunc(mem, 'float', func_misc.Float())
837 _SetGlobalFunc(mem, 'str', func_misc.Str_())
838 _SetGlobalFunc(mem, 'list', func_misc.List_())
839 _SetGlobalFunc(mem, 'dict', func_misc.Dict_())
840
841 _SetGlobalFunc(mem, 'runes', func_misc.Runes())
842 _SetGlobalFunc(mem, 'encodeRunes', func_misc.EncodeRunes())
843 _SetGlobalFunc(mem, 'bytes', func_misc.Bytes())
844 _SetGlobalFunc(mem, 'encodeBytes', func_misc.EncodeBytes())
845
846 # Str
847 #_SetGlobalFunc(mem, 'strcmp', None)
848 # TODO: This should be Python style splitting
849 _SetGlobalFunc(mem, 'split', func_misc.Split(splitter))
850 _SetGlobalFunc(mem, 'shSplit', func_misc.Split(splitter))
851
852 # Float
853 _SetGlobalFunc(mem, 'floatsEqual', func_misc.FloatsEqual())
854
855 # List
856 _SetGlobalFunc(mem, 'join', func_misc.Join())
857 _SetGlobalFunc(mem, 'maybe', func_misc.Maybe())
858 _SetGlobalFunc(mem, 'glob', func_misc.Glob(globber))
859
860 _SetGlobalFunc(mem, 'shvarGet', func_misc.Shvar_get(mem))
861 _SetGlobalFunc(mem, 'getVar', func_misc.GetVar(mem))
862
863 # Serialize
864 _SetGlobalFunc(mem, 'toJson8', func_misc.ToJson8(True))
865 _SetGlobalFunc(mem, 'toJson', func_misc.ToJson8(False))
866
867 _SetGlobalFunc(mem, 'fromJson8', func_misc.FromJson8(True))
868 _SetGlobalFunc(mem, 'fromJson', func_misc.FromJson8(False))
869
870 # Demos
871 _SetGlobalFunc(mem, '_a2sp', func_misc.BashArrayToSparse())
872 _SetGlobalFunc(mem, '_opsp', func_misc.SparseOp())
873
874 mem.SetNamed(location.LName('_io'), global_io, scope_e.GlobalOnly)
875 mem.SetNamed(location.LName('_guts'), global_guts, scope_e.GlobalOnly)
876
877 mem.SetNamed(location.LName('stdin'), value.Stdin, scope_e.GlobalOnly)
878
879 #
880 # Is the shell interactive?
881 #
882
883 # History evaluation is a no-op if readline is None.
884 hist_ev = history.Evaluator(readline, hist_ctx, debug_f)
885
886 if flag.c is not None:
887 src = source.CFlag # type: source_t
888 line_reader = reader.StringLineReader(flag.c,
889 arena) # type: reader._Reader
890 if flag.i: # -c and -i can be combined
891 mutable_opts.set_interactive()
892
893 elif flag.i: # force interactive
894 src = source.Stdin(' -i')
895 line_reader = reader.InteractiveLineReader(arena, prompt_ev, hist_ev,
896 readline, prompt_state)
897 mutable_opts.set_interactive()
898
899 else:
900 if script_name is None:
901 if flag.headless:
902 src = source.Headless
903 line_reader = None # unused!
904 # Not setting '-i' flag for now. Some people's bashrc may want it?
905 else:
906 stdin_ = mylib.Stdin()
907 # --tool never starts a prompt
908 if len(flag.tool) == 0 and stdin_.isatty():
909 src = source.Interactive
910 line_reader = reader.InteractiveLineReader(
911 arena, prompt_ev, hist_ev, readline, prompt_state)
912 mutable_opts.set_interactive()
913 else:
914 src = source.Stdin('')
915 line_reader = reader.FileLineReader(stdin_, arena)
916 else:
917 src = source.MainFile(script_name)
918 try:
919 f = fd_state.Open(script_name)
920 except (IOError, OSError) as e:
921 print_stderr("%s: Couldn't open %r: %s" %
922 (lang, script_name, posix.strerror(e.errno)))
923 return 1
924 line_reader = reader.FileLineReader(f, arena)
925
926 # Pretend it came from somewhere else
927 if flag.location_str is not None:
928 src = source.Synthetic(flag.location_str)
929 assert line_reader is not None
930 location_start_line = mops.BigTruncate(flag.location_start_line)
931 if location_start_line != -1:
932 line_reader.SetLineOffset(location_start_line)
933
934 arena.PushSource(src)
935
936 # Calculate ~/.config/oils/oshrc or yshrc. Used for both -i and --headless
937 # We avoid cluttering the user's home directory. Some users may want to ln
938 # -s ~/.config/oils/oshrc ~/oshrc or ~/.oshrc.
939
940 # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other
941
942 config_dir = '.config/oils'
943 rc_paths = [] # type: List[str]
944 if not flag.norc and (flag.headless or exec_opts.interactive()):
945 # User's rcfile comes FIRST. Later we can add an 'after-rcdir' hook
946 rc_path = flag.rcfile
947 if rc_path is None:
948 rc_paths.append(
949 os_path.join(home_dir, '%s/%src' % (config_dir, lang)))
950 else:
951 rc_paths.append(rc_path)
952
953 # Load all files in ~/.config/oil/oshrc.d or oilrc.d
954 # This way "installers" can avoid mutating oshrc directly
955
956 rc_dir = flag.rcdir
957 if rc_dir is None:
958 rc_dir = os_path.join(home_dir, '%s/%src.d' % (config_dir, lang))
959
960 rc_paths.extend(libc.glob(os_path.join(rc_dir, '*')))
961 else:
962 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
963 print_stderr('%s warning: --rcfile ignored with --norc' % lang)
964 if flag.rcdir is not None:
965 print_stderr('%s warning: --rcdir ignored with --norc' % lang)
966
967 # Initialize even in non-interactive shell, for 'compexport'
968 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
969
970 if flag.headless:
971 state.InitInteractive(mem)
972 mutable_opts.set_redefine_proc_func()
973 mutable_opts.set_redefine_module()
974
975 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
976 for rc_path in rc_paths:
977 with state.ctx_ThisDir(mem, rc_path):
978 try:
979 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
980 cmd_ev, errfmt)
981 except util.UserExit as e:
982 return e.status
983
984 loop = main_loop.Headless(cmd_ev, parse_ctx, errfmt)
985 try:
986 # TODO: What other exceptions happen here?
987 status = loop.Loop()
988 except util.UserExit as e:
989 status = e.status
990
991 # Same logic as interactive shell
992 mut_status = IntParamBox(status)
993 cmd_ev.MaybeRunExitTrap(mut_status)
994 status = mut_status.i
995
996 return status
997
998 # Note: headless mode above doesn't use c_parser
999 assert line_reader is not None
1000 c_parser = parse_ctx.MakeOshParser(line_reader)
1001
1002 if exec_opts.interactive():
1003 state.InitInteractive(mem)
1004 # bash: 'set -o emacs' is the default only in the interactive shell
1005 mutable_opts.set_emacs()
1006 mutable_opts.set_redefine_proc_func()
1007 mutable_opts.set_redefine_module()
1008
1009 if readline:
1010 term_width = 0
1011 if flag.completion_display == 'nice':
1012 try:
1013 term_width = libc.get_terminal_width()
1014 except (IOError, OSError): # stdin not a terminal
1015 pass
1016
1017 if term_width != 0:
1018 display = comp_ui.NiceDisplay(
1019 term_width, comp_ui_state, prompt_state, debug_f, readline,
1020 signal_safe) # type: comp_ui._IDisplay
1021 else:
1022 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1023 debug_f)
1024
1025 comp_ui.InitReadline(readline, sh_files.HistoryFile(), root_comp,
1026 display, debug_f)
1027
1028 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
1029 if flag.completion_demo:
1030 _CompletionDemo(comp_lookup)
1031
1032 else: # Without readline module
1033 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1034 debug_f)
1035
1036 process.InitInteractiveShell() # Set signal handlers
1037
1038 # The interactive shell leads a process group which controls the terminal.
1039 # It MUST give up the terminal afterward, otherwise we get SIGTTIN /
1040 # SIGTTOU bugs.
1041 with process.ctx_TerminalControl(job_control, errfmt):
1042
1043 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1044 for rc_path in rc_paths:
1045 with state.ctx_ThisDir(mem, rc_path):
1046 try:
1047 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1048 cmd_ev, errfmt)
1049 except util.UserExit as e:
1050 return e.status
1051
1052 assert line_reader is not None
1053 line_reader.Reset() # After sourcing startup file, render $PS1
1054
1055 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, cmd_ev, errfmt)
1056 try:
1057 status = main_loop.Interactive(flag, cmd_ev, c_parser, display,
1058 prompt_plugin, waiter, errfmt)
1059 except util.UserExit as e:
1060 status = e.status
1061
1062 mut_status = IntParamBox(status)
1063 cmd_ev.MaybeRunExitTrap(mut_status)
1064 status = mut_status.i
1065
1066 if readline:
1067 hist_file = sh_files.HistoryFile()
1068 if hist_file is not None:
1069 try:
1070 readline.write_history_file(hist_file)
1071 except (IOError, OSError):
1072 pass
1073
1074 return status
1075
1076 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
1077 print_stderr('%s warning: --rcfile ignored in non-interactive shell' %
1078 lang)
1079 if flag.rcdir is not None:
1080 print_stderr('%s warning: --rcdir ignored in non-interactive shell' %
1081 lang)
1082
1083 #
1084 # Tools that use the OSH/YSH parsing mode, etc.
1085 #
1086
1087 # flag.tool is '' if nothing is passed
1088 # osh --tool syntax-tree is equivalent to osh -n --one-pass-parse
1089 tool_name = 'syntax-tree' if exec_opts.noexec() else flag.tool
1090
1091 if len(tool_name):
1092 # Don't save tokens becaues it's slow
1093 if tool_name != 'syntax-tree':
1094 arena.SaveTokens()
1095
1096 try:
1097 node = main_loop.ParseWholeFile(c_parser)
1098 except error.Parse as e:
1099 errfmt.PrettyPrintError(e)
1100 return 2
1101
1102 if tool_name == 'syntax-tree':
1103 ui.PrintAst(node, flag)
1104
1105 elif tool_name == 'tokens':
1106 ysh_ify.PrintTokens(arena)
1107
1108 elif tool_name == 'lossless-cat': # for test/lossless.sh
1109 ysh_ify.LosslessCat(arena)
1110
1111 elif tool_name == 'fmt':
1112 fmt.Format(arena, node)
1113
1114 elif tool_name == 'test':
1115 raise AssertionError('TODO')
1116
1117 elif tool_name == 'ysh-ify':
1118 ysh_ify.Ysh_ify(arena, node)
1119
1120 elif tool_name == 'deps':
1121 if mylib.PYTHON:
1122 deps.Deps(node)
1123
1124 else:
1125 raise AssertionError(tool_name) # flag parser validated it
1126
1127 return 0
1128
1129 #
1130 # Run a shell script
1131 #
1132
1133 with state.ctx_ThisDir(mem, script_name):
1134 try:
1135 status = main_loop.Batch(cmd_ev,
1136 c_parser,
1137 errfmt,
1138 cmd_flags=cmd_eval.IsMainProgram)
1139 except util.UserExit as e:
1140 status = e.status
1141 mut_status = IntParamBox(status)
1142 cmd_ev.MaybeRunExitTrap(mut_status)
1143
1144 multi_trace.WriteDumps()
1145
1146 # NOTE: We haven't closed the file opened with fd_state.Open
1147 return mut_status.i