22import inspect
33import string
44from copy import copy
5- from functools import partial
5+ from functools import partialmethod
66from pythonwhat .parsing import (
77 TargetVars ,
88 FunctionParser ,
@@ -89,6 +89,8 @@ def __init__(
8989 if not hasattr (self , "pre_exercise_tree" ):
9090 _ , self .pre_exercise_tree = self .parse (self .pre_exercise_code , test = False )
9191
92+ self .ast_dispatcher = Dispatcher (self .pre_exercise_tree )
93+
9294 if not hasattr (self , "parent_state" ):
9395 self .parent_state = None
9496
@@ -286,12 +288,10 @@ def assert_is_not(self, klasses, fun, prev_fun):
286288 % (fun , " or " .join (["`%s()`" % pf for pf in prev_fun ]))
287289 )
288290
289- def parse_external (self , x ):
291+ def parse_external (self , code ):
290292 res = (None , None )
291293 try :
292- res = asttokens .ASTTokens (x , parse = True )
293- return (res , res .tree )
294-
294+ return Dispatcher .parse (code )
295295 except IndentationError as e :
296296 e .filename = "script.py"
297297 # no line info for now
@@ -326,10 +326,9 @@ def parse_external(self, x):
326326 return res
327327
328328 @staticmethod
329- def parse_internal (x ):
329+ def parse_internal (code ):
330330 try :
331- res = asttokens .ASTTokens (x , parse = True )
332- return (res , res ._tree )
331+ return Dispatcher .parse (code )
333332 except Exception as e :
334333 raise InstructorError (
335334 "Something went wrong when parsing PEC or solution code: %s" % str (e )
@@ -344,55 +343,61 @@ def parse(self, text, test=True):
344343 return parse_method (text )
345344
346345
347- # add property methods for retrieving parser outputs --------------------------
348- # note that this code is an alternative means of using something like..
349- # @property
350- # def student_withs(self): ...
351- # when defining the State class.
352- def getx (tree_name , Parser , ext_attr , self ):
353- """getter for Parser outputs"""
354- # return cached output if possible
355- cache_key = tree_name + Parser .__name__
356- if self ._parser_cache .get (cache_key ):
357- p = self ._parser_cache [cache_key ]
358- else :
359- # otherwise, run parser over tree
360- p = Parser ()
361- # set mappings for parsers that inspect attribute access
362- if ext_attr != "mappings" and Parser in [FunctionParser , ObjectAccessParser ]:
363- p .mappings = self .pre_exercise_mappings .copy ()
364- # run parser
365- p .visit (getattr (self , tree_name ))
366- # cache
367- self ._parser_cache [cache_key ] = p
368- return getattr (p , ext_attr )
369-
370-
371- # put a property getter on state for each parsed ast tree output.
372- # since the getter takes only one argument, self, partial functions
373- # are used to set all other arguments on getx
374- for s in ["student" , "solution" ]:
375- tree_name = s + "_tree"
376- for k , Parser in parser_dict .items ():
377- setattr (State , s + "_" + k , property (partial (getx , tree_name , Parser , "out" )))
378-
379- # mappings from ObjectAccessParser
380- prop_oa_map = property (partial (getx , tree_name , ObjectAccessParser , "mappings" ))
381- setattr (State , s + "_oa_mappings" , prop_oa_map )
382-
383- # mappings from FunctionParser
384- prop_map = property (partial (getx , tree_name , FunctionParser , "mappings" ))
385- setattr (State , s + "_mappings" , prop_map )
346+ class Dispatcher :
347+ def __init__ (self , pre_exercise_tree ):
348+ self ._parser_cache = dict ()
349+ self .pre_exercise_mappings = self ._getx (FunctionParser , "mappings" , pre_exercise_tree )
350+
351+ def __call__ (self , name , node ):
352+ return getattr (self , name )(node )
353+
354+ @staticmethod
355+ def parse (code ):
356+ res = asttokens .ASTTokens (code , parse = True )
357+ return res , res .tree
358+
359+ # add methods for retrieving parser outputs --------------------------
360+ def _getx (self , Parser , ext_attr , tree ):
361+ """getter for Parser outputs"""
362+ # return cached output if possible
363+ cache_key = Parser .__name__ + str (hash (tree ))
364+ if self ._parser_cache .get (cache_key ):
365+ p = self ._parser_cache [cache_key ]
366+ else :
367+ # otherwise, run parser over tree
368+ p = Parser ()
369+ # set mappings for parsers that inspect attribute access
370+ if ext_attr != "mappings" and Parser in [FunctionParser , ObjectAccessParser ]:
371+ p .mappings = self .pre_exercise_mappings .copy ()
372+ # run parser
373+ p .visit (tree )
374+ # cache
375+ self ._parser_cache [cache_key ] = p
376+ return getattr (p , ext_attr )
377+
378+
379+ # put a function on the dispatcher
380+ for k , Parser in parser_dict .items ():
381+ setattr (Dispatcher , k , partialmethod (Dispatcher ._getx , Parser , "out" ))
382+
383+ # mappings from ObjectAccessParser
384+ prop_oa_map = partialmethod (Dispatcher ._getx , ObjectAccessParser , "mappings" )
385+ setattr (Dispatcher , "oa_mappings" , prop_oa_map )
386+
387+ # mappings from FunctionParser
388+ prop_map = partialmethod (Dispatcher ._getx , FunctionParser , "mappings" )
389+ setattr (Dispatcher , "mappings" , prop_map )
386390
387391# mappings for pre exercise code from FunctionParser
388- pec_prop_map = property ( partial ( getx , "pre_exercise_tree" , FunctionParser , "mappings" ) )
389- setattr (State , "pre_exercise_mappings" , pec_prop_map )
392+ pec_prop_map = partialmethod ( Dispatcher . _getx , FunctionParser , "mappings" )
393+ setattr (Dispatcher , "pre_exercise_mappings" , pec_prop_map )
390394
391395# State subclasses based on parsed output -------------------------------------
392396State .SUBCLASSES = {
393397 node_name : type (node_name , (State ,), {}) for node_name in parser_dict
394398}
395399
400+
396401# global setters on State -----------------------------------------------------
397402def set_converter (key , fundef ):
398403 # note that root state is set on the State class in test_exercise
0 commit comments