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

1185 lines, 752 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, 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_osh
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.SourcedFile(rc_path, loc.Missing)):
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.InitMem(mem, environ, version_str)
363
364 # TODO: consider turning on no_copy_env in YSH
365 if exec_opts.no_copy_env():
366 # Don't consult the environment
367 mem.SetPwd(state.GetWorkingDir())
368 else:
369 state.InitVarsFromEnv(mem, environ)
370
371 # MUTABLE GLOBAL that's SEPARATE from $PWD. Used by the 'pwd' builtin, but
372 # it can't be modified by users.
373 val = mem.GetValue('PWD')
374 # should be true since it's exported
375 assert val.tag() == value_e.Str, val
376 pwd = cast(value.Str, val).s
377 mem.SetPwd(pwd)
378
379 if attrs.show_options: # special case: sh -o
380 mutable_opts.ShowOptions([])
381 return 0
382
383 # feedback between runtime and parser
384 aliases = {} # type: Dict[str, str]
385
386 ysh_grammar = pyutil.LoadYshGrammar(loader)
387
388 if flag.do_lossless and not exec_opts.noexec():
389 raise error.Usage('--one-pass-parse requires noexec (-n)', loc.Missing)
390
391 # Tools always use one pass parse
392 # Note: osh --tool syntax-tree is like osh -n --one-pass-parse
393 do_lossless = True if len(flag.tool) else flag.do_lossless
394
395 parse_ctx = parse_lib.ParseContext(arena,
396 parse_opts,
397 aliases,
398 ysh_grammar,
399 do_lossless=do_lossless)
400
401 # Three ParseContext instances SHARE aliases.
402 comp_arena = alloc.Arena()
403 comp_arena.PushSource(source.Unused('completion'))
404 trail1 = parse_lib.Trail()
405 # do_lossless needs to be turned on to complete inside backticks. TODO:
406 # fix the issue where ` gets erased because it's not part of
407 # set_completer_delims().
408 comp_ctx = parse_lib.ParseContext(comp_arena,
409 parse_opts,
410 aliases,
411 ysh_grammar,
412 do_lossless=True)
413 comp_ctx.Init_Trail(trail1)
414
415 hist_arena = alloc.Arena()
416 hist_arena.PushSource(source.Unused('history'))
417 trail2 = parse_lib.Trail()
418 hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases,
419 ysh_grammar)
420 hist_ctx.Init_Trail(trail2)
421
422 # Deps helps manages dependencies. These dependencies are circular:
423 # - cmd_ev and word_ev, arith_ev -- for command sub, arith sub
424 # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 ))
425 # - cmd_ev and builtins (which execute code, like eval)
426 # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P
427 cmd_deps = cmd_eval.Deps()
428 cmd_deps.mutable_opts = mutable_opts
429
430 job_control = process.JobControl()
431 job_list = process.JobList()
432 fd_state = process.FdState(errfmt, job_control, job_list, mem, None, None,
433 exec_opts)
434
435 my_pid = posix.getpid()
436
437 debug_path = ''
438 debug_dir = environ.get('OILS_DEBUG_DIR')
439 if flag.debug_file is not None:
440 # --debug-file takes precedence over OSH_DEBUG_DIR
441 debug_path = flag.debug_file
442 elif debug_dir is not None:
443 debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid)
444
445 if len(debug_path):
446 # This will be created as an empty file if it doesn't exist, or it could be
447 # a pipe.
448 try:
449 debug_f = util.DebugFile(
450 fd_state.OpenForWrite(debug_path)) # type: util._DebugFile
451 except (IOError, OSError) as e:
452 print_stderr("%s: Couldn't open %r: %s" %
453 (lang, debug_path, posix.strerror(e.errno)))
454 return 2
455 else:
456 debug_f = util.NullDebugFile()
457
458 if flag.xtrace_to_debug_file:
459 trace_f = debug_f
460 else:
461 trace_f = util.DebugFile(mylib.Stderr())
462
463 trace_dir = environ.get('OILS_TRACE_DIR', '')
464 dumps = environ.get('OILS_TRACE_DUMPS', '')
465 streams = environ.get('OILS_TRACE_STREAMS', '')
466 multi_trace = dev.MultiTracer(my_pid, trace_dir, dumps, streams, fd_state)
467
468 tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, trace_f,
469 multi_trace)
470 fd_state.tracer = tracer # circular dep
471
472 # RegisterSignalInterest should return old sigint handler
473 # then InteractiveLineReader can use it
474 # InteractiveLineReader
475 signal_safe = pyos.InitSignalSafe()
476 trap_state = trap_osh.TrapState(signal_safe)
477
478 waiter = process.Waiter(job_list, exec_opts, signal_safe, tracer)
479 fd_state.waiter = waiter
480
481 cmd_deps.debug_f = debug_f
482
483 now = time_.time()
484 iso_stamp = time_.strftime("%Y-%m-%d %H:%M:%S", time_.localtime(now))
485
486 argv_buf = mylib.BufWriter()
487 dev.PrintShellArgv(arg_r.argv, argv_buf)
488
489 debug_f.writeln('%s [%d] Oils started with argv %s' %
490 (iso_stamp, my_pid, argv_buf.getvalue()))
491 if len(debug_path):
492 debug_f.writeln('Writing logs to %r' % debug_path)
493
494 interp = environ.get('OILS_HIJACK_SHEBANG', '')
495 search_path = state.SearchPath(mem)
496 ext_prog = process.ExternalProgram(interp, fd_state, errfmt, debug_f)
497
498 splitter = split.SplitContext(mem)
499 # TODO: This is instantiation is duplicated in osh/word_eval.py
500 globber = glob_.Globber(exec_opts)
501
502 # This could just be OILS_TRACE_DUMPS='crash:argv0'
503 crash_dump_dir = environ.get('OILS_CRASH_DUMP_DIR', '')
504 cmd_deps.dumper = dev.CrashDumper(crash_dump_dir, fd_state)
505
506 comp_lookup = completion.Lookup()
507
508 # Various Global State objects to work around readline interfaces
509 compopt_state = completion.OptionState()
510
511 comp_ui_state = comp_ui.State()
512 prompt_state = comp_ui.PromptState()
513
514 # The login program is supposed to set $HOME
515 # https://superuser.com/questions/271925/where-is-the-home-environment-variable-set
516 # state.InitMem(mem) must happen first
517 tilde_ev = word_eval.TildeEvaluator(mem, exec_opts)
518 home_dir = tilde_ev.GetMyHomeDir()
519 if home_dir is None:
520 # TODO: print errno from getpwuid()
521 print_stderr("%s: Failed to get home dir from $HOME or getpwuid()" %
522 lang)
523 return 1
524
525 sh_files = ShellFiles(lang, home_dir, mem, flag)
526 sh_files.InitAfterLoadingEnv()
527
528 #
529 # Executor and Evaluators (are circularly dependent)
530 #
531
532 # Global proc namespace. Funcs are defined in the common variable
533 # namespace.
534 procs = state.Procs(mem) # type: state.Procs
535
536 builtins = {} # type: Dict[int, vm._Builtin]
537
538 # e.g. s->startswith()
539 methods = {} # type: Dict[int, Dict[str, vm._Callable]]
540
541 hay_state = hay_ysh.HayState()
542
543 shell_ex = executor.ShellExecutor(mem, exec_opts, mutable_opts, procs,
544 hay_state, builtins, search_path,
545 ext_prog, waiter, tracer, job_control,
546 job_list, fd_state, trap_state, errfmt)
547
548 arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, mutable_opts,
549 parse_ctx, errfmt)
550 bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, mutable_opts,
551 parse_ctx, errfmt)
552 expr_ev = expr_eval.ExprEvaluator(mem, mutable_opts, methods, splitter,
553 errfmt)
554 word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, mutable_opts,
555 tilde_ev, splitter, errfmt)
556
557 assign_b = InitAssignmentBuiltins(mem, procs, exec_opts, errfmt)
558 cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_b,
559 arena, cmd_deps, trap_state,
560 signal_safe)
561
562 # PromptEvaluator rendering is needed in non-interactive shells for @P.
563 prompt_ev = prompt.Evaluator(lang, version_str, parse_ctx, mem)
564
565 io_methods = {} # type: Dict[str, value_t]
566 io_methods['promptVal'] = value.BuiltinFunc(method_io.PromptVal(prompt_ev))
567
568 # The M/ prefix means it's io->eval()
569 io_methods['M/eval'] = value.BuiltinFunc(
570 method_io.Eval(cmd_ev, method_io.EVAL_NULL))
571 io_methods['M/evalToDict'] = value.BuiltinFunc(
572 method_io.Eval(cmd_ev, method_io.EVAL_DICT))
573
574 # Identical to command sub
575 io_methods['captureStdout'] = value.BuiltinFunc(
576 method_io.CaptureStdout(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 # Wire up circular dependencies.
587 vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex,
588 prompt_ev, io_obj, tracer)
589
590 unsafe_arith = sh_expr_eval.UnsafeArith(mem, exec_opts, mutable_opts,
591 parse_ctx, arith_ev, errfmt)
592 vm.InitUnsafeArith(mem, word_ev, unsafe_arith)
593
594 #
595 # Initialize Built-in Procs
596 #
597
598 b = builtins # short alias for initialization
599
600 if mylib.PYTHON:
601 if help_meta:
602 help_data = help_meta.TopicMetadata()
603 else:
604 help_data = {} # minimal build
605 else:
606 help_data = help_meta.TopicMetadata()
607 b[builtin_i.help] = misc_osh.Help(lang, loader, help_data, errfmt)
608
609 # Interpreter state
610 b[builtin_i.set] = pure_osh.Set(mutable_opts, mem)
611 b[builtin_i.shopt] = pure_osh.Shopt(mutable_opts, cmd_ev)
612
613 b[builtin_i.hash] = pure_osh.Hash(search_path) # not really pure
614 b[builtin_i.trap] = trap_osh.Trap(trap_state, parse_ctx, tracer, errfmt)
615
616 b[builtin_i.shvar] = pure_ysh.Shvar(mem, search_path, cmd_ev)
617 b[builtin_i.ctx] = pure_ysh.Ctx(mem, cmd_ev)
618 b[builtin_i.push_registers] = pure_ysh.PushRegisters(mem, cmd_ev)
619
620 # Hay
621 b[builtin_i.hay] = hay_ysh.Hay(hay_state, mutable_opts, mem, cmd_ev)
622 b[builtin_i.haynode] = hay_ysh.HayNode_(hay_state, mem, cmd_ev)
623
624 # Interpreter introspection
625 b[builtin_i.type] = meta_osh.Type(procs, aliases, search_path, errfmt)
626 b[builtin_i.builtin] = meta_osh.Builtin(shell_ex, errfmt)
627 b[builtin_i.command] = meta_osh.Command(shell_ex, procs, aliases,
628 search_path)
629 # Part of YSH, but similar to builtin/command
630 b[builtin_i.runproc] = meta_osh.RunProc(shell_ex, procs, errfmt)
631
632 # Meta builtins
633 source_builtin = meta_osh.Source(parse_ctx, search_path, cmd_ev, fd_state,
634 tracer, errfmt, loader)
635 b[builtin_i.source] = source_builtin
636 b[builtin_i.dot] = source_builtin
637 b[builtin_i.eval] = meta_osh.Eval(parse_ctx, exec_opts, cmd_ev, tracer,
638 errfmt, mem)
639
640 # Module builtins
641 guards = {} # type: Dict[str, bool]
642 b[builtin_i.source_guard] = module_ysh.SourceGuard(guards, exec_opts,
643 errfmt)
644 b[builtin_i.is_main] = module_ysh.IsMain(mem)
645 b[builtin_i.use] = module_ysh.Use(mem, errfmt)
646
647 # Errors
648 b[builtin_i.error] = error_ysh.Error()
649 b[builtin_i.failed] = error_ysh.Failed(mem)
650 b[builtin_i.boolstatus] = error_ysh.BoolStatus(shell_ex, errfmt)
651 b[builtin_i.try_] = error_ysh.Try(mutable_opts, mem, cmd_ev, shell_ex,
652 errfmt)
653 b[builtin_i.assert_] = error_ysh.Assert(expr_ev, errfmt)
654
655 # Pure builtins
656 true_ = pure_osh.Boolean(0)
657 b[builtin_i.colon] = true_ # a "special" builtin
658 b[builtin_i.true_] = true_
659 b[builtin_i.false_] = pure_osh.Boolean(1)
660
661 b[builtin_i.alias] = pure_osh.Alias(aliases, errfmt)
662 b[builtin_i.unalias] = pure_osh.UnAlias(aliases, errfmt)
663
664 b[builtin_i.getopts] = pure_osh.GetOpts(mem, errfmt)
665
666 b[builtin_i.shift] = assign_osh.Shift(mem)
667 b[builtin_i.unset] = assign_osh.Unset(mem, procs, unsafe_arith, errfmt)
668
669 b[builtin_i.append] = pure_ysh.Append(mem, errfmt)
670
671 # test / [ differ by need_right_bracket
672 b[builtin_i.test] = bracket_osh.Test(False, exec_opts, mem, errfmt)
673 b[builtin_i.bracket] = bracket_osh.Test(True, exec_opts, mem, errfmt)
674
675 # Output
676 b[builtin_i.echo] = io_osh.Echo(exec_opts)
677 b[builtin_i.printf] = printf_osh.Printf(mem, parse_ctx, unsafe_arith,
678 errfmt)
679 b[builtin_i.write] = io_ysh.Write(mem, errfmt)
680 b[builtin_i.fopen] = io_ysh.Fopen(mem, cmd_ev)
681
682 # (pp output format isn't stable)
683 b[builtin_i.pp] = io_ysh.Pp(expr_ev, mem, errfmt, procs, arena)
684
685 # Input
686 b[builtin_i.cat] = io_osh.Cat() # for $(<file)
687 b[builtin_i.read] = read_osh.Read(splitter, mem, parse_ctx, cmd_ev, errfmt)
688
689 mapfile = io_osh.MapFile(mem, errfmt, cmd_ev)
690 b[builtin_i.mapfile] = mapfile
691 b[builtin_i.readarray] = mapfile
692
693 # Dirs
694 dir_stack = dirs_osh.DirStack()
695 b[builtin_i.cd] = dirs_osh.Cd(mem, dir_stack, cmd_ev, errfmt)
696 b[builtin_i.pushd] = dirs_osh.Pushd(mem, dir_stack, errfmt)
697 b[builtin_i.popd] = dirs_osh.Popd(mem, dir_stack, errfmt)
698 b[builtin_i.dirs] = dirs_osh.Dirs(mem, dir_stack, errfmt)
699 b[builtin_i.pwd] = dirs_osh.Pwd(mem, errfmt)
700
701 b[builtin_i.times] = misc_osh.Times()
702
703 b[builtin_i.json] = json_ysh.Json(mem, errfmt, False)
704 b[builtin_i.json8] = json_ysh.Json(mem, errfmt, True)
705
706 ### Process builtins
707 b[builtin_i.exec_] = process_osh.Exec(mem, ext_prog, fd_state, search_path,
708 errfmt)
709 b[builtin_i.umask] = process_osh.Umask()
710 b[builtin_i.ulimit] = process_osh.Ulimit()
711 b[builtin_i.wait] = process_osh.Wait(waiter, job_list, mem, tracer, errfmt)
712
713 b[builtin_i.jobs] = process_osh.Jobs(job_list)
714 b[builtin_i.fg] = process_osh.Fg(job_control, job_list, waiter)
715 b[builtin_i.bg] = process_osh.Bg(job_list)
716
717 # Could be in process_ysh
718 b[builtin_i.fork] = process_osh.Fork(shell_ex)
719 b[builtin_i.forkwait] = process_osh.ForkWait(shell_ex)
720
721 # Interactive builtins depend on readline
722 b[builtin_i.bind] = readline_osh.Bind(readline, errfmt)
723 b[builtin_i.history] = readline_osh.History(readline, sh_files, errfmt,
724 mylib.Stdout())
725
726 # Completion
727 spec_builder = completion_osh.SpecBuilder(cmd_ev, parse_ctx, word_ev,
728 splitter, comp_lookup, help_data,
729 errfmt)
730 complete_builtin = completion_osh.Complete(spec_builder, comp_lookup)
731 b[builtin_i.complete] = complete_builtin
732 b[builtin_i.compgen] = completion_osh.CompGen(spec_builder)
733 b[builtin_i.compopt] = completion_osh.CompOpt(compopt_state, errfmt)
734 b[builtin_i.compadjust] = completion_osh.CompAdjust(mem)
735
736 comp_ev = word_eval.CompletionWordEvaluator(mem, exec_opts, mutable_opts,
737 tilde_ev, splitter, errfmt)
738
739 comp_ev.arith_ev = arith_ev
740 comp_ev.expr_ev = expr_ev
741 comp_ev.prompt_ev = prompt_ev
742 comp_ev.CheckCircularDeps()
743
744 root_comp = completion.RootCompleter(comp_ev, mem, comp_lookup,
745 compopt_state, comp_ui_state,
746 comp_ctx, debug_f)
747 b[builtin_i.compexport] = completion_ysh.CompExport(root_comp)
748
749 #
750 # Initialize Builtin-in Methods
751 #
752
753 methods[value_e.Str] = {
754 'startsWith': method_str.HasAffix(method_str.START),
755 'endsWith': method_str.HasAffix(method_str.END),
756 'trim': method_str.Trim(method_str.START | method_str.END),
757 'trimStart': method_str.Trim(method_str.START),
758 'trimEnd': method_str.Trim(method_str.END),
759 'upper': method_str.Upper(),
760 'lower': method_str.Lower(),
761 'split': method_str.Split(),
762
763 # finds a substring, optional position to start at
764 'find': None,
765
766 # replace substring, OR an eggex
767 # takes count=3, the max number of replacements to do.
768 'replace': method_str.Replace(mem, expr_ev),
769
770 # Like Python's re.search, except we put it on the string object
771 # It's more consistent with Str->find(substring, pos=0)
772 # It returns value.Match() rather than an integer
773 'search': method_str.SearchMatch(method_str.SEARCH),
774
775 # like Python's re.match()
776 'leftMatch': method_str.SearchMatch(method_str.LEFT_MATCH),
777
778 # like Python's re.fullmatch(), not sure if we really need it
779 'fullMatch': None,
780 }
781 methods[value_e.Dict] = {
782 'M/erase': method_dict.Erase(),
783
784 # Dict.get()
785 # Dict.keys()
786 # Dict.values()
787 'get': method_dict.Get(),
788 'keys': method_dict.Keys(),
789 'values': method_dict.Values(),
790
791 # I think items() isn't as necessary because dicts are ordered?
792 # YSH code shouldn't use the List of Lists representation.
793
794 # could be d->tally() or d->increment(), but inc() is short
795 #
796 # call d->inc('mycounter')
797 # call d->inc('mycounter', 3)
798 'M/inc': None,
799
800 # call d->accum('mygroup', 'value')
801 'M/accum': None,
802 }
803 methods[value_e.List] = {
804 # TODO: __mut_{reverse,append,extend,pop,insert,remove}
805 'M/reverse': method_list.Reverse(),
806 'M/append': method_list.Append(),
807 'M/extend': method_list.Extend(),
808 'M/pop': method_list.Pop(),
809 'M/insert': None, # insert object before index
810 'M/remove': None, # insert object before index
811 'indexOf': method_list.IndexOf(), # return first index of value, or -1
812 # Python list() has index(), which raises ValueError
813 # But this is consistent with Str->find(), and doesn't
814 # use exceptions
815 'join': func_misc.Join(), # both a method and a func
816 }
817
818 methods[value_e.Match] = {
819 'group': func_eggex.MatchMethod(func_eggex.G, expr_ev),
820 'start': func_eggex.MatchMethod(func_eggex.S, None),
821 'end': func_eggex.MatchMethod(func_eggex.E, None),
822 }
823
824 methods[value_e.Place] = {
825 # __mut_setValue()
826
827 # instead of setplace keyword
828 'M/setValue': method_other.SetValue(mem),
829 }
830
831 methods[value_e.Command] = {
832 # var x = ^(echo hi)
833 # Export source code and line number
834 # Useful for test frameworks and so forth
835 'export': None,
836 }
837
838 #
839 # Initialize Built-in Funcs
840 #
841
842 parse_hay = func_hay.ParseHay(fd_state, parse_ctx, errfmt)
843 eval_hay = func_hay.EvalHay(hay_state, mutable_opts, mem, cmd_ev)
844 hay_func = func_hay.HayFunc(hay_state)
845
846 _AddBuiltinFunc(mem, 'parseHay', parse_hay)
847 _AddBuiltinFunc(mem, 'evalHay', eval_hay)
848 _AddBuiltinFunc(mem, '_hay', hay_func)
849
850 _AddBuiltinFunc(mem, 'len', func_misc.Len())
851 _AddBuiltinFunc(mem, 'type', func_misc.Type())
852
853 g = func_eggex.MatchFunc(func_eggex.G, expr_ev, mem)
854 _AddBuiltinFunc(mem, '_group', g)
855 _AddBuiltinFunc(mem, '_match',
856 g) # TODO: remove this backward compat alias
857 _AddBuiltinFunc(mem, '_start',
858 func_eggex.MatchFunc(func_eggex.S, None, mem))
859 _AddBuiltinFunc(mem, '_end', func_eggex.MatchFunc(func_eggex.E, None, mem))
860
861 _AddBuiltinFunc(mem, 'parseCommand',
862 func_reflect.ParseCommand(parse_ctx, errfmt))
863 _AddBuiltinFunc(mem, 'parseExpr',
864 func_reflect.ParseExpr(parse_ctx, errfmt))
865 _AddBuiltinFunc(mem, 'evalExpr', func_reflect.EvalExpr(expr_ev))
866
867 _AddBuiltinFunc(mem, 'shvarGet', func_reflect.Shvar_get(mem))
868 _AddBuiltinFunc(mem, 'getVar', func_reflect.GetVar(mem))
869
870 _AddBuiltinFunc(mem, 'Object', func_misc.Object())
871 _AddBuiltinFunc(mem, 'prototype', func_misc.Prototype())
872 _AddBuiltinFunc(mem, 'propView', func_misc.PropView())
873
874 # type conversions
875 _AddBuiltinFunc(mem, 'bool', func_misc.Bool())
876 _AddBuiltinFunc(mem, 'int', func_misc.Int())
877 _AddBuiltinFunc(mem, 'float', func_misc.Float())
878 _AddBuiltinFunc(mem, 'str', func_misc.Str_())
879 _AddBuiltinFunc(mem, 'list', func_misc.List_())
880 _AddBuiltinFunc(mem, 'dict', func_misc.DictFunc())
881
882 _AddBuiltinFunc(mem, 'runes', func_misc.Runes())
883 _AddBuiltinFunc(mem, 'encodeRunes', func_misc.EncodeRunes())
884 _AddBuiltinFunc(mem, 'bytes', func_misc.Bytes())
885 _AddBuiltinFunc(mem, 'encodeBytes', func_misc.EncodeBytes())
886
887 # Str
888 #_AddBuiltinFunc(mem, 'strcmp', None)
889 # TODO: This should be Python style splitting
890 _AddBuiltinFunc(mem, 'split', func_misc.Split(splitter))
891 _AddBuiltinFunc(mem, 'shSplit', func_misc.Split(splitter))
892
893 # Float
894 _AddBuiltinFunc(mem, 'floatsEqual', func_misc.FloatsEqual())
895
896 # List
897 _AddBuiltinFunc(mem, 'join', func_misc.Join())
898 _AddBuiltinFunc(mem, 'maybe', func_misc.Maybe())
899 _AddBuiltinFunc(mem, 'glob', func_misc.Glob(globber))
900
901 # Serialize
902 _AddBuiltinFunc(mem, 'toJson8', func_misc.ToJson8(True))
903 _AddBuiltinFunc(mem, 'toJson', func_misc.ToJson8(False))
904
905 _AddBuiltinFunc(mem, 'fromJson8', func_misc.FromJson8(True))
906 _AddBuiltinFunc(mem, 'fromJson', func_misc.FromJson8(False))
907
908 # Demos
909 _AddBuiltinFunc(mem, '_a2sp', func_misc.BashArrayToSparse())
910 _AddBuiltinFunc(mem, '_opsp', func_misc.SparseOp())
911
912 mem.AddBuiltin('io', io_obj)
913
914 #
915 # Is the shell interactive?
916 #
917
918 # History evaluation is a no-op if readline is None.
919 hist_ev = history.Evaluator(readline, hist_ctx, debug_f)
920
921 if flag.c is not None:
922 src = source.CFlag # type: source_t
923 line_reader = reader.StringLineReader(flag.c,
924 arena) # type: reader._Reader
925 if flag.i: # -c and -i can be combined
926 mutable_opts.set_interactive()
927
928 elif flag.i: # force interactive
929 src = source.Stdin(' -i')
930 line_reader = reader.InteractiveLineReader(arena, prompt_ev, hist_ev,
931 readline, prompt_state)
932 mutable_opts.set_interactive()
933
934 else:
935 if script_name is None:
936 if flag.headless:
937 src = source.Headless
938 line_reader = None # unused!
939 # Not setting '-i' flag for now. Some people's bashrc may want it?
940 else:
941 stdin_ = mylib.Stdin()
942 # --tool never starts a prompt
943 if len(flag.tool) == 0 and stdin_.isatty():
944 src = source.Interactive
945 line_reader = reader.InteractiveLineReader(
946 arena, prompt_ev, hist_ev, readline, prompt_state)
947 mutable_opts.set_interactive()
948 else:
949 src = source.Stdin('')
950 line_reader = reader.FileLineReader(stdin_, arena)
951 else:
952 src = source.MainFile(script_name)
953 try:
954 f = fd_state.Open(script_name)
955 except (IOError, OSError) as e:
956 print_stderr("%s: Couldn't open %r: %s" %
957 (lang, script_name, posix.strerror(e.errno)))
958 return 1
959 line_reader = reader.FileLineReader(f, arena)
960
961 # Pretend it came from somewhere else
962 if flag.location_str is not None:
963 src = source.Synthetic(flag.location_str)
964 assert line_reader is not None
965 location_start_line = mops.BigTruncate(flag.location_start_line)
966 if location_start_line != -1:
967 line_reader.SetLineOffset(location_start_line)
968
969 arena.PushSource(src)
970
971 # Calculate ~/.config/oils/oshrc or yshrc. Used for both -i and --headless
972 # We avoid cluttering the user's home directory. Some users may want to ln
973 # -s ~/.config/oils/oshrc ~/oshrc or ~/.oshrc.
974
975 # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other
976
977 config_dir = '.config/oils'
978 rc_paths = [] # type: List[str]
979 if not flag.norc and (flag.headless or exec_opts.interactive()):
980 # User's rcfile comes FIRST. Later we can add an 'after-rcdir' hook
981 rc_path = flag.rcfile
982 if rc_path is None:
983 rc_paths.append(
984 os_path.join(home_dir, '%s/%src' % (config_dir, lang)))
985 else:
986 rc_paths.append(rc_path)
987
988 # Load all files in ~/.config/oil/oshrc.d or oilrc.d
989 # This way "installers" can avoid mutating oshrc directly
990
991 rc_dir = flag.rcdir
992 if rc_dir is None:
993 rc_dir = os_path.join(home_dir, '%s/%src.d' % (config_dir, lang))
994
995 rc_paths.extend(libc.glob(os_path.join(rc_dir, '*')))
996 else:
997 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
998 print_stderr('%s warning: --rcfile ignored with --norc' % lang)
999 if flag.rcdir is not None:
1000 print_stderr('%s warning: --rcdir ignored with --norc' % lang)
1001
1002 # Initialize even in non-interactive shell, for 'compexport'
1003 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
1004
1005 if flag.headless:
1006 state.InitInteractive(mem)
1007 mutable_opts.set_redefine_proc_func()
1008 mutable_opts.set_redefine_module()
1009
1010 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1011 for rc_path in rc_paths:
1012 with state.ctx_ThisDir(mem, rc_path):
1013 try:
1014 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1015 cmd_ev, errfmt)
1016 except util.UserExit as e:
1017 return e.status
1018
1019 loop = main_loop.Headless(cmd_ev, parse_ctx, errfmt)
1020 try:
1021 # TODO: What other exceptions happen here?
1022 status = loop.Loop()
1023 except util.UserExit as e:
1024 status = e.status
1025
1026 # Same logic as interactive shell
1027 mut_status = IntParamBox(status)
1028 cmd_ev.RunTrapsOnExit(mut_status)
1029 status = mut_status.i
1030
1031 return status
1032
1033 # Note: headless mode above doesn't use c_parser
1034 assert line_reader is not None
1035 c_parser = parse_ctx.MakeOshParser(line_reader)
1036
1037 if exec_opts.interactive():
1038 state.InitInteractive(mem)
1039 # bash: 'set -o emacs' is the default only in the interactive shell
1040 mutable_opts.set_emacs()
1041 mutable_opts.set_redefine_proc_func()
1042 mutable_opts.set_redefine_module()
1043
1044 if readline:
1045 term_width = 0
1046 if flag.completion_display == 'nice':
1047 try:
1048 term_width = libc.get_terminal_width()
1049 except (IOError, OSError): # stdin not a terminal
1050 pass
1051
1052 if term_width != 0:
1053 display = comp_ui.NiceDisplay(
1054 term_width, comp_ui_state, prompt_state, debug_f, readline,
1055 signal_safe) # type: comp_ui._IDisplay
1056 else:
1057 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1058 debug_f)
1059
1060 comp_ui.InitReadline(readline, sh_files.HistoryFile(), root_comp,
1061 display, debug_f)
1062
1063 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
1064 if flag.completion_demo:
1065 _CompletionDemo(comp_lookup)
1066
1067 else: # Without readline module
1068 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1069 debug_f)
1070
1071 process.InitInteractiveShell(signal_safe) # Set signal handlers
1072
1073 # The interactive shell leads a process group which controls the terminal.
1074 # It MUST give up the terminal afterward, otherwise we get SIGTTIN /
1075 # SIGTTOU bugs.
1076 with process.ctx_TerminalControl(job_control, errfmt):
1077
1078 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1079 for rc_path in rc_paths:
1080 with state.ctx_ThisDir(mem, rc_path):
1081 try:
1082 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1083 cmd_ev, errfmt)
1084 except util.UserExit as e:
1085 return e.status
1086
1087 assert line_reader is not None
1088 line_reader.Reset() # After sourcing startup file, render $PS1
1089
1090 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, cmd_ev, errfmt)
1091 try:
1092 status = main_loop.Interactive(flag, cmd_ev, c_parser, display,
1093 prompt_plugin, waiter, errfmt)
1094 except util.UserExit as e:
1095 status = e.status
1096
1097 mut_status = IntParamBox(status)
1098 cmd_ev.RunTrapsOnExit(mut_status)
1099 status = mut_status.i
1100
1101 if readline:
1102 hist_file = sh_files.HistoryFile()
1103 if hist_file is not None:
1104 try:
1105 readline.write_history_file(hist_file)
1106 except (IOError, OSError):
1107 pass
1108
1109 return status
1110
1111 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
1112 print_stderr('%s warning: --rcfile ignored in non-interactive shell' %
1113 lang)
1114 if flag.rcdir is not None:
1115 print_stderr('%s warning: --rcdir ignored in non-interactive shell' %
1116 lang)
1117
1118 #
1119 # Tools that use the OSH/YSH parsing mode, etc.
1120 #
1121
1122 # flag.tool is '' if nothing is passed
1123 # osh --tool syntax-tree is equivalent to osh -n --one-pass-parse
1124 tool_name = 'syntax-tree' if exec_opts.noexec() else flag.tool
1125
1126 if len(tool_name):
1127 # Don't save tokens becaues it's slow
1128 if tool_name != 'syntax-tree':
1129 arena.SaveTokens()
1130
1131 try:
1132 node = main_loop.ParseWholeFile(c_parser)
1133 except error.Parse as e:
1134 errfmt.PrettyPrintError(e)
1135 return 2
1136
1137 if tool_name == 'syntax-tree':
1138 ui.PrintAst(node, flag)
1139
1140 elif tool_name == 'tokens':
1141 ysh_ify.PrintTokens(arena)
1142
1143 elif tool_name == 'lossless-cat': # for test/lossless.sh
1144 ysh_ify.LosslessCat(arena)
1145
1146 elif tool_name == 'fmt':
1147 fmt.Format(arena, node)
1148
1149 elif tool_name == 'test':
1150 raise AssertionError('TODO')
1151
1152 elif tool_name == 'ysh-ify':
1153 ysh_ify.Ysh_ify(arena, node)
1154
1155 elif tool_name == 'deps':
1156 if mylib.PYTHON:
1157 deps.Deps(node)
1158
1159 else:
1160 raise AssertionError(tool_name) # flag parser validated it
1161
1162 return 0
1163
1164 #
1165 # Run a shell script
1166 #
1167
1168 with state.ctx_ThisDir(mem, script_name):
1169 try:
1170 status = main_loop.Batch(cmd_ev,
1171 c_parser,
1172 errfmt,
1173 cmd_flags=cmd_eval.IsMainProgram)
1174 except util.UserExit as e:
1175 status = e.status
1176 except KeyboardInterrupt:
1177 # The interactive shell handles this in main_loop.Interactive
1178 status = 130 # 128 + 2
1179 mut_status = IntParamBox(status)
1180 cmd_ev.RunTrapsOnExit(mut_status)
1181
1182 multi_trace.WriteDumps()
1183
1184 # NOTE: We haven't closed the file opened with fd_state.Open
1185 return mut_status.i