def PrintAst(nodes, opts): if len(nodes) == 1: node = nodes[0] else: node = ast.CommandList(nodes) 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')
def ParseWholeFile(c_parser): """Parse an entire shell script. This uses the same logic as Batch(). """ children = [] while True: node = c_parser.ParseLogicalLine() # can raise ParseError if node is None: # EOF c_parser.CheckForPendingHereDocs() # can raise ParseError break children.append(node) if len(children) == 1: return children[0] else: return ast.CommandList(children)
def ParseCommandTerm(self): """" command_term : and_or (trailer and_or)* ; trailer : sync_op newline_ok | NEWLINES; sync_op : '&' | ';'; This is handled in imperative style, like ParseCommandLine. Called by ParseCommandList for all blocks, and also for ParseCaseItem, which is slightly different. (HOW? Is it the DSEMI?) Returns: ast.command """ # Word types that will end the command term. END_LIST = ( Id.Eof_Real, Id.Eof_RParen, Id.Eof_Backtick, Id.Right_Subshell, Id.Lit_RBrace, Id.Op_DSemi) # NOTE: This is similar to ParseCommandLine, except there is a lot of stuff # about here docs. Here docs are inherently line-oriented. # # - Why aren't we doing END_LIST in ParseCommandLine? # - Because you will never be inside $() at the top level. # - We also know it will end in a newline. It can't end in "fi"! # - example: if true; then { echo hi; } fi # - Why aren't we doing 'for c in children' too? children = [] done = False while not done: if not self._Peek(): return None #print('====> ParseCommandTerm word', self.cur_word) # Most keywords are valid "first words". But do/done/then do not BEGIN # commands, so they are not valid. if self.c_id in ( Id.KW_Do, Id.KW_Done, Id.KW_Then, Id.KW_Fi, Id.KW_Elif, Id.KW_Else, Id.KW_Esac): break child = self.ParseAndOr() if not child: self.AddErrorContext('Error parsing AndOr in ParseCommandTerm') return None if not self._Peek(): return None if self.c_id == Id.Op_Newline: self._Next() if not self._Peek(): return None if self.c_id in END_LIST: done = True elif self.c_id in (Id.Op_Semi, Id.Op_Amp): child = ast.Sentence(child, self.cur_word.token) self._Next() if not self._Peek(): return None if self.c_id == Id.Op_Newline: self._Next() # skip over newline # Test if we should keep going. There might be another command after # the semi and newline. if not self._Peek(): return None if self.c_id in END_LIST: done = True elif self.c_id in END_LIST: # ; EOF done = True elif self.c_id in END_LIST: # EOF done = True else: pass # e.g. "} done", "fi fi", ") fi", etc. is OK children.append(child) if not self._Peek(): return None return ast.CommandList(children)
def ParseCommandLine(self): """ NOTE: This is only called in InteractiveLoop. Oh crap I need to really read and execute a line at a time then? BUG: sleep 1 & sleep 1 & doesn't work here, when written in REPL. But it does work with '-c', because that calls ParseFile and not ParseCommandLine over and over. TODO: Get rid of ParseFile and stuff? Shouldn't be used for -c and so forth. Just have an ExecuteLoop for now. But you still need ParseCommandList, for internal nodes. command_line : and_or (sync_op and_or)* trailer? ; trailer : sync_op newline_ok | NEWLINES; sync_op : '&' | ';'; This rule causes LL(k > 1) behavior. We would have to peek to see if there is another command word after the sync op. But it's easier to express imperatively. Do the following in a loop: 1. ParseAndOr 2. Peek. a. If there's a newline, then return. (We're only parsing a single line.) b. If there's a sync_op, process it. Then look for a newline and return. Otherwise, parse another AndOr. COMPARE command_line : and_or (sync_op and_or)* trailer? ; # TOP LEVEL command_term : and_or (trailer and_or)* ; # CHILDREN I think you should be able to factor these out. """ children = [] done = False while not done: child = self.ParseAndOr() if not child: return None if not self._Peek(): return None if self.c_id in (Id.Op_Semi, Id.Op_Amp): # also Id.Op_Amp. child = ast.Sentence(child, self.cur_word.token) self._Next() if not self._Peek(): return None if self.c_id in (Id.Op_Newline, Id.Eof_Real): done = True elif self.c_id == Id.Op_Newline: done = True elif self.c_id == Id.Eof_Real: done = True else: self.AddErrorContext( 'ParseCommandLine: Unexpected token %s', self.cur_word) return None children.append(child) return ast.CommandList(children)