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

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