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

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