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 and on SIGWINCH. This is analogous to
112 bash's 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