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