potluck.patterns
Common code patterns for use with the mast
AST matching module.
patterns.py
1""" 2Common code patterns for use with the `mast` AST matching module. 3 4patterns.py 5""" 6 7 8#-------------------# 9# Pattern constants # 10#-------------------# 11 12# Common mast pattern for a for loop. 13FOR_PATTERN = ''' 14for _ in _: 15 ___ 16''' 17""" 18`potluck.mast` pattern for simple `for` loop. 19""" 20 21 22# For loop w/ else (above pattern doesn't match this). 23FOR_ELSE_PATTERN = ''' 24for _ in _: 25 ___ 26else: 27 ___ 28''' 29""" 30`potluck.mast` pattern for a `for` loop with an `else` clause. 31""" 32 33 34# Both of the above... 35ALL_FOR_PATTERNS = [ FOR_PATTERN, FOR_ELSE_PATTERN ] 36""" 37`potluck.mast` patterns for all `for` loop variants. 38""" 39 40 41# Pattern for a while loop. 42WHILE_PATTERN = ''' 43while _: 44 ___ 45''' 46""" 47A `potluck.mast` pattern for a simple `while` loop. 48""" 49 50 51# While loop with an else. 52WHILE_ELSE_PATTERN = ''' 53while _: 54 ___ 55else: 56 ___ 57''' 58""" 59A `potluck.mast` pattern for `while`/`else`. 60""" 61 62 63# Both of the above... 64ALL_WHILE_PATTERNS = [ WHILE_PATTERN, WHILE_ELSE_PATTERN ] 65""" 66`potluck.mast` patterns for all `while` loop variants. 67""" 68 69 70# List comprehensions 71SIMPLE_COMPREHENSION_PATTERN = ''' 72[ _ for _ in _ ] 73''' 74""" 75A `potluck.mast` pattern for a single for loop comprehension without a filter. 76""" 77 78 79FILTER_COMPREHENSION_PATTERN = ''' 80[ _ for _ in _ if _ ] 81''' 82""" 83A `potluck.mast` pattern for a single for loop comprehension with a filter. 84""" 85 86 87DOUBLE_COMPREHENSION_PATTERN = ''' 88[ _ for _ in _ for _ in _ ] 89''' 90""" 91A `potluck.mast` pattern for a double for loop comprehension without a filter. 92""" 93 94 95DOUBLE_FILTER_COMPREHENSION_PATTERN = ''' 96[ _ for _ in _ for _ in _ if _ ] 97''' 98""" 99A `potluck.mast` pattern for a double for loop comprehension with a filter. 100""" 101 102 103TRIPLE_COMPREHENSION_PATTERN = ''' 104[ _ for _ in _ for _ in _ for _ in _ ] 105''' 106""" 107A `potluck.mast` pattern for a triple for loop comprehension without a filter. 108""" 109 110 111TRIPLE_FILTER_COMPREHENSION_PATTERN = ''' 112[ _ for _ in _ for _ in _ for _ in _ if _ ] 113''' 114""" 115A `potluck.mast` pattern for a triple for loop comprehension with a filter. 116""" 117 118 119SINGLE_LOOP_COMPREHENSION_PATTERNS = [ 120 SIMPLE_COMPREHENSION_PATTERN, 121 FILTER_COMPREHENSION_PATTERN 122] 123""" 124`potluck.mast patterns for just single list comprehensions with and 125without filtering. 126""" 127 128ALL_REASONABLE_COMPREHENSION_PATTERNS = [ 129 SIMPLE_COMPREHENSION_PATTERN, 130 FILTER_COMPREHENSION_PATTERN, 131 DOUBLE_COMPREHENSION_PATTERN, 132 DOUBLE_FILTER_COMPREHENSION_PATTERN, 133 TRIPLE_COMPREHENSION_PATTERN, 134 TRIPLE_FILTER_COMPREHENSION_PATTERN, 135] 136""" 137`potluck.mast` patterns for single, double, and triple list comprehensions 138with and without filtering. 139 140Note: does not include dictionary comprehensions (see below) 141 142We will simply hope that students do not use quadruple comprehensions... 143""" 144 145ALL_SINGLE_LOOP_GENERATOR_EXPRESSION_PATTERNS = ( 146 SINGLE_LOOP_COMPREHENSION_PATTERNS 147 + [ 148 p.replace('[', '(').replace(']', ')') 149 for p in SINGLE_LOOP_COMPREHENSION_PATTERNS 150 ] # generator expressions 151 + [ 152 p.replace('[', '{').replace(']', '}') 153 for p in SINGLE_LOOP_COMPREHENSION_PATTERNS 154 ] # set comprehensions 155 + [ 156 p.replace('[ _', '{ _: _').replace(']', '}') 157 for p in SINGLE_LOOP_COMPREHENSION_PATTERNS 158 ] # dictionary comprehensions 159) 160""" 161`potluck.mast` patterns for single-loop comprehensions including set and 162dictionary comprehensions and generator expressions. 163""" 164 165ALL_GENERATOR_EXPRESSION_PATTERNS = ( 166 ALL_REASONABLE_COMPREHENSION_PATTERNS 167 + [ 168 p.replace('[', '(').replace(']', ')') 169 for p in ALL_REASONABLE_COMPREHENSION_PATTERNS 170 ] # generator expressions 171 + [ 172 p.replace('[', '{').replace(']', '}') 173 for p in ALL_REASONABLE_COMPREHENSION_PATTERNS 174 ] # set comprehensions 175 + [ 176 p.replace('[ _', '{ _: _').replace(']', '}') 177 for p in ALL_REASONABLE_COMPREHENSION_PATTERNS 178 ] # dictionary comprehensions 179) 180""" 181Patterns for up to triple-loop list comprehensions, plus equivalent 182generator expressions and set- and dictionary-comprehensions. 183""" 184 185 186# Combination patterns for loops and loops + comprehensions 187ALL_FOR_AND_WHILE_LOOP_PATTERNS = ALL_FOR_PATTERNS + ALL_WHILE_PATTERNS 188""" 189`potluck.mast` patterns for all while/for loop variants. 190""" 191ALL_LOOP_AND_COMPREHENSION_PATTERNS = ( 192 ALL_FOR_AND_WHILE_LOOP_PATTERNS 193 + ALL_GENERATOR_EXPRESSION_PATTERNS 194) 195""" 196`potluck.mast` patterns for all reasonable loop/comprehension types, 197although highly-nested comprehensions are not included. 198""" 199ALL_SINGLE_LOOP_AND_COMPREHENSION_PATTERNS = ( 200 ALL_FOR_AND_WHILE_LOOP_PATTERNS 201 + ALL_SINGLE_LOOP_GENERATOR_EXPRESSION_PATTERNS 202) 203""" 204`potluck.mast` patterns for all loop/comprehension patterns that are 205single loops, including generator expressions, set comprehensions, and 206dictionary comprehensions. 207""" 208 209 210IF_PATTERN = ''' 211if _: 212 ___ 213else: 214 ___ 215''' 216""" 217Common mast pattern for an if statement (matches even ifs that don't have 218an else, we think; note this also matches elifs, which Python treats as 219nested ifs). 220""" 221 222 223ALL_DEF_PATTERNS = [ 224 "def _f_(___):\n ___", 225 "def _f_(___, ___=_):\n ___", 226 "def _f_(___=_):\n ___", 227] 228""" 229Patterns for function definitions (without keyword variables, with both, 230and with only keyword variables). These patterns bind 'f' as the name of 231the function that's defined. 232""" 233 234 235FUNCTION_CALL_PATTERNS = [ 236 "_f_(___)", 237 "_f_(___, ___=_)", 238 "_f_(___=_)", 239] 240""" 241Patterns for function calls. These bind 'f' as the function (possibly a 242function expression). 243""" 244 245 246METHOD_CALL_PATTERNS = [ 247 "_._f_(___)", 248 "_._f_(___, ___=_)", 249 "_._f_(___=_)", 250] 251""" 252Patterns for method calls specifically. Note that the 253`FUNCTION_CALL_PATTERNS` will find method calls too, but will bind 'f' as 254the entire object + method expression, whereas these patterns will bind 255'f' as the method name (multiple bindings will result from chained method 256calls). 257""" 258 259TRY_EXCEPT_PATTERNS = [ 260 "try:\n ___\nexcept:\n ___", 261 "try:\n ___\nexcept:\n ___\nfinally:\n ___", 262 "try:\n ___\nexcept _:\n ___", 263 "try:\n ___\nexcept _:\n ___\nfinally:\n ___", 264 "try:\n ___\nexcept _ as e:\n ___", 265 "try:\n ___\nexcept _ as e:\n ___\nfinally:\n ___", 266 "try:\n ___\nfinally:\n ___", 267] 268""" 269Patterns for try/except/finally blocks. Note that these do not include 270matching for a single try with multiple excepts nor do they allow 271matching variable exception names using 'as' (because that's not 272currently supported by `mast`). 273""" 274 275WITH_PATTERNS = [ 276 "with _:\n ___", 277 "with _, _:\n ___", 278 "with _ as _:\n ___", 279 "with _ as _, _ as _:\n ___", 280 "with _ as _, _:\n ___", 281 "with _, _ as _:\n ___", 282] 283""" 284Patterns for with blocks with up to two context handlers (TODO: let there 285be an arbitrary number of them...). 286""" 287 288 289#-----------------------------# 290# Pattern-producing functions # 291#-----------------------------# 292 293def function_def_patterns(fn_name, params_pattern=None): 294 """ 295 Returns a list of mast pattern strings for definitions of functions 296 with the given name (or list of names). If an args pattern is given, 297 it should be a string containing mast code that goes between the 298 parentheses of a function definition. For example: 299 300 - `"_, _"` 301 There must be exactly two arguments. 302 - `"___, x=3"` 303 There may be any number of positional arguments, and there must 304 be a single keyword argument named 'x' with default value 3. 305 - `"one, two"` 306 There must be exactly two arguments and they must be named 'one' 307 and 'two'. 308 309 The params_pattern may also be a list of strings and any of those 310 alternatives will be accepted, or it may be an integer, in which case 311 that many blank slots will be inserted. 312 """ 313 patterns = [] 314 315 if isinstance(fn_name, str): 316 names = [fn_name] 317 else: 318 names = list(fn_name) 319 320 if isinstance(params_pattern, int): 321 params_pattern = ', '.join('_' for _ in range(params_pattern)) 322 323 for name in names: 324 if params_pattern is None: 325 # kwargs not allowed by default 326 patterns.append("def {}(___):\n ___".format(name)) 327 else: 328 if isinstance(params_pattern, str): 329 patterns.append( 330 "def {}({}):\n ___".format(name, params_pattern) 331 ) 332 elif not isinstance(params_pattern, (list, tuple)): 333 raise TypeError( 334 "params_pattern must be either a string or a" 335 " sequence of strings." 336 ) 337 else: 338 patterns.extend( 339 [ 340 "def {}({}):\n ___".format(name, params_pat) 341 for params_pat in params_pattern 342 ] 343 ) 344 345 return patterns 346 347 348def function_call_patterns(fn_name, args_pattern, is_method=False): 349 """ 350 Works like function_def_patterns, but produces patterns for calls to 351 a function rather than definitions of it. In this context the args 352 spec is specifying arguments given to the function rather than what 353 parameters it defines. 354 355 If is_method is true, the patterns generated use '_.' to ensure that 356 the function call is as a method of some object. 357 """ 358 patterns = [] 359 if isinstance(fn_name, str): 360 names = [fn_name] 361 else: 362 names = list(fn_name) 363 364 if isinstance(args_pattern, int): 365 args_pattern = ', '.join('_' for _ in range(args_pattern)) 366 367 for name in names: 368 if args_pattern is None: 369 if is_method: 370 patterns.extend( 371 [ 372 "_.{}(___)".format(name), 373 "_.{}(___,___=_)".format(name), 374 "_.{}(___=_)".format(name), 375 ] 376 ) 377 else: 378 patterns.extend( 379 [ 380 "{}(___)".format(name), 381 "{}(___,___=_)".format(name), 382 "{}(___=_)".format(name), 383 ] 384 ) 385 else: 386 if isinstance(args_pattern, str): 387 if is_method: 388 patterns.append("_.{}({})".format(name, args_pattern)) 389 else: 390 patterns.append("{}({})".format(name, args_pattern)) 391 elif not isinstance(args_pattern, (list, tuple)): 392 raise TypeError( 393 "args_pattern must be either a string or a sequence of " 394 + "strings." 395 ) 396 else: 397 if is_method: 398 patterns.extend( 399 [ 400 "_.{}({})".format(name, args_pat) 401 for args_pat in args_pattern 402 ] 403 ) 404 else: 405 patterns.extend( 406 [ 407 "{}({})".format(name, args_pat) 408 for args_pat in args_pattern 409 ] 410 ) 411 412 return patterns
potluck.mast
pattern for simple for
loop.
potluck.mast
pattern for a for
loop with an else
clause.
potluck.mast
patterns for all for
loop variants.
A potluck.mast
pattern for a simple while
loop.
A potluck.mast
pattern for while
/else
.
potluck.mast
patterns for all while
loop variants.
A potluck.mast
pattern for a single for loop comprehension without a filter.
A potluck.mast
pattern for a single for loop comprehension with a filter.
A potluck.mast
pattern for a double for loop comprehension without a filter.
A potluck.mast
pattern for a double for loop comprehension with a filter.
A potluck.mast
pattern for a triple for loop comprehension without a filter.
A potluck.mast
pattern for a triple for loop comprehension with a filter.
`potluck.mast patterns for just single list comprehensions with and without filtering.
potluck.mast
patterns for single, double, and triple list comprehensions
with and without filtering.
Note: does not include dictionary comprehensions (see below)
We will simply hope that students do not use quadruple comprehensions...
potluck.mast
patterns for single-loop comprehensions including set and
dictionary comprehensions and generator expressions.
Patterns for up to triple-loop list comprehensions, plus equivalent generator expressions and set- and dictionary-comprehensions.
potluck.mast
patterns for all while/for loop variants.
potluck.mast
patterns for all reasonable loop/comprehension types,
although highly-nested comprehensions are not included.
potluck.mast
patterns for all loop/comprehension patterns that are
single loops, including generator expressions, set comprehensions, and
dictionary comprehensions.
Common mast pattern for an if statement (matches even ifs that don't have an else, we think; note this also matches elifs, which Python treats as nested ifs).
Patterns for function definitions (without keyword variables, with both, and with only keyword variables). These patterns bind 'f' as the name of the function that's defined.
Patterns for function calls. These bind 'f' as the function (possibly a function expression).
Patterns for method calls specifically. Note that the
FUNCTION_CALL_PATTERNS
will find method calls too, but will bind 'f' as
the entire object + method expression, whereas these patterns will bind
'f' as the method name (multiple bindings will result from chained method
calls).
Patterns for try/except/finally blocks. Note that these do not include
matching for a single try with multiple excepts nor do they allow
matching variable exception names using 'as' (because that's not
currently supported by mast
).
Patterns for with blocks with up to two context handlers (TODO: let there be an arbitrary number of them...).
294def function_def_patterns(fn_name, params_pattern=None): 295 """ 296 Returns a list of mast pattern strings for definitions of functions 297 with the given name (or list of names). If an args pattern is given, 298 it should be a string containing mast code that goes between the 299 parentheses of a function definition. For example: 300 301 - `"_, _"` 302 There must be exactly two arguments. 303 - `"___, x=3"` 304 There may be any number of positional arguments, and there must 305 be a single keyword argument named 'x' with default value 3. 306 - `"one, two"` 307 There must be exactly two arguments and they must be named 'one' 308 and 'two'. 309 310 The params_pattern may also be a list of strings and any of those 311 alternatives will be accepted, or it may be an integer, in which case 312 that many blank slots will be inserted. 313 """ 314 patterns = [] 315 316 if isinstance(fn_name, str): 317 names = [fn_name] 318 else: 319 names = list(fn_name) 320 321 if isinstance(params_pattern, int): 322 params_pattern = ', '.join('_' for _ in range(params_pattern)) 323 324 for name in names: 325 if params_pattern is None: 326 # kwargs not allowed by default 327 patterns.append("def {}(___):\n ___".format(name)) 328 else: 329 if isinstance(params_pattern, str): 330 patterns.append( 331 "def {}({}):\n ___".format(name, params_pattern) 332 ) 333 elif not isinstance(params_pattern, (list, tuple)): 334 raise TypeError( 335 "params_pattern must be either a string or a" 336 " sequence of strings." 337 ) 338 else: 339 patterns.extend( 340 [ 341 "def {}({}):\n ___".format(name, params_pat) 342 for params_pat in params_pattern 343 ] 344 ) 345 346 return patterns
Returns a list of mast pattern strings for definitions of functions with the given name (or list of names). If an args pattern is given, it should be a string containing mast code that goes between the parentheses of a function definition. For example:
"_, _"
There must be exactly two arguments."___, x=3"
There may be any number of positional arguments, and there must be a single keyword argument named 'x' with default value 3."one, two"
There must be exactly two arguments and they must be named 'one' and 'two'.
The params_pattern may also be a list of strings and any of those alternatives will be accepted, or it may be an integer, in which case that many blank slots will be inserted.
349def function_call_patterns(fn_name, args_pattern, is_method=False): 350 """ 351 Works like function_def_patterns, but produces patterns for calls to 352 a function rather than definitions of it. In this context the args 353 spec is specifying arguments given to the function rather than what 354 parameters it defines. 355 356 If is_method is true, the patterns generated use '_.' to ensure that 357 the function call is as a method of some object. 358 """ 359 patterns = [] 360 if isinstance(fn_name, str): 361 names = [fn_name] 362 else: 363 names = list(fn_name) 364 365 if isinstance(args_pattern, int): 366 args_pattern = ', '.join('_' for _ in range(args_pattern)) 367 368 for name in names: 369 if args_pattern is None: 370 if is_method: 371 patterns.extend( 372 [ 373 "_.{}(___)".format(name), 374 "_.{}(___,___=_)".format(name), 375 "_.{}(___=_)".format(name), 376 ] 377 ) 378 else: 379 patterns.extend( 380 [ 381 "{}(___)".format(name), 382 "{}(___,___=_)".format(name), 383 "{}(___=_)".format(name), 384 ] 385 ) 386 else: 387 if isinstance(args_pattern, str): 388 if is_method: 389 patterns.append("_.{}({})".format(name, args_pattern)) 390 else: 391 patterns.append("{}({})".format(name, args_pattern)) 392 elif not isinstance(args_pattern, (list, tuple)): 393 raise TypeError( 394 "args_pattern must be either a string or a sequence of " 395 + "strings." 396 ) 397 else: 398 if is_method: 399 patterns.extend( 400 [ 401 "_.{}({})".format(name, args_pat) 402 for args_pat in args_pattern 403 ] 404 ) 405 else: 406 patterns.extend( 407 [ 408 "{}({})".format(name, args_pat) 409 for args_pat in args_pattern 410 ] 411 ) 412 413 return patterns
Works like function_def_patterns, but produces patterns for calls to a function rather than definitions of it. In this context the args spec is specifying arguments given to the function rather than what parameters it defines.
If is_method is true, the patterns generated use '_.' to ensure that the function call is as a method of some object.