Skip to content

Commit d22a012

Browse files
committed
clean up getting FunctionParser output on state, add State subclassing
1 parent e57aeca commit d22a012

3 files changed

Lines changed: 24 additions & 32 deletions

File tree

pythonwhat/State.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ast
22
import inspect
33
from copy import copy
4+
from functools import partial
45
from pythonwhat.parsing import TargetVars, FunctionParser, ObjectAccessParser, parser_dict
56
from pythonwhat.Reporter import Reporter
67
from pythonwhat.Feedback import Feedback
@@ -120,7 +121,7 @@ def to_child_state(self, student_subtree, solution_subtree,
120121
student_context=None, solution_context=None,
121122
student_parts=None, solution_parts=None,
122123
highlight = None,
123-
append_message=""):
124+
append_message="", node_name=""):
124125
"""Dive into nested tree.
125126
126127
Set the current state as a state with a subtree of this syntax tree as
@@ -154,7 +155,8 @@ def to_child_state(self, student_subtree, solution_subtree,
154155
student_parts = student_parts, solution_parts = solution_parts,
155156
highlight = highlight, messages = messages)
156157

157-
child = State(student_code = utils_ast.extract_text_from_node(self.full_student_code, student_subtree),
158+
klass = State if not node_name else self.SUBCLASSES[node_name]
159+
child = klass(student_code = utils_ast.extract_text_from_node(self.full_student_code, student_subtree),
158160
full_student_code = self.full_student_code,
159161
pre_exercise_code = self.pre_exercise_code,
160162
student_context = student_context,
@@ -238,8 +240,6 @@ def parse_int(x):
238240
# @property
239241
# def student_withs(self): ...
240242
# when defining the State class.
241-
from functools import partial
242-
243243
def getx(tree_name, Parser, ext_attr, self):
244244
"""getter for Parser outputs"""
245245
# return cached output if possible
@@ -249,26 +249,18 @@ def getx(tree_name, Parser, ext_attr, self):
249249
else:
250250
# otherwise, run parser over tree
251251
p = Parser()
252+
# set mappings for parsers that inspect attribute access
253+
if ext_attr != 'mappings' and Parser in [FunctionParser, ObjectAccessParser]:
254+
p.mappings = self.pre_exercise_mappings.copy()
255+
# run parser
252256
p.visit(getattr(self, tree_name))
253257
# cache
254258
self._parser_cache[cache_key] = p
255259
return getattr(p, ext_attr)
256260

257-
def get_func_map(tree_name, ext_attr, self):
258-
"""getter for FunctionParser outputs, uses pre_exercise_mappings"""
259-
cache_key = tree_name + FunctionParser.__name__
260-
if self._parser_cache.get(cache_key):
261-
p = self._parser_cache[cache_key]
262-
else:
263-
p = FunctionParser()
264-
p.mappings = self.pre_exercise_mappings.copy()
265-
p.visit(getattr(self, tree_name))
266-
self._parser_cache[cache_key] = p
267-
return getattr(p, ext_attr)
268-
269261
# put a property getter on state for each parsed ast tree output.
270262
# since the getter takes only one argument, self, partial functions
271-
# are used to set all other arguments on getx and get_func_map
263+
# are used to set all other arguments on getx
272264
for s in ['student', 'solution']:
273265
tree_name = s+'_tree'
274266
for k, Parser in parser_dict.items():
@@ -278,17 +270,16 @@ def get_func_map(tree_name, ext_attr, self):
278270
prop_oa_map = property(partial(getx, tree_name, ObjectAccessParser, 'mappings'))
279271
setattr(State, s+'_oa_mappings', prop_oa_map)
280272

281-
# Getters for FunctionParser -----
282-
# calls
283-
prop_calls = property(partial(get_func_map, tree_name, 'calls'))
284-
setattr(State, s+'_function_calls', prop_calls)
285-
# mappings
286-
prop_map = property(partial(get_func_map, tree_name, 'mappings'))
273+
# mappings from FunctionParser
274+
prop_map = property(partial(getx, tree_name, FunctionParser, 'mappings'))
287275
setattr(State, s+'_mappings', prop_map)
288276

277+
# mappings for pre exercise code from FunctionParser
289278
pec_prop_map = property(partial(getx, 'pre_exercise_tree', FunctionParser, 'mappings'))
290279
setattr(State, 'pre_exercise_mappings', pec_prop_map)
291280

281+
# State subclasses based on parsed output -------------------------------------
282+
State.SUBCLASSES = {node_name: type(node_name, (State,), {}) for node_name in parser_dict}
292283

293284
# global setters on State -----------------------------------------------------
294285
def set_converter(key, fundef):

pythonwhat/check_funcs.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from functools import partial
66
import copy
77

8-
def part_to_child(stu_part, sol_part, append_message, state):
8+
def part_to_child(stu_part, sol_part, append_message, state, node_name=None):
99
# stu_part and sol_part will be accessible on all templates
1010
append_message['kwargs'].update({'stu_part': stu_part, 'sol_part': sol_part})
1111

@@ -15,7 +15,7 @@ def part_to_child(stu_part, sol_part, append_message, state):
1515
stu_part.get('target_vars'), sol_part.get('target_vars'),
1616
stu_part, sol_part,
1717
highlight = stu_part.get('highlight'),
18-
append_message = append_message)
18+
append_message = append_message, node_name=node_name)
1919

2020
# otherwise, assume they are just nodes
2121
return state.to_child_state(stu_part, sol_part, append_message = append_message)
@@ -90,7 +90,7 @@ def check_node(name, index, typestr, missing_msg=MSG_MISSING, expand_msg=MSG_PRE
9090
'kwargs': fmt_kwargs
9191
}
9292

93-
return part_to_child(stu_part, sol_part, append_message, state)
93+
return part_to_child(stu_part, sol_part, append_message, state, node_name=name)
9494

9595

9696
# Part tests ------------------------------------------------------------------

pythonwhat/parsing.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def __init__(self):
263263
self.gen_name = ''
264264
self.raw_name = ''
265265
self.mappings = {}
266-
self.calls = {}
266+
self.out = {}
267267
self.call_lookup_active = False
268268

269269
def visit_BinOp(self, node):
@@ -304,11 +304,11 @@ def visit_Call(self, node):
304304
self.visit(node.func) # Need to visit func to start recording the current function name.
305305

306306
if self.gen_name:
307-
if (self.gen_name not in self.calls):
308-
self.calls[self.gen_name] = []
307+
if (self.gen_name not in self.out):
308+
self.out[self.gen_name] = []
309309

310-
self.calls[self.gen_name].append(self.get_call_part(node))
311-
#self.calls[self.current].append((node, node.args, node.keywords))
310+
self.out[self.gen_name].append(self.get_call_part(node))
311+
#self.out[self.current].append((node, node.args, node.keywords))
312312

313313
self.gen_name = self.raw_name = ""
314314
self.call_lookup_active = False
@@ -832,5 +832,6 @@ def parse_handler(handler): return {
832832
"dict_comps": DictCompParser,
833833
"generator_exps": GeneratorExpParser,
834834
"withs": WithParser,
835-
"try_excepts": TryExceptParser
835+
"try_excepts": TryExceptParser,
836+
"function_calls": FunctionParser
836837
}

0 commit comments

Comments
 (0)