aligner.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. # Copyright (c) 2018, 2022-2024 by Rocky Bernstein
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. import sys
  16. from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
  17. from xdis import iscode
  18. from xdis.version_info import IS_PYPY, PYTHON_VERSION_TRIPLE
  19. from uncompyle6.scanner import get_scanner
  20. from uncompyle6.semantics.consts import ASSIGN_DOC_STRING
  21. from uncompyle6.semantics.pysource import (
  22. RETURN_NONE,
  23. TREE_DEFAULT_DEBUG,
  24. SourceWalker,
  25. SourceWalkerError,
  26. find_globals_and_nonlocals
  27. )
  28. from uncompyle6.show import maybe_show_asm
  29. #
  30. class AligningWalker(SourceWalker, object):
  31. def __init__(
  32. self,
  33. version,
  34. out,
  35. scanner,
  36. showast=TREE_DEFAULT_DEBUG,
  37. debug_parser=PARSER_DEFAULT_DEBUG,
  38. compile_mode="exec",
  39. is_pypy=False,
  40. ):
  41. SourceWalker.__init__(
  42. self, version, out, scanner, showast, debug_parser, compile_mode, is_pypy
  43. )
  44. self.desired_line_number = 0
  45. self.current_line_number = 0
  46. self.showast = showast
  47. def println(self, *data):
  48. if data and not (len(data) == 1 and data[0] == ""):
  49. self.write(*data)
  50. self.pending_newlines = max(self.pending_newlines, 1)
  51. def write(self, *data):
  52. if (len(data) == 1) and data[0] == self.indent:
  53. diff = max(
  54. self.pending_newlines,
  55. self.desired_line_number - self.current_line_number,
  56. )
  57. self.f.write("\n" * diff)
  58. self.current_line_number += diff
  59. self.pending_newlines = 0
  60. if (len(data) == 0) or (len(data) == 1 and data[0] == ""):
  61. return
  62. out = "".join((str(j) for j in data))
  63. n = 0
  64. for i in out:
  65. if i == "\n":
  66. n += 1
  67. if n == len(out):
  68. self.pending_newlines = max(self.pending_newlines, n)
  69. return
  70. elif n:
  71. self.pending_newlines = max(self.pending_newlines, n)
  72. out = out[n:]
  73. break
  74. else:
  75. break
  76. if self.pending_newlines > 0:
  77. diff = max(
  78. self.pending_newlines,
  79. self.desired_line_number - self.current_line_number,
  80. )
  81. self.f.write("\n" * diff)
  82. self.current_line_number += diff
  83. self.pending_newlines = 0
  84. for i in out[::-1]:
  85. if i == "\n":
  86. self.pending_newlines += 1
  87. else:
  88. break
  89. if self.pending_newlines:
  90. out = out[: -self.pending_newlines]
  91. self.f.write(out)
  92. def default(self, node):
  93. mapping = self._get_mapping(node)
  94. if hasattr(node, "linestart"):
  95. if node.linestart:
  96. self.desired_line_number = node.linestart
  97. table = mapping[0]
  98. key = node
  99. for i in mapping[1:]:
  100. key = key[i]
  101. pass
  102. if key.kind in table:
  103. self.template_engine(table[key.kind], node)
  104. self.prune()
  105. DEFAULT_DEBUG_OPTS = {"asm": False, "tree": TREE_DEFAULT_DEBUG, "grammar": False}
  106. def code_deparse_align(
  107. co,
  108. out=sys.stderr,
  109. version=None,
  110. is_pypy=None,
  111. debug_opts=DEFAULT_DEBUG_OPTS,
  112. code_objects={},
  113. compile_mode="exec",
  114. ):
  115. """
  116. ingests and deparses a given code block 'co'
  117. """
  118. assert iscode(co)
  119. if version is None:
  120. version = PYTHON_VERSION_TRIPLE
  121. if is_pypy is None:
  122. is_pypy = IS_PYPY
  123. # store final output stream for case of error
  124. scanner = get_scanner(version, is_pypy=is_pypy)
  125. tokens, customize = scanner.ingest(co, code_objects=code_objects)
  126. show_asm = debug_opts.get("asm", None)
  127. maybe_show_asm(show_asm, tokens)
  128. debug_parser = dict(PARSER_DEFAULT_DEBUG)
  129. show_grammar = debug_opts.get("grammar", None)
  130. show_grammar = debug_opts.get("grammar", None)
  131. if show_grammar:
  132. debug_parser["reduce"] = show_grammar
  133. debug_parser["errorstack"] = True
  134. # Build a parse tree from tokenized and massaged disassembly.
  135. show_ast = debug_opts.get("ast", TREE_DEFAULT_DEBUG)
  136. deparsed = AligningWalker(
  137. version,
  138. out,
  139. scanner,
  140. showast=show_ast,
  141. debug_parser=debug_parser,
  142. compile_mode=compile_mode,
  143. is_pypy=is_pypy,
  144. )
  145. is_top_level_module = co.co_name == "<module>"
  146. deparsed.ast = deparsed.build_ast(
  147. tokens, customize, co, is_top_level_module=is_top_level_module
  148. )
  149. assert deparsed.ast == "stmts", "Should have parsed grammar start"
  150. del tokens # save memory
  151. (deparsed.mod_globs, _) = find_globals_and_nonlocals(
  152. deparsed.ast, set(), set(), co, version
  153. )
  154. # convert leading '__doc__ = "..." into doc string
  155. try:
  156. if deparsed.ast[0][0] == ASSIGN_DOC_STRING(co.co_consts[0]):
  157. deparsed.print_docstring("", co.co_consts[0])
  158. del deparsed.ast[0]
  159. if deparsed.ast[-1] == RETURN_NONE:
  160. deparsed.ast.pop() # remove last node
  161. # todo: if empty, add 'pass'
  162. except Exception:
  163. pass
  164. # What we've been waiting for: Generate Python source from the parse tree!
  165. deparsed.gen_source(deparsed.ast, co.co_name, customize)
  166. for g in sorted(deparsed.mod_globs):
  167. deparsed.write("# global %s ## Warning: Unused global\n" % g)
  168. if deparsed.ERROR:
  169. raise SourceWalkerError("Deparsing stopped due to parse error")
  170. return deparsed
  171. if __name__ == "__main__":
  172. def deparse_test(co):
  173. "This is a docstring"
  174. deparsed = code_deparse_align(co)
  175. print(deparsed.text)
  176. return
  177. deparse_test(deparse_test.func_code)