1+ import asttokens
2+
13from functools import partialmethod
4+ from collections .abc import Mapping
5+
6+ from protowhat .failure import debugger
7+ from protowhat .Feedback import FeedbackComponent
8+ from protowhat .selectors import DispatcherInterface
9+ from protowhat .State import State as ProtoState
10+ from protowhat .utils import parameters_attr
11+ from pythonwhat import signatures
12+ from pythonwhat .converters import get_manual_converters
13+ from pythonwhat .feedback import Feedback
214from pythonwhat .parsing import (
315 TargetVars ,
416 FunctionParser ,
517 ObjectAccessParser ,
618 parser_dict ,
719)
8- from protowhat .State import State as ProtoState
9- from protowhat .selectors import DispatcherInterface
10- from protowhat .Feedback import InstructorError
11- from pythonwhat import signatures
12- from pythonwhat .converters import get_manual_converters
13- from collections .abc import Mapping
14- import asttokens
1520from pythonwhat .utils_ast import wrap_in_module
1621
1722
@@ -35,6 +40,7 @@ def __len__(self):
3540 return len (self ._items )
3641
3742
43+ @parameters_attr
3844class State (ProtoState ):
3945 """State of the SCT environment.
4046
@@ -48,6 +54,8 @@ class State(ProtoState):
4854
4955 """
5056
57+ feedback_cls = Feedback
58+
5159 def __init__ (
5260 self ,
5361 student_code ,
@@ -60,8 +68,9 @@ def __init__(
6068 reporter ,
6169 force_diagnose = False ,
6270 highlight = None ,
71+ highlight_offset = None ,
6372 highlighting_disabled = None ,
64- messages = None ,
73+ feedback_context = None ,
6574 creator = None ,
6675 student_ast = None ,
6776 solution_ast = None ,
@@ -75,23 +84,21 @@ def __init__(
7584 solution_env = Context (),
7685 ):
7786 args = locals ().copy ()
78- self .params = list ()
87+ self .debug = False
7988
8089 for k , v in args .items ():
8190 if k != "self" :
82- self .params .append (k )
8391 setattr (self , k , v )
8492
85- self .messages = messages if messages else []
86-
8793 self .ast_dispatcher = self .get_dispatcher ()
8894
8995 # Parse solution and student code
9096 # if possible, not done yet and wanted (ast arguments not False)
9197 if isinstance (self .student_code , str ) and student_ast is None :
9298 self .student_ast = self .parse (student_code )
9399 if isinstance (self .solution_code , str ) and solution_ast is None :
94- self .solution_ast = self .parse (solution_code , test = False )
100+ with debugger (self ):
101+ self .solution_ast = self .parse (solution_code )
95102
96103 if highlight is None : # todo: check parent_state? (move check to reporting?)
97104 self .highlight = self .student_ast
@@ -106,26 +113,29 @@ def get_manual_sigs(self):
106113
107114 return self .manual_sigs
108115
109- def to_child (self , append_message = "" , node_name = "" , ** kwargs ):
116+ def to_child (self , append_message = None , node_name = "" , ** kwargs ):
110117 """Dive into nested tree.
111118
112119 Set the current state as a state with a subtree of this syntax tree as
113120 student tree and solution tree. This is necessary when testing if statements or
114121 for loops for example.
115122 """
116- bad_pars = set (kwargs ) - set (self .params )
117- if bad_pars :
118- raise ValueError ("Invalid init params for State: %s" % ", " .join (bad_pars ))
123+ bad_parameters = set (kwargs ) - set (self .parameters )
124+ if bad_parameters :
125+ raise ValueError (
126+ "Invalid init parameters for State: %s" % ", " .join (bad_parameters )
127+ )
119128
120129 base_kwargs = {
121130 attr : getattr (self , attr )
122- for attr in self .params
123- if attr not in ["highlight" ]
131+ for attr in self .parameters
132+ if hasattr ( self , attr ) and attr not in ["ast_dispatcher" , "highlight" ]
124133 }
125134
126- if not isinstance (append_message , dict ):
127- append_message = {"msg" : append_message , "kwargs" : {}}
128- kwargs ["messages" ] = [* self .messages , append_message ]
135+ if append_message and not isinstance (append_message , FeedbackComponent ):
136+ append_message = FeedbackComponent (append_message )
137+ kwargs ["feedback_context" ] = append_message
138+ kwargs ["creator" ] = {"type" : "to_child" , "args" : {"state" : self }}
129139
130140 def update_kwarg (name , func ):
131141 kwargs [name ] = func (kwargs [name ])
@@ -162,11 +172,11 @@ def update_context(name):
162172 init_kwargs = {** base_kwargs , ** kwargs }
163173 child = klass (** init_kwargs )
164174
165- extra_attrs = set (vars (self )) - set (self .params )
175+ extra_attrs = set (vars (self )) - set (self .parameters )
166176 for attr in extra_attrs :
167177 # don't copy attrs set on new instances in init
168178 # the cached manual_sigs is passed
169- if attr not in {"params" , " ast_dispatcher" , "converters" }:
179+ if attr not in {"ast_dispatcher" , "converters" }:
170180 setattr (child , attr , getattr (self , attr ))
171181
172182 return child
@@ -183,27 +193,30 @@ def has_different_processes(self):
183193
184194 def assert_execution_root (self , fun , extra_msg = "" ):
185195 if not (self .is_root or self .is_creator_type ("run" )):
186- raise InstructorError (
187- "`%s()` should only be called focusing on a full script, following `Ex()` or `run()`. %s"
188- % (fun , extra_msg )
189- )
196+ with debugger (self ):
197+ self .report (
198+ "`%s()` should only be called focusing on a full script, following `Ex()` or `run()`. %s"
199+ % (fun , extra_msg )
200+ )
190201
191202 def is_creator_type (self , type ):
192203 return self .creator and self .creator .get ("type" ) == type
193204
194205 def assert_is (self , klasses , fun , prev_fun ):
195206 if self .__class__ .__name__ not in klasses :
196- raise InstructorError (
197- "`%s()` can only be called on %s."
198- % (fun , " or " .join (["`%s()`" % pf for pf in prev_fun ]))
199- )
207+ with debugger (self ):
208+ self .report (
209+ "`%s()` can only be called on %s."
210+ % (fun , " or " .join (["`%s()`" % pf for pf in prev_fun ]))
211+ )
200212
201213 def assert_is_not (self , klasses , fun , prev_fun ):
202214 if self .__class__ .__name__ in klasses :
203- raise InstructorError (
204- "`%s()` should not be called on %s."
205- % (fun , " or " .join (["`%s()`" % pf for pf in prev_fun ]))
206- )
215+ with debugger (self ):
216+ self .report (
217+ "`%s()` should not be called on %s."
218+ % (fun , " or " .join (["`%s()`" % pf for pf in prev_fun ]))
219+ )
207220
208221 def parse_external (self , code ):
209222 res = (None , None )
@@ -235,17 +248,17 @@ def parse_internal(self, code):
235248 try :
236249 return self .ast_dispatcher .parse (code )
237250 except Exception as e :
238- raise InstructorError (
251+ self . report (
239252 "Something went wrong when parsing the solution code: %s" % str (e )
240253 )
241254
242- def parse (self , text , test = True ):
243- if test :
244- parse_method = self .parse_external
245- token_attr = "student_ast_tokens"
246- else :
255+ def parse (self , text ):
256+ if self .debug :
247257 parse_method = self .parse_internal
248258 token_attr = "solution_ast_tokens"
259+ else :
260+ parse_method = self .parse_external
261+ token_attr = "student_ast_tokens"
249262
250263 tokens , ast = parse_method (text )
251264 setattr (self , token_attr , tokens )
@@ -256,9 +269,8 @@ def get_dispatcher(self):
256269 try :
257270 return Dispatcher (self .pre_exercise_code )
258271 except Exception as e :
259- raise InstructorError (
260- "Something went wrong when parsing the PEC: %s" % str (e )
261- )
272+ with debugger (self ):
273+ self .report ("Something went wrong when parsing the PEC: %s" % str (e ))
262274
263275
264276class Dispatcher (DispatcherInterface ):
0 commit comments