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

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