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

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