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

1198 lines, 767 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_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.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.push_registers] = pure_ysh.PushRegisters(mem, cmd_ev)
618
619 # Hay
620 b[builtin_i.hay] = hay_ysh.Hay(hay_state, mutable_opts, mem, cmd_ev)
621 b[builtin_i.haynode] = hay_ysh.HayNode_(hay_state, mem, cmd_ev)
622
623 # Interpreter introspection
624 b[builtin_i.type] = meta_oils.Type(procs, aliases, search_path, errfmt)
625 b[builtin_i.builtin] = meta_oils.Builtin(shell_ex, errfmt)
626 b[builtin_i.command] = meta_oils.Command(shell_ex, procs, aliases,
627 search_path)
628 # Part of YSH, but similar to builtin/command
629 b[builtin_i.runproc] = meta_oils.RunProc(shell_ex, procs, errfmt)
630 b[builtin_i.invoke] = meta_oils.Invoke(shell_ex, procs, errfmt)
631 b[builtin_i.extern_] = meta_oils.Extern(shell_ex, procs, errfmt)
632
633 # Meta builtins
634 b[builtin_i.use] = meta_oils.ShellFile(parse_ctx,
635 search_path,
636 cmd_ev,
637 fd_state,
638 tracer,
639 errfmt,
640 loader,
641 ysh_use=True)
642 source_builtin = meta_oils.ShellFile(parse_ctx, search_path, cmd_ev,
643 fd_state, tracer, errfmt, loader)
644 b[builtin_i.source] = source_builtin
645 b[builtin_i.dot] = source_builtin
646 b[builtin_i.eval] = meta_oils.Eval(parse_ctx, exec_opts, cmd_ev, tracer,
647 errfmt, mem)
648
649 # Module builtins
650 guards = {} # type: Dict[str, bool]
651 b[builtin_i.source_guard] = module_ysh.SourceGuard(guards, exec_opts,
652 errfmt)
653 b[builtin_i.is_main] = module_ysh.IsMain(mem)
654
655 # Errors
656 b[builtin_i.error] = error_ysh.Error()
657 b[builtin_i.failed] = error_ysh.Failed(mem)
658 b[builtin_i.boolstatus] = error_ysh.BoolStatus(shell_ex, errfmt)
659 b[builtin_i.try_] = error_ysh.Try(mutable_opts, mem, cmd_ev, shell_ex,
660 errfmt)
661 b[builtin_i.assert_] = error_ysh.Assert(expr_ev, errfmt)
662
663 # Pure builtins
664 true_ = pure_osh.Boolean(0)
665 b[builtin_i.colon] = true_ # a "special" builtin
666 b[builtin_i.true_] = true_
667 b[builtin_i.false_] = pure_osh.Boolean(1)
668
669 b[builtin_i.alias] = pure_osh.Alias(aliases, errfmt)
670 b[builtin_i.unalias] = pure_osh.UnAlias(aliases, errfmt)
671
672 b[builtin_i.getopts] = pure_osh.GetOpts(mem, errfmt)
673
674 b[builtin_i.shift] = assign_osh.Shift(mem)
675 b[builtin_i.unset] = assign_osh.Unset(mem, procs, unsafe_arith, errfmt)
676
677 b[builtin_i.append] = pure_ysh.Append(mem, errfmt)
678
679 # test / [ differ by need_right_bracket
680 b[builtin_i.test] = bracket_osh.Test(False, exec_opts, mem, errfmt)
681 b[builtin_i.bracket] = bracket_osh.Test(True, exec_opts, mem, errfmt)
682
683 # Output
684 b[builtin_i.echo] = io_osh.Echo(exec_opts)
685 b[builtin_i.printf] = printf_osh.Printf(mem, parse_ctx, unsafe_arith,
686 errfmt)
687 b[builtin_i.write] = io_ysh.Write(mem, errfmt)
688 redir_builtin = io_ysh.RunBlock(mem, cmd_ev) # used only for redirects
689 b[builtin_i.redir] = redir_builtin
690 b[builtin_i.fopen] = redir_builtin # alias for backward compatibility
691
692 # (pp output format isn't stable)
693 b[builtin_i.pp] = io_ysh.Pp(expr_ev, mem, errfmt, procs, arena)
694
695 # Input
696 b[builtin_i.cat] = io_osh.Cat() # for $(<file)
697 b[builtin_i.read] = read_osh.Read(splitter, mem, parse_ctx, cmd_ev, errfmt)
698
699 mapfile = io_osh.MapFile(mem, errfmt, cmd_ev)
700 b[builtin_i.mapfile] = mapfile
701 b[builtin_i.readarray] = mapfile
702
703 # Dirs
704 dir_stack = dirs_osh.DirStack()
705 b[builtin_i.cd] = dirs_osh.Cd(mem, dir_stack, cmd_ev, errfmt)
706 b[builtin_i.pushd] = dirs_osh.Pushd(mem, dir_stack, errfmt)
707 b[builtin_i.popd] = dirs_osh.Popd(mem, dir_stack, errfmt)
708 b[builtin_i.dirs] = dirs_osh.Dirs(mem, dir_stack, errfmt)
709 b[builtin_i.pwd] = dirs_osh.Pwd(mem, errfmt)
710
711 b[builtin_i.times] = misc_osh.Times()
712
713 b[builtin_i.json] = json_ysh.Json(mem, errfmt, False)
714 b[builtin_i.json8] = json_ysh.Json(mem, errfmt, True)
715
716 ### Process builtins
717 b[builtin_i.exec_] = process_osh.Exec(mem, ext_prog, fd_state, search_path,
718 errfmt)
719 b[builtin_i.umask] = process_osh.Umask()
720 b[builtin_i.ulimit] = process_osh.Ulimit()
721 b[builtin_i.wait] = process_osh.Wait(waiter, job_list, mem, tracer, errfmt)
722
723 b[builtin_i.jobs] = process_osh.Jobs(job_list)
724 b[builtin_i.fg] = process_osh.Fg(job_control, job_list, waiter)
725 b[builtin_i.bg] = process_osh.Bg(job_list)
726
727 # Could be in process_ysh
728 b[builtin_i.fork] = process_osh.Fork(shell_ex)
729 b[builtin_i.forkwait] = process_osh.ForkWait(shell_ex)
730
731 # Interactive builtins depend on readline
732 b[builtin_i.bind] = readline_osh.Bind(readline, errfmt)
733 b[builtin_i.history] = readline_osh.History(readline, sh_files, errfmt,
734 mylib.Stdout())
735
736 # Completion
737 spec_builder = completion_osh.SpecBuilder(cmd_ev, parse_ctx, word_ev,
738 splitter, comp_lookup, help_data,
739 errfmt)
740 complete_builtin = completion_osh.Complete(spec_builder, comp_lookup)
741 b[builtin_i.complete] = complete_builtin
742 b[builtin_i.compgen] = completion_osh.CompGen(spec_builder)
743 b[builtin_i.compopt] = completion_osh.CompOpt(compopt_state, errfmt)
744 b[builtin_i.compadjust] = completion_osh.CompAdjust(mem)
745
746 comp_ev = word_eval.CompletionWordEvaluator(mem, exec_opts, mutable_opts,
747 tilde_ev, splitter, errfmt)
748
749 comp_ev.arith_ev = arith_ev
750 comp_ev.expr_ev = expr_ev
751 comp_ev.prompt_ev = prompt_ev
752 comp_ev.CheckCircularDeps()
753
754 root_comp = completion.RootCompleter(comp_ev, mem, comp_lookup,
755 compopt_state, comp_ui_state,
756 comp_ctx, debug_f)
757 b[builtin_i.compexport] = completion_ysh.CompExport(root_comp)
758
759 #
760 # Initialize Builtin-in Methods
761 #
762
763 methods[value_e.Str] = {
764 'startsWith': method_str.HasAffix(method_str.START),
765 'endsWith': method_str.HasAffix(method_str.END),
766 'trim': method_str.Trim(method_str.START | method_str.END),
767 'trimStart': method_str.Trim(method_str.START),
768 'trimEnd': method_str.Trim(method_str.END),
769 'upper': method_str.Upper(),
770 'lower': method_str.Lower(),
771 'split': method_str.Split(),
772
773 # finds a substring, optional position to start at
774 'find': None,
775
776 # replace substring, OR an eggex
777 # takes count=3, the max number of replacements to do.
778 'replace': method_str.Replace(mem, expr_ev),
779
780 # Like Python's re.search, except we put it on the string object
781 # It's more consistent with Str->find(substring, pos=0)
782 # It returns value.Match() rather than an integer
783 'search': method_str.SearchMatch(method_str.SEARCH),
784
785 # like Python's re.match()
786 'leftMatch': method_str.SearchMatch(method_str.LEFT_MATCH),
787
788 # like Python's re.fullmatch(), not sure if we really need it
789 'fullMatch': None,
790 }
791 methods[value_e.Dict] = {
792 # keys() values() get() are FREE functions, not methods
793 # I think items() isn't as necessary because dicts are ordered? YSH
794 # code shouldn't use the List of Lists representation.
795 'M/erase': method_dict.Erase(),
796 # could be d->tally() or d->increment(), but inc() is short
797 #
798 # call d->inc('mycounter')
799 # call d->inc('mycounter', 3)
800 'M/inc': None,
801
802 # call d->accum('mygroup', 'value')
803 'M/accum': None,
804
805 # DEPRECATED - use free functions
806 'get': method_dict.Get(),
807 'keys': method_dict.Keys(),
808 'values': method_dict.Values(),
809 }
810 methods[value_e.List] = {
811 'M/reverse': method_list.Reverse(),
812 'M/append': method_list.Append(),
813 'M/extend': method_list.Extend(),
814 'M/pop': method_list.Pop(),
815 'M/insert': None, # insert object before index
816 'M/remove': None, # insert object before index
817 'indexOf': method_list.IndexOf(), # return first index of value, or -1
818 # Python list() has index(), which raises ValueError
819 # But this is consistent with Str->find(), and doesn't
820 # use exceptions
821 'join': func_misc.Join(), # both a method and a func
822 }
823
824 methods[value_e.Match] = {
825 'group': func_eggex.MatchMethod(func_eggex.G, expr_ev),
826 'start': func_eggex.MatchMethod(func_eggex.S, None),
827 'end': func_eggex.MatchMethod(func_eggex.E, None),
828 }
829
830 methods[value_e.Place] = {
831 # __mut_setValue()
832
833 # instead of setplace keyword
834 'M/setValue': method_other.SetValue(mem),
835 }
836
837 methods[value_e.Command] = {
838 # var x = ^(echo hi)
839 # Export source code and line number
840 # Useful for test frameworks and so forth
841 'export': None,
842 }
843
844 #
845 # Initialize Built-in Funcs
846 #
847
848 parse_hay = func_hay.ParseHay(fd_state, parse_ctx, errfmt)
849 eval_hay = func_hay.EvalHay(hay_state, mutable_opts, mem, cmd_ev)
850 hay_func = func_hay.HayFunc(hay_state)
851
852 _AddBuiltinFunc(mem, 'parseHay', parse_hay)
853 _AddBuiltinFunc(mem, 'evalHay', eval_hay)
854 _AddBuiltinFunc(mem, '_hay', hay_func)
855
856 _AddBuiltinFunc(mem, 'len', func_misc.Len())
857 _AddBuiltinFunc(mem, 'type', func_misc.Type())
858
859 g = func_eggex.MatchFunc(func_eggex.G, expr_ev, mem)
860 _AddBuiltinFunc(mem, '_group', g)
861 _AddBuiltinFunc(mem, '_match',
862 g) # TODO: remove this backward compat alias
863 _AddBuiltinFunc(mem, '_start',
864 func_eggex.MatchFunc(func_eggex.S, None, mem))
865 _AddBuiltinFunc(mem, '_end', func_eggex.MatchFunc(func_eggex.E, None, mem))
866
867 _AddBuiltinFunc(mem, 'id', func_reflect.Id())
868 _AddBuiltinFunc(mem, 'parseCommand',
869 func_reflect.ParseCommand(parse_ctx, errfmt))
870 _AddBuiltinFunc(mem, 'parseExpr',
871 func_reflect.ParseExpr(parse_ctx, errfmt))
872 _AddBuiltinFunc(mem, 'evalExpr', func_reflect.EvalExpr(expr_ev))
873
874 _AddBuiltinFunc(mem, 'shvarGet', func_reflect.Shvar_get(mem))
875 _AddBuiltinFunc(mem, 'getVar', func_reflect.GetVar(mem))
876 _AddBuiltinFunc(mem, 'setVar', func_reflect.SetVar(mem))
877
878 _AddBuiltinFunc(mem, 'Object', func_misc.Object())
879 _AddBuiltinFunc(mem, 'prototype', func_misc.Prototype())
880 _AddBuiltinFunc(mem, 'propView', func_misc.PropView())
881
882 # type conversions
883 _AddBuiltinFunc(mem, 'bool', func_misc.Bool())
884 _AddBuiltinFunc(mem, 'int', func_misc.Int())
885 _AddBuiltinFunc(mem, 'float', func_misc.Float())
886 _AddBuiltinFunc(mem, 'str', func_misc.Str_())
887 _AddBuiltinFunc(mem, 'list', func_misc.List_())
888 _AddBuiltinFunc(mem, 'dict', func_misc.DictFunc())
889
890 # Dict functions
891 _AddBuiltinFunc(mem, 'get', method_dict.Get())
892 _AddBuiltinFunc(mem, 'keys', method_dict.Keys())
893 _AddBuiltinFunc(mem, 'values', method_dict.Values())
894
895 _AddBuiltinFunc(mem, 'runes', func_misc.Runes())
896 _AddBuiltinFunc(mem, 'encodeRunes', func_misc.EncodeRunes())
897 _AddBuiltinFunc(mem, 'bytes', func_misc.Bytes())
898 _AddBuiltinFunc(mem, 'encodeBytes', func_misc.EncodeBytes())
899
900 # Str
901 #_AddBuiltinFunc(mem, 'strcmp', None)
902 # TODO: This should be Python style splitting
903 _AddBuiltinFunc(mem, 'split', func_misc.Split(splitter))
904 _AddBuiltinFunc(mem, 'shSplit', func_misc.Split(splitter))
905
906 # Float
907 _AddBuiltinFunc(mem, 'floatsEqual', func_misc.FloatsEqual())
908
909 # List
910 _AddBuiltinFunc(mem, 'join', func_misc.Join())
911 _AddBuiltinFunc(mem, 'maybe', func_misc.Maybe())
912 _AddBuiltinFunc(mem, 'glob', func_misc.Glob(globber))
913
914 # Serialize
915 _AddBuiltinFunc(mem, 'toJson8', func_misc.ToJson8(True))
916 _AddBuiltinFunc(mem, 'toJson', func_misc.ToJson8(False))
917
918 _AddBuiltinFunc(mem, 'fromJson8', func_misc.FromJson8(True))
919 _AddBuiltinFunc(mem, 'fromJson', func_misc.FromJson8(False))
920
921 # Demos
922 _AddBuiltinFunc(mem, '_a2sp', func_misc.BashArrayToSparse())
923 _AddBuiltinFunc(mem, '_opsp', func_misc.SparseOp())
924
925 mem.AddBuiltin('io', io_obj)
926
927 #
928 # Is the shell interactive?
929 #
930
931 # History evaluation is a no-op if readline is None.
932 hist_ev = history.Evaluator(readline, hist_ctx, debug_f)
933
934 if flag.c is not None:
935 src = source.CFlag # type: source_t
936 line_reader = reader.StringLineReader(flag.c,
937 arena) # type: reader._Reader
938 if flag.i: # -c and -i can be combined
939 mutable_opts.set_interactive()
940
941 elif flag.i: # force interactive
942 src = source.Stdin(' -i')
943 line_reader = reader.InteractiveLineReader(arena, prompt_ev, hist_ev,
944 readline, prompt_state)
945 mutable_opts.set_interactive()
946
947 else:
948 if script_name is None:
949 if flag.headless:
950 src = source.Headless
951 line_reader = None # unused!
952 # Not setting '-i' flag for now. Some people's bashrc may want it?
953 else:
954 stdin_ = mylib.Stdin()
955 # --tool never starts a prompt
956 if len(flag.tool) == 0 and stdin_.isatty():
957 src = source.Interactive
958 line_reader = reader.InteractiveLineReader(
959 arena, prompt_ev, hist_ev, readline, prompt_state)
960 mutable_opts.set_interactive()
961 else:
962 src = source.Stdin('')
963 line_reader = reader.FileLineReader(stdin_, arena)
964 else:
965 src = source.MainFile(script_name)
966 try:
967 f = fd_state.Open(script_name)
968 except (IOError, OSError) as e:
969 print_stderr("%s: Couldn't open %r: %s" %
970 (lang, script_name, posix.strerror(e.errno)))
971 return 1
972 line_reader = reader.FileLineReader(f, arena)
973
974 # Pretend it came from somewhere else
975 if flag.location_str is not None:
976 src = source.Synthetic(flag.location_str)
977 assert line_reader is not None
978 location_start_line = mops.BigTruncate(flag.location_start_line)
979 if location_start_line != -1:
980 line_reader.SetLineOffset(location_start_line)
981
982 arena.PushSource(src)
983
984 # Calculate ~/.config/oils/oshrc or yshrc. Used for both -i and --headless
985 # We avoid cluttering the user's home directory. Some users may want to ln
986 # -s ~/.config/oils/oshrc ~/oshrc or ~/.oshrc.
987
988 # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other
989
990 config_dir = '.config/oils'
991 rc_paths = [] # type: List[str]
992 if not flag.norc and (flag.headless or exec_opts.interactive()):
993 # User's rcfile comes FIRST. Later we can add an 'after-rcdir' hook
994 rc_path = flag.rcfile
995 if rc_path is None:
996 rc_paths.append(
997 os_path.join(home_dir, '%s/%src' % (config_dir, lang)))
998 else:
999 rc_paths.append(rc_path)
1000
1001 # Load all files in ~/.config/oil/oshrc.d or oilrc.d
1002 # This way "installers" can avoid mutating oshrc directly
1003
1004 rc_dir = flag.rcdir
1005 if rc_dir is None:
1006 rc_dir = os_path.join(home_dir, '%s/%src.d' % (config_dir, lang))
1007
1008 rc_paths.extend(libc.glob(os_path.join(rc_dir, '*')))
1009 else:
1010 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
1011 print_stderr('%s warning: --rcfile ignored with --norc' % lang)
1012 if flag.rcdir is not None:
1013 print_stderr('%s warning: --rcdir ignored with --norc' % lang)
1014
1015 # Initialize even in non-interactive shell, for 'compexport'
1016 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
1017
1018 if flag.headless:
1019 state.InitInteractive(mem)
1020 mutable_opts.set_redefine_proc_func()
1021 mutable_opts.set_redefine_module()
1022
1023 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1024 for rc_path in rc_paths:
1025 with state.ctx_ThisDir(mem, rc_path):
1026 try:
1027 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1028 cmd_ev, errfmt)
1029 except util.UserExit as e:
1030 return e.status
1031
1032 loop = main_loop.Headless(cmd_ev, parse_ctx, errfmt)
1033 try:
1034 # TODO: What other exceptions happen here?
1035 status = loop.Loop()
1036 except util.UserExit as e:
1037 status = e.status
1038
1039 # Same logic as interactive shell
1040 mut_status = IntParamBox(status)
1041 cmd_ev.RunTrapsOnExit(mut_status)
1042 status = mut_status.i
1043
1044 return status
1045
1046 # Note: headless mode above doesn't use c_parser
1047 assert line_reader is not None
1048 c_parser = parse_ctx.MakeOshParser(line_reader)
1049
1050 if exec_opts.interactive():
1051 state.InitInteractive(mem)
1052 # bash: 'set -o emacs' is the default only in the interactive shell
1053 mutable_opts.set_emacs()
1054 mutable_opts.set_redefine_proc_func()
1055 mutable_opts.set_redefine_module()
1056
1057 if readline:
1058 term_width = 0
1059 if flag.completion_display == 'nice':
1060 try:
1061 term_width = libc.get_terminal_width()
1062 except (IOError, OSError): # stdin not a terminal
1063 pass
1064
1065 if term_width != 0:
1066 display = comp_ui.NiceDisplay(
1067 term_width, comp_ui_state, prompt_state, debug_f, readline,
1068 signal_safe) # type: comp_ui._IDisplay
1069 else:
1070 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1071 debug_f)
1072
1073 comp_ui.InitReadline(readline, sh_files.HistoryFile(), root_comp,
1074 display, debug_f)
1075
1076 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
1077 if flag.completion_demo:
1078 _CompletionDemo(comp_lookup)
1079
1080 else: # Without readline module
1081 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1082 debug_f)
1083
1084 process.InitInteractiveShell(signal_safe) # Set signal handlers
1085
1086 # The interactive shell leads a process group which controls the terminal.
1087 # It MUST give up the terminal afterward, otherwise we get SIGTTIN /
1088 # SIGTTOU bugs.
1089 with process.ctx_TerminalControl(job_control, errfmt):
1090
1091 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1092 for rc_path in rc_paths:
1093 with state.ctx_ThisDir(mem, rc_path):
1094 try:
1095 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1096 cmd_ev, errfmt)
1097 except util.UserExit as e:
1098 return e.status
1099
1100 assert line_reader is not None
1101 line_reader.Reset() # After sourcing startup file, render $PS1
1102
1103 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, cmd_ev, errfmt)
1104 try:
1105 status = main_loop.Interactive(flag, cmd_ev, c_parser, display,
1106 prompt_plugin, waiter, errfmt)
1107 except util.UserExit as e:
1108 status = e.status
1109
1110 mut_status = IntParamBox(status)
1111 cmd_ev.RunTrapsOnExit(mut_status)
1112 status = mut_status.i
1113
1114 if readline:
1115 hist_file = sh_files.HistoryFile()
1116 if hist_file is not None:
1117 try:
1118 readline.write_history_file(hist_file)
1119 except (IOError, OSError):
1120 pass
1121
1122 return status
1123
1124 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
1125 print_stderr('%s warning: --rcfile ignored in non-interactive shell' %
1126 lang)
1127 if flag.rcdir is not None:
1128 print_stderr('%s warning: --rcdir ignored in non-interactive shell' %
1129 lang)
1130
1131 #
1132 # Tools that use the OSH/YSH parsing mode, etc.
1133 #
1134
1135 # flag.tool is '' if nothing is passed
1136 # osh --tool syntax-tree is equivalent to osh -n --one-pass-parse
1137 tool_name = 'syntax-tree' if exec_opts.noexec() else flag.tool
1138
1139 if len(tool_name):
1140 # Don't save tokens becaues it's slow
1141 if tool_name != 'syntax-tree':
1142 arena.SaveTokens()
1143
1144 try:
1145 node = main_loop.ParseWholeFile(c_parser)
1146 except error.Parse as e:
1147 errfmt.PrettyPrintError(e)
1148 return 2
1149
1150 if tool_name == 'syntax-tree':
1151 ui.PrintAst(node, flag)
1152
1153 elif tool_name == 'tokens':
1154 ysh_ify.PrintTokens(arena)
1155
1156 elif tool_name == 'lossless-cat': # for test/lossless.sh
1157 ysh_ify.LosslessCat(arena)
1158
1159 elif tool_name == 'fmt':
1160 fmt.Format(arena, node)
1161
1162 elif tool_name == 'test':
1163 raise AssertionError('TODO')
1164
1165 elif tool_name == 'ysh-ify':
1166 ysh_ify.Ysh_ify(arena, node)
1167
1168 elif tool_name == 'deps':
1169 if mylib.PYTHON:
1170 deps.Deps(node)
1171
1172 else:
1173 raise AssertionError(tool_name) # flag parser validated it
1174
1175 return 0
1176
1177 #
1178 # Run a shell script
1179 #
1180
1181 with state.ctx_ThisDir(mem, script_name):
1182 try:
1183 status = main_loop.Batch(cmd_ev,
1184 c_parser,
1185 errfmt,
1186 cmd_flags=cmd_eval.IsMainProgram)
1187 except util.UserExit as e:
1188 status = e.status
1189 except KeyboardInterrupt:
1190 # The interactive shell handles this in main_loop.Interactive
1191 status = 130 # 128 + 2
1192 mut_status = IntParamBox(status)
1193 cmd_ev.RunTrapsOnExit(mut_status)
1194
1195 multi_trace.WriteDumps()
1196
1197 # NOTE: We haven't closed the file opened with fd_state.Open
1198 return mut_status.i