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

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