# Copyright (c) 2017-2020 by Rocky Bernstein
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
"""Constants and initial table values used in pysource.py and fragments.py"""
import re, sys
from uncompyle6.parsers.treenode import SyntaxTree
from uncompyle6 import PYTHON3
from uncompyle6.scanners.tok import Token, NoneToken
if PYTHON3:
minint = -sys.maxsize - 1
maxint = sys.maxsize
else:
minint = -sys.maxint - 1
maxint = sys.maxint
# Operator precidence See
# https://docs.python.org/2/reference/expressions.html#operator-precedence
# or
# https://docs.python.org/3/reference/expressions.html#operator-precedence
# for a list. We keep the same top-to-botom order here as in the above links,
# so we start with low precedence (high values) and go down in value.
# Things at the bottom of this list below with high precedence (low value) will
# tend to have parenthesis around them. Things at the top
# of the list will tend not to have parenthesis around them.
# Note: The values in this table are even numbers. Inside
# various templates we use odd values. Avoiding equal-precedent comparisons
# avoids ambiguity what to do when the precedence is equal.
# The precidence of a key below applies the key, a node, and the its
# *parent*. A node however sometimes sets the precidence for its
# children. For example, "call" has precidence 2 so we don't get
# additional the additional parenthesis of: ".. op (call())". However
# for call's children, it parameters, we set the the precidence high,
# say to 100, to make sure we avoid additional prenthesis in
# call((.. op ..)).
PRECEDENCE = {
"named_expr": 40, # :=
"yield": 38, # Needs to be below named_expr
"yield_from": 38,
"_mklambda": 30,
"mklambda": 30,
"if_exp": 28, # IfExp ( a if x else b)
"if_exp_lambda": 28, # IfExp involving a lambda expression
"if_exp_not_lambda": 28, # negated IfExp involving a lambda expression
"if_exp_not": 28,
"if_exp_true": 28, # (a if True else b)
"if_exp_ret": 28,
"or": 26, # Boolean OR
"ret_or": 26,
"and": 24, # Boolean AND
"ret_and": 24,
"not": 22, # Boolean NOT
"unary_not": 22, # Boolean NOT
"compare": 20, # in, not in, is, is not, <, <=, >, >=, !=, ==
"BINARY_AND": 14, # Bitwise AND
"BINARY_OR": 18, # Bitwise OR
"BINARY_XOR": 16, # Bitwise XOR
"BINARY_LSHIFT": 12, # Shifts <<
"BINARY_RSHIFT": 12, # Shifts >>
"BINARY_ADD": 10, # -
"BINARY_SUBTRACT": 10, # +
"BINARY_DIVIDE": 8, # /
"BINARY_FLOOR_DIVIDE": 8, # //
"BINARY_MATRIX_MULTIPLY": 8, # @
"BINARY_MODULO": 8, # Remainder, %
"BINARY_MULTIPLY": 8, # *
"BINARY_TRUE_DIVIDE": 8, # Division /
"unary_op": 6, # +x, -x, ~x
"BINARY_POWER": 4, # Exponentiation, *
"await_expr": 3, # await x, *
"attribute": 2, # x.attribute
"buildslice2": 2, # x[index]
"buildslice3": 2, # x[index:index]
"call": 2, # x(arguments...)
"delete_subscript": 2,
"slice0": 2,
"slice1": 2,
"slice2": 2,
"slice3": 2,
"store_subscript": 2,
"subscript": 2,
"subscript2": 2,
"dict": 0, # {expressions...}
"dict_comp": 0,
"generator_exp": 0, # (expressions...)
"list": 0, # [expressions...]
"list_comp": 0,
"set_comp": 0,
"set_comp_expr": 0,
"unary_convert": 0,
}
LINE_LENGTH = 80
# Some parse trees created below are used for comparing code
# fragments (like "return None" at the end of functions).
RETURN_LOCALS = SyntaxTree(
"return",
[
SyntaxTree("ret_expr", [SyntaxTree("expr", [Token("LOAD_LOCALS")])]),
Token("RETURN_VALUE"),
],
)
NONE = SyntaxTree("expr", [NoneToken])
RETURN_NONE = SyntaxTree("stmt", [SyntaxTree("return", [NONE, Token("RETURN_VALUE")])])
PASS = SyntaxTree(
"stmts", [SyntaxTree("sstmt", [SyntaxTree("stmt", [SyntaxTree("pass", [])])])]
)
ASSIGN_DOC_STRING = lambda doc_string, doc_load: SyntaxTree(
"stmt",
[
SyntaxTree(
"assign",
[
SyntaxTree(
"expr", [Token(doc_load, pattr=doc_string, attr=doc_string)]
),
SyntaxTree("store", [Token("STORE_NAME", pattr="__doc__")]),
],
)
],
)
NAME_MODULE = SyntaxTree(
"assign",
[
SyntaxTree(
"expr", [Token("LOAD_NAME", pattr="__name__", offset=0, has_arg=True)]
),
SyntaxTree(
"store", [Token("STORE_NAME", pattr="__module__", offset=3, has_arg=True)]
),
],
)
# God intended \t, but Python has decided to use 4 spaces.
# If you want real tabs, use Go.
# TAB = "\t"
TAB = " " * 4
INDENT_PER_LEVEL = " " # additional intent per pretty-print level
TABLE_R = {
"STORE_ATTR": ("%c.%[1]{pattr}", 0),
"DELETE_ATTR": ("%|del %c.%[-1]{pattr}\n", 0),
}
TABLE_R0 = {
# "BUILD_LIST": ( "[%C]", (0,-1,", ") ),
# "BUILD_TUPLE": ( "(%C)", (0,-1,", ") ),
# "CALL_FUNCTION": ( "%c(%P)", 0, (1,-1,", ") ),
}
TABLE_DIRECT = {
"BINARY_ADD": ("+",),
"BINARY_SUBTRACT": ("-",),
"BINARY_MULTIPLY": ("*",),
"BINARY_DIVIDE": ("/",),
"BINARY_MATRIX_MULTIPLY": ("@",),
"BINARY_TRUE_DIVIDE": ("/",), # Not in <= 2.1
"BINARY_FLOOR_DIVIDE": ("//",),
"BINARY_MODULO": ("%%",),
"BINARY_POWER": ("**",),
"BINARY_LSHIFT": ("<<",),
"BINARY_RSHIFT": (">>",),
"BINARY_AND": ("&",),
"BINARY_OR": ("|",),
"BINARY_XOR": ("^",),
"INPLACE_ADD": ("+=",),
"INPLACE_SUBTRACT": ("-=",),
"INPLACE_MULTIPLY": ("*=",),
"INPLACE_MATRIX_MULTIPLY": ("@=",),
"INPLACE_DIVIDE": ("/=",),
"INPLACE_TRUE_DIVIDE": ("/=",), # Not in <= 2.1; 2.6 generates INPLACE_DIVIDE only?
"INPLACE_FLOOR_DIVIDE": ("//=",),
"INPLACE_MODULO": ("%%=",),
"INPLACE_POWER": ("**=",),
"INPLACE_LSHIFT": ("<<=",),
"INPLACE_RSHIFT": (">>=",),
"INPLACE_AND": ("&=",),
"INPLACE_OR": ("|=",),
"INPLACE_XOR": ("^=",),
# bin_op (formerly "binary_expr") is the Python AST BinOp
"bin_op": ("%c %c %c", 0, (-1, "binary_operator"), (1, "expr")),
"UNARY_POSITIVE": ("+",),
"UNARY_NEGATIVE": ("-",),
"UNARY_INVERT": ("~"),
# unary_op (formerly "unary_expr") is the Python AST UnaryOp
"unary_op": ("%c%c", (1, "unary_operator"), (0, "expr")),
"unary_not": ("not %c", (0, "expr")),
"unary_convert": ("`%c`", (0, "expr"),),
"get_iter": ("iter(%c)", (0, "expr"),),
"slice0": ("%c[:]", (0, "expr"),),
"slice1": ("%c[%p:]", (0, "expr"), (1, 100)),
"slice2": ("%c[:%p]", (0, "expr"), (1, 100)),
"slice3": ("%c[%p:%p]", (0, "expr"), (1, 100), (2, 100)),
"IMPORT_FROM": ("%{pattr}",),
"IMPORT_NAME_ATTR": ("%{pattr}",),
"attribute": ("%c.%[1]{pattr}", (0, "expr")),
"LOAD_STR": ("%{pattr}",),
"LOAD_FAST": ("%{pattr}",),
"LOAD_NAME": ("%{pattr}",),
"LOAD_CLASSNAME": ("%{pattr}",),
"LOAD_GLOBAL": ("%{pattr}",),
"LOAD_DEREF": ("%{pattr}",),
"LOAD_LOCALS": ("locals()",),
"LOAD_ASSERT": ("%{pattr}",),
"DELETE_FAST": ("%|del %{pattr}\n",),
"DELETE_NAME": ("%|del %{pattr}\n",),
"DELETE_GLOBAL": ("%|del %{pattr}\n",),
"delete_subscript": (
"%|del %p[%c]\n",
(0, "expr", PRECEDENCE["subscript"]),
(1, "expr"),
),
"subscript": ("%p[%c]", (0, "expr", PRECEDENCE["subscript"]), (1, "expr")),
"subscript2": ("%p[%c]", (0, "expr", PRECEDENCE["subscript"]), (1, "expr")),
"store_subscript": ("%p[%c]", (0, "expr", PRECEDENCE["subscript"]), (1, "expr")),
"STORE_FAST": ("%{pattr}",),
"STORE_NAME": ("%{pattr}",),
"STORE_GLOBAL": ("%{pattr}",),
"STORE_DEREF": ("%{pattr}",),
"unpack": ("%C%,", (1, maxint, ", ")),
# This nonterminal we create on the fly in semantic routines
"unpack_w_parens": ("(%C%,)", (1, maxint, ", ")),
# This nonterminal we create on the fly in semantic routines
"attribute_w_parens": ("(%c).%[1]{pattr}", (0, "expr")),
# This nonterminal we create on the fly in semantic routines
"store_w_parens": ("(%c).%[1]{pattr}", (0, "expr")),
"unpack_list": ("[%C]", (1, maxint, ", ")),
"build_tuple2": ("%P", (0, -1, ", ", 100)),
"list_iter": ("%c", 0),
"list_for": (" for %c in %c%c", 2, 0, 3),
"list_if": (" if %p%c", (0, "expr", 27), 2),
"list_if_not": (" if not %p%c", (0, "expr", PRECEDENCE["unary_not"]), 2),
"lc_body": ("",), # ignore when recursing
"comp_iter": ("%c", 0),
"comp_if": (" if %c%c", 0, 2),
"comp_if_not": (" if not %p%c", (0, "expr", PRECEDENCE["unary_not"]), 2),
"comp_body": ("",), # ignore when recusing
"set_comp_body": ("%c", 0),
"gen_comp_body": ("%c", 0),
"dict_comp_body": ("%c:%c", 1, 0),
"assign": ("%|%c = %p\n", -1, (0, 200)),
# The 2nd parameter should have a = suffix.
# There is a rule with a 4th parameter "store"
# which we don't use here.
"aug_assign1": ("%|%c %c %c\n", 0, 2, 1),
"aug_assign2": ("%|%c.%[2]{pattr} %c %c\n", 0, -3, -4),
"designList": ("%c = %c", 0, -1),
"and": ("%c and %c", 0, 2),
"ret_and": ("%c and %c", 0, 2),
"and2": ("%c", 3),
"or": ("%p or %p", (0, PRECEDENCE["or"]), (1, PRECEDENCE["or"])),
"ret_or": ("%c or %c", 0, 2),
"if_exp": ("%p if %c else %c", (2, "expr", 27), 0, 4),
"if_exp_lambda": ("%p if %c else %c", (2, "expr", 27), (0, "expr"), 4),
"if_exp_true": ("%p if 1 else %c", (0, "expr", 27), 2),
"if_exp_ret": ("%p if %p else %p", (2, 27), (0, 27), (-1, 27)),
"if_exp_not": (
"%p if not %p else %p",
(2, 27),
(0, "expr", PRECEDENCE["unary_not"]),
(4, 27),
),
"if_exp_not_lambda": ("%p if not %c else %c", (2, "expr", 27), 0, 4),
"compare_single": ('%p %[-1]{pattr.replace("-", " ")} %p', (0, 19), (1, 19)),
"compare_chained": ("%p %p", (0, 29), (1, 30)),
"compare_chained1": ('%[3]{pattr.replace("-", " ")} %p %p', (0, 19), (-2, 19)),
"compare_chained2": ('%[1]{pattr.replace("-", " ")} %p', (0, 19)),
# "classdef": (), # handled by n_classdef()
# A custom rule in n_function def distinguishes whether to call this or
# function_def_async
"function_def": ("\n\n%|def %c\n", -2), # -2 to handle closures
"function_def_deco": ("\n\n%c", 0),
"mkfuncdeco": ("%|@%c\n%c", 0, 1),
# A custom rule in n_function def distinguishes whether to call this or
# function_def_async
"mkfuncdeco0": ("%|def %c\n", 0),
"classdefdeco": ("\n\n%c", 0),
"classdefdeco1": ("%|@%c\n%c", 0, 1),
"kwarg": ("%[0]{pattr}=%c", 1), # Change when Python 2 does LOAD_STR
"kwargs": ("%D", (0, maxint, ", ")),
"kwargs1": ("%D", (0, maxint, ", ")),
"assert_expr_or": ("%c or %c", 0, 2),
"assert_expr_and": ("%c and %c", 0, 2),
"print_items_stmt": ("%|print %c%c,\n", 0, 2), # Python 2 only
"print_items_nl_stmt": ("%|print %c%c\n", 0, 2),
"print_item": (", %c", 0),
"print_nl": ("%|print\n",),
"print_to": ("%|print >> %c, %c,\n", 0, 1),
"print_to_nl": ("%|print >> %c, %c\n", 0, 1),
"print_nl_to": ("%|print >> %c\n", 0),
"print_to_items": ("%C", (0, 2, ", ")),
# This is only generated by transform
# it is a string at the beginning of a function that is *not* a docstring
# 3.7 test_fstring.py tests for this kind of crap.
# For compatibility with older Python, we'll use "%" instead of
# a format string.
"string_at_beginning": ('%|"%%s" %% %c\n', 0),
"call_stmt": ("%|%p\n", (0, 200)),
"break": ("%|break\n",),
"continue": ("%|continue\n",),
"raise_stmt0": ("%|raise\n",),
"raise_stmt1": ("%|raise %c\n", 0),
"raise_stmt3": ("%|raise %c, %c, %c\n", 0, 1, 2),
# "yield": ( "yield %c", 0),
# Note: we have a custom rule, which calls when we don't
# have "return None"
"return": ( "%|return %c\n", 0),
"return_if_stmt": ("return %c\n", 0),
"ifstmt": (
"%|if %c:\n%+%c%-",
0, # "testexpr" or "testexpr_then"
1, # "_ifstmts_jump" or "return_stmts"
),
"iflaststmt": ("%|if %c:\n%+%c%-", 0, 1),
"iflaststmtl": ("%|if %c:\n%+%c%-", 0, 1),
"testtrue": ("not %p", (0, PRECEDENCE["unary_not"])),
# Generally the args here are 0: (some sort of) "testexpr",
# 1: (some sort of) "cstmts_opt",
# 2 or 3: "else_suite"
# But unfortunately there are irregularities, For example, 2.6- uses "testexpr_then"
# and sometimes "cstmts" instead of "cstmts_opt" happens.
# Down the line we might isolate these into version-specific rules.
"ifelsestmt": ("%|if %c:\n%+%c%-%|else:\n%+%c%-", 0, 1, 3),
"ifelsestmtc": ("%|if %c:\n%+%c%-%|else:\n%+%c%-", 0, 1, 3),
"ifelsestmtl": ("%|if %c:\n%+%c%-%|else:\n%+%c%-", 0, 1, 3),
# These are created only via transformation
"ifelifstmt": ("%|if %c:\n%+%c%-%c", 0, 1, 3), # "testexpr" or "testexpr_then"
"elifelifstmt": ("%|elif %c:\n%+%c%-%c", 0, 1, 3),
"elifstmt": ("%|elif %c:\n%+%c%-", 0, 1),
"elifelsestmt": ("%|elif %c:\n%+%c%-%|else:\n%+%c%-", 0, 1, 3),
"ifelsestmtr": ("%|if %c:\n%+%c%-%|else:\n%+%c%-", 0, 1, 2),
"ifelsestmtr2": ("%|if %c:\n%+%c%-%|else:\n%+%c%-\n\n", 0, 1, 3), # has COME_FROM
"elifelsestmtr": ("%|elif %c:\n%+%c%-%|else:\n%+%c%-\n\n", 0, 1, 2),
"elifelsestmtr2": (
"%|elif %c:\n%+%c%-%|else:\n%+%c%-\n\n",
0,
1,
3,
), # has COME_FROM
"whileTruestmt": ("%|while True:\n%+%c%-\n\n", 1),
"whilestmt": ("%|while %c:\n%+%c%-\n\n", 1, 2),
"while1stmt": ("%|while 1:\n%+%c%-\n\n", 1),
"while1elsestmt": ("%|while 1:\n%+%c%-%|else:\n%+%c%-\n\n", 1, -2),
"whileelsestmt": ("%|while %c:\n%+%c%-%|else:\n%+%c%-\n\n", 1, 2, -2),
"whileelsestmt2": ("%|while %c:\n%+%c%-%|else:\n%+%c%-\n\n", 1, 2, -3),
"whileelselaststmt": ("%|while %c:\n%+%c%-%|else:\n%+%c%-", 1, 2, -2),
# Note: Python 3.8+ changes this
"for": ("%|for %c in %c:\n%+%c%-\n\n", (3, "store"), (1, "expr"), (4, "for_block")),
"forelsestmt": (
"%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n",
(3, "store"),
(1, "expr"),
(4, "for_block"),
-2,
),
"forelselaststmt": (
"%|for %c in %c:\n%+%c%-%|else:\n%+%c%-",
(3, "store"),
(1, "expr"),
(4, "for_block"),
-2,
),
"forelselaststmtl": (
"%|for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n",
(3, "store"),
(1, "expr"),
(4, "for_block"),
-2,
),
"try_except": ("%|try:\n%+%c%-%c\n\n", 1, 3),
"tryelsestmt": ("%|try:\n%+%c%-%c%|else:\n%+%c%-\n\n", 1, 3, 4),
"tryelsestmtc": ("%|try:\n%+%c%-%c%|else:\n%+%c%-", 1, 3, 4),
"tryelsestmtl": ("%|try:\n%+%c%-%c%|else:\n%+%c%-", 1, 3, 4),
# Note: this is generated generated by grammar rules but in this phase.
"tf_try_except": ("%c%-%c%+", 1, 3),
"tf_tryelsestmt": ("%c%-%c%|else:\n%+%c", 1, 3, 4),
"tryfinallystmt": ("%|try:\n%+%c%-%|finally:\n%+%c%-\n\n", 1, 5),
"except": ("%|except:\n%+%c%-", 3),
"except_cond1": ("%|except %c:\n", 1),
"except_cond2": ("%|except %c as %c:\n", (1, "expr"), (5, "store")),
"except_suite": ("%+%c%-%C", 0, (1, maxint, "")),
# In Python 3.6+, this is more complicated in the presence of "returns"
"except_suite_finalize": ("%+%c%-%C", 1, (3, maxint, "")),
"pass": ("%|pass\n",),
"STORE_FAST": ("%{pattr}",),
"kv": ("%c: %c", 3, 1),
"kv2": ("%c: %c", 1, 2),
"import": ("%|import %c\n", 2),
"importlist": ("%C", (0, maxint, ", ")),
"import_from": ("%|from %[2]{pattr} import %c\n", (3, "importlist")),
"import_from_star": ("%|from %[2]{pattr} import *\n",),
}
MAP_DIRECT = (TABLE_DIRECT,)
MAP_R0 = (TABLE_R0, -1, 0)
MAP_R = (TABLE_R, -1)
MAP = {
"stmt": MAP_R,
"call": MAP_R,
"del_stmt": MAP_R,
"store": MAP_R,
"exprlist": MAP_R0,
}
ASSIGN_TUPLE_PARAM = lambda param_name: SyntaxTree(
"expr", [Token("LOAD_FAST", pattr=param_name)]
)
escape = re.compile(
r"""
(?P [^%]* )
% ( \[ (?P -? \d+ ) \] )?
((?P [^{] ) |
( [{] (?P [^}]* ) [}] ))
""",
re.VERBOSE,
)