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

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