def InitOilParser(code_str, arena=None): # NOTE: aliases don't exist in the Oil parser? arena = arena or MakeArena('<cmd_exec_test.py>') parse_ctx = parse_lib.ParseContext(arena, {}) line_reader, _ = InitLexer(code_str, arena) c_parser = parse_ctx.MakeOilParser(line_reader) return arena, c_parser
def InitExecutor(parse_ctx=None, comp_lookup=None, arena=None, mem=None): if parse_ctx: arena = parse_ctx.arena else: arena or MakeArena('<InitExecutor>') parse_ctx = parse_lib.ParseContext(arena, {}) mem = mem or state.Mem('', [], {}, arena) fd_state = process.FdState() funcs = {} comp_state = completion.State() comp_lookup = comp_lookup or completion.Lookup() readline = None # simulate not having it builtins = { # Lookup builtin_e.HISTORY: builtin.History(readline), builtin_e.COMPOPT: builtin_comp.CompOpt(comp_state), builtin_e.COMPADJUST: builtin_comp.CompAdjust(mem), } # For the tests, we do not use 'readline'. exec_opts = state.ExecOpts(mem, None) debug_f = util.DebugFile(sys.stderr) exec_deps = cmd_exec.Deps() exec_deps.dumper = dev.CrashDumper('') exec_deps.debug_f = debug_f exec_deps.trace_f = debug_f splitter = split.SplitContext(mem) exec_deps.splitter = splitter word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, exec_deps, arena) exec_deps.word_ev = word_ev arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, word_ev, arena) exec_deps.arith_ev = arith_ev word_ev.arith_ev = arith_ev # Circular bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, word_ev, arena) exec_deps.bool_ev = bool_ev tracer = cmd_exec.Tracer(parse_ctx, exec_opts, mem, word_ev, debug_f) exec_deps.tracer = tracer ex = cmd_exec.Executor(mem, fd_state, funcs, builtins, exec_opts, parse_ctx, exec_deps) spec_builder = builtin_comp.SpecBuilder(ex, parse_ctx, word_ev, splitter, comp_lookup) # Add some builtins that depend on the executor! complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) # used later builtins[builtin_e.COMPLETE] = complete_builtin builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(spec_builder) return ex
def InitWordParser(code_str, arena=None): arena = arena or MakeArena('<test_lib>') parse_ctx = parse_lib.ParseContext(arena, {}) line_reader, _ = InitLexer(code_str, arena) c_parser = parse_ctx.MakeOshParser(line_reader) # Hack return c_parser.w_parser
def InitCommandParser(code_str, arena=None): arena = arena or MakeArena('<test_lib>') parse_opts = parse_lib.OilParseOptions() parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, None) line_reader, _ = InitLexer(code_str, arena) c_parser = parse_ctx.MakeOshParser(line_reader) return c_parser
def main(argv): # type: (List[str]) -> int arena = alloc.Arena() arena.PushSource(source.Stdin('')) parse_opts = parse_lib.OilParseOptions() # Dummy value; not respecting aliases! aliases = {} # type: Dict[str, Any] # parse `` and a[x+1]=bar differently loader = pyutil.GetResourceLoader() oil_grammar = meta.LoadOilGrammar(loader) parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar, one_pass_parse=True) line_reader = reader.FileLineReader(mylib.Stdin(), arena) c_parser = parse_ctx.MakeOshParser(line_reader) try: node = main_loop.ParseWholeFile(c_parser) except util.ParseError as e: ui.PrettyPrintError(e, arena) return 2 assert node is not None tree = node.AbbreviatedTree() #tree = node.PrettyTree() ast_f = fmt.DetectConsoleOutput(mylib.Stdout()) fmt.PrintTree(tree, ast_f) ast_f.write('\n') return 0
def main(argv): # type: (List[str]) -> int pool = alloc.Pool() arena = pool.NewArena() arena.PushSource('<stdin>') line_reader = reader.FileLineReader(sys.stdin, arena) # Dummy value; not respecting aliases! aliases = {} # type: Dict[str, Any] # parse `` and a[x+1]=bar differently parse_ctx = parse_lib.ParseContext(arena, aliases, one_pass_parse=True) c_parser = parse_ctx.MakeOshParser(line_reader) try: node = main_loop.ParseWholeFile(c_parser) except util.ParseError as e: ui.PrettyPrintError(e, arena) return 2 assert node is not None tree = node.AbbreviatedTree() #tree = node.PrettyTree() ast_f = fmt.DetectConsoleOutput(sys.stdout) fmt.PrintTree(tree, ast_f) ast_f.write('\n') return 0
def testNoInfiniteLoop(self): # This was ONE place where we got an infinite loop. with open('testdata/completion/return-124.bash') as f: code_str = f.read() trail = parse_lib.Trail() arena = test_lib.MakeArena('<completion_test.py>') parse_opts = parse_lib.OilParseOptions() parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, None, trail=trail) comp_lookup = completion.Lookup() ex = test_lib.EvalCode(code_str, parse_ctx, comp_lookup=comp_lookup) r = _MakeRootCompleter(parse_ctx=parse_ctx, comp_lookup=comp_lookup) m = list(r.Matches(MockApi('bad '))) self.assertEqual([], sorted(m)) # Error: spec not changed m = list(r.Matches(MockApi('both '))) self.assertEqual([], sorted(m)) # Redefines completions m = list(r.Matches(MockApi('both2 '))) self.assertEqual(['both2 b1 ', 'both2 b2 '], sorted(m))
def _MakeHistoryEvaluator(history_items): arena = test_lib.MakeArena('<reader_test.py>') trail = parse_lib.Trail() parse_ctx = parse_lib.ParseContext(arena, {}, trail=trail) debug_f = util.DebugFile(sys.stdout) readline = _MockReadlineHistory(history_items) return reader.HistoryEvaluator(readline, parse_ctx, debug_f)
def InitExecutor(comp_state=None, arena=None, mem=None): arena = arena or MakeArena('<InitExecutor>') mem = mem or state.Mem('', [], {}, arena) fd_state = process.FdState() funcs = {} comp_state = comp_state or completion.State() readline = None # simulate not having it builtins = { # Lookup builtin_e.HISTORY: builtin.History(readline), builtin_e.COMPOPT: builtin_comp.CompOpt(comp_state), builtin_e.COMPADJUST: builtin_comp.CompAdjust(mem), } # For the tests, we do not use 'readline'. exec_opts = state.ExecOpts(mem, None) parse_ctx = parse_lib.ParseContext(arena, {}) debug_f = util.DebugFile(sys.stderr) devtools = dev.DevTools(dev.CrashDumper(''), debug_f, debug_f) ex = cmd_exec.Executor(mem, fd_state, funcs, builtins, exec_opts, parse_ctx, devtools) # Add some builtins that depend on the executor! complete_builtin = builtin_comp.Complete(ex, comp_state) # used later builtins[builtin_e.COMPLETE] = complete_builtin builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(ex) return ex
def testCompletesAliases(self): # I put some aliases in this file. with open('testdata/completion/osh-unit.bash') as f: code_str = f.read() trail = parse_lib.Trail() arena = alloc.SideArena('<completion_test.py>') parse_ctx = parse_lib.ParseContext(arena, {}, trail=trail) comp_lookup = completion.Lookup() ex = test_lib.EvalCode(code_str, parse_ctx, comp_lookup=comp_lookup) r = _MakeRootCompleter(parse_ctx=parse_ctx, comp_lookup=comp_lookup) # The original command m = list(r.Matches(MockApi('ls '))) self.assertEqual(['ls one ', 'ls two '], sorted(m)) # Alias for the command m = list(r.Matches(MockApi('ll '))) self.assertEqual(['ll one ', 'll two '], sorted(m)) # DOUBLE alias expansion goes back to original m = list(r.Matches(MockApi('ll_classify '))) self.assertEqual(['ll_classify one ', 'll_classify two '], sorted(m)) # Trailing space m = list(r.Matches(MockApi('ll_trailing '))) self.assertEqual(['ll_trailing one ', 'll_trailing two '], sorted(m)) # It should NOT clobber completio registered for aliases m = list(r.Matches(MockApi('ll_own_completion '))) self.assertEqual( ['ll_own_completion own ', 'll_own_completion words '], sorted(m))
def testRunsUserDefinedFunctions(self): # This is here because it's hard to test readline with the spec tests. with open('testdata/completion/osh-unit.bash') as f: code_str = f.read() trail = parse_lib.Trail() arena = test_lib.MakeArena('<completion_test.py>') parse_opts = parse_lib.OilParseOptions() parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, None, trail=trail) comp_lookup = completion.Lookup() ex = test_lib.EvalCode(code_str, parse_ctx, comp_lookup=comp_lookup) r = _MakeRootCompleter(comp_lookup=comp_lookup) # By default, we get a space on the end. m = list(r.Matches(MockApi('mywords t'))) self.assertEqual(['mywords three ', 'mywords two '], sorted(m)) # No space m = list(r.Matches(MockApi('mywords_nospace t'))) self.assertEqual(['mywords_nospace three', 'mywords_nospace two'], sorted(m)) # Filtered out two and bin m = list(r.Matches(MockApi('flagX '))) self.assertEqual(['flagX one ', 'flagX three '], sorted(m)) # Filter out everything EXCEPT two and bin m = list(r.Matches(MockApi('flagX_bang '))) self.assertEqual(['flagX_bang bin ', 'flagX_bang two '], sorted(m)) # -X with -P m = list(r.Matches(MockApi('flagX_prefix '))) self.assertEqual(['flagX_prefix __one ', 'flagX_prefix __three '], sorted(m)) # -P with plusdirs m = list(r.Matches(MockApi('prefix_plusdirs b'))) self.assertEqual([ 'prefix_plusdirs __bin ', 'prefix_plusdirs benchmarks/', 'prefix_plusdirs bin/', 'prefix_plusdirs build/' ], sorted(m)) # -X with plusdirs. We're filtering out bin/, and then it's added back by # plusdirs. The filter doesn't kill it. m = list(r.Matches(MockApi('flagX_plusdirs b'))) self.assertEqual([ 'flagX_plusdirs benchmarks/', 'flagX_plusdirs bin/', 'flagX_plusdirs build/' ], sorted(m)) # -P with dirnames. -P is NOT respected. m = list(r.Matches(MockApi('prefix_dirnames b'))) self.assertEqual([ 'prefix_dirnames benchmarks/', 'prefix_dirnames bin/', 'prefix_dirnames build/' ], sorted(m))
def InitParseContext(arena=None, oil_grammar=None, aliases=None): arena = arena or MakeArena('<test_lib>') if aliases is None: aliases = {} opt_array = [False] * option_i.ARRAY_SIZE parse_opts = optview.Parse(opt_array) parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) return parse_ctx
def _MakeHistoryEvaluator(history_items): arena = test_lib.MakeArena('<reader_test.py>') parse_opts = parse_lib.OilParseOptions() parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, None) parse_ctx.Init_Trail(parse_lib.Trail()) debug_f = util.DebugFile(sys.stdout) readline = _MockReadlineHistory(history_items) return history.Evaluator(readline, parse_ctx, debug_f)
def __init__(self, script_path=None): # generally, defer work until we know the script loaded with (open(script_path) if script_path else sys.stdin) as script: arena = alloc.Arena() parse_ctx = parse_lib.ParseContext( arena=arena, parse_opts=optview.Parse(NO_OPTIONS), aliases={}, # dummy oil_grammar=None, ) parse_ctx.Init_OnePassParse(True) if script_path: # TODO: is there a real difference between using mainfile and # sourcedfile? (this gets re-used for sourced scripts) arena.PushSource(source.MainFile(script_path)) else: arena.PushSource(source.Stdin()) try: node = main_loop.ParseWholeFile( self._make_parser(parse_ctx, script, arena)) except error.Parse as e: ui.PrettyPrintError(e, arena) raise assert node is not None # actually initialize self.arena = arena # TODO: not certain we don't want more, but minimize for now self.aliases = set() self.builtins = defaultdict(list) self.commands = defaultdict(list) self.sources = defaultdict(list) self.funcs_defined = set() self.resolved_commands = dict() self.resolved_functions = dict() self.resolved_aliases = dict() self.resolved_source = dict() self.parsed_source = dict() self.unresolved_commands = set() # unresolved functions doesn't make sense because we can't disambiguate an unresolved function from an unresolved external command... self.unresolved_source = set() self.word_obs = dict() # "resolve" try: self.Visit(node) self.resolve_records() except ResolutionError as e: e.print_if_needed() raise
def InitWordParser(word_str, oil_at=False, arena=None): arena = arena or MakeArena('<test_lib>') parse_opts = parse_lib.OilParseOptions() parse_opts.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
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)
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
def InitParseContext(arena=None, oil_grammar=None, aliases=None, parse_opts=None): arena = arena or MakeArena('<test_lib>') if aliases is None: aliases = {} mem = state.Mem('', [], arena, []) if parse_opts is None: parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, None) parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) return parse_ctx
def main(argv): init_code = ' echo hi >&2 ' arena = test_lib.MakeArena('<InitCompletionTest>') parse_ctx = parse_lib.ParseContext(arena, {}) mem = state.Mem('', [], {}, arena) comp_lookup = completion.Lookup() ex = test_lib.EvalCode(init_code, parse_ctx, comp_lookup=comp_lookup, mem=mem) print('hi')
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
def _MakeRootCompleter(comp_state=None): comp_state = comp_state or completion.State() ev = test_lib.MakeTestEvaluator() pool = alloc.Pool() arena = pool.NewArena() arena.PushSource('<_MakeRootCompleter>') trail = parse_lib.Trail() parse_ctx = parse_lib.ParseContext(arena, {}, trail=trail) if 0: # enable for details debug_f = util.DebugFile(sys.stdout) else: debug_f = util.NullDebugFile() progress_f = ui.TestStatusLine() return completion.RootCompleter(ev, comp_state, mem, parse_ctx, progress_f, debug_f)
def _Detect(test, word_str, expected): # TODO: This function could be moved to test_lib. log('-'*80) w = word_parse_test._assertReadWord(test, word_str) actual = word_.DetectShAssignment(w) left_token, close_token, part_offset = actual expected_left, expected_close, expected_part_offset = expected print(left_token, close_token, part_offset) print() if expected_left is None: test.assertEqual(None, left_token) else: test.assertEqual(expected_left, left_token.id) if expected_close is None: test.assertEqual(None, close_token) else: test.assertEqual(expected_left, left_token.id) test.assertEqual(expected_part_offset, part_offset) arena = test_lib.MakeArena('word_test.py') parse_opts = parse_lib.OilParseOptions() parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, None) if left_token and left_token.id in (Id.Lit_VarLike, Id.Lit_ArrayLhsOpen): more_env = [] preparsed = (left_token, close_token, part_offset, w) try: cmd_parse._AppendMoreEnv([preparsed], more_env) except Exception as e: log('Error: %s', e) else: log('more_env: %s', more_env) try: assign_pair = cmd_parse._MakeAssignPair(parse_ctx, preparsed) except Exception as e: log('Error: %s', e) else: log('assign_pair: %s', assign_pair)
def _MakeRootCompleter(parse_ctx=None, comp_lookup=None): #comp_state = comp_state or completion.State() compopt_state = completion.OptionState() comp_ui_state = comp_ui.State() comp_lookup = comp_lookup or completion.Lookup() ev = test_lib.MakeTestEvaluator() if not parse_ctx: arena = alloc.Arena() arena.PushSource(source.MainFile('<_MakeRootCompleter>')) parse_opts = parse_lib.OilParseOptions() parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, None) parse_ctx.Init_Trail(parse_lib.Trail()) parse_ctx.Init_OnePassParse(True) if 1: # enable for details debug_f = util.DebugFile(sys.stdout) else: debug_f = util.NullDebugFile() return completion.RootCompleter(ev, mem, comp_lookup, compopt_state, comp_ui_state, parse_ctx, debug_f)
def _MakeRootCompleter(parse_ctx=None, comp_lookup=None): #comp_state = comp_state or completion.State() compopt_state = completion.OptionState() comp_ui_state = comp_ui.State() comp_lookup = comp_lookup or completion.Lookup() ev = test_lib.MakeTestEvaluator() if not parse_ctx: pool = alloc.Pool() arena = pool.NewArena() arena.PushSource('<_MakeRootCompleter>') trail = parse_lib.Trail() parse_ctx = parse_lib.ParseContext(arena, {}, trail=trail, one_pass_parse=True) if 1: # enable for details debug_f = util.DebugFile(sys.stdout) else: debug_f = util.NullDebugFile() return completion.RootCompleter(ev, mem, comp_lookup, compopt_state, comp_ui_state, parse_ctx, debug_f)
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
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
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 args.UsageError('oshc: Missing required subcommand.') if action not in SUBCOMMANDS: raise args.UsageError('oshc: Invalid subcommand %r.' % action) try: script_name = argv[1] except IndexError: script_name = '<stdin>' f = sys.stdin else: try: f = open(script_name) except IOError as e: util.error("Couldn't open %r: %s", script_name, posix.strerror(e.errno)) return 2 pool = alloc.Pool() arena = pool.NewArena() arena.PushSource(script_name) line_reader = reader.FileLineReader(f, arena) aliases = {} # Dummy value; not respecting aliases! parse_ctx = parse_lib.ParseContext(arena, aliases) c_parser = parse_ctx.MakeOshParser(line_reader) try: node = main_loop.ParseWholeFile(c_parser) except util.ParseError as e: ui.PrettyPrintError(e, arena, sys.stderr) 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
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 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
def InitExecutor(parse_ctx=None, comp_lookup=None, arena=None, mem=None, aliases=None, ext_prog=None): if parse_ctx: arena = parse_ctx.arena parse_opts = parse_ctx.parse_opts else: arena or MakeArena('<InitExecutor>') parse_opts = parse_lib.OilParseOptions() parse_ctx = parse_lib.ParseContext(arena, parse_opts, {}, None) mem = mem or state.Mem('', [], {}, arena) exec_opts = state.ExecOpts(mem, parse_opts, None) errfmt = ui.ErrorFormatter(arena) job_state = process.JobState() fd_state = process.FdState(errfmt, job_state) funcs = {} aliases = {} if aliases is None else aliases compopt_state = completion.OptionState() comp_lookup = comp_lookup or completion.Lookup() readline = None # simulate not having it new_var = builtin_assign.NewVar(mem, funcs, errfmt) builtins = { # Lookup builtin_e.ECHO: builtin_pure.Echo(exec_opts), builtin_e.SHIFT: builtin_assign.Shift(mem), builtin_e.HISTORY: builtin.History(readline), builtin_e.COMPOPT: builtin_comp.CompOpt(compopt_state, errfmt), builtin_e.COMPADJUST: builtin_comp.CompAdjust(mem), builtin_e.ALIAS: builtin_pure.Alias(aliases, errfmt), builtin_e.UNALIAS: builtin_pure.UnAlias(aliases, errfmt), builtin_e.DECLARE: new_var, builtin_e.TYPESET: new_var, builtin_e.LOCAL: new_var, builtin_e.EXPORT: builtin_assign.Export(mem, errfmt), builtin_e.READONLY: builtin_assign.Readonly(mem, errfmt), } # For the tests, we do not use 'readline'. exec_opts = state.ExecOpts(mem, parse_opts, None) debug_f = util.DebugFile(sys.stderr) exec_deps = cmd_exec.Deps() exec_deps.search_path = state.SearchPath(mem) exec_deps.errfmt = errfmt exec_deps.trap_nodes = [] exec_deps.job_state = job_state exec_deps.waiter = process.Waiter(exec_deps.job_state, exec_opts) exec_deps.ext_prog = \ ext_prog or process.ExternalProgram('', fd_state, exec_deps.search_path, errfmt, debug_f) exec_deps.dumper = dev.CrashDumper('') exec_deps.debug_f = debug_f exec_deps.trace_f = debug_f splitter = split.SplitContext(mem) exec_deps.splitter = splitter word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, exec_deps, arena) exec_deps.word_ev = word_ev arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, word_ev, arena) exec_deps.arith_ev = arith_ev word_ev.arith_ev = arith_ev # Circular bool_ev = expr_eval.BoolEvaluator(mem, exec_opts, word_ev, arena) exec_deps.bool_ev = bool_ev tracer = dev.Tracer(parse_ctx, exec_opts, mem, word_ev, debug_f) exec_deps.tracer = tracer ex = cmd_exec.Executor(mem, fd_state, funcs, builtins, exec_opts, parse_ctx, exec_deps) spec_builder = builtin_comp.SpecBuilder(ex, parse_ctx, word_ev, splitter, comp_lookup) # Add some builtins that depend on the executor! complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) builtins[builtin_e.COMPLETE] = complete_builtin builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(spec_builder) return ex