def _UnsetVar(self, arg, spid, proc_fallback): # type: (str, int, bool) -> bool """ Returns: bool: whether the 'unset' builtin should succeed with code 0. """ arena = self.parse_ctx.arena a_parser = self.parse_ctx.MakeArithParser(arg) arena.PushSource(source.ArgvWord(spid)) try: anode = a_parser.Parse() except error.Parse as e: # show parse error ui.PrettyPrintError(e, arena) # point to word e_usage('Invalid unset expression', span_id=spid) finally: arena.PopSource() lval = self.arith_ev.EvalArithLhs(anode, spid) # Prevent attacks like these by default: # # unset -v 'A["$(echo K; rm *)"]' if not self.exec_opts.eval_unsafe_arith( ) and lval.tag_() != lvalue_e.Named: e_die( 'Expected a variable name. Expressions are allowed with shopt -s eval_unsafe_arith', span_id=spid) #log('lval %s', lval) found = False try: # not strict found = self.mem.Unset(lval, scope_e.Dynamic, False) except error.Runtime as e: # note: in bash, myreadonly=X fails, but declare myreadonly=X doens't # fail because it's a builtin. So I guess the same is true of 'unset'. e.span_id = spid ui.PrettyPrintError(e, arena) return False if proc_fallback and not found: if arg in self.funcs: del self.funcs[arg] return True
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 arena = alloc.Arena() arena.PushSource(source.Stdin('')) loader = pyutil.GetResourceLoader() oil_grammar = meta.LoadOilGrammar(loader) parse_ctx = None e_parser = expr_parse.ExprParser(parse_ctx, oil_grammar) line_lexer = lexer.LineLexer('', arena) line_reader = reader.FileLineReader(sys.stdin, arena) lex = lexer.Lexer(line_lexer, line_reader) try: pnode, _ = e_parser.Parse(lex, grammar_nt.command_expr) except error.Parse as e: ui.PrettyPrintError(e, arena) return 2 #print(pnode) tr = expr_to_ast.Transformer(oil_grammar) node = tr.Expr(pnode) 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 Run(self): # type: () -> None val = self.mem.GetValue('PROMPT_COMMAND') if val.tag_() != value_e.Str: return # PROMPT_COMMAND almost never changes, so we try to cache its parsing. # This avoids memory allocations. prompt_cmd = cast(value__Str, val).s node = self.parse_cache.get(prompt_cmd) if node is None: line_reader = reader.StringLineReader(prompt_cmd, self.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) # NOTE: This is similar to CommandEvaluator.ParseTrapCode(). # TODO: Add spid with alloc.ctx_Location(self.arena, source.PromptCommand(runtime.NO_SPID)): try: node = main_loop.ParseWholeFile(c_parser) except error.Parse as e: ui.PrettyPrintError(e, self.arena) return # don't execute self.parse_cache[prompt_cmd] = node # Save this so PROMPT_COMMAND can't set $? with state.ctx_Status(self.mem): # Catches fatal execution error self.cmd_ev.ExecuteAndCatch(node)
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 punshow(): args = parser.parse_args() logger.debug("argparsed: %r", args) if args.allowed_executable_varsubs: allowed_executable_varsubs.update(args.allowed_executable_varsubs) # this is a lie; we'll look up against PATH without it--but it might be a common mis-use? assert ("SHELL_RUNTIME_DEPENDENCY_PATH" in os.environ), "SHELL_RUNTIME_DEPENDENCY_PATH must be set" # adopt the runtime dependency path for resolving external executables os.environ["PATH"] = os.environ["SHELL_RUNTIME_DEPENDENCY_PATH"] try: if len(args.scripts) == 0: resolved = ResolvedScript() # TODO: before this is okay, you've gotta move everything else that prints to a log, stderr, or a dynamic logging function that can choose depending on mode. resolved_scripts["<stdin>"] = resolved resolved.write_to() for script in args.scripts: resolved = resolve_script(os.path.abspath(script)) except IOError as e: sys.stderr.write("whoooo buddy " + str(e)) return 2 except ResolutionError as e: e.print_if_needed() # return e.exit_status except error._ErrorWithLocation as e: ui.PrettyPrintError(e)
def _UnsetVar(self, arg, spid, proc_fallback): # type: (str, int, bool) -> bool """ Returns: bool: whether the 'unset' builtin should succeed with code 0. """ arena = self.parse_ctx.arena a_parser = self.parse_ctx.MakeArithParser(arg) with alloc.ctx_Location(arena, source.ArgvWord(spid)): try: anode = a_parser.Parse() except error.Parse as e: ui.PrettyPrintError(e, arena) # show parse error e_usage('Invalid unset expression', span_id=spid) lval = self.arith_ev.EvalArithLhs(anode, spid) # Prevent attacks like these by default: # # unset -v 'A["$(echo K; rm *)"]' if not self.exec_opts.eval_unsafe_arith( ) and lval.tag_() != lvalue_e.Named: e_usage( 'expected a variable name. shopt -s eval_unsafe_arith allows expressions', span_id=spid) #log('lval %s', lval) found = False try: # Note: This has 'setvar' semantics. It could be 'setref' too? # So it composes? found = self.mem.Unset(lval, False) except error.Runtime as e: # note: in bash, myreadonly=X fails, but declare myreadonly=X doens't # fail because it's a builtin. So I guess the same is true of 'unset'. e.span_id = spid ui.PrettyPrintError(e, arena) return False if proc_fallback and not found: if arg in self.funcs: del self.funcs[arg] return True
def ExecuteAndCatch(self, node, fork_external=True): """Execute a subprogram, handling _ControlFlow and fatal exceptions. Args: node: LST subtree fork_external: whether external commands require forking Returns: TODO: use enum 'why' instead of the 2 booleans Used by main_loop.py. Also: - SubProgramThunk for pipelines, subshell, command sub, process sub - TODO: Signals besides EXIT trap Most other clients call _Execute(): - _Source() for source builtin - _Eval() for eval builtin - _RunFunc() for function call """ is_return = False is_fatal = False try: status = self._Execute(node, fork_external=fork_external) except _ControlFlow as e: # Return at top level is OK, unlike in bash. if e.IsReturn(): is_return = True status = e.StatusCode() else: # Invalid control flow self.errfmt.Print( "Loop and control flow can't be in different processes", span_id=e.token.span_id) is_fatal = True # All shells exit 0 here. It could be hidden behind # strict-control-flow if the incompatibility causes problems. status = 1 except util.ParseError as e: self.dumper.MaybeCollect(self, e) # Do this before unwinding stack raise except util.FatalRuntimeError as e: self.dumper.MaybeCollect(self, e) # Do this before unwinding stack if not e.HasLocation(): # Last resort! e.span_id = self.mem.CurrentSpanId() ui.PrettyPrintError(e, self.arena, prefix='fatal: ') is_fatal = True status = e.exit_status if e.exit_status is not None else 1 self.dumper.MaybeDump(status) self.mem.SetLastStatus(status) return is_return, is_fatal
def _assert_ParseCommandListError(test, code_str): arena, c_parser = InitCommandParser(code_str) try: node = c_parser._ParseCommandLine() except util.ParseError as e: ui.PrettyPrintError(e, arena, sys.stdout) else: print('UNEXPECTED:') ast_lib.PrettyPrint(node) test.fail("Expected %r to fail" % code_str)
def RunFuncForCompletion(self, func_node, argv): try: status = self._RunFunc(func_node, argv) except util.FatalRuntimeError as e: ui.PrettyPrintError(e, self.arena, sys.stderr) status = e.exit_status if e.exit_status is not None else 1 except _ControlFlow as e: # shouldn't be able to exit the shell from a completion hook! util.error('Attempted to exit from completion hook.') status = 1 return status
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 Matches(self, comp): try: val = self.word_ev.EvalWordToString(self.arg_word) except util.FatalRuntimeError as e: ui.PrettyPrintError(e, self.arena) raise # SplitForWordEval() Allows \ escapes candidates = self.splitter.SplitForWordEval(val.s) for c in candidates: if c.startswith(comp.to_complete): yield c
def Interactive(opts, ex, c_parser, arena): status = 0 while True: # Reset internal newline state. NOTE: It would actually be correct to # reinitialize all objects (except Env) on every iteration. c_parser.Reset() c_parser.ResetInputObjects() try: w = c_parser.Peek() # may raise HistoryError or ParseError c_id = word.CommandId(w) if c_id == Id.Op_Newline: # print PS1 again, not PS2 continue # next command elif c_id == Id.Eof_Real: # InteractiveLineReader prints ^D break # end node = c_parser.ParseLogicalLine( ) # ditto, HistoryError or ParseError except util.HistoryError as e: # e.g. expansion failed # Where this happens: # for i in 1 2 3; do # !invalid # done print(e.UserErrorString()) continue except util.ParseError as e: ui.PrettyPrintError(e, arena) # NOTE: This should set the status interactively! Bash does this. status = 2 continue if node is None: # EOF # NOTE: We don't care if there are pending here docs in the interative case. break is_control_flow, is_fatal = ex.ExecuteAndCatch(node) status = ex.LastStatus() if is_control_flow: # e.g. 'exit' in the middle of a script break if is_fatal: # e.g. divide by zero continue # TODO: Replace this with a shell hook? with 'trap', or it could be just # like command_not_found. The hook can be 'echo $?' or something more # complicated, i.e. with timetamps. if opts.print_status: print('STATUS', repr(status)) if ex.MaybeRunExitTrap(): return ex.LastStatus() else: return status # could be a parse error
def _assert_ParseCommandListError(test, code_str): arena = test_lib.MakeArena('<cmd_parse_test>') c_parser = test_lib.InitCommandParser(code_str, arena=arena) try: node = c_parser._ParseCommandLine() except error.Parse as e: ui.PrettyPrintError(e, arena) else: print('UNEXPECTED:') node.PrettyPrint() test.fail("Expected %r to fail" % code_str)
def Batch(ex, c_parser, arena, nodes_out=None): # type: (Any, CommandParser, Arena, Optional[List[command_t]]) -> Any """Loop for batch execution. Args: nodes_out: if set to a list, the input lines are parsed, and LST nodes are appended to it instead of executed. For 'sh -n'. Can this be combined with interative loop? Differences: - Handling of parse errors. - Have to detect here docs at the end? Not a problem: - Get rid of --print-status and --show-ast for now - Get rid of EOF difference TODO: - Do source / eval need this? - 'source' needs to parse incrementally so that aliases are respected - I doubt 'eval' does! You can test it. - In contrast, 'trap' should parse up front? - What about $() ? """ status = 0 while True: try: node = c_parser.ParseLogicalLine() # can raise ParseError if node is None: # EOF c_parser.CheckForPendingHereDocs() # can raise ParseError break except util.ParseError as e: ui.PrettyPrintError(e, arena) status = 2 break if nodes_out is not None: nodes_out.append(node) continue #log('parsed %s', node) is_control_flow, is_fatal = ex.ExecuteAndCatch(node) status = ex.LastStatus() # e.g. divide by zero or 'exit' in the middle of a script if is_control_flow or is_fatal: break if ex.MaybeRunExitTrap(): return ex.LastStatus() else: return status # could be a parse error
def Matches(self, comp): # type: (Api) -> Iterator[Union[Iterator, Iterator[str]]] try: val = self.word_ev.EvalWordToString(self.arg_word) except error.FatalRuntime as e: ui.PrettyPrintError(e, self.arena) raise # SplitForWordEval() Allows \ escapes candidates = self.splitter.SplitForWordEval(val.s) for c in candidates: if c.startswith(comp.to_complete): yield c
def RunFuncForCompletion(self, func_node, argv): try: status = self._RunFunc(func_node, argv) except util.FatalRuntimeError as e: ui.PrettyPrintError(e, self.arena) status = e.exit_status if e.exit_status is not None else 1 except _ControlFlow as e: # shouldn't be able to exit the shell from a completion hook! # TODO: Avoid overwriting the prompt! self.errfmt.Print('Attempted to exit from completion hook.', span_id=e.token.span_id) status = 1 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback return status
def Batch(cmd_ev, c_parser, arena, cmd_flags=0): # type: (CommandEvaluator, CommandParser, Arena, int) -> int """Loop for batch execution. Returns: int status, e.g. 2 on parse error Can this be combined with interative loop? Differences: - Handling of parse errors. - Have to detect here docs at the end? Not a problem: - Get rid of --print-status and --show-ast for now - Get rid of EOF difference TODO: - Do source / eval need this? - 'source' needs to parse incrementally so that aliases are respected - I doubt 'eval' does! You can test it. - In contrast, 'trap' should parse up front? - What about $() ? """ status = 0 while True: try: node = c_parser.ParseLogicalLine() # can raise ParseError if node is None: # EOF c_parser.CheckForPendingHereDocs() # can raise ParseError break except error.Parse as e: ui.PrettyPrintError(e, arena) status = 2 break # Only optimize if we're on the last line like -c "echo hi" etc. if (cmd_flags & cmd_eval.IsMainProgram and c_parser.line_reader.LastLineHint()): cmd_flags |= cmd_eval.Optimize # can't optimize this because we haven't seen the end yet is_return, is_fatal = cmd_ev.ExecuteAndCatch(node, cmd_flags=cmd_flags) status = cmd_ev.LastStatus() # e.g. 'return' in middle of script, or divide by zero if is_return or is_fatal: break return status
def ExecuteAndCatch(self, node, fork_external=True): """Execute a subprogram, handling _ControlFlow and fatal exceptions. Args: node: LST subtree fork_external: whether external commands require forking Returns: TODO: use enum 'why' instead of the 2 booleans Used by main_loop.py. Also: - SubProgramThunk for pipelines, subshell, command sub, process sub - TODO: Signals besides EXIT trap Most other clients call _Execute(): - _Source() for source builtin - _Eval() for eval builtin - _RunFunc() for function call """ is_control_flow = False is_fatal = False try: status = self._Execute(node, fork_external=fork_external) except _ControlFlow as e: # Return at top level is OK, unlike in bash. if e.IsReturn() or e.IsExit(): is_control_flow = True status = e.StatusCode() else: raise # Invalid except util.ParseError as e: self.dumper.MaybeCollect(self, e) # Do this before unwinding stack raise except util.FatalRuntimeError as e: self.dumper.MaybeCollect(self, e) # Do this before unwinding stack ui.PrettyPrintError(e, self.arena) is_fatal = True status = e.exit_status if e.exit_status is not None else 1 self.dumper.MaybeDump(status) self.mem.last_status = status return is_control_flow, is_fatal
def _ParseTrapCode(self, code_str): # type: (str) -> command_t """ Returns: A node, or None if the code is invalid. """ line_reader = reader.StringLineReader(code_str, self.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) # TODO: the SPID should be passed through argv. Use ArgvWord? with alloc.ctx_Location(self.arena, source.Trap(runtime.NO_SPID)): try: node = main_loop.ParseWholeFile(c_parser) except error.Parse as e: ui.PrettyPrintError(e, self.arena) return None return node
def _assertParseMethod(test, code_str, method, expect_success=True): arena = test_lib.MakeArena('<cmd_parse_test>') c_parser = test_lib.InitCommandParser(code_str, arena=arena) m = getattr(c_parser, method) try: node = m() except error.Parse as e: ui.PrettyPrintError(e, arena) if expect_success: test.fail('%r failed' % code_str) node = None else: node.PrettyPrint() if not expect_success: test.fail('Expected %r to fail ' % code_str) return node
def Execute(self, node, fork_external=True): """Execute a top level LST node.""" # Use exceptions internally, but exit codes externally. try: status = self._Execute(node, fork_external=fork_external) except _ControlFlow as e: # TODO: pretty print error with e.token log('osh failed: Unexpected %r at top level' % e.token.val) status = 1 except util.FatalRuntimeError as e: # TODO: ui.PrettyPrintError(e, self.arena, sys.stderr) print('osh failed: %s' % e.UserErrorString(), file=sys.stderr) status = e.exit_status if e.exit_status is not None else 1 # TODO: Hook this up #print('break / continue can only be used inside loop') #status = 129 # TODO: Fix this. Use correct macros return status
def _assertParseMethod(test, code_str, method, expect_success=True): arena, c_parser = InitCommandParser(code_str) m = getattr(c_parser, method) try: if method == 'ParseSimpleCommand': node = m([]) # required cur_aliases arg else: node = m() except util.ParseError as e: ui.PrettyPrintError(e, arena, sys.stdout) if expect_success: test.fail('%r failed' % code_str) node = None else: ast_lib.PrettyPrint(node) if not expect_success: test.fail('Expected %r to fail ' % code_str) return node
def ExecuteAndCatch(self, node, fork_external=True): """Used directly by the interactive loop.""" is_control_flow = False try: status = self._Execute(node, fork_external=fork_external) except _ControlFlow as e: # Return at top level is OK, unlike in bash. if e.IsReturn() or e.IsExit(): is_control_flow = True status = e.StatusCode() else: raise # Invalid except util.FatalRuntimeError as e: ui.PrettyPrintError(e, self.arena) print('osh failed: %s' % e.UserErrorString(), file=sys.stderr) status = e.exit_status if e.exit_status is not None else 1 # TODO: dump self.mem if requested. Maybe speify with OIL_DUMP_PREFIX. # Other exceptions: SystemExit for sys.exit() return status, is_control_flow
def ParseTrapCode(self, code_str): """ Returns: A node, or None if the code is invalid. """ line_reader = reader.StringLineReader(code_str, self.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) # TODO: the SPID should be passed through argv self.arena.PushSource(source.Trap(const.NO_INTEGER)) try: try: node = main_loop.ParseWholeFile(c_parser) except util.ParseError as e: ui.PrettyPrintError(e, self.arena) return None finally: self.arena.PopSource() return node
def _Line(self, arg, var_name): # type: (arg_types.read, str) -> int line = _ReadLine() if len(line) == 0: # EOF return 1 if not arg.with_eol: if line.endswith('\r\n'): line = line[:-2] elif line.endswith('\n'): line = line[:-1] # Lines that don't start with a single quote aren't QSN. They may contain # a single quote internally, like: # # Fool's Gold if arg.q and line.startswith("'"): arena = self.parse_ctx.arena line_reader = reader.StringLineReader(line, arena) lexer = self.parse_ctx._MakeLexer(line_reader) # The parser only yields valid tokens: # Char_Literals, Char_OneChar, Char_Hex, Char_UBraced # So we can use word_compile.EvalCStringToken, which is also used for # $''. # Important: we don't generate Id.Unknown_Backslash because that is valid # in echo -e. We just make it Id.Unknown_Tok? try: # TODO: read should know about stdin, and redirects, and pipelines? with alloc.ctx_Location(arena, source.Stdin('')): tokens = qsn_native.Parse(lexer) except error.Parse as e: ui.PrettyPrintError(e, arena) return 1 tmp = [word_compile.EvalCStringToken(t) for t in tokens] line = ''.join(tmp) lhs = lvalue.Named(var_name) self.mem.SetValue(lhs, value.Str(line), scope_e.LocalOnly) return 0
def _RunFunc(self, func_node, argv): """Used to run SimpleCommand and to run registered completion hooks.""" # These are redirects at DEFINITION SITE. You can also have redirects at # the CALL SITE. For example: # # f() { echo hi; } 1>&2 # f 2>&1 try: def_redirects = self._EvalRedirects(func_node) except util.RedirectEvalError as e: ui.PrettyPrintError(e, self.arena) return 1 if def_redirects: if not self.fd_state.Push(def_redirects, self.waiter): return 1 # error self.mem.PushCall(func_node.name, func_node.spids[0], argv) # Redirects still valid for functions. # Here doc causes a pipe and Process(SubProgramThunk). try: status = self._Execute(func_node.body) except _ControlFlow as e: if e.IsReturn(): status = e.StatusCode() else: # break/continue used in the wrong place. e_die('Unexpected %r (in function call)', e.token.val, token=e.token) except (util.FatalRuntimeError, util.ParseError) as e: self.dumper.MaybeCollect(self, e) # Do this before unwinding stack raise finally: self.mem.PopCall() if def_redirects: self.fd_state.Pop() return status
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 ParseTrapCode(self, code_str): """ Returns: A node, or None if the code is invalid. """ line_reader = reader.StringLineReader(code_str, self.arena) _, c_parser = self.parse_ctx.MakeParser(line_reader) source_name = '<trap string>' self.arena.PushSource(source_name) try: try: node = main_loop.ParseWholeFile(c_parser) except util.ParseError as e: util.error('Parse error in %r:', source_name) ui.PrettyPrintError(e, self.arena, sys.stderr) return None finally: self.arena.PopSource() return node
def Interactive(opts, ex, c_parser, arena): status = 0 while True: # Reset internal newline state. NOTE: It would actually be correct to # reinitialize all objects (except Env) on every iteration. c_parser.Reset() c_parser.ResetInputObjects() try: node = c_parser.ParseLogicalLine() except util.ParseError as e: ui.PrettyPrintError(e, arena) # NOTE: This should set the status interactively! Bash does this. status = 2 continue if node is None: # EOF # NOTE: We don't care if there are pending here docs in the interative case. break is_control_flow, is_fatal = ex.ExecuteAndCatch(node) status = ex.LastStatus() if is_control_flow: # e.g. 'exit' in the middle of a script break if is_fatal: # e.g. divide by zero continue # TODO: Replace this with a shell hook? with 'trap', or it could be just # like command_not_found. The hook can be 'echo $?' or something more # complicated, i.e. with timetamps. if opts.print_status: print('STATUS', repr(status)) if ex.MaybeRunExitTrap(): return ex.LastStatus() else: return status # could be a parse error