1
1
"""Implementation of the variable-replacement operator."""
2
2
from .operator import Operator
3
3
from .example import Example
4
- from parso .python .tree import Number , ExprStmt , Leaf
4
+ from parso .python .tree import Number , ExprStmt , Leaf , PythonNode , IfStmt
5
5
from random import randint
6
6
7
7
@@ -13,38 +13,103 @@ def __init__(self, cause_variable, effect_variable=None):
13
13
self .effect_variable = effect_variable
14
14
15
15
def mutation_positions (self , node ):
16
- """Mutate usages of the specified cause variable. If an effect variable is also
17
- specified, then only mutate usages of the cause variable in definitions of the
18
- effect variable."""
19
-
20
- if isinstance ( node , ExprStmt ):
21
- # Confirm that name node is used on right hand side of the expression
22
- cause_variables = list ( self . _get_causes_from_expr_node ( node ))
23
- cause_variable_names = [ cause_variable . value for cause_variable in cause_variables ]
24
- if self . cause_variable in cause_variable_names :
25
- mutation_position = ( node . start_pos , node . end_pos )
26
-
27
- # If an effect variable is specified, confirm that it appears on left hand
28
- # side of the expression
29
- if self . effect_variable :
30
- effect_variable_names = [ v . value for v in node . get_defined_names ()]
31
- if self . effect_variable in effect_variable_names :
32
- yield mutation_position
33
-
34
- # If no effect variable is specified, any occurrence of the cause variable
35
- # on the right hand side of an expression can be mutated
36
- else :
37
- yield mutation_position
16
+ """Replace usages of the cause variable with a constant to remove its causal effect on the effect variable.
17
+
18
+ This method identifies all 'suites' that are used to define the value of the effect variable, where a 'suite'
19
+ is a body of code that follows an if statement. The entire suite is later replaced with a copy in which all
20
+ usages of the cause variable are replaced with a randomly sampled numeric constant.
21
+
22
+ :param node: node of parso parse tree that is a potential candidate for mutation.
23
+ :return (start_pos, end_pos): A pair representing the position in the abstract syntax tree to mutate (only if
24
+ the mutation operator is applicable at this position).
25
+ """
26
+
27
+ if isinstance ( node , PythonNode ) and node . type == "suite" and isinstance ( node . parent , IfStmt ):
28
+
29
+ # This node is the body of an if-statement.
30
+ # We are only interested in the body of the outer-most if statements that have no else branch.
31
+ if 'else' not in node . parent . children :
32
+ causes , effects = self . _get_cause_and_effect_nodes_from_suite_node ( node )
33
+ named_causes = [ cause . value for cause in causes ]
34
+ named_effects = [ effect . value for effect in effects ]
35
+ if ( self . effect_variable in named_effects ) and ( self . cause_variable in named_causes ):
36
+ print ( f" { self . cause_variable } --> { self . effect_variable } " )
37
+ yield node . start_pos , node . end_pos
38
38
39
39
def mutate (self , node , index ):
40
- """Replace cause variable with random constant."""
41
- assert isinstance (node , ExprStmt )
42
- # Find all occurrences of the cause node in the ExprStatement and replace with a random number
43
- rhs = node .get_rhs ()
44
- new_rhs = self ._replace_named_variable_in_expr (rhs , self .cause_variable )
45
- node .children [2 ] = new_rhs
40
+ """Replace 'suite' defining the effect variable with a copy in which the cause variable is absent.
41
+
42
+ There are three parts of the 'suite' in which the cause variable can have an effect on the effect variable:
43
+ (1) The predicate of the if statement: if (X1 + X2 + X3) >= 10:
44
+ (2) The statement of the true branch: Y1 = X2 + 10
45
+ else:
46
+ (3) The statement of the false branch: Y1 = X2 + X3 + 4
47
+
48
+ In the above example, X1 --> Y1 via the predicate, X2 --> Y1 via the true and false branches, and X3 --> Y1 via
49
+ only the false branch.
50
+
51
+ This method finds usages of the specified cause variable in either (1), (2), or (3), and replaces them
52
+ simultaneously with a randomly sampled numeric constant.
53
+ """
54
+ assert isinstance (node , PythonNode ) and node .type == "suite" , "Error: Node is not a suite."
55
+ print ("PRE-MUTATION CODE: " , node .get_code ())
56
+ no_causes_node = self ._replace_causes_in_suite (node )
57
+ print ("POST-MUTATION CODE: " , no_causes_node .get_code ())
58
+ return no_causes_node
59
+
60
+ def _replace_causes_in_suite (self , node ):
61
+ expr_nodes = []
62
+ for child_node in node .children :
63
+ # Expression/statement
64
+ if isinstance (child_node , ExprStmt ):
65
+ expr_nodes .append (child_node )
66
+ elif isinstance (child_node , PythonNode ):
67
+ expr_nodes .append (child_node .children [0 ])
68
+ # If statement
69
+ elif isinstance (child_node , IfStmt ):
70
+ for grandchild_node in child_node .children :
71
+ # Predicate of if statement
72
+ if isinstance (grandchild_node , PythonNode ) and grandchild_node .type == "comparison" :
73
+ arith_expr = grandchild_node .children [0 ]
74
+ new_arith_expr = self ._replace_named_variable_in_expr (arith_expr , self .cause_variable )
75
+ grandchild_node .children [0 ] = new_arith_expr
76
+ # Expressions/statements in true/false branch of if statement
77
+ elif isinstance (grandchild_node , PythonNode ) and grandchild_node .type == "suite" :
78
+ grandchild_node = self ._replace_named_variable_in_expr (grandchild_node , self .cause_variable )
79
+
80
+ # Replace usages of cause variable in expression nodes
81
+ for expr_node in expr_nodes :
82
+ rhs = expr_node .get_rhs ()
83
+ new_rhs = self ._replace_named_variable_in_expr (rhs , self .cause_variable )
84
+ expr_node .children [2 ] = new_rhs
85
+
46
86
return node
47
87
88
+ def _get_cause_and_effect_nodes_from_suite_node (self , suite_node ):
89
+ causes = [] # Variables that appear on RHS of expressions/statements OR in the predicate of an if statement
90
+ effects = [] # Variables appearing on LHS of expressions/statements
91
+ expr_nodes = [] # These are expressions/statements
92
+
93
+ for child_node in suite_node .children :
94
+ if isinstance (child_node , ExprStmt ):
95
+ expr_nodes .append (child_node )
96
+ elif isinstance (child_node , PythonNode ):
97
+ expr_nodes .append (child_node .children [0 ])
98
+ elif isinstance (child_node , IfStmt ):
99
+ for grandchild_node in child_node .children :
100
+ if isinstance (grandchild_node , PythonNode ) and grandchild_node .type == "suite" :
101
+ gc_causes , gc_effects = self ._get_cause_and_effect_nodes_from_suite_node (grandchild_node )
102
+ causes += gc_causes
103
+ effects += gc_effects
104
+ elif isinstance (grandchild_node , PythonNode ) and grandchild_node .type == "comparison" :
105
+ gc_comparison_causes = list (self ._flatten_comparison (grandchild_node ))
106
+ causes += gc_comparison_causes
107
+
108
+ for expr_node in expr_nodes :
109
+ causes += list (self ._get_causes_from_expr_node (expr_node ))
110
+ effects += expr_node .get_defined_names ()
111
+ return causes , effects
112
+
48
113
def _get_causes_from_expr_node (self , expr_node ):
49
114
rhs = expr_node .get_rhs ().children
50
115
return self ._flatten_expr (rhs )
@@ -56,16 +121,39 @@ def _flatten_expr(self, expr):
56
121
item_to_flatten = item .children
57
122
except AttributeError :
58
123
item_to_flatten = item
59
- #
60
124
try :
61
125
yield from self ._flatten_expr (item_to_flatten )
62
126
except TypeError :
63
127
yield item_to_flatten
64
128
129
+ def _flatten_comparison (self , conditional ):
130
+ try :
131
+ # PythonNode (has children)
132
+ to_iterate = conditional .children
133
+ except AttributeError :
134
+ # Not PythonNode (has no children)
135
+ to_iterate = conditional
136
+
137
+ for child_node in to_iterate :
138
+ try :
139
+ # If the current node has children, flatten these
140
+ item_to_flatten = child_node .children
141
+ except AttributeError :
142
+ # Otherwise flatted the node itself
143
+ item_to_flatten = child_node
144
+
145
+ try :
146
+ yield from self ._flatten_comparison (item_to_flatten )
147
+ except TypeError :
148
+ # Non-iterable (leaf node)
149
+ yield item_to_flatten
150
+
65
151
def _replace_named_variable_in_expr (self , node , variable_name ):
66
152
if isinstance (node , Leaf ):
67
153
if node .value == variable_name :
68
- return Number (start_pos = node .start_pos , value = str (randint (- 100 , 100 )))
154
+ print (node )
155
+ print (node .start_pos , node .end_pos )
156
+ return Number (start_pos = node .start_pos , value = str (randint (- 100 , 100 )), prefix = ' ' )
69
157
else :
70
158
return node
71
159
0 commit comments