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