aligner.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # Copyright (c) 2018, 2020, 2022 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 decompyle3.semantics.consts import ASSIGN_DOC_STRING, RETURN_NONE
  17. from decompyle3.semantics.helper import find_globals
  18. from decompyle3.semantics.pysource import SourceWalker, SourceWalkerError
  19. from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
  20. from decompyle3 import IS_PYPY
  21. class AligningWalker(SourceWalker, object):
  22. def __init__(
  23. self,
  24. version,
  25. out,
  26. scanner,
  27. showast=False,
  28. debug_parser=PARSER_DEFAULT_DEBUG,
  29. compile_mode="exec",
  30. is_pypy=False,
  31. ):
  32. SourceWalker.__init__(
  33. self, version, out, scanner, showast, debug_parser, compile_mode, is_pypy
  34. )
  35. self.desired_line_number = 0
  36. self.current_line_number = 0
  37. def println(self, *data):
  38. if data and not (len(data) == 1 and data[0] == ""):
  39. self.write(*data)
  40. self.pending_newlines = max(self.pending_newlines, 1)
  41. def write(self, *data):
  42. if (len(data) == 1) and data[0] == self.indent:
  43. diff = max(
  44. self.pending_newlines,
  45. self.desired_line_number - self.current_line_number,
  46. )
  47. self.f.write("\n" * diff)
  48. self.current_line_number += diff
  49. self.pending_newlines = 0
  50. if (len(data) == 0) or (len(data) == 1 and data[0] == ""):
  51. return
  52. out = "".join((str(j) for j in data))
  53. n = 0
  54. for i in out:
  55. if i == "\n":
  56. n += 1
  57. if n == len(out):
  58. self.pending_newlines = max(self.pending_newlines, n)
  59. return
  60. elif n:
  61. self.pending_newlines = max(self.pending_newlines, n)
  62. out = out[n:]
  63. break
  64. else:
  65. break
  66. if self.pending_newlines > 0:
  67. diff = max(
  68. self.pending_newlines,
  69. self.desired_line_number - self.current_line_number,
  70. )
  71. self.f.write("\n" * diff)
  72. self.current_line_number += diff
  73. self.pending_newlines = 0
  74. for i in out[::-1]:
  75. if i == "\n":
  76. self.pending_newlines += 1
  77. else:
  78. break
  79. if self.pending_newlines:
  80. out = out[: -self.pending_newlines]
  81. self.f.write(out)
  82. def default(self, node):
  83. mapping = self._get_mapping(node)
  84. if hasattr(node, "linestart"):
  85. if node.linestart:
  86. self.desired_line_number = node.linestart
  87. table = mapping[0]
  88. key = node
  89. for i in mapping[1:]:
  90. key = key[i]
  91. pass
  92. if key.type in table:
  93. self.engine(table[key.type], node)
  94. self.prune()
  95. from xdis import iscode
  96. from decompyle3.scanner import get_scanner
  97. from decompyle3.show import maybe_show_asm
  98. #
  99. DEFAULT_DEBUG_OPTS = {"asm": False, "tree": False, "grammar": False}
  100. def code_deparse_align(
  101. co,
  102. out=sys.stderr,
  103. version=None,
  104. is_pypy=None,
  105. debug_opts=DEFAULT_DEBUG_OPTS,
  106. code_objects={},
  107. compile_mode="exec",
  108. ):
  109. """
  110. ingests and deparses a given code block 'co'
  111. """
  112. assert iscode(co)
  113. if version is None:
  114. version = float(sys.version[0:3])
  115. if is_pypy is None:
  116. is_pypy = IS_PYPY
  117. # store final output stream for case of error
  118. scanner = get_scanner(version, is_pypy=is_pypy)
  119. tokens, customize = scanner.ingest(co, code_objects=code_objects)
  120. show_asm = debug_opts.get("asm", None)
  121. maybe_show_asm(show_asm, tokens)
  122. debug_parser = dict(PARSER_DEFAULT_DEBUG)
  123. show_grammar = debug_opts.get("grammar", None)
  124. show_grammar = debug_opts.get("grammar", None)
  125. if show_grammar:
  126. debug_parser["reduce"] = show_grammar
  127. debug_parser["errorstack"] = True
  128. # Build a parse tree from tokenized and massaged disassembly.
  129. show_ast = debug_opts.get("ast", None)
  130. deparsed = AligningWalker(
  131. version,
  132. scanner,
  133. out,
  134. showast=show_ast,
  135. debug_parser=debug_parser,
  136. compile_mode=compile_mode,
  137. is_pypy=is_pypy,
  138. )
  139. is_top_level_module = co.co_name == "<module>"
  140. deparsed.ast = deparsed.build_ast(
  141. tokens, customize, co, is_top_level_module=is_top_level_module
  142. )
  143. assert deparsed.ast == "stmts", "Should have parsed grammar start"
  144. del tokens # save memory
  145. deparsed.mod_globs = find_globals(deparsed.ast, set())
  146. # convert leading '__doc__ = "..." into doc string
  147. try:
  148. if deparsed.ast[0][0] == ASSIGN_DOC_STRING(co.co_consts[0]):
  149. deparsed.print_docstring("", co.co_consts[0])
  150. del deparsed.ast[0]
  151. if deparsed.ast[-1] == RETURN_NONE:
  152. deparsed.ast.pop() # remove last node
  153. # todo: if empty, add 'pass'
  154. except:
  155. pass
  156. # What we've been waiting for: Generate Python source from the parse tree!
  157. deparsed.gen_source(deparsed.ast, co.co_name, customize)
  158. for g in sorted(deparsed.mod_globs):
  159. deparsed.write("# global %s ## Warning: Unused global\n" % g)
  160. if deparsed.ERROR:
  161. raise SourceWalkerError("Deparsing stopped due to parse error")
  162. return deparsed
  163. if __name__ == "__main__":
  164. def deparse_test(co):
  165. "This is a docstring"
  166. deparsed = code_deparse_align(co)
  167. print(deparsed.text)
  168. return
  169. deparse_test(deparse_test.__code__)