OILS / core / shell.py View on Github | oils.pub

1307 lines, 844 significant
1"""shell.py -- assemble the shell interpreter"""
2from __future__ import print_function
3
4from errno import ENOENT
5import time as time_
6
7from _devbuild.gen import arg_types
8from _devbuild.gen.option_asdl import option_i, builtin_i
9from _devbuild.gen.syntax_asdl import (loc, source, source_t, IntParamBox,
10 debug_frame, debug_frame_t)
11from _devbuild.gen.value_asdl import (value, value_e, value_t, value_str, Obj)
12from core import alloc
13from core import comp_ui
14from core import dev
15from core import error
16from core import executor
17from core import completion
18from core import main_loop
19from core import optview
20from core import process
21from core import pyutil
22from core import sh_init
23from core import state
24from display import ui
25from core import util
26from core import vm
27
28from frontend import args
29from frontend import flag_def # side effect: flags are defined!
30
31unused1 = flag_def
32from frontend import flag_util
33from frontend import reader
34from frontend import parse_lib
35
36from builtin import assign_osh
37from builtin import bracket_osh
38from builtin import completion_osh
39from builtin import completion_ysh
40from builtin import dirs_osh
41from builtin import error_ysh
42from builtin import hay_ysh
43from builtin import io_osh
44from builtin import io_ysh
45from builtin import json_ysh
46from builtin import meta_oils
47from builtin import misc_osh
48from builtin import module_ysh
49from builtin import printf_osh
50from builtin import private_ysh
51from builtin import process_osh
52from builtin import pure_osh
53from builtin import pure_ysh
54from builtin import readline_osh
55from builtin import read_osh
56from builtin import trap_osh
57
58from builtin import func_eggex
59from builtin import func_hay
60from builtin import func_misc
61from builtin import func_reflect
62
63from builtin import method_dict
64from builtin import method_io
65from builtin import method_list
66from builtin import method_other
67from builtin import method_str
68from builtin import method_type
69
70from osh import cmd_eval
71from osh import glob_
72from osh import prompt
73from osh import sh_expr_eval
74from osh import split
75from osh import word_eval
76
77from mycpp import iolib
78from mycpp import mops
79from mycpp import mylib
80from mycpp.mylib import NewDict, 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
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, False)
163
164 with alloc.ctx_SourceCode(arena, source.MainFile(rc_path)):
165 # Note: bash keep going after parse error in startup file. Should we
166 # have a strict mode for this?
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 _AddBuiltinFunc(mem, name, func):
204 # type: (state.Mem, str, vm._Callable) -> None
205 assert isinstance(func, vm._Callable), func
206 mem.AddBuiltin(name, value.BuiltinFunc(func))
207
208
209def InitAssignmentBuiltins(
210 mem, # type: state.Mem
211 procs, # type: state.Procs
212 exec_opts, # type: optview.Exec
213 arith_ev, # type: sh_expr_eval.ArithEvaluator
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, exec_opts, arith_ev, 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, arith_ev, errfmt)
226 assign_b[builtin_i.readonly] = assign_osh.Readonly(mem, arith_ev, errfmt)
227
228 return assign_b
229
230
231def Main(
232 lang, # type: str
233 arg_r, # type: args.Reader
234 environ, # type: Dict[str, str]
235 login_shell, # type: bool
236 loader, # type: pyutil._ResourceLoader
237 readline, # type: Optional[Readline]
238 bash_compat=True, # type: bool
239):
240 # type: (...) -> int
241 """The full shell lifecycle. Used by bin/osh and bin/ysh.
242
243 Args:
244 lang: 'osh' or 'ysh'
245 login_shell: Was - on argv[0]?
246 loader: to get help, version, grammar, etc.
247 readline: optional GNU readline
248 """
249 # Differences between osh and ysh:
250 # - oshrc vs yshrc
251 # - shopt -s ysh:all
252 # - Prompt
253 # - --help
254
255 argv0 = arg_r.Peek()
256 assert argv0 is not None
257 arg_r.Next()
258
259 assert lang in ('osh', 'ysh'), lang
260
261 try:
262 attrs = flag_util.ParseMore('main', arg_r)
263 except error.Usage as e:
264 print_stderr('%s usage error: %s' % (lang, e.msg))
265 return 2
266 flag = arg_types.main(attrs.attrs)
267
268 arena = alloc.Arena()
269 errfmt = ui.ErrorFormatter()
270
271 if flag.help:
272 util.HelpFlag(loader, '%s-usage' % lang, mylib.Stdout())
273 return 0
274 if flag.version:
275 util.VersionFlag(loader, mylib.Stdout())
276 return 0
277
278 if flag.tool == 'cat-em':
279 paths = arg_r.Rest()
280
281 status = 0
282 for p in paths:
283 try:
284 contents = loader.Get(p)
285 print(contents)
286 except (OSError, IOError):
287 print_stderr("cat-em: %r not found" % p)
288 status = 1
289 return status
290
291 script_name = arg_r.Peek() # type: Optional[str]
292 arg_r.Next()
293
294 if script_name is None:
295 dollar0 = argv0
296 # placeholder for -c or stdin (depending on flag.c)
297 frame0 = debug_frame.Dummy # type: debug_frame_t
298 else:
299 dollar0 = script_name
300 frame0 = debug_frame.MainFile(script_name)
301
302 debug_stack = [frame0]
303
304 argv = arg_r.Rest()
305 env_dict = NewDict() # type: Dict[str, value_t]
306 defaults = NewDict() # type: Dict[str, value_t]
307 mem = state.Mem(dollar0,
308 argv,
309 arena,
310 debug_stack,
311 env_dict,
312 defaults=defaults)
313
314 opt_hook = ShellOptHook(readline)
315 # Note: only MutableOpts needs mem, so it's not a true circular dep.
316 parse_opts, exec_opts, mutable_opts = state.MakeOpts(
317 mem, environ, opt_hook)
318 mem.exec_opts = exec_opts # circular dep
319
320 # Set these BEFORE processing flags, so they can be overridden.
321 if lang == 'ysh':
322 mutable_opts.SetAnyOption('ysh:all', True)
323
324 pure_osh.SetOptionsFromFlags(mutable_opts, attrs.opt_changes,
325 attrs.shopt_changes)
326
327 version_str = pyutil.GetVersion(loader)
328 sh_init.InitBuiltins(mem, version_str, defaults)
329 sh_init.InitDefaultVars(mem, argv)
330 if bash_compat:
331 # 2025-09: bash 5.3 is the latest version; can increase this with
332 # future Oils releases
333 state.SetGlobalString(mem, 'BASH_VERSION', '5.3')
334 # major minor patch build release-status machine-type
335 state.SetGlobalArray(mem, 'BASH_VERSINFO',
336 ['5', '3', '0', '0', 'release', 'unknown'])
337
338 sh_init.CopyVarsFromEnv(exec_opts, environ, mem)
339
340 # PATH PWD, etc. must be set after CopyVarsFromEnv()
341 # Also mutate options from SHELLOPTS, if set
342 sh_init.InitVarsAfterEnv(mem, mutable_opts)
343
344 if attrs.show_options: # special case: sh -o
345 pure_osh.ShowOptions(mutable_opts, [])
346 return 0
347
348 # feedback between runtime and parser
349 aliases = NewDict() # type: Dict[str, str]
350
351 ysh_grammar = pyutil.LoadYshGrammar(loader)
352
353 if flag.do_lossless and not exec_opts.noexec():
354 raise error.Usage('--one-pass-parse requires noexec (-n)', loc.Missing)
355
356 # Tools always use one pass parse
357 # Note: osh --tool syntax-tree is like osh -n --one-pass-parse
358 do_lossless = True if len(flag.tool) else flag.do_lossless
359
360 parse_ctx = parse_lib.ParseContext(arena,
361 parse_opts,
362 aliases,
363 ysh_grammar,
364 do_lossless=do_lossless)
365
366 # Three ParseContext instances SHARE aliases.
367 comp_arena = alloc.Arena()
368 comp_arena.PushSource(source.Unused('completion'))
369 trail1 = parse_lib.Trail()
370 # do_lossless needs to be turned on to complete inside backticks. TODO:
371 # fix the issue where ` gets erased because it's not part of
372 # set_completer_delims().
373 comp_ctx = parse_lib.ParseContext(comp_arena,
374 parse_opts,
375 aliases,
376 ysh_grammar,
377 do_lossless=True)
378 comp_ctx.Init_Trail(trail1)
379
380 hist_arena = alloc.Arena()
381 hist_arena.PushSource(source.Unused('history'))
382 trail2 = parse_lib.Trail()
383 hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases,
384 ysh_grammar)
385 hist_ctx.Init_Trail(trail2)
386
387 # Deps helps manages dependencies. These dependencies are circular:
388 # - cmd_ev and word_ev, arith_ev -- for command sub, arith sub
389 # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 ))
390 # - cmd_ev and builtins (which execute code, like eval)
391 # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P
392 cmd_deps = cmd_eval.Deps()
393 cmd_deps.mutable_opts = mutable_opts
394
395 job_control = process.JobControl()
396 job_list = process.JobList()
397 fd_state = process.FdState(errfmt, job_control, job_list, mem, None, None,
398 exec_opts)
399
400 my_pid = posix.getpid()
401
402 debug_path = ''
403 debug_dir = environ.get('OILS_DEBUG_DIR')
404 if flag.debug_file is not None:
405 # --debug-file takes precedence over OSH_DEBUG_DIR
406 debug_path = flag.debug_file
407 elif debug_dir is not None:
408 debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid)
409
410 if len(debug_path):
411 # This will be created as an empty file if it doesn't exist, or it could be
412 # a pipe.
413 try:
414 debug_f = util.DebugFile(
415 fd_state.OpenForWrite(debug_path)) # type: util._DebugFile
416 except (IOError, OSError) as e:
417 print_stderr("%s: Couldn't open %r: %s" %
418 (lang, debug_path, posix.strerror(e.errno)))
419 return 2
420 else:
421 debug_f = util.NullDebugFile()
422
423 if flag.xtrace_to_debug_file:
424 trace_f = debug_f
425 else:
426 trace_f = util.DebugFile(mylib.Stderr())
427
428 trace_dir = environ.get('OILS_TRACE_DIR', '')
429 dumps = environ.get('OILS_TRACE_DUMPS', '')
430 streams = environ.get('OILS_TRACE_STREAMS', '')
431 multi_trace = dev.MultiTracer(my_pid, trace_dir, dumps, streams, fd_state)
432
433 tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, trace_f,
434 multi_trace)
435 fd_state.tracer = tracer # circular dep
436
437 signal_safe = iolib.InitSignalSafe()
438 trap_state = trap_osh.TrapState(signal_safe)
439
440 waiter = process.Waiter(job_list, exec_opts, signal_safe, tracer)
441 fd_state.waiter = waiter
442
443 cmd_deps.debug_f = debug_f
444
445 cflow_builtin = cmd_eval.ControlFlowBuiltin(mem, exec_opts, tracer, errfmt)
446 cmd_deps.cflow_builtin = cflow_builtin
447
448 now = time_.time()
449 iso_stamp = time_.strftime("%Y-%m-%d %H:%M:%S", time_.localtime(now))
450
451 argv_buf = mylib.BufWriter()
452 dev.PrintShellArgv(arg_r.argv, argv_buf)
453
454 debug_f.writeln('%s [%d] Oils started with argv %s' %
455 (iso_stamp, my_pid, argv_buf.getvalue()))
456 if len(debug_path):
457 debug_f.writeln('Writing logs to %r' % debug_path)
458
459 interp = environ.get('OILS_HIJACK_SHEBANG', '')
460 search_path = executor.SearchPath(mem, exec_opts)
461 ext_prog = process.ExternalProgram(interp, fd_state, errfmt, debug_f)
462
463 splitter = split.SplitContext(mem)
464 # TODO: This is instantiation is duplicated in osh/word_eval.py
465 globber = glob_.Globber(exec_opts, mem)
466
467 # This could just be OILS_TRACE_DUMPS='crash:argv0'
468 crash_dump_dir = environ.get('OILS_CRASH_DUMP_DIR', '')
469 cmd_deps.dumper = dev.CrashDumper(crash_dump_dir, fd_state)
470
471 comp_lookup = completion.Lookup()
472
473 # Various Global State objects to work around readline interfaces
474 compopt_state = completion.OptionState()
475
476 comp_ui_state = comp_ui.State()
477 prompt_state = comp_ui.PromptState()
478
479 # The login program is supposed to set $HOME
480 # https://superuser.com/questions/271925/where-is-the-home-environment-variable-set
481 # state.InitMem(mem) must happen first
482 tilde_ev = word_eval.TildeEvaluator(mem, exec_opts)
483 home_dir = tilde_ev.GetMyHomeDir()
484 if home_dir is None:
485 # TODO: print errno from getpwuid()
486 print_stderr("%s: Failed to get home dir from $HOME or getpwuid()" %
487 lang)
488 return 1
489
490 sh_files = sh_init.ShellFiles(lang, home_dir, mem, flag)
491
492 #
493 # Executor and Evaluators (are circularly dependent)
494 #
495
496 # Global proc namespace. Funcs are defined in the common variable
497 # namespace.
498 procs = state.Procs(mem) # type: state.Procs
499
500 builtins = {} # type: Dict[int, vm._Builtin]
501
502 # e.g. s.startswith()
503 methods = {} # type: Dict[int, Dict[str, vm._Callable]]
504
505 hay_state = hay_ysh.HayState()
506
507 shell_ex = executor.ShellExecutor(mem, exec_opts, mutable_opts, procs,
508 hay_state, builtins, tracer, errfmt,
509 search_path, ext_prog, waiter,
510 job_control, job_list, fd_state,
511 trap_state)
512
513 pure_ex = executor.PureExecutor(mem, exec_opts, mutable_opts, procs,
514 hay_state, builtins, tracer, errfmt)
515
516 arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, mutable_opts,
517 parse_ctx, errfmt)
518 bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, mutable_opts,
519 parse_ctx, errfmt)
520 expr_ev = expr_eval.ExprEvaluator(mem, mutable_opts, methods, splitter,
521 errfmt)
522 word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, mutable_opts,
523 tilde_ev, splitter, errfmt)
524
525 assign_b = InitAssignmentBuiltins(mem, procs, exec_opts, arith_ev, errfmt)
526 cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_b,
527 arena, cmd_deps, trap_state,
528 signal_safe)
529
530 # PromptEvaluator rendering is needed in non-interactive shells for @P.
531 prompt_ev = prompt.Evaluator(lang, version_str, parse_ctx, mem)
532
533 io_methods = NewDict() # type: Dict[str, value_t]
534 io_methods['promptVal'] = value.BuiltinFunc(method_io.PromptVal(prompt_ev))
535
536 # The M/ prefix means it's io->eval()
537 io_methods['M/eval'] = value.BuiltinFunc(
538 method_io.Eval(mem, cmd_ev, None, method_io.EVAL_NULL))
539 io_methods['M/evalExpr'] = value.BuiltinFunc(
540 method_io.EvalExpr(expr_ev, None, None))
541
542 # Identical to command sub
543 io_methods['captureStdout'] = value.BuiltinFunc(
544 method_io.CaptureStdout(mem, shell_ex))
545 # Like captureStdout but capture stderr, too
546 io_methods['captureAll'] = value.BuiltinFunc(
547 method_io.CaptureAll(mem, shell_ex))
548
549 # TODO: remove these 2 deprecated methods
550 io_methods['M/evalToDict'] = value.BuiltinFunc(
551 method_io.Eval(mem, cmd_ev, None, method_io.EVAL_DICT))
552 io_methods['M/evalInFrame'] = value.BuiltinFunc(
553 method_io.EvalInFrame(mem, cmd_ev))
554
555 io_methods['time'] = value.BuiltinFunc(method_io.Time())
556 io_methods['strftime'] = value.BuiltinFunc(method_io.Strftime())
557 io_methods['glob'] = value.BuiltinFunc(
558 method_io.Glob(globber, is_method=True))
559 io_methods['libcGlob'] = value.BuiltinFunc(method_io.LibcGlob(globber))
560
561 io_props = {'stdin': value.Stdin} # type: Dict[str, value_t]
562 io_obj = Obj(Obj(None, io_methods), io_props)
563
564 vm_methods = NewDict() # type: Dict[str, value_t]
565 # These are methods, not free functions, because they reflect VM state
566 vm_methods['getFrame'] = value.BuiltinFunc(func_reflect.GetFrame(mem))
567 vm_methods['getDebugStack'] = value.BuiltinFunc(
568 func_reflect.GetDebugStack(mem))
569 vm_methods['id'] = value.BuiltinFunc(func_reflect.Id())
570
571 vm_props = NewDict() # type: Dict[str, value_t]
572 vm_obj = Obj(Obj(None, vm_methods), vm_props)
573
574 # Add basic type objects for flag parser
575 # flag -v --verbose (Bool, help='foo')
576 #
577 # TODO:
578 # - Add other types like Dict, CommandFlag
579 # - Obj(first, rest)
580 # - List() Dict() Obj() can do shallow copy with __call__
581
582 # - type(x) should return these Obj, or perhaps typeObj(x)
583 # - __str__ method for echo $[type(x)] ?
584
585 # TODO: List and Dict could be the only ones with __index__?
586 i_func = method_type.Index__()
587 type_m = NewDict() # type: Dict[str, value_t]
588 type_m['__index__'] = value.BuiltinFunc(i_func)
589 type_obj_methods = Obj(None, type_m)
590
591 # Note: Func[Int -> Int] is something we should do?
592 for tag in [
593 value_e.Bool,
594 value_e.Int,
595 value_e.Float,
596 value_e.Str,
597 value_e.List,
598 value_e.Dict,
599 ]:
600 type_name = value_str(tag, dot=False)
601 #log('%s %s' , type_name, tag)
602 type_obj = Obj(type_obj_methods, {'name': value.Str(type_name)})
603 mem.AddBuiltin(type_name, type_obj)
604
605 # Initialize Obj
606 tag = value_e.Obj
607 type_name = value_str(tag, dot=False)
608
609 # TODO: change Obj.new to __call__
610 type_props = NewDict() # type: Dict[str, value_t]
611 type_props['name'] = value.Str(type_name)
612 type_props['new'] = value.BuiltinFunc(func_misc.Obj_call())
613 type_obj = Obj(type_obj_methods, type_props)
614
615 mem.AddBuiltin(type_name, type_obj)
616
617 # Wire up circular dependencies.
618 vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex,
619 pure_ex, prompt_ev, io_obj, tracer)
620
621 unsafe_arith = sh_expr_eval.UnsafeArith(mem, exec_opts, mutable_opts,
622 parse_ctx, arith_ev, errfmt)
623 vm.InitUnsafeArith(mem, word_ev, unsafe_arith)
624
625 #
626 # Initialize Built-in Procs
627 #
628
629 b = builtins # short alias for initialization
630
631 if mylib.PYTHON:
632 if help_meta:
633 help_data = help_meta.TopicMetadata()
634 else:
635 help_data = NewDict() # minimal build
636 else:
637 help_data = help_meta.TopicMetadata()
638 b[builtin_i.help] = misc_osh.Help(lang, loader, help_data, errfmt)
639
640 # Control flow
641 b[builtin_i.break_] = cflow_builtin
642 b[builtin_i.continue_] = cflow_builtin
643 b[builtin_i.return_] = cflow_builtin
644 b[builtin_i.exit] = cflow_builtin
645
646 # Interpreter state
647 b[builtin_i.set] = pure_osh.Set(mutable_opts, mem)
648 b[builtin_i.shopt] = pure_osh.Shopt(exec_opts, mutable_opts, cmd_ev, mem,
649 environ)
650
651 b[builtin_i.hash] = pure_osh.Hash(search_path) # not really pure
652 b[builtin_i.trap] = trap_osh.Trap(trap_state, parse_ctx, exec_opts, tracer,
653 errfmt)
654
655 b[builtin_i.shvar] = pure_ysh.Shvar(mem, search_path, cmd_ev)
656 b[builtin_i.ctx] = pure_ysh.Ctx(mem, cmd_ev)
657 b[builtin_i.push_registers] = pure_ysh.PushRegisters(mem, cmd_ev)
658
659 # Hay
660 b[builtin_i.hay] = hay_ysh.Hay(hay_state, mutable_opts, mem, cmd_ev)
661 b[builtin_i.haynode] = hay_ysh.HayNode_(hay_state, mem, cmd_ev)
662
663 # Interpreter introspection
664 b[builtin_i.type] = meta_oils.Type(procs, aliases, search_path, errfmt)
665 b[builtin_i.builtin] = meta_oils.Builtin(shell_ex, errfmt)
666 b[builtin_i.command] = meta_oils.Command(shell_ex, procs, aliases,
667 search_path)
668 # Part of YSH, but similar to builtin/command
669 b[builtin_i.invoke] = meta_oils.Invoke(shell_ex, procs, aliases,
670 search_path, errfmt)
671 # Note: runproc is like invoke --proc, could be deprecated?
672 b[builtin_i.runproc] = meta_oils.RunProc(shell_ex, procs, errfmt)
673
674 # allows setting ENV and PATH
675 #b[builtin_i.extern_] = meta_oils.Extern(shell_ex, procs, errfmt)
676
677 # Meta builtins
678 module_invoke = module_ysh.ModuleInvoke(cmd_ev, tracer, errfmt)
679 b[builtin_i.use] = meta_oils.ShellFile(parse_ctx,
680 search_path,
681 cmd_ev,
682 fd_state,
683 tracer,
684 errfmt,
685 loader,
686 module_invoke=module_invoke)
687 source_builtin = meta_oils.ShellFile(parse_ctx, search_path, cmd_ev,
688 fd_state, tracer, errfmt, loader)
689 b[builtin_i.source] = source_builtin
690 b[builtin_i.dot] = source_builtin
691 eval_builtin = meta_oils.Eval(parse_ctx, exec_opts, cmd_ev, tracer, errfmt,
692 mem)
693 b[builtin_i.eval] = eval_builtin
694
695 # Module builtins
696 guards = NewDict() # type: Dict[str, bool]
697 b[builtin_i.source_guard] = module_ysh.SourceGuard(guards, exec_opts,
698 errfmt)
699 b[builtin_i.is_main] = module_ysh.IsMain(mem)
700
701 # Errors
702 b[builtin_i.error] = error_ysh.Error()
703 b[builtin_i.failed] = error_ysh.Failed(mem)
704 b[builtin_i.boolstatus] = error_ysh.BoolStatus(shell_ex, errfmt)
705 b[builtin_i.try_] = error_ysh.Try(mutable_opts, mem, cmd_ev, shell_ex,
706 errfmt)
707 b[builtin_i.assert_] = error_ysh.Assert(expr_ev, errfmt)
708
709 # Pure builtins
710 true_ = pure_osh.Boolean(0)
711 b[builtin_i.colon] = true_ # a "special" builtin
712 b[builtin_i.true_] = true_
713 b[builtin_i.false_] = pure_osh.Boolean(1)
714
715 b[builtin_i.alias] = pure_osh.Alias(aliases, errfmt)
716 b[builtin_i.unalias] = pure_osh.UnAlias(aliases, errfmt)
717
718 b[builtin_i.getopts] = pure_osh.GetOpts(mem, errfmt)
719
720 b[builtin_i.shift] = assign_osh.Shift(mem)
721 b[builtin_i.unset] = assign_osh.Unset(mem, procs, unsafe_arith, errfmt)
722
723 b[builtin_i.append] = pure_ysh.Append(mem, errfmt)
724
725 # test / [ differ by need_right_bracket
726 b[builtin_i.test] = bracket_osh.Test(False, exec_opts, mem, errfmt)
727 b[builtin_i.bracket] = bracket_osh.Test(True, exec_opts, mem, errfmt)
728
729 # Output
730 b[builtin_i.echo] = io_osh.Echo(exec_opts)
731 b[builtin_i.printf] = printf_osh.Printf(mem, parse_ctx, unsafe_arith,
732 errfmt)
733 b[builtin_i.write] = io_ysh.Write(mem, errfmt)
734 redir_builtin = io_ysh.RunBlock(mem, cmd_ev) # used only for redirects
735 b[builtin_i.redir] = redir_builtin
736 b[builtin_i.fopen] = redir_builtin # alias for backward compatibility
737
738 # (pp output format isn't stable)
739 b[builtin_i.pp] = io_ysh.Pp(expr_ev, mem, errfmt, procs, arena)
740
741 cat = private_ysh.Cat(errfmt)
742 b[builtin_i.cat] = cat
743 b[builtin_i.read] = read_osh.Read(splitter, mem, parse_ctx, cmd_ev, errfmt)
744
745 # PRIVATE builtins
746 b[builtin_i.sleep] = private_ysh.Sleep(cmd_ev, signal_safe)
747 b[builtin_i.rm] = private_ysh.Rm(errfmt)
748
749 mapfile = io_osh.MapFile(mem, errfmt, cmd_ev)
750 b[builtin_i.mapfile] = mapfile
751 b[builtin_i.readarray] = mapfile
752
753 # Dirs
754 dir_stack = dirs_osh.DirStack()
755 cd_builtin = dirs_osh.Cd(mem, dir_stack, cmd_ev, errfmt)
756 b[builtin_i.cd] = cd_builtin
757 b[builtin_i.chdir] = cd_builtin # alias that dash, mksh, zsh have
758 b[builtin_i.pushd] = dirs_osh.Pushd(mem, dir_stack, errfmt)
759 b[builtin_i.popd] = dirs_osh.Popd(mem, dir_stack, errfmt)
760 b[builtin_i.dirs] = dirs_osh.Dirs(mem, dir_stack, errfmt)
761 b[builtin_i.pwd] = dirs_osh.Pwd(mem, errfmt)
762
763 b[builtin_i.times] = misc_osh.Times()
764
765 b[builtin_i.json] = json_ysh.Json(mem, errfmt, False)
766 b[builtin_i.json8] = json_ysh.Json(mem, errfmt, True)
767
768 ### Process builtins
769 b[builtin_i.exec_] = process_osh.Exec(mem, ext_prog, fd_state, search_path,
770 errfmt)
771 b[builtin_i.umask] = process_osh.Umask()
772 b[builtin_i.ulimit] = process_osh.Ulimit()
773 b[builtin_i.wait] = process_osh.Wait(waiter, job_list, mem, tracer, errfmt)
774
775 b[builtin_i.jobs] = process_osh.Jobs(job_list)
776 b[builtin_i.fg] = process_osh.Fg(job_control, job_list, waiter)
777 b[builtin_i.kill] = process_osh.Kill(job_list)
778 b[builtin_i.bg] = process_osh.Bg(job_list)
779
780 # Could be in process_ysh
781 b[builtin_i.fork] = process_osh.Fork(shell_ex)
782 b[builtin_i.forkwait] = process_osh.ForkWait(shell_ex)
783
784 # Interactive builtins depend on readline
785 bindx_cb = readline_osh.BindXCallback(eval_builtin, mem, errfmt)
786 b[builtin_i.bind] = readline_osh.Bind(readline, errfmt, bindx_cb)
787 b[builtin_i.history] = readline_osh.History(readline, sh_files, errfmt,
788 mylib.Stdout())
789 b[builtin_i.fc] = readline_osh.Fc(exec_opts, readline, mylib.Stdout())
790
791 # Completion
792 spec_builder = completion_osh.SpecBuilder(cmd_ev, parse_ctx, word_ev,
793 splitter, comp_lookup, help_data,
794 errfmt)
795 complete_builtin = completion_osh.Complete(spec_builder, comp_lookup)
796 b[builtin_i.complete] = complete_builtin
797 b[builtin_i.compgen] = completion_osh.CompGen(spec_builder)
798 b[builtin_i.compopt] = completion_osh.CompOpt(compopt_state, errfmt)
799 b[builtin_i.compadjust] = completion_osh.CompAdjust(mem)
800
801 comp_ev = word_eval.CompletionWordEvaluator(mem, exec_opts, mutable_opts,
802 tilde_ev, splitter, errfmt)
803
804 comp_ev.arith_ev = arith_ev
805 comp_ev.expr_ev = expr_ev
806 comp_ev.prompt_ev = prompt_ev
807 comp_ev.CheckCircularDeps()
808
809 root_comp = completion.RootCompleter(comp_ev, mem, comp_lookup,
810 compopt_state, comp_ui_state,
811 comp_ctx, debug_f)
812 b[builtin_i.compexport] = completion_ysh.CompExport(root_comp)
813
814 #
815 # Initialize Builtin-in Methods
816 #
817
818 methods[value_e.Str] = {
819 'startsWith': method_str.HasAffix(method_str.START),
820 'endsWith': method_str.HasAffix(method_str.END),
821 'trim': method_str.Trim(method_str.START | method_str.END),
822 'trimStart': method_str.Trim(method_str.START),
823 'trimEnd': method_str.Trim(method_str.END),
824 'upper': method_str.Upper(),
825 'lower': method_str.Lower(),
826 'split': method_str.Split(),
827 'lines': method_str.Lines(),
828
829 # finds a substring, optional named parameters specifying the slice
830 # of the string to search in - [start:end]
831 'find': method_str.Find(method_str.START),
832 'findLast': method_str.Find(method_str.END),
833 'contains': method_str.Contains(),
834
835 # replace substring, OR an eggex
836 # takes count=3, the max number of replacements to do.
837 'replace': method_str.Replace(mem, expr_ev),
838
839 # Like Python's re.search, except we put it on the string object
840 # It's more consistent with Str->find(substring, pos=0)
841 # It returns value.Match() rather than an integer
842 'search': method_str.SearchMatch(method_str.SEARCH),
843
844 # like Python's re.match()
845 'leftMatch': method_str.SearchMatch(method_str.LEFT_MATCH),
846
847 # like Python's re.fullmatch(), not sure if we really need it
848 'fullMatch': None,
849 }
850 methods[value_e.Dict] = {
851 # keys() values() get() are FREE functions, not methods
852 # I think items() isn't as necessary because dicts are ordered? YSH
853 # code shouldn't use the List of Lists representation.
854 'M/erase': method_dict.Erase(),
855 'M/clear': method_dict.Clear(),
856 'M/inc': method_dict.Inc(),
857 'M/append': method_dict.Append(),
858 'M/update': method_dict.Update(),
859
860 # DEPRECATED - use free functions
861 'get': method_dict.Get(),
862 'keys': method_dict.Keys(),
863 'values': method_dict.Values(),
864 }
865 methods[value_e.List] = {
866 'M/reverse': method_list.Reverse(),
867 'M/append': method_list.Append(),
868 'M/clear': method_list.Clear(),
869 'M/extend': method_list.Extend(),
870 'M/pop': method_list.Pop(),
871 'M/insert': method_list.Insert(),
872 'M/remove': method_list.Remove(),
873 'indexOf': method_list.IndexOf(), # return first index of value, or -1
874 # Python list() has index(), which raises ValueError
875 # But this is consistent with Str->find(), and doesn't
876 # use exceptions
877 'lastIndexOf': method_list.LastIndexOf(),
878 'join': func_misc.Join(), # both a method and a func
879 }
880
881 methods[value_e.Match] = {
882 'group': func_eggex.MatchMethod(func_eggex.G, expr_ev),
883 'start': func_eggex.MatchMethod(func_eggex.S, None),
884 'end': func_eggex.MatchMethod(func_eggex.E, None),
885 }
886
887 methods[value_e.Place] = {
888 # __mut_setValue()
889
890 # instead of setplace keyword
891 'M/setValue': method_other.SetValue(mem),
892 }
893
894 methods[value_e.Command] = {
895 # var x = ^(echo hi)
896 # p { echo hi }
897 # Export source code and location
898 # Useful for test frameworks, built systems and so forth
899 'sourceCode': method_other.SourceCode(),
900 }
901
902 methods[value_e.Proc] = {
903 'docComment': method_other.DocComment(),
904 }
905
906 methods[value_e.DebugFrame] = {
907 'toString': func_reflect.DebugFrameToString(),
908 }
909
910 #
911 # Initialize Built-in Funcs
912 #
913
914 # Pure functions
915 _AddBuiltinFunc(mem, 'eval',
916 method_io.Eval(mem, cmd_ev, pure_ex, method_io.EVAL_NULL))
917 _AddBuiltinFunc(mem, 'evalExpr',
918 method_io.EvalExpr(expr_ev, pure_ex, cmd_ev))
919
920 parse_hay = func_hay.ParseHay(fd_state, parse_ctx, mem, errfmt)
921 eval_hay = func_hay.EvalHay(hay_state, mutable_opts, mem, cmd_ev)
922 hay_func = func_hay.HayFunc(hay_state)
923
924 _AddBuiltinFunc(mem, 'parseHay', parse_hay)
925 _AddBuiltinFunc(mem, 'evalHay', eval_hay)
926 _AddBuiltinFunc(mem, '_hay', hay_func)
927
928 _AddBuiltinFunc(mem, 'len', func_misc.Len())
929 _AddBuiltinFunc(mem, 'type', func_misc.Type())
930
931 g = func_eggex.MatchFunc(func_eggex.G, expr_ev, mem)
932 _AddBuiltinFunc(mem, '_group', g)
933 _AddBuiltinFunc(mem, '_match',
934 g) # TODO: remove this backward compat alias
935 _AddBuiltinFunc(mem, '_start',
936 func_eggex.MatchFunc(func_eggex.S, None, mem))
937 _AddBuiltinFunc(mem, '_end', func_eggex.MatchFunc(func_eggex.E, None, mem))
938
939 # TODO: should this be parseCommandStr() vs. parseFile() for Hay?
940 _AddBuiltinFunc(mem, 'parseCommand',
941 func_reflect.ParseCommand(parse_ctx, mem, errfmt))
942 _AddBuiltinFunc(mem, 'parseExpr',
943 func_reflect.ParseExpr(parse_ctx, errfmt))
944
945 _AddBuiltinFunc(mem, 'shvarGet', func_reflect.Shvar_get(mem))
946 _AddBuiltinFunc(mem, 'getVar', func_reflect.GetVar(mem))
947 _AddBuiltinFunc(mem, 'setVar', func_reflect.SetVar(mem))
948
949 # TODO: implement bindFrame() to turn CommandFrag -> Command
950 # Then parseCommand() and parseHay() will not depend on mem; they will not
951 # bind a frame yet
952 #
953 # what about newFrame() and globalFrame()?
954 _AddBuiltinFunc(mem, 'bindFrame', func_reflect.BindFrame())
955
956 _AddBuiltinFunc(mem, 'Object', func_misc.Object())
957
958 _AddBuiltinFunc(mem, 'rest', func_misc.Prototype())
959 _AddBuiltinFunc(mem, 'first', func_misc.PropView())
960
961 # TODO: remove these aliases
962 _AddBuiltinFunc(mem, 'prototype', func_misc.Prototype())
963 _AddBuiltinFunc(mem, 'propView', func_misc.PropView())
964
965 # type conversions
966 _AddBuiltinFunc(mem, 'bool', func_misc.Bool())
967 _AddBuiltinFunc(mem, 'int', func_misc.Int())
968 _AddBuiltinFunc(mem, 'float', func_misc.Float())
969 _AddBuiltinFunc(mem, 'str', func_misc.Str_())
970 _AddBuiltinFunc(mem, 'list', func_misc.List_())
971 _AddBuiltinFunc(mem, 'dict', func_misc.DictFunc())
972
973 # Dict functions
974 _AddBuiltinFunc(mem, 'get', method_dict.Get())
975 _AddBuiltinFunc(mem, 'keys', method_dict.Keys())
976 _AddBuiltinFunc(mem, 'values', method_dict.Values())
977
978 _AddBuiltinFunc(mem, 'runes', func_misc.Runes())
979 _AddBuiltinFunc(mem, 'encodeRunes', func_misc.EncodeRunes())
980 _AddBuiltinFunc(mem, 'bytes', func_misc.Bytes())
981 _AddBuiltinFunc(mem, 'encodeBytes', func_misc.EncodeBytes())
982
983 # Str
984 _AddBuiltinFunc(mem, 'strcmp', func_misc.StrCmp())
985 # TODO: This should be Python style splitting
986 _AddBuiltinFunc(mem, 'split', func_misc.Split(splitter))
987 _AddBuiltinFunc(mem, 'shSplit', func_misc.Split(splitter))
988
989 # Float
990 _AddBuiltinFunc(mem, 'floatsEqual', func_misc.FloatsEqual())
991
992 # List
993 _AddBuiltinFunc(mem, 'join', func_misc.Join())
994 _AddBuiltinFunc(mem, 'maybe', func_misc.Maybe())
995 # TODO: deprecate in favor of io.glob()
996 _AddBuiltinFunc(mem, 'glob', method_io.Glob(globber))
997
998 # Serialize
999 _AddBuiltinFunc(mem, 'toJson8', func_misc.ToJson8(True))
1000 _AddBuiltinFunc(mem, 'toJson', func_misc.ToJson8(False))
1001
1002 _AddBuiltinFunc(mem, 'fromJson8', func_misc.FromJson8(True))
1003 _AddBuiltinFunc(mem, 'fromJson', func_misc.FromJson8(False))
1004
1005 mem.AddBuiltin('io', io_obj)
1006 mem.AddBuiltin('vm', vm_obj)
1007
1008 # Special case for testing
1009 mem.AddBuiltin('module-invoke', value.BuiltinProc(module_invoke))
1010
1011 # First, process --eval flags. In interactive mode, this comes before --rcfile.
1012 # (It could be used for the headless shell. Although terminals have a
1013 # bootstrap process.)
1014
1015 for path, is_pure in attrs.eval_flags:
1016 ex = pure_ex if is_pure else None
1017 with vm.ctx_MaybePure(ex, cmd_ev):
1018 try:
1019 ok, status = main_loop.EvalFile(path, fd_state, parse_ctx,
1020 cmd_ev, lang)
1021 except util.HardExit as e:
1022 # Doesn't seem like we need this, and verbose_errexit isn't the right option
1023 #if exec_opts.verbose_errexit():
1024 # print-stderr('oils: --eval exit')
1025 return e.status
1026
1027 # I/O error opening file, parse error. Message was already printed.
1028 if not ok:
1029 return 1
1030
1031 # YSH will stop on errors. OSH keep going, a bit like 'source'.
1032 if status != 0 and exec_opts.errexit():
1033 return status
1034
1035 #
1036 # Is the shell interactive?
1037 #
1038
1039 if flag.c is not None:
1040 src = source.CFlag # type: source_t
1041 line_reader = reader.StringLineReader(flag.c,
1042 arena) # type: reader._Reader
1043 if flag.i: # -c and -i can be combined
1044 mutable_opts.set_interactive()
1045
1046 elif flag.i: # force interactive
1047 src = source.Stdin(' -i')
1048 line_reader = reader.InteractiveLineReader(arena, prompt_ev, readline,
1049 prompt_state)
1050 mutable_opts.set_interactive()
1051
1052 else:
1053 if script_name is None:
1054 if flag.headless:
1055 src = source.Headless
1056 line_reader = None # unused!
1057 # Not setting '-i' flag for now. Some people's bashrc may want it?
1058 else:
1059 stdin_ = mylib.Stdin()
1060 # --tool never starts a prompt
1061 if len(flag.tool) == 0 and stdin_.isatty():
1062 src = source.Interactive
1063 line_reader = reader.InteractiveLineReader(
1064 arena, prompt_ev, readline, prompt_state)
1065 mutable_opts.set_interactive()
1066 else:
1067 src = source.Stdin('')
1068 line_reader = reader.FileLineReader(stdin_, arena)
1069 else:
1070 src = source.MainFile(script_name)
1071 try:
1072 f = fd_state.Open(script_name)
1073 except (IOError, OSError) as e:
1074 print_stderr("%s: Couldn't open %r: %s" %
1075 (lang, script_name, posix.strerror(e.errno)))
1076 return 1
1077 line_reader = reader.FileLineReader(f, arena)
1078
1079 # Pretend it came from somewhere else
1080 if flag.location_str is not None:
1081 src = source.Synthetic(flag.location_str)
1082 assert line_reader is not None
1083 location_start_line = mops.BigTruncate(flag.location_start_line)
1084 if location_start_line != -1:
1085 line_reader.SetLineOffset(location_start_line)
1086
1087 arena.PushSource(src)
1088
1089 # Calculate ~/.config/oils/oshrc or yshrc. Used for both -i and --headless
1090 # We avoid cluttering the user's home directory. Some users may want to ln
1091 # -s ~/.config/oils/oshrc ~/oshrc or ~/.oshrc.
1092
1093 # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other
1094
1095 config_dir = '.config/oils'
1096 rc_paths = [] # type: List[str]
1097 if flag.headless or exec_opts.interactive():
1098 if flag.norc:
1099 # bash doesn't have this warning, but it's useful
1100 if flag.rcfile is not None:
1101 print_stderr('%s warning: --rcfile ignored with --norc' % lang)
1102 if flag.rcdir is not None:
1103 print_stderr('%s warning: --rcdir ignored with --norc' % lang)
1104 else:
1105 # User's rcfile comes FIRST. Later we can add an 'after-rcdir' hook
1106 rc_path = flag.rcfile
1107 if rc_path is None:
1108 rc_paths.append(
1109 os_path.join(home_dir, '%s/%src' % (config_dir, lang)))
1110 else:
1111 rc_paths.append(rc_path)
1112
1113 # Load all files in ~/.config/oils/oshrc.d or oilrc.d
1114 # This way "installers" can avoid mutating oshrc directly
1115
1116 rc_dir = flag.rcdir
1117 if rc_dir is None:
1118 rc_dir = os_path.join(home_dir,
1119 '%s/%src.d' % (config_dir, lang))
1120
1121 rc_paths.extend(libc.glob(os_path.join(rc_dir, '*'), 0))
1122
1123 # Initialize even in non-interactive shell, for 'compexport'
1124 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
1125
1126 if flag.headless:
1127 sh_init.InitInteractive(mem, sh_files, lang)
1128 mutable_opts.set_redefine_const()
1129 mutable_opts.set_redefine_source()
1130
1131 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1132 for rc_path in rc_paths:
1133 with state.ctx_ThisDir(mem, rc_path):
1134 try:
1135 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1136 cmd_ev, errfmt)
1137 except util.HardExit as e:
1138 return e.status
1139
1140 loop = main_loop.Headless(cmd_ev, parse_ctx, errfmt)
1141 try:
1142 # TODO: What other exceptions happen here?
1143 status = loop.Loop()
1144 except util.HardExit as e:
1145 status = e.status
1146
1147 # Same logic as interactive shell
1148 mut_status = IntParamBox(status)
1149 cmd_ev.RunTrapsOnExit(mut_status)
1150 status = mut_status.i
1151
1152 return status
1153
1154 # Note: headless mode above doesn't use c_parser
1155 assert line_reader is not None
1156 interactive_parsing = (mutable_opts.get_interactive() or
1157 exec_opts.interactive())
1158 c_parser = parse_ctx.MakeOshParser(line_reader, interactive_parsing)
1159
1160 if exec_opts.interactive():
1161 sh_init.InitInteractive(mem, sh_files, lang)
1162 # bash: 'set -o emacs' is the default only in the interactive shell
1163 mutable_opts.set_emacs()
1164 mutable_opts.set_redefine_const()
1165 mutable_opts.set_redefine_source()
1166
1167 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1168 for rc_path in rc_paths:
1169 with state.ctx_ThisDir(mem, rc_path):
1170 try:
1171 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1172 cmd_ev, errfmt)
1173 except util.HardExit as e:
1174 return e.status
1175
1176 completion_display = state.MaybeString(mem, 'OILS_COMP_UI')
1177 if completion_display is None:
1178 completion_display = flag.completion_display
1179
1180 if readline:
1181 if completion_display == 'nice':
1182 display = comp_ui.NiceDisplay(
1183 comp_ui_state, prompt_state, debug_f, readline,
1184 signal_safe) # type: comp_ui._IDisplay
1185 else:
1186 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1187 debug_f, signal_safe)
1188
1189 comp_ui.InitReadline(readline, sh_files.HistoryFile(), root_comp,
1190 display, debug_f)
1191
1192 if flag.completion_demo:
1193 _CompletionDemo(comp_lookup)
1194
1195 else: # Without readline module
1196 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1197 debug_f, signal_safe)
1198
1199 process.InitInteractiveShell(signal_safe) # Set signal handlers
1200 # The interactive shell leads a process group which controls the terminal.
1201 # It MUST give up the terminal afterward, otherwise we get SIGTTIN /
1202 # SIGTTOU bugs.
1203 with process.ctx_TerminalControl(job_control, errfmt):
1204
1205 assert line_reader is not None
1206 line_reader.Reset() # After sourcing startup file, render $PS1
1207
1208 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, cmd_ev, errfmt)
1209 try:
1210 status = main_loop.Interactive(flag, cmd_ev, c_parser, display,
1211 prompt_plugin, waiter, errfmt)
1212 except util.HardExit as e:
1213 status = e.status
1214
1215 mut_status = IntParamBox(status)
1216 cmd_ev.RunTrapsOnExit(mut_status)
1217 status = mut_status.i
1218
1219 if readline:
1220 hist_file = sh_files.HistoryFile()
1221 if hist_file is not None:
1222 try:
1223 readline.write_history_file(hist_file)
1224 except (IOError, OSError):
1225 pass
1226
1227 return status
1228
1229 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
1230 print_stderr('%s warning: --rcfile ignored in non-interactive shell' %
1231 lang)
1232 if flag.rcdir is not None:
1233 print_stderr('%s warning: --rcdir ignored in non-interactive shell' %
1234 lang)
1235
1236 #
1237 # Tools that use the OSH/YSH parsing mode, etc.
1238 #
1239
1240 # flag.tool is '' if nothing is passed
1241 # osh --tool syntax-tree is equivalent to osh -n --one-pass-parse
1242 tool_name = 'syntax-tree' if exec_opts.noexec() else flag.tool
1243
1244 if len(tool_name):
1245 # Don't save tokens because it's slow
1246 if tool_name != 'syntax-tree':
1247 arena.SaveTokens()
1248
1249 try:
1250 node = main_loop.ParseWholeFile(c_parser)
1251 except error.Parse as e:
1252 errfmt.PrettyPrintError(e)
1253 return 2
1254
1255 if tool_name == 'syntax-tree':
1256 ui.PrintAst(node, flag)
1257
1258 elif tool_name == 'tokens':
1259 ysh_ify.PrintTokens(arena)
1260
1261 elif tool_name == 'find-lhs-array':
1262 ysh_ify.TreeFind(arena, node, errfmt)
1263
1264 elif tool_name == 'lossless-cat': # for test/lossless.sh
1265 ysh_ify.LosslessCat(arena)
1266
1267 elif tool_name == 'fmt':
1268 fmt.Format(arena, node)
1269
1270 elif tool_name == 'test':
1271 # Do we need this? Couldn't this just be a YSH script?
1272 raise AssertionError('TODO')
1273
1274 elif tool_name == 'ysh-ify':
1275 ysh_ify.Ysh_ify(arena, node)
1276
1277 elif tool_name == 'deps':
1278 if mylib.PYTHON:
1279 deps.Deps(node)
1280
1281 else:
1282 raise AssertionError(tool_name) # flag parser validated it
1283
1284 return 0
1285
1286 #
1287 # Batch mode: shell script or -c
1288 #
1289
1290 with state.ctx_ThisDir(mem, script_name):
1291 try:
1292 status = main_loop.Batch(cmd_ev,
1293 c_parser,
1294 errfmt,
1295 cmd_flags=cmd_eval.IsMainProgram)
1296 except util.HardExit as e:
1297 status = e.status
1298 except KeyboardInterrupt:
1299 # The interactive shell handles this in main_loop.Interactive
1300 status = 130 # 128 + 2
1301 mut_status = IntParamBox(status)
1302 cmd_ev.RunTrapsOnExit(mut_status)
1303
1304 multi_trace.WriteDumps()
1305
1306 # NOTE: We haven't closed the file opened with fd_state.Open
1307 return mut_status.i