From 8cdf741b626dab1e6d59c49da23d5c2d4f562a3c Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 24 Apr 2022 17:02:05 -0400 Subject: [PATCH] Bugs in long-literal handlin Move n_dict to n_actions and special case n_const_list. Generalize build_collection out of 3.7+ and into all Pythons --- test/bytecode_3.8_run/05_long_literals.pyc | Bin 0 -> 15555 bytes .../expression/05_long_literals.py | 45 ++++- uncompyle6/scanner.py | 77 ++++++++ uncompyle6/scanners/scanner37base.py | 77 -------- uncompyle6/semantics/n_actions.py | 183 ++++++++++++++++++ uncompyle6/semantics/pysource.py | 178 ----------------- 6 files changed, 304 insertions(+), 256 deletions(-) create mode 100644 test/bytecode_3.8_run/05_long_literals.pyc diff --git a/test/bytecode_3.8_run/05_long_literals.pyc b/test/bytecode_3.8_run/05_long_literals.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f952df75d410636180c1518d42d890c30fefb908 GIT binary patch literal 15555 zcmeI%Wti2~8U^rkh8nuNJ0#B8d!HBu#BL147z2TK90rDP1`sT;6$`tt6T1_;#cszy z#TvT+>&9Ae_VV8Q^*)y`_c+h`&EGxa`#v7dn(Z@AY}v9|0)O>iQ88uBeu>0t{>{Js zwJw=RjKe|g$To>YWuiP`>pvhe7_Z6fsOMD#Gi4Nv=VDz4Xu$iwkB4(tQ0qG(wbVcWhLc_ ziUhve)S9pVju!Zymg~Q(m9@4u*4Eltd+T5wt&?@OF4on$S$FGUJ*}7Zwm#O^`dNP) zU;}NC4YnaR)P~t6Hrz(oNE>A-OPe!q!7`S$oaL>|Hnq)cw2iT`wz-Y7Eo@8M%C@#` zY+KvTwznN@N88DEwq0zz?P|N(?zV?busv;}?PYu0KDMuw+kUpc9bgCABs<6^+Z3B> zW)*g@RoXPGvgtO%s%@stve`Dr4zU`mwYfIW=G&pRz!utJcDNm3N7_-g$QIktc8ncs z$Jz0Af}Ln5*~zxVPO($%G&|kSu%&jUon>d+Id-m{XXo1mcA;Hl7uzzs#4feV>~g!p zuC%M{YP-g+wd?G9TW&YljdqjWY`560cAMR9ci5eFm)&jm*u8e2-ER-rgSNsRvWM*v zd(O`XYbnw z_Mxq^kL+Xn#8%s<_L+TdU)YycZ(rHh_KkgO-`V%}gZ*ef+0XWit+BPX&VIGu?05Ua z{PHb=%GTOeB^TOnH`+aTK_+acQ{J0LqEJ0UwGyCCC{ zU6I|8-H|tL9D>v!wa8p#9x@*}6j^{QL=Hm^M~*;_M2il# zkrR*;k&}>sA3S%Ex+Jd8YoJc>MqJdQkpJc&GoJdHeqJc~SsJddnIUO-+%UP4|*UO`?(UPE3- z-ay_&-a_6+-a+0)>X7%4_mK~f50O>KN65#>C&+5#Q{*$`bL0!;OQat83i%rO2Kg5G z4*4GW0r?U63HcfM1zCfvMb;s|BEKz7E?Zk(f}it=&iuLmU;VVlyx*7gEib|6KPDuG z{E6Qk)FX-d64HP)CQV3F(wwvaLrd$DQ|gja>yj09$;!Iqw7TT?+zAb4-+LI2XBk4pslP;ty=|;Mf9;7Gf zMS7Dyq%Y}5`jY`+2)5#fRDLIpz zMb0MYkaNj-iMOq?9xz%}8_7g0v*9NNduDv?c9Gd(we)B%MfS(uH&--AH%RgY+c5 zNN>`I^dj!B!kFcGK35z!^kFNI2l1kl2If@(!>!@0?Cjp$&oxMBb$=V$Y?T# zj3t|sabyd!CE1E>O|~K1lI_U$WCyY%*@^5-b|K@*u4Ff|JK2LwAbXODWG}Ke*@x^) z%E^9Ye{ujhkW3;6k;!BVnM#aQkb_AjnMSI}bTWfflbK`|nN8-9LkO?Ls#-Fa%p>#3 zp=1GBNDd>1lOxEHjW5}`OIC4BWft*NAA}5n2

r$IgOl7&LB(4ndB^T zHaUl!OU@(blMBd&U%-CAo@RO|Bu=lIzIzWI4Hk+(>RBHiXxJGq10N$w(dlY7X$d1TKeewbMkgOsfk&nqIWHtGed`3Pe zUyv_JJ^6}!O}-)DlJCg(XRiq1_UE#AYd=7=r znZoC6;d8F=IbZl(R@Y?1oK$>B#|IZ5e0&J;Arl|6@gWx<^6?=~c5$+clUD+TparX@}De zryWi^oOXEa@Y>2l^b(v;&QfXWD^I7tT=X_@o`Ebv)A!6g!@22dW*WChf~j;dhXP7r=C0Y+^OeIJ$LH4Q_r1x?$mRqo;&s2spn2Tcj~!Q&z*Yi z)N`kvJN4YD=T1F$>bX4mWk-tLnMKT^-M~1GjZN z(+=F%@k~2#V;9cUbElp=T-x!g+JS33o@oaz?s%phxVqz+cBtnLw|C*I>bb-H9nZ7_ zH+VeL4&34KOgnIk7tYjkr=B}pbY0Xy?XA|bFZFz_1vrHUOo5fxmVAbY0Xy?X9- zpL_M(tLI)l_v*RVeeTtBulwAq=U(@@SI@ofbFZFz_1vrHUOo4^&%Ju?b)S3n-0ME~ z>bY0Xy?XA|bFcf{tLI+#xmVAUmJlgL)p+^Prvw z^*pHOK|K%Zc~H-TdLGpCpq>ZyJgDbEJrC-6P|t&U9@O)oo(J_jsOLdF59)bP&x3j% z)bpU82lYIt=RrLW>UmJlgL)p+^Prvw^*pHOK|K%Zc~H-TdLGpCpq>ZyJgDbEJrC-6 zP|t&U9@O)oo(J_jsOLdF59)bP&x3j%)bpU82lYIt=RrLW>UmJlgL)p+^Prvw^*pHO zK|K%Zc~H-TdLGpCpq>ZyJgDbEJrC-6P|t&U9@O)oo(J_jsOLdF59)bP&x3j%)bpU8 z2lYIoo@dnaOv(-IThkQ3Hmhkynv)i!C22)klQyI+X-C?V4x}ULL^_i$q$}x0x|1HH zC+S6clRl&`=|}pL0c0Qe)ix@vAk&E)FZk#iQ-m5!b{+vZhQjBSGd z8B#l}RU*-_QM2YH$>K-x71#)`z~aQ>#LC8t5{nY!Ho~^zySn%aBuoB!&y8u=p!lvU zz5*NN75M92H+G}!D>l9O3jFn+`_J3;*T$`yH7ULi|6gB$hRNc`|J5tNp9G5&OPcY& RwE5Af@qhc%aaHMne*#+iBjNx6 literal 0 HcmV?d00001 diff --git a/test/simple_source/expression/05_long_literals.py b/test/simple_source/expression/05_long_literals.py index e67b3006..f9589a7e 100644 --- a/test/simple_source/expression/05_long_literals.py +++ b/test/simple_source/expression/05_long_literals.py @@ -1306,7 +1306,7 @@ assert tuple(x.keys()) == (1, 3) # Try a long dictionary. # This should not be slow as it has been in the past values = { - "valuea": a + 1, + "value1": x, "value2": 2 + 1, "value3": 3 + 1, "value4": 4 + 1, @@ -1811,3 +1811,46 @@ values = { } assert list(values.values()) == list(range(2, 502 + 2)) + + +# Try a long dictionary that fails because we have a binary op. +# We can get a expr32 grouping speedup +# which is slower than if this were all constant. +# The above was not implemented at the time this test was written. +values = { + "value1": x + 1, # This is a binary op not consant + "value2": 2, + "value3": 3, + "value4": 4, + "value5": 5, + "value6": 6, + "value7": 7, + "value8": 8, + "value9": 9, + "value10": 10, + "value11": 11, + "value12": 12, + "value13": 13, + "value14": 14, + "value15": 15, + "value16": 16, + "value17": 17, + "value18": 18, + "value19": 19, + "value20": 20, + "value21": 21, + "value22": 22, + "value23": 23, + "value24": 24, + "value25": 25, + "value26": 26, + "value27": 27, + "value28": 28, + "value29": 29, + "value30": 30, + "value31": 31, + "value32": 32, + "value33": 33, +} + +assert list(values.values()) == list(range(2, 502 + 2)) diff --git a/uncompyle6/scanner.py b/uncompyle6/scanner.py index 7baf8fb5..30dcab2d 100644 --- a/uncompyle6/scanner.py +++ b/uncompyle6/scanner.py @@ -84,6 +84,9 @@ def long(num): return num +CONST_COLLECTIONS = ("CONST_LIST", "CONST_SET", "CONST_DICT") + + class Code(object): """ Class for representing code-objects. @@ -122,6 +125,80 @@ class Scanner(object): # FIXME: This weird Python2 behavior is not Python3 self.resetTokenClass() + def bound_collection( + self, tokens: list, next_tokens: list, t: Token, i: int, collection_type: str + ): + count = t.attr + assert isinstance(count, int) + + assert count <= i + + if collection_type == "CONST_DICT": + # constant dictonaries work via BUILD_CONST_KEY_MAP and + # handle the values() like sets and lists. + # However the keys() are an LOAD_CONST of the keys. + # adjust offset to account for this + count += 1 + + # For small lists don't bother + if count < 5: + return next_tokens + [t] + + collection_start = i - count + + for j in range(collection_start, i): + if tokens[j].kind not in ( + "LOAD_CONST", + "LOAD_FAST", + "LOAD_GLOBAL", + "LOAD_NAME", + ): + return next_tokens + [t] + + collection_enum = CONST_COLLECTIONS.index(collection_type) + + # If we go there all instructions before tokens[i] are LOAD_CONST and we can replace + # add a boundary marker and change LOAD_CONST to something else + new_tokens = next_tokens[:-count] + start_offset = tokens[collection_start].offset + new_tokens.append( + Token( + opname="COLLECTION_START", + attr=collection_enum, + pattr=collection_type, + offset=f"{start_offset}_0", + has_arg=True, + opc=self.opc, + has_extended_arg=False, + ) + ) + for j in range(collection_start, i): + new_tokens.append( + Token( + opname="ADD_VALUE", + attr=tokens[j].attr, + pattr=tokens[j].pattr, + offset=tokens[j].offset, + has_arg=True, + linestart=tokens[j].linestart, + opc=self.opc, + has_extended_arg=False, + ) + ) + new_tokens.append( + Token( + opname=f"BUILD_{collection_type}", + attr=t.attr, + pattr=t.pattr, + offset=t.offset, + has_arg=t.has_arg, + linestart=t.linestart, + opc=t.opc, + has_extended_arg=False, + ) + ) + return new_tokens + def build_instructions(self, co): """ Create a list of instructions (a structured object rather than diff --git a/uncompyle6/scanners/scanner37base.py b/uncompyle6/scanners/scanner37base.py index bb6061bb..2c6410e1 100644 --- a/uncompyle6/scanners/scanner37base.py +++ b/uncompyle6/scanners/scanner37base.py @@ -47,9 +47,6 @@ import sys globals().update(op3.opmap) -CONST_COLLECTIONS = ("CONST_LIST", "CONST_SET", "CONST_DICT") - - class Scanner37Base(Scanner): def __init__(self, version, show_asm=None, is_pypy=False): super(Scanner37Base, self).__init__(version, show_asm, is_pypy) @@ -184,80 +181,6 @@ class Scanner37Base(Scanner): # self.varargs_ops = frozenset(self.opc.hasvargs) return - def bound_collection( - self, tokens: list, next_tokens: list, t: Token, i: int, collection_type: str - ): - count = t.attr - assert isinstance(count, int) - - assert count <= i - - if collection_type == "CONST_DICT": - # constant dictonaries work via BUILD_CONST_KEY_MAP and - # handle the values() like sets and lists. - # However the keys() are an LOAD_CONST of the keys. - # adjust offset to account for this - count += 1 - - # For small lists don't bother - if count < 5: - return next_tokens + [t] - - collection_start = i - count - - for j in range(collection_start, i): - if tokens[j].kind not in ( - "LOAD_CONST", - "LOAD_FAST", - "LOAD_GLOBAL", - "LOAD_NAME", - ): - return next_tokens + [t] - - collection_enum = CONST_COLLECTIONS.index(collection_type) - - # If we go there all instructions before tokens[i] are LOAD_CONST and we can replace - # add a boundary marker and change LOAD_CONST to something else - new_tokens = next_tokens[:-count] - start_offset = tokens[collection_start].offset - new_tokens.append( - Token( - opname="COLLECTION_START", - attr=collection_enum, - pattr=collection_type, - offset=f"{start_offset}_0", - has_arg=True, - opc=self.opc, - has_extended_arg=False, - ) - ) - for j in range(collection_start, i): - new_tokens.append( - Token( - opname="ADD_VALUE", - attr=tokens[j].attr, - pattr=tokens[j].pattr, - offset=tokens[j].offset, - has_arg=True, - linestart=tokens[j].linestart, - opc=self.opc, - has_extended_arg=False, - ) - ) - new_tokens.append( - Token( - opname=f"BUILD_{collection_type}", - attr=t.attr, - pattr=t.pattr, - offset=t.offset, - has_arg=t.has_arg, - linestart=t.linestart, - opc=t.opc, - has_extended_arg=False, - ) - ) - return new_tokens - def ingest(self, co, classname=None, code_objects={}, show_asm=None): """ Pick out tokens from an uncompyle6 code object, and transform them, diff --git a/uncompyle6/semantics/n_actions.py b/uncompyle6/semantics/n_actions.py index 54f98aa9..d108b742 100644 --- a/uncompyle6/semantics/n_actions.py +++ b/uncompyle6/semantics/n_actions.py @@ -274,6 +274,189 @@ class NonterminalActions: n_store_subscript = n_subscript = n_delete_subscript + def n_dict(self, node): + """ + Prettyprint a dict. + 'dict' is something like k = {'a': 1, 'b': 42}" + We will use source-code line breaks to guide us when to break. + """ + if len(node) == 1 and node[0] == "const_list": + self.preorder(node[0]) + self.prune() + return + + p = self.prec + self.prec = 100 + + self.indent_more(INDENT_PER_LEVEL) + sep = INDENT_PER_LEVEL[:-1] + if node[0] != "dict_entry": + self.write("{") + line_number = self.line_number + + if self.version >= (3, 0) and not self.is_pypy: + if node[0].kind.startswith("kvlist"): + # Python 3.5+ style key/value list in dict + kv_node = node[0] + l = list(kv_node) + length = len(l) + if kv_node[-1].kind.startswith("BUILD_MAP"): + length -= 1 + i = 0 + + # Respect line breaks from source + while i < length: + self.write(sep) + name = self.traverse(l[i], indent="") + if i > 0: + line_number = self.indent_if_source_nl( + line_number, self.indent + INDENT_PER_LEVEL[:-1] + ) + line_number = self.line_number + self.write(name, ": ") + value = self.traverse( + l[i + 1], indent=self.indent + (len(name) + 2) * " " + ) + self.write(value) + sep = ", " + if line_number != self.line_number: + sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1] + line_number = self.line_number + i += 2 + pass + pass + elif len(node) > 1 and node[1].kind.startswith("kvlist"): + # Python 3.0..3.4 style key/value list in dict + kv_node = node[1] + l = list(kv_node) + if len(l) > 0 and l[0].kind == "kv3": + # Python 3.2 does this + kv_node = node[1][0] + l = list(kv_node) + i = 0 + while i < len(l): + self.write(sep) + name = self.traverse(l[i + 1], indent="") + if i > 0: + line_number = self.indent_if_source_nl( + line_number, self.indent + INDENT_PER_LEVEL[:-1] + ) + pass + line_number = self.line_number + self.write(name, ": ") + value = self.traverse( + l[i], indent=self.indent + (len(name) + 2) * " " + ) + self.write(value) + sep = ", " + if line_number != self.line_number: + sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1] + line_number = self.line_number + else: + sep += " " + i += 3 + pass + pass + elif node[-1].kind.startswith("BUILD_CONST_KEY_MAP"): + # Python 3.6+ style const map + keys = node[-2].pattr + values = node[:-2] + # FIXME: Line numbers? + for key, value in zip(keys, values): + self.write(sep) + self.write(repr(key)) + line_number = self.line_number + self.write(":") + self.write(self.traverse(value[0])) + sep = ", " + if line_number != self.line_number: + sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1] + line_number = self.line_number + else: + sep += " " + pass + pass + if sep.startswith(",\n"): + self.write(sep[1:]) + pass + elif node[0].kind.startswith("dict_entry"): + assert self.version >= (3, 5) + template = ("%C", (0, len(node[0]), ", **")) + self.template_engine(template, node[0]) + sep = "" + elif node[-1].kind.startswith("BUILD_MAP_UNPACK") or node[ + -1 + ].kind.startswith("dict_entry"): + assert self.version >= (3, 5) + # FIXME: I think we can intermingle dict_comp's with other + # dictionary kinds of things. The most common though is + # a sequence of dict_comp's + kwargs = node[-1].attr + template = ("**%C", (0, kwargs, ", **")) + self.template_engine(template, node) + sep = "" + + pass + else: + # Python 2 style kvlist. Find beginning of kvlist. + indent = self.indent + " " + line_number = self.line_number + if node[0].kind.startswith("BUILD_MAP"): + if len(node) > 1 and node[1].kind in ("kvlist", "kvlist_n"): + kv_node = node[1] + else: + kv_node = node[1:] + self.kv_map(kv_node, sep, line_number, indent) + else: + sep = "" + opname = node[-1].kind + if self.is_pypy and self.version >= (3, 5): + if opname.startswith("BUILD_CONST_KEY_MAP"): + keys = node[-2].attr + # FIXME: DRY this and the above + for i in range(len(keys)): + key = keys[i] + value = self.traverse(node[i], indent="") + self.write(sep, key, ": ", value) + sep = ", " + if line_number != self.line_number: + sep += "\n" + self.indent + " " + line_number = self.line_number + pass + pass + pass + else: + if opname.startswith("kvlist"): + list_node = node[0] + else: + list_node = node + + assert list_node[-1].kind.startswith("BUILD_MAP") + for i in range(0, len(list_node) - 1, 2): + key = self.traverse(list_node[i], indent="") + value = self.traverse(list_node[i + 1], indent="") + self.write(sep, key, ": ", value) + sep = ", " + if line_number != self.line_number: + sep += "\n" + self.indent + " " + line_number = self.line_number + pass + pass + pass + elif opname.startswith("kvlist"): + kv_node = node[-1] + self.kv_map(node[-1], sep, line_number, indent) + + pass + pass + if sep.startswith(",\n"): + self.write(sep[1:]) + if node[0] != "dict_entry": + self.write("}") + self.indent_less(INDENT_PER_LEVEL) + self.prec = p + self.prune() + def n_docstring(self, node): indent = self.indent diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index bea3f36b..52a7014e 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -696,184 +696,6 @@ class SourceWalker(GenericASTTraversal, NonterminalActions, ComprehensionMixin): pass pass - def n_dict(self, node): - """ - prettyprint a dict - 'dict' is something like k = {'a': 1, 'b': 42}" - We will use source-code line breaks to guide us when to break. - """ - p = self.prec - self.prec = 100 - - self.indent_more(INDENT_PER_LEVEL) - sep = INDENT_PER_LEVEL[:-1] - if node[0] != "dict_entry": - self.write("{") - line_number = self.line_number - - if self.version >= (3, 0) and not self.is_pypy: - if node[0].kind.startswith("kvlist"): - # Python 3.5+ style key/value list in dict - kv_node = node[0] - l = list(kv_node) - length = len(l) - if kv_node[-1].kind.startswith("BUILD_MAP"): - length -= 1 - i = 0 - - # Respect line breaks from source - while i < length: - self.write(sep) - name = self.traverse(l[i], indent="") - if i > 0: - line_number = self.indent_if_source_nl( - line_number, self.indent + INDENT_PER_LEVEL[:-1] - ) - line_number = self.line_number - self.write(name, ": ") - value = self.traverse( - l[i + 1], indent=self.indent + (len(name) + 2) * " " - ) - self.write(value) - sep = ", " - if line_number != self.line_number: - sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1] - line_number = self.line_number - i += 2 - pass - pass - elif len(node) > 1 and node[1].kind.startswith("kvlist"): - # Python 3.0..3.4 style key/value list in dict - kv_node = node[1] - l = list(kv_node) - if len(l) > 0 and l[0].kind == "kv3": - # Python 3.2 does this - kv_node = node[1][0] - l = list(kv_node) - i = 0 - while i < len(l): - self.write(sep) - name = self.traverse(l[i + 1], indent="") - if i > 0: - line_number = self.indent_if_source_nl( - line_number, self.indent + INDENT_PER_LEVEL[:-1] - ) - pass - line_number = self.line_number - self.write(name, ": ") - value = self.traverse( - l[i], indent=self.indent + (len(name) + 2) * " " - ) - self.write(value) - sep = ", " - if line_number != self.line_number: - sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1] - line_number = self.line_number - else: - sep += " " - i += 3 - pass - pass - elif node[-1].kind.startswith("BUILD_CONST_KEY_MAP"): - # Python 3.6+ style const map - keys = node[-2].pattr - values = node[:-2] - # FIXME: Line numbers? - for key, value in zip(keys, values): - self.write(sep) - self.write(repr(key)) - line_number = self.line_number - self.write(":") - self.write(self.traverse(value[0])) - sep = ", " - if line_number != self.line_number: - sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1] - line_number = self.line_number - else: - sep += " " - pass - pass - if sep.startswith(",\n"): - self.write(sep[1:]) - pass - elif node[0].kind.startswith("dict_entry"): - assert self.version >= (3, 5) - template = ("%C", (0, len(node[0]), ", **")) - self.template_engine(template, node[0]) - sep = "" - elif node[-1].kind.startswith("BUILD_MAP_UNPACK") or node[ - -1 - ].kind.startswith("dict_entry"): - assert self.version >= (3, 5) - # FIXME: I think we can intermingle dict_comp's with other - # dictionary kinds of things. The most common though is - # a sequence of dict_comp's - kwargs = node[-1].attr - template = ("**%C", (0, kwargs, ", **")) - self.template_engine(template, node) - sep = "" - - pass - else: - # Python 2 style kvlist. Find beginning of kvlist. - indent = self.indent + " " - line_number = self.line_number - if node[0].kind.startswith("BUILD_MAP"): - if len(node) > 1 and node[1].kind in ("kvlist", "kvlist_n"): - kv_node = node[1] - else: - kv_node = node[1:] - self.kv_map(kv_node, sep, line_number, indent) - else: - sep = "" - opname = node[-1].kind - if self.is_pypy and self.version >= (3, 5): - if opname.startswith("BUILD_CONST_KEY_MAP"): - keys = node[-2].attr - # FIXME: DRY this and the above - for i in range(len(keys)): - key = keys[i] - value = self.traverse(node[i], indent="") - self.write(sep, key, ": ", value) - sep = ", " - if line_number != self.line_number: - sep += "\n" + self.indent + " " - line_number = self.line_number - pass - pass - pass - else: - if opname.startswith("kvlist"): - list_node = node[0] - else: - list_node = node - - assert list_node[-1].kind.startswith("BUILD_MAP") - for i in range(0, len(list_node) - 1, 2): - key = self.traverse(list_node[i], indent="") - value = self.traverse(list_node[i + 1], indent="") - self.write(sep, key, ": ", value) - sep = ", " - if line_number != self.line_number: - sep += "\n" + self.indent + " " - line_number = self.line_number - pass - pass - pass - elif opname.startswith("kvlist"): - kv_node = node[-1] - self.kv_map(node[-1], sep, line_number, indent) - - pass - pass - if sep.startswith(",\n"): - self.write(sep[1:]) - if node[0] != "dict_entry": - self.write("}") - self.indent_less(INDENT_PER_LEVEL) - self.prec = p - self.prune() - def template_engine(self, entry, startnode): """The format template interpetation engine. See the comment at the beginning of this module for the how we interpret format