@@ -119,6 +119,43 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None:
119119 self .generic_visit (node )
120120 self .scope_depth -= 1
121121
122+ def visit_NamedExpr (self , node : ast .NamedExpr ) -> None :
123+ if self .scope_depth == 0 :
124+ self .add_target (node .target )
125+ self .generic_visit (node )
126+
127+ def visit_Match (self , node : ast .Match ) -> None :
128+ if self .scope_depth == 0 :
129+ for case in node .cases :
130+ self .collect_match_pattern (case .pattern )
131+ self .generic_visit (node )
132+
133+ def collect_match_pattern (self , pattern : ast .pattern ) -> None :
134+ if isinstance (pattern , ast .MatchAs ):
135+ if pattern .name :
136+ self .names .add (pattern .name )
137+ if pattern .pattern is not None :
138+ self .collect_match_pattern (pattern .pattern )
139+ elif isinstance (pattern , ast .MatchStar ):
140+ if pattern .name :
141+ self .names .add (pattern .name )
142+ elif isinstance (pattern , ast .MatchMapping ):
143+ if pattern .rest :
144+ self .names .add (pattern .rest )
145+ for subpattern in pattern .patterns :
146+ self .collect_match_pattern (subpattern )
147+ elif isinstance (pattern , ast .MatchSequence ):
148+ for subpattern in pattern .patterns :
149+ self .collect_match_pattern (subpattern )
150+ elif isinstance (pattern , ast .MatchClass ):
151+ for subpattern in pattern .patterns :
152+ self .collect_match_pattern (subpattern )
153+ for subpattern in pattern .kwd_patterns :
154+ self .collect_match_pattern (subpattern )
155+ elif isinstance (pattern , ast .MatchOr ):
156+ for subpattern in pattern .patterns :
157+ self .collect_match_pattern (subpattern )
158+
122159
123160class MontyREPL (NonIsolatedEnv ):
124161 """
@@ -149,6 +186,7 @@ def __init__(
149186 self .locals : dict [str , Any ] = {}
150187 self .pending_llm_calls : list [RLMChatCompletion ] = []
151188 self .stdout_parts : list [str ] = []
189+ self .stderr_parts : list [str ] = []
152190 self ._context_count = 0
153191 self ._history_count = 0
154192
@@ -165,6 +203,7 @@ def setup(self) -> None:
165203 self .locals = {}
166204 self .pending_llm_calls = []
167205 self .stdout_parts = []
206+ self .stderr_parts = []
168207 self ._context_count = 0
169208 self ._history_count = 0
170209
@@ -176,13 +215,12 @@ def execute_code(self, code: str) -> REPLResult:
176215 """Execute code in the Monty sandbox and return result."""
177216 start_time = time .perf_counter ()
178217 self .stdout_parts = []
218+ self .stderr_parts = []
179219 self .pending_llm_calls = []
180220
181221 wrapper_script = self .build_wrapper_script (code )
182222 external_functions = {
183223 "__rlm_capture_locals" : self .capture_locals ,
184- "FINAL_VAR" : self .final_var ,
185- "SHOW_VARS" : self .show_vars ,
186224 "llm_query" : self .llm_query ,
187225 "llm_query_batched" : self .llm_query_batched ,
188226 }
@@ -204,15 +242,20 @@ def execute_code(self, code: str) -> REPLResult:
204242
205243 return REPLResult (
206244 stdout = "" .join (self .stdout_parts ),
207- stderr = "" ,
245+ stderr = "" . join ( self . stderr_parts ) ,
208246 locals = self .locals .copy (),
209247 execution_time = time .perf_counter () - start_time ,
210248 rlm_calls = self .pending_llm_calls .copy (),
211249 )
212250 except Exception as exc :
251+ stderr = "" .join (self .stderr_parts )
252+ if stderr :
253+ stderr = f"{ stderr .rstrip ()} \n { type (exc ).__name__ } : { exc } "
254+ else :
255+ stderr = f"{ type (exc ).__name__ } : { exc } "
213256 return REPLResult (
214257 stdout = "" .join (self .stdout_parts ),
215- stderr = f" { type ( exc ). __name__ } : { exc } " ,
258+ stderr = stderr ,
216259 locals = self .locals .copy (),
217260 execution_time = time .perf_counter () - start_time ,
218261 rlm_calls = self .pending_llm_calls .copy (),
@@ -221,18 +264,56 @@ def execute_code(self, code: str) -> REPLResult:
221264 def build_wrapper_script (self , user_code : str ) -> str :
222265 """Build a wrapper script that restores state, captures output, and persists locals."""
223266 lines : list [str ] = []
224- for key in self .locals :
225- if key .isidentifier ():
226- lines .append (f"{ key } = __rlm_state.get({ key !r} )" )
227-
228- lines .append (user_code )
229-
230267 assigned_names = self .collect_assigned_names (user_code )
231268 persisted_names = {
232269 name
233270 for name in set (self .locals ) | assigned_names
234271 if name .isidentifier () and not name .startswith ("_" ) and name not in RESERVED_NAMES
235272 }
273+ for name in sorted (persisted_names ):
274+ lines .append (f"{ name } = __rlm_state.get({ name !r} )" )
275+
276+ lines .append ("def FINAL_VAR(variable_name):" )
277+ lines .append (' variable_name = variable_name.strip().strip("\\ "\' ")' )
278+ for name in sorted (persisted_names ):
279+ lines .append (f" if variable_name == { name !r} :" )
280+ lines .append (" try:" )
281+ lines .append (f" return str({ name } )" )
282+ lines .append (" except NameError:" )
283+ lines .append (" pass" )
284+ lines .append (" available = []" )
285+ for name in sorted (persisted_names ):
286+ lines .append (" try:" )
287+ lines .append (f" { name } " )
288+ lines .append (f" available.append({ name !r} )" )
289+ lines .append (" except NameError:" )
290+ lines .append (" pass" )
291+ lines .append (" if available:" )
292+ lines .append (
293+ " return f\" Error: Variable '{variable_name}' not found. "
294+ "Available variables: {available}. "
295+ 'You must create and assign a variable BEFORE calling FINAL_VAR on it."'
296+ )
297+ lines .append (
298+ " return f\" Error: Variable '{variable_name}' not found. "
299+ "No variables have been created yet. "
300+ 'You must create and assign a variable in a REPL block BEFORE calling FINAL_VAR on it."'
301+ )
302+
303+ lines .append ("def SHOW_VARS():" )
304+ lines .append (" available = {}" )
305+ for name in sorted (persisted_names ):
306+ lines .append (" try:" )
307+ lines .append (f" available[{ name !r} ] = type({ name } ).__name__" )
308+ lines .append (" except NameError:" )
309+ lines .append (" pass" )
310+ lines .append (" if not available:" )
311+ lines .append (
312+ ' return "No variables created yet. Use ```repl``` blocks to create variables."'
313+ )
314+ lines .append (' return f"Available variables: {available}"' )
315+
316+ lines .append (user_code )
236317
237318 lines .append ("__rlm_state_out = {}" )
238319 for name in sorted (persisted_names ):
@@ -248,6 +329,8 @@ def handle_print_callback(self, stream: Literal["stdout", "stderr"], text: str)
248329 """Collect printed output from Monty."""
249330 if stream == "stdout" :
250331 self .stdout_parts .append (text )
332+ elif stream == "stderr" :
333+ self .stderr_parts .append (text )
251334
252335 def capture_locals (self , vars_dict : dict [str , Any ]) -> None :
253336 """Capture locals after execution."""
0 commit comments