1
+ import asttokens
2
+
1
3
from 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
2
14
from pythonwhat .parsing import (
3
15
TargetVars ,
4
16
FunctionParser ,
5
17
ObjectAccessParser ,
6
18
parser_dict ,
7
19
)
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
15
20
from pythonwhat .utils_ast import wrap_in_module
16
21
17
22
@@ -35,6 +40,7 @@ def __len__(self):
35
40
return len (self ._items )
36
41
37
42
43
+ @parameters_attr
38
44
class State (ProtoState ):
39
45
"""State of the SCT environment.
40
46
@@ -48,6 +54,8 @@ class State(ProtoState):
48
54
49
55
"""
50
56
57
+ feedback_cls = Feedback
58
+
51
59
def __init__ (
52
60
self ,
53
61
student_code ,
@@ -60,8 +68,9 @@ def __init__(
60
68
reporter ,
61
69
force_diagnose = False ,
62
70
highlight = None ,
71
+ highlight_offset = None ,
63
72
highlighting_disabled = None ,
64
- messages = None ,
73
+ feedback_context = None ,
65
74
creator = None ,
66
75
student_ast = None ,
67
76
solution_ast = None ,
@@ -75,23 +84,21 @@ def __init__(
75
84
solution_env = Context (),
76
85
):
77
86
args = locals ().copy ()
78
- self .params = list ()
87
+ self .debug = False
79
88
80
89
for k , v in args .items ():
81
90
if k != "self" :
82
- self .params .append (k )
83
91
setattr (self , k , v )
84
92
85
- self .messages = messages if messages else []
86
-
87
93
self .ast_dispatcher = self .get_dispatcher ()
88
94
89
95
# Parse solution and student code
90
96
# if possible, not done yet and wanted (ast arguments not False)
91
97
if isinstance (self .student_code , str ) and student_ast is None :
92
98
self .student_ast = self .parse (student_code )
93
99
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 )
95
102
96
103
if highlight is None : # todo: check parent_state? (move check to reporting?)
97
104
self .highlight = self .student_ast
@@ -106,26 +113,29 @@ def get_manual_sigs(self):
106
113
107
114
return self .manual_sigs
108
115
109
- def to_child (self , append_message = "" , node_name = "" , ** kwargs ):
116
+ def to_child (self , append_message = None , node_name = "" , ** kwargs ):
110
117
"""Dive into nested tree.
111
118
112
119
Set the current state as a state with a subtree of this syntax tree as
113
120
student tree and solution tree. This is necessary when testing if statements or
114
121
for loops for example.
115
122
"""
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
+ )
119
128
120
129
base_kwargs = {
121
130
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" ]
124
133
}
125
134
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 }}
129
139
130
140
def update_kwarg (name , func ):
131
141
kwargs [name ] = func (kwargs [name ])
@@ -162,11 +172,11 @@ def update_context(name):
162
172
init_kwargs = {** base_kwargs , ** kwargs }
163
173
child = klass (** init_kwargs )
164
174
165
- extra_attrs = set (vars (self )) - set (self .params )
175
+ extra_attrs = set (vars (self )) - set (self .parameters )
166
176
for attr in extra_attrs :
167
177
# don't copy attrs set on new instances in init
168
178
# the cached manual_sigs is passed
169
- if attr not in {"params" , " ast_dispatcher" , "converters" }:
179
+ if attr not in {"ast_dispatcher" , "converters" }:
170
180
setattr (child , attr , getattr (self , attr ))
171
181
172
182
return child
@@ -183,27 +193,30 @@ def has_different_processes(self):
183
193
184
194
def assert_execution_root (self , fun , extra_msg = "" ):
185
195
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
+ )
190
201
191
202
def is_creator_type (self , type ):
192
203
return self .creator and self .creator .get ("type" ) == type
193
204
194
205
def assert_is (self , klasses , fun , prev_fun ):
195
206
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
+ )
200
212
201
213
def assert_is_not (self , klasses , fun , prev_fun ):
202
214
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
+ )
207
220
208
221
def parse_external (self , code ):
209
222
res = (None , None )
@@ -235,17 +248,17 @@ def parse_internal(self, code):
235
248
try :
236
249
return self .ast_dispatcher .parse (code )
237
250
except Exception as e :
238
- raise InstructorError (
251
+ self . report (
239
252
"Something went wrong when parsing the solution code: %s" % str (e )
240
253
)
241
254
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 :
247
257
parse_method = self .parse_internal
248
258
token_attr = "solution_ast_tokens"
259
+ else :
260
+ parse_method = self .parse_external
261
+ token_attr = "student_ast_tokens"
249
262
250
263
tokens , ast = parse_method (text )
251
264
setattr (self , token_attr , tokens )
@@ -256,9 +269,8 @@ def get_dispatcher(self):
256
269
try :
257
270
return Dispatcher (self .pre_exercise_code )
258
271
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 ))
262
274
263
275
264
276
class Dispatcher (DispatcherInterface ):
0 commit comments