Exemple #1
0
  def setUp(self):
    """Done on every test."""
    self.arena = alloc.Arena()
    self.arena.PushSource(source.Unused(''))
    parse_opts = parse_lib.OilParseOptions()

    loader = pyutil.GetResourceLoader()
    oil_grammar = meta.LoadOilGrammar(loader)

    self.parse_ctx = parse_lib.ParseContext(self.arena, parse_opts, {},
                                            oil_grammar, one_pass_parse=True)
Exemple #2
0
def InitWordParser(word_str, oil_at=False, arena=None):
  arena = arena or MakeArena('<test_lib>')
  parse_opts = parse_lib.OilParseOptions()
  parse_opts.at = oil_at
  loader = pyutil.GetResourceLoader()
  oil_grammar = meta.LoadOilGrammar(loader)
  parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, oil_grammar)
  line_reader, _ = InitLexer(word_str, arena)
  c_parser = parse_ctx.MakeOshParser(line_reader)
  # Hack
  return c_parser.w_parser
Exemple #3
0
    def setUp(self):
        """Done on every test."""
        self.arena = alloc.Arena()
        self.arena.PushSource(source.Unused(''))

        loader = pyutil.GetResourceLoader()
        oil_grammar = pyutil.LoadOilGrammar(loader)

        self.parse_ctx = test_lib.InitParseContext(arena=self.arena,
                                                   oil_grammar=oil_grammar)
        self.parse_ctx.Init_OnePassParse(True)
Exemple #4
0
def main(argv):
    # Localization: Optionally  use GNU gettext()?  For help only.  Might be
    # useful in parser error messages too.  Good thing both kinds of code are
    # generated?  Because I don't want to deal with a C toolchain for it.

    loader = pyutil.GetResourceLoader()
    Help([], loader)

    for name, spec in BUILTIN_DEF.arg_specs.iteritems():
        print(name)
        spec.PrintHelp(sys.stdout)
        print()
Exemple #5
0
def InitWordParser(word_str, oil_at=False, arena=None):
    arena = arena or MakeArena('<test_lib>')
    opt_array = [False] * option_i.ARRAY_SIZE
    parse_opts = optview.Parse(opt_array)
    opt_array[option_i.parse_at] = oil_at
    loader = pyutil.GetResourceLoader()
    oil_grammar = meta.LoadOilGrammar(loader)
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, oil_grammar)
    line_reader, _ = InitLexer(word_str, arena)
    c_parser = parse_ctx.MakeOshParser(line_reader)
    # Hack
    return c_parser.w_parser
Exemple #6
0
def InitWordParser(word_str, oil_at=False, arena=None):
  arena = arena or MakeArena('<test_lib>')

  mem = state.Mem('', [], arena, [])
  parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, None)

  # CUSTOM SETTING
  mutable_opts.opt0_array[option_i.parse_at] = oil_at

  loader = pyutil.GetResourceLoader()
  oil_grammar = pyutil.LoadOilGrammar(loader)
  parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, oil_grammar)
  line_reader, _ = InitLexer(word_str, arena)
  c_parser = parse_ctx.MakeOshParser(line_reader)
  # Hack
  return c_parser.w_parser
Exemple #7
0
def main(argv):
    # type: (List[str]) -> int
    loader = pyutil.GetResourceLoader()
    login_shell = False

    environ = {}  # type: Dict[str, str]
    environ['PWD'] = posix.getcwd()

    arg_r = args.Reader(argv, spids=[runtime.NO_SPID] * len(argv))

    try:
        status = pure.Main('osh', arg_r, environ, login_shell, loader, None)
        return status
    except error.Usage as e:
        #builtin.Help(['oil-usage'], util.GetResourceLoader())
        log('oil: %s', e.msg)
        return 2
    except RuntimeError as e:
        if 0:
            import traceback
            traceback.print_exc()
        # NOTE: The Python interpreter can cause this, e.g. on stack overflow.
        # f() { f; }; f will cause this
        msg = e.message  # type: str
        stderr_line('osh fatal error: %s', msg)
        return 1

    # Note: This doesn't happen in C++.
    except KeyboardInterrupt:
        print('')
        return 130  # 128 + 2

    except OSError as e:
        if 0:
            import traceback
            traceback.print_exc()

        # test this with prlimit --nproc=1 --pid=$$
        stderr_line('osh I/O error: %s', pyutil.strerror_OS(e))
        return 2  # dash gives status 2

    except IOError as e:  # duplicate of above because CPython is inconsistent
        stderr_line('osh I/O error: %s', pyutil.strerror_IO(e))
        return 2
Exemple #8
0
def TeaMain(argv):
    # type: (str, List[str]) -> int
    arena = alloc.Arena()
    try:
        script_name = argv[1]
        arena.PushSource(source.MainFile(script_name))
    except IndexError:
        arena.PushSource(source.Stdin())
        f = sys.stdin
    else:
        try:
            f = open(script_name)
        except IOError as e:
            stderr_line("tea: Couldn't open %r: %s", script_name,
                        posix.strerror(e.errno))
            return 2

    aliases = {}  # Dummy value; not respecting aliases!

    loader = pyutil.GetResourceLoader()
    oil_grammar = pyutil.LoadOilGrammar(loader)

    # Not used in tea, but OK...
    opt0_array = state.InitOpts()
    parse_opts = optview.Parse(opt0_array)

    # parse `` and a[x+1]=bar differently
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)

    line_reader = reader.FileLineReader(f, arena)

    try:
        parse_ctx.ParseTeaModule(line_reader)
        status = 0
    except error.Parse as e:
        ui.PrettyPrintError(e, arena)
        status = 2

    return status
Exemple #9
0
def main(argv):
    # type: (List[str]) -> int
    arena = alloc.Arena()

    dollar0 = argv[0]
    debug_stack = []  # type: List[state.DebugFrame]
    mem = state.Mem(dollar0, argv, arena, debug_stack)
    parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, None)
    # Dummy value; not respecting aliases!
    aliases = {}  # type: Dict[str, str]
    # parse `` and a[x+1]=bar differently

    oil_grammar = None  # type: Grammar
    if mylib.PYTHON:
        loader = pyutil.GetResourceLoader()
        oil_grammar = meta.LoadOilGrammar(loader)

    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)

    pretty_print = True

    if len(argv) == 1:
        line_reader = reader.FileLineReader(mylib.Stdin(), arena)
        src = source.Stdin('')  # type: source_t

    elif len(argv) == 2:
        path = argv[1]
        f = mylib.open(path)
        line_reader = reader.FileLineReader(f, arena)
        src = source.MainFile(path)

    elif len(argv) == 3:
        if argv[1] == '-c':
            # This path is easier to run through GDB
            line_reader = reader.StringLineReader(argv[2], arena)
            src = source.CFlag()

        elif argv[1] == '-n':  # For benchmarking, allow osh_parse -n file.txt
            path = argv[2]
            f = mylib.open(path)
            line_reader = reader.FileLineReader(f, arena)
            src = source.MainFile(path)
            # This is like --ast-format none, which benchmarks/osh-helper.sh passes.
            pretty_print = False

        else:
            raise AssertionError()

    else:
        raise AssertionError()

    arena.PushSource(src)

    c_parser = parse_ctx.MakeOshParser(line_reader)

    try:
        #node = main_loop.ParseWholeFile(c_parser)
        node = ParseWholeFile(c_parser)
    except error.Parse as e:
        ui.PrettyPrintError(e, arena)
        return 2
    assert node is not None

    # C++ doesn't have the abbreviations yet (though there are some differences
    # like omitting spids)
    #tree = node.AbbreviatedTree()
    if 0:
        tree = node.PrettyTree()

        ast_f = fmt.DetectConsoleOutput(mylib.Stdout())
        fmt.PrintTree(tree, ast_f)
        ast_f.write('\n')

    # New osh_eval.py instantiations

    errfmt = ui.ErrorFormatter(arena)

    splitter = split.SplitContext(mem)
    arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, errfmt)
    word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt)

    arith_ev.word_ev = word_ev
    word_ev.arith_ev = arith_ev

    test_ev = TestEvaluator(arith_ev, word_ev)
    test_ev.Eval(node)

    return 0
Exemple #10
0
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.usage('osh usage error: %s', e)
        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()

    # 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()
    exec_opts = state.ExecOpts(mem, line_input)
    builtin.SetExecOpts(exec_opts, opts.opt_changes)
    aliases = {}  # feedback between runtime and parser

    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,
                                       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,
                                      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, 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()

    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:
            util.error("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, 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()

    builtins = {  # Lookup
        builtin_e.HISTORY: builtin.History(line_input),

        builtin_e.COMPOPT: builtin_comp.CompOpt(compopt_state),
        builtin_e.COMPADJUST: builtin_comp.CompAdjust(mem),
    }
    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, arena)
    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, arena)
    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)

    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)

    # 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 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'))
        # interactive shell only
        line_reader = reader.InteractiveLineReader(arena, prompt_ev, hist_ev,
                                                   line_input, prompt_state)
        exec_opts.interactive = True

    else:
        try:
            script_name = arg_r.Peek()
        except IndexError:
            if sys.stdin.isatty():
                arena.PushSource(source.Interactive())
                # interactive shell only
                line_reader = reader.InteractiveLineReader(
                    arena, prompt_ev, hist_ev, line_input, prompt_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:
                util.error("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)

    # NOTE: SIGINT is temporarily enabled during readline() by
    # frontend/reader.py.
    # It's treated differently than SIGQUIT and SIGTSTP because Python handles it
    # with KeyboardInterrupt.  We don't want KeyboardInterrupt at arbitrary
    # points in a non-interactive shell.  (e.g. osh -c 'sleep 5' then Ctrl-C)
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    if exec_opts.interactive:
        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)

        # The shell itself should ignore Ctrl-\.
        signal.signal(signal.SIGQUIT, signal.SIG_IGN)

        # This prevents Ctrl-Z from suspending OSH in interactive mode.  But we're
        # not getting notification via wait() that the child stopped?
        signal.signal(signal.SIGTSTP, signal.SIG_IGN)

        # Register a callback to receive terminal width changes.
        signal.signal(signal.SIGWINCH, lambda x, y: display.OnWindowChange())

        # 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, arena)

    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)

    # 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
Exemple #11
0
def main(argv):
  # type: (List[str]) -> int
  arena = alloc.Arena()

  dollar0 = argv[0]
  debug_stack = []  # type: List[state.DebugFrame]

  argv = argv[1:]  # remove binary name
  i, flag_a, flag_c, flag_n = Parse(argv)
  argv = argv[i:]  # truncate

  mem = state.Mem(dollar0, argv, arena, debug_stack)

  # TODO: look at extern char** environ;

  environ = {}  # type: Dict[str, str]
  environ['PWD'] = posix.getcwd()
  state.InitMem(mem, environ, 'VERSION')

  opt_hook = state.OptHook()
  parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook)
  # Dummy value; not respecting aliases!
  aliases = {}  # type: Dict[str, str]
  # parse `` and a[x+1]=bar differently

  oil_grammar = None  # type: Grammar
  if mylib.PYTHON:
    loader = pyutil.GetResourceLoader()
    oil_grammar = meta.LoadOilGrammar(loader)

  parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)

  if flag_c:
    # This path is easier to run through GDB
    line_reader = reader.StringLineReader(flag_c, arena)
    src = source.CFlag()  # type: source_t

  elif len(argv) == 0:
    line_reader = reader.FileLineReader(mylib.Stdin(), arena)
    src = source.Stdin('')

  elif len(argv) == 1:
    path = argv[0]
    f = mylib.open(path)
    line_reader = reader.FileLineReader(f, arena)
    src = source.MainFile(path)

  else:
    raise AssertionError(argv)

  arena.PushSource(src)
  c_parser = parse_ctx.MakeOshParser(line_reader)

  # C++ doesn't have the abbreviations yet (though there are some differences
  # like omitting spids)
  #tree = node.AbbreviatedTree()
  if flag_n:
    try:
      node = main_loop.ParseWholeFile(c_parser)
    except error.Parse as e:
      ui.PrettyPrintError(e, arena)
      return 2
    assert node is not None

    if flag_a:
      tree = node.PrettyTree()

      ast_f = fmt.DetectConsoleOutput(mylib.Stdout())
      fmt.PrintTree(tree, ast_f)
      ast_f.write('\n')
    return 0

  # New osh_eval.py instantiations

  errfmt = ui.ErrorFormatter(arena)

  splitter = split.SplitContext(mem)
  arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt)
  bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt)
  word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt)
  prompt_ev = prompt.Evaluator('osh', parse_ctx, mem)

  arith_ev.word_ev = word_ev
  word_ev.arith_ev = arith_ev
  word_ev.prompt_ev = prompt_ev

  prompt_ev.word_ev = word_ev

  procs = {}  # type: Dict[str, command__ShFunction]

  assign_builtins = {}  # type: Dict[int, _AssignBuiltin]

  new_var = builtin_assign.NewVar(mem, procs, errfmt)
  assign_builtins[builtin_i.declare] = new_var
  assign_builtins[builtin_i.typeset] = new_var
  assign_builtins[builtin_i.local] = new_var
  assign_builtins[builtin_i.export_] = builtin_assign.Export(mem, errfmt)
  assign_builtins[builtin_i.readonly] = builtin_assign.Readonly(mem, errfmt)

  #assign_builtins = {
  #    # ShAssignment (which are pure)
  #    builtin_i.declare: new_var,
  #    builtin_i.typeset: new_var,
  #    builtin_i.local: new_var,

  #    builtin_i.export_: builtin_assign.Export(mem, errfmt),
  #    builtin_i.readonly: builtin_assign.Readonly(mem, errfmt),
  #}

  cmd_deps = cmd_eval.Deps()
  cmd_deps.mutable_opts = mutable_opts
  cmd_deps.traps = {}
  cmd_deps.trap_nodes = []  # TODO: Clear on fork() to avoid duplicates

  cmd_deps.dumper = dev.CrashDumper('')

  search_path = state.SearchPath(mem)

  builtins = {}  # type: Dict[int, vm._Builtin]
  builtins[builtin_i.echo] = builtin_pure.Echo(exec_opts)

  builtins[builtin_i.set] = Set(mutable_opts)  # DUMMY until ParseMore()
  if mylib.PYTHON:
    # Use the real one
    builtins[builtin_i.set] = builtin_pure.Set(mutable_opts, mem)

  builtins[builtin_i.shopt] = builtin_pure.Shopt(mutable_opts)
  builtins[builtin_i.alias] = builtin_pure.Alias(aliases, errfmt)
  builtins[builtin_i.unalias] = builtin_pure.UnAlias(aliases, errfmt)

  builtins[builtin_i.hash] = builtin_pure.Hash(search_path)
  builtins[builtin_i.getopts] = builtin_pure.GetOpts(mem, errfmt)

  builtins[builtin_i.shift] = builtin_assign.Shift(mem)
  builtins[builtin_i.unset] = builtin_assign.Unset(
      mem, exec_opts, procs, parse_ctx, arith_ev, errfmt)

  true_ = builtin_pure.Boolean(0)
  builtins[builtin_i.colon] = true_  # a "special" builtin 
  builtins[builtin_i.true_] = true_
  builtins[builtin_i.false_] = builtin_pure.Boolean(1)

  # builtin_meta
  builtins[builtin_i.type] = builtin_meta.Type(procs, aliases, search_path, errfmt)

  shell_ex = NullExecutor(exec_opts, mutable_opts, procs, builtins)

  trace_f = util.DebugFile(mylib.Stderr())
  tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev, trace_f)

  cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs,
                                     assign_builtins, arena, cmd_deps)

  # TODO: can't instantiate this yet
  #fd_state = None

  # needs cmd_ev
  builtins[builtin_i.eval] = builtin_meta.Eval(parse_ctx, exec_opts, cmd_ev)
  #source_builtin = builtin_meta.Source(
  #    parse_ctx, search_path, cmd_ev, fd_state, errfmt)
  #builtins[builtin_i.source] = source_builtin
  #builtins[builtin_i.dot] = source_builtin

  builtins[builtin_i.builtin] = builtin_meta.Builtin(shell_ex, errfmt)
  builtins[builtin_i.command] = builtin_meta.Command(shell_ex, procs, aliases,
                                                     search_path)

  builtins[builtin_i.printf] = builtin_printf.Printf(mem, parse_ctx, errfmt)

  builtins[builtin_i.test] = builtin_bracket.Test(False, exec_opts, mem, errfmt)
  builtins[builtin_i.bracket] = builtin_bracket.Test(True, exec_opts, mem, errfmt)

  dir_stack = state.DirStack()
  builtins[builtin_i.pushd] = builtin_misc.Pushd(mem, dir_stack, errfmt)
  builtins[builtin_i.popd] = builtin_misc.Popd(mem, dir_stack, errfmt)
  builtins[builtin_i.dirs] = builtin_misc.Dirs(mem, dir_stack, errfmt)
  builtins[builtin_i.pwd] = builtin_misc.Pwd(mem, errfmt)

  builtins[builtin_i.times] = builtin_misc.Times()
  builtins[builtin_i.read] = builtin_misc.Read(splitter, mem)

  builtins[builtin_i.cat] = builtin_misc.Cat()  # for $(<file)
  builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, cmd_ev, errfmt)

  # vm.InitCircularDeps
  cmd_ev.arith_ev = arith_ev
  cmd_ev.bool_ev = bool_ev
  cmd_ev.word_ev = word_ev
  cmd_ev.tracer = tracer
  cmd_ev.shell_ex = shell_ex

  shell_ex.cmd_ev = cmd_ev

  bool_ev.word_ev = word_ev

  try:
    status = main_loop.Batch(cmd_ev, c_parser, arena,
                             cmd_flags=cmd_eval.IsMainProgram)
  except util.UserExit as e:
    # TODO: fix this
    #status = e.status
    status = 1
  return status
Exemple #12
0
def AppBundleMain(argv):
    # type: (List[str]) -> int

    # NOTE: This has a side effect of deleting _OVM_* from the environment!
    loader = pyutil.GetResourceLoader()

    b = os_path.basename(argv[0])
    main_name, ext = os_path.splitext(b)

    arg_r = args.Reader(argv)
    if main_name == 'oil' and ext:  # oil.py or oil.ovm
        arg_r.Next()
        first_arg = arg_r.Peek()
        if first_arg is None:
            raise error.Usage('Missing required applet name.')

        if first_arg in ('-h', '--help'):
            errfmt = None  # not needed here
            help_builtin = builtin_misc.Help(loader, errfmt)
            help_builtin.Run(pure.MakeBuiltinArgv(['bundle-usage']))
            sys.exit(0)

        if first_arg in ('-V', '--version'):
            pyutil.ShowAppVersion('Oil', loader)
            sys.exit(0)

        main_name = first_arg

    login_shell = False
    if main_name.startswith('-'):
        login_shell = True
        main_name = main_name[1:]

    if main_name in ('osh', 'sh'):
        # TODO:
        # - Initialize a different shell if line_input isn't present
        status = shell.Main('osh', arg_r, posix.environ, login_shell, loader,
                            line_input)

        _tlog('done osh main')
        return status

    elif main_name == 'osh-pure':
        # TODO: pure.Main()
        pass

    elif main_name == 'oshc':
        arg_r.Next()
        main_argv = arg_r.Rest()
        try:
            return OshCommandMain(main_argv)
        except error.Usage as e:
            stderr_line('oshc usage error: %s', e.msg)
            return 2

    elif main_name == 'oil':
        return shell.Main('oil', arg_r, posix.environ, login_shell, loader,
                          line_input)

    elif main_name == 'tea':
        main_argv = arg_r.Rest()
        return TeaMain(main_argv)

    # For testing latency
    elif main_name == 'true':
        return 0
    elif main_name == 'false':
        return 1
    elif main_name == 'readlink':
        main_argv = arg_r.Rest()
        return readlink.main(main_argv)
    else:
        raise error.Usage('Invalid applet name %r.' % main_name)
Exemple #13
0
def OshCommandMain(argv):
    """Run an 'oshc' tool.

  'osh' is short for "osh compiler" or "osh command".

  TODO:
  - oshc --help

  oshc deps
    --path: the $PATH to use to find executables.  What about libraries?

    NOTE: we're leaving out su -c, find, xargs, etc.?  Those should generally
    run functions using the $0 pattern.
    --chained-command sudo
  """
    try:
        action = argv[0]
    except IndexError:
        raise error.Usage('Missing required subcommand.')

    if action not in SUBCOMMANDS:
        raise error.Usage('Invalid subcommand %r.' % action)

    if action == 'parse-glob':
        # Pretty-print the AST produced by osh/glob_.py
        print('TODO:parse-glob')
        return 0

    if action == 'parse-printf':
        # Pretty-print the AST produced by osh/builtin_printf.py
        print('TODO:parse-printf')
        return 0

    arena = alloc.Arena()
    try:
        script_name = argv[1]
        arena.PushSource(source.MainFile(script_name))
    except IndexError:
        arena.PushSource(source.Stdin())
        f = sys.stdin
    else:
        try:
            f = open(script_name)
        except IOError as e:
            stderr_line("oshc: Couldn't open %r: %s", script_name,
                        posix.strerror(e.errno))
            return 2

    aliases = {}  # Dummy value; not respecting aliases!

    loader = pyutil.GetResourceLoader()
    oil_grammar = pyutil.LoadOilGrammar(loader)

    opt0_array = state.InitOpts()
    no_stack = None  # type: List[bool]  # for mycpp
    opt_stacks = [no_stack] * option_i.ARRAY_SIZE  # type: List[List[bool]]

    parse_opts = optview.Parse(opt0_array, opt_stacks)
    # parse `` and a[x+1]=bar differently
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)
    parse_ctx.Init_OnePassParse(True)

    line_reader = reader.FileLineReader(f, arena)
    c_parser = parse_ctx.MakeOshParser(line_reader)

    try:
        node = main_loop.ParseWholeFile(c_parser)
    except error.Parse as e:
        ui.PrettyPrintError(e, arena)
        return 2
    assert node is not None

    f.close()

    # Columns for list-*
    # path line name
    # where name is the binary path, variable name, or library path.

    # bin-deps and lib-deps can be used to make an app bundle.
    # Maybe I should list them together?  'deps' can show 4 columns?
    #
    # path, line, type, name
    #
    # --pretty can show the LST location.

    # stderr: show how we're following imports?

    if action == 'translate':
        osh2oil.PrintAsOil(arena, node)

    elif action == 'arena':  # for debugging
        osh2oil.PrintArena(arena)

    elif action == 'spans':  # for debugging
        osh2oil.PrintSpans(arena)

    elif action == 'format':
        # TODO: autoformat code
        raise NotImplementedError(action)

    elif action == 'deps':
        deps.Deps(node)

    elif action == 'undefined-vars':  # could be environment variables
        raise NotImplementedError()

    else:
        raise AssertionError  # Checked above

    return 0
Exemple #14
0
def main(argv):
  # type: (List[str]) -> int
  arena = alloc.Arena()

  dollar0 = argv[0]
  debug_stack = []  # type: List[state.DebugFrame]
  mem = state.Mem(dollar0, argv, arena, debug_stack)
  opt_hook = state.OptHook()
  parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook)
  # Dummy value; not respecting aliases!
  aliases = {}  # type: Dict[str, str]
  # parse `` and a[x+1]=bar differently

  state.SetGlobalString(mem, 'SHELLOPTS', '')

  oil_grammar = None  # type: Grammar
  if mylib.PYTHON:
    loader = pyutil.GetResourceLoader()
    oil_grammar = meta.LoadOilGrammar(loader)

  parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)

  argv = argv[1:]  # remove binary name
  i, flag_a, flag_c, flag_n = Parse(argv)

  argv = argv[i:]  # truncate

  if flag_c:
    # This path is easier to run through GDB
    line_reader = reader.StringLineReader(flag_c, arena)
    src = source.CFlag()  # type: source_t

  elif len(argv) == 0:
    line_reader = reader.FileLineReader(mylib.Stdin(), arena)
    src = source.Stdin('')

  elif len(argv) == 1:
    path = argv[0]
    f = mylib.open(path)
    line_reader = reader.FileLineReader(f, arena)
    src = source.MainFile(path)

  else:
    raise AssertionError(argv)

  arena.PushSource(src)
  c_parser = parse_ctx.MakeOshParser(line_reader)

  # C++ doesn't have the abbreviations yet (though there are some differences
  # like omitting spids)
  #tree = node.AbbreviatedTree()
  if flag_n:
    try:
      node = main_loop.ParseWholeFile(c_parser)
    except error.Parse as e:
      ui.PrettyPrintError(e, arena)
      return 2
    assert node is not None

    if flag_a:
      tree = node.PrettyTree()

      ast_f = fmt.DetectConsoleOutput(mylib.Stdout())
      fmt.PrintTree(tree, ast_f)
      ast_f.write('\n')
    return 0

  # New osh_eval.py instantiations

  errfmt = ui.ErrorFormatter(arena)

  splitter = split.SplitContext(mem)
  arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt)
  bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt)
  word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt)

  arith_ev.word_ev = word_ev
  word_ev.arith_ev = arith_ev

  procs = {}  # type: Dict[str, command__ShFunction]

  assign_builtins = {}  # type: Dict[int, _AssignBuiltin]

  new_var = builtin_assign.NewVar(mem, procs, errfmt)
  assign_builtins[builtin_i.declare] = new_var
  assign_builtins[builtin_i.typeset] = new_var
  assign_builtins[builtin_i.local] = new_var

  #assign_builtins = {
  #    # ShAssignment (which are pure)
  #    builtin_i.declare: new_var,
  #    builtin_i.typeset: new_var,
  #    builtin_i.local: new_var,

  #    builtin_i.export_: builtin_assign.Export(mem, errfmt),
  #    builtin_i.readonly: builtin_assign.Readonly(mem, errfmt),
  #}

  cmd_deps = cmd_eval.Deps()
  cmd_deps.mutable_opts = mutable_opts
  cmd_deps.traps = {}
  cmd_deps.trap_nodes = []  # TODO: Clear on fork() to avoid duplicates

  cmd_deps.dumper = dev.CrashDumper('')

  builtins = {}  # type: Dict[int, _Builtin]
  builtins[builtin_i.echo] = Echo()
  builtins[builtin_i.shopt] = Shopt(mutable_opts)
  builtins[builtin_i.set] = Set(mutable_opts)
  ex = NullExecutor(builtins)

  trace_f = util.DebugFile(mylib.Stderr())
  tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev, trace_f)

  cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs,
                                     assign_builtins, arena, cmd_deps)

  # vm.InitCircularDeps
  cmd_ev.arith_ev = arith_ev
  cmd_ev.bool_ev = bool_ev
  cmd_ev.word_ev = word_ev
  cmd_ev.tracer = tracer
  cmd_ev.shell_ex = ex

  bool_ev.word_ev = word_ev

  status = main_loop.Batch(cmd_ev, c_parser, arena, is_main=True)
  return status
Exemple #15
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
Exemple #16
0
def main(argv):
    # type: (List[str]) -> int
    arena = alloc.Arena()

    opt_array = [False] * option_i.ARRAY_SIZE
    parse_opts = optview.Parse(opt_array)
    # Dummy value; not respecting aliases!
    aliases = {}  # type: Dict[str, str]
    # parse `` and a[x+1]=bar differently

    oil_grammar = None  # type: Grammar
    if mylib.PYTHON:
        loader = pyutil.GetResourceLoader()
        oil_grammar = pyutil.LoadOilGrammar(loader)

    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)

    pretty_print = True

    if len(argv) == 1:
        line_reader = reader.FileLineReader(mylib.Stdin(), arena)
        src = source.Stdin('')  # type: source_t

    elif len(argv) == 2:
        path = argv[1]
        f = mylib.open(path)
        line_reader = reader.FileLineReader(f, arena)
        src = source.MainFile(path)

    elif len(argv) == 3:
        if argv[1] == '-c':
            # This path is easier to run through GDB
            line_reader = reader.StringLineReader(argv[2], arena)
            src = source.CFlag()

        elif argv[1] == '-n':  # For benchmarking, allow osh_parse -n file.txt
            path = argv[2]
            f = mylib.open(path)
            line_reader = reader.FileLineReader(f, arena)
            src = source.MainFile(path)
            # This is like --ast-format none, which benchmarks/osh-helper.sh passes.
            pretty_print = False

        else:
            raise AssertionError()

    else:
        raise AssertionError()

    arena.PushSource(src)

    c_parser = parse_ctx.MakeOshParser(line_reader)

    try:
        #node = main_loop.ParseWholeFile(c_parser)
        node = ParseWholeFile(c_parser)
    except error.Parse as e:
        ui.PrettyPrintError(e, arena)
        return 2
    assert node is not None

    # C++ doesn't have the abbreviations yet (though there are some differences
    # like omitting spids)
    #tree = node.AbbreviatedTree()
    if pretty_print:
        tree = node.PrettyTree()

        ast_f = fmt.DetectConsoleOutput(mylib.Stdout())
        fmt.PrintTree(tree, ast_f)
        ast_f.write('\n')

    return 0
Exemple #17
0
def ShellMain(lang, argv0, argv, login_shell):
    # type: (str, str, List[str], bool) -> int
    """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_misc.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

    no_str = None  # type: str

    debug_stack = []
    if arg_r.AtEnd():
        dollar0 = argv0
    else:
        dollar0 = arg_r.Peek()  # the script name, or the arg after -c

        # Copy quirky bash behavior.
        frame0 = state.DebugFrame(dollar0, 'main', no_str, state.LINE_ZERO, 0,
                                  0)
        debug_stack.append(frame0)

    # Copy quirky bash behavior.
    frame1 = state.DebugFrame(no_str, no_str, no_str, runtime.NO_SPID, 0, 0)
    debug_stack.append(frame1)

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

    mem = state.Mem(dollar0, argv[arg_r.i + 1:], arena, debug_stack)
    state.InitMem(mem, posix.environ)
    builtin_funcs.Init(mem)

    procs = {}

    job_state = process.JobState()
    fd_state = process.FdState(errfmt, job_state)

    opt_hook = ShellOptHook(line_input)
    parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook)
    # TODO: only MutableOpts needs mem, so it's not a true circular dep.
    mem.exec_opts = exec_opts  # circular dep

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

    # Set these BEFORE processing flags, so they can be overridden.
    if lang == 'oil':
        mutable_opts.SetShoptOption('oil:all', True)

    builtin_pure.SetShellOpts(mutable_opts, opts.opt_changes,
                              opts.shopt_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, parse_opts, aliases, oil_grammar)
    parse_ctx.Init_OnePassParse(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, parse_opts, aliases,
                                      oil_grammar)
    comp_ctx.Init_Trail(trail1)
    comp_ctx.Init_OnePassParse(True)

    hist_arena = alloc.Arena()
    hist_arena.PushSource(source.Unused('history'))
    trail2 = parse_lib.Trail()
    hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases,
                                      oil_grammar)
    hist_ctx.Init_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()
    exec_deps.mutable_opts = mutable_opts

    # 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 = []  # TODO: Clear on fork() to avoid duplicates

    exec_deps.job_state = job_state
    exec_deps.waiter = process.Waiter(job_state, exec_opts)
    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.search_path = state.SearchPath(mem)
    exec_deps.ext_prog = process.ExternalProgram(interp, fd_state,
                                                 exec_deps.search_path, errfmt,
                                                 debug_f)

    splitter = split.SplitContext(mem)

    # split() builtin
    # TODO: Accept IFS as a named arg?  split('a b', IFS=' ')
    builtin_funcs.SetGlobalFunc(
        mem,
        'split',
        lambda s, ifs=None: splitter.SplitForWordEval(s, ifs=ifs))

    # glob() builtin
    # TODO: This is instantiation is duplicated in osh/word_eval.py
    globber = glob_.Globber(exec_opts)
    builtin_funcs.SetGlobalFunc(mem, 'glob', lambda s: globber.OilFuncCall(s))

    # 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()

    new_var = builtin_assign.NewVar(mem, procs, errfmt)

    builtins = {  # Lookup
        builtin_i.echo: builtin_pure.Echo(exec_opts),
        builtin_i.printf: builtin_printf.Printf(mem, parse_ctx, errfmt),

        builtin_i.pushd: builtin_misc.Pushd(mem, dir_stack, errfmt),
        builtin_i.popd: builtin_misc.Popd(mem, dir_stack, errfmt),
        builtin_i.dirs: builtin_misc.Dirs(mem, dir_stack, errfmt),
        builtin_i.pwd: builtin_misc.Pwd(mem, errfmt),

        builtin_i.times: builtin_misc.Times(),
        builtin_i.read: builtin_misc.Read(splitter, mem),
        builtin_i.help: builtin_misc.Help(loader, errfmt),
        builtin_i.history: builtin_misc.History(line_input),

        # Completion (more added below)
        builtin_i.compopt: builtin_comp.CompOpt(compopt_state, errfmt),
        builtin_i.compadjust: builtin_comp.CompAdjust(mem),

        # test / [ differ by need_right_bracket
        builtin_i.test: builtin_bracket.Test(False, exec_opts, errfmt),
        builtin_i.bracket: builtin_bracket.Test(True, exec_opts, errfmt),

        # ShAssignment (which are pure)
        builtin_i.declare: new_var,
        builtin_i.typeset: new_var,
        builtin_i.local: new_var,

        builtin_i.export_: builtin_assign.Export(mem, errfmt),
        builtin_i.readonly: builtin_assign.Readonly(mem, errfmt),

        builtin_i.unset: builtin_assign.Unset(mem, procs, errfmt),
        builtin_i.shift: builtin_assign.Shift(mem),

        # Pure
        builtin_i.set: builtin_pure.Set(mutable_opts, mem),
        builtin_i.shopt: builtin_pure.Shopt(mutable_opts),

        builtin_i.alias: builtin_pure.Alias(aliases, errfmt),
        builtin_i.unalias: builtin_pure.UnAlias(aliases, errfmt),

        builtin_i.type: builtin_pure.Type(procs, aliases, exec_deps.search_path),
        builtin_i.hash: builtin_pure.Hash(exec_deps.search_path),
        builtin_i.getopts: builtin_pure.GetOpts(mem, errfmt),

        builtin_i.colon: builtin_pure.Boolean(0),  # a "special" builtin 
        builtin_i.true_: builtin_pure.Boolean(0),
        builtin_i.false_: builtin_pure.Boolean(1),

        # Process
        builtin_i.wait: builtin_process.Wait(exec_deps.waiter,
                                             exec_deps.job_state, mem, errfmt),
        builtin_i.jobs: builtin_process.Jobs(exec_deps.job_state),
        builtin_i.fg: builtin_process.Fg(exec_deps.job_state, exec_deps.waiter),
        builtin_i.bg: builtin_process.Bg(exec_deps.job_state),
        builtin_i.umask: builtin_process.Umask(),

        # Oil
        builtin_i.push: builtin_oil.Push(mem, errfmt),
        builtin_i.append: builtin_oil.Append(mem, errfmt),

        builtin_i.write: builtin_oil.Write(mem, errfmt),
        builtin_i.getline: builtin_oil.Getline(mem, errfmt),

        builtin_i.repr: builtin_oil.Repr(mem, errfmt),
        builtin_i.use: builtin_oil.Use(mem, errfmt),
        builtin_i.opts: builtin_oil.Opts(mem, errfmt),
    }

    arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, errfmt)
    bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, errfmt)
    expr_ev = expr_eval.OilEvaluator(mem, procs, errfmt)
    word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt)
    ex = cmd_exec.Executor(mem, fd_state, procs, builtins, exec_opts,
                           parse_ctx, exec_deps)
    # PromptEvaluator rendering is needed in non-interactive shells for @P.
    prompt_ev = prompt.Evaluator(lang, parse_ctx, mem)
    tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev,
                        trace_f)

    # Wire up circular dependencies.
    vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, ex, prompt_ev,
                        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_i.complete] = complete_builtin
    builtins[builtin_i.compgen] = builtin_comp.CompGen(spec_builder)
    builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, ex, errfmt)
    builtins[builtin_i.json] = builtin_oil.Json(mem, ex, errfmt)

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

    builtins[builtin_i.trap] = builtin_process.Trap(sig_state, exec_deps.traps,
                                                    exec_deps.trap_nodes, ex,
                                                    errfmt)

    # 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
            mutable_opts.set_interactive()

    elif opts.i:  # force interactive
        arena.PushSource(source.Stdin(' -i'))
        line_reader = py_reader.InteractiveLineReader(arena, prompt_ev,
                                                      hist_ev, line_input,
                                                      prompt_state)
        mutable_opts.set_interactive()

    else:
        script_name = arg_r.Peek()
        if script_name is None:
            if sys.stdin.isatty():
                arena.PushSource(source.Interactive())
                line_reader = py_reader.InteractiveLineReader(
                    arena, prompt_ev, hist_ev, line_input, prompt_state)
                mutable_opts.set_interactive()
            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.
    c_parser = parse_ctx.MakeOshParser(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 = passwd.GetMyHomeDir()
        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, splitter,
                                                   errfmt)

            ev.arith_ev = arith_ev
            ev.expr_ev = expr_ev
            ev.prompt_ev = prompt_ev
            ev.CheckCircularDeps()

            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.
        try:
            SourceStartupFile(rc_path, lang, parse_ctx, ex)
        except util.UserExit as e:
            return e.status

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

        prompt_plugin = prompt.UserPlugin(mem, parse_ctx, ex)
        try:
            status = main_loop.Interactive(opts, ex, c_parser, display,
                                           prompt_plugin, errfmt)
            if ex.MaybeRunExitTrap():
                status = ex.LastStatus()
        except util.UserExit as e:
            status = e.status
        return status

    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)')
    try:
        status = main_loop.Batch(ex, c_parser, arena, nodes_out=nodes_out)
        if ex.MaybeRunExitTrap():
            status = ex.LastStatus()
    except util.UserExit as e:
        status = e.status

    # 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
Exemple #18
0
def OpyCommandMain(argv):
  """Dispatch to the right action."""

  # TODO: Use core/arg_def.
  #opts, argv = Options().parse_args(argv)

  try:
    action = argv[0]
  except IndexError:
    raise error.Usage('opy: Missing required subcommand.')

  argv = argv[1:]  # TODO: Should I do input.ReadRequiredArg()?
                   # That will shift the input.

  if action in (
      'parse', 'parse-with', 'compile', 'dis', 'ast', 'symbols', 'cfg',
      'compile-ovm', 'eval', 'repl', 'run', 'run-ovm'):
    loader = pyutil.GetResourceLoader()
    f = loader.open(GRAMMAR_REL_PATH)
    contents = f.read()
    f.close()
    gr = grammar.Grammar()
    gr.loads(contents)

    # In Python 2 code, always use from __future__ import print_function.
    try:
      del gr.keywords["print"]
    except KeyError:
      pass

    symbols = Symbols(gr)
    pytree.Init(symbols)  # for type_repr() pretty printing
    transformer.Init(symbols)  # for _names and other dicts

    compiler = skeleton.Compiler(gr)
  else:
    # e.g. pgen2 doesn't use any of these.  Maybe we should make a different
    # tool.
    compiler = None

  # TODO: Also have a run_spec for 'opyc run'.
  compile_spec = arg_def.OilFlags('opy')
  compile_spec.Flag('-emit-docstring', args.Bool, default=True,
                    help='Whether to emit docstrings')
  compile_spec.Flag('-fast-ops', args.Bool, default=True,
                    help='Whether to emit LOAD_FAST, STORE_FAST, etc.')
  compile_spec.Flag('-oil-subset', args.Bool, default=False,
                    help='Only allow the constructs necessary to implement'
                    'Oil. Example: using multiple inheritance will abort '
                    'compilation.')

  #
  # Actions
  #

  if action == 'pgen2':
    grammar_path = argv[0]
    marshal_path = argv[1]
    WriteGrammar(grammar_path, marshal_path)

  elif action == 'stdlib-parse':
    # This is what the compiler/ package was written against.
    import parser

    py_path = argv[1]
    with open(py_path) as f:
      st = parser.suite(f.read())

    tree = st.totuple()

    printer = TupleTreePrinter(HostStdlibNames())
    printer.Print(tree)
    n = CountTupleTree(tree)
    log('COUNT %d', n)

  elif action == 'lex':
    py_path = argv[0]
    with open(py_path) as f:
      tokens = tokenize.generate_tokens(f.readline)
      for typ, val, start, end, unused_line in tokens:
        print('%10s %10s %-10s %r' % (start, end, token.tok_name[typ], val))

  elif action == 'lex-names':  # Print all the NAME tokens.
    for py_path in argv:
      log('Lexing %s', py_path)
      with open(py_path) as f:
        tokens = tokenize.generate_tokens(f.readline)
        for typ, val, start, end, unused_line in tokens:
          if typ == token.NAME:
            print(val)

  elif action == 'parse':
    py_path = argv[0]
    with open(py_path) as f:
      tokens = tokenize.generate_tokens(f.readline)
      p = parse.Parser(gr)
      pnode  = driver.PushTokens(p, tokens, gr, 'file_input')

    printer = ParseTreePrinter(transformer._names)  # print raw nodes
    printer.Print(pnode)

  # Parse with an arbitrary grammar, but the Python lexer.
  elif action == 'parse-with':
    grammar_path = argv[0]
    start_symbol = argv[1]
    code_str = argv[2]

    with open(grammar_path) as f:
      gr = pgen.MakeGrammar(f)

    f = cStringIO.StringIO(code_str)
    tokens = tokenize.generate_tokens(f.readline)
    p = parse.Parser(gr)  # no convert=
    try:
      pnode = driver.PushTokens(p, tokens, gr, start_symbol)
    except parse.ParseError as e:
      # Extract location information and show it.
      _, _, (lineno, offset) = e.opaque
      # extra line needed for '\n' ?
      lines = code_str.splitlines() + ['']

      line = lines[lineno-1]
      log('  %s', line)
      log('  %s^', ' '*offset)
      log('Parse Error: %s', e)
      return 1
    printer = ParseTreePrinter(transformer._names)  # print raw nodes
    printer.Print(pnode)

  elif action == 'ast':  # output AST
    opt, i = compile_spec.ParseArgv(argv)
    py_path = argv[i]
    with open(py_path) as f:
      graph = compiler.Compile(f, opt, 'exec', print_action='ast')

  elif action == 'symbols':  # output symbols
    opt, i = compile_spec.ParseArgv(argv)
    py_path = argv[i]
    with open(py_path) as f:
      graph = compiler.Compile(f, opt, 'exec', print_action='symbols')

  elif action == 'cfg':  # output Control Flow Graph
    opt, i = compile_spec.ParseArgv(argv)
    py_path = argv[i]
    with open(py_path) as f:
      graph = compiler.Compile(f, opt, 'exec', print_action='cfg')

  elif action == 'compile':  # 'opyc compile' is pgen2 + compiler2
    # spec.Arg('action', ['foo', 'bar'])
    # But that leads to some duplication.

    opt, i = compile_spec.ParseArgv(argv)

    py_path = argv[i]
    out_path = argv[i+1]

    with open(py_path) as f:
      co = compiler.Compile(f, opt, 'exec')

    log("Compiled to %d bytes of top-level bytecode", len(co.co_code))

    # Write the .pyc file
    with open(out_path, 'wb') as out_f:
      h = misc.getPycHeader(py_path)
      out_f.write(h)
      marshal.dump(co, out_f)

  elif action == 'compile-ovm':
    # NOTE: obsolete
    from ovm2 import oheap2
    opt, i = compile_spec.ParseArgv(argv)
    py_path = argv[i]
    out_path = argv[i+1]

    # Compile to Python bytecode (TODO: remove ovm_codegen.py)
    mode = 'exec'
    with open(py_path) as f:
      co = compiler.Compile(f, opt, mode)

    if 1:
      with open(out_path, 'wb') as out_f:
        oheap2.Write(co, out_f)
      return 0

    log("Compiled to %d bytes of top-level bytecode", len(co.co_code))
    # Write the .pyc file
    with open(out_path, 'wb') as out_f:
      if 1:
        out_f.write(co.co_code)
      else:
        h = misc.getPycHeader(py_path)
        out_f.write(h)
        marshal.dump(co, out_f)
    log('Wrote only the bytecode to %r', out_path)

  elif action == 'eval':  # Like compile, but parses to a code object and prints it
    opt, i = compile_spec.ParseArgv(argv)
    py_expr = argv[i]
    f = skeleton.StringInput(py_expr, '<eval input>')
    co = compiler.Compile(f, opt, 'eval')

    v = dis_tool.Visitor()
    v.show_code(co)
    print()
    print('RESULT:')
    print(eval(co))

  elif action == 'repl':  # Like eval in a loop
    while True:
      py_expr = raw_input('opy> ')
      f = skeleton.StringInput(py_expr, '<REPL input>')

      # TODO: change this to 'single input'?  Why doesn't this work?
      co = compiler.Compile(f, opt, 'eval')

      v = dis_tool.Visitor()
      v.show_code(co)
      print(eval(co))

  elif action == 'dis-tables':
    out_dir = argv[0]
    pyc_paths = argv[1:]

    out = TableOutput(out_dir)

    for pyc_path in pyc_paths:
      with open(pyc_path) as f:
        magic, unixtime, timestamp, code = dis_tool.unpack_pyc(f)
        WriteDisTables(pyc_path, code, out)

    out.Close()

  elif action == 'dis':
    opt, i = compile_spec.ParseArgv(argv)
    path = argv[i]
    v = dis_tool.Visitor()

    if path.endswith('.py'):
      with open(path) as f:
        co = compiler.Compile(f, opt, 'exec')

      log("Compiled to %d bytes of top-level bytecode", len(co.co_code))
      v.show_code(co)

    else:  # assume pyc_path
      with open(path, 'rb') as f:
        v.Visit(f)

  elif action == 'dis-md5':
    pyc_paths = argv
    if not pyc_paths:
      raise error.Usage('dis-md5: At least one .pyc path is required.')

    for path in pyc_paths:
      h = hashlib.md5()
      with open(path) as f:
        magic = f.read(4)
        h.update(magic)
        ignored_timestamp = f.read(4)
        while True:
          b = f.read(64 * 1024)
          if not b:
            break
          h.update(b)
      print('%6d %s %s' % (os.path.getsize(path), h.hexdigest(), path))

  elif action == 'run':  # Compile and run, without writing pyc file
    # TODO: Add an option like -v in __main__

    #level = logging.DEBUG if args.verbose else logging.WARNING
    #logging.basicConfig(level=level)
    #logging.basicConfig(level=logging.DEBUG)

    opt, i = compile_spec.ParseArgv(argv)

    py_path = argv[i]
    opy_argv = argv[i:]

    if py_path.endswith('.py'):
      with open(py_path) as f:
        co = compiler.Compile(f, opt, 'exec')
      num_ticks = execfile.run_code_object(co, opy_argv)

    elif py_path.endswith('.pyc') or py_path.endswith('.opyc'):
      with open(py_path) as f:
        f.seek(8)  # past header.  TODO: validate it!
        co = marshal.load(f)
      num_ticks = execfile.run_code_object(co, opy_argv)

    else:
      raise error.Usage('Invalid path %r' % py_path)

  elif action == 'run-ovm':  # Compile and run, without writing pyc file
    opt, i = compile_spec.ParseArgv(argv)
    py_path = argv[i]
    opy_argv = argv[i+1:]

    if py_path.endswith('.py'):
      #mode = 'exec'
      mode = 'ovm'  # OVM bytecode is different!
      with open(py_path) as f:
        co = compiler.Compile(f, opt, mode)
      log('Compiled to %d bytes of OVM code', len(co.co_code))
      num_ticks = ovm.run_code_object(co, opy_argv)

    elif py_path.endswith('.pyc') or py_path.endswith('.opyc'):
      with open(py_path) as f:
        f.seek(8)  # past header.  TODO: validate it!
        co = marshal.load(f)
      num_ticks = ovm.run_code_object(co, opy_argv)

    else:
      raise error.Usage('Invalid path %r' % py_path)

  else:
    raise error.Usage('Invalid action %r' % action)
Exemple #19
0
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)
    builtin_funcs.Init(mem)

    procs = {}

    job_state = process.JobState()
    fd_state = process.FdState(errfmt, job_state)
    parse_opts = parse_lib.OilParseOptions()
    exec_opts = state.ExecOpts(mem, parse_opts, line_input)

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

    builtin_pure.SetExecOpts(exec_opts, opts.opt_changes, opts.shopt_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,
                                       parse_opts,
                                       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,
                                      parse_opts,
                                      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,
                                      parse_opts,
                                      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 = []  # TODO: Clear on fork() to avoid duplicates

    exec_deps.job_state = job_state
    # note: exec_opts.interactive set later
    exec_deps.waiter = process.Waiter(job_state, exec_opts)
    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.search_path = state.SearchPath(mem)
    exec_deps.ext_prog = process.ExternalProgram(interp, fd_state,
                                                 exec_deps.search_path, errfmt,
                                                 debug_f)

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

    # split() builtin
    builtin_funcs.SetGlobalFunc(mem, 'split',
                                lambda s: splitter.SplitForWordEval(s))

    # 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()

    new_var = builtin_assign.NewVar(mem, procs, errfmt)

    builtins = {  # Lookup
        builtin_e.ECHO: builtin_pure.Echo(exec_opts),
        builtin_e.PRINTF: builtin_printf.Printf(mem, parse_ctx, 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(mem, errfmt),

        builtin_e.READ: builtin.Read(splitter, mem),
        builtin_e.HELP: builtin.Help(loader, errfmt),
        builtin_e.HISTORY: builtin.History(line_input),

        # Completion (more added below)
        builtin_e.COMPOPT: builtin_comp.CompOpt(compopt_state, errfmt),
        builtin_e.COMPADJUST: builtin_comp.CompAdjust(mem),

        # test / [ differ by need_right_bracket
        builtin_e.TEST: builtin_bracket.Test(False, errfmt),
        builtin_e.BRACKET: builtin_bracket.Test(True, errfmt),

        # Assignment (which are pure)
        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),

        builtin_e.UNSET: builtin_assign.Unset(mem, procs, errfmt),
        builtin_e.SHIFT: builtin_assign.Shift(mem),

        # Pure
        builtin_e.SET: builtin_pure.Set(exec_opts, mem),
        builtin_e.SHOPT: builtin_pure.Shopt(exec_opts),

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

        builtin_e.TYPE: builtin_pure.Type(procs, aliases, exec_deps.search_path),
        builtin_e.HASH: builtin_pure.Hash(exec_deps.search_path),
        builtin_e.GETOPTS: builtin_pure.GetOpts(mem, errfmt),

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

        # Process
        builtin_e.WAIT: builtin_process.Wait(exec_deps.waiter,
                                             exec_deps.job_state, mem, errfmt),
        builtin_e.JOBS: builtin_process.Jobs(exec_deps.job_state),
        builtin_e.FG: builtin_process.Fg(exec_deps.job_state, exec_deps.waiter),
        builtin_e.BG: builtin_process.Bg(exec_deps.job_state),
        builtin_e.UMASK: builtin_process.Umask,

        # Oil
        builtin_e.REPR: builtin_oil.Repr(mem, errfmt),
        builtin_e.PUSH: builtin_oil.Push(mem, errfmt),
        builtin_e.USE: builtin_oil.Use(mem, errfmt),
    }

    ex = cmd_exec.Executor(mem, fd_state, procs, 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 = osh_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 = osh_expr_eval.BoolEvaluator(mem, exec_opts, word_ev, errfmt)
    exec_deps.bool_ev = bool_ev

    expr_ev = expr_eval.OilEvaluator(mem, procs, ex, word_ev, errfmt)
    exec_deps.expr_ev = expr_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.expr_ev = expr_ev
    ex.tracer = tracer

    word_ev.expr_ev = expr_ev

    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)
    builtins[builtin_e.CD] = builtin.Cd(mem, dir_stack, ex, errfmt)
    builtins[builtin_e.JSON] = builtin_oil.Json(mem, ex, errfmt)

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

    builtins[builtin_e.TRAP] = builtin_process.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.
        try:
            SourceStartupFile(rc_path, lang, parse_ctx, ex)
        except util.UserExit as e:
            return e.status

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

        prompt_plugin = prompt.UserPlugin(mem, parse_ctx, ex)
        try:
            status = main_loop.Interactive(opts, ex, c_parser, display,
                                           prompt_plugin, errfmt)
            if ex.MaybeRunExitTrap():
                status = ex.LastStatus()
        except util.UserExit as e:
            status = e.status
        return status

    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)')
    try:
        status = main_loop.Batch(ex, c_parser, arena, nodes_out=nodes_out)
        if ex.MaybeRunExitTrap():
            status = ex.LastStatus()
    except util.UserExit as e:
        status = e.status

    # 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
Exemple #20
0
def Main(arg_r):
    # type: (args.Reader) -> int
    """
  Usage:
    tea myprog.tea                      # Run the script
    tea -c 'func main() { echo "hi" }'  # Run this snippet.  Not common since
                                        # there's no top level statementes!
                                        # Use bin/oil for that.
    tea -n -c 'var x = 1'               # Parse it

    # Compile to C++.  Produce myprog.cc and myprog.h.
    tea --cpp-out myprog myprog.tea lib.tea
  """
    try:
        attrs = flag_spec.ParseOil('tea_main', arg_r)
    except error.Usage as e:
        stderr_line('tea usage error: %s', e.msg)
        return 2
    arg = arg_types.tea_main(attrs.attrs)

    arena = alloc.Arena()

    if arg.c is not None:
        arena.PushSource(source.CFlag())
        line_reader = reader.StringLineReader(arg.c,
                                              arena)  # type: reader._Reader
    else:
        script_name = arg_r.Peek()
        if script_name is None:
            arena.PushSource(source.Stdin())
            f = mylib.Stdin()  # type: mylib.LineReader
        else:
            arena.PushSource(source.MainFile(script_name))
            try:
                # Hack for type checking.  core/shell.py uses fd_state.Open().
                f = cast('mylib.LineReader', open(script_name))
            except IOError as e:
                stderr_line("tea: Couldn't open %r: %s", script_name,
                            posix.strerror(e.errno))
                return 2
        line_reader = reader.FileLineReader(f, arena)

    # Dummy value; not respecting aliases!
    aliases = {}  # type: Dict[str, str]

    loader = pyutil.GetResourceLoader()
    oil_grammar = pyutil.LoadOilGrammar(loader)

    # Not used in tea, but OK...
    opt0_array = state.InitOpts()
    no_stack = None  # type: List[bool]  # for mycpp
    opt_stacks = [no_stack] * option_i.ARRAY_SIZE  # type: List[List[bool]]
    parse_opts = optview.Parse(opt0_array, opt_stacks)

    # parse `` and a[x+1]=bar differently
    parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar)

    if arg.n:
        try:
            parse_ctx.ParseTeaModule(line_reader)
            status = 0
        except error.Parse as e:
            ui.PrettyPrintError(e, arena)
            status = 2
    else:
        e_usage("Tea doesn't run anything yet.  Pass -n to parse.")

    return status