示例#1
0
def InitExecutor(parse_ctx=None, comp_lookup=None, arena=None, mem=None,
                 aliases=None, ext_prog=None):
  if parse_ctx:
    arena = parse_ctx.arena
  else:
    arena or MakeArena('<InitExecutor>')
    parse_ctx = parse_lib.ParseContext(arena, {}, None)

  mem = mem or state.Mem('', [], {}, arena)
  errfmt = ui.ErrorFormatter(arena)
  job_state = process.JobState()
  fd_state = process.FdState(errfmt, job_state)
  funcs = {}
  aliases = {} if aliases is None else aliases

  compopt_state = completion.OptionState()
  comp_lookup = comp_lookup or completion.Lookup()

  readline = None  # simulate not having it
  new_var = builtin_assign.NewVar(mem, funcs, errfmt)
  builtins = {  # Lookup
      builtin_e.ECHO: builtin.Echo,
      builtin_e.SHIFT: builtin_assign.Shift(mem),

      builtin_e.HISTORY: builtin.History(readline),

      builtin_e.COMPOPT: builtin_comp.CompOpt(compopt_state, errfmt),
      builtin_e.COMPADJUST: builtin_comp.CompAdjust(mem),

      builtin_e.ALIAS: builtin.Alias(aliases, errfmt),
      builtin_e.UNALIAS: builtin.UnAlias(aliases, errfmt),

      builtin_e.DECLARE: new_var,
      builtin_e.TYPESET: new_var,
      builtin_e.LOCAL: new_var,

      builtin_e.EXPORT: builtin_assign.Export(mem, errfmt),
      builtin_e.READONLY: builtin_assign.Readonly(mem, errfmt),
  }

  # For the tests, we do not use 'readline'.
  exec_opts = state.ExecOpts(mem, None)

  debug_f = util.DebugFile(sys.stderr)
  exec_deps = cmd_exec.Deps()
  exec_deps.search_path = state.SearchPath(mem)
  exec_deps.errfmt = errfmt
  exec_deps.job_state = job_state
  exec_deps.waiter = process.Waiter(exec_deps.job_state, exec_opts)

  exec_deps.ext_prog = \
      ext_prog or process.ExternalProgram('', fd_state,
                                          exec_deps.search_path, errfmt,
                                          debug_f)

  exec_deps.dumper = dev.CrashDumper('')
  exec_deps.debug_f = debug_f
  exec_deps.trace_f = debug_f

  splitter = split.SplitContext(mem)
  exec_deps.splitter = splitter

  word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, exec_deps, arena)
  exec_deps.word_ev = word_ev

  arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, word_ev, arena)
  exec_deps.arith_ev = arith_ev

  word_ev.arith_ev = arith_ev  # Circular

  bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, word_ev, arena)
  exec_deps.bool_ev = bool_ev

  tracer = dev.Tracer(parse_ctx, exec_opts, mem, word_ev, debug_f)
  exec_deps.tracer = tracer

  ex = cmd_exec.Executor(mem, fd_state, funcs, builtins, exec_opts,
                         parse_ctx, exec_deps)

  spec_builder = builtin_comp.SpecBuilder(ex, parse_ctx, word_ev, splitter,
                                          comp_lookup)
  # Add some builtins that depend on the executor!
  complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup)  # used later
  builtins[builtin_e.COMPLETE] = complete_builtin
  builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(spec_builder)

  return ex
示例#2
0
文件: oil.py 项目: mrshu/oil
def ShellMain(lang, argv0, argv, login_shell):
    """Used by bin/osh and bin/oil.

  Args:
    lang: 'osh' or 'oil'
    argv0, argv: So we can also invoke bin/osh as 'oil.ovm osh'.  Like busybox.
    login_shell: Was - on the front?
  """
    # Differences between osh and oil:
    # - --help?  I guess Oil has a SUPERSET of OSH options.
    # - oshrc vs oilrc
    # - the parser and executor
    # - Change the prompt in the interactive shell?

    assert lang in ('osh', 'oil'), lang

    arg_r = args.Reader(argv)
    try:
        opts = OSH_SPEC.Parse(arg_r)
    except args.UsageError as e:
        ui.Stderr('osh usage error: %s', e.msg)
        return 2

    # NOTE: This has a side effect of deleting _OVM_* from the environment!
    # TODO: Thread this throughout the program, and get rid of the global
    # variable in core/util.py.  Rename to InitResourceLaoder().  It's now only
    # used for the 'help' builtin and --version.
    loader = pyutil.GetResourceLoader()

    if opts.help:
        builtin.Help(['%s-usage' % lang], loader)
        return 0
    if opts.version:
        # OSH version is the only binary in Oil right now, so it's all one version.
        _ShowVersion()
        return 0

    if arg_r.AtEnd():
        dollar0 = argv0
        has_main = False
    else:
        dollar0 = arg_r.Peek()  # the script name, or the arg after -c
        has_main = True

    arena = alloc.Arena()
    errfmt = ui.ErrorFormatter(arena)

    # NOTE: has_main is only for ${BASH_SOURCE[@} and family.  Could be a
    # required arg.
    mem = state.Mem(dollar0,
                    argv[arg_r.i + 1:],
                    posix.environ,
                    arena,
                    has_main=has_main)
    funcs = {}

    fd_state = process.FdState(errfmt)
    exec_opts = state.ExecOpts(mem, line_input)

    if opts.show_options:  # special case: sh -o
        exec_opts.ShowOptions([])
        return 0

    builtin.SetExecOpts(exec_opts, opts.opt_changes)
    aliases = {}  # feedback between runtime and parser

    oil_grammar = meta.LoadOilGrammar(loader)

    if opts.one_pass_parse and not exec_opts.noexec:
        raise args.UsageError('--one-pass-parse requires noexec (-n)')
    parse_ctx = parse_lib.ParseContext(arena,
                                       aliases,
                                       oil_grammar,
                                       one_pass_parse=opts.one_pass_parse)

    # Three ParseContext instances SHARE aliases.
    comp_arena = alloc.Arena()
    comp_arena.PushSource(source.Unused('completion'))
    trail1 = parse_lib.Trail()
    # one_pass_parse needs to be turned on to complete inside backticks.  TODO:
    # fix the issue where ` gets erased because it's not part of
    # set_completer_delims().
    comp_ctx = parse_lib.ParseContext(comp_arena,
                                      aliases,
                                      oil_grammar,
                                      trail=trail1,
                                      one_pass_parse=True)

    hist_arena = alloc.Arena()
    hist_arena.PushSource(source.Unused('history'))
    trail2 = parse_lib.Trail()
    hist_ctx = parse_lib.ParseContext(hist_arena,
                                      aliases,
                                      oil_grammar,
                                      trail=trail2)

    # Deps helps manages dependencies.  These dependencies are circular:
    # - ex and word_ev, arith_ev -- for command sub, arith sub
    # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 ))
    # - ex and builtins (which execute code, like eval)
    # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P
    exec_deps = cmd_exec.Deps()

    # TODO: In general, exec_deps are shared between the mutually recursive
    # evaluators.  Some of the four below are only shared between a builtin and
    # the Executor, so we could put them somewhere else.
    exec_deps.traps = {}
    exec_deps.trap_nodes = []
    exec_deps.job_state = process.JobState()
    exec_deps.waiter = process.Waiter()
    exec_deps.errfmt = errfmt

    my_pid = posix.getpid()

    debug_path = ''
    debug_dir = posix.environ.get('OSH_DEBUG_DIR')
    if opts.debug_file:  # --debug-file takes precedence over OSH_DEBUG_DIR
        debug_path = opts.debug_file
    elif debug_dir:
        debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid)

    if debug_path:
        # This will be created as an empty file if it doesn't exist, or it could be
        # a pipe.
        try:
            debug_f = util.DebugFile(fd_state.Open(debug_path, mode='w'))
        except OSError as e:
            ui.Stderr("osh: Couldn't open %r: %s", debug_path,
                      posix.strerror(e.errno))
            return 2
    else:
        debug_f = util.NullDebugFile()

    exec_deps.debug_f = debug_f

    # Not using datetime for dependency reasons.  TODO: maybe show the date at
    # the beginning of the log, and then only show time afterward?  To save
    # space, and make space for microseconds.  (datetime supports microseconds
    # but time.strftime doesn't).
    iso_stamp = time.strftime("%Y-%m-%d %H:%M:%S")
    debug_f.log('%s [%d] OSH started with argv %s', iso_stamp, my_pid, argv)
    if debug_path:
        debug_f.log('Writing logs to %r', debug_path)

    interp = posix.environ.get('OSH_HIJACK_SHEBANG', '')
    exec_deps.ext_prog = process.ExternalProgram(interp, fd_state, errfmt,
                                                 debug_f)

    splitter = split.SplitContext(mem)
    exec_deps.splitter = splitter

    # This could just be OSH_DEBUG_STREAMS='debug crash' ?  That might be
    # stuffing too much into one, since a .json crash dump isn't a stream.
    crash_dump_dir = posix.environ.get('OSH_CRASH_DUMP_DIR', '')
    exec_deps.dumper = dev.CrashDumper(crash_dump_dir)

    if opts.xtrace_to_debug_file:
        trace_f = debug_f
    else:
        trace_f = util.DebugFile(sys.stderr)
    exec_deps.trace_f = trace_f

    comp_lookup = completion.Lookup()

    # Various Global State objects to work around readline interfaces
    compopt_state = completion.OptionState()
    comp_ui_state = comp_ui.State()
    prompt_state = comp_ui.PromptState()

    dir_stack = state.DirStack()

    declare_typeset = builtin.DeclareTypeset(mem, funcs)

    builtins = {  # Lookup
        builtin_e.ECHO: builtin.Echo,
        builtin_e.PRINTF: builtin_printf.Printf(mem, parse_ctx, errfmt),

        builtin_e.CD: builtin.Cd(mem, dir_stack, errfmt),
        builtin_e.PUSHD: builtin.Pushd(mem, dir_stack, errfmt),
        builtin_e.POPD: builtin.Popd(mem, dir_stack, errfmt),
        builtin_e.DIRS: builtin.Dirs(mem, dir_stack, errfmt),
        builtin_e.PWD: builtin.Pwd(errfmt),

        builtin_e.HISTORY: builtin.History(line_input),

        builtin_e.COMPOPT: builtin_comp.CompOpt(compopt_state, errfmt),
        builtin_e.COMPADJUST: builtin_comp.CompAdjust(mem),

        # need_right_bracket
        builtin_e.TEST: builtin_bracket.Test(False, errfmt),
        builtin_e.BRACKET: builtin_bracket.Test(True, errfmt),

        builtin_e.READ: builtin.Read(splitter, mem),

        builtin_e.SET: builtin.Set(exec_opts, mem),
        builtin_e.SHOPT: builtin.Shopt(exec_opts),
        builtin_e.UNSET: builtin.Unset(mem, funcs, errfmt),

        builtin_e.SHIFT: builtin.Shift(mem),
        builtin_e.EXPORT: builtin.Export(mem),
        builtin_e.DECLARE: declare_typeset,
        builtin_e.TYPESET: declare_typeset,

        builtin_e.ALIAS: builtin.Alias(aliases, errfmt),
        builtin_e.UNALIAS: builtin.UnAlias(aliases, errfmt),

        builtin_e.HELP: builtin.Help(loader, errfmt),

        builtin_e.TYPE: builtin.Type(funcs, aliases, mem),
        builtin_e.REPR: builtin.Repr(mem, errfmt),

        builtin_e.GETOPTS: builtin.GetOpts(mem, errfmt),

        builtin_e.WAIT: builtin.Wait(exec_deps.waiter, exec_deps.job_state, mem,
                                     errfmt),
        builtin_e.JOBS: builtin.Jobs(exec_deps.job_state),
        builtin_e.UMASK: builtin.Umask,

        builtin_e.COLON: lambda arg_vec: 0,  # a "special" builtin 
        builtin_e.TRUE: lambda arg_vec: 0,
        builtin_e.FALSE: lambda arg_vec: 1,
    }

    ex = cmd_exec.Executor(mem, fd_state, funcs, builtins, exec_opts,
                           parse_ctx, exec_deps)
    exec_deps.ex = ex

    word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, exec_deps, arena)
    exec_deps.word_ev = word_ev

    arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, word_ev, errfmt)
    exec_deps.arith_ev = arith_ev
    word_ev.arith_ev = arith_ev  # Another circular dependency

    bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, word_ev, errfmt)
    exec_deps.bool_ev = bool_ev

    tracer = dev.Tracer(parse_ctx, exec_opts, mem, word_ev, trace_f)
    exec_deps.tracer = tracer

    # HACK for circular deps
    ex.word_ev = word_ev
    ex.arith_ev = arith_ev
    ex.bool_ev = bool_ev
    ex.tracer = tracer

    spec_builder = builtin_comp.SpecBuilder(ex, parse_ctx, word_ev, splitter,
                                            comp_lookup)

    # Add some builtins that depend on the executor!
    complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup)
    builtins[builtin_e.COMPLETE] = complete_builtin
    builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(spec_builder)

    sig_state = process.SignalState()
    sig_state.InitShell()

    builtins[builtin_e.TRAP] = builtin.Trap(sig_state, exec_deps.traps,
                                            exec_deps.trap_nodes, ex, errfmt)

    if lang == 'oil':
        # The Oil executor wraps an OSH executor?  It needs to be able to source
        # it.
        ex = oil_cmd_exec.OilExecutor(ex)

    # PromptEvaluator rendering is needed in non-interactive shells for @P.
    prompt_ev = prompt.Evaluator(lang, parse_ctx, ex, mem)
    exec_deps.prompt_ev = prompt_ev
    word_ev.prompt_ev = prompt_ev  # HACK for circular deps

    # History evaluation is a no-op if line_input is None.
    hist_ev = history.Evaluator(line_input, hist_ctx, debug_f)

    if opts.c is not None:
        arena.PushSource(source.CFlag())
        line_reader = reader.StringLineReader(opts.c, arena)
        if opts.i:  # -c and -i can be combined
            exec_opts.interactive = True

    elif opts.i:  # force interactive
        arena.PushSource(source.Stdin(' -i'))
        line_reader = reader.InteractiveLineReader(arena, prompt_ev, hist_ev,
                                                   line_input, prompt_state,
                                                   sig_state)
        exec_opts.interactive = True

    else:
        try:
            script_name = arg_r.Peek()
        except IndexError:
            if sys.stdin.isatty():
                arena.PushSource(source.Interactive())
                line_reader = reader.InteractiveLineReader(
                    arena, prompt_ev, hist_ev, line_input, prompt_state,
                    sig_state)
                exec_opts.interactive = True
            else:
                arena.PushSource(source.Stdin(''))
                line_reader = reader.FileLineReader(sys.stdin, arena)
        else:
            arena.PushSource(source.MainFile(script_name))
            try:
                f = fd_state.Open(script_name)
            except OSError as e:
                ui.Stderr("osh: Couldn't open %r: %s", script_name,
                          posix.strerror(e.errno))
                return 1
            line_reader = reader.FileLineReader(f, arena)

    # TODO: assert arena.NumSourcePaths() == 1
    # TODO: .rc file needs its own arena.
    if lang == 'osh':
        c_parser = parse_ctx.MakeOshParser(line_reader)
    else:
        c_parser = parse_ctx.MakeOilParser(line_reader)

    if exec_opts.interactive:
        # Calculate ~/.config/oil/oshrc or oilrc
        # Use ~/.config/oil to avoid cluttering the user's home directory.  Some
        # users may want to ln -s ~/.config/oil/oshrc ~/oshrc or ~/.oshrc.

        # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other
        home_dir = process.GetHomeDir()
        assert home_dir is not None
        rc_path = opts.rcfile or os_path.join(home_dir, '.config/oil',
                                              lang + 'rc')

        history_filename = os_path.join(home_dir, '.config/oil',
                                        'history_' + lang)

        if line_input:
            # NOTE: We're using a different WordEvaluator here.
            ev = word_eval.CompletionWordEvaluator(mem, exec_opts, exec_deps,
                                                   arena)
            root_comp = completion.RootCompleter(ev, mem, comp_lookup,
                                                 compopt_state, comp_ui_state,
                                                 comp_ctx, debug_f)

            term_width = 0
            if opts.completion_display == 'nice':
                try:
                    term_width = libc.get_terminal_width()
                except IOError:  # stdin not a terminal
                    pass

            if term_width != 0:
                display = comp_ui.NiceDisplay(term_width, comp_ui_state,
                                              prompt_state, debug_f,
                                              line_input)
            else:
                display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
                                                 debug_f)

            _InitReadline(line_input, history_filename, root_comp, display,
                          debug_f)
            _InitDefaultCompletions(ex, complete_builtin, comp_lookup)

        else:  # Without readline module
            display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
                                             debug_f)

        sig_state.InitInteractiveShell(display)

        # NOTE: Call this AFTER _InitDefaultCompletions.
        SourceStartupFile(rc_path, lang, parse_ctx, ex)

        line_reader.Reset()  # After sourcing startup file, render $PS1

        return main_loop.Interactive(opts, ex, c_parser, display, errfmt)

    nodes_out = [] if exec_opts.noexec else None

    if nodes_out is None and opts.parser_mem_dump:
        raise args.UsageError('--parser-mem-dump can only be used with -n')

    _tlog('Execute(node)')
    status = main_loop.Batch(ex, c_parser, arena, nodes_out=nodes_out)
    if ex.MaybeRunExitTrap():
        status = ex.LastStatus()

    # Only print nodes if the whole parse succeeded.
    if nodes_out is not None and status == 0:
        if opts.parser_mem_dump:  # only valid in -n mode
            # This might be superstition, but we want to let the value stabilize
            # after parsing.  bash -c 'cat /proc/$$/status' gives different results
            # with a sleep.
            time.sleep(0.001)
            input_path = '/proc/%d/status' % posix.getpid()
            with open(input_path) as f, open(opts.parser_mem_dump, 'w') as f2:
                contents = f.read()
                f2.write(contents)
                log('Wrote %s to %s (--parser-mem-dump)', input_path,
                    opts.parser_mem_dump)

        ui.PrintAst(nodes_out, opts)

    # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub
    # don't because they call sys.exit().
    if opts.runtime_mem_dump:
        # This might be superstition, but we want to let the value stabilize
        # after parsing.  bash -c 'cat /proc/$$/status' gives different results
        # with a sleep.
        time.sleep(0.001)
        input_path = '/proc/%d/status' % posix.getpid()
        with open(input_path) as f, open(opts.runtime_mem_dump, 'w') as f2:
            contents = f.read()
            f2.write(contents)
            log('Wrote %s to %s (--runtime-mem-dump)', input_path,
                opts.runtime_mem_dump)

    # NOTE: We haven't closed the file opened with fd_state.Open
    return status
示例#3
0
  def _RunBuiltin(self, builtin_id, argv, fork_external, span_id):
    argv = argv[1:]  # Builtins don't need to know their own name.

    #
    # TODO: Convert everything to this new style
    #

    builtin_func = self.builtins.get(builtin_id)
    if builtin_func is not None:
      status = builtin_func(argv)

    elif builtin_id == builtin_e.EXEC:
      status = self._Exec(argv)  # may never return
      # But if it returns, then we want to permanently apply the redirects
      # associated with it.
      self.fd_state.MakePermanent()

    elif builtin_id == builtin_e.READ:
      status = builtin.Read(argv, self.splitter, self.mem)

    elif builtin_id == builtin_e.ECHO:
      status = builtin.Echo(argv)

    elif builtin_id == builtin_e.PRINTF:
      status = builtin.Printf(argv, self.mem)

    elif builtin_id == builtin_e.SHIFT:
      status = builtin.Shift(argv, self.mem)

    elif builtin_id == builtin_e.CD:
      status = builtin.Cd(argv, self.mem, self.dir_stack)

    elif builtin_id == builtin_e.SET:
      status = builtin.Set(argv, self.exec_opts, self.mem)

    elif builtin_id == builtin_e.SHOPT:
      status = builtin.Shopt(argv, self.exec_opts)

    elif builtin_id == builtin_e.UNSET:
      status = builtin.Unset(argv, self.mem, self.funcs)

    elif builtin_id == builtin_e.EXPORT:
      status = builtin.Export(argv, self.mem)

    elif builtin_id == builtin_e.WAIT:
      status = builtin.Wait(argv, self.waiter, self.job_state, self.mem)

    elif builtin_id == builtin_e.JOBS:
      status = builtin.Jobs(argv, self.job_state)

    elif builtin_id == builtin_e.PUSHD:
      status = builtin.Pushd(argv, self.mem, self.dir_stack)

    elif builtin_id == builtin_e.POPD:
      status = builtin.Popd(argv, self.mem, self.dir_stack)

    elif builtin_id == builtin_e.DIRS:
      status = builtin.Dirs(argv, self.mem.GetVar('HOME'), self.dir_stack)

    elif builtin_id == builtin_e.PWD:
      status = builtin.Pwd(argv, self.mem)

    elif builtin_id in (builtin_e.SOURCE, builtin_e.DOT):
      status = self._Source(argv)

    elif builtin_id == builtin_e.TRAP:
      status = builtin.Trap(argv, self.traps, self.nodes_to_run, self)

    elif builtin_id == builtin_e.UMASK:
      status = builtin.Umask(argv)

    elif builtin_id == builtin_e.EVAL:
      status = self._Eval(argv, span_id)

    elif builtin_id == builtin_e.COLON:  # special builtin like 'true'
      status = 0

    elif builtin_id == builtin_e.TRUE:
      status = 0

    elif builtin_id == builtin_e.FALSE:
      status = 1

    elif builtin_id == builtin_e.TEST:
      status = builtin_bracket.Test(argv, False)

    elif builtin_id == builtin_e.BRACKET:
      status = builtin_bracket.Test(argv, True)  # need_right_bracket

    elif builtin_id == builtin_e.GETOPTS:
      status = builtin.GetOpts(argv, self.mem)

    elif builtin_id == builtin_e.COMMAND:
      # TODO: Pull Command up to the top level?
      b = builtin.Command(self, self.funcs, self.mem)
      status = b(argv, fork_external, span_id)

    elif builtin_id == builtin_e.TYPE:
      path = self.mem.GetVar('PATH')
      status = builtin.Type(argv, self.funcs, path)

    elif builtin_id == builtin_e.HELP:
      loader = pyutil.GetResourceLoader()
      status = builtin.Help(argv, loader)

    elif builtin_id in (builtin_e.DECLARE, builtin_e.TYPESET):
      # These are synonyms
      status = builtin.DeclareTypeset(argv, self.mem, self.funcs)

    elif builtin_id == builtin_e.ALIAS:
      status = builtin.Alias(argv, self.aliases)

    elif builtin_id == builtin_e.UNALIAS:
      status = builtin.UnAlias(argv, self.aliases)

    elif builtin_id == builtin_e.REPR:
      status = builtin.Repr(argv, self.mem)

    elif builtin_id == builtin_e.BUILTIN:  # NOTE: uses early return style
      if not argv:
        return 0  # this could be an error in strict mode?

      # Run regular builtin or special builtin
      to_run = builtin.Resolve(argv[0])
      if to_run == builtin_e.NONE:
        to_run = builtin.ResolveSpecial(argv[0])
      if to_run == builtin_e.NONE:
        util.error("builtin: %s: not a shell builtin", argv[0])
        return 1

      return self._RunBuiltin(to_run, argv, fork_external, span_id)

    else:
      raise AssertionError('Unhandled builtin: %s' % builtin_id)

    assert isinstance(status, int)
    return status