11import ast
22import math
3+ import sys
34
5+ from python_minifier .ast_compare import compare_ast
46from python_minifier .expression_printer import ExpressionPrinter
57from python_minifier .transforms .suite_transformer import SuiteTransformer
68from python_minifier .util import is_ast_node
79
8-
910class FoldConstants (SuiteTransformer ):
1011 """
1112 Fold Constants if it would reduce the size of the source
@@ -36,61 +37,60 @@ def visit_BinOp(self, node):
3637 # It can also be slow to evaluate
3738 return node
3839
39- expression_printer = ExpressionPrinter ()
40-
40+ # Evaluate the expression
4141 try :
42- original_expression = expression_printer (node )
43- globals = {}
44- locals = {}
45- value = eval (original_expression , globals , locals )
46- except Exception as e :
42+ original_expression = unparse_expression (node )
43+ original_value = safe_eval (original_expression )
44+ except Exception :
4745 return node
4846
49- if isinstance (value , float ) and math .isnan (value ):
47+ # Choose the best representation of the value
48+ if isinstance (original_value , float ) and math .isnan (original_value ):
5049 # There is no nan literal.
51- new_node = ast .Call (func = ast .Name (id = 'float' , ctx = ast .Load ()), args = [ast .Str (s = 'nan' )], keywords = [])
52- elif isinstance (value , bool ):
53- new_node = ast .NameConstant (value = value )
54- elif isinstance (value , (int , float , complex )):
50+ # we could use float('nan'), but that complicates folding as it's not a Constant
51+ return node
52+ elif isinstance (original_value , bool ):
53+ new_node = ast .NameConstant (value = original_value )
54+ elif isinstance (original_value , (int , float , complex )):
5555 try :
56- if repr (value ).startswith ('-' ):
56+ if repr (original_value ).startswith ('-' ) and not sys . version_info < ( 3 , 0 ):
5757 # Represent negative numbers as a USub UnaryOp, so that the ast roundtrip is correct
58- new_node = ast .UnaryOp (op = ast .USub (), operand = ast .Num (n = - value ))
58+ new_node = ast .UnaryOp (op = ast .USub (), operand = ast .Num (n = - original_value ))
5959 else :
60- new_node = ast .Num (n = value )
60+ new_node = ast .Num (n = original_value )
6161 except Exception :
6262 # repr(value) failed, most likely due to some limit
6363 return node
6464 else :
6565 return node
6666
67- expression_printer = ExpressionPrinter ()
68- folded_expression = expression_printer (new_node )
67+ # Evaluate the new value representation
68+ try :
69+ folded_expression = unparse_expression (new_node )
70+ folded_value = safe_eval (folded_expression )
71+ except Exception as e :
72+ # This can happen if the value is too large to be represented as a literal
73+ # or if the value is unparsed as nan, inf or -inf - which are not valid python literals
74+ return node
6975
7076 if len (folded_expression ) >= len (original_expression ):
7177 # Result is not shorter than original expression
7278 return node
7379
80+ # Check the folded expression parses back to the same AST
7481 try :
75- globals = {'__builtins__' : {'float' : float }}
76- locals = {}
77- folded_value = eval (folded_expression , globals , locals )
78- except NameError as ne :
79- if ne .name in ['inf' , 'infj' , 'nan' ]:
80- # When the value is something like inf+0j the expression printer will print it that way, which is not valid Python.
81- # In python code it should be '1e999+0j', which parses as a BinOp that the expression printer can handle.
82- # It's not worth fixing the expression printer to handle this case, since it is unlikely to occur in real code.
83- return node
84-
85- # Some other NameError...
86- return node
82+ folded_ast = ast .parse (folded_expression , 'folded expression' , mode = 'eval' )
83+ compare_ast (new_node , folded_ast .body )
8784 except Exception :
85+ # This can happen if the printed value doesn't parse back to the same AST
86+ # e.g. complex numbers can be parsed as BinOp
8887 return node
8988
9089 # Check the folded value is the same as the original value
91- if not equal_value_and_type (folded_value , value ):
90+ if not equal_value_and_type (folded_value , original_value ):
9291 return node
9392
93+ # New representation is shorter and has the same value, so use it
9494 return self .add_child (new_node , node .parent , node .namespace )
9595
9696def equal_value_and_type (a , b ):
@@ -101,3 +101,14 @@ def equal_value_and_type(a, b):
101101 return False
102102
103103 return a == b
104+
105+ def safe_eval (expression ):
106+ globals = {}
107+ locals = {}
108+
109+ # This will return the value, or could raise an exception
110+ return eval (expression , globals , locals )
111+
112+ def unparse_expression (node ):
113+ expression_printer = ExpressionPrinter ()
114+ return expression_printer (node )
0 commit comments