def AppBundleMain(argv): login_shell = False b = os_path.basename(argv[0]) main_name, ext = os_path.splitext(b) if main_name.startswith('-'): login_shell = True main_name = main_name[1:] if main_name == 'oil' and ext: # oil.py or oil.ovm try: first_arg = argv[1] except IndexError: raise args.UsageError('Missing required applet name.') if first_arg in ('-h', '--help'): builtin.Help(['bundle-usage'], util.GetResourceLoader()) sys.exit(0) if first_arg in ('-V', '--version'): _ShowVersion() sys.exit(0) main_name = first_arg if main_name.startswith('-'): # TODO: Remove duplication above login_shell = True main_name = main_name[1:] argv0 = argv[1] main_argv = argv[2:] else: argv0 = argv[0] main_argv = argv[1:] if main_name in ('osh', 'sh'): status = ShellMain('osh', argv0, main_argv, login_shell) _tlog('done osh main') return status elif main_name == 'oshc': return OshCommandMain(main_argv) elif main_name == 'oil': return ShellMain('oil', argv0, main_argv, login_shell) elif main_name == 'wok': return WokMain(main_argv) elif main_name == 'boil': return BoilMain(main_argv) # For testing latency elif main_name == 'true': return 0 elif main_name == 'false': return 1 elif main_name == 'readlink': return readlink.main(main_argv) else: raise args.UsageError('Invalid applet name %r.' % main_name)
def main(argv): try: sys.exit(OilMain(argv)) except NotImplementedError as e: raise except OilUsageError as e: builtin.Help(['oil-usage'], util.GetResourceLoader()) print(str(e), file=sys.stderr) sys.exit(2) except RuntimeError as e: print('FATAL: %s' % e, file=sys.stderr) sys.exit(1)
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 = util.GetResourceLoader() Help([], loader) for name, spec in BUILTIN_DEF.arg_specs.iteritems(): print(name) spec.PrintHelp(sys.stdout) print()
def main(argv): try: sys.exit(OilMain(argv)) except NotImplementedError as e: raise except OilUsageError as e: builtin.Help(['oil-usage'], util.GetResourceLoader()) print(str(e), file=sys.stderr) sys.exit(2) except RuntimeError as e: print('FATAL: %s' % e, file=sys.stderr) sys.exit(1) finally: _tlog('Exiting main()') if _trace_path: _tracer.Stop(_trace_path)
def OilMain(argv): login_shell = False b = os.path.basename(argv[0]) main_name, _ = os.path.splitext(b) if main_name.startswith("-"): login_shell = True main_name = main_name[1:] if main_name in ('oil', 'oil_main'): try: first_arg = argv[1] except IndexError: raise OilUsageError('Missing name of main()') if first_arg in ('-h', '--help'): builtin.Help(['oil-usage'], util.GetResourceLoader()) sys.exit(0) if first_arg in ('-V', '--version'): _ShowVersion() sys.exit(0) main_name = first_arg if main_name.startswith("-"): login_shell = True main_name = main_name[1:] main_argv = argv[2:] else: main_argv = argv[1:] if main_name in ('osh', 'sh'): status = OshMain(main_argv, login_shell) tlog('done osh main') return status elif main_name == 'wok': return WokMain(main_argv) elif main_name == 'boil': return BoilMain(main_argv) elif main_name == 'true': return 0 elif main_name == 'false': return 1 else: raise OilUsageError('Invalid main %r' % main_name)
def OilMain(argv): arg_r = args.Reader(argv) try: opts = OIL_SPEC.Parse(arg_r) except args.UsageError as e: ui.usage('oil usage error: %s', e) return 2 if opts.help: loader = util.GetResourceLoader() builtin.Help(['oil-usage'], 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 raise NotImplementedError('oil') return 0
def _ShowVersion(): loader = util.GetResourceLoader() f = loader.open('oil-version.txt') version = f.readline().strip() f.close() try: f = loader.open('release-date.txt') except IOError: release_date = '-' # in dev tree else: release_date = f.readline().strip() finally: f.close() # What C functions do these come from? print('Oil version %s' % version) print('Release Date: %s' % release_date) print('Arch: %s' % platform.machine()) print('OS: %s' % platform.system()) print('Platform: %s' % platform.version())
def OilMain(argv): spec = args.FlagsAndOptions() # TODO: -h too spec.LongFlag('--help') spec.LongFlag('--version') #builtin.AddOptionsToArgSpec(spec) try: opts, opt_index = spec.Parse(argv) except args.UsageError as e: util.usage(str(e)) return 2 if opts.help: loader = util.GetResourceLoader() builtin.Help(['oil-usage'], 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 raise NotImplementedError('oil') return 0
def _RunBuiltin(self, builtin_id, argv): restore_fd_state = True # NOTE: Builtins don't need to know their own name. argv = argv[1:] # TODO: figure out a quicker dispatch mechanism. Just make a table of # builtins I guess. if builtin_id == EBuiltin.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 == EBuiltin.READ: status = builtin.Read(argv, self.mem) elif builtin_id == EBuiltin.ECHO: status = builtin.Echo(argv) elif builtin_id == EBuiltin.SHIFT: status = builtin.Shift(argv, self.mem) elif builtin_id == EBuiltin.CD: status = builtin.Cd(argv, self.mem) elif builtin_id == EBuiltin.SET: status = builtin.Set(argv, self.exec_opts, self.mem) elif builtin_id == EBuiltin.SHOPT: status = builtin.Shopt(argv, self.exec_opts) elif builtin_id == EBuiltin.UNSET: status = builtin.Unset(argv, self.mem, self.funcs) elif builtin_id == EBuiltin.EXPORT: status = builtin.Export(argv, self.mem) elif builtin_id == EBuiltin.EXIT: status = builtin.Exit(argv) elif builtin_id == EBuiltin.WAIT: status = builtin.Wait(argv, self.waiter, self.job_state, self.mem) elif builtin_id == EBuiltin.JOBS: status = builtin.Jobs(argv, self.job_state) elif builtin_id == EBuiltin.PUSHD: status = builtin.Pushd(argv, self.dir_stack) elif builtin_id == EBuiltin.POPD: status = builtin.Popd(argv, self.dir_stack) elif builtin_id == EBuiltin.DIRS: status = builtin.Dirs(argv, self.dir_stack) elif builtin_id in (EBuiltin.SOURCE, EBuiltin.DOT): status = self._Source(argv) elif builtin_id == EBuiltin.TRAP: status = builtin.Trap(argv, self.traps) elif builtin_id == EBuiltin.UMASK: status = builtin.Umask(argv) elif builtin_id == EBuiltin.EVAL: status = self._Eval(argv) elif builtin_id == EBuiltin.COMPLETE: status = self._Complete(argv) elif builtin_id == EBuiltin.COMPGEN: status = self._CompGen(argv) elif builtin_id == EBuiltin.COLON: # special builtin like 'true' status = 0 elif builtin_id == EBuiltin.TRUE: status = 0 elif builtin_id == EBuiltin.FALSE: status = 1 elif builtin_id == EBuiltin.HELP: loader = util.GetResourceLoader() status = builtin.Help(argv, loader) elif builtin_id == EBuiltin.DEBUG_LINE: status = builtin.DebugLine(argv, self.status_lines) else: raise AssertionError('Unhandled builtin: %d' % builtin_id) assert isinstance(status, int) return status
def _RunBuiltin(self, builtin_id, argv, span_id): # NOTE: Builtins don't need to know their own name. argv = argv[1:] # TODO: figure out a quicker dispatch mechanism. Just make a table of # builtins I guess. if 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.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.GetVar('HOME'), self.dir_stack) elif builtin_id == builtin_e.POPD: status = builtin.Popd(argv, self.mem.GetVar('HOME'), 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.COMPLETE: status = comp_builtins.Complete(argv, self, self.comp_lookup) elif builtin_id == builtin_e.COMPGEN: status = comp_builtins.CompGen(argv, self) elif builtin_id == builtin_e.COMPOPT: status = comp_builtins.CompOpt(argv) 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 = test_builtin.Test(argv, False) elif builtin_id == builtin_e.BRACKET: status = test_builtin.Test(argv, True) # need_right_bracket elif builtin_id == builtin_e.GETOPTS: status = builtin.GetOpts(argv, self.mem) elif builtin_id == builtin_e.COMMAND: path = self.mem.GetVar('PATH') status = builtin.Command(argv, self.funcs, path) elif builtin_id == builtin_e.TYPE: path = self.mem.GetVar('PATH') status = builtin.Type(argv, self.funcs, path) 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.HELP: loader = util.GetResourceLoader() status = builtin.Help(argv, loader) elif builtin_id == builtin_e.REPR: status = builtin.Repr(argv, self.mem) else: raise AssertionError('Unhandled builtin: %s' % builtin_id) assert isinstance(status, int) return status
def OshMain(argv0, argv, login_shell): spec = args.FlagsAndOptions() spec.ShortFlag('-c', args.Str, quit_parsing_flags=True) # command string spec.ShortFlag('-i') # interactive # TODO: -h too spec.LongFlag('--help') spec.LongFlag('--version') spec.LongFlag( '--ast-format', ['text', 'abbrev-text', 'html', 'abbrev-html', 'oheap', 'none'], default='abbrev-text') spec.LongFlag('--show-ast') # execute and show spec.LongFlag('--fix') spec.LongFlag('--debug-spans') # For oshc translate spec.LongFlag('--print-status') spec.LongFlag( '--trace', ['cmd-parse', 'word-parse', 'lexer']) # NOTE: can only trace one now spec.LongFlag('--hijack-shebang') # For benchmarks/*.sh spec.LongFlag('--parser-mem-dump', args.Str) spec.LongFlag('--runtime-mem-dump', args.Str) builtin.AddOptionsToArgSpec(spec) try: opts, opt_index = spec.Parse(argv) except args.UsageError as e: util.usage(str(e)) return 2 if opts.help: loader = util.GetResourceLoader() builtin.Help(['osh-usage'], 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 trace_state = util.TraceState() if 'cmd-parse' == opts.trace: util.WrapMethods(cmd_parse.CommandParser, trace_state) if 'word-parse' == opts.trace: util.WrapMethods(word_parse.WordParser, trace_state) if 'lexer' == opts.trace: util.WrapMethods(lexer.Lexer, trace_state) if opt_index == len(argv): dollar0 = argv0 else: dollar0 = argv[opt_index] # the script name, or the arg after -c # TODO: Create a --parse action or 'osh parse' or 'oil osh-parse' # osh-fix # It uses a different memory-management model. It's a batch program and not # an interactive program. pool = alloc.Pool() arena = pool.NewArena() # TODO: Maybe wrap this initialization sequence up in an oil_State, like # lua_State. status_lines = ui.MakeStatusLines() mem = state.Mem(dollar0, argv[opt_index + 1:], os.environ, arena) funcs = {} # Passed to Executor for 'complete', and passed to completion.Init if completion: comp_lookup = completion.CompletionLookup() else: # TODO: NullLookup? comp_lookup = None exec_opts = state.ExecOpts(mem) builtin.SetExecOpts(exec_opts, opts.opt_changes) fd_state = process.FdState() ex = cmd_exec.Executor(mem, fd_state, status_lines, funcs, readline, completion, comp_lookup, exec_opts, arena) # NOTE: The rc file can contain both commands and functions... ideally we # would only want to save nodes/lines for the functions. try: rc_path = 'oilrc' arena.PushSource(rc_path) with open(rc_path) as f: rc_line_reader = reader.FileLineReader(f, arena) _, rc_c_parser = parse_lib.MakeParser(rc_line_reader, arena) try: rc_node = rc_c_parser.ParseWholeFile() if not rc_node: err = rc_c_parser.Error() ui.PrintErrorStack(err, arena, sys.stderr) return 2 # parse error is code 2 finally: arena.PopSource() status = ex.Execute(rc_node) #print('oilrc:', status, cflow, file=sys.stderr) # Ignore bad status? except IOError as e: if e.errno != errno.ENOENT: raise if opts.c is not None: arena.PushSource('<command string>') 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('<stdin -i>') line_reader = reader.InteractiveLineReader(OSH_PS1, arena) exec_opts.interactive = True else: try: script_name = argv[opt_index] except IndexError: if sys.stdin.isatty(): arena.PushSource('<interactive>') line_reader = reader.InteractiveLineReader(OSH_PS1, arena) exec_opts.interactive = True else: arena.PushSource('<stdin>') line_reader = reader.FileLineReader(sys.stdin, arena) else: arena.PushSource(script_name) try: f = fd_state.Open(script_name) except OSError as e: util.error("Couldn't open %r: %s", script_name, os.strerror(e.errno)) return 1 line_reader = reader.FileLineReader(f, arena) # TODO: assert arena.NumSourcePaths() == 1 # TODO: .rc file needs its own arena. w_parser, c_parser = parse_lib.MakeParser(line_reader, arena) if exec_opts.interactive: # NOTE: We're using a different evaluator here. The completion system can # also run functions... it gets the Executor through Executor._Complete. if HAVE_READLINE: splitter = legacy.SplitContext(mem) ev = word_eval.CompletionWordEvaluator(mem, exec_opts, splitter) status_out = completion.StatusOutput(status_lines, exec_opts) completion.Init(pool, builtin.BUILTIN_DEF, mem, funcs, comp_lookup, status_out, ev) return InteractiveLoop(opts, ex, c_parser, w_parser, line_reader) else: # Parse the whole thing up front #print('Parsing file') _tlog('ParseWholeFile') # TODO: Do I need ParseAndEvalLoop? How is it different than # InteractiveLoop? try: node = c_parser.ParseWholeFile() except util.ParseError as e: ui.PrettyPrintError(e, arena, sys.stderr) print('parse error: %s' % e.UserErrorString(), file=sys.stderr) return 2 else: # TODO: Remove this older form of error handling. if not node: err = c_parser.Error() assert err, err # can't be empty ui.PrintErrorStack(err, arena, sys.stderr) return 2 # parse error is code 2 do_exec = True if opts.fix: #log('SPANS: %s', arena.spans) osh2oil.PrintAsOil(arena, node, opts.debug_spans) do_exec = False if exec_opts.noexec: do_exec = False # Do this after parsing the entire file. There could be another option to # do it before exiting runtime? if opts.parser_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' % os.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) # -n prints AST, --show-ast prints and executes if exec_opts.noexec or opts.show_ast: if opts.ast_format == 'none': print('AST not printed.', file=sys.stderr) elif opts.ast_format == 'oheap': # TODO: Make this a separate flag? if sys.stdout.isatty(): raise RuntimeError( 'ERROR: Not dumping binary data to a TTY.') f = sys.stdout enc = encode.Params() out = encode.BinOutput(f) encode.EncodeRoot(node, enc, out) else: # text output f = sys.stdout if opts.ast_format in ('text', 'abbrev-text'): ast_f = fmt.DetectConsoleOutput(f) elif opts.ast_format in ('html', 'abbrev-html'): ast_f = fmt.HtmlOutput(f) else: raise AssertionError abbrev_hook = (ast_lib.AbbreviateNodes if 'abbrev-' in opts.ast_format else None) tree = fmt.MakeTree(node, abbrev_hook=abbrev_hook) ast_f.FileHeader() fmt.PrintTree(tree, ast_f) ast_f.FileFooter() ast_f.write('\n') #util.log("Execution skipped because 'noexec' is on ") status = 0 if do_exec: _tlog('Execute(node)') status = ex.ExecuteAndRunExitTrap(node) # 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' % os.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) else: status = 0 return status
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 if opts.help: loader = util.GetResourceLoader() 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 # TODO: This should be in interactive mode only? builtin.RegisterSigIntHandler() 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 pool = alloc.Pool() arena = pool.NewArena() # 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, readline) builtin.SetExecOpts(exec_opts, opts.opt_changes) aliases = {} # feedback between runtime and parser parse_ctx = parse_lib.ParseContext(arena, aliases) # For main_loop # Three ParseContext instances SHARE aliases. TODO: Complete aliases. comp_arena = pool.NewArena() comp_arena.PushSource('<completion>') trail1 = parse_lib.Trail() comp_ctx = parse_lib.ParseContext(comp_arena, aliases, trail=trail1) hist_arena = pool.NewArena() hist_arena.PushSource('<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() if opts.debug_file: debug_f = util.DebugFile(fd_state.Open(opts.debug_file, mode='w')) else: debug_f = util.NullDebugFile() exec_deps.debug_f = debug_f debug_f.log('Debug file is %s', opts.debug_file) splitter = split.SplitContext(mem) exec_deps.splitter = splitter # Controlled by env variable, flag, or hook? exec_deps.dumper = dev.CrashDumper( posix.environ.get('OSH_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 # TODO: Separate comp_state and comp_lookup. comp_state = completion.State() comp_lookup = completion.Lookup() builtins = { # Lookup builtin_e.HISTORY: builtin.History(readline), builtin_e.COMPOPT: builtin_comp.CompOpt(comp_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 = cmd_exec.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) # Add some builtins that depend on the executor! complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) # used later builtins[builtin_e.COMPLETE] = complete_builtin builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(spec_builder) 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 = ui.PromptEvaluator(lang, arena, 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 readline is None. hist_ev = reader.HistoryEvaluator(readline, 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 = mem.GetVar('HOME') assert home_dir.tag == value_e.Str, home_dir rc_path = opts.rcfile or os_path.join(home_dir.s, '.config/oil', lang + 'rc') history_filename = os_path.join(home_dir.s, '.config/oil', 'history_' + lang) if opts.c is not None: arena.PushSource('<command string>') 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('<stdin -i>') # interactive shell only line_reader = reader.InteractiveLineReader(arena, prompt_ev, hist_ev) exec_opts.interactive = True else: try: script_name = arg_r.Peek() except IndexError: if sys.stdin.isatty(): arena.PushSource('<interactive>') # interactive shell only line_reader = reader.InteractiveLineReader( arena, prompt_ev, hist_ev) exec_opts.interactive = True else: arena.PushSource('<stdin>') line_reader = reader.FileLineReader(sys.stdin, arena) else: arena.PushSource(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) if exec_opts.interactive: # NOTE: We're using a different evaluator here. The completion system can # also run functions... it gets the Executor through Executor._Complete. if readline: ev = word_eval.CompletionWordEvaluator(mem, exec_opts, exec_deps, arena) progress_f = ui.StatusLine() root_comp = completion.RootCompleter(ev, mem, comp_lookup, comp_state, comp_ctx, progress_f, debug_f) _InitReadline(readline, history_filename, root_comp, debug_f) _InitDefaultCompletions(ex, complete_builtin, comp_lookup) # NOTE: Call this AFTER _InitDefaultCompletions. SourceStartupFile(rc_path, lang, parse_ctx, ex) return main_loop.Interactive(opts, ex, c_parser, arena) # TODO: Remove this after removing it from benchmarks/osh-runtime. It's no # longer relevant with main_loop. if opts.parser_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.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) nodes_out = [] if exec_opts.noexec else None _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: 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: This doesn't cause any spec tests to fail, but it could. if posix.environ.get('ASDL_TYPE_CHECK'): log('NOTE: Performed %d ASDL_TYPE_CHECKs.', runtime.NUM_TYPE_CHECKS) # NOTE: We haven't closed the file opened with fd_state.Open return status
def OshMain(argv0, argv, login_shell): 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 if opts.help: loader = util.GetResourceLoader() builtin.Help(['osh-usage'], 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 # TODO: This should be in interactive mode only? builtin.RegisterSigIntHandler() 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 pool = alloc.Pool() arena = pool.NewArena() # 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 = {} comp_lookup = completion.CompletionLookup() fd_state = process.FdState() exec_opts = state.ExecOpts(mem, readline) builtin.SetExecOpts(exec_opts, opts.opt_changes) aliases = {} # feedback between runtime and parser parse_ctx = parse_lib.ParseContext(arena, aliases) if opts.debug_file: debug_f = util.DebugFile(fd_state.Open(opts.debug_file, mode='w')) else: debug_f = util.NullDebugFile() debug_f.log('Debug file is %s', opts.debug_file) # Controlled by env variable, flag, or hook? dumper = dev.CrashDumper(posix.environ.get('OSH_CRASH_DUMP_DIR', '')) if opts.xtrace_to_debug_file: trace_f = debug_f else: trace_f = util.DebugFile(sys.stderr) devtools = dev.DevTools(dumper, debug_f, trace_f) ex = cmd_exec.Executor(mem, fd_state, funcs, comp_lookup, exec_opts, parse_ctx, devtools) # NOTE: The rc file can contain both commands and functions... ideally we # would only want to save nodes/lines for the functions. try: rc_path = 'oilrc' arena.PushSource(rc_path) with open(rc_path) as f: rc_line_reader = reader.FileLineReader(f, arena) _, rc_c_parser = parse_ctx.MakeParser(rc_line_reader) try: status = main_loop.Batch(ex, rc_c_parser, arena) finally: arena.PopSource() except IOError as e: if e.errno != errno.ENOENT: raise # Needed in non-interactive shells for @P prompt = ui.Prompt(arena, parse_ctx, ex) ui.PROMPT = prompt if opts.c is not None: arena.PushSource('<command string>') 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('<stdin -i>') line_reader = reader.InteractiveLineReader(arena, prompt) exec_opts.interactive = True else: try: script_name = arg_r.Peek() except IndexError: if sys.stdin.isatty(): arena.PushSource('<interactive>') line_reader = reader.InteractiveLineReader(arena, prompt) exec_opts.interactive = True else: arena.PushSource('<stdin>') line_reader = reader.FileLineReader(sys.stdin, arena) else: arena.PushSource(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. w_parser, c_parser = parse_ctx.MakeParser(line_reader) if exec_opts.interactive: # NOTE: We're using a different evaluator here. The completion system can # also run functions... it gets the Executor through Executor._Complete. if HAVE_READLINE: splitter = legacy.SplitContext(mem) # TODO: share with executor. ev = word_eval.CompletionWordEvaluator(mem, exec_opts, splitter, arena) progress_f = ui.StatusLine() var_action = completion.VariablesActionInternal(ex.mem) root_comp = completion.RootCompleter(ev, comp_lookup, var_action, parse_ctx, progress_f, debug_f) completion.Init(readline, root_comp, debug_f) _InitDefaultCompletions(ex, comp_lookup) return main_loop.Interactive(opts, ex, c_parser, arena) # TODO: Remove this after removing it from benchmarks/osh-runtime. It's no # longer relevant with main_loop. if opts.parser_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.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) nodes_out = [] if exec_opts.noexec else None _tlog('Execute(node)') status = main_loop.Batch(ex, c_parser, arena, nodes_out=nodes_out) if nodes_out is not None: 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
def OpyCommandMain(argv): """Dispatch to the right action.""" # TODO: Use core/args. #opts, argv = Options().parse_args(argv) try: action = argv[0] except IndexError: raise args.UsageError('opy: Missing required subcommand.') if action in ('parse', 'compile', 'eval', 'repl', 'run'): loader = util.GetResourceLoader() f = loader.open(PICKLE_REL_PATH) gr = grammar.Grammar() gr.load(f) f.close() # 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 tr = transformer.Transformer() else: # e.g. pgen2 doesn't use any of these. Maybe we should make a different # tool. gr = None symbols = None tr = None # # Actions # if action == 'pgen2': grammar_path = argv[1] pickle_path = argv[2] WriteGrammar(grammar_path, pickle_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[1] 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 == 'parse': py_path = argv[1] with open(py_path) as f: tokens = tokenize.generate_tokens(f.readline) p = parse.Parser(gr, convert=skeleton.py2st) parse_tree = driver.PushTokens(p, tokens, gr.symbol2number['file_input']) if isinstance(parse_tree, tuple): n = CountTupleTree(parse_tree) log('COUNT %d', n) printer = TupleTreePrinter(transformer._names) printer.Print(parse_tree) else: tree.PrettyPrint(sys.stdout) log('\tChildren: %d' % len(tree.children), file=sys.stderr) elif action == 'compile': # 'opyc compile' is pgen2 + compiler2 py_path = argv[1] out_path = argv[2] with open(py_path) as f: co = skeleton.Compile(f, py_path, gr, 'file_input', 'exec') log("Compiled to %d bytes of 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 == 'eval': # Like compile, but parses to a code object and prints it py_expr = argv[1] f = cStringIO.StringIO(py_expr) co = skeleton.Compile(f, '<eval input>', gr, 'eval_input', '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 = cStringIO.StringIO(py_expr) # TODO: change this to 'single input'? Why doesn't this work? co = skeleton.Compile(f, '<REPL input>', gr, 'eval_input', 'eval') v = dis_tool.Visitor() v.show_code(co) print(eval(co)) elif action == 'dis': pyc_path = argv[1] try: report_path = argv[2] report_f = open(report_path, 'w') except IndexError: report_f = sys.stdout with open(pyc_path, 'rb') as f: # TODO: Make this a flag. #v = dis_tool.Visitor(dis_bytecode=False) v = dis_tool.Visitor() #v = dis_tool.Visitor(co_name='_parse') v.Visit(f) v.Report(report_f) elif action == 'dis-md5': pyc_paths = argv[1:] if not pyc_paths: raise args.UsageError( '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': # 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) # Compile and run, without writing pyc file py_path = argv[1] opy_argv = argv[1:] if py_path.endswith('.py'): with open(py_path) as f: co = skeleton.Compile(f, py_path, gr, 'file_input', 'exec') 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) execfile.run_code_object(co, opy_argv) else: raise args.UsageError('Invalid path %r' % py_path) else: raise args.UsageError('Invalid action %r' % action)
#!/usr/bin/env python """ arith_ast.py """ import sys from asdl import front_end from asdl import py_meta from core import util f = util.GetResourceLoader().open('asdl/arith.asdl') _asdl_module, _type_lookup = front_end.LoadSchema(f, {}) # no app_types f.close() root = sys.modules[__name__] py_meta.MakeTypes(_asdl_module, root, _type_lookup)
def OpyCommandMain(argv): """Dispatch to the right action.""" # TODO: Use core/args. opts, argv = Options().parse_args(argv) try: action = argv[0] except IndexError: raise args.UsageError('opy: Missing required subcommand.') if action in ('parse', 'compile'): loader = util.GetResourceLoader() f = loader.open(PICKLE_REL_PATH) gr = grammar.Grammar() gr.load(f) f.close() # In Python 2 code, always use from __future__ import print_function. try: del gr.keywords["print"] except KeyError: pass FILE_INPUT = gr.symbol2number['file_input'] symbols = Symbols(gr) pytree.Init(symbols) # for type_repr() pretty printing transformer.Init(symbols) # for _names and other dicts else: # e.g. pgen2 doesn't use any of these. Maybe we should make a different # tool. gr = None FILE_INPUT = None symbols = None #do_glue = False do_glue = True if do_glue: # Make it a flag # Emulating parser.st structures from parsermodule.c. # They have a totuple() method, which outputs tuples like this. def py2st(gr, raw_node): type, value, context, children = raw_node # See pytree.Leaf if context: _, (lineno, column) = context else: lineno = 0 # default in Leaf column = 0 if children: return (type, ) + tuple(children) else: return (type, value, lineno, column) convert = py2st else: convert = pytree.convert dr = driver.Driver(gr, convert=convert) if action == 'pgen2': grammar_path = argv[1] pickle_path = argv[2] WriteGrammar(grammar_path, pickle_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() n = CountTupleTree(tree) log('COUNT %d', n) printer = TupleTreePrinter(HostStdlibNames()) printer.Print(tree) elif action == 'parse': py_path = argv[1] with open(py_path) as f: tokens = tokenize.generate_tokens(f.readline) tree = dr.parse_tokens(tokens, start_symbol=FILE_INPUT) if isinstance(tree, tuple): n = CountTupleTree(tree) log('COUNT %d', n) printer = TupleTreePrinter(transformer._names) printer.Print(tree) else: tree.PrettyPrint(sys.stdout) log('\tChildren: %d' % len(tree.children), file=sys.stderr) elif action == 'compile': # 'opy compile' is pgen2 + compiler2 py_path = argv[1] out_path = argv[2] if do_glue: py_parser = Pgen2PythonParser(dr, FILE_INPUT) printer = TupleTreePrinter(transformer._names) tr = transformer.Pgen2Transformer(py_parser, printer) else: tr = transformer.Transformer() with open(py_path) as f: contents = f.read() co = pycodegen.compile(contents, py_path, 'exec', transformer=tr) log("Code length: %d", len(co.co_code)) # Write the .pyc file with open(out_path, 'wb') as out_f: h = pycodegen.getPycHeader(py_path) out_f.write(h) marshal.dump(co, out_f) elif action == 'dis': pyc_path = argv[1] try: report_path = argv[2] report_f = open(report_path, 'w') except IndexError: report_f = sys.stdout with open(pyc_path, 'rb') as f: # TODO: Make this a flag. #v = inspect_pyc.Visitor(dis_bytecode=False) v = inspect_pyc.Visitor() v.Visit(f) v.Report(report_f) elif action == 'dis-md5': pyc_paths = argv[1:] if not pyc_paths: raise args.UsageError( '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)) # NOTE: Unused elif action == 'old-compile': py_path = argv[1] out_path = argv[2] if do_glue: py_parser = Pgen2PythonParser(dr, FILE_INPUT) printer = TupleTreePrinter(transformer._names) tr = transformer.Pgen2Transformer(py_parser, printer) else: tr = transformer.Transformer() f = open(py_path) contents = f.read() co = pycodegen.compile(contents, py_path, 'exec', transformer=tr) log("Code length: %d", len(co.co_code)) # Write the .pyc file with open(out_path, 'wb') as out_f: h = pycodegen.getPycHeader(py_path) out_f.write(h) marshal.dump(co, out_f) # TODO: Not used elif action == 'compile2': in_path = argv[1] out_path = argv[2] from compiler2 import pycodegen as pycodegen2 from misc import stdlib_compile stdlib_compile.compileAndWrite(in_path, out_path, pycodegen2.compile) elif action == 'run': # 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) # Compile and run, without writing pyc file py_path = argv[1] opy_argv = argv[1:] if py_path.endswith('.py'): py_parser = Pgen2PythonParser(dr, FILE_INPUT) printer = TupleTreePrinter(transformer._names) tr = transformer.Pgen2Transformer(py_parser, printer) with open(py_path) as f: contents = f.read() co = pycodegen.compile(contents, py_path, 'exec', transformer=tr) #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) #execfile.run_code_object(co, opy_argv) else: raise args.UsageError('Invalid path %r' % py_path) else: raise args.UsageError('Invalid action %r' % action)
def OshMain(argv, login_shell): spec = args.FlagsAndOptions() spec.ShortFlag('-c', args.Str, quit_parsing_flags=True) # command string spec.ShortFlag('-i') # interactive # TODO: -h too spec.LongFlag('--help') spec.LongFlag('--version') spec.LongFlag('--ast-format', ['text', 'abbrev-text', 'html', 'abbrev-html', 'oheap'], default='abbrev-text') spec.LongFlag('--show-ast') # execute and show spec.LongFlag('--fix') spec.LongFlag('--debug-spans') spec.LongFlag('--print-status') spec.LongFlag( '--trace', ['cmd-parse', 'word-parse', 'lexer']) # NOTE: can only trace one now spec.LongFlag('--hijack-shebang') builtin.AddOptionsToArgSpec(spec) try: opts, opt_index = spec.Parse(argv) except args.UsageError as e: util.usage(str(e)) return 2 if opts.help: loader = util.GetResourceLoader() # TOOD: Use Global builtin.Help(['osh-usage'], 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 trace_state = util.TraceState() if 'cmd-parse' == opts.trace: util.WrapMethods(cmd_parse.CommandParser, trace_state) if 'word-parse' == opts.trace: util.WrapMethods(word_parse.WordParser, trace_state) if 'lexer' == opts.trace: util.WrapMethods(lexer.Lexer, trace_state) if opt_index == len(argv): dollar0 = sys.argv[0] # e.g. bin/osh else: dollar0 = argv[opt_index] # the script name, or the arg after -c # TODO: Create a --parse action or 'osh parse' or 'oil osh-parse' # osh-fix # It uses a different memory-management model. It's a batch program and not # an interactive program. pool = alloc.Pool() arena = pool.NewArena() # TODO: Maybe wrap this initialization sequence up in an oil_State, like # lua_State. status_lines = ui.MakeStatusLines() mem = state.Mem(dollar0, argv[opt_index + 1:], os.environ) funcs = {} # Passed to Executor for 'complete', and passed to completion.Init if completion: comp_lookup = completion.CompletionLookup() else: # TODO: NullLookup? comp_lookup = None exec_opts = state.ExecOpts() builtin.SetExecOpts(exec_opts, opts.opt_changes) # TODO: How to get a handle to initialized builtins here? # tokens.py has it. I think you just make a separate table, with # metaprogramming. ex = cmd_exec.Executor(mem, status_lines, funcs, completion, comp_lookup, exec_opts, arena) # NOTE: The rc file can contain both commands and functions... ideally we # would only want to save nodes/lines for the functions. try: rc_path = 'oilrc' arena.PushSource(rc_path) with open(rc_path) as f: rc_line_reader = reader.FileLineReader(f, arena=arena) _, rc_c_parser = parse_lib.MakeParser(rc_line_reader, arena) try: rc_node = rc_c_parser.ParseWholeFile() if not rc_node: # TODO: Error should return a token, and then the token should have a # arena index, and then look that up in the arena. err = rc_c_parser.Error() ui.PrintErrorStack(err, arena, sys.stderr) return 2 # parse error is code 2 finally: arena.PopSource() status = ex.Execute(rc_node) #print('oilrc:', status, cflow, file=sys.stderr) # Ignore bad status? except IOError as e: if e.errno != errno.ENOENT: raise if opts.c is not None: arena.PushSource('<command string>') line_reader = reader.StringLineReader(opts.c, arena=arena) interactive = False elif opts.i: # force interactive arena.PushSource('<stdin -i>') line_reader = reader.InteractiveLineReader(OSH_PS1, arena=arena) interactive = True else: try: script_name = argv[opt_index] except IndexError: if sys.stdin.isatty(): arena.PushSource('<interactive>') line_reader = reader.InteractiveLineReader(OSH_PS1, arena=arena) interactive = True else: arena.PushSource('<stdin>') line_reader = reader.FileLineReader(sys.stdin, arena=arena) interactive = False else: arena.PushSource(script_name) # TODO: Does this open file descriptor need to be moved beyond 3..9 ? # Yes! See dash input.c setinputfile. It calls savefd(). # TODO: It also needs to be closed later. try: f = open(script_name) except IOError as e: util.error("Couldn't open %r: %s", script_name, os.strerror(e.errno)) return 1 line_reader = reader.FileLineReader(f, arena=arena) interactive = False # TODO: assert arena.NumSourcePaths() == 1 # TODO: .rc file needs its own arena. w_parser, c_parser = parse_lib.MakeParser(line_reader, arena) if interactive: # NOTE: We're using a different evaluator here. The completion system can # also run functions... it gets the Executor through Executor._Complete. if HAVE_READLINE: ev = word_eval.CompletionWordEvaluator(mem, exec_opts) status_out = completion.StatusOutput(status_lines, exec_opts) completion.Init(builtin.BUILTIN_DEF, mem, funcs, comp_lookup, status_out, ev) # TODO: Could instantiate "printer" instead of showing ops InteractiveLoop(opts, ex, c_parser, w_parser, line_reader) status = 0 # TODO: set code else: # Parse the whole thing up front #print('Parsing file') tlog('ParseWholeFile') # TODO: Do I need ParseAndEvalLoop? How is it different than # InteractiveLoop? try: node = c_parser.ParseWholeFile() except util.ParseError as e: ui.PrettyPrintError(e, arena, sys.stderr) print('parse error: %s' % e.UserErrorString(), file=sys.stderr) return 2 else: # TODO: Remove this older form of error handling. if not node: err = c_parser.Error() ui.PrintErrorStack(err, arena, sys.stderr) return 2 # parse error is code 2 do_exec = True if opts.fix: osh2oil.PrintAsOil(arena, node, opts.debug_spans) do_exec = False if exec_opts.noexec: do_exec = False if exec_opts.noexec or opts.show_ast: # -n shows the AST if opts.ast_format == 'oheap': # TODO: Make this a separate flag? if sys.stdout.isatty(): raise RuntimeError( 'ERROR: Not dumping binary data to a TTY.') f = sys.stdout enc = encode.Params() out = encode.BinOutput(f) encode.EncodeRoot(node, enc, out) else: # text output f = sys.stdout if opts.ast_format in ('text', 'abbrev-text'): ast_f = fmt.DetectConsoleOutput(f) elif opts.ast_format in ('html', 'abbrev-html'): ast_f = fmt.HtmlOutput(f) else: raise AssertionError abbrev_hook = (ast.AbbreviateNodes if 'abbrev-' in opts.ast_format else None) tree = fmt.MakeTree(node, abbrev_hook=abbrev_hook) ast_f.FileHeader() fmt.PrintTree(tree, ast_f) ast_f.FileFooter() ast_f.write('\n') #util.log("Execution skipped because 'noexec' is on ") status = 0 if do_exec: tlog('Execute(node)') status = ex.Execute(node) else: status = 0 return status
from core.id_kind import Id def _LoadSchema(f): module = asdl.parse(f) app_types = {'id': asdl.UserType(Id)} type_lookup = asdl.ResolveTypes(module, app_types) # Check for type errors if not asdl.check(module, app_types): raise AssertionError('ASDL file is invalid') return module, type_lookup f = util.GetResourceLoader().open('core/runtime.asdl') root = sys.modules[__name__] module, type_lookup = _LoadSchema(f) if 0: py_meta.MakeTypes(module, root, type_lookup) else: # Exported for the generated code to use TYPE_LOOKUP = type_lookup # Get the types from elsewhere from _devbuild.gen import runtime_asdl py_meta.AssignTypes(runtime_asdl, root) f.close()
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 = util.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 pool = alloc.Pool() arena = pool.NewArena() # 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 = pool.NewArena() comp_arena.PushSource('<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 = pool.NewArena() hist_arena.PushSource('<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 = cmd_exec.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, arena, 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 = mem.GetVar('HOME') assert home_dir.tag == value_e.Str, home_dir rc_path = opts.rcfile or os_path.join(home_dir.s, '.config/oil', lang + 'rc') history_filename = os_path.join(home_dir.s, '.config/oil', 'history_' + lang) if opts.c is not None: arena.PushSource('<command string>') 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('<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('<interactive>') # interactive shell only line_reader = reader.InteractiveLineReader( arena, prompt_ev, hist_ev, line_input, prompt_state) exec_opts.interactive = True else: arena.PushSource('<stdin>') line_reader = reader.FileLineReader(sys.stdin, arena) else: arena.PushSource(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) 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) # TODO: This doesn't do anything interesting. # - Remove the column from osh-runtime, since it doesn't work with main_loop. # - http://www.oilshell.org/release/0.6.pre6/benchmarks.wwz/osh-runtime/ # - Move it to right before ui.PrintAst(), so we can use it in -n mode. # This benchmark has been broken since 0.6.pre4. # http://www.oilshell.org/release/0.6.pre3/benchmarks.wwz/osh-parser/ if opts.parser_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.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) nodes_out = [] if exec_opts.noexec else None _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: 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
# Do NOT create any any more instances of them! Always used IdInstance(). # TODO: Fold Id into ASDL, which will enforce uniqueness? _ID_INSTANCES = {} # int -> Id def IdInstance(i): return _ID_INSTANCES[i] # # Instantiate osh/types.asdl # f = util.GetResourceLoader().open('osh/types.asdl') _asdl_module, _type_lookup = asdl.LoadSchema(f, {}) # no app_types types = _AsdlModule() if 0: py_meta.MakeTypes(_asdl_module, types, _type_lookup) else: # Exported for the generated code to use TYPES_TYPE_LOOKUP = _type_lookup # Get the types from elsewhere from _devbuild.gen import types_asdl py_meta.AssignTypes(types_asdl, types) f.close()
def OpyCommandMain(argv): """Dispatch to the right action.""" # TODO: Use core/args. #opts, argv = Options().parse_args(argv) try: action = argv[0] except IndexError: raise args.UsageError('opy: Missing required subcommand.') argv = argv[1:] # TODO: Should I do input.ReadRequiredArg()? # That will shift the input. if action in ('parse', 'compile', 'dis', 'cfg', 'compile-ovm', 'eval', 'repl', 'run', 'run-ovm'): loader = util.GetResourceLoader() f = loader.open(PICKLE_REL_PATH) gr = grammar.Grammar() gr.load(f) f.close() # 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 = args.OilFlags() 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] pickle_path = argv[1] WriteGrammar(grammar_path, pickle_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 == 'parse': py_path = argv[0] with open(py_path) as f: tokens = tokenize.generate_tokens(f.readline) p = parse.Parser(gr, convert=skeleton.py2st) parse_tree = driver.PushTokens(p, tokens, gr.symbol2number['file_input']) if isinstance(parse_tree, tuple): n = CountTupleTree(parse_tree) log('COUNT %d', n) printer = TupleTreePrinter(transformer._names) printer.Print(parse_tree) else: tree.PrettyPrint(sys.stdout) log('\tChildren: %d' % len(tree.children), file=sys.stderr) elif action == 'cfg': # output Control Flow Graph opt, i = compile_spec.Parse(argv) py_path = argv[i] with open(py_path) as f: graph = compiler.Compile(f, opt, 'exec', return_cfg=True) print(graph) elif action == 'compile': # 'opyc compile' is pgen2 + compiler2 # spec.Arg('action', ['foo', 'bar']) # But that leads to some duplication. opt, i = compile_spec.Parse(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': opt, i = compile_spec.Parse(argv) py_path = argv[i] out_path = argv[i + 1] # Compile to OVM bytecode with open(py_path) as f: co = compiler.Compile(f, opt, 'ovm') 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.Parse(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.Parse(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 args.UsageError( '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.Parse(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 args.UsageError('Invalid path %r' % py_path) elif action == 'run-ovm': # Compile and run, without writing pyc file opt, i = compile_spec.Parse(argv) py_path = argv[1] opy_argv = argv[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 args.UsageError('Invalid path %r' % py_path) else: raise args.UsageError('Invalid action %r' % action)
node.unnamed_fields.append(out_val) def PrettyPrint(node, f=sys.stdout): ast_f = fmt.DetectConsoleOutput(f) tree = fmt.MakeTree(node, AbbreviateNodes) fmt.PrintTree(tree, ast_f) f.write('\n') def _ParseAndMakeTypes(f, root): # TODO: A better syntax for this might be: # # id = external # # in osh.asdl. Then we can show an error if it's not provided. app_types = {'id': asdl.UserType(Id)} module = asdl.parse(f) # Check for type errors if not asdl.check(module, app_types): raise AssertionError('ASDL file is invalid') py_meta.MakeTypes(module, root, app_types) f = util.GetResourceLoader().open('osh/osh.asdl') root = sys.modules[__name__] _ParseAndMakeTypes(f, root) f.close()