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

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