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

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