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

1203 lines, 770 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.MainFile(rc_path)):
165 # TODO: handle status, e.g. 2 for ParseError
166 unused = main_loop.Batch(cmd_ev, rc_c_parser, errfmt)
167
168 f.close()
169
170
171class ShellOptHook(state.OptHook):
172
173 def __init__(self, readline):
174 # type: (Optional[Readline]) -> None
175 self.readline = readline
176
177 def OnChange(self, opt0_array, opt_name, b):
178 # type: (List[bool], str, bool) -> bool
179 """This method is called whenever an option is changed.
180
181 Returns success or failure.
182 """
183 if opt_name == 'vi' or opt_name == 'emacs':
184 # TODO: Replace with a hook? Just like setting LANG= can have a hook.
185 if self.readline:
186 self.readline.parse_and_bind("set editing-mode " + opt_name)
187 else:
188 print_stderr(
189 "Warning: Can't set option %r because shell wasn't compiled with GNU readline"
190 % opt_name)
191 return False
192
193 # Invert: they are mutually exclusive!
194 if opt_name == 'vi':
195 opt0_array[option_i.emacs] = not b
196 elif opt_name == 'emacs':
197 opt0_array[option_i.vi] = not b
198
199 return True
200
201
202def _AddBuiltinFunc(mem, name, func):
203 # type: (state.Mem, str, vm._Callable) -> None
204 assert isinstance(func, vm._Callable), func
205 mem.AddBuiltin(name, value.BuiltinFunc(func))
206
207
208def InitAssignmentBuiltins(
209 mem, # type: state.Mem
210 procs, # type: state.Procs
211 exec_opts, # type: optview.Exec
212 errfmt, # type: ui.ErrorFormatter
213):
214 # type: (...) -> Dict[int, vm._AssignBuiltin]
215
216 assign_b = {} # type: Dict[int, vm._AssignBuiltin]
217
218 new_var = assign_osh.NewVar(mem, procs, exec_opts, errfmt)
219 assign_b[builtin_i.declare] = new_var
220 assign_b[builtin_i.typeset] = new_var
221 assign_b[builtin_i.local] = new_var
222
223 assign_b[builtin_i.export_] = assign_osh.Export(mem, errfmt)
224 assign_b[builtin_i.readonly] = assign_osh.Readonly(mem, errfmt)
225
226 return assign_b
227
228
229class ShellFiles(object):
230
231 def __init__(self, lang, home_dir, mem, flag):
232 # type: (str, str, state.Mem, arg_types.main) -> None
233 assert lang in ('osh', 'ysh'), lang
234 self.lang = lang
235 self.home_dir = home_dir
236 self.mem = mem
237 self.flag = flag
238
239 def _HistVar(self):
240 # type: () -> str
241 return 'HISTFILE' if self.lang == 'osh' else 'YSH_HISTFILE'
242
243 def _DefaultHistoryFile(self):
244 # type: () -> str
245 return os_path.join(self.home_dir,
246 '.local/share/oils/%s_history' % self.lang)
247
248 def InitAfterLoadingEnv(self):
249 # type: () -> None
250
251 hist_var = self._HistVar()
252 if self.mem.GetValue(hist_var).tag() == value_e.Undef:
253 # Note: if the directory doesn't exist, GNU readline ignores
254 state.SetGlobalString(self.mem, hist_var,
255 self._DefaultHistoryFile())
256
257 def HistoryFile(self):
258 # type: () -> Optional[str]
259 # TODO: In non-strict mode we should try to cast the HISTFILE value to a
260 # string following bash's rules
261
262 UP_val = self.mem.GetValue(self._HistVar())
263 if UP_val.tag() == value_e.Str:
264 val = cast(value.Str, UP_val)
265 return val.s
266 else:
267 # Note: if HISTFILE is an array, bash will return ${HISTFILE[0]}
268 return None
269 #return self._DefaultHistoryFile()
270
271 # TODO: can we recover line information here?
272 # might be useful to show where HISTFILE was set
273 #raise error.Strict("$HISTFILE should only ever be a string", loc.Missing)
274
275
276def Main(
277 lang, # type: str
278 arg_r, # type: args.Reader
279 environ, # type: Dict[str, str]
280 login_shell, # type: bool
281 loader, # type: pyutil._ResourceLoader
282 readline, # type: Optional[Readline]
283):
284 # type: (...) -> int
285 """The full shell lifecycle. Used by bin/osh and bin/ysh.
286
287 Args:
288 lang: 'osh' or 'ysh'
289 login_shell: Was - on argv[0]?
290 loader: to get help, version, grammar, etc.
291 readline: optional GNU readline
292 """
293 # Differences between osh and ysh:
294 # - oshrc vs yshrc
295 # - shopt -s ysh:all
296 # - Prompt
297 # - --help
298
299 argv0 = arg_r.Peek()
300 assert argv0 is not None
301 arg_r.Next()
302
303 assert lang in ('osh', 'ysh'), lang
304
305 try:
306 attrs = flag_util.ParseMore('main', arg_r)
307 except error.Usage as e:
308 print_stderr('%s usage error: %s' % (lang, e.msg))
309 return 2
310 flag = arg_types.main(attrs.attrs)
311
312 arena = alloc.Arena()
313 errfmt = ui.ErrorFormatter()
314
315 if flag.help:
316 util.HelpFlag(loader, '%s-usage' % lang, mylib.Stdout())
317 return 0
318 if flag.version:
319 util.VersionFlag(loader, mylib.Stdout())
320 return 0
321
322 if flag.tool == 'cat-em':
323 paths = arg_r.Rest()
324
325 status = 0
326 for p in paths:
327 try:
328 contents = loader.Get(p)
329 print(contents)
330 except (OSError, IOError):
331 print_stderr("cat-em: %r not found" % p)
332 status = 1
333 return status
334
335 debug_stack = [] # type: List[debug_frame_t]
336 if arg_r.AtEnd():
337 dollar0 = argv0
338 else:
339 dollar0 = arg_r.Peek() # the script name, or the arg after -c
340
341 frame0 = debug_frame.Main(dollar0)
342 debug_stack.append(frame0)
343
344 script_name = arg_r.Peek() # type: Optional[str]
345 arg_r.Next()
346 mem = state.Mem(dollar0, arg_r.Rest(), arena, debug_stack)
347
348 opt_hook = ShellOptHook(readline)
349 # Note: only MutableOpts needs mem, so it's not a true circular dep.
350 parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook)
351 mem.exec_opts = exec_opts # circular dep
352 mutable_opts.Init()
353
354 # Set these BEFORE processing flags, so they can be overridden.
355 if lang == 'ysh':
356 mutable_opts.SetAnyOption('ysh:all', True)
357
358 pure_osh.SetOptionsFromFlags(mutable_opts, attrs.opt_changes,
359 attrs.shopt_changes)
360
361 version_str = pyutil.GetVersion(loader)
362 state.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_oils.Type(procs, aliases, search_path, errfmt)
626 b[builtin_i.builtin] = meta_oils.Builtin(shell_ex, errfmt)
627 b[builtin_i.command] = meta_oils.Command(shell_ex, procs, aliases,
628 search_path)
629 # Part of YSH, but similar to builtin/command
630 b[builtin_i.runproc] = meta_oils.RunProc(shell_ex, procs, errfmt)
631 b[builtin_i.invoke] = meta_oils.Invoke(shell_ex, procs, errfmt)
632 b[builtin_i.extern_] = meta_oils.Extern(shell_ex, procs, errfmt)
633
634 # Meta builtins
635 module_invoke = module_ysh.ModuleInvoke(cmd_ev, tracer, errfmt)
636 b[builtin_i.use] = meta_oils.ShellFile(parse_ctx,
637 search_path,
638 cmd_ev,
639 fd_state,
640 tracer,
641 errfmt,
642 loader,
643 module_invoke=module_invoke)
644 source_builtin = meta_oils.ShellFile(parse_ctx, search_path, cmd_ev,
645 fd_state, tracer, errfmt, loader)
646 b[builtin_i.source] = source_builtin
647 b[builtin_i.dot] = source_builtin
648 b[builtin_i.eval] = meta_oils.Eval(parse_ctx, exec_opts, cmd_ev, tracer,
649 errfmt, mem)
650
651 # Module builtins
652 guards = {} # type: Dict[str, bool]
653 b[builtin_i.source_guard] = module_ysh.SourceGuard(guards, exec_opts,
654 errfmt)
655 b[builtin_i.is_main] = module_ysh.IsMain(mem)
656
657 # Errors
658 b[builtin_i.error] = error_ysh.Error()
659 b[builtin_i.failed] = error_ysh.Failed(mem)
660 b[builtin_i.boolstatus] = error_ysh.BoolStatus(shell_ex, errfmt)
661 b[builtin_i.try_] = error_ysh.Try(mutable_opts, mem, cmd_ev, shell_ex,
662 errfmt)
663 b[builtin_i.assert_] = error_ysh.Assert(expr_ev, errfmt)
664
665 # Pure builtins
666 true_ = pure_osh.Boolean(0)
667 b[builtin_i.colon] = true_ # a "special" builtin
668 b[builtin_i.true_] = true_
669 b[builtin_i.false_] = pure_osh.Boolean(1)
670
671 b[builtin_i.alias] = pure_osh.Alias(aliases, errfmt)
672 b[builtin_i.unalias] = pure_osh.UnAlias(aliases, errfmt)
673
674 b[builtin_i.getopts] = pure_osh.GetOpts(mem, errfmt)
675
676 b[builtin_i.shift] = assign_osh.Shift(mem)
677 b[builtin_i.unset] = assign_osh.Unset(mem, procs, unsafe_arith, errfmt)
678
679 b[builtin_i.append] = pure_ysh.Append(mem, errfmt)
680
681 # test / [ differ by need_right_bracket
682 b[builtin_i.test] = bracket_osh.Test(False, exec_opts, mem, errfmt)
683 b[builtin_i.bracket] = bracket_osh.Test(True, exec_opts, mem, errfmt)
684
685 # Output
686 b[builtin_i.echo] = io_osh.Echo(exec_opts)
687 b[builtin_i.printf] = printf_osh.Printf(mem, parse_ctx, unsafe_arith,
688 errfmt)
689 b[builtin_i.write] = io_ysh.Write(mem, errfmt)
690 redir_builtin = io_ysh.RunBlock(mem, cmd_ev) # used only for redirects
691 b[builtin_i.redir] = redir_builtin
692 b[builtin_i.fopen] = redir_builtin # alias for backward compatibility
693
694 # (pp output format isn't stable)
695 b[builtin_i.pp] = io_ysh.Pp(expr_ev, mem, errfmt, procs, arena)
696
697 # Input
698 b[builtin_i.cat] = io_osh.Cat() # for $(<file)
699 b[builtin_i.read] = read_osh.Read(splitter, mem, parse_ctx, cmd_ev, errfmt)
700
701 mapfile = io_osh.MapFile(mem, errfmt, cmd_ev)
702 b[builtin_i.mapfile] = mapfile
703 b[builtin_i.readarray] = mapfile
704
705 # Dirs
706 dir_stack = dirs_osh.DirStack()
707 b[builtin_i.cd] = dirs_osh.Cd(mem, dir_stack, cmd_ev, errfmt)
708 b[builtin_i.pushd] = dirs_osh.Pushd(mem, dir_stack, errfmt)
709 b[builtin_i.popd] = dirs_osh.Popd(mem, dir_stack, errfmt)
710 b[builtin_i.dirs] = dirs_osh.Dirs(mem, dir_stack, errfmt)
711 b[builtin_i.pwd] = dirs_osh.Pwd(mem, errfmt)
712
713 b[builtin_i.times] = misc_osh.Times()
714
715 b[builtin_i.json] = json_ysh.Json(mem, errfmt, False)
716 b[builtin_i.json8] = json_ysh.Json(mem, errfmt, True)
717
718 ### Process builtins
719 b[builtin_i.exec_] = process_osh.Exec(mem, ext_prog, fd_state, search_path,
720 errfmt)
721 b[builtin_i.umask] = process_osh.Umask()
722 b[builtin_i.ulimit] = process_osh.Ulimit()
723 b[builtin_i.wait] = process_osh.Wait(waiter, job_list, mem, tracer, errfmt)
724
725 b[builtin_i.jobs] = process_osh.Jobs(job_list)
726 b[builtin_i.fg] = process_osh.Fg(job_control, job_list, waiter)
727 b[builtin_i.bg] = process_osh.Bg(job_list)
728
729 # Could be in process_ysh
730 b[builtin_i.fork] = process_osh.Fork(shell_ex)
731 b[builtin_i.forkwait] = process_osh.ForkWait(shell_ex)
732
733 # Interactive builtins depend on readline
734 b[builtin_i.bind] = readline_osh.Bind(readline, errfmt)
735 b[builtin_i.history] = readline_osh.History(readline, sh_files, errfmt,
736 mylib.Stdout())
737
738 # Completion
739 spec_builder = completion_osh.SpecBuilder(cmd_ev, parse_ctx, word_ev,
740 splitter, comp_lookup, help_data,
741 errfmt)
742 complete_builtin = completion_osh.Complete(spec_builder, comp_lookup)
743 b[builtin_i.complete] = complete_builtin
744 b[builtin_i.compgen] = completion_osh.CompGen(spec_builder)
745 b[builtin_i.compopt] = completion_osh.CompOpt(compopt_state, errfmt)
746 b[builtin_i.compadjust] = completion_osh.CompAdjust(mem)
747
748 comp_ev = word_eval.CompletionWordEvaluator(mem, exec_opts, mutable_opts,
749 tilde_ev, splitter, errfmt)
750
751 comp_ev.arith_ev = arith_ev
752 comp_ev.expr_ev = expr_ev
753 comp_ev.prompt_ev = prompt_ev
754 comp_ev.CheckCircularDeps()
755
756 root_comp = completion.RootCompleter(comp_ev, mem, comp_lookup,
757 compopt_state, comp_ui_state,
758 comp_ctx, debug_f)
759 b[builtin_i.compexport] = completion_ysh.CompExport(root_comp)
760
761 #
762 # Initialize Builtin-in Methods
763 #
764
765 methods[value_e.Str] = {
766 'startsWith': method_str.HasAffix(method_str.START),
767 'endsWith': method_str.HasAffix(method_str.END),
768 'trim': method_str.Trim(method_str.START | method_str.END),
769 'trimStart': method_str.Trim(method_str.START),
770 'trimEnd': method_str.Trim(method_str.END),
771 'upper': method_str.Upper(),
772 'lower': method_str.Lower(),
773 'split': method_str.Split(),
774
775 # finds a substring, optional position to start at
776 'find': None,
777
778 # replace substring, OR an eggex
779 # takes count=3, the max number of replacements to do.
780 'replace': method_str.Replace(mem, expr_ev),
781
782 # Like Python's re.search, except we put it on the string object
783 # It's more consistent with Str->find(substring, pos=0)
784 # It returns value.Match() rather than an integer
785 'search': method_str.SearchMatch(method_str.SEARCH),
786
787 # like Python's re.match()
788 'leftMatch': method_str.SearchMatch(method_str.LEFT_MATCH),
789
790 # like Python's re.fullmatch(), not sure if we really need it
791 'fullMatch': None,
792 }
793 methods[value_e.Dict] = {
794 # keys() values() get() are FREE functions, not methods
795 # I think items() isn't as necessary because dicts are ordered? YSH
796 # code shouldn't use the List of Lists representation.
797 'M/erase': method_dict.Erase(),
798 # could be d->tally() or d->increment(), but inc() is short
799 #
800 # call d->inc('mycounter')
801 # call d->inc('mycounter', 3)
802 'M/inc': None,
803
804 # call d->accum('mygroup', 'value')
805 'M/accum': None,
806
807 # DEPRECATED - use free functions
808 'get': method_dict.Get(),
809 'keys': method_dict.Keys(),
810 'values': method_dict.Values(),
811 }
812 methods[value_e.List] = {
813 'M/reverse': method_list.Reverse(),
814 'M/append': method_list.Append(),
815 'M/extend': method_list.Extend(),
816 'M/pop': method_list.Pop(),
817 'M/insert': None, # insert object before index
818 'M/remove': None, # insert object before index
819 'indexOf': method_list.IndexOf(), # return first index of value, or -1
820 # Python list() has index(), which raises ValueError
821 # But this is consistent with Str->find(), and doesn't
822 # use exceptions
823 'join': func_misc.Join(), # both a method and a func
824 }
825
826 methods[value_e.Match] = {
827 'group': func_eggex.MatchMethod(func_eggex.G, expr_ev),
828 'start': func_eggex.MatchMethod(func_eggex.S, None),
829 'end': func_eggex.MatchMethod(func_eggex.E, None),
830 }
831
832 methods[value_e.Place] = {
833 # __mut_setValue()
834
835 # instead of setplace keyword
836 'M/setValue': method_other.SetValue(mem),
837 }
838
839 methods[value_e.CommandFrag] = {
840 # var x = ^(echo hi)
841 # Export source code and line number
842 # Useful for test frameworks and so forth
843 'export': None,
844 }
845
846 #
847 # Initialize Built-in Funcs
848 #
849
850 parse_hay = func_hay.ParseHay(fd_state, parse_ctx, mem, errfmt)
851 eval_hay = func_hay.EvalHay(hay_state, mutable_opts, mem, cmd_ev)
852 hay_func = func_hay.HayFunc(hay_state)
853
854 _AddBuiltinFunc(mem, 'parseHay', parse_hay)
855 _AddBuiltinFunc(mem, 'evalHay', eval_hay)
856 _AddBuiltinFunc(mem, '_hay', hay_func)
857
858 _AddBuiltinFunc(mem, 'len', func_misc.Len())
859 _AddBuiltinFunc(mem, 'type', func_misc.Type())
860
861 g = func_eggex.MatchFunc(func_eggex.G, expr_ev, mem)
862 _AddBuiltinFunc(mem, '_group', g)
863 _AddBuiltinFunc(mem, '_match',
864 g) # TODO: remove this backward compat alias
865 _AddBuiltinFunc(mem, '_start',
866 func_eggex.MatchFunc(func_eggex.S, None, mem))
867 _AddBuiltinFunc(mem, '_end', func_eggex.MatchFunc(func_eggex.E, None, mem))
868
869 _AddBuiltinFunc(mem, 'id', func_reflect.Id())
870 _AddBuiltinFunc(mem, 'parseCommand',
871 func_reflect.ParseCommand(parse_ctx, mem, errfmt))
872 _AddBuiltinFunc(mem, 'parseExpr',
873 func_reflect.ParseExpr(parse_ctx, errfmt))
874 _AddBuiltinFunc(mem, 'evalExpr', func_reflect.EvalExpr(expr_ev))
875
876 _AddBuiltinFunc(mem, 'shvarGet', func_reflect.Shvar_get(mem))
877 _AddBuiltinFunc(mem, 'getVar', func_reflect.GetVar(mem))
878 _AddBuiltinFunc(mem, 'setVar', func_reflect.SetVar(mem))
879
880 _AddBuiltinFunc(mem, 'Object', func_misc.Object())
881 _AddBuiltinFunc(mem, 'prototype', func_misc.Prototype())
882 _AddBuiltinFunc(mem, 'propView', func_misc.PropView())
883
884 # type conversions
885 _AddBuiltinFunc(mem, 'bool', func_misc.Bool())
886 _AddBuiltinFunc(mem, 'int', func_misc.Int())
887 _AddBuiltinFunc(mem, 'float', func_misc.Float())
888 _AddBuiltinFunc(mem, 'str', func_misc.Str_())
889 _AddBuiltinFunc(mem, 'list', func_misc.List_())
890 _AddBuiltinFunc(mem, 'dict', func_misc.DictFunc())
891
892 # Dict functions
893 _AddBuiltinFunc(mem, 'get', method_dict.Get())
894 _AddBuiltinFunc(mem, 'keys', method_dict.Keys())
895 _AddBuiltinFunc(mem, 'values', method_dict.Values())
896
897 _AddBuiltinFunc(mem, 'runes', func_misc.Runes())
898 _AddBuiltinFunc(mem, 'encodeRunes', func_misc.EncodeRunes())
899 _AddBuiltinFunc(mem, 'bytes', func_misc.Bytes())
900 _AddBuiltinFunc(mem, 'encodeBytes', func_misc.EncodeBytes())
901
902 # Str
903 #_AddBuiltinFunc(mem, 'strcmp', None)
904 # TODO: This should be Python style splitting
905 _AddBuiltinFunc(mem, 'split', func_misc.Split(splitter))
906 _AddBuiltinFunc(mem, 'shSplit', func_misc.Split(splitter))
907
908 # Float
909 _AddBuiltinFunc(mem, 'floatsEqual', func_misc.FloatsEqual())
910
911 # List
912 _AddBuiltinFunc(mem, 'join', func_misc.Join())
913 _AddBuiltinFunc(mem, 'maybe', func_misc.Maybe())
914 _AddBuiltinFunc(mem, 'glob', func_misc.Glob(globber))
915
916 # Serialize
917 _AddBuiltinFunc(mem, 'toJson8', func_misc.ToJson8(True))
918 _AddBuiltinFunc(mem, 'toJson', func_misc.ToJson8(False))
919
920 _AddBuiltinFunc(mem, 'fromJson8', func_misc.FromJson8(True))
921 _AddBuiltinFunc(mem, 'fromJson', func_misc.FromJson8(False))
922
923 # Demos
924 _AddBuiltinFunc(mem, '_a2sp', func_misc.BashArrayToSparse())
925 _AddBuiltinFunc(mem, '_opsp', func_misc.SparseOp())
926
927 mem.AddBuiltin('io', io_obj)
928
929 # Special case for testing
930 mem.AddBuiltin('module-invoke', value.BuiltinProc(module_invoke))
931
932 #
933 # Is the shell interactive?
934 #
935
936 # History evaluation is a no-op if readline is None.
937 hist_ev = history.Evaluator(readline, hist_ctx, debug_f)
938
939 if flag.c is not None:
940 src = source.CFlag # type: source_t
941 line_reader = reader.StringLineReader(flag.c,
942 arena) # type: reader._Reader
943 if flag.i: # -c and -i can be combined
944 mutable_opts.set_interactive()
945
946 elif flag.i: # force interactive
947 src = source.Stdin(' -i')
948 line_reader = reader.InteractiveLineReader(arena, prompt_ev, hist_ev,
949 readline, prompt_state)
950 mutable_opts.set_interactive()
951
952 else:
953 if script_name is None:
954 if flag.headless:
955 src = source.Headless
956 line_reader = None # unused!
957 # Not setting '-i' flag for now. Some people's bashrc may want it?
958 else:
959 stdin_ = mylib.Stdin()
960 # --tool never starts a prompt
961 if len(flag.tool) == 0 and stdin_.isatty():
962 src = source.Interactive
963 line_reader = reader.InteractiveLineReader(
964 arena, prompt_ev, hist_ev, readline, prompt_state)
965 mutable_opts.set_interactive()
966 else:
967 src = source.Stdin('')
968 line_reader = reader.FileLineReader(stdin_, arena)
969 else:
970 src = source.MainFile(script_name)
971 try:
972 f = fd_state.Open(script_name)
973 except (IOError, OSError) as e:
974 print_stderr("%s: Couldn't open %r: %s" %
975 (lang, script_name, posix.strerror(e.errno)))
976 return 1
977 line_reader = reader.FileLineReader(f, arena)
978
979 # Pretend it came from somewhere else
980 if flag.location_str is not None:
981 src = source.Synthetic(flag.location_str)
982 assert line_reader is not None
983 location_start_line = mops.BigTruncate(flag.location_start_line)
984 if location_start_line != -1:
985 line_reader.SetLineOffset(location_start_line)
986
987 arena.PushSource(src)
988
989 # Calculate ~/.config/oils/oshrc or yshrc. Used for both -i and --headless
990 # We avoid cluttering the user's home directory. Some users may want to ln
991 # -s ~/.config/oils/oshrc ~/oshrc or ~/.oshrc.
992
993 # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other
994
995 config_dir = '.config/oils'
996 rc_paths = [] # type: List[str]
997 if not flag.norc and (flag.headless or exec_opts.interactive()):
998 # User's rcfile comes FIRST. Later we can add an 'after-rcdir' hook
999 rc_path = flag.rcfile
1000 if rc_path is None:
1001 rc_paths.append(
1002 os_path.join(home_dir, '%s/%src' % (config_dir, lang)))
1003 else:
1004 rc_paths.append(rc_path)
1005
1006 # Load all files in ~/.config/oil/oshrc.d or oilrc.d
1007 # This way "installers" can avoid mutating oshrc directly
1008
1009 rc_dir = flag.rcdir
1010 if rc_dir is None:
1011 rc_dir = os_path.join(home_dir, '%s/%src.d' % (config_dir, lang))
1012
1013 rc_paths.extend(libc.glob(os_path.join(rc_dir, '*')))
1014 else:
1015 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
1016 print_stderr('%s warning: --rcfile ignored with --norc' % lang)
1017 if flag.rcdir is not None:
1018 print_stderr('%s warning: --rcdir ignored with --norc' % lang)
1019
1020 # Initialize even in non-interactive shell, for 'compexport'
1021 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
1022
1023 if flag.headless:
1024 state.InitInteractive(mem, lang)
1025 mutable_opts.set_redefine_const()
1026 mutable_opts.set_redefine_source()
1027
1028 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1029 for rc_path in rc_paths:
1030 with state.ctx_ThisDir(mem, rc_path):
1031 try:
1032 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1033 cmd_ev, errfmt)
1034 except util.UserExit as e:
1035 return e.status
1036
1037 loop = main_loop.Headless(cmd_ev, parse_ctx, errfmt)
1038 try:
1039 # TODO: What other exceptions happen here?
1040 status = loop.Loop()
1041 except util.UserExit as e:
1042 status = e.status
1043
1044 # Same logic as interactive shell
1045 mut_status = IntParamBox(status)
1046 cmd_ev.RunTrapsOnExit(mut_status)
1047 status = mut_status.i
1048
1049 return status
1050
1051 # Note: headless mode above doesn't use c_parser
1052 assert line_reader is not None
1053 c_parser = parse_ctx.MakeOshParser(line_reader)
1054
1055 if exec_opts.interactive():
1056 state.InitInteractive(mem, lang)
1057 # bash: 'set -o emacs' is the default only in the interactive shell
1058 mutable_opts.set_emacs()
1059 mutable_opts.set_redefine_const()
1060 mutable_opts.set_redefine_source()
1061
1062 if readline:
1063 term_width = 0
1064 if flag.completion_display == 'nice':
1065 try:
1066 term_width = libc.get_terminal_width()
1067 except (IOError, OSError): # stdin not a terminal
1068 pass
1069
1070 if term_width != 0:
1071 display = comp_ui.NiceDisplay(
1072 term_width, comp_ui_state, prompt_state, debug_f, readline,
1073 signal_safe) # type: comp_ui._IDisplay
1074 else:
1075 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1076 debug_f)
1077
1078 comp_ui.InitReadline(readline, sh_files.HistoryFile(), root_comp,
1079 display, debug_f)
1080
1081 _InitDefaultCompletions(cmd_ev, complete_builtin, comp_lookup)
1082 if flag.completion_demo:
1083 _CompletionDemo(comp_lookup)
1084
1085 else: # Without readline module
1086 display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1087 debug_f)
1088
1089 process.InitInteractiveShell(signal_safe) # Set signal handlers
1090
1091 # The interactive shell leads a process group which controls the terminal.
1092 # It MUST give up the terminal afterward, otherwise we get SIGTTIN /
1093 # SIGTTOU bugs.
1094 with process.ctx_TerminalControl(job_control, errfmt):
1095
1096 # NOTE: rc files loaded AFTER _InitDefaultCompletions.
1097 for rc_path in rc_paths:
1098 with state.ctx_ThisDir(mem, rc_path):
1099 try:
1100 SourceStartupFile(fd_state, rc_path, lang, parse_ctx,
1101 cmd_ev, errfmt)
1102 except util.UserExit as e:
1103 return e.status
1104
1105 assert line_reader is not None
1106 line_reader.Reset() # After sourcing startup file, render $PS1
1107
1108 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, cmd_ev, errfmt)
1109 try:
1110 status = main_loop.Interactive(flag, cmd_ev, c_parser, display,
1111 prompt_plugin, waiter, errfmt)
1112 except util.UserExit as e:
1113 status = e.status
1114
1115 mut_status = IntParamBox(status)
1116 cmd_ev.RunTrapsOnExit(mut_status)
1117 status = mut_status.i
1118
1119 if readline:
1120 hist_file = sh_files.HistoryFile()
1121 if hist_file is not None:
1122 try:
1123 readline.write_history_file(hist_file)
1124 except (IOError, OSError):
1125 pass
1126
1127 return status
1128
1129 if flag.rcfile is not None: # bash doesn't have this warning, but it's useful
1130 print_stderr('%s warning: --rcfile ignored in non-interactive shell' %
1131 lang)
1132 if flag.rcdir is not None:
1133 print_stderr('%s warning: --rcdir ignored in non-interactive shell' %
1134 lang)
1135
1136 #
1137 # Tools that use the OSH/YSH parsing mode, etc.
1138 #
1139
1140 # flag.tool is '' if nothing is passed
1141 # osh --tool syntax-tree is equivalent to osh -n --one-pass-parse
1142 tool_name = 'syntax-tree' if exec_opts.noexec() else flag.tool
1143
1144 if len(tool_name):
1145 # Don't save tokens becaues it's slow
1146 if tool_name != 'syntax-tree':
1147 arena.SaveTokens()
1148
1149 try:
1150 node = main_loop.ParseWholeFile(c_parser)
1151 except error.Parse as e:
1152 errfmt.PrettyPrintError(e)
1153 return 2
1154
1155 if tool_name == 'syntax-tree':
1156 ui.PrintAst(node, flag)
1157
1158 elif tool_name == 'tokens':
1159 ysh_ify.PrintTokens(arena)
1160
1161 elif tool_name == 'lossless-cat': # for test/lossless.sh
1162 ysh_ify.LosslessCat(arena)
1163
1164 elif tool_name == 'fmt':
1165 fmt.Format(arena, node)
1166
1167 elif tool_name == 'test':
1168 raise AssertionError('TODO')
1169
1170 elif tool_name == 'ysh-ify':
1171 ysh_ify.Ysh_ify(arena, node)
1172
1173 elif tool_name == 'deps':
1174 if mylib.PYTHON:
1175 deps.Deps(node)
1176
1177 else:
1178 raise AssertionError(tool_name) # flag parser validated it
1179
1180 return 0
1181
1182 #
1183 # Run a shell script
1184 #
1185
1186 with state.ctx_ThisDir(mem, script_name):
1187 try:
1188 status = main_loop.Batch(cmd_ev,
1189 c_parser,
1190 errfmt,
1191 cmd_flags=cmd_eval.IsMainProgram)
1192 except util.UserExit as e:
1193 status = e.status
1194 except KeyboardInterrupt:
1195 # The interactive shell handles this in main_loop.Interactive
1196 status = 130 # 128 + 2
1197 mut_status = IntParamBox(status)
1198 cmd_ev.RunTrapsOnExit(mut_status)
1199
1200 multi_trace.WriteDumps()
1201
1202 # NOTE: We haven't closed the file opened with fd_state.Open
1203 return mut_status.i