/home/noah/src/ruchy/src/runtime/repl.rs
Line | Count | Source |
1 | | //! REPL implementation for interactive Ruchy development |
2 | | //! |
3 | | //! Production-grade REPL with resource bounds, error recovery, and grammar coverage |
4 | | #![allow(clippy::cast_sign_loss)] |
5 | | //! |
6 | | //! # Examples |
7 | | //! |
8 | | //! ``` |
9 | | //! use ruchy::runtime::Repl; |
10 | | //! |
11 | | //! let mut repl = Repl::new().unwrap(); |
12 | | //! |
13 | | //! // Evaluate arithmetic |
14 | | //! let result = repl.eval("2 + 2").unwrap(); |
15 | | //! assert_eq!(result, "4"); |
16 | | //! |
17 | | //! // Define variables |
18 | | //! repl.eval("let x = 10").unwrap(); |
19 | | //! let result = repl.eval("x * 2").unwrap(); |
20 | | //! assert_eq!(result, "20"); |
21 | | //! ``` |
22 | | //! |
23 | | //! # One-liner evaluation |
24 | | //! |
25 | | //! ``` |
26 | | //! use ruchy::runtime::Repl; |
27 | | //! use std::time::{Duration, Instant}; |
28 | | //! |
29 | | //! let mut repl = Repl::new().unwrap(); |
30 | | //! let deadline = Some(Instant::now() + Duration::from_millis(100)); |
31 | | //! |
32 | | //! let value = repl.evaluate_expr_str("5 + 3", deadline).unwrap(); |
33 | | //! assert_eq!(value.to_string(), "8"); |
34 | | //! ``` |
35 | | |
36 | | #![allow(clippy::print_stdout)] // REPL needs to print to stdout |
37 | | #![allow(clippy::print_stderr)] // REPL needs to print errors |
38 | | #![allow(clippy::expect_used)] // REPL can panic on initialization failure |
39 | | |
40 | | use crate::frontend::ast::{ |
41 | | BinaryOp, Expr, ExprKind, ImportItem, Literal, MatchArm, Pattern, PipelineStage, Span, StructPatternField, UnaryOp, |
42 | | }; |
43 | | use crate::runtime::completion::RuchyCompleter; |
44 | | use crate::runtime::magic::{MagicRegistry, UnicodeExpander}; |
45 | | use crate::runtime::transaction::TransactionalState; |
46 | | use crate::{Parser, Transpiler}; |
47 | | use anyhow::{bail, Context, Result}; |
48 | | use colored::Colorize; |
49 | | |
50 | | // mod display; |
51 | | // mod inspect; |
52 | | use rustyline::error::ReadlineError; |
53 | | use rustyline::history::DefaultHistory; |
54 | | use rustyline::{CompletionType, Config, EditMode}; |
55 | | use std::collections::{HashMap, HashSet}; |
56 | | use std::fmt; |
57 | | #[allow(unused_imports)] |
58 | | use std::fmt::Write; |
59 | | use std::fs; |
60 | | use std::path::{Path, PathBuf}; |
61 | | use std::process::Command; |
62 | | use std::time::{Duration, Instant, SystemTime}; |
63 | | |
64 | | /// Runtime value for evaluation |
65 | | #[derive(Debug, Clone, PartialEq)] |
66 | | pub enum Value { |
67 | | Int(i64), |
68 | | Float(f64), |
69 | | String(String), |
70 | | Bool(bool), |
71 | | Char(char), |
72 | | List(Vec<Value>), |
73 | | Tuple(Vec<Value>), |
74 | | Function { |
75 | | name: String, |
76 | | params: Vec<String>, |
77 | | body: Box<Expr>, |
78 | | }, |
79 | | Lambda { |
80 | | params: Vec<String>, |
81 | | body: Box<Expr>, |
82 | | }, |
83 | | DataFrame { |
84 | | columns: Vec<DataFrameColumn>, |
85 | | }, |
86 | | Object(HashMap<String, Value>), |
87 | | HashMap(HashMap<Value, Value>), |
88 | | HashSet(HashSet<Value>), |
89 | | Range { |
90 | | start: i64, |
91 | | end: i64, |
92 | | inclusive: bool, |
93 | | }, |
94 | | EnumVariant { |
95 | | enum_name: String, |
96 | | variant_name: String, |
97 | | data: Option<Vec<Value>>, |
98 | | }, |
99 | | Unit, |
100 | | Nil, |
101 | | } |
102 | | |
103 | | /// `DataFrame` column representation for pretty printing |
104 | | #[derive(Debug, Clone, PartialEq)] |
105 | | pub struct DataFrameColumn { |
106 | | pub name: String, |
107 | | pub values: Vec<Value>, |
108 | | } |
109 | | |
110 | | // Manual Eq implementation for Value |
111 | | impl Eq for Value {} |
112 | | |
113 | | // Manual Hash implementation for Value |
114 | | impl std::hash::Hash for Value { |
115 | 0 | fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
116 | 0 | std::mem::discriminant(self).hash(state); |
117 | 0 | match self { |
118 | 0 | Value::Int(n) => n.hash(state), |
119 | 0 | Value::Float(f) => { |
120 | 0 | // Hash floats by their bit representation to handle NaN properly |
121 | 0 | f.to_bits().hash(state); |
122 | 0 | }, |
123 | 0 | Value::String(s) => s.hash(state), |
124 | 0 | Value::Bool(b) => b.hash(state), |
125 | 0 | Value::Char(c) => c.hash(state), |
126 | 0 | Value::List(items) => { |
127 | 0 | for item in items { |
128 | 0 | item.hash(state); |
129 | 0 | } |
130 | | }, |
131 | 0 | Value::Tuple(items) => { |
132 | 0 | for item in items { |
133 | 0 | item.hash(state); |
134 | 0 | } |
135 | | }, |
136 | | // Functions, DataFrames, Objects, HashMaps, and HashSets are not hashable |
137 | | // We'll just hash their discriminant |
138 | 0 | Value::Function { name, .. } => name.hash(state), |
139 | 0 | Value::Lambda { .. } => "lambda".hash(state), |
140 | 0 | Value::DataFrame { .. } => "dataframe".hash(state), |
141 | 0 | Value::Object(_) => "object".hash(state), |
142 | 0 | Value::HashMap(_) => "hashmap".hash(state), |
143 | 0 | Value::HashSet(_) => "hashset".hash(state), |
144 | 0 | Value::Range { start, end, inclusive } => { |
145 | 0 | start.hash(state); |
146 | 0 | end.hash(state); |
147 | 0 | inclusive.hash(state); |
148 | 0 | }, |
149 | 0 | Value::EnumVariant { enum_name, variant_name, data } => { |
150 | 0 | enum_name.hash(state); |
151 | 0 | variant_name.hash(state); |
152 | 0 | if let Some(d) = data { |
153 | 0 | for item in d { |
154 | 0 | item.hash(state); |
155 | 0 | } |
156 | 0 | } |
157 | | }, |
158 | 0 | Value::Unit => "unit".hash(state), |
159 | 0 | Value::Nil => "nil".hash(state), |
160 | | } |
161 | 0 | } |
162 | | } |
163 | | |
164 | | // Display implementations moved to repl_display.rs |
165 | | |
166 | | impl fmt::Display for Value { |
167 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
168 | 0 | match self { |
169 | 0 | Value::Int(n) => write!(f, "{n}"), |
170 | 0 | Value::Float(x) => write!(f, "{x}"), |
171 | 0 | Value::String(s) => write!(f, "\"{s}\""), |
172 | 0 | Value::Bool(b) => write!(f, "{b}"), |
173 | 0 | Value::Char(c) => write!(f, "'{c}'"), |
174 | 0 | Value::List(items) => { |
175 | 0 | write!(f, "[")?; |
176 | 0 | for (i, item) in items.iter().enumerate() { |
177 | 0 | if i > 0 { write!(f, ", ")?; } |
178 | 0 | write!(f, "{item}")?; |
179 | | } |
180 | 0 | write!(f, "]") |
181 | | } |
182 | 0 | Value::Tuple(items) => { |
183 | 0 | write!(f, "(")?; |
184 | 0 | for (i, item) in items.iter().enumerate() { |
185 | 0 | if i > 0 { write!(f, ", ")?; } |
186 | 0 | write!(f, "{item}")?; |
187 | | } |
188 | 0 | write!(f, ")") |
189 | | } |
190 | 0 | Value::Function { name, params, .. } => { |
191 | 0 | write!(f, "fn {}({})", name, params.join(", ")) |
192 | | } |
193 | 0 | Value::Lambda { params, .. } => { |
194 | 0 | write!(f, "|{}| <closure>", params.join(", ")) |
195 | | } |
196 | 0 | Value::DataFrame { columns } => { |
197 | 0 | writeln!(f, "DataFrame with {} columns:", columns.len())?; |
198 | 0 | for col in columns { |
199 | 0 | writeln!(f, " {}: {} rows", col.name, col.values.len())?; |
200 | | } |
201 | 0 | Ok(()) |
202 | | } |
203 | 0 | Value::Object(map) => { |
204 | 0 | write!(f, "{{")?; |
205 | 0 | for (i, (k, v)) in map.iter().enumerate() { |
206 | 0 | if i > 0 { write!(f, ", ")?; } |
207 | 0 | write!(f, "{k}: {v}")?; |
208 | | } |
209 | 0 | write!(f, "}}") |
210 | | } |
211 | 0 | Value::HashMap(map) => { |
212 | 0 | write!(f, "HashMap{{")?; |
213 | 0 | for (i, (k, v)) in map.iter().enumerate() { |
214 | 0 | if i > 0 { write!(f, ", ")?; } |
215 | 0 | write!(f, "{k}: {v}")?; |
216 | | } |
217 | 0 | write!(f, "}}") |
218 | | } |
219 | 0 | Value::HashSet(set) => { |
220 | 0 | write!(f, "HashSet{{")?; |
221 | 0 | for (i, v) in set.iter().enumerate() { |
222 | 0 | if i > 0 { write!(f, ", ")?; } |
223 | 0 | write!(f, "{v}")?; |
224 | | } |
225 | 0 | write!(f, "}}") |
226 | | } |
227 | | Value::Range { |
228 | 0 | start, |
229 | 0 | end, |
230 | 0 | inclusive, |
231 | | } => { |
232 | 0 | if *inclusive { |
233 | 0 | write!(f, "{start}..={end}") |
234 | | } else { |
235 | 0 | write!(f, "{start}..{end}") |
236 | | } |
237 | | } |
238 | | Value::EnumVariant { |
239 | 0 | enum_name, |
240 | 0 | variant_name, |
241 | 0 | data, |
242 | | } => { |
243 | 0 | write!(f, "{enum_name}::{variant_name}")?; |
244 | 0 | if let Some(data) = data { |
245 | 0 | write!(f, "(")?; |
246 | 0 | for (i, val) in data.iter().enumerate() { |
247 | 0 | if i > 0 { write!(f, ", ")?; } |
248 | 0 | write!(f, "{val}")?; |
249 | | } |
250 | 0 | write!(f, ")")?; |
251 | 0 | } |
252 | 0 | Ok(()) |
253 | | } |
254 | 0 | Value::Unit => write!(f, "()"), |
255 | 0 | Value::Nil => write!(f, "null"), |
256 | | } |
257 | 0 | } |
258 | | } |
259 | | |
260 | | impl Value { |
261 | | /// Check if the value is considered truthy in boolean contexts |
262 | 0 | fn is_truthy(&self) -> bool { |
263 | 0 | match self { |
264 | 0 | Value::Bool(b) => *b, |
265 | 0 | Value::Nil => false, |
266 | 0 | Value::Unit => false, |
267 | 0 | Value::Int(0) => false, |
268 | 0 | Value::Float(f) => *f != 0.0 && !f.is_nan(), |
269 | 0 | Value::String(s) => !s.is_empty(), |
270 | 0 | Value::List(items) => !items.is_empty(), |
271 | 0 | _ => true, |
272 | | } |
273 | 0 | } |
274 | | } |
275 | | |
276 | | /// REPL mode determines how input is processed |
277 | | #[derive(Debug, Clone, Copy, PartialEq)] |
278 | | pub enum ReplMode { |
279 | | Normal, // Standard Ruchy evaluation |
280 | | Shell, // Execute everything as shell commands |
281 | | Pkg, // Package management mode |
282 | | Help, // Help documentation mode |
283 | | Sql, // SQL query mode |
284 | | Math, // Mathematical expression mode |
285 | | Debug, // Debug mode with extra info and trace timing |
286 | | Time, // Time mode showing execution timing |
287 | | Test, // Test mode with assertions and table tests |
288 | | } |
289 | | |
290 | | impl ReplMode { |
291 | 0 | fn prompt(&self) -> String { |
292 | 0 | match self { |
293 | 0 | ReplMode::Normal => "ruchy> ".to_string(), |
294 | 0 | ReplMode::Shell => "shell> ".to_string(), |
295 | 0 | ReplMode::Pkg => "pkg> ".to_string(), |
296 | 0 | ReplMode::Help => "help> ".to_string(), |
297 | 0 | ReplMode::Sql => "sql> ".to_string(), |
298 | 0 | ReplMode::Math => "math> ".to_string(), |
299 | 0 | ReplMode::Debug => "debug> ".to_string(), |
300 | 0 | ReplMode::Time => "time> ".to_string(), |
301 | 0 | ReplMode::Test => "test> ".to_string(), |
302 | | } |
303 | 0 | } |
304 | | } |
305 | | |
306 | | /// Debug information for post-mortem analysis |
307 | | #[derive(Debug, Clone)] |
308 | | pub struct DebugInfo { |
309 | | /// The expression that caused the error |
310 | | pub expression: String, |
311 | | /// The error message |
312 | | pub error_message: String, |
313 | | /// Stack trace at time of error |
314 | | pub stack_trace: Vec<String>, |
315 | | /// Variable bindings at time of error |
316 | | pub bindings_snapshot: HashMap<String, Value>, |
317 | | /// Timestamp when error occurred |
318 | | pub timestamp: std::time::SystemTime, |
319 | | } |
320 | | |
321 | | // === Error Recovery UI System === |
322 | | |
323 | | /// Interactive error recovery options |
324 | | #[derive(Debug, Clone)] |
325 | | pub enum RecoveryOption { |
326 | | /// Continue with a default or empty value |
327 | | ContinueWithDefault(String), |
328 | | /// Retry with a corrected expression |
329 | | RetryWith(String), |
330 | | /// Show completion suggestions |
331 | | ShowCompletions, |
332 | | /// Discard the failed expression |
333 | | Abort, |
334 | | /// Restore from previous checkpoint |
335 | | RestoreCheckpoint, |
336 | | /// Use a specific value from history |
337 | | UseHistoryValue(usize), |
338 | | } |
339 | | |
340 | | /// Error recovery context with available options |
341 | | #[derive(Debug, Clone)] |
342 | | pub struct ErrorRecovery { |
343 | | /// The original failed expression |
344 | | pub failed_expression: String, |
345 | | /// The error that occurred |
346 | | pub error_message: String, |
347 | | /// Line and column where error occurred |
348 | | pub position: Option<(usize, usize)>, |
349 | | /// Available recovery options for this error type |
350 | | pub options: Vec<RecoveryOption>, |
351 | | /// Suggested completions if applicable |
352 | | pub completions: Vec<String>, |
353 | | /// Checkpoint at time of error for recovery |
354 | | pub error_checkpoint: Checkpoint, |
355 | | } |
356 | | |
357 | | /// Recovery result after user chooses an option |
358 | | #[derive(Debug)] |
359 | | pub enum RecoveryResult { |
360 | | /// Successfully recovered with new expression |
361 | | Recovered(String), |
362 | | /// User chose to abort the operation |
363 | | Aborted, |
364 | | /// Restored from checkpoint |
365 | | Restored, |
366 | | /// Show completions to user |
367 | | ShowCompletions(Vec<String>), |
368 | | } |
369 | | |
370 | | // === Transactional State Machine === |
371 | | |
372 | | /// Checkpoint for O(1) state recovery using persistent data structures |
373 | | #[derive(Debug, Clone)] |
374 | | pub struct Checkpoint { |
375 | | /// Persistent bindings snapshot |
376 | | bindings: im::HashMap<String, Value>, |
377 | | /// Persistent mutability tracking |
378 | | mutability: im::HashMap<String, bool>, |
379 | | /// Result history snapshot |
380 | | result_history: im::Vector<Value>, |
381 | | /// Enum definitions snapshot |
382 | | enum_definitions: im::HashMap<String, im::Vector<String>>, |
383 | | /// Timestamp of checkpoint creation |
384 | | timestamp: SystemTime, |
385 | | /// Program counter for recovery context |
386 | | _pc: usize, |
387 | | } |
388 | | |
389 | | /// REPL transaction state for reliable evaluation |
390 | | #[derive(Clone, Default)] |
391 | | pub enum ReplState { |
392 | | /// Ready to accept input |
393 | | #[default] |
394 | | Ready, |
395 | | /// Currently evaluating (with checkpoint for rollback) |
396 | | Evaluating(Checkpoint), |
397 | | /// Failed state (with checkpoint for recovery) |
398 | | Failed(Checkpoint), |
399 | | } |
400 | | |
401 | | impl Checkpoint { |
402 | | /// Create new checkpoint from current REPL state |
403 | 0 | fn from_repl(repl: &Repl) -> Self { |
404 | 0 | let bindings = repl.bindings.iter() |
405 | 0 | .map(|(k, v)| (k.clone(), v.clone())) |
406 | 0 | .collect(); |
407 | | |
408 | 0 | let mutability = repl.binding_mutability.iter() |
409 | 0 | .map(|(k, v)| (k.clone(), *v)) |
410 | 0 | .collect(); |
411 | | |
412 | 0 | let result_history = repl.result_history.iter().cloned().collect(); |
413 | | |
414 | 0 | let enum_definitions = repl.enum_definitions.iter() |
415 | 0 | .map(|(k, v)| (k.clone(), v.iter().cloned().collect())) |
416 | 0 | .collect(); |
417 | | |
418 | 0 | Self { |
419 | 0 | bindings, |
420 | 0 | mutability, |
421 | 0 | result_history, |
422 | 0 | enum_definitions, |
423 | 0 | timestamp: SystemTime::now(), |
424 | 0 | _pc: repl.history.len(), |
425 | 0 | } |
426 | 0 | } |
427 | | |
428 | | /// Restore REPL state from checkpoint (O(1) with persistent structures) |
429 | 0 | fn restore_to(&self, repl: &mut Repl) { |
430 | | // Convert from persistent structures back to std collections |
431 | 0 | repl.bindings = self.bindings.iter() |
432 | 0 | .map(|(k, v)| (k.clone(), v.clone())) |
433 | 0 | .collect(); |
434 | | |
435 | 0 | repl.binding_mutability = self.mutability.iter() |
436 | 0 | .map(|(k, v)| (k.clone(), *v)) |
437 | 0 | .collect(); |
438 | | |
439 | 0 | repl.result_history = self.result_history.iter().cloned().collect(); |
440 | | |
441 | 0 | repl.enum_definitions = self.enum_definitions.iter() |
442 | 0 | .map(|(k, v)| (k.clone(), v.iter().cloned().collect())) |
443 | 0 | .collect(); |
444 | | |
445 | | // Update history variables (_1, _2, etc.) after restoration |
446 | 0 | repl.update_history_variables(); |
447 | 0 | } |
448 | | |
449 | | /// Get checkpoint age |
450 | 0 | pub fn age(&self) -> Duration { |
451 | 0 | SystemTime::now().duration_since(self.timestamp) |
452 | 0 | .unwrap_or(Duration::ZERO) |
453 | 0 | } |
454 | | } |
455 | | |
456 | | |
457 | | impl ReplState { |
458 | | /// Transition state machine for evaluation |
459 | 0 | pub fn eval(self, repl: &mut Repl, input: &str) -> (ReplState, Result<String>) { |
460 | 0 | match self { |
461 | | ReplState::Ready => { |
462 | | // Create checkpoint before evaluation |
463 | 0 | let checkpoint = Checkpoint::from_repl(repl); |
464 | | |
465 | | // Attempt evaluation |
466 | 0 | match repl.eval_internal(input) { |
467 | 0 | Ok(result) => (ReplState::Ready, Ok(result)), |
468 | 0 | Err(e) => (ReplState::Failed(checkpoint), Err(e)), |
469 | | } |
470 | | } |
471 | 0 | ReplState::Evaluating(checkpoint) => { |
472 | | // Should not happen - evaluating state is transient |
473 | 0 | (ReplState::Failed(checkpoint), Err(anyhow::anyhow!("Invalid state transition"))) |
474 | | } |
475 | 0 | ReplState::Failed(checkpoint) => { |
476 | | // Restore from checkpoint and retry |
477 | 0 | checkpoint.restore_to(repl); |
478 | 0 | (ReplState::Ready, Err(anyhow::anyhow!("Recovered from previous failure"))) |
479 | | } |
480 | | } |
481 | 0 | } |
482 | | } |
483 | | |
484 | | /// REPL configuration |
485 | | #[derive(Clone)] |
486 | | pub struct ReplConfig { |
487 | | /// Maximum memory for evaluation (default: 10MB) |
488 | | pub max_memory: usize, |
489 | | /// Timeout for evaluation (default: 100ms) |
490 | | pub timeout: Duration, |
491 | | /// Maximum stack depth (default: 1000) |
492 | | pub max_depth: usize, |
493 | | /// Enable debug mode |
494 | | pub debug: bool, |
495 | | } |
496 | | |
497 | | impl Default for ReplConfig { |
498 | 0 | fn default() -> Self { |
499 | 0 | Self { |
500 | 0 | max_memory: 10 * 1024 * 1024, // 10MB arena allocation limit |
501 | 0 | timeout: Duration::from_millis(100), // 100ms hard limit per spec |
502 | 0 | max_depth: 1000, // 1000 frame maximum per spec |
503 | 0 | debug: false, |
504 | 0 | } |
505 | 0 | } |
506 | | } |
507 | | |
508 | | // RuchyCompleter is now imported from crate::runtime::completion |
509 | | |
510 | | // Old RuchyCompleter implementation removed - now using advanced completion from runtime::completion module |
511 | | |
512 | | // Keep only the trait implementations that rustyline needs |
513 | | // The Completer trait is already implemented in the completion module |
514 | | |
515 | | // rustyline trait implementations moved to completion.rs module |
516 | | |
517 | | /// Memory tracker for bounded allocation |
518 | | /// Arena-style memory tracker for bounded evaluation |
519 | | /// Provides fixed memory allocation with reset capability |
520 | | struct MemoryTracker { |
521 | | max_size: usize, |
522 | | current: usize, |
523 | | peak_usage: usize, |
524 | | allocation_count: usize, |
525 | | } |
526 | | |
527 | | impl MemoryTracker { |
528 | 0 | fn new(max_size: usize) -> Self { |
529 | 0 | Self { |
530 | 0 | max_size, |
531 | 0 | current: 0, |
532 | 0 | peak_usage: 0, |
533 | 0 | allocation_count: 0, |
534 | 0 | } |
535 | 0 | } |
536 | | |
537 | | /// Reset arena for new evaluation (O(1) operation) |
538 | 0 | fn reset(&mut self) { |
539 | 0 | self.current = 0; |
540 | 0 | self.allocation_count = 0; |
541 | 0 | } |
542 | | |
543 | | /// Track memory usage during evaluation |
544 | 0 | fn try_alloc(&mut self, size: usize) -> Result<()> { |
545 | 0 | if self.current + size > self.max_size { |
546 | 0 | bail!( |
547 | 0 | "Memory limit exceeded: {} + {} > {} (peak: {}, allocs: {})", |
548 | | self.current, |
549 | | size, |
550 | | self.max_size, |
551 | | self.peak_usage, |
552 | | self.allocation_count |
553 | | ); |
554 | 0 | } |
555 | 0 | self.current += size; |
556 | 0 | self.allocation_count += 1; |
557 | | |
558 | | // Track peak usage |
559 | 0 | if self.current > self.peak_usage { |
560 | 0 | self.peak_usage = self.current; |
561 | 0 | } |
562 | | |
563 | 0 | Ok(()) |
564 | 0 | } |
565 | | |
566 | | /// Get current memory usage |
567 | 0 | fn memory_used(&self) -> usize { |
568 | 0 | self.current |
569 | 0 | } |
570 | | |
571 | | /// Get peak memory usage since last reset |
572 | 0 | fn peak_memory(&self) -> usize { |
573 | 0 | self.peak_usage |
574 | 0 | } |
575 | | |
576 | | /// Get allocation count since last reset |
577 | | #[allow(dead_code)] |
578 | 0 | fn allocation_count(&self) -> usize { |
579 | 0 | self.allocation_count |
580 | 0 | } |
581 | | |
582 | | /// Check if we're approaching memory limit |
583 | 0 | fn memory_pressure(&self) -> f64 { |
584 | 0 | self.current as f64 / self.max_size as f64 |
585 | 0 | } |
586 | | } |
587 | | |
588 | | /// REPL state management with resource bounds |
589 | | pub struct Repl { |
590 | | /// History of successfully parsed expressions |
591 | | history: Vec<String>, |
592 | | /// History of evaluation results (for _ and _n variables) |
593 | | result_history: Vec<Value>, |
594 | | /// Accumulated definitions for the session |
595 | | definitions: Vec<String>, |
596 | | /// Bindings and their types/values |
597 | | bindings: HashMap<String, Value>, |
598 | | /// Mutability tracking for bindings |
599 | | binding_mutability: HashMap<String, bool>, |
600 | | /// Impl methods: `Type::method` -> (params, body) |
601 | | impl_methods: HashMap<String, (Vec<String>, Box<Expr>)>, |
602 | | /// Enum definitions: `EnumName` -> list of variant names |
603 | | enum_definitions: HashMap<String, Vec<String>>, |
604 | | /// Transpiler instance |
605 | | transpiler: Transpiler, |
606 | | /// Working directory for compilation |
607 | | temp_dir: PathBuf, |
608 | | /// Session counter for unique naming |
609 | | session_counter: usize, |
610 | | /// Configuration |
611 | | config: ReplConfig, |
612 | | /// Memory tracker |
613 | | memory: MemoryTracker, |
614 | | /// O(1) in-memory module cache: path -> parsed functions |
615 | | /// Guarantees O(1) performance regardless of storage backend (EFS, NFS, etc) |
616 | | module_cache: HashMap<String, HashMap<String, Value>>, |
617 | | /// Current REPL mode |
618 | | mode: ReplMode, |
619 | | /// Debug information from last error |
620 | | last_error_debug: Option<DebugInfo>, |
621 | | /// Error recovery context for interactive recovery |
622 | | error_recovery: Option<ErrorRecovery>, |
623 | | /// Transactional state machine for reliable evaluation |
624 | | state: ReplState, |
625 | | /// Magic command registry |
626 | | magic_registry: MagicRegistry, |
627 | | /// Unicode expander for LaTeX-style input |
628 | | unicode_expander: UnicodeExpander, |
629 | | /// Transactional state for safe evaluation |
630 | | tx_state: TransactionalState, |
631 | | } |
632 | | |
633 | | impl Repl { |
634 | | /// Create a new REPL instance with default config |
635 | | /// |
636 | | /// # Errors |
637 | | /// |
638 | | /// Returns an error if the working directory cannot be created |
639 | | /// |
640 | | /// # Examples |
641 | | /// |
642 | | /// ``` |
643 | | /// use ruchy::runtime::Repl; |
644 | | /// |
645 | | /// let repl = Repl::new(); |
646 | | /// assert!(repl.is_ok()); |
647 | | /// ``` |
648 | 0 | pub fn new() -> Result<Self> { |
649 | 0 | Self::with_config(ReplConfig::default()) |
650 | 0 | } |
651 | | |
652 | | /// Create a new REPL instance with custom config |
653 | | /// |
654 | | /// # Errors |
655 | | /// |
656 | | /// Returns an error if the working directory cannot be created |
657 | 0 | pub fn with_config(config: ReplConfig) -> Result<Self> { |
658 | 0 | let temp_dir = std::env::temp_dir().join("ruchy_repl"); |
659 | 0 | fs::create_dir_all(&temp_dir)?; |
660 | | |
661 | 0 | let memory = MemoryTracker::new(config.max_memory); |
662 | | |
663 | 0 | let mut repl = Self { |
664 | 0 | history: Vec::new(), |
665 | 0 | result_history: Vec::new(), |
666 | 0 | definitions: Vec::new(), |
667 | 0 | bindings: HashMap::new(), |
668 | 0 | binding_mutability: HashMap::new(), |
669 | 0 | impl_methods: HashMap::new(), |
670 | 0 | enum_definitions: HashMap::new(), |
671 | 0 | transpiler: Transpiler::new(), |
672 | 0 | temp_dir, |
673 | 0 | session_counter: 0, |
674 | 0 | memory, |
675 | 0 | module_cache: HashMap::new(), |
676 | 0 | mode: ReplMode::Normal, |
677 | 0 | last_error_debug: None, |
678 | 0 | error_recovery: None, |
679 | 0 | state: ReplState::Ready, |
680 | 0 | magic_registry: MagicRegistry::new(), |
681 | 0 | unicode_expander: UnicodeExpander::new(), |
682 | 0 | tx_state: TransactionalState::new(config.max_memory), |
683 | 0 | config, |
684 | 0 | }; |
685 | | |
686 | | // Initialize built-in types |
687 | 0 | repl.init_builtins(); |
688 | | |
689 | 0 | Ok(repl) |
690 | 0 | } |
691 | | |
692 | | /// Initialize built-in enum types (Option, Result) |
693 | 0 | fn init_builtins(&mut self) { |
694 | | // Register Option enum |
695 | 0 | self.enum_definitions.insert( |
696 | 0 | "Option".to_string(), |
697 | 0 | vec!["None".to_string(), "Some".to_string()], |
698 | | ); |
699 | | |
700 | | // Register Result enum |
701 | 0 | self.enum_definitions.insert( |
702 | 0 | "Result".to_string(), |
703 | 0 | vec!["Ok".to_string(), "Err".to_string()], |
704 | | ); |
705 | | |
706 | | // Add Option and Result to definitions for transpiler |
707 | 0 | self.definitions |
708 | 0 | .push("enum Option<T> { None, Some(T) }".to_string()); |
709 | 0 | self.definitions |
710 | 0 | .push("enum Result<T, E> { Ok(T), Err(E) }".to_string()); |
711 | 0 | } |
712 | | |
713 | | // === Helper Functions for Common Value Creation === |
714 | | |
715 | | /// Create an `Option::None` value |
716 | 0 | fn create_option_none() -> Value { |
717 | 0 | Value::EnumVariant { |
718 | 0 | enum_name: "Option".to_string(), |
719 | 0 | variant_name: "None".to_string(), |
720 | 0 | data: None, |
721 | 0 | } |
722 | 0 | } |
723 | | |
724 | | /// Create an `Option::Some(value)` value |
725 | 0 | fn create_option_some(value: Value) -> Value { |
726 | 0 | Value::EnumVariant { |
727 | 0 | enum_name: "Option".to_string(), |
728 | 0 | variant_name: "Some".to_string(), |
729 | 0 | data: Some(vec![value]), |
730 | 0 | } |
731 | 0 | } |
732 | | |
733 | | /// Create a `Result::Ok(value)` value |
734 | 0 | fn create_result_ok(value: Value) -> Value { |
735 | 0 | Value::EnumVariant { |
736 | 0 | enum_name: "Result".to_string(), |
737 | 0 | variant_name: "Ok".to_string(), |
738 | 0 | data: Some(vec![value]), |
739 | 0 | } |
740 | 0 | } |
741 | | |
742 | | /// Create a `Result::Err(value)` value |
743 | 0 | fn create_result_err(value: Value) -> Value { |
744 | 0 | Value::EnumVariant { |
745 | 0 | enum_name: "Result".to_string(), |
746 | 0 | variant_name: "Err".to_string(), |
747 | 0 | data: Some(vec![value]), |
748 | 0 | } |
749 | 0 | } |
750 | | |
751 | | /// Evaluate a unary math function |
752 | 0 | fn evaluate_unary_math_function( |
753 | 0 | &mut self, |
754 | 0 | args: &[Expr], |
755 | 0 | deadline: Instant, |
756 | 0 | depth: usize, |
757 | 0 | func_name: &str, |
758 | 0 | operation: fn(f64) -> f64, |
759 | 0 | ) -> Result<Value> { |
760 | 0 | self.validate_arg_count(func_name, args, 1)?; |
761 | | |
762 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
763 | 0 | match value { |
764 | 0 | Value::Float(f) => Self::ok_float(operation(f)), |
765 | 0 | Value::Int(n) => Self::ok_float(operation(n as f64)), |
766 | 0 | _ => bail!("{}", Self::numeric_arg_error(func_name)), |
767 | | } |
768 | 0 | } |
769 | | |
770 | | /// Evaluate a unary math function with validation |
771 | 0 | fn evaluate_unary_math_function_validated( |
772 | 0 | &mut self, |
773 | 0 | args: &[Expr], |
774 | 0 | deadline: Instant, |
775 | 0 | depth: usize, |
776 | 0 | func_name: &str, |
777 | 0 | operation: fn(f64) -> f64, |
778 | 0 | validator: fn(f64) -> Result<()>, |
779 | 0 | ) -> Result<Value> { |
780 | 0 | self.validate_arg_count(func_name, args, 1)?; |
781 | | |
782 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
783 | 0 | match value { |
784 | 0 | Value::Float(f) => { |
785 | 0 | validator(f)?; |
786 | 0 | Self::ok_float(operation(f)) |
787 | | } |
788 | 0 | Value::Int(n) => { |
789 | 0 | let f = n as f64; |
790 | 0 | validator(f)?; |
791 | 0 | Self::ok_float(operation(f)) |
792 | | } |
793 | 0 | _ => bail!("{}", Self::numeric_arg_error(func_name)), |
794 | | } |
795 | 0 | } |
796 | | |
797 | | // === Resource-Bounded Evaluation API === |
798 | | |
799 | | /// Create a sandboxed REPL instance for testing/fuzzing |
800 | | /// Uses minimal resource limits for safety |
801 | 0 | pub fn sandboxed() -> Result<Self> { |
802 | 0 | let config = ReplConfig { |
803 | 0 | max_memory: 1024 * 1024, // 1MB limit for sandbox |
804 | 0 | timeout: Duration::from_millis(10), // Very short timeout |
805 | 0 | max_depth: 100, // Limited recursion |
806 | 0 | debug: false, |
807 | 0 | }; |
808 | 0 | Self::with_config(config) |
809 | 0 | } |
810 | | |
811 | | /// Get current memory usage in bytes |
812 | 0 | pub fn memory_used(&self) -> usize { |
813 | 0 | self.memory.memory_used() |
814 | 0 | } |
815 | | |
816 | | /// Get peak memory usage since last evaluation |
817 | 0 | pub fn peak_memory(&self) -> usize { |
818 | 0 | self.memory.peak_memory() |
819 | 0 | } |
820 | | |
821 | | /// Get memory pressure (0.0 to 1.0) |
822 | 0 | pub fn memory_pressure(&self) -> f64 { |
823 | 0 | self.memory.memory_pressure() |
824 | 0 | } |
825 | | |
826 | | /// Check if REPL can accept new input (not at resource limits) |
827 | 0 | pub fn can_accept_input(&self) -> bool { |
828 | 0 | self.memory_pressure() < 0.95 // Less than 95% memory usage |
829 | 0 | } |
830 | | |
831 | | /// Validate that all bindings are still valid (no corruption) |
832 | 0 | pub fn bindings_valid(&self) -> bool { |
833 | | // Check that mutability tracking doesn't have orphaned entries |
834 | | // (bindings without mutability entries are allowed - they default to immutable) |
835 | 0 | for name in self.binding_mutability.keys() { |
836 | 0 | if !self.bindings.contains_key(name) { |
837 | 0 | return false; |
838 | 0 | } |
839 | | } |
840 | 0 | true |
841 | 0 | } |
842 | | |
843 | | /// Evaluate with explicit resource bounds (for testing) |
844 | 0 | pub fn eval_bounded(&mut self, input: &str, max_memory: usize, timeout: Duration) -> Result<String> { |
845 | | // Save current config |
846 | 0 | let old_config = self.config.clone(); |
847 | | |
848 | | // Apply working bounds |
849 | 0 | self.config.max_memory = max_memory; |
850 | 0 | self.config.timeout = timeout; |
851 | | |
852 | | // Update memory tracker limit |
853 | 0 | self.memory.max_size = max_memory; |
854 | | |
855 | | // Evaluate |
856 | 0 | let result = self.eval(input); |
857 | | |
858 | | // Restore original config |
859 | 0 | self.config = old_config; |
860 | 0 | self.memory.max_size = self.config.max_memory; |
861 | | |
862 | 0 | result |
863 | 0 | } |
864 | | |
865 | | /// Evaluate an expression string and return the Value |
866 | | /// |
867 | | /// This is used for one-liner evaluation from CLI |
868 | | /// |
869 | | /// # Errors |
870 | | /// |
871 | | /// Returns an error if parsing or evaluation fails |
872 | 0 | pub fn evaluate_expr_str(&mut self, input: &str, deadline: Option<Instant>) -> Result<Value> { |
873 | | // Reset memory tracker for fresh evaluation |
874 | 0 | self.memory.reset(); |
875 | | |
876 | | // Track input memory |
877 | 0 | self.memory.try_alloc(input.len())?; |
878 | | |
879 | | // Use provided deadline or default timeout |
880 | 0 | let deadline = deadline.unwrap_or_else(|| Instant::now() + self.config.timeout); |
881 | | |
882 | | // Preprocess macro syntax: convert println! -> println, etc. |
883 | 0 | let preprocessed_input = Self::preprocess_macro_syntax(input); |
884 | | |
885 | | // Parse the input |
886 | 0 | let mut parser = Parser::new(&preprocessed_input); |
887 | 0 | let ast = parser.parse().context("Failed to parse input")?; |
888 | | |
889 | | // Check memory for AST |
890 | 0 | self.memory.try_alloc(std::mem::size_of_val(&ast))?; |
891 | | |
892 | | // Evaluate the expression |
893 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
894 | | |
895 | | // Handle let bindings specially (for backward compatibility) |
896 | 0 | if let ExprKind::Let { name, type_annotation: _, is_mutable, .. } = &ast.kind { |
897 | 0 | self.create_binding(name.clone(), value.clone(), *is_mutable); |
898 | 0 | } |
899 | | |
900 | 0 | Ok(value) |
901 | 0 | } |
902 | | |
903 | | // === Transactional Evaluation API === |
904 | | |
905 | | /// Evaluate with transactional state machine |
906 | 0 | pub fn eval_transactional(&mut self, input: &str) -> Result<String> { |
907 | 0 | let (new_state, result) = std::mem::take(&mut self.state).eval(self, input); |
908 | 0 | self.state = new_state; |
909 | 0 | result |
910 | 0 | } |
911 | | |
912 | | /// Create checkpoint of current state |
913 | | /// |
914 | | /// # Example |
915 | | /// ``` |
916 | | /// use ruchy::runtime::Repl; |
917 | | /// |
918 | | /// let mut repl = Repl::new().unwrap(); |
919 | | /// repl.eval("let x = 42").unwrap(); |
920 | | /// let checkpoint = repl.checkpoint(); |
921 | | /// repl.eval("let x = 100").unwrap(); |
922 | | /// repl.restore_checkpoint(&checkpoint); |
923 | | /// assert_eq!(repl.eval("x").unwrap(), "42"); |
924 | | /// ``` |
925 | 0 | pub fn checkpoint(&self) -> Checkpoint { |
926 | 0 | Checkpoint::from_repl(self) |
927 | 0 | } |
928 | | |
929 | | /// Restore from checkpoint |
930 | | /// |
931 | | /// # Example |
932 | | /// ``` |
933 | | /// use ruchy::runtime::Repl; |
934 | | /// |
935 | | /// let mut repl = Repl::new().unwrap(); |
936 | | /// let checkpoint = repl.checkpoint(); |
937 | | /// repl.eval("let y = 100").unwrap(); |
938 | | /// repl.restore_checkpoint(&checkpoint); |
939 | | /// // y is no longer defined after restore |
940 | | /// ``` |
941 | 0 | pub fn restore_checkpoint(&mut self, checkpoint: &Checkpoint) { |
942 | 0 | checkpoint.restore_to(self); |
943 | 0 | self.state = ReplState::Ready; |
944 | 0 | } |
945 | | |
946 | | /// Get current state |
947 | 0 | pub fn get_state(&self) -> &ReplState { |
948 | 0 | &self.state |
949 | 0 | } |
950 | | |
951 | | /// Set state (for testing purposes only - do not use in production) |
952 | 0 | pub fn set_state_for_testing(&mut self, state: ReplState) { |
953 | 0 | self.state = state; |
954 | 0 | } |
955 | | |
956 | | /// Get result history length (for debugging) |
957 | 0 | pub fn result_history_len(&self) -> usize { |
958 | 0 | self.result_history.len() |
959 | 0 | } |
960 | | |
961 | | /// Get bindings (for replay testing) |
962 | 0 | pub fn get_bindings(&self) -> &HashMap<String, Value> { |
963 | 0 | &self.bindings |
964 | 0 | } |
965 | | |
966 | | /// Get mutable bindings (for replay testing) |
967 | 0 | pub fn get_bindings_mut(&mut self) -> &mut HashMap<String, Value> { |
968 | 0 | &mut self.bindings |
969 | 0 | } |
970 | | |
971 | | /// Clear bindings (for replay testing) |
972 | 0 | pub fn clear_bindings(&mut self) { |
973 | 0 | self.bindings.clear(); |
974 | 0 | self.binding_mutability.clear(); |
975 | 0 | } |
976 | | |
977 | | /// Get last error (for magic commands) |
978 | 0 | pub fn get_last_error(&self) -> Option<&DebugInfo> { |
979 | 0 | self.last_error_debug.as_ref() |
980 | 0 | } |
981 | | |
982 | | /// Check if REPL is in failed state |
983 | 0 | pub fn is_failed(&self) -> bool { |
984 | 0 | matches!(self.state, ReplState::Failed(_)) |
985 | 0 | } |
986 | | |
987 | | /// Recover from failed state (if applicable) |
988 | 0 | pub fn recover(&mut self) -> Result<String> { |
989 | 0 | match std::mem::take(&mut self.state) { |
990 | 0 | ReplState::Failed(checkpoint) => { |
991 | 0 | checkpoint.restore_to(self); |
992 | 0 | self.state = ReplState::Ready; |
993 | 0 | Ok("Recovered from previous failure".to_string()) |
994 | | } |
995 | 0 | state => { |
996 | | // Restore original state if not failed |
997 | 0 | self.state = state; |
998 | 0 | Err(anyhow::anyhow!("REPL is not in failed state")) |
999 | | } |
1000 | | } |
1001 | 0 | } |
1002 | | |
1003 | | // === Interactive Error Recovery System === |
1004 | | |
1005 | | /// Create error recovery context when evaluation fails |
1006 | 0 | pub fn create_error_recovery(&mut self, failed_expr: &str, error_msg: &str) -> ErrorRecovery { |
1007 | 0 | let checkpoint = self.checkpoint(); |
1008 | | |
1009 | | // Parse error message to determine position if possible |
1010 | 0 | let position = self.parse_error_position(error_msg); |
1011 | | |
1012 | | // Determine appropriate recovery options based on error type |
1013 | 0 | let options = self.suggest_recovery_options(failed_expr, error_msg); |
1014 | | |
1015 | | // Generate completions if appropriate |
1016 | 0 | let completions = if failed_expr.trim().is_empty() || error_msg.contains("expected expression") { |
1017 | 0 | self.generate_completions_for_error(failed_expr) |
1018 | | } else { |
1019 | 0 | Vec::new() |
1020 | | }; |
1021 | | |
1022 | 0 | let recovery = ErrorRecovery { |
1023 | 0 | failed_expression: failed_expr.to_string(), |
1024 | 0 | error_message: error_msg.to_string(), |
1025 | 0 | position, |
1026 | 0 | options, |
1027 | 0 | completions, |
1028 | 0 | error_checkpoint: checkpoint, |
1029 | 0 | }; |
1030 | | |
1031 | 0 | self.error_recovery = Some(recovery.clone()); |
1032 | 0 | recovery |
1033 | 0 | } |
1034 | | |
1035 | | /// Parse error position from error message |
1036 | 0 | fn parse_error_position(&self, error_msg: &str) -> Option<(usize, usize)> { |
1037 | | // Try to extract line:column from common error formats |
1038 | 0 | if let Some(caps) = regex::Regex::new(r"line (\d+):(\d+)") |
1039 | 0 | .ok() |
1040 | 0 | .and_then(|re| re.captures(error_msg)) |
1041 | | { |
1042 | 0 | if let (Ok(line), Ok(col)) = ( |
1043 | 0 | caps.get(1)?.as_str().parse::<usize>(), |
1044 | 0 | caps.get(2)?.as_str().parse::<usize>() |
1045 | | ) { |
1046 | 0 | return Some((line, col)); |
1047 | 0 | } |
1048 | 0 | } |
1049 | 0 | None |
1050 | 0 | } |
1051 | | |
1052 | | /// Suggest appropriate recovery options based on error type |
1053 | 0 | fn suggest_recovery_options(&self, failed_expr: &str, error_msg: &str) -> Vec<RecoveryOption> { |
1054 | 0 | let mut options = Vec::new(); |
1055 | | |
1056 | | // Common recovery options based on error patterns |
1057 | 0 | if error_msg.contains("Unexpected EOF") || error_msg.contains("expected expression") || |
1058 | 0 | error_msg.contains("Unexpected end of input") || error_msg.contains("end of input") { |
1059 | 0 | if failed_expr.starts_with("let ") && failed_expr.ends_with(" = ") { |
1060 | 0 | if let Some(without_let) = failed_expr.strip_prefix("let ") { |
1061 | 0 | if let Some(var_name) = without_let.strip_suffix(" = ") { |
1062 | 0 | options.push(RecoveryOption::ContinueWithDefault( |
1063 | 0 | format!("let {var_name} = ()") |
1064 | 0 | )); |
1065 | 0 | options.push(RecoveryOption::RetryWith( |
1066 | 0 | format!("let {var_name} = 0") |
1067 | 0 | )); |
1068 | 0 | } |
1069 | 0 | } |
1070 | 0 | } |
1071 | 0 | options.push(RecoveryOption::ShowCompletions); |
1072 | 0 | } |
1073 | | |
1074 | 0 | if error_msg.to_lowercase().contains("undefined variable") || error_msg.contains("not found") { |
1075 | | // Suggest similar variable names |
1076 | 0 | if let Some(undefined_var) = self.extract_undefined_variable(error_msg) { |
1077 | 0 | let similar_vars = self.find_similar_variables(&undefined_var); |
1078 | 0 | for similar_var in &similar_vars { |
1079 | 0 | options.push(RecoveryOption::RetryWith( |
1080 | 0 | failed_expr.replace(&undefined_var, similar_var) |
1081 | 0 | )); |
1082 | 0 | } |
1083 | | |
1084 | | // If no similar variables found, provide a default fallback |
1085 | 0 | if similar_vars.is_empty() { |
1086 | 0 | options.push(RecoveryOption::ContinueWithDefault(format!("let {undefined_var} = ()"))); |
1087 | 0 | options.push(RecoveryOption::RetryWith("0".to_string())); // Simple default value |
1088 | 0 | } |
1089 | 0 | } |
1090 | 0 | } |
1091 | | |
1092 | 0 | if error_msg.contains("type mismatch") || error_msg.contains("cannot convert") { |
1093 | 0 | // Suggest type conversions |
1094 | 0 | options.push(RecoveryOption::RetryWith( |
1095 | 0 | format!("{}.to_string()", failed_expr.trim()) |
1096 | 0 | )); |
1097 | 0 | } |
1098 | | |
1099 | | // Always provide these standard options |
1100 | 0 | options.push(RecoveryOption::Abort); |
1101 | 0 | options.push(RecoveryOption::RestoreCheckpoint); |
1102 | | |
1103 | | // Suggest using recent history values |
1104 | 0 | if !self.result_history.is_empty() { |
1105 | 0 | for (i, _) in self.result_history.iter().enumerate().take(3) { |
1106 | 0 | options.push(RecoveryOption::UseHistoryValue(i + 1)); |
1107 | 0 | } |
1108 | 0 | } |
1109 | | |
1110 | 0 | options |
1111 | 0 | } |
1112 | | |
1113 | | /// Extract undefined variable name from error message |
1114 | 0 | pub fn extract_undefined_variable(&self, error_msg: &str) -> Option<String> { |
1115 | | // Try to find variable name in various error message formats |
1116 | | // Pattern for "Undefined variable: name" |
1117 | 0 | if let Some(caps) = regex::Regex::new(r"Undefined variable: ([a-zA-Z_][a-zA-Z0-9_]*)") |
1118 | 0 | .ok() |
1119 | 0 | .and_then(|re| re.captures(error_msg)) |
1120 | | { |
1121 | 0 | return Some(caps.get(1)?.as_str().to_string()); |
1122 | 0 | } |
1123 | | |
1124 | | // Pattern for "undefined variable name" or "undefined variable 'name'" |
1125 | 0 | if let Some(caps) = regex::Regex::new(r#"undefined variable[: ]+['"]?([a-zA-Z_][a-zA-Z0-9_]*)['"]?"#) |
1126 | 0 | .ok() |
1127 | 0 | .and_then(|re| re.captures(error_msg)) |
1128 | | { |
1129 | 0 | return Some(caps.get(1)?.as_str().to_string()); |
1130 | 0 | } |
1131 | | |
1132 | | // Pattern for "variable name not found" |
1133 | 0 | if let Some(caps) = regex::Regex::new(r#"variable[: ]+['"]?([a-zA-Z_][a-zA-Z0-9_]*)['"]? not found"#) |
1134 | 0 | .ok() |
1135 | 0 | .and_then(|re| re.captures(error_msg)) |
1136 | | { |
1137 | 0 | return Some(caps.get(1)?.as_str().to_string()); |
1138 | 0 | } |
1139 | | |
1140 | 0 | None |
1141 | 0 | } |
1142 | | |
1143 | | /// Find variables similar to the undefined one (for typo correction) |
1144 | 0 | pub fn find_similar_variables(&self, target: &str) -> Vec<String> { |
1145 | 0 | let mut similar = Vec::new(); |
1146 | | |
1147 | 0 | for var_name in self.bindings.keys() { |
1148 | 0 | let distance = self.edit_distance(target, var_name); |
1149 | | // Suggest variables with edit distance <= 2 |
1150 | 0 | if distance <= 2 && distance > 0 { |
1151 | 0 | similar.push(var_name.clone()); |
1152 | 0 | } |
1153 | | } |
1154 | | |
1155 | | // Sort by similarity (lower distance first) |
1156 | 0 | similar.sort_by_key(|var| self.edit_distance(target, var)); |
1157 | 0 | similar.truncate(5); // Limit to top 5 suggestions |
1158 | 0 | similar |
1159 | 0 | } |
1160 | | |
1161 | | /// Calculate edit distance between two strings (Levenshtein distance) |
1162 | 0 | pub fn edit_distance(&self, a: &str, b: &str) -> usize { |
1163 | 0 | let a_chars: Vec<char> = a.chars().collect(); |
1164 | 0 | let b_chars: Vec<char> = b.chars().collect(); |
1165 | 0 | let mut matrix = vec![vec![0; b_chars.len() + 1]; a_chars.len() + 1]; |
1166 | | |
1167 | | // Initialize first row and column |
1168 | 0 | for (i, row) in matrix.iter_mut().enumerate().take(a_chars.len() + 1) { |
1169 | 0 | row[0] = i; |
1170 | 0 | } |
1171 | 0 | for j in 0..=b_chars.len() { |
1172 | 0 | matrix[0][j] = j; |
1173 | 0 | } |
1174 | | |
1175 | | // Fill the matrix |
1176 | 0 | for i in 1..=a_chars.len() { |
1177 | 0 | for j in 1..=b_chars.len() { |
1178 | 0 | let cost = usize::from(a_chars[i-1] != b_chars[j-1]); |
1179 | 0 | matrix[i][j] = std::cmp::min( |
1180 | 0 | std::cmp::min( |
1181 | 0 | matrix[i-1][j] + 1, // deletion |
1182 | 0 | matrix[i][j-1] + 1 // insertion |
1183 | 0 | ), |
1184 | 0 | matrix[i-1][j-1] + cost // substitution |
1185 | 0 | ); |
1186 | 0 | } |
1187 | | } |
1188 | | |
1189 | 0 | matrix[a_chars.len()][b_chars.len()] |
1190 | 0 | } |
1191 | | |
1192 | | /// Generate completions for incomplete expressions |
1193 | 0 | pub fn generate_completions_for_error(&self, partial_expr: &str) -> Vec<String> { |
1194 | 0 | let mut completions = Vec::new(); |
1195 | | |
1196 | 0 | if partial_expr.trim().is_empty() { |
1197 | | // Suggest common starting patterns |
1198 | 0 | completions.extend(vec![ |
1199 | 0 | "let ".to_string(), |
1200 | 0 | "if ".to_string(), |
1201 | 0 | "match ".to_string(), |
1202 | 0 | "for ".to_string(), |
1203 | 0 | "while ".to_string(), |
1204 | 0 | "fun ".to_string(), |
1205 | | ]); |
1206 | | |
1207 | | // Add some variable names |
1208 | 0 | for var_name in self.bindings.keys().take(10) { |
1209 | 0 | completions.push(var_name.clone()); |
1210 | 0 | } |
1211 | 0 | } else if partial_expr.starts_with("let ") && partial_expr.contains(" = ") { |
1212 | 0 | // Suggest values for let bindings |
1213 | 0 | completions.extend(vec![ |
1214 | 0 | "0".to_string(), |
1215 | 0 | "true".to_string(), |
1216 | 0 | "false".to_string(), |
1217 | 0 | "[]".to_string(), |
1218 | 0 | "{}".to_string(), |
1219 | 0 | "\"\"".to_string(), |
1220 | 0 | ]); |
1221 | 0 | } else { |
1222 | | // Context-sensitive completions based on available variables |
1223 | 0 | let prefix = partial_expr.trim(); |
1224 | 0 | for var_name in self.bindings.keys() { |
1225 | 0 | if var_name.starts_with(prefix) { |
1226 | 0 | completions.push(var_name.clone()); |
1227 | 0 | } |
1228 | | } |
1229 | | } |
1230 | | |
1231 | 0 | completions.sort(); |
1232 | 0 | completions.dedup(); |
1233 | 0 | completions.truncate(10); // Limit suggestions |
1234 | 0 | completions |
1235 | 0 | } |
1236 | | |
1237 | | /// Apply a recovery option and return the result |
1238 | 0 | pub fn apply_recovery(&mut self, option: RecoveryOption) -> Result<RecoveryResult> { |
1239 | 0 | match option { |
1240 | 0 | RecoveryOption::ContinueWithDefault(expr) => { |
1241 | 0 | self.error_recovery = None; |
1242 | 0 | Ok(RecoveryResult::Recovered(expr)) |
1243 | | } |
1244 | 0 | RecoveryOption::RetryWith(expr) => { |
1245 | 0 | self.error_recovery = None; |
1246 | 0 | Ok(RecoveryResult::Recovered(expr)) |
1247 | | } |
1248 | | RecoveryOption::ShowCompletions => { |
1249 | 0 | if let Some(recovery) = &self.error_recovery { |
1250 | 0 | Ok(RecoveryResult::ShowCompletions(recovery.completions.clone())) |
1251 | | } else { |
1252 | 0 | Ok(RecoveryResult::ShowCompletions(Vec::new())) |
1253 | | } |
1254 | | } |
1255 | | RecoveryOption::Abort => { |
1256 | 0 | self.error_recovery = None; |
1257 | 0 | Ok(RecoveryResult::Aborted) |
1258 | | } |
1259 | | RecoveryOption::RestoreCheckpoint => { |
1260 | 0 | if let Some(recovery) = self.error_recovery.take() { |
1261 | 0 | recovery.error_checkpoint.restore_to(self); |
1262 | 0 | Ok(RecoveryResult::Restored) |
1263 | | } else { |
1264 | 0 | Err(anyhow::anyhow!("No error recovery context available")) |
1265 | | } |
1266 | | } |
1267 | 0 | RecoveryOption::UseHistoryValue(index) => { |
1268 | 0 | if index > 0 && index <= self.result_history.len() { |
1269 | | // Check that history value exists |
1270 | 0 | let expr = format!("_{index}"); |
1271 | 0 | self.error_recovery = None; |
1272 | 0 | Ok(RecoveryResult::Recovered(expr)) |
1273 | | } else { |
1274 | 0 | Err(anyhow::anyhow!("History index {} not available", index)) |
1275 | | } |
1276 | | } |
1277 | | } |
1278 | 0 | } |
1279 | | |
1280 | | /// Get current error recovery context |
1281 | 0 | pub fn get_error_recovery(&self) -> Option<&ErrorRecovery> { |
1282 | 0 | self.error_recovery.as_ref() |
1283 | 0 | } |
1284 | | |
1285 | | /// Clear error recovery context |
1286 | 0 | pub fn clear_error_recovery(&mut self) { |
1287 | 0 | self.error_recovery = None; |
1288 | 0 | } |
1289 | | |
1290 | | /// Format error recovery options for display |
1291 | 0 | pub fn format_error_recovery(&self, recovery: &ErrorRecovery) -> String { |
1292 | 0 | let mut output = String::new(); |
1293 | | |
1294 | 0 | output.push_str(&format!("Error: {}\n", recovery.error_message)); |
1295 | | |
1296 | 0 | if let Some((line, col)) = recovery.position { |
1297 | 0 | output.push_str(&format!(" │ {} \n", recovery.failed_expression)); |
1298 | 0 | output.push_str(&format!(" │ {}↑ at line {}:{}\n", |
1299 | 0 | " ".repeat(col.saturating_sub(1)), line, col)); |
1300 | 0 | } |
1301 | | |
1302 | 0 | output.push_str("\nRecovery Options:\n"); |
1303 | | |
1304 | 0 | for (i, option) in recovery.options.iter().enumerate() { |
1305 | 0 | match option { |
1306 | 0 | RecoveryOption::ContinueWithDefault(expr) => { |
1307 | 0 | output.push_str(&format!(" {}. Continue with: {}\n", i + 1, expr)); |
1308 | 0 | } |
1309 | 0 | RecoveryOption::RetryWith(expr) => { |
1310 | 0 | output.push_str(&format!(" {}. Retry with: {}\n", i + 1, expr)); |
1311 | 0 | } |
1312 | 0 | RecoveryOption::ShowCompletions => { |
1313 | 0 | output.push_str(&format!(" {}. Show completions\n", i + 1)); |
1314 | 0 | } |
1315 | 0 | RecoveryOption::Abort => { |
1316 | 0 | output.push_str(&format!(" {}. Abort operation\n", i + 1)); |
1317 | 0 | } |
1318 | 0 | RecoveryOption::RestoreCheckpoint => { |
1319 | 0 | output.push_str(&format!(" {}. Restore from checkpoint\n", i + 1)); |
1320 | 0 | } |
1321 | 0 | RecoveryOption::UseHistoryValue(index) => { |
1322 | 0 | output.push_str(&format!(" {}. Use history value _{}\n", i + 1, index)); |
1323 | 0 | } |
1324 | | } |
1325 | | } |
1326 | | |
1327 | 0 | if !recovery.completions.is_empty() { |
1328 | 0 | output.push_str("\nSuggestions: "); |
1329 | 0 | output.push_str(&recovery.completions.join(", ")); |
1330 | 0 | output.push('\n'); |
1331 | 0 | } |
1332 | | |
1333 | 0 | output.push_str("\nEnter option number, or press Ctrl+C to abort."); |
1334 | 0 | output |
1335 | 0 | } |
1336 | | |
1337 | | /// Check if error recovery is available |
1338 | 0 | pub fn has_error_recovery(&self) -> bool { |
1339 | 0 | self.error_recovery.is_some() |
1340 | 0 | } |
1341 | | |
1342 | | /// Get a formatted error recovery prompt if available |
1343 | 0 | pub fn get_error_recovery_prompt(&self) -> Option<String> { |
1344 | 0 | self.error_recovery.as_ref().map(|recovery| self.format_error_recovery(recovery)) |
1345 | 0 | } |
1346 | | |
1347 | | /// Internal evaluation method (called by state machine) |
1348 | 0 | fn eval_internal(&mut self, input: &str) -> Result<String> { |
1349 | | // This will be the core evaluation logic without state machine overhead |
1350 | | // For now, use a simplified approach that bypasses the state machine |
1351 | | |
1352 | | // Reset memory tracker for fresh evaluation |
1353 | 0 | self.memory.reset(); |
1354 | | |
1355 | | // Track input memory |
1356 | 0 | self.memory.try_alloc(input.len())?; |
1357 | | |
1358 | | // Check for magic commands |
1359 | 0 | let trimmed = input.trim(); |
1360 | | |
1361 | 0 | if trimmed.starts_with('%') { |
1362 | 0 | return self.handle_magic_command(trimmed); |
1363 | 0 | } |
1364 | | |
1365 | | // Check for REPL commands |
1366 | 0 | if trimmed.starts_with(':') { |
1367 | 0 | let (should_quit, output) = self.handle_command_with_output(trimmed)?; |
1368 | 0 | if should_quit { |
1369 | 0 | return Ok("Exiting REPL...".to_string()); |
1370 | 0 | } |
1371 | 0 | return Ok(output); |
1372 | 0 | } |
1373 | | |
1374 | | // Set evaluation deadline |
1375 | 0 | let deadline = Instant::now() + self.config.timeout; |
1376 | | |
1377 | | // Preprocess macro syntax: convert println! -> println, etc. |
1378 | 0 | let preprocessed = trimmed |
1379 | 0 | .replace("println!", "println") |
1380 | 0 | .replace("print!", "print") |
1381 | 0 | .replace("assert!", "assert") |
1382 | 0 | .replace("assert_eq!", "assert_eq") |
1383 | 0 | .replace("panic!", "panic") |
1384 | 0 | .replace("vec!", "vec") |
1385 | 0 | .replace("format!", "format"); |
1386 | | |
1387 | | // Try to parse the input as an expression |
1388 | 0 | let mut parser = Parser::new(&preprocessed); |
1389 | 0 | let ast = parser.parse().context("Failed to parse input")?; |
1390 | | |
1391 | | // Track AST memory |
1392 | 0 | self.memory.try_alloc(std::mem::size_of_val(&ast))?; |
1393 | | |
1394 | | // Evaluate the expression |
1395 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
1396 | | |
1397 | | // Add to history and update variables |
1398 | 0 | self.history.push(input.to_string()); |
1399 | 0 | self.result_history.push(value.clone()); |
1400 | 0 | self.update_history_variables(); |
1401 | | |
1402 | | // Return string representation (suppress Unit values from loops/statements) |
1403 | 0 | match value { |
1404 | 0 | Value::Unit => Ok(String::new()), |
1405 | 0 | _ => Ok(value.to_string()) |
1406 | | } |
1407 | 0 | } |
1408 | | |
1409 | | /// Evaluate an expression with resource bounds |
1410 | | /// |
1411 | | /// # Errors |
1412 | | /// |
1413 | | /// Returns an error if: |
1414 | | /// - Memory limit is exceeded |
1415 | | /// - Timeout is reached |
1416 | | /// - Stack depth limit is exceeded |
1417 | | /// - Parse or evaluation fails |
1418 | | /// |
1419 | | /// # Example |
1420 | | /// ``` |
1421 | | /// use ruchy::runtime::Repl; |
1422 | | /// |
1423 | | /// let mut repl = Repl::new().unwrap(); |
1424 | | /// let result = repl.eval("1 + 1"); |
1425 | | /// assert_eq!(result.unwrap(), "2"); |
1426 | | /// ``` |
1427 | | /// Handle mode-specific evaluation (complexity: 9) |
1428 | 0 | fn handle_mode_evaluation(&mut self, trimmed: &str) -> Option<Result<String>> { |
1429 | 0 | if trimmed.starts_with(':') { |
1430 | 0 | return None; // Colon commands are handled normally |
1431 | 0 | } |
1432 | | |
1433 | 0 | match self.mode { |
1434 | 0 | ReplMode::Shell => Some(self.execute_shell_command(trimmed)), |
1435 | 0 | ReplMode::Pkg => Some(self.handle_pkg_command(trimmed)), |
1436 | 0 | ReplMode::Help => Some(self.handle_help_command(trimmed)), |
1437 | 0 | ReplMode::Sql => Some(Ok(format!("SQL mode not yet implemented: {trimmed}"))), |
1438 | 0 | ReplMode::Math => Some(self.handle_math_command(trimmed)), |
1439 | 0 | ReplMode::Debug => Some(self.handle_debug_evaluation(trimmed)), |
1440 | 0 | ReplMode::Time => Some(self.handle_timed_evaluation(trimmed)), |
1441 | 0 | ReplMode::Test => Some(self.handle_test_evaluation(trimmed)), |
1442 | 0 | ReplMode::Normal => None, |
1443 | | } |
1444 | 0 | } |
1445 | | |
1446 | | /// Check if input is a shell command (complexity: 5) |
1447 | 0 | fn is_shell_command(&self, trimmed: &str) -> bool { |
1448 | 0 | if let Some(stripped) = trimmed.strip_prefix('!') { |
1449 | | // Not a shell command if it's a unary expression |
1450 | 0 | !(stripped.starts_with("true") || |
1451 | 0 | stripped.starts_with("false") || |
1452 | 0 | stripped.starts_with('(') || |
1453 | 0 | (stripped.chars().next().is_some_and(char::is_lowercase) && |
1454 | 0 | stripped.chars().all(|c| c.is_alphanumeric() || c == '_'))) |
1455 | | } else { |
1456 | 0 | false |
1457 | | } |
1458 | 0 | } |
1459 | | |
1460 | | /// Handle shell substitution in let bindings (complexity: 6) |
1461 | 0 | fn handle_shell_substitution(&mut self, input: &str, trimmed: &str) -> Option<Result<String>> { |
1462 | 0 | if !trimmed.starts_with("let ") { |
1463 | 0 | return None; |
1464 | 0 | } |
1465 | | |
1466 | 0 | if let Some(bang_pos) = trimmed.find(" = !") { |
1467 | 0 | let var_part = &trimmed[4..bang_pos]; |
1468 | 0 | let command_part = &trimmed[bang_pos + 4..]; |
1469 | | |
1470 | | // Execute the shell command |
1471 | 0 | let result = match self.execute_shell_command(command_part) { |
1472 | 0 | Ok(r) => r, |
1473 | 0 | Err(e) => return Some(Err(e)), |
1474 | | }; |
1475 | | |
1476 | | // Create a let binding with the result |
1477 | 0 | let modified_input = format!("let {} = \"{}\"", var_part, result.replace('"', "\\\"")); |
1478 | | |
1479 | | // Parse and evaluate the modified input |
1480 | 0 | let deadline = Instant::now() + self.config.timeout; |
1481 | 0 | let mut parser = Parser::new(&modified_input); |
1482 | 0 | let ast = match parser.parse() { |
1483 | 0 | Ok(a) => a, |
1484 | 0 | Err(e) => return Some(Err(e.context("Failed to parse shell substitution"))), |
1485 | | }; |
1486 | | |
1487 | 0 | if let Err(e) = self.memory.try_alloc(std::mem::size_of_val(&ast)) { |
1488 | 0 | return Some(Err(e)); |
1489 | 0 | } |
1490 | | |
1491 | 0 | match self.evaluate_expr(&ast, deadline, 0) { |
1492 | 0 | Ok(value) => { |
1493 | 0 | self.history.push(input.to_string()); |
1494 | 0 | self.result_history.push(value); |
1495 | 0 | self.update_history_variables(); |
1496 | 0 | Some(Ok(String::new())) |
1497 | | } |
1498 | 0 | Err(e) => Some(Err(e)), |
1499 | | } |
1500 | | } else { |
1501 | 0 | None |
1502 | | } |
1503 | 0 | } |
1504 | | |
1505 | | /// Main eval function with reduced complexity (complexity: 10) |
1506 | 0 | pub fn eval(&mut self, input: &str) -> Result<String> { |
1507 | | // Reset memory tracker for fresh evaluation |
1508 | 0 | self.memory.reset(); |
1509 | 0 | self.memory.try_alloc(input.len())?; |
1510 | | |
1511 | 0 | let trimmed = input.trim(); |
1512 | | |
1513 | | // Handle progressive mode activation via attributes |
1514 | 0 | if let Some(activated_mode) = self.detect_mode_activation(trimmed) { |
1515 | 0 | self.mode = activated_mode; |
1516 | 0 | return Ok(format!("Activated {} mode", self.get_mode())); |
1517 | 0 | } |
1518 | | |
1519 | | // Handle mode-specific evaluation |
1520 | 0 | if let Some(result) = self.handle_mode_evaluation(trimmed) { |
1521 | 0 | return result; |
1522 | 0 | } |
1523 | | // Handle magic commands |
1524 | 0 | if trimmed.starts_with('%') { |
1525 | 0 | return self.handle_magic_command(trimmed); |
1526 | 0 | } |
1527 | | |
1528 | | // Check for REPL commands |
1529 | 0 | if trimmed.starts_with(':') { |
1530 | 0 | let (should_quit, output) = self.handle_command_with_output(trimmed)?; |
1531 | 0 | if should_quit { |
1532 | 0 | return Ok("Exiting REPL...".to_string()); |
1533 | 0 | } |
1534 | 0 | return Ok(output); |
1535 | 0 | } |
1536 | | |
1537 | | // Check for shell commands |
1538 | 0 | if self.is_shell_command(trimmed) { |
1539 | 0 | if let Some(stripped) = trimmed.strip_prefix('!') { |
1540 | 0 | return self.execute_shell_command(stripped); |
1541 | 0 | } |
1542 | 0 | } |
1543 | | |
1544 | | // Check for introspection commands |
1545 | 0 | if let Some(stripped) = trimmed.strip_prefix("??") { |
1546 | 0 | return self.detailed_introspection(stripped.trim()); |
1547 | 0 | } else if trimmed.starts_with('?') && !trimmed.starts_with("?:") { |
1548 | 0 | return self.basic_introspection(trimmed[1..].trim()); |
1549 | 0 | } |
1550 | | |
1551 | | // Handle shell substitution in let bindings |
1552 | 0 | if let Some(result) = self.handle_shell_substitution(input, trimmed) { |
1553 | 0 | return result; |
1554 | 0 | } |
1555 | | |
1556 | | // Set evaluation deadline |
1557 | 0 | let deadline = Instant::now() + self.config.timeout; |
1558 | | |
1559 | | // Preprocess macro syntax: convert println! -> println, etc. |
1560 | 0 | let preprocessed_input = Self::preprocess_macro_syntax(input); |
1561 | | |
1562 | | // Parse the input |
1563 | 0 | let mut parser = Parser::new(&preprocessed_input); |
1564 | 0 | let ast = match parser.parse() { |
1565 | 0 | Ok(ast) => ast, |
1566 | 0 | Err(e) => { |
1567 | | // Create error recovery context for parse errors using original error message |
1568 | 0 | let _recovery = self.create_error_recovery(input, &e.to_string()); |
1569 | 0 | let parse_error = e.context("Failed to parse input"); |
1570 | 0 | return Err(parse_error); |
1571 | | } |
1572 | | }; |
1573 | | |
1574 | | // Check memory for AST |
1575 | 0 | self.memory.try_alloc(std::mem::size_of_val(&ast))?; |
1576 | | |
1577 | | // Evaluate the expression with debug capture |
1578 | 0 | let value = match self.evaluate_expr(&ast, deadline, 0) { |
1579 | 0 | Ok(value) => { |
1580 | | // Clear debug info on successful evaluation |
1581 | 0 | self.last_error_debug = None; |
1582 | 0 | value |
1583 | | } |
1584 | 0 | Err(e) => { |
1585 | | // Capture debug information |
1586 | 0 | self.last_error_debug = Some(DebugInfo { |
1587 | 0 | expression: input.to_string(), |
1588 | 0 | error_message: e.to_string(), |
1589 | 0 | stack_trace: self.generate_stack_trace(&e), |
1590 | 0 | bindings_snapshot: self.bindings.clone(), |
1591 | 0 | timestamp: std::time::SystemTime::now(), |
1592 | 0 | }); |
1593 | | |
1594 | | // Create error recovery context for interactive recovery |
1595 | 0 | let _recovery = self.create_error_recovery(input, &e.to_string()); |
1596 | | |
1597 | 0 | return Err(e); |
1598 | | } |
1599 | | }; |
1600 | | |
1601 | | // Store successful evaluation |
1602 | 0 | self.history.push(input.to_string()); |
1603 | 0 | self.result_history.push(value.clone()); |
1604 | | |
1605 | | // Update history variables |
1606 | 0 | self.update_history_variables(); |
1607 | | |
1608 | | // Let bindings are handled in evaluate_expr, no need to duplicate here |
1609 | | |
1610 | | // Return string representation (suppress Unit values from loops/statements) |
1611 | 0 | match value { |
1612 | 0 | Value::Unit => Ok(String::new()), |
1613 | 0 | _ => Ok(value.to_string()) |
1614 | | } |
1615 | 0 | } |
1616 | | |
1617 | | /// Get tab completions for the given input at the cursor position |
1618 | 0 | pub fn complete(&self, input: &str) -> Vec<String> { |
1619 | 0 | let pos = input.len(); |
1620 | 0 | let mut completer = RuchyCompleter::new(); |
1621 | 0 | completer.get_completions(input, pos, &self.bindings) |
1622 | 0 | } |
1623 | | |
1624 | | /// Get the current REPL mode |
1625 | 0 | pub fn get_mode(&self) -> &str { |
1626 | 0 | match self.mode { |
1627 | 0 | ReplMode::Normal => "normal", |
1628 | 0 | ReplMode::Shell => "shell", |
1629 | 0 | ReplMode::Pkg => "pkg", |
1630 | 0 | ReplMode::Help => "help", |
1631 | 0 | ReplMode::Sql => "sql", |
1632 | 0 | ReplMode::Math => "math", |
1633 | 0 | ReplMode::Debug => "debug", |
1634 | 0 | ReplMode::Time => "time", |
1635 | 0 | ReplMode::Test => "test", |
1636 | | } |
1637 | 0 | } |
1638 | | |
1639 | | /// Get the current prompt |
1640 | 0 | pub fn get_prompt(&self) -> String { |
1641 | 0 | self.mode.prompt() |
1642 | 0 | } |
1643 | | |
1644 | | /// Create a new binding (for let/var) - handles shadowing |
1645 | 0 | fn create_binding(&mut self, name: String, value: Value, is_mutable: bool) { |
1646 | 0 | self.bindings.insert(name.clone(), value); |
1647 | 0 | self.binding_mutability.insert(name, is_mutable); |
1648 | 0 | } |
1649 | | |
1650 | | /// Try to update an existing binding (for assignment) |
1651 | 0 | fn update_binding(&mut self, name: &str, value: Value) -> Result<()> { |
1652 | 0 | if !self.bindings.contains_key(name) { |
1653 | 0 | bail!("Cannot assign to undefined variable '{}'. \n Hint: Declare it first with 'let {} = value' or 'var {} = value'", name, name, name) |
1654 | 0 | } |
1655 | | |
1656 | 0 | let is_mutable = self.binding_mutability.get(name).copied().unwrap_or(false); |
1657 | 0 | if !is_mutable { |
1658 | 0 | bail!("Cannot assign to immutable binding '{}'. \n Hint: Use 'var {} = value' for mutable bindings or shadow with 'let {} = new_value'", name, name, name) |
1659 | 0 | } |
1660 | | |
1661 | 0 | self.bindings.insert(name.to_string(), value); |
1662 | 0 | Ok(()) |
1663 | 0 | } |
1664 | | |
1665 | | /// Get the value of a binding |
1666 | 0 | fn get_binding(&self, name: &str) -> Option<Value> { |
1667 | 0 | self.bindings.get(name).cloned() |
1668 | 0 | } |
1669 | | |
1670 | | /// Check if a binding exists |
1671 | | #[allow(dead_code)] |
1672 | 0 | fn has_binding(&self, name: &str) -> bool { |
1673 | 0 | self.bindings.contains_key(name) |
1674 | 0 | } |
1675 | | |
1676 | | /// Evaluate an expression to a value |
1677 | | #[allow(clippy::too_many_lines)] |
1678 | | #[allow(clippy::cognitive_complexity)] |
1679 | 0 | fn evaluate_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1680 | | // Check resource bounds |
1681 | 0 | self.check_resource_limits(deadline, depth)?; |
1682 | | |
1683 | | // COMPLEXITY REDUCTION: Dispatcher pattern by expression category |
1684 | 0 | match &expr.kind { |
1685 | | // Basic expressions (literals, identifiers, binaries, unaries) |
1686 | | ExprKind::Literal(_) | ExprKind::Binary { .. } | ExprKind::Unary { .. } |
1687 | | | ExprKind::Identifier(_) | ExprKind::QualifiedName { .. } => { |
1688 | 0 | self.evaluate_basic_expr(expr, deadline, depth) |
1689 | | } |
1690 | | |
1691 | | // Control flow expressions |
1692 | | ExprKind::If { .. } | ExprKind::Match { .. } | ExprKind::For { .. } |
1693 | | | ExprKind::While { .. } | ExprKind::IfLet { .. } | ExprKind::WhileLet { .. } |
1694 | | | ExprKind::Loop { .. } | ExprKind::Break { .. } | ExprKind::Continue { .. } |
1695 | | | ExprKind::TryCatch { .. } => { |
1696 | 0 | self.evaluate_control_flow_expr(expr, deadline, depth) |
1697 | | } |
1698 | | |
1699 | | // Data structure expressions |
1700 | | ExprKind::List(_) | ExprKind::Tuple(_) | ExprKind::ObjectLiteral { .. } |
1701 | | | ExprKind::Range { .. } | ExprKind::FieldAccess { .. } | ExprKind::OptionalFieldAccess { .. } |
1702 | | | ExprKind::IndexAccess { .. } | ExprKind::Slice { .. } => { |
1703 | 0 | self.evaluate_data_structure_expr(expr, deadline, depth) |
1704 | | } |
1705 | | |
1706 | | // Function and call expressions |
1707 | | ExprKind::Function { .. } | ExprKind::Lambda { .. } | ExprKind::Call { .. } |
1708 | | | ExprKind::MethodCall { .. } | ExprKind::OptionalMethodCall { .. } => { |
1709 | 0 | self.evaluate_function_expr(expr, deadline, depth) |
1710 | | } |
1711 | | |
1712 | | // Advanced language features |
1713 | 0 | _ => self.evaluate_advanced_expr(expr, deadline, depth) |
1714 | | } |
1715 | 0 | } |
1716 | | |
1717 | | // COMPLEXITY REDUCTION: Basic expressions dispatcher |
1718 | 0 | fn evaluate_basic_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1719 | 0 | match &expr.kind { |
1720 | 0 | ExprKind::Literal(lit) => self.evaluate_literal(lit), |
1721 | 0 | ExprKind::Binary { left, op, right } => { |
1722 | 0 | self.evaluate_binary_expr(left, *op, right, deadline, depth) |
1723 | | } |
1724 | 0 | ExprKind::Unary { op, operand } => { |
1725 | 0 | self.evaluate_unary_expr(*op, operand, deadline, depth) |
1726 | | } |
1727 | 0 | ExprKind::Identifier(name) => self.evaluate_identifier(name), |
1728 | 0 | ExprKind::QualifiedName { module, name } => { |
1729 | 0 | Ok(Self::evaluate_qualified_name(module, name)) |
1730 | | } |
1731 | 0 | _ => bail!("Non-basic expression in basic dispatcher"), |
1732 | | } |
1733 | 0 | } |
1734 | | |
1735 | | // COMPLEXITY REDUCTION: Control flow expressions dispatcher |
1736 | 0 | fn evaluate_control_flow_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1737 | 0 | match &expr.kind { |
1738 | 0 | ExprKind::If { condition, then_branch, else_branch } => { |
1739 | 0 | self.evaluate_if(condition, then_branch, else_branch.as_deref(), deadline, depth) |
1740 | | } |
1741 | 0 | ExprKind::Match { expr: match_expr, arms } => { |
1742 | 0 | self.evaluate_match(match_expr, arms, deadline, depth) |
1743 | | } |
1744 | 0 | ExprKind::For { var, pattern, iter, body } => { |
1745 | 0 | self.evaluate_for_loop(var, pattern.as_ref(), iter, body, deadline, depth) |
1746 | | } |
1747 | 0 | ExprKind::While { condition, body } => { |
1748 | 0 | self.evaluate_while_loop(condition, body, deadline, depth) |
1749 | | } |
1750 | 0 | ExprKind::IfLet { pattern, expr, then_branch, else_branch } => { |
1751 | 0 | self.evaluate_if_let(pattern, expr, then_branch, else_branch.as_deref(), deadline, depth) |
1752 | | } |
1753 | 0 | ExprKind::WhileLet { pattern, expr, body } => { |
1754 | 0 | self.evaluate_while_let(pattern, expr, body, deadline, depth) |
1755 | | } |
1756 | 0 | ExprKind::Loop { body } => self.evaluate_loop(body, deadline, depth), |
1757 | 0 | ExprKind::Break { .. } => Err(anyhow::anyhow!("break")), |
1758 | 0 | ExprKind::Continue { .. } => Err(anyhow::anyhow!("continue")), |
1759 | 0 | ExprKind::TryCatch { try_block, catch_clauses, finally_block } => { |
1760 | 0 | self.evaluate_try_catch_block(try_block, catch_clauses, finally_block.as_deref(), deadline, depth) |
1761 | | } |
1762 | 0 | _ => bail!("Non-control-flow expression in control flow dispatcher"), |
1763 | | } |
1764 | 0 | } |
1765 | | |
1766 | | // COMPLEXITY REDUCTION: Data structure expressions dispatcher |
1767 | 0 | fn evaluate_data_structure_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1768 | 0 | match &expr.kind { |
1769 | 0 | ExprKind::List(elements) => self.evaluate_list_literal(elements, deadline, depth), |
1770 | 0 | ExprKind::Tuple(elements) => self.evaluate_tuple_literal(elements, deadline, depth), |
1771 | 0 | ExprKind::ObjectLiteral { fields } => self.evaluate_object_literal(fields, deadline, depth), |
1772 | 0 | ExprKind::Range { start, end, inclusive } => { |
1773 | 0 | self.evaluate_range_literal(start, end, *inclusive, deadline, depth) |
1774 | | } |
1775 | 0 | ExprKind::FieldAccess { object, field } => { |
1776 | 0 | self.evaluate_field_access(object, field, deadline, depth) |
1777 | | } |
1778 | 0 | ExprKind::OptionalFieldAccess { object, field } => { |
1779 | 0 | self.evaluate_optional_field_access(object, field, deadline, depth) |
1780 | | } |
1781 | 0 | ExprKind::IndexAccess { object, index } => { |
1782 | 0 | self.evaluate_index_access(object, index, deadline, depth) |
1783 | | } |
1784 | 0 | ExprKind::Slice { object, start, end } => { |
1785 | 0 | self.evaluate_slice(object, start.as_deref(), end.as_deref(), deadline, depth) |
1786 | | } |
1787 | 0 | _ => bail!("Non-data-structure expression in data structure dispatcher"), |
1788 | | } |
1789 | 0 | } |
1790 | | |
1791 | | // COMPLEXITY REDUCTION: Function expressions dispatcher |
1792 | 0 | fn evaluate_function_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1793 | 0 | match &expr.kind { |
1794 | 0 | ExprKind::Function { name, params, body, .. } => { |
1795 | 0 | Ok(self.evaluate_function_definition(name, params, body)) |
1796 | | } |
1797 | 0 | ExprKind::Lambda { params, body } => Ok(Self::evaluate_lambda_expression(params, body)), |
1798 | 0 | ExprKind::Call { func, args } => self.evaluate_call(func, args, deadline, depth), |
1799 | 0 | ExprKind::MethodCall { receiver, method, args } => { |
1800 | 0 | let receiver_val = self.evaluate_expr(receiver, deadline, depth + 1)?; |
1801 | 0 | match receiver_val { |
1802 | 0 | Value::List(items) => { |
1803 | 0 | self.evaluate_list_methods(items, method, args, deadline, depth) |
1804 | | } |
1805 | 0 | Value::String(s) => { |
1806 | | // Special case for Array constructor |
1807 | 0 | if s == "Array constructor" && method == "new" { |
1808 | 0 | self.validate_arg_count("Array.new", args, 2)?; |
1809 | | // Evaluate arguments |
1810 | 0 | let size_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
1811 | 0 | let default_val = self.evaluate_arg(args, 1, deadline, depth)?; |
1812 | | |
1813 | | // Return a stub Array representation |
1814 | 0 | return Self::ok_string(format!("Array(size: {size_val}, default: {default_val})")); |
1815 | 0 | } |
1816 | 0 | Self::evaluate_string_methods(&s, method, args, deadline, depth) |
1817 | | } |
1818 | 0 | Value::Int(_) | Value::Float(_) => self.evaluate_numeric_methods(&receiver_val, method), |
1819 | 0 | Value::Char(c) => Self::evaluate_char_methods(c, method), |
1820 | 0 | Value::Object(obj) => { |
1821 | 0 | Self::evaluate_object_methods(obj, method, args, deadline, depth) |
1822 | | } |
1823 | 0 | Value::HashMap(map) => { |
1824 | 0 | self.evaluate_hashmap_methods(map, method, args, deadline, depth) |
1825 | | } |
1826 | 0 | Value::HashSet(set) => { |
1827 | 0 | self.evaluate_hashset_methods(set, method, args, deadline, depth) |
1828 | | } |
1829 | | Value::EnumVariant { .. } => { |
1830 | 0 | self.evaluate_enum_methods(receiver_val, method, args, deadline, depth) |
1831 | | } |
1832 | 0 | Value::DataFrame { columns } => { |
1833 | 0 | self.evaluate_dataframe_methods(columns, method, args, deadline, depth) |
1834 | | } |
1835 | 0 | _ => Err(Self::method_not_supported(method, "this type"))?, |
1836 | | } |
1837 | | } |
1838 | 0 | ExprKind::OptionalMethodCall { receiver, method, args } => { |
1839 | 0 | self.evaluate_optional_method_call(receiver, method, args, deadline, depth) |
1840 | | } |
1841 | 0 | _ => bail!("Non-function expression in function dispatcher"), |
1842 | | } |
1843 | 0 | } |
1844 | | |
1845 | | // COMPLEXITY REDUCTION: Advanced expressions dispatcher |
1846 | | /// Dispatch binding and assignment expressions (complexity: 6) |
1847 | 0 | fn dispatch_binding_exprs(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Option<Result<Value>> { |
1848 | 0 | match &expr.kind { |
1849 | 0 | ExprKind::Let { name, type_annotation: _, value, body, is_mutable } => { |
1850 | 0 | Some(self.evaluate_let_binding(name, value, body, *is_mutable, deadline, depth)) |
1851 | | } |
1852 | 0 | ExprKind::LetPattern { pattern, type_annotation: _, value, body, is_mutable } => { |
1853 | 0 | Some(self.evaluate_let_pattern(pattern, value, body, *is_mutable, deadline, depth)) |
1854 | | } |
1855 | 0 | ExprKind::Assign { target, value } => { |
1856 | 0 | Some(self.evaluate_assignment(target, value, deadline, depth)) |
1857 | | } |
1858 | 0 | ExprKind::Block(exprs) => Some(self.evaluate_block(exprs, deadline, depth)), |
1859 | 0 | ExprKind::Module { name: _name, body } => { |
1860 | 0 | Some(self.evaluate_expr(body, deadline, depth + 1)) |
1861 | | } |
1862 | 0 | _ => None, |
1863 | | } |
1864 | 0 | } |
1865 | | |
1866 | | /// Dispatch data structure expressions (complexity: 5) |
1867 | 0 | fn dispatch_data_exprs(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Option<Result<Value>> { |
1868 | 0 | match &expr.kind { |
1869 | 0 | ExprKind::DataFrame { columns } => { |
1870 | 0 | Some(self.evaluate_dataframe_literal(columns, deadline, depth)) |
1871 | | } |
1872 | 0 | ExprKind::DataFrameOperation { .. } => Some(Self::evaluate_dataframe_operation()), |
1873 | 0 | ExprKind::StructLiteral { name: _, fields } => { |
1874 | 0 | Some(self.evaluate_struct_literal(fields, deadline, depth)) |
1875 | | } |
1876 | 0 | ExprKind::Pipeline { expr, stages } => { |
1877 | 0 | Some(self.evaluate_pipeline(expr, stages, deadline, depth)) |
1878 | | } |
1879 | 0 | ExprKind::StringInterpolation { parts } => { |
1880 | 0 | Some(self.evaluate_string_interpolation(parts, deadline, depth)) |
1881 | | } |
1882 | 0 | _ => None, |
1883 | | } |
1884 | 0 | } |
1885 | | |
1886 | | /// Dispatch type definition expressions (complexity: 5) |
1887 | 0 | fn dispatch_type_definitions(&mut self, expr: &Expr) -> Option<Result<Value>> { |
1888 | 0 | match &expr.kind { |
1889 | 0 | ExprKind::Enum { name, variants, .. } => { |
1890 | 0 | Some(Ok(self.evaluate_enum_definition(name, variants))) |
1891 | | } |
1892 | 0 | ExprKind::Struct { name, fields, .. } => { |
1893 | 0 | Some(Ok(Self::evaluate_struct_definition(name, fields))) |
1894 | | } |
1895 | 0 | ExprKind::Trait { name, methods, .. } => { |
1896 | 0 | Some(Ok(Self::evaluate_trait_definition(name, methods))) |
1897 | | } |
1898 | 0 | ExprKind::Impl { for_type, methods, .. } => { |
1899 | 0 | Some(Ok(self.evaluate_impl_block(for_type, methods))) |
1900 | | } |
1901 | 0 | _ => None, |
1902 | | } |
1903 | 0 | } |
1904 | | |
1905 | | /// Dispatch Result/Option expressions (complexity: 5) |
1906 | 0 | fn dispatch_result_option_exprs(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Option<Result<Value>> { |
1907 | 0 | match &expr.kind { |
1908 | 0 | ExprKind::Ok { value } => Some(self.evaluate_result_ok(value, deadline, depth)), |
1909 | 0 | ExprKind::Err { error } => Some(self.evaluate_result_err(error, deadline, depth)), |
1910 | 0 | ExprKind::Some { value } => Some(self.evaluate_option_some(value, deadline, depth)), |
1911 | 0 | ExprKind::None => Some(Ok(Self::evaluate_option_none())), |
1912 | 0 | ExprKind::Try { expr } => Some(self.evaluate_try_operator(expr, deadline, depth)), |
1913 | 0 | _ => None, |
1914 | | } |
1915 | 0 | } |
1916 | | |
1917 | | /// Dispatch control flow expressions (complexity: 4) |
1918 | 0 | fn dispatch_control_flow_exprs(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Option<Result<Value>> { |
1919 | 0 | match &expr.kind { |
1920 | 0 | ExprKind::Return { value } => { |
1921 | 0 | if let Some(val) = value { |
1922 | 0 | let result = self.evaluate_expr(val, deadline, depth + 1); |
1923 | 0 | Some(result.and_then(|v| Err(anyhow::anyhow!("return:{}", v)))) |
1924 | | } else { |
1925 | 0 | Some(Err(anyhow::anyhow!("return:()"))) |
1926 | | } |
1927 | | } |
1928 | 0 | ExprKind::Await { expr } => Some(self.evaluate_await_expr(expr, deadline, depth)), |
1929 | 0 | ExprKind::AsyncBlock { body } => Some(self.evaluate_async_block(body, deadline, depth)), |
1930 | 0 | _ => None, |
1931 | | } |
1932 | 0 | } |
1933 | | |
1934 | | /// Main advanced expression dispatcher (complexity: 8) |
1935 | 0 | fn evaluate_advanced_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1936 | | // Try binding and assignment expressions |
1937 | 0 | if let Some(result) = self.dispatch_binding_exprs(expr, deadline, depth) { |
1938 | 0 | return result; |
1939 | 0 | } |
1940 | | |
1941 | | // Try data structure expressions |
1942 | 0 | if let Some(result) = self.dispatch_data_exprs(expr, deadline, depth) { |
1943 | 0 | return result; |
1944 | 0 | } |
1945 | | |
1946 | | // Try type definitions |
1947 | 0 | if let Some(result) = self.dispatch_type_definitions(expr) { |
1948 | 0 | return result; |
1949 | 0 | } |
1950 | | |
1951 | | // Try Result/Option expressions |
1952 | 0 | if let Some(result) = self.dispatch_result_option_exprs(expr, deadline, depth) { |
1953 | 0 | return result; |
1954 | 0 | } |
1955 | | |
1956 | | // Try control flow expressions |
1957 | 0 | if let Some(result) = self.dispatch_control_flow_exprs(expr, deadline, depth) { |
1958 | 0 | return result; |
1959 | 0 | } |
1960 | | |
1961 | | // Handle remaining cases |
1962 | 0 | match &expr.kind { |
1963 | 0 | ExprKind::Command { program, args, env: _, working_dir: _ } => { |
1964 | 0 | Self::evaluate_command(program, args, deadline, depth) |
1965 | | } |
1966 | 0 | ExprKind::Macro { name, args } => { |
1967 | 0 | self.evaluate_macro(name, args, deadline, depth) |
1968 | | } |
1969 | 0 | ExprKind::Import { path, items } => { |
1970 | 0 | self.evaluate_import(path, items) |
1971 | | } |
1972 | 0 | ExprKind::Export { items } => { |
1973 | 0 | self.evaluate_export(items) |
1974 | | } |
1975 | 0 | ExprKind::TypeCast { expr, target_type } => { |
1976 | 0 | self.evaluate_type_cast(expr, target_type, deadline, depth) |
1977 | | } |
1978 | | ExprKind::Spread { .. } => { |
1979 | 0 | bail!("Spread operator (...) can only be used inside array literals") |
1980 | | } |
1981 | 0 | _ => bail!("Expression type not yet implemented: {:?}", expr.kind), |
1982 | | } |
1983 | 0 | } |
1984 | | |
1985 | | // ======================================================================== |
1986 | | // Helper methods extracted to reduce evaluate_expr complexity |
1987 | | // Following Toyota Way: Each function has single responsibility |
1988 | | // Target: All functions < 50 cyclomatic complexity |
1989 | | // ======================================================================== |
1990 | | |
1991 | | /// Handle method calls on list values (complexity < 20) |
1992 | 0 | fn evaluate_list_methods( |
1993 | 0 | &mut self, |
1994 | 0 | items: Vec<Value>, |
1995 | 0 | method: &str, |
1996 | 0 | args: &[Expr], |
1997 | 0 | deadline: Instant, |
1998 | 0 | depth: usize, |
1999 | 0 | ) -> Result<Value> { |
2000 | 0 | match method { |
2001 | 0 | "map" => self.evaluate_list_map(items, args, deadline, depth), |
2002 | 0 | "filter" => self.evaluate_list_filter(items, args, deadline, depth), |
2003 | 0 | "reduce" => self.evaluate_list_reduce(items, args, deadline, depth), |
2004 | 0 | "len" | "length" => { |
2005 | 0 | self.validate_arg_count(method, args, 0)?; |
2006 | 0 | Self::evaluate_list_length(&items) |
2007 | | } |
2008 | 0 | "head" | "first" => { |
2009 | 0 | self.validate_arg_count(method, args, 0)?; |
2010 | 0 | Self::evaluate_list_head(&items) |
2011 | | } |
2012 | 0 | "last" => { |
2013 | 0 | self.validate_arg_count("last", args, 0)?; |
2014 | 0 | Self::evaluate_list_last(&items) |
2015 | | } |
2016 | 0 | "tail" | "rest" => { |
2017 | 0 | self.validate_arg_count(method, args, 0)?; |
2018 | 0 | Self::evaluate_list_tail(items) |
2019 | | } |
2020 | 0 | "reverse" => { |
2021 | 0 | self.validate_arg_count("reverse", args, 0)?; |
2022 | 0 | Self::evaluate_list_reverse(items) |
2023 | | } |
2024 | 0 | "sum" => { |
2025 | 0 | self.validate_arg_count("sum", args, 0)?; |
2026 | 0 | Self::evaluate_list_sum(&items) |
2027 | | } |
2028 | 0 | "push" => self.evaluate_list_push(items, args, deadline, depth), |
2029 | 0 | "pop" => Self::evaluate_list_pop(items, args), |
2030 | 0 | "append" => self.evaluate_list_append(items, args, deadline, depth), |
2031 | 0 | "insert" => self.evaluate_list_insert(items, args, deadline, depth), |
2032 | 0 | "remove" => self.evaluate_list_remove(items, args, deadline, depth), |
2033 | 0 | "slice" => self.evaluate_list_slice(items, args, deadline, depth), |
2034 | 0 | "concat" => self.evaluate_list_concat(items, args, deadline, depth), |
2035 | 0 | "flatten" => Self::evaluate_list_flatten(items, args), |
2036 | 0 | "unique" => Self::evaluate_list_unique(items, args), |
2037 | 0 | "join" => self.evaluate_list_join(items, args, deadline, depth), |
2038 | 0 | "find" => self.evaluate_list_find(items, args, deadline, depth), |
2039 | 0 | "any" => self.evaluate_list_any(items, args, deadline, depth), |
2040 | 0 | "all" => self.evaluate_list_all(items, args, deadline, depth), |
2041 | 0 | "product" => Self::evaluate_list_product(&items), |
2042 | 0 | "min" => Self::evaluate_list_min(&items), |
2043 | 0 | "max" => Self::evaluate_list_max(&items), |
2044 | 0 | "take" => self.evaluate_list_take(items, args, deadline, depth), |
2045 | 0 | "drop" => self.evaluate_list_drop(items, args, deadline, depth), |
2046 | 0 | _ => self.unknown_method_error("list", method), |
2047 | | } |
2048 | 0 | } |
2049 | | |
2050 | | /// Evaluate `list.map()` operation (complexity: 8) |
2051 | 0 | fn evaluate_list_map( |
2052 | 0 | &mut self, |
2053 | 0 | items: Vec<Value>, |
2054 | 0 | args: &[Expr], |
2055 | 0 | deadline: Instant, |
2056 | 0 | depth: usize, |
2057 | 0 | ) -> Result<Value> { |
2058 | 0 | self.validate_arg_count("map", args, 1)?; |
2059 | | |
2060 | 0 | if let ExprKind::Lambda { params, body } = &args[0].kind { |
2061 | 0 | if params.len() != 1 { |
2062 | 0 | bail!("map lambda must take exactly 1 parameter"); |
2063 | 0 | } |
2064 | | |
2065 | 0 | self.with_saved_bindings(|repl| { |
2066 | 0 | let mut results = Vec::new(); |
2067 | 0 | for item in items { |
2068 | 0 | repl.bindings.insert(params[0].name(), item); |
2069 | 0 | let result = repl.evaluate_expr(body, deadline, depth + 1)?; |
2070 | 0 | results.push(result); |
2071 | | } |
2072 | 0 | Self::ok_list(results) |
2073 | 0 | }) |
2074 | | } else { |
2075 | 0 | bail!("map currently only supports lambda expressions"); |
2076 | | } |
2077 | 0 | } |
2078 | | |
2079 | | /// Evaluate `list.filter()` operation (complexity: 9) |
2080 | 0 | fn evaluate_list_filter( |
2081 | 0 | &mut self, |
2082 | 0 | items: Vec<Value>, |
2083 | 0 | args: &[Expr], |
2084 | 0 | deadline: Instant, |
2085 | 0 | depth: usize, |
2086 | 0 | ) -> Result<Value> { |
2087 | 0 | self.validate_arg_count("filter", args, 1)?; |
2088 | | |
2089 | 0 | if let ExprKind::Lambda { params, body } = &args[0].kind { |
2090 | 0 | if params.len() != 1 { |
2091 | 0 | bail!("filter lambda must take exactly 1 parameter"); |
2092 | 0 | } |
2093 | | |
2094 | 0 | self.with_saved_bindings(|repl| { |
2095 | 0 | let mut results = Vec::new(); |
2096 | 0 | for item in items { |
2097 | 0 | repl.bindings.insert(params[0].name(), item.clone()); |
2098 | 0 | let predicate_result = repl.evaluate_expr(body, deadline, depth + 1)?; |
2099 | | |
2100 | 0 | if let Value::Bool(true) = predicate_result { |
2101 | 0 | results.push(item); |
2102 | 0 | } |
2103 | | } |
2104 | 0 | Self::ok_list(results) |
2105 | 0 | }) |
2106 | | } else { |
2107 | 0 | bail!("filter currently only supports lambda expressions"); |
2108 | | } |
2109 | 0 | } |
2110 | | |
2111 | | /// Evaluate `list.reduce()` operation (complexity: 10) |
2112 | 0 | fn evaluate_list_reduce( |
2113 | 0 | &mut self, |
2114 | 0 | items: Vec<Value>, |
2115 | 0 | args: &[Expr], |
2116 | 0 | deadline: Instant, |
2117 | 0 | depth: usize, |
2118 | 0 | ) -> Result<Value> { |
2119 | 0 | if args.len() != 2 { |
2120 | 0 | bail!("reduce expects 2 arguments: lambda and initial value"); |
2121 | 0 | } |
2122 | | |
2123 | | // Args are now: [lambda, initial_value] to match JS/Ruby style |
2124 | 0 | let mut accumulator = self.evaluate_arg(args, 1, deadline, depth)?; |
2125 | | |
2126 | | // Debug: Check what type of expression args[0] is |
2127 | 0 | match &args[0].kind { |
2128 | 0 | ExprKind::Lambda { params, body } => { |
2129 | 0 | if params.len() != 2 { |
2130 | 0 | bail!("reduce lambda must take exactly 2 parameters"); |
2131 | 0 | } |
2132 | | |
2133 | 0 | self.with_saved_bindings(|repl| { |
2134 | 0 | for item in items { |
2135 | 0 | repl.bindings.insert(params[0].name(), accumulator); |
2136 | 0 | repl.bindings.insert(params[1].name(), item); |
2137 | 0 | accumulator = repl.evaluate_expr(body, deadline, depth + 1)?; |
2138 | | } |
2139 | 0 | Ok(accumulator) |
2140 | 0 | }) |
2141 | | } |
2142 | 0 | other => { |
2143 | | // Debug: Check the actual expression kind |
2144 | 0 | match other { |
2145 | 0 | ExprKind::Call { .. } => bail!("reduce first argument is a function call, not a lambda"), |
2146 | 0 | ExprKind::Identifier(..) => bail!("reduce first argument is an identifier, not a lambda"), |
2147 | 0 | ExprKind::Literal(..) => bail!("reduce first argument is a literal, not a lambda"), |
2148 | 0 | ExprKind::Binary { .. } => bail!("reduce first argument is a binary expression, not a lambda"), |
2149 | 0 | ExprKind::Unary { .. } => bail!("reduce first argument is a unary expression, not a lambda"), |
2150 | 0 | _ => bail!("reduce first argument is not a lambda expression (some other type)"), |
2151 | | } |
2152 | | } |
2153 | | } |
2154 | 0 | } |
2155 | | |
2156 | | /// Evaluate `list.len()` and `list.length()` operations (complexity: 3) |
2157 | 0 | fn evaluate_list_length(items: &[Value]) -> Result<Value> { |
2158 | 0 | let len = items.len(); |
2159 | 0 | i64::try_from(len) |
2160 | 0 | .map(Value::Int) |
2161 | 0 | .map_err(|_| anyhow::anyhow!("List length too large to represent as i64")) |
2162 | 0 | } |
2163 | | |
2164 | | /// Evaluate `list.head()` and `list.first()` operations (complexity: 2) |
2165 | 0 | fn evaluate_list_head(items: &[Value]) -> Result<Value> { |
2166 | 0 | items |
2167 | 0 | .first() |
2168 | 0 | .cloned() |
2169 | 0 | .ok_or_else(|| anyhow::anyhow!("Empty list")) |
2170 | 0 | } |
2171 | | |
2172 | | /// Evaluate `list.last()` operation (complexity: 2) |
2173 | 0 | fn evaluate_list_last(items: &[Value]) -> Result<Value> { |
2174 | 0 | items |
2175 | 0 | .last() |
2176 | 0 | .cloned() |
2177 | 0 | .ok_or_else(|| anyhow::anyhow!("Empty list")) |
2178 | 0 | } |
2179 | | |
2180 | | /// Evaluate `list.tail()` and `list.rest()` operations (complexity: 2) |
2181 | 0 | fn evaluate_list_tail(items: Vec<Value>) -> Result<Value> { |
2182 | 0 | if items.is_empty() { |
2183 | 0 | Self::ok_list(Vec::new()) |
2184 | | } else { |
2185 | 0 | Self::ok_list(items[1..].to_vec()) |
2186 | | } |
2187 | 0 | } |
2188 | | |
2189 | | /// Evaluate `list.reverse()` operation (complexity: 2) |
2190 | 0 | fn evaluate_list_reverse(mut items: Vec<Value>) -> Result<Value> { |
2191 | 0 | items.reverse(); |
2192 | 0 | Self::ok_list(items) |
2193 | 0 | } |
2194 | | |
2195 | | /// Evaluate `list.sum()` operation (complexity: 4) |
2196 | 0 | fn evaluate_list_sum(items: &[Value]) -> Result<Value> { |
2197 | 0 | if items.is_empty() { |
2198 | 0 | return Self::ok_int(0); |
2199 | 0 | } |
2200 | | |
2201 | | // Check if we have any floats |
2202 | 0 | let has_float = items.iter().any(|v| matches!(v, Value::Float(_))); |
2203 | | |
2204 | 0 | if has_float { |
2205 | 0 | let mut sum = 0.0; |
2206 | 0 | for item in items { |
2207 | 0 | match item { |
2208 | 0 | Value::Int(n) => sum += *n as f64, |
2209 | 0 | Value::Float(f) => sum += f, |
2210 | 0 | _ => bail!("sum can only be applied to numbers"), |
2211 | | } |
2212 | | } |
2213 | 0 | Self::ok_float(sum) |
2214 | | } else { |
2215 | 0 | let mut sum = 0i64; |
2216 | 0 | for item in items { |
2217 | 0 | if let Value::Int(n) = item { |
2218 | 0 | sum += n; |
2219 | 0 | } else { |
2220 | 0 | bail!("sum can only be applied to numbers"); |
2221 | | } |
2222 | | } |
2223 | 0 | Self::ok_int(sum) |
2224 | | } |
2225 | 0 | } |
2226 | | |
2227 | | /// Evaluate `list.push()` operation (complexity: 4) |
2228 | 0 | fn evaluate_list_push( |
2229 | 0 | &mut self, |
2230 | 0 | mut items: Vec<Value>, |
2231 | 0 | args: &[Expr], |
2232 | 0 | deadline: Instant, |
2233 | 0 | depth: usize, |
2234 | 0 | ) -> Result<Value> { |
2235 | 0 | Self::validate_exact_args("push", 1, args.len())?; |
2236 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
2237 | 0 | items.push(value); |
2238 | 0 | Self::ok_list(items) |
2239 | 0 | } |
2240 | | |
2241 | | /// Evaluate `list.pop()` operation (complexity: 3) |
2242 | 0 | fn evaluate_list_pop(mut items: Vec<Value>, args: &[Expr]) -> Result<Value> { |
2243 | 0 | if !args.is_empty() { |
2244 | 0 | bail!("pop requires no arguments"); |
2245 | 0 | } |
2246 | 0 | if let Some(popped) = items.pop() { |
2247 | 0 | Ok(popped) |
2248 | | } else { |
2249 | 0 | bail!("Cannot pop from empty list") |
2250 | | } |
2251 | 0 | } |
2252 | | |
2253 | | /// Evaluate `list.append()` operation (complexity: 5) |
2254 | 0 | fn evaluate_list_append( |
2255 | 0 | &mut self, |
2256 | 0 | mut items: Vec<Value>, |
2257 | 0 | args: &[Expr], |
2258 | 0 | deadline: Instant, |
2259 | 0 | depth: usize, |
2260 | 0 | ) -> Result<Value> { |
2261 | 0 | Self::validate_exact_args("append", 1, args.len())?; |
2262 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
2263 | 0 | if let Value::List(other_items) = value { |
2264 | 0 | items.extend(other_items); |
2265 | 0 | Self::ok_list(items) |
2266 | | } else { |
2267 | 0 | bail!("append requires a list argument"); |
2268 | | } |
2269 | 0 | } |
2270 | | |
2271 | | /// Evaluate `list.insert()` operation (complexity: 6) |
2272 | 0 | fn evaluate_list_insert( |
2273 | 0 | &mut self, |
2274 | 0 | mut items: Vec<Value>, |
2275 | 0 | args: &[Expr], |
2276 | 0 | deadline: Instant, |
2277 | 0 | depth: usize, |
2278 | 0 | ) -> Result<Value> { |
2279 | 0 | if args.len() != 2 { |
2280 | 0 | bail!("insert requires exactly 2 arguments (index, value)"); |
2281 | 0 | } |
2282 | 0 | let index = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2283 | 0 | let value = self.evaluate_arg(args, 1, deadline, depth)?; |
2284 | 0 | if let Value::Int(idx) = index { |
2285 | 0 | if idx < 0 || idx as usize > items.len() { |
2286 | 0 | bail!("Insert index out of bounds"); |
2287 | 0 | } |
2288 | 0 | items.insert(idx as usize, value); |
2289 | 0 | Self::ok_list(items) |
2290 | | } else { |
2291 | 0 | bail!("Insert index must be an integer"); |
2292 | | } |
2293 | 0 | } |
2294 | | |
2295 | | /// Evaluate `list.remove()` operation (complexity: 6) |
2296 | 0 | fn evaluate_list_remove( |
2297 | 0 | &mut self, |
2298 | 0 | mut items: Vec<Value>, |
2299 | 0 | args: &[Expr], |
2300 | 0 | deadline: Instant, |
2301 | 0 | depth: usize, |
2302 | 0 | ) -> Result<Value> { |
2303 | 0 | if args.len() != 1 { |
2304 | 0 | bail!("remove requires exactly 1 argument (index)"); |
2305 | 0 | } |
2306 | 0 | let index = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2307 | 0 | if let Value::Int(idx) = index { |
2308 | 0 | if idx < 0 || idx as usize >= items.len() { |
2309 | 0 | bail!("Remove index out of bounds"); |
2310 | 0 | } |
2311 | 0 | let removed = items.remove(idx as usize); |
2312 | 0 | Ok(removed) |
2313 | | } else { |
2314 | 0 | bail!("Remove index must be an integer"); |
2315 | | } |
2316 | 0 | } |
2317 | | |
2318 | | /// Evaluate `list.slice()` operation (complexity: 7) |
2319 | 0 | fn evaluate_list_slice( |
2320 | 0 | &mut self, |
2321 | 0 | items: Vec<Value>, |
2322 | 0 | args: &[Expr], |
2323 | 0 | deadline: Instant, |
2324 | 0 | depth: usize, |
2325 | 0 | ) -> Result<Value> { |
2326 | 0 | if args.len() != 2 { |
2327 | 0 | bail!("slice requires exactly 2 arguments (start, end)"); |
2328 | 0 | } |
2329 | 0 | let start_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2330 | 0 | let end_val = self.evaluate_arg(args, 1, deadline, depth)?; |
2331 | | |
2332 | 0 | if let (Value::Int(start), Value::Int(end)) = (start_val, end_val) { |
2333 | 0 | let start = start as usize; |
2334 | 0 | let end = end as usize; |
2335 | | |
2336 | 0 | if start > items.len() || end > items.len() || start > end { |
2337 | 0 | Self::ok_list(Vec::new()) // Return empty for out of bounds |
2338 | | } else { |
2339 | 0 | Self::ok_list(items[start..end].to_vec()) |
2340 | | } |
2341 | | } else { |
2342 | 0 | bail!("slice arguments must be integers"); |
2343 | | } |
2344 | 0 | } |
2345 | | |
2346 | | /// Evaluate `list.concat()` operation (complexity: 5) |
2347 | 0 | fn evaluate_list_concat( |
2348 | 0 | &mut self, |
2349 | 0 | mut items: Vec<Value>, |
2350 | 0 | args: &[Expr], |
2351 | 0 | deadline: Instant, |
2352 | 0 | depth: usize, |
2353 | 0 | ) -> Result<Value> { |
2354 | 0 | if args.len() != 1 { |
2355 | 0 | bail!("concat requires exactly 1 argument"); |
2356 | 0 | } |
2357 | 0 | let other_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2358 | | |
2359 | 0 | if let Value::List(other_items) = other_val { |
2360 | 0 | items.extend(other_items); |
2361 | 0 | Self::ok_list(items) |
2362 | | } else { |
2363 | 0 | bail!("concat argument must be a list"); |
2364 | | } |
2365 | 0 | } |
2366 | | |
2367 | | /// Evaluate `list.flatten()` operation (complexity: 4) |
2368 | 0 | fn evaluate_list_flatten(items: Vec<Value>, args: &[Expr]) -> Result<Value> { |
2369 | 0 | if !args.is_empty() { |
2370 | 0 | bail!("flatten requires no arguments"); |
2371 | 0 | } |
2372 | 0 | let mut result = Vec::new(); |
2373 | 0 | for item in items { |
2374 | 0 | if let Value::List(inner_items) = item { |
2375 | 0 | result.extend(inner_items); |
2376 | 0 | } else { |
2377 | 0 | result.push(item); |
2378 | 0 | } |
2379 | | } |
2380 | 0 | Self::ok_list(result) |
2381 | 0 | } |
2382 | | |
2383 | | /// Evaluate `list.unique()` operation (complexity: 5) |
2384 | 0 | fn evaluate_list_unique(items: Vec<Value>, args: &[Expr]) -> Result<Value> { |
2385 | | use std::collections::HashSet; |
2386 | 0 | if !args.is_empty() { |
2387 | 0 | bail!("unique requires no arguments"); |
2388 | 0 | } |
2389 | 0 | let mut seen = HashSet::new(); |
2390 | 0 | let mut result = Vec::new(); |
2391 | | |
2392 | 0 | for item in items { |
2393 | | // Use string representation for hashing since Value doesn't implement Hash |
2394 | 0 | let key = format!("{item:?}"); |
2395 | 0 | if seen.insert(key) { |
2396 | 0 | result.push(item); |
2397 | 0 | } |
2398 | | } |
2399 | 0 | Self::ok_list(result) |
2400 | 0 | } |
2401 | | |
2402 | | /// Evaluate `list.join()` operation (complexity: 7) |
2403 | 0 | fn evaluate_list_join( |
2404 | 0 | &mut self, |
2405 | 0 | items: Vec<Value>, |
2406 | 0 | args: &[Expr], |
2407 | 0 | deadline: Instant, |
2408 | 0 | depth: usize, |
2409 | 0 | ) -> Result<Value> { |
2410 | 0 | if args.len() != 1 { |
2411 | 0 | bail!("join requires exactly 1 argument (separator)"); |
2412 | 0 | } |
2413 | 0 | let sep_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2414 | | |
2415 | 0 | if let Value::String(separator) = sep_val { |
2416 | 0 | let strings: Result<Vec<String>, _> = items.iter().map(|item| { |
2417 | 0 | if let Value::String(s) = item { |
2418 | 0 | Ok(s.clone()) |
2419 | | } else { |
2420 | 0 | bail!("join requires a list of strings"); |
2421 | | } |
2422 | 0 | }).collect(); |
2423 | | |
2424 | 0 | match strings { |
2425 | 0 | Ok(string_vec) => Self::ok_string(string_vec.join(&separator)), |
2426 | 0 | Err(e) => Err(e), |
2427 | | } |
2428 | | } else { |
2429 | 0 | bail!("join separator must be a string"); |
2430 | | } |
2431 | 0 | } |
2432 | | |
2433 | | /// Evaluate `list.find()` operation - find first element matching predicate |
2434 | 0 | fn evaluate_list_find( |
2435 | 0 | &mut self, |
2436 | 0 | items: Vec<Value>, |
2437 | 0 | args: &[Expr], |
2438 | 0 | deadline: Instant, |
2439 | 0 | depth: usize, |
2440 | 0 | ) -> Result<Value> { |
2441 | 0 | if args.len() != 1 { |
2442 | 0 | bail!("find expects exactly one argument (predicate function)"); |
2443 | 0 | } |
2444 | | |
2445 | | // Handle lambda expression |
2446 | 0 | if let ExprKind::Lambda { params, body } = &args[0].kind { |
2447 | 0 | if params.len() != 1 { |
2448 | 0 | bail!("find lambda must take exactly 1 parameter"); |
2449 | 0 | } |
2450 | | |
2451 | 0 | let saved_bindings = self.bindings.clone(); |
2452 | | |
2453 | 0 | for item in items { |
2454 | 0 | self.bindings.insert(params[0].name(), item.clone()); |
2455 | 0 | let result = self.evaluate_expr(body, deadline, depth + 1)?; |
2456 | 0 | self.bindings = saved_bindings.clone(); |
2457 | | |
2458 | 0 | if let Value::Bool(true) = result { |
2459 | 0 | return Ok(Self::create_option_some(item)); |
2460 | 0 | } |
2461 | | } |
2462 | | |
2463 | 0 | self.bindings = saved_bindings; |
2464 | | } else { |
2465 | 0 | bail!("find currently only supports lambda expressions"); |
2466 | | } |
2467 | | |
2468 | 0 | Ok(Self::create_option_none()) |
2469 | 0 | } |
2470 | | |
2471 | | /// Evaluate `list.any()` operation - check if any element matches predicate |
2472 | 0 | fn evaluate_list_any( |
2473 | 0 | &mut self, |
2474 | 0 | items: Vec<Value>, |
2475 | 0 | args: &[Expr], |
2476 | 0 | deadline: Instant, |
2477 | 0 | depth: usize, |
2478 | 0 | ) -> Result<Value> { |
2479 | 0 | if args.len() != 1 { |
2480 | 0 | bail!("any expects exactly one argument (predicate function)"); |
2481 | 0 | } |
2482 | | |
2483 | 0 | if let ExprKind::Lambda { params, body } = &args[0].kind { |
2484 | 0 | if params.len() != 1 { |
2485 | 0 | bail!("any lambda must take exactly 1 parameter"); |
2486 | 0 | } |
2487 | | |
2488 | 0 | let saved_bindings = self.bindings.clone(); |
2489 | | |
2490 | 0 | for item in items { |
2491 | 0 | self.bindings.insert(params[0].name(), item); |
2492 | 0 | let result = self.evaluate_expr(body, deadline, depth + 1)?; |
2493 | | |
2494 | 0 | if let Value::Bool(true) = result { |
2495 | 0 | self.bindings = saved_bindings; |
2496 | 0 | return Self::ok_bool(true); |
2497 | 0 | } |
2498 | | } |
2499 | | |
2500 | 0 | self.bindings = saved_bindings; |
2501 | 0 | Self::ok_bool(false) |
2502 | | } else { |
2503 | 0 | bail!("any currently only supports lambda expressions"); |
2504 | | } |
2505 | 0 | } |
2506 | | |
2507 | | /// Evaluate `list.all()` operation - check if all elements match predicate |
2508 | 0 | fn evaluate_list_all( |
2509 | 0 | &mut self, |
2510 | 0 | items: Vec<Value>, |
2511 | 0 | args: &[Expr], |
2512 | 0 | deadline: Instant, |
2513 | 0 | depth: usize, |
2514 | 0 | ) -> Result<Value> { |
2515 | 0 | if args.len() != 1 { |
2516 | 0 | bail!("all expects exactly one argument (predicate function)"); |
2517 | 0 | } |
2518 | | |
2519 | 0 | if let ExprKind::Lambda { params, body } = &args[0].kind { |
2520 | 0 | if params.len() != 1 { |
2521 | 0 | bail!("all lambda must take exactly 1 parameter"); |
2522 | 0 | } |
2523 | | |
2524 | 0 | let saved_bindings = self.bindings.clone(); |
2525 | | |
2526 | 0 | for item in items { |
2527 | 0 | self.bindings.insert(params[0].name(), item); |
2528 | 0 | let result = self.evaluate_expr(body, deadline, depth + 1)?; |
2529 | | |
2530 | 0 | match result { |
2531 | | Value::Bool(false) => { |
2532 | 0 | self.bindings = saved_bindings; |
2533 | 0 | return Self::ok_bool(false); |
2534 | | } |
2535 | 0 | Value::Bool(true) => {} |
2536 | | _ => { |
2537 | 0 | self.bindings = saved_bindings; |
2538 | 0 | bail!("Predicate must return boolean"); |
2539 | | } |
2540 | | } |
2541 | | } |
2542 | | |
2543 | 0 | self.bindings = saved_bindings; |
2544 | 0 | Self::ok_bool(true) |
2545 | | } else { |
2546 | 0 | bail!("all currently only supports lambda expressions"); |
2547 | | } |
2548 | 0 | } |
2549 | | |
2550 | | /// Evaluate `list.product()` operation - multiply all elements |
2551 | 0 | fn evaluate_list_product(items: &[Value]) -> Result<Value> { |
2552 | 0 | if items.is_empty() { |
2553 | 0 | return Self::ok_int(1); |
2554 | 0 | } |
2555 | | |
2556 | 0 | let mut product = Value::Int(1); |
2557 | 0 | for item in items { |
2558 | 0 | match (&product, item) { |
2559 | 0 | (Value::Int(a), Value::Int(b)) => { |
2560 | 0 | product = Value::Int(a * b); |
2561 | 0 | } |
2562 | 0 | (Value::Float(a), Value::Int(b)) => { |
2563 | 0 | product = Value::Float(a * (*b as f64)); |
2564 | 0 | } |
2565 | 0 | (Value::Int(a), Value::Float(b)) => { |
2566 | 0 | product = Value::Float((*a as f64) * b); |
2567 | 0 | } |
2568 | 0 | (Value::Float(a), Value::Float(b)) => { |
2569 | 0 | product = Value::Float(a * b); |
2570 | 0 | } |
2571 | 0 | _ => bail!("Product can only be applied to numbers"), |
2572 | | } |
2573 | | } |
2574 | | |
2575 | 0 | Ok(product) |
2576 | 0 | } |
2577 | | |
2578 | | /// Evaluate `list.min()` operation - find minimum element |
2579 | 0 | fn evaluate_list_min(items: &[Value]) -> Result<Value> { |
2580 | 0 | if items.is_empty() { |
2581 | 0 | return Ok(Self::create_option_none()); |
2582 | 0 | } |
2583 | | |
2584 | 0 | let mut min = items[0].clone(); |
2585 | 0 | for item in &items[1..] { |
2586 | 0 | match (&min, item) { |
2587 | 0 | (Value::Int(a), Value::Int(b)) if b < a => min = item.clone(), |
2588 | 0 | (Value::Float(a), Value::Float(b)) if b < a => min = item.clone(), |
2589 | 0 | (Value::Int(a), Value::Float(b)) if b < &(*a as f64) => min = item.clone(), |
2590 | 0 | (Value::Float(a), Value::Int(b)) if (*b as f64) < *a => min = item.clone(), |
2591 | 0 | _ => {} |
2592 | | } |
2593 | | } |
2594 | | |
2595 | 0 | Ok(Self::create_option_some(min)) |
2596 | 0 | } |
2597 | | |
2598 | | /// Evaluate `list.max()` operation - find maximum element |
2599 | 0 | fn evaluate_list_max(items: &[Value]) -> Result<Value> { |
2600 | 0 | if items.is_empty() { |
2601 | 0 | return Ok(Self::create_option_none()); |
2602 | 0 | } |
2603 | | |
2604 | 0 | let mut max = items[0].clone(); |
2605 | 0 | for item in &items[1..] { |
2606 | 0 | match (&max, item) { |
2607 | 0 | (Value::Int(a), Value::Int(b)) if b > a => max = item.clone(), |
2608 | 0 | (Value::Float(a), Value::Float(b)) if b > a => max = item.clone(), |
2609 | 0 | (Value::Int(a), Value::Float(b)) if b > &(*a as f64) => max = item.clone(), |
2610 | 0 | (Value::Float(a), Value::Int(b)) if (*b as f64) > *a => max = item.clone(), |
2611 | 0 | _ => {} |
2612 | | } |
2613 | | } |
2614 | | |
2615 | 0 | Ok(Self::create_option_some(max)) |
2616 | 0 | } |
2617 | | |
2618 | | /// Evaluate `list.take()` operation - take first n elements |
2619 | 0 | fn evaluate_list_take( |
2620 | 0 | &mut self, |
2621 | 0 | items: Vec<Value>, |
2622 | 0 | args: &[Expr], |
2623 | 0 | deadline: Instant, |
2624 | 0 | depth: usize, |
2625 | 0 | ) -> Result<Value> { |
2626 | 0 | if args.len() != 1 { |
2627 | 0 | bail!("take expects exactly one argument (count)"); |
2628 | 0 | } |
2629 | | |
2630 | 0 | let count_val = self.evaluate_expr(&args[0], deadline, depth)?; |
2631 | 0 | if let Value::Int(n) = count_val { |
2632 | 0 | let n = n.max(0) as usize; |
2633 | 0 | let taken: Vec<Value> = items.into_iter().take(n).collect(); |
2634 | 0 | Self::ok_list(taken) |
2635 | | } else { |
2636 | 0 | bail!("take count must be an integer"); |
2637 | | } |
2638 | 0 | } |
2639 | | |
2640 | | /// Evaluate `list.drop()` operation - drop first n elements |
2641 | 0 | fn evaluate_list_drop( |
2642 | 0 | &mut self, |
2643 | 0 | items: Vec<Value>, |
2644 | 0 | args: &[Expr], |
2645 | 0 | deadline: Instant, |
2646 | 0 | depth: usize, |
2647 | 0 | ) -> Result<Value> { |
2648 | 0 | if args.len() != 1 { |
2649 | 0 | bail!("drop expects exactly one argument (count)"); |
2650 | 0 | } |
2651 | | |
2652 | 0 | let count_val = self.evaluate_expr(&args[0], deadline, depth)?; |
2653 | 0 | if let Value::Int(n) = count_val { |
2654 | 0 | let n = n.max(0) as usize; |
2655 | 0 | let dropped: Vec<Value> = items.into_iter().skip(n).collect(); |
2656 | 0 | Self::ok_list(dropped) |
2657 | | } else { |
2658 | 0 | bail!("drop count must be an integer"); |
2659 | | } |
2660 | 0 | } |
2661 | | |
2662 | | /// Handle method calls on string values (complexity < 10) |
2663 | | /// Handle simple string transformation methods (complexity: 5) |
2664 | 0 | fn handle_string_transforms(s: &str, method: &str) -> Option<Result<Value>> { |
2665 | 0 | match method { |
2666 | 0 | "len" | "length" => { |
2667 | 0 | let len = s.len(); |
2668 | 0 | Some(i64::try_from(len) |
2669 | 0 | .map(Value::Int) |
2670 | 0 | .map_err(|_| anyhow::anyhow!("String length too large to represent as i64"))) |
2671 | | } |
2672 | 0 | "upper" | "to_upper" | "to_uppercase" => Some(Self::ok_string(s.to_uppercase())), |
2673 | 0 | "lower" | "to_lower" | "to_lowercase" => Some(Self::ok_string(s.to_lowercase())), |
2674 | 0 | "trim" => Some(Self::ok_string(s.trim().to_string())), |
2675 | 0 | "chars" => { |
2676 | 0 | let chars: Vec<Value> = s.chars() |
2677 | 0 | .map(|c| Value::String(c.to_string())) |
2678 | 0 | .collect(); |
2679 | 0 | Some(Self::ok_list(chars)) |
2680 | | } |
2681 | 0 | "reverse" => { |
2682 | 0 | let reversed: String = s.chars().rev().collect(); |
2683 | 0 | Some(Self::ok_string(reversed)) |
2684 | | } |
2685 | 0 | "to_int" => { |
2686 | 0 | match s.parse::<i64>() { |
2687 | 0 | Ok(n) => Some(Self::ok_int(n)), |
2688 | 0 | Err(_) => Some(Err(anyhow::anyhow!("Cannot parse '{}' as integer", s))), |
2689 | | } |
2690 | | } |
2691 | 0 | "to_float" => { |
2692 | 0 | match s.parse::<f64>() { |
2693 | 0 | Ok(f) => Some(Self::ok_float(f)), |
2694 | 0 | Err(_) => Some(Err(anyhow::anyhow!("Cannot parse '{}' as float", s))), |
2695 | | } |
2696 | | } |
2697 | 0 | "parse" => { |
2698 | | // Try to parse as int first, then float |
2699 | 0 | if let Ok(n) = s.parse::<i64>() { |
2700 | 0 | Some(Self::ok_int(n)) |
2701 | 0 | } else if let Ok(f) = s.parse::<f64>() { |
2702 | 0 | Some(Self::ok_float(f)) |
2703 | | } else { |
2704 | 0 | Some(Err(anyhow::anyhow!("Cannot parse '{}' as number", s))) |
2705 | | } |
2706 | | } |
2707 | 0 | "bytes" => { |
2708 | 0 | let bytes: Vec<Value> = s.bytes() |
2709 | 0 | .map(|b| Value::Int(i64::from(b))) |
2710 | 0 | .collect(); |
2711 | 0 | Some(Self::ok_list(bytes)) |
2712 | | } |
2713 | 0 | "is_numeric" => { |
2714 | 0 | let is_num = s.parse::<f64>().is_ok(); |
2715 | 0 | Some(Self::ok_bool(is_num)) |
2716 | | } |
2717 | 0 | "is_alpha" => { |
2718 | 0 | let is_alpha = !s.is_empty() && s.chars().all(char::is_alphabetic); |
2719 | 0 | Some(Self::ok_bool(is_alpha)) |
2720 | | } |
2721 | 0 | "is_alphanumeric" => { |
2722 | 0 | let is_alnum = !s.is_empty() && s.chars().all(char::is_alphanumeric); |
2723 | 0 | Some(Self::ok_bool(is_alnum)) |
2724 | | } |
2725 | 0 | _ => None, |
2726 | | } |
2727 | 0 | } |
2728 | | |
2729 | | /// Handle string search methods (complexity: 6) |
2730 | 0 | fn handle_string_search(s: &str, method: &str, args: &[Expr]) -> Option<Result<Value>> { |
2731 | 0 | match method { |
2732 | 0 | "contains" => { |
2733 | 0 | if args.len() != 1 { |
2734 | 0 | return Some(Err(anyhow::anyhow!("contains expects 1 argument"))); |
2735 | 0 | } |
2736 | 0 | if let ExprKind::Literal(Literal::String(needle)) = &args[0].kind { |
2737 | 0 | Some(Self::ok_bool(s.contains(needle))) |
2738 | | } else { |
2739 | 0 | Some(Err(anyhow::anyhow!("contains argument must be a string literal"))) |
2740 | | } |
2741 | | } |
2742 | 0 | "starts_with" => { |
2743 | 0 | if args.len() != 1 { |
2744 | 0 | return Some(Err(anyhow::anyhow!("starts_with expects 1 argument"))); |
2745 | 0 | } |
2746 | 0 | if let ExprKind::Literal(Literal::String(prefix)) = &args[0].kind { |
2747 | 0 | Some(Self::ok_bool(s.starts_with(prefix))) |
2748 | | } else { |
2749 | 0 | Some(Err(anyhow::anyhow!("starts_with argument must be a string literal"))) |
2750 | | } |
2751 | | } |
2752 | 0 | "ends_with" => { |
2753 | 0 | if args.len() != 1 { |
2754 | 0 | return Some(Err(anyhow::anyhow!("ends_with expects 1 argument"))); |
2755 | 0 | } |
2756 | 0 | if let ExprKind::Literal(Literal::String(suffix)) = &args[0].kind { |
2757 | 0 | Some(Self::ok_bool(s.ends_with(suffix))) |
2758 | | } else { |
2759 | 0 | Some(Err(anyhow::anyhow!("ends_with argument must be a string literal"))) |
2760 | | } |
2761 | | } |
2762 | 0 | _ => None, |
2763 | | } |
2764 | 0 | } |
2765 | | |
2766 | | /// Handle string manipulation methods (complexity: 8) |
2767 | 0 | fn handle_string_manipulation(s: &str, method: &str, args: &[Expr]) -> Option<Result<Value>> { |
2768 | 0 | match method { |
2769 | 0 | "split" => { |
2770 | 0 | if args.len() != 1 { |
2771 | 0 | return Some(Err(anyhow::anyhow!("split expects 1 argument"))); |
2772 | 0 | } |
2773 | 0 | if let ExprKind::Literal(Literal::String(sep)) = &args[0].kind { |
2774 | 0 | let parts: Vec<Value> = s.split(sep) |
2775 | 0 | .map(|p| Value::String(p.to_string())) |
2776 | 0 | .collect(); |
2777 | 0 | Some(Self::ok_list(parts)) |
2778 | | } else { |
2779 | 0 | Some(Err(anyhow::anyhow!("split separator must be a string literal"))) |
2780 | | } |
2781 | | } |
2782 | 0 | "replace" => { |
2783 | 0 | if args.len() != 2 { |
2784 | 0 | return Some(Err(anyhow::anyhow!("replace expects 2 arguments (from, to)"))); |
2785 | 0 | } |
2786 | 0 | if let (ExprKind::Literal(Literal::String(from)), ExprKind::Literal(Literal::String(to))) = |
2787 | 0 | (&args[0].kind, &args[1].kind) { |
2788 | 0 | Some(Self::ok_string(s.replace(from, to))) |
2789 | | } else { |
2790 | 0 | Some(Err(anyhow::anyhow!("replace arguments must be string literals"))) |
2791 | | } |
2792 | | } |
2793 | 0 | "repeat" => { |
2794 | 0 | if args.len() != 1 { |
2795 | 0 | return Some(Err(anyhow::anyhow!("repeat expects 1 argument"))); |
2796 | 0 | } |
2797 | 0 | if let ExprKind::Literal(Literal::Integer(count)) = &args[0].kind { |
2798 | 0 | if *count < 0 { |
2799 | 0 | Some(Err(anyhow::anyhow!("repeat count cannot be negative"))) |
2800 | | } else { |
2801 | 0 | Some(Self::ok_string(s.repeat(*count as usize))) |
2802 | | } |
2803 | | } else { |
2804 | 0 | Some(Err(anyhow::anyhow!("repeat argument must be an integer"))) |
2805 | | } |
2806 | | } |
2807 | 0 | "pad_left" => { |
2808 | 0 | if args.len() != 2 { |
2809 | 0 | return Some(Err(anyhow::anyhow!("pad_left expects 2 arguments (width, fill)"))); |
2810 | 0 | } |
2811 | 0 | if let (ExprKind::Literal(Literal::Integer(width)), ExprKind::Literal(Literal::String(fill))) = |
2812 | 0 | (&args[0].kind, &args[1].kind) { |
2813 | 0 | let width = *width as usize; |
2814 | 0 | if s.len() >= width { |
2815 | 0 | Some(Self::ok_string(s.to_string())) |
2816 | | } else { |
2817 | 0 | let padding_needed = width - s.len(); |
2818 | 0 | let fill_char = fill.chars().next().unwrap_or(' '); |
2819 | 0 | let padding = fill_char.to_string().repeat(padding_needed); |
2820 | 0 | Some(Self::ok_string(format!("{padding}{s}"))) |
2821 | | } |
2822 | | } else { |
2823 | 0 | Some(Err(anyhow::anyhow!("pad_left arguments must be (integer, string)"))) |
2824 | | } |
2825 | | } |
2826 | 0 | "pad_right" => { |
2827 | 0 | if args.len() != 2 { |
2828 | 0 | return Some(Err(anyhow::anyhow!("pad_right expects 2 arguments (width, fill)"))); |
2829 | 0 | } |
2830 | 0 | if let (ExprKind::Literal(Literal::Integer(width)), ExprKind::Literal(Literal::String(fill))) = |
2831 | 0 | (&args[0].kind, &args[1].kind) { |
2832 | 0 | let width = *width as usize; |
2833 | 0 | if s.len() >= width { |
2834 | 0 | Some(Self::ok_string(s.to_string())) |
2835 | | } else { |
2836 | 0 | let padding_needed = width - s.len(); |
2837 | 0 | let fill_char = fill.chars().next().unwrap_or(' '); |
2838 | 0 | let padding = fill_char.to_string().repeat(padding_needed); |
2839 | 0 | Some(Self::ok_string(format!("{s}{padding}"))) |
2840 | | } |
2841 | | } else { |
2842 | 0 | Some(Err(anyhow::anyhow!("pad_right arguments must be (integer, string)"))) |
2843 | | } |
2844 | | } |
2845 | 0 | _ => None, |
2846 | | } |
2847 | 0 | } |
2848 | | |
2849 | | /// Handle substring extraction (complexity: 7) |
2850 | 0 | fn handle_substring(s: &str, args: &[Expr]) -> Result<Value> { |
2851 | 0 | if args.len() == 2 { |
2852 | | // substring(start, end) |
2853 | 0 | if let (ExprKind::Literal(Literal::Integer(start)), ExprKind::Literal(Literal::Integer(end))) = |
2854 | 0 | (&args[0].kind, &args[1].kind) { |
2855 | 0 | let start_idx = (*start as usize).min(s.len()); |
2856 | 0 | let end_idx = (*end as usize).min(s.len()); |
2857 | 0 | if start_idx <= end_idx { |
2858 | 0 | Self::ok_string(s[start_idx..end_idx].to_string()) |
2859 | | } else { |
2860 | 0 | Self::ok_string(String::new()) |
2861 | | } |
2862 | | } else { |
2863 | 0 | bail!("substring arguments must be integers"); |
2864 | | } |
2865 | 0 | } else if args.len() == 1 { |
2866 | | // substring(start) - to end of string |
2867 | 0 | if let ExprKind::Literal(Literal::Integer(start)) = &args[0].kind { |
2868 | 0 | let start_idx = (*start as usize).min(s.len()); |
2869 | 0 | Self::ok_string(s[start_idx..].to_string()) |
2870 | | } else { |
2871 | 0 | bail!("substring argument must be an integer"); |
2872 | | } |
2873 | | } else { |
2874 | 0 | bail!("substring expects 1 or 2 arguments"); |
2875 | | } |
2876 | 0 | } |
2877 | | |
2878 | | /// Main string methods dispatcher (complexity: 6) |
2879 | 0 | fn evaluate_string_methods( |
2880 | 0 | s: &str, |
2881 | 0 | method: &str, |
2882 | 0 | args: &[Expr], |
2883 | 0 | _deadline: Instant, |
2884 | 0 | _depth: usize, |
2885 | 0 | ) -> Result<Value> { |
2886 | | // Try simple transforms first - these methods take no arguments |
2887 | 0 | if let Some(result) = Self::handle_string_transforms(s, method) { |
2888 | | // Check that no arguments were provided for no-arg methods |
2889 | 0 | if !args.is_empty() { |
2890 | 0 | bail!("{} requires no arguments", method); |
2891 | 0 | } |
2892 | 0 | return result; |
2893 | 0 | } |
2894 | | |
2895 | | // Try search methods |
2896 | 0 | if let Some(result) = Self::handle_string_search(s, method, args) { |
2897 | 0 | return result; |
2898 | 0 | } |
2899 | | |
2900 | | // Try manipulation methods |
2901 | 0 | if let Some(result) = Self::handle_string_manipulation(s, method, args) { |
2902 | 0 | return result; |
2903 | 0 | } |
2904 | | |
2905 | | // Handle substring specially |
2906 | 0 | if method == "substring" || method == "substr" { |
2907 | 0 | return Self::handle_substring(s, args); |
2908 | 0 | } |
2909 | | |
2910 | 0 | bail!("Unknown string method: {}", method) |
2911 | 0 | } |
2912 | | |
2913 | | /// Handle method calls on char values (complexity < 10) |
2914 | 0 | fn evaluate_char_methods(c: char, method: &str) -> Result<Value> { |
2915 | 0 | match method { |
2916 | 0 | "to_int" => Self::ok_int(c as i64), |
2917 | 0 | "to_string" => Self::ok_string(c.to_string()), |
2918 | 0 | "is_alphabetic" => Self::ok_bool(c.is_alphabetic()), |
2919 | 0 | "is_numeric" => Self::ok_bool(c.is_numeric()), |
2920 | 0 | "is_alphanumeric" => Self::ok_bool(c.is_alphanumeric()), |
2921 | 0 | "is_whitespace" => Self::ok_bool(c.is_whitespace()), |
2922 | 0 | "to_uppercase" => Self::ok_string(c.to_uppercase().to_string()), |
2923 | 0 | "to_lowercase" => Self::ok_string(c.to_lowercase().to_string()), |
2924 | 0 | _ => bail!("Unknown char method: {}", method), |
2925 | | } |
2926 | 0 | } |
2927 | | |
2928 | | /// Handle method calls on numeric values (complexity < 10) |
2929 | 0 | fn evaluate_numeric_methods(&self, value: &Value, method: &str) -> Result<Value> { |
2930 | 0 | match (value, method) { |
2931 | | // Integer-specific methods |
2932 | 0 | (Value::Int(n), "abs") => Self::ok_int(n.abs()), |
2933 | 0 | (Value::Int(n), "to_string") => Self::ok_string(n.to_string()), |
2934 | | |
2935 | | // Float-specific methods |
2936 | 0 | (Value::Float(f), "abs") => Self::ok_float(f.abs()), |
2937 | 0 | (Value::Float(f), "floor") => Self::ok_float(f.floor()), |
2938 | 0 | (Value::Float(f), "ceil") => Self::ok_float(f.ceil()), |
2939 | 0 | (Value::Float(f), "round") => Self::ok_float(f.round()), |
2940 | | |
2941 | | // Math operations that work on both (convert int to float) |
2942 | 0 | (Value::Int(n), op @ ("sqrt" | "sin" | "cos" | "tan" | "log" | "log10" | "exp")) => { |
2943 | | #[allow(clippy::cast_precision_loss)] |
2944 | 0 | let f = *n as f64; |
2945 | 0 | self.evaluate_float_math(f, op) |
2946 | | } |
2947 | 0 | (Value::Float(f), op @ ("sqrt" | "sin" | "cos" | "tan" | "log" | "log10" | "exp")) => { |
2948 | 0 | self.evaluate_float_math(*f, op) |
2949 | | } |
2950 | | |
2951 | 0 | (Value::Int(_), _) => self.unknown_method_error("integer", method), |
2952 | 0 | (Value::Float(_), _) => self.unknown_method_error("float", method), |
2953 | 0 | _ => Err(Self::method_not_supported(method, &format!("{value:?}")))?, |
2954 | | } |
2955 | 0 | } |
2956 | | |
2957 | | /// Helper for float math operations (complexity: 8) |
2958 | 0 | fn evaluate_float_math(&self, f: f64, op: &str) -> Result<Value> { |
2959 | 0 | let result = match op { |
2960 | 0 | "sqrt" => f.sqrt(), |
2961 | 0 | "sin" => f.sin(), |
2962 | 0 | "cos" => f.cos(), |
2963 | 0 | "tan" => f.tan(), |
2964 | 0 | "log" => f.ln(), |
2965 | 0 | "log10" => f.log10(), |
2966 | 0 | "exp" => f.exp(), |
2967 | 0 | _ => bail!("Unknown math operation: {}", op), |
2968 | | }; |
2969 | 0 | Self::ok_float(result) |
2970 | 0 | } |
2971 | | |
2972 | | /// Handle method calls on object values (complexity < 10) |
2973 | 0 | fn evaluate_object_methods( |
2974 | 0 | obj: HashMap<String, Value>, |
2975 | 0 | method: &str, |
2976 | 0 | _args: &[Expr], |
2977 | 0 | _deadline: Instant, |
2978 | 0 | _depth: usize, |
2979 | 0 | ) -> Result<Value> { |
2980 | 0 | match method { |
2981 | 0 | "items" => { |
2982 | | // Return list of (key, value) tuples |
2983 | 0 | let mut items = Vec::new(); |
2984 | 0 | for (key, value) in obj { |
2985 | 0 | let tuple = Value::Tuple(vec![Value::String(key), value]); |
2986 | 0 | items.push(tuple); |
2987 | 0 | } |
2988 | 0 | Self::ok_list(items) |
2989 | | } |
2990 | 0 | "keys" => { |
2991 | | // Return list of keys |
2992 | 0 | let keys: Vec<Value> = obj.keys().map(|k| Value::String(k.clone())).collect(); |
2993 | 0 | Self::ok_list(keys) |
2994 | | } |
2995 | 0 | "values" => { |
2996 | | // Return list of values |
2997 | 0 | let values: Vec<Value> = obj.values().cloned().collect(); |
2998 | 0 | Self::ok_list(values) |
2999 | | } |
3000 | 0 | "len" => { |
3001 | | // Return length of object |
3002 | 0 | Self::ok_int(obj.len() as i64) |
3003 | | } |
3004 | 0 | "has_key" => { |
3005 | | // This would need args handling - simplified for now |
3006 | 0 | bail!("has_key method requires arguments") |
3007 | | } |
3008 | 0 | _ => bail!("Unknown object method: {}", method), |
3009 | | } |
3010 | 0 | } |
3011 | | |
3012 | | /// Handle method calls on `HashMap` values (complexity < 10) |
3013 | 0 | fn evaluate_hashmap_methods( |
3014 | 0 | &mut self, |
3015 | 0 | mut map: HashMap<Value, Value>, |
3016 | 0 | method: &str, |
3017 | 0 | args: &[Expr], |
3018 | 0 | deadline: Instant, |
3019 | 0 | depth: usize, |
3020 | 0 | ) -> Result<Value> { |
3021 | 0 | match method { |
3022 | 0 | "insert" => { |
3023 | 0 | if args.len() != 2 { |
3024 | 0 | bail!("insert requires exactly 2 arguments (key, value)"); |
3025 | 0 | } |
3026 | 0 | let key = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3027 | 0 | let value = self.evaluate_arg(args, 1, deadline, depth)?; |
3028 | 0 | map.insert(key, value); |
3029 | 0 | Self::ok_hashmap(map) |
3030 | | } |
3031 | 0 | "get" => { |
3032 | 0 | if args.len() != 1 { |
3033 | 0 | bail!("get requires exactly 1 argument (key)"); |
3034 | 0 | } |
3035 | 0 | let key = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3036 | 0 | match map.get(&key) { |
3037 | 0 | Some(value) => Ok(value.clone()), |
3038 | 0 | None => Self::ok_unit(), // Could return Option::None in future |
3039 | | } |
3040 | | } |
3041 | 0 | "contains_key" => { |
3042 | 0 | if args.len() != 1 { |
3043 | 0 | bail!("contains_key requires exactly 1 argument (key)"); |
3044 | 0 | } |
3045 | 0 | let key = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3046 | 0 | Self::ok_bool(map.contains_key(&key)) |
3047 | | } |
3048 | 0 | "remove" => { |
3049 | 0 | if args.len() != 1 { |
3050 | 0 | bail!("remove requires exactly 1 argument (key)"); |
3051 | 0 | } |
3052 | 0 | let key = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3053 | 0 | let removed_value = map.remove(&key); |
3054 | 0 | match removed_value { |
3055 | 0 | Some(value) => Self::ok_tuple(vec![Value::HashMap(map), value]), |
3056 | 0 | None => Self::ok_tuple(vec![Value::HashMap(map), Value::Unit]), |
3057 | | } |
3058 | | } |
3059 | 0 | "len" => Self::ok_int(map.len() as i64), |
3060 | 0 | "is_empty" => Self::ok_bool(map.is_empty()), |
3061 | 0 | "clear" => { |
3062 | 0 | map.clear(); |
3063 | 0 | Self::ok_hashmap(map) |
3064 | | } |
3065 | 0 | _ => bail!("Unknown HashMap method: {}", method), |
3066 | | } |
3067 | 0 | } |
3068 | | |
3069 | | /// Handle basic `HashSet` methods (complexity: 6) |
3070 | 0 | fn handle_basic_hashset_methods( |
3071 | 0 | &mut self, |
3072 | 0 | mut set: HashSet<Value>, |
3073 | 0 | method: &str, |
3074 | 0 | args: &[Expr], |
3075 | 0 | deadline: Instant, |
3076 | 0 | depth: usize, |
3077 | 0 | ) -> Option<Result<Value>> { |
3078 | 0 | match method { |
3079 | 0 | "insert" => { |
3080 | 0 | if args.len() != 1 { |
3081 | 0 | return Some(Err(anyhow::anyhow!("insert requires exactly 1 argument (value)"))); |
3082 | 0 | } |
3083 | 0 | let value = match self.evaluate_expr(&args[0], deadline, depth + 1) { |
3084 | 0 | Ok(v) => v, |
3085 | 0 | Err(e) => return Some(Err(e)), |
3086 | | }; |
3087 | 0 | let was_new = set.insert(value); |
3088 | 0 | Some(Self::ok_tuple(vec![Value::HashSet(set), Self::bool_value(was_new)])) |
3089 | | } |
3090 | 0 | "contains" => { |
3091 | 0 | if args.len() != 1 { |
3092 | 0 | return Some(Err(anyhow::anyhow!("contains requires exactly 1 argument (value)"))); |
3093 | 0 | } |
3094 | 0 | let value = match self.evaluate_expr(&args[0], deadline, depth + 1) { |
3095 | 0 | Ok(v) => v, |
3096 | 0 | Err(e) => return Some(Err(e)), |
3097 | | }; |
3098 | 0 | Some(Self::ok_bool(set.contains(&value))) |
3099 | | } |
3100 | 0 | "remove" => { |
3101 | 0 | if args.len() != 1 { |
3102 | 0 | return Some(Err(anyhow::anyhow!("remove requires exactly 1 argument (value)"))); |
3103 | 0 | } |
3104 | 0 | let value = match self.evaluate_expr(&args[0], deadline, depth + 1) { |
3105 | 0 | Ok(v) => v, |
3106 | 0 | Err(e) => return Some(Err(e)), |
3107 | | }; |
3108 | 0 | let was_present = set.remove(&value); |
3109 | 0 | Some(Self::ok_tuple(vec![Value::HashSet(set), Self::bool_value(was_present)])) |
3110 | | } |
3111 | 0 | "len" => Some(Self::ok_int(set.len() as i64)), |
3112 | 0 | "is_empty" => Some(Self::ok_bool(set.is_empty())), |
3113 | 0 | "clear" => { |
3114 | 0 | set.clear(); |
3115 | 0 | Some(Self::ok_hashset(set)) |
3116 | | } |
3117 | 0 | _ => None, |
3118 | | } |
3119 | 0 | } |
3120 | | |
3121 | | /// Handle set operation methods (complexity: 8) |
3122 | 0 | fn handle_set_operation_methods( |
3123 | 0 | &mut self, |
3124 | 0 | set: HashSet<Value>, |
3125 | 0 | method: &str, |
3126 | 0 | args: &[Expr], |
3127 | 0 | deadline: Instant, |
3128 | 0 | depth: usize, |
3129 | 0 | ) -> Option<Result<Value>> { |
3130 | 0 | if args.len() != 1 { |
3131 | 0 | return Some(Err(anyhow::anyhow!("{} requires exactly 1 argument (other set)", method))); |
3132 | 0 | } |
3133 | | |
3134 | 0 | let other_val = match self.evaluate_expr(&args[0], deadline, depth + 1) { |
3135 | 0 | Ok(v) => v, |
3136 | 0 | Err(e) => return Some(Err(e)), |
3137 | | }; |
3138 | | |
3139 | 0 | if let Value::HashSet(other_set) = other_val { |
3140 | 0 | match method { |
3141 | 0 | "union" => { |
3142 | 0 | let union_set = set.union(&other_set).cloned().collect(); |
3143 | 0 | Some(Self::ok_hashset(union_set)) |
3144 | | } |
3145 | 0 | "intersection" => { |
3146 | 0 | let intersection_set = set.intersection(&other_set).cloned().collect(); |
3147 | 0 | Some(Self::ok_hashset(intersection_set)) |
3148 | | } |
3149 | 0 | "difference" => { |
3150 | 0 | let difference_set = set.difference(&other_set).cloned().collect(); |
3151 | 0 | Some(Self::ok_hashset(difference_set)) |
3152 | | } |
3153 | 0 | _ => None, |
3154 | | } |
3155 | | } else { |
3156 | 0 | Some(Err(anyhow::anyhow!("{} argument must be a HashSet", method))) |
3157 | | } |
3158 | 0 | } |
3159 | | |
3160 | | /// Handle method calls on `HashSet` values (complexity: 5) |
3161 | 0 | fn evaluate_hashset_methods( |
3162 | 0 | &mut self, |
3163 | 0 | set: HashSet<Value>, |
3164 | 0 | method: &str, |
3165 | 0 | args: &[Expr], |
3166 | 0 | deadline: Instant, |
3167 | 0 | depth: usize, |
3168 | 0 | ) -> Result<Value> { |
3169 | | // Try basic methods first |
3170 | 0 | if let Some(result) = self.handle_basic_hashset_methods(set.clone(), method, args, deadline, depth) { |
3171 | 0 | return result; |
3172 | 0 | } |
3173 | | |
3174 | | // Try set operation methods |
3175 | 0 | if let Some(result) = self.handle_set_operation_methods(set, method, args, deadline, depth) { |
3176 | 0 | return result; |
3177 | 0 | } |
3178 | | |
3179 | | // Unknown method |
3180 | 0 | bail!("Unknown HashSet method: {}", method) |
3181 | 0 | } |
3182 | | |
3183 | | // ======================================================================== |
3184 | | // Additional helper methods to further reduce evaluate_expr complexity |
3185 | | // Phase 2: Control flow extraction (Target: < 50 total complexity) |
3186 | | // ======================================================================== |
3187 | | |
3188 | | /// Evaluate for loop (complexity: 10) |
3189 | 0 | fn evaluate_for_loop( |
3190 | 0 | &mut self, |
3191 | 0 | var: &str, |
3192 | 0 | pattern: Option<&Pattern>, |
3193 | 0 | iter: &Expr, |
3194 | 0 | body: &Expr, |
3195 | 0 | deadline: Instant, |
3196 | 0 | depth: usize, |
3197 | 0 | ) -> Result<Value> { |
3198 | | // Evaluate the iterable |
3199 | 0 | let iterable = self.evaluate_expr(iter, deadline, depth + 1)?; |
3200 | | |
3201 | | // Save the previous value of the loop variable (if any) |
3202 | 0 | let saved_loop_var = self.bindings.get(var).cloned(); |
3203 | | |
3204 | | // If we have a pattern, save all variables it will bind |
3205 | 0 | let saved_pattern_vars = if let Some(pat) = pattern { |
3206 | 0 | self.save_pattern_variables(pat) |
3207 | | } else { |
3208 | 0 | HashMap::new() |
3209 | | }; |
3210 | | |
3211 | | // Execute the loop based on iterable type |
3212 | 0 | let result = match iterable { |
3213 | 0 | Value::List(items) => { |
3214 | 0 | if let Some(pat) = pattern { |
3215 | 0 | self.iterate_list_with_pattern(pat, items, body, deadline, depth) |
3216 | | } else { |
3217 | 0 | self.iterate_list(var, items, body, deadline, depth) |
3218 | | } |
3219 | | }, |
3220 | | Value::Range { |
3221 | 0 | start, |
3222 | 0 | end, |
3223 | 0 | inclusive, |
3224 | 0 | } => self.iterate_range(var, start, end, inclusive, body, deadline, depth), |
3225 | 0 | Value::String(s) => self.iterate_string(var, &s, body, deadline, depth), |
3226 | 0 | _ => bail!( |
3227 | 0 | "For loops only support lists, ranges, and strings, got: {:?}", |
3228 | | iterable |
3229 | | ), |
3230 | | }; |
3231 | | |
3232 | | // Restore the loop variable |
3233 | 0 | if let Some(prev_value) = saved_loop_var { |
3234 | 0 | self.bindings.insert(var.to_string(), prev_value); |
3235 | 0 | } else { |
3236 | 0 | self.bindings.remove(var); |
3237 | 0 | } |
3238 | | |
3239 | | // Restore pattern variables |
3240 | 0 | for (name, value) in saved_pattern_vars { |
3241 | 0 | if let Some(val) = value { |
3242 | 0 | self.bindings.insert(name, val); |
3243 | 0 | } else { |
3244 | 0 | self.bindings.remove(&name); |
3245 | 0 | } |
3246 | | } |
3247 | | |
3248 | 0 | result |
3249 | 0 | } |
3250 | | |
3251 | | /// Helper: Iterate over a list (complexity: 4) |
3252 | 0 | fn iterate_list( |
3253 | 0 | &mut self, |
3254 | 0 | var: &str, |
3255 | 0 | items: Vec<Value>, |
3256 | 0 | body: &Expr, |
3257 | 0 | deadline: Instant, |
3258 | 0 | depth: usize, |
3259 | 0 | ) -> Result<Value> { |
3260 | 0 | let mut result = Value::Unit; |
3261 | 0 | for item in items { |
3262 | 0 | self.bindings.insert(var.to_string(), item); |
3263 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
3264 | 0 | Ok(value) => result = value, |
3265 | 0 | Err(e) if e.to_string() == "break" => break, |
3266 | 0 | Err(e) if e.to_string() == "continue" => {}, |
3267 | 0 | Err(e) => return Err(e), |
3268 | | } |
3269 | | } |
3270 | 0 | Ok(result) |
3271 | 0 | } |
3272 | | |
3273 | | /// Helper: Iterate over a range (complexity: 5) |
3274 | | #[allow(clippy::too_many_arguments)] |
3275 | 0 | fn iterate_range( |
3276 | 0 | &mut self, |
3277 | 0 | var: &str, |
3278 | 0 | start: i64, |
3279 | 0 | end: i64, |
3280 | 0 | inclusive: bool, |
3281 | 0 | body: &Expr, |
3282 | 0 | deadline: Instant, |
3283 | 0 | depth: usize, |
3284 | 0 | ) -> Result<Value> { |
3285 | 0 | let mut result = Value::Unit; |
3286 | 0 | let actual_end = if inclusive { end + 1 } else { end }; |
3287 | 0 | for i in start..actual_end { |
3288 | 0 | self.bindings.insert(var.to_string(), Value::Int(i)); |
3289 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
3290 | 0 | Ok(value) => result = value, |
3291 | 0 | Err(e) if e.to_string() == "break" => break, |
3292 | 0 | Err(e) if e.to_string() == "continue" => {}, |
3293 | 0 | Err(e) => return Err(e), |
3294 | | } |
3295 | | } |
3296 | 0 | Ok(result) |
3297 | 0 | } |
3298 | | |
3299 | | /// Helper: Iterate over a string (as characters) |
3300 | 0 | fn iterate_string( |
3301 | 0 | &mut self, |
3302 | 0 | var: &str, |
3303 | 0 | s: &str, |
3304 | 0 | body: &Expr, |
3305 | 0 | deadline: Instant, |
3306 | 0 | depth: usize, |
3307 | 0 | ) -> Result<Value> { |
3308 | 0 | let mut result = Value::Unit; |
3309 | 0 | for ch in s.chars() { |
3310 | 0 | self.bindings.insert(var.to_string(), Value::String(ch.to_string())); |
3311 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
3312 | 0 | Ok(value) => result = value, |
3313 | 0 | Err(e) if e.to_string() == "break" => break, |
3314 | 0 | Err(e) if e.to_string() == "continue" => {}, |
3315 | 0 | Err(e) => return Err(e), |
3316 | | } |
3317 | | } |
3318 | 0 | Ok(result) |
3319 | 0 | } |
3320 | | |
3321 | | /// Helper: Save pattern variables for restoration |
3322 | 0 | fn save_pattern_variables(&self, pattern: &Pattern) -> HashMap<String, Option<Value>> { |
3323 | 0 | let mut saved = HashMap::new(); |
3324 | 0 | self.collect_pattern_vars(pattern, &mut saved); |
3325 | 0 | saved |
3326 | 0 | } |
3327 | | |
3328 | | /// Helper: Collect all variables from a pattern |
3329 | 0 | fn collect_pattern_vars(&self, pattern: &Pattern, saved: &mut HashMap<String, Option<Value>>) { |
3330 | 0 | match pattern { |
3331 | 0 | Pattern::Identifier(name) => { |
3332 | 0 | saved.insert(name.clone(), self.bindings.get(name).cloned()); |
3333 | 0 | } |
3334 | 0 | Pattern::Tuple(patterns) => { |
3335 | 0 | for p in patterns { |
3336 | 0 | self.collect_pattern_vars(p, saved); |
3337 | 0 | } |
3338 | | } |
3339 | 0 | _ => {} // Other patterns don't bind variables |
3340 | | } |
3341 | 0 | } |
3342 | | |
3343 | | /// Helper: Iterate over a list with pattern destructuring |
3344 | 0 | fn iterate_list_with_pattern( |
3345 | 0 | &mut self, |
3346 | 0 | pattern: &Pattern, |
3347 | 0 | items: Vec<Value>, |
3348 | 0 | body: &Expr, |
3349 | 0 | deadline: Instant, |
3350 | 0 | depth: usize, |
3351 | 0 | ) -> Result<Value> { |
3352 | 0 | let mut result = Value::Unit; |
3353 | 0 | for item in items { |
3354 | | // Bind the pattern variables |
3355 | 0 | self.bind_pattern(pattern, &item)?; |
3356 | | |
3357 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
3358 | 0 | Ok(value) => result = value, |
3359 | 0 | Err(e) if e.to_string() == "break" => break, |
3360 | 0 | Err(e) if e.to_string() == "continue" => {}, |
3361 | 0 | Err(e) => return Err(e), |
3362 | | } |
3363 | | } |
3364 | 0 | Ok(result) |
3365 | 0 | } |
3366 | | |
3367 | | /// Helper: Bind pattern variables from a value |
3368 | 0 | fn bind_pattern(&mut self, pattern: &Pattern, value: &Value) -> Result<()> { |
3369 | 0 | match (pattern, value) { |
3370 | 0 | (Pattern::Identifier(name), val) => { |
3371 | 0 | self.bindings.insert(name.clone(), val.clone()); |
3372 | 0 | Ok(()) |
3373 | | } |
3374 | 0 | (Pattern::Tuple(patterns), Value::Tuple(values)) => { |
3375 | 0 | if patterns.len() != values.len() { |
3376 | 0 | bail!("Pattern tuple has {} elements but value has {}", patterns.len(), values.len()); |
3377 | 0 | } |
3378 | 0 | for (p, v) in patterns.iter().zip(values.iter()) { |
3379 | 0 | self.bind_pattern(p, v)?; |
3380 | | } |
3381 | 0 | Ok(()) |
3382 | | } |
3383 | 0 | _ => bail!("Pattern does not match value") |
3384 | | } |
3385 | 0 | } |
3386 | | |
3387 | | /// Evaluate while loop (complexity: 7) |
3388 | | /// |
3389 | | /// While loops always return Unit, regardless of body expression. |
3390 | | /// |
3391 | | /// # Example |
3392 | | /// ``` |
3393 | | /// use ruchy::runtime::Repl; |
3394 | | /// let mut repl = Repl::new().unwrap(); |
3395 | | /// |
3396 | | /// // While loops return Unit, not the last body value |
3397 | | /// let result = repl.eval("let i = 0; while i < 3 { i = i + 1 }; i").unwrap(); |
3398 | | /// assert_eq!(result.to_string(), "3"); // i is 3 after loop |
3399 | | /// |
3400 | | /// // While loop doesn't return body value |
3401 | | /// let result = repl.eval("let i = 0; while i < 1 { i = i + 1; 42 }").unwrap(); |
3402 | | /// assert_eq!(result.to_string(), "()"); // Returns Unit, not 42 |
3403 | | /// ``` |
3404 | 0 | fn evaluate_while_loop( |
3405 | 0 | &mut self, |
3406 | 0 | condition: &Expr, |
3407 | 0 | body: &Expr, |
3408 | 0 | deadline: Instant, |
3409 | 0 | depth: usize, |
3410 | 0 | ) -> Result<Value> { |
3411 | 0 | let max_iterations = 1000; // Prevent infinite loops in REPL |
3412 | 0 | let mut iterations = 0; |
3413 | | |
3414 | | loop { |
3415 | 0 | if iterations >= max_iterations { |
3416 | 0 | bail!( |
3417 | 0 | "While loop exceeded maximum iterations ({})", |
3418 | | max_iterations |
3419 | | ); |
3420 | 0 | } |
3421 | | |
3422 | | // Evaluate condition |
3423 | 0 | let cond_val = self.evaluate_expr(condition, deadline, depth + 1)?; |
3424 | 0 | match cond_val { |
3425 | | Value::Bool(true) => { |
3426 | | // Execute body but don't save result - while loops return Unit |
3427 | 0 | self.evaluate_expr(body, deadline, depth + 1)?; |
3428 | 0 | iterations += 1; |
3429 | | } |
3430 | 0 | Value::Bool(false) => break, |
3431 | 0 | _ => bail!("While condition must be boolean, got: {:?}", cond_val), |
3432 | | } |
3433 | | } |
3434 | | // While loops always return Unit |
3435 | 0 | Self::ok_unit() |
3436 | 0 | } |
3437 | | |
3438 | | /// Evaluate loop expression (complexity: 6) |
3439 | 0 | fn evaluate_loop(&mut self, body: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
3440 | 0 | let mut result = Value::Unit; |
3441 | 0 | let max_iterations = 1000; // Prevent infinite loops in REPL |
3442 | 0 | let mut iterations = 0; |
3443 | | |
3444 | | loop { |
3445 | 0 | if iterations >= max_iterations { |
3446 | 0 | bail!("Loop exceeded maximum iterations ({})", max_iterations); |
3447 | 0 | } |
3448 | | |
3449 | | // Evaluate body, catching break |
3450 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
3451 | 0 | Ok(val) => { |
3452 | 0 | result = val; |
3453 | 0 | iterations += 1; |
3454 | 0 | } |
3455 | 0 | Err(e) if e.to_string() == "break" => { |
3456 | 0 | break; |
3457 | | } |
3458 | 0 | Err(e) if e.to_string() == "continue" => { |
3459 | 0 | iterations += 1; |
3460 | 0 | } |
3461 | 0 | Err(e) => return Err(e), |
3462 | | } |
3463 | | } |
3464 | | |
3465 | 0 | Ok(result) |
3466 | 0 | } |
3467 | | |
3468 | | /// Evaluate block expression (complexity: 4) |
3469 | 0 | fn evaluate_block(&mut self, exprs: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
3470 | 0 | if exprs.is_empty() { |
3471 | 0 | return Self::ok_unit(); |
3472 | 0 | } |
3473 | | |
3474 | 0 | let mut result = Value::Unit; |
3475 | 0 | for expr in exprs { |
3476 | 0 | result = self.evaluate_expr(expr, deadline, depth + 1)?; |
3477 | | } |
3478 | 0 | Ok(result) |
3479 | 0 | } |
3480 | | |
3481 | | /// Evaluate list literal (complexity: 4) |
3482 | 0 | fn evaluate_list_literal( |
3483 | 0 | &mut self, |
3484 | 0 | elements: &[Expr], |
3485 | 0 | deadline: Instant, |
3486 | 0 | depth: usize, |
3487 | 0 | ) -> Result<Value> { |
3488 | 0 | let mut results = Vec::new(); |
3489 | 0 | for elem in elements { |
3490 | 0 | if let ExprKind::Spread { expr } = &elem.kind { |
3491 | | // Evaluate the spread expression and expand it into the array |
3492 | 0 | let val = self.evaluate_expr(expr, deadline, depth + 1)?; |
3493 | 0 | match val { |
3494 | 0 | Value::List(items) => { |
3495 | 0 | // Spread the items into the result |
3496 | 0 | results.extend(items); |
3497 | 0 | } |
3498 | 0 | Value::Tuple(items) => { |
3499 | 0 | // Also allow spreading tuples |
3500 | 0 | results.extend(items); |
3501 | 0 | } |
3502 | 0 | Value::Range { start, end, inclusive } => { |
3503 | | // Spread range values into individual integers |
3504 | 0 | let range_values = self.expand_range_to_values(start, end, inclusive)?; |
3505 | 0 | results.extend(range_values); |
3506 | | } |
3507 | | _ => { |
3508 | 0 | bail!("Cannot spread non-iterable value: {}", self.get_value_type_name(&val)); |
3509 | | } |
3510 | | } |
3511 | | } else { |
3512 | | // Regular element |
3513 | 0 | let val = self.evaluate_expr(elem, deadline, depth + 1)?; |
3514 | 0 | results.push(val); |
3515 | | } |
3516 | | } |
3517 | 0 | Ok(Value::List(results)) |
3518 | 0 | } |
3519 | | |
3520 | | /// Expand a range into individual `Value::Int` items for spreading |
3521 | 0 | fn expand_range_to_values(&self, start: i64, end: i64, inclusive: bool) -> Result<Vec<Value>> { |
3522 | 0 | let actual_end = if inclusive { end } else { end - 1 }; |
3523 | | |
3524 | 0 | if start > actual_end { |
3525 | 0 | return Ok(Vec::new()); // Empty range |
3526 | 0 | } |
3527 | | |
3528 | | // Prevent excessive memory allocation for very large ranges |
3529 | 0 | let range_size = (actual_end - start + 1) as usize; |
3530 | 0 | if range_size > 10000 { |
3531 | 0 | bail!("Range too large to expand: {} elements (limit: 10000)", range_size); |
3532 | 0 | } |
3533 | | |
3534 | 0 | let mut values = Vec::with_capacity(range_size); |
3535 | 0 | for i in start..=actual_end { |
3536 | 0 | values.push(Value::Int(i)); |
3537 | 0 | } |
3538 | | |
3539 | 0 | Ok(values) |
3540 | 0 | } |
3541 | | |
3542 | | /// Evaluate tuple literal (complexity: 4) |
3543 | 0 | fn evaluate_tuple_literal( |
3544 | 0 | &mut self, |
3545 | 0 | elements: &[Expr], |
3546 | 0 | deadline: Instant, |
3547 | 0 | depth: usize, |
3548 | 0 | ) -> Result<Value> { |
3549 | 0 | let mut results = Vec::new(); |
3550 | 0 | for elem in elements { |
3551 | 0 | let val = self.evaluate_expr(elem, deadline, depth + 1)?; |
3552 | 0 | results.push(val); |
3553 | | } |
3554 | 0 | Self::ok_tuple(results) |
3555 | 0 | } |
3556 | | |
3557 | | /// Evaluate range literal (complexity: 5) |
3558 | 0 | fn evaluate_range_literal( |
3559 | 0 | &mut self, |
3560 | 0 | start: &Expr, |
3561 | 0 | end: &Expr, |
3562 | 0 | inclusive: bool, |
3563 | 0 | deadline: Instant, |
3564 | 0 | depth: usize, |
3565 | 0 | ) -> Result<Value> { |
3566 | 0 | let start_val = self.evaluate_expr(start, deadline, depth + 1)?; |
3567 | 0 | let end_val = self.evaluate_expr(end, deadline, depth + 1)?; |
3568 | | |
3569 | 0 | match (start_val, end_val) { |
3570 | 0 | (Value::Int(s), Value::Int(e)) => Self::ok_range(s, e, inclusive), |
3571 | 0 | _ => bail!("Range endpoints must be integers"), |
3572 | | } |
3573 | 0 | } |
3574 | | |
3575 | | /// Evaluate assignment expression (complexity: 5) |
3576 | 0 | fn evaluate_assignment( |
3577 | 0 | &mut self, |
3578 | 0 | target: &Expr, |
3579 | 0 | value: &Expr, |
3580 | 0 | deadline: Instant, |
3581 | 0 | depth: usize, |
3582 | 0 | ) -> Result<Value> { |
3583 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3584 | | |
3585 | | // For now, only support simple variable assignment |
3586 | 0 | if let ExprKind::Identifier(name) = &target.kind { |
3587 | | // Use update_binding which checks mutability |
3588 | 0 | self.update_binding(name, val.clone())?; |
3589 | 0 | Ok(val) |
3590 | | } else { |
3591 | 0 | bail!( |
3592 | 0 | "Only simple variable assignment is supported, got: {:?}", |
3593 | | target.kind |
3594 | | ); |
3595 | | } |
3596 | 0 | } |
3597 | | |
3598 | | /// Evaluate let binding (complexity: 5) |
3599 | 0 | fn evaluate_let_binding( |
3600 | 0 | &mut self, |
3601 | 0 | name: &str, |
3602 | 0 | value: &Expr, |
3603 | 0 | body: &Expr, |
3604 | 0 | is_mutable: bool, |
3605 | 0 | deadline: Instant, |
3606 | 0 | depth: usize, |
3607 | 0 | ) -> Result<Value> { |
3608 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3609 | 0 | self.create_binding(name.to_string(), val.clone(), is_mutable); |
3610 | | |
3611 | | // If there's a body, evaluate it; otherwise return the value |
3612 | 0 | match &body.kind { |
3613 | 0 | ExprKind::Literal(Literal::Unit) => Ok(val), |
3614 | 0 | _ => self.evaluate_expr(body, deadline, depth + 1), |
3615 | | } |
3616 | 0 | } |
3617 | | |
3618 | | /// Evaluate let pattern binding (destructuring assignment) |
3619 | 0 | fn evaluate_let_pattern( |
3620 | 0 | &mut self, |
3621 | 0 | pattern: &crate::frontend::ast::Pattern, |
3622 | 0 | value: &Expr, |
3623 | 0 | body: &Expr, |
3624 | 0 | is_mutable: bool, |
3625 | 0 | deadline: Instant, |
3626 | 0 | depth: usize, |
3627 | 0 | ) -> Result<Value> { |
3628 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3629 | | |
3630 | | // Use existing pattern matching logic |
3631 | 0 | if let Some(bindings) = Self::pattern_matches(&val, pattern)? { |
3632 | 0 | let _saved_bindings = self.bindings.clone(); |
3633 | | |
3634 | | // Apply all pattern bindings |
3635 | 0 | for (name, binding_val) in bindings { |
3636 | 0 | self.create_binding(name, binding_val, is_mutable); |
3637 | 0 | } |
3638 | | |
3639 | | // Evaluate the body expression |
3640 | | |
3641 | | |
3642 | | // Pattern matching succeeded, keep the new bindings |
3643 | 0 | match &body.kind { |
3644 | 0 | ExprKind::Literal(Literal::Unit) => Ok(val), |
3645 | 0 | _ => self.evaluate_expr(body, deadline, depth + 1), |
3646 | | } |
3647 | | } else { |
3648 | 0 | bail!("Pattern does not match value in let binding"); |
3649 | | } |
3650 | 0 | } |
3651 | | |
3652 | | /// Evaluate string interpolation (complexity: 7) |
3653 | 0 | fn evaluate_string_interpolation( |
3654 | 0 | &mut self, |
3655 | 0 | parts: &[crate::frontend::ast::StringPart], |
3656 | 0 | deadline: Instant, |
3657 | 0 | depth: usize, |
3658 | 0 | ) -> Result<Value> { |
3659 | | use crate::frontend::ast::StringPart; |
3660 | | |
3661 | 0 | let mut result = String::new(); |
3662 | 0 | for part in parts { |
3663 | 0 | match part { |
3664 | 0 | StringPart::Text(text) => result.push_str(text), |
3665 | 0 | StringPart::Expr(expr) => { |
3666 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
3667 | | // Format the value for interpolation (without quotes for strings) |
3668 | 0 | match value { |
3669 | 0 | Value::String(s) => result.push_str(&s), |
3670 | 0 | Value::Char(c) => result.push(c), |
3671 | 0 | other => result.push_str(&other.to_string()), |
3672 | | } |
3673 | | } |
3674 | 0 | StringPart::ExprWithFormat { expr, format_spec } => { |
3675 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
3676 | | // Apply format specifier for REPL |
3677 | 0 | let formatted = Self::format_value_with_spec(&value, format_spec); |
3678 | 0 | result.push_str(&formatted); |
3679 | | } |
3680 | | } |
3681 | | } |
3682 | 0 | Self::ok_string(result) |
3683 | 0 | } |
3684 | | |
3685 | | /// Format a value with a format specifier like :.2 for floats |
3686 | 0 | fn format_value_with_spec(value: &Value, spec: &str) -> String { |
3687 | | // Parse format specifier (e.g., ":.2" -> precision 2) |
3688 | 0 | if let Some(stripped) = spec.strip_prefix(":.") { |
3689 | 0 | if let Ok(precision) = stripped.parse::<usize>() { |
3690 | 0 | match value { |
3691 | 0 | Value::Float(f) => return format!("{f:.precision$}"), |
3692 | 0 | Value::Int(i) => return format!("{:.precision$}", *i as f64, precision = precision), |
3693 | 0 | _ => {} |
3694 | | } |
3695 | 0 | } |
3696 | 0 | } |
3697 | | // Default formatting if spec doesn't match or isn't supported |
3698 | 0 | value.to_string() |
3699 | 0 | } |
3700 | | |
3701 | | /// Evaluate function definition (complexity: 5) |
3702 | 0 | fn evaluate_function_definition( |
3703 | 0 | &mut self, |
3704 | 0 | name: &str, |
3705 | 0 | params: &[crate::frontend::ast::Param], |
3706 | 0 | body: &Expr, |
3707 | 0 | ) -> Value { |
3708 | 0 | let param_names: Vec<String> = params |
3709 | 0 | .iter() |
3710 | 0 | .map(crate::frontend::ast::Param::name) |
3711 | 0 | .collect(); |
3712 | 0 | let func_value = Value::Function { |
3713 | 0 | name: name.to_string(), |
3714 | 0 | params: param_names, |
3715 | 0 | body: Box::new(body.clone()), |
3716 | 0 | }; |
3717 | | |
3718 | | // Store the function in bindings |
3719 | 0 | self.bindings.insert(name.to_string(), func_value.clone()); |
3720 | 0 | func_value |
3721 | 0 | } |
3722 | | |
3723 | | /// Evaluate lambda expression (complexity: 3) |
3724 | 0 | fn evaluate_lambda_expression(params: &[crate::frontend::ast::Param], body: &Expr) -> Value { |
3725 | 0 | let param_names: Vec<String> = params |
3726 | 0 | .iter() |
3727 | 0 | .map(crate::frontend::ast::Param::name) |
3728 | 0 | .collect(); |
3729 | 0 | Value::Lambda { |
3730 | 0 | params: param_names, |
3731 | 0 | body: Box::new(body.clone()), |
3732 | 0 | } |
3733 | 0 | } |
3734 | | |
3735 | | /// Evaluate `DataFrame` literal (complexity: 6) |
3736 | 0 | fn evaluate_dataframe_literal( |
3737 | 0 | &mut self, |
3738 | 0 | columns: &[crate::frontend::ast::DataFrameColumn], |
3739 | 0 | deadline: Instant, |
3740 | 0 | depth: usize, |
3741 | 0 | ) -> Result<Value> { |
3742 | 0 | let mut df_columns = Vec::new(); |
3743 | 0 | for col in columns { |
3744 | 0 | let mut values = Vec::new(); |
3745 | 0 | for val_expr in &col.values { |
3746 | 0 | let val = self.evaluate_expr(val_expr, deadline, depth + 1)?; |
3747 | 0 | values.push(val); |
3748 | | } |
3749 | 0 | df_columns.push(DataFrameColumn { |
3750 | 0 | name: col.name.clone(), |
3751 | 0 | values, |
3752 | 0 | }); |
3753 | | } |
3754 | 0 | Self::ok_dataframe(df_columns) |
3755 | 0 | } |
3756 | | |
3757 | | |
3758 | | /// Evaluate `Result::Ok` constructor (complexity: 3) |
3759 | 0 | fn evaluate_result_ok( |
3760 | 0 | &mut self, |
3761 | 0 | value: &Expr, |
3762 | 0 | deadline: Instant, |
3763 | 0 | depth: usize, |
3764 | 0 | ) -> Result<Value> { |
3765 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3766 | 0 | Self::ok_enum_variant("Result".to_string(), "Ok".to_string(), Some(vec![val])) |
3767 | 0 | } |
3768 | | |
3769 | | /// Evaluate `Result::Err` constructor (complexity: 3) |
3770 | 0 | fn evaluate_result_err( |
3771 | 0 | &mut self, |
3772 | 0 | error: &Expr, |
3773 | 0 | deadline: Instant, |
3774 | 0 | depth: usize, |
3775 | 0 | ) -> Result<Value> { |
3776 | 0 | let err = self.evaluate_expr(error, deadline, depth + 1)?; |
3777 | 0 | Self::ok_enum_variant("Result".to_string(), "Err".to_string(), Some(vec![err])) |
3778 | 0 | } |
3779 | | |
3780 | | /// Evaluate `Option::Some` constructor (complexity: 3) |
3781 | 0 | fn evaluate_option_some( |
3782 | 0 | &mut self, |
3783 | 0 | value: &Expr, |
3784 | 0 | deadline: Instant, |
3785 | 0 | depth: usize, |
3786 | 0 | ) -> Result<Value> { |
3787 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3788 | 0 | Self::ok_enum_variant("Option".to_string(), "Some".to_string(), Some(vec![val])) |
3789 | 0 | } |
3790 | | |
3791 | | /// Evaluate `Option::None` constructor (complexity: 1) |
3792 | 0 | fn evaluate_option_none() -> Value { |
3793 | 0 | Value::EnumVariant { |
3794 | 0 | enum_name: "Option".to_string(), |
3795 | 0 | variant_name: "None".to_string(), |
3796 | 0 | data: None, |
3797 | 0 | } |
3798 | 0 | } |
3799 | | |
3800 | | /// Evaluate try operator (?) - early return on Err or None |
3801 | 0 | fn evaluate_try_operator( |
3802 | 0 | &mut self, |
3803 | 0 | expr: &Expr, |
3804 | 0 | deadline: Instant, |
3805 | 0 | depth: usize, |
3806 | 0 | ) -> Result<Value> { |
3807 | 0 | let val = self.evaluate_expr(expr, deadline, depth + 1)?; |
3808 | | |
3809 | | // Check if it's a Result::Err or Option::None and propagate |
3810 | 0 | if let Value::EnumVariant { enum_name, variant_name, data } = &val { |
3811 | 0 | if enum_name == "Result" && variant_name == "Err" { |
3812 | | // For Result::Err, propagate the error |
3813 | 0 | return Ok(val.clone()); |
3814 | 0 | } else if enum_name == "Option" && variant_name == "None" { |
3815 | | // For Option::None, propagate None |
3816 | 0 | return Ok(val.clone()); |
3817 | 0 | } else if enum_name == "Result" && variant_name == "Ok" { |
3818 | | // For Result::Ok, unwrap the value |
3819 | 0 | if let Some(values) = data { |
3820 | 0 | if !values.is_empty() { |
3821 | 0 | return Ok(values[0].clone()); |
3822 | 0 | } |
3823 | 0 | } |
3824 | 0 | } else if enum_name == "Option" && variant_name == "Some" { |
3825 | | // For Option::Some, unwrap the value |
3826 | 0 | if let Some(values) = data { |
3827 | 0 | if !values.is_empty() { |
3828 | 0 | return Ok(values[0].clone()); |
3829 | 0 | } |
3830 | 0 | } |
3831 | 0 | } |
3832 | 0 | } |
3833 | | |
3834 | | // If not a Result or Option, return as-is (this might be an error case) |
3835 | 0 | Ok(val) |
3836 | 0 | } |
3837 | | |
3838 | | /// Evaluate methods on enum variants (Result/Option types) |
3839 | | #[allow(clippy::too_many_lines)] |
3840 | | /// Evaluate enum methods with complexity <10 |
3841 | | /// |
3842 | | /// Delegates to specialized handlers for each enum type |
3843 | | /// |
3844 | | /// Example Usage: |
3845 | | /// |
3846 | | /// Handles methods on Result and Option enums: |
3847 | | /// - `Result::unwrap()` - Returns Ok value or panics on Err |
3848 | | /// - `Result::unwrap_or(default)` - Returns Ok value or default |
3849 | | /// - `Option::unwrap()` - Returns Some value or panics on None |
3850 | | /// - `Option::unwrap_or(default)` - Returns Some value or default |
3851 | 0 | fn evaluate_enum_methods( |
3852 | 0 | &mut self, |
3853 | 0 | receiver: Value, |
3854 | 0 | method: &str, |
3855 | 0 | args: &[Expr], |
3856 | 0 | deadline: Instant, |
3857 | 0 | depth: usize, |
3858 | 0 | ) -> Result<Value> { |
3859 | 0 | if let Value::EnumVariant { enum_name, variant_name, data } = receiver { |
3860 | 0 | match enum_name.as_str() { |
3861 | 0 | "Result" => self.evaluate_result_methods(&variant_name, method, data.as_ref(), args, deadline, depth), |
3862 | 0 | "Option" => self.evaluate_option_methods(&variant_name, method, data.as_ref(), args, deadline, depth), |
3863 | 0 | "Vec" => self.evaluate_vec_methods(&variant_name, method, data.as_ref(), args, deadline, depth), |
3864 | 0 | _ => Err(Self::method_not_supported(method, &enum_name))?, |
3865 | | } |
3866 | | } else { |
3867 | 0 | bail!("evaluate_enum_methods called on non-enum variant") |
3868 | | } |
3869 | 0 | } |
3870 | | |
3871 | | /// Handle Result enum methods (unwrap, expect, map, `and_then`) |
3872 | | /// |
3873 | | /// # Example Usage |
3874 | | /// Evaluates methods on enum variants like `Some(x).unwrap()` or `Ok(v).is_ok()`. |
3875 | | /// |
3876 | | /// use `ruchy::runtime::{Repl`, Value}; |
3877 | | /// use `std::time::{Duration`, Instant}; |
3878 | | /// |
3879 | | /// let mut repl = `Repl::new().unwrap()`; |
3880 | | /// let deadline = `Instant::now()` + `Duration::from_secs(1)`; |
3881 | | /// let data = Some(vec![`Value::Int(42)`]); |
3882 | | /// |
3883 | | /// // Test Ok unwrap |
3884 | | /// let result = `repl.evaluate_result_methods("Ok`", "unwrap", &data, &[], deadline, `0).unwrap()`; |
3885 | | /// `assert_eq!(result`, `Value::Int(42)`); |
3886 | | /// ``` |
3887 | 0 | fn evaluate_result_methods( |
3888 | 0 | &mut self, |
3889 | 0 | variant_name: &str, |
3890 | 0 | method: &str, |
3891 | 0 | data: Option<&Vec<Value>>, |
3892 | 0 | args: &[Expr], |
3893 | 0 | deadline: Instant, |
3894 | 0 | depth: usize, |
3895 | 0 | ) -> Result<Value> { |
3896 | 0 | match (variant_name, method) { |
3897 | 0 | ("Ok", "unwrap" | "expect") if args.is_empty() || args.len() == 1 => { |
3898 | 0 | self.extract_value_or_unit(data) |
3899 | | } |
3900 | 0 | ("Err", "unwrap") if args.is_empty() => { |
3901 | 0 | let error_msg = self.format_error_message("Result::unwrap()", "Err", data); |
3902 | 0 | bail!(error_msg) |
3903 | | } |
3904 | 0 | ("Err", "expect") if args.len() == 1 => { |
3905 | 0 | let custom_msg = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3906 | 0 | let msg = self.value_to_string(custom_msg); |
3907 | 0 | bail!(msg) |
3908 | | } |
3909 | 0 | ("Ok", "map") if args.len() == 1 => { |
3910 | 0 | self.apply_function_to_value("Result", "Ok", data, &args[0], deadline, depth) |
3911 | | } |
3912 | 0 | ("Err", "map") if args.len() == 1 => { |
3913 | | // Err values are not transformed by map |
3914 | 0 | if let Some(err_val) = data.and_then(|d| d.first()) { |
3915 | 0 | Ok(Self::create_result_err(err_val.clone())) |
3916 | | } else { |
3917 | 0 | Ok(Self::create_result_err(Value::Unit)) |
3918 | | } |
3919 | | } |
3920 | 0 | ("Ok", "and_then") if args.len() == 1 => { |
3921 | 0 | self.apply_function_and_flatten(data, &args[0], deadline, depth) |
3922 | | } |
3923 | 0 | ("Err", "and_then") if args.len() == 1 => { |
3924 | 0 | Ok(Value::EnumVariant { |
3925 | 0 | enum_name: "Result".to_string(), |
3926 | 0 | variant_name: variant_name.to_string(), |
3927 | 0 | data: data.cloned(), |
3928 | 0 | }) |
3929 | | } |
3930 | | // ok converts Result to Option |
3931 | 0 | ("Ok", "ok") if args.is_empty() => { |
3932 | | // Result::Ok(x).ok() -> Option::Some(x) |
3933 | 0 | let value = self.extract_value_or_unit(data)?; |
3934 | 0 | Ok(Self::create_option_some(value)) |
3935 | | } |
3936 | 0 | ("Err", "ok") if args.is_empty() => { |
3937 | | // Result::Err(e).ok() -> Option::None |
3938 | 0 | Ok(Self::create_option_none()) |
3939 | | } |
3940 | 0 | _ => Err(Self::method_not_supported(method, &format!("Result::{variant_name}")))?, |
3941 | | } |
3942 | 0 | } |
3943 | | |
3944 | | /// Handle Option enum methods (unwrap, expect, map, `and_then`) |
3945 | 0 | fn evaluate_option_methods( |
3946 | 0 | &mut self, |
3947 | 0 | variant_name: &str, |
3948 | 0 | method: &str, |
3949 | 0 | data: Option<&Vec<Value>>, |
3950 | 0 | args: &[Expr], |
3951 | 0 | deadline: Instant, |
3952 | 0 | depth: usize, |
3953 | 0 | ) -> Result<Value> { |
3954 | 0 | match (variant_name, method) { |
3955 | 0 | ("Some", "unwrap" | "expect") if args.is_empty() || args.len() == 1 => { |
3956 | 0 | self.extract_value_or_unit(data) |
3957 | | } |
3958 | 0 | ("None", "unwrap") if args.is_empty() => { |
3959 | 0 | bail!("called `Option::unwrap()` on a `None` value") |
3960 | | } |
3961 | 0 | ("None", "expect") if args.len() == 1 => { |
3962 | 0 | let custom_msg = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3963 | 0 | let msg = self.value_to_string(custom_msg); |
3964 | 0 | bail!(msg) |
3965 | | } |
3966 | 0 | ("Some", "map") if args.len() == 1 => { |
3967 | 0 | self.apply_function_to_value("Option", "Some", data, &args[0], deadline, depth) |
3968 | | } |
3969 | 0 | ("None", "map" | "and_then") if args.len() == 1 => { |
3970 | 0 | Ok(Value::EnumVariant { |
3971 | 0 | enum_name: "Option".to_string(), |
3972 | 0 | variant_name: variant_name.to_string(), |
3973 | 0 | data: data.cloned(), |
3974 | 0 | }) |
3975 | | } |
3976 | | // ok_or converts Option to Result |
3977 | 0 | ("Some", "ok_or") if args.len() == 1 => { |
3978 | | // Option::Some(x).ok_or(err) -> Result::Ok(x) |
3979 | 0 | let value = self.extract_value_or_unit(data)?; |
3980 | 0 | Ok(Self::create_result_ok(value)) |
3981 | | } |
3982 | 0 | ("None", "ok_or") if args.len() == 1 => { |
3983 | | // Option::None.ok_or(err) -> Result::Err(err) |
3984 | 0 | let err_value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3985 | 0 | Ok(Self::create_result_err(err_value)) |
3986 | | } |
3987 | 0 | ("Some", "and_then") if args.len() == 1 => { |
3988 | 0 | self.apply_function_and_flatten(data, &args[0], deadline, depth) |
3989 | | } |
3990 | 0 | _ => Err(Self::method_not_supported(method, &format!("Option::{variant_name}")))?, |
3991 | | } |
3992 | 0 | } |
3993 | | |
3994 | | /// Handle Vec enum methods (placeholder for future Vec methods) |
3995 | 0 | fn evaluate_vec_methods( |
3996 | 0 | &mut self, |
3997 | 0 | variant_name: &str, |
3998 | 0 | method: &str, |
3999 | 0 | data: Option<&Vec<Value>>, |
4000 | 0 | args: &[Expr], |
4001 | 0 | deadline: Instant, |
4002 | 0 | depth: usize, |
4003 | 0 | ) -> Result<Value> { |
4004 | 0 | match method { |
4005 | 0 | "len" => Ok(Value::Int(data.as_ref().map_or(0, |v| v.len() as i64))), |
4006 | 0 | "push" if args.len() == 1 => { |
4007 | 0 | let new_elem = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
4008 | 0 | let mut vec_data = data.cloned().unwrap_or_default(); |
4009 | 0 | vec_data.push(new_elem); |
4010 | 0 | Ok(Value::EnumVariant { |
4011 | 0 | enum_name: "Vec".to_string(), |
4012 | 0 | variant_name: variant_name.to_string(), |
4013 | 0 | data: Some(vec_data), |
4014 | 0 | }) |
4015 | | } |
4016 | 0 | _ => Err(Self::method_not_supported(method, "Vec"))?, |
4017 | | } |
4018 | 0 | } |
4019 | | |
4020 | | /// Extract value from enum data or return Unit |
4021 | 0 | fn extract_value_or_unit(&self, data: Option<&Vec<Value>>) -> Result<Value> { |
4022 | 0 | if let Some(values) = data { |
4023 | 0 | if !values.is_empty() { |
4024 | 0 | return Ok(values[0].clone()); |
4025 | 0 | } |
4026 | 0 | } |
4027 | 0 | Self::ok_unit() |
4028 | 0 | } |
4029 | | |
4030 | | /// Format error message for unwrap operations |
4031 | 0 | fn format_error_message(&self, method: &str, variant: &str, data: Option<&Vec<Value>>) -> String { |
4032 | 0 | if let Some(values) = data { |
4033 | 0 | if values.is_empty() { |
4034 | 0 | format!("called `{method}` on an `{variant}` value") |
4035 | | } else { |
4036 | 0 | format!("called `{}` on an `{}` value: {}", method, variant, values[0]) |
4037 | | } |
4038 | | } else { |
4039 | 0 | format!("called `{method}` on an `{variant}` value") |
4040 | | } |
4041 | 0 | } |
4042 | | |
4043 | | /// Convert Value to string representation |
4044 | 0 | fn value_to_string(&self, value: Value) -> String { |
4045 | 0 | match value { |
4046 | 0 | Value::String(s) => s, |
4047 | 0 | other => format!("{other}"), |
4048 | | } |
4049 | 0 | } |
4050 | | |
4051 | | /// Evaluate `DataFrame` methods (builder pattern and queries) |
4052 | 0 | fn evaluate_dataframe_methods( |
4053 | 0 | &mut self, |
4054 | 0 | mut columns: Vec<DataFrameColumn>, |
4055 | 0 | method: &str, |
4056 | 0 | args: &[Expr], |
4057 | 0 | deadline: Instant, |
4058 | 0 | depth: usize, |
4059 | 0 | ) -> Result<Value> { |
4060 | 0 | match method { |
4061 | 0 | "column" => { |
4062 | | // Builder pattern: add a column |
4063 | 0 | if args.len() != 2 { |
4064 | 0 | bail!("DataFrame.column() requires exactly 2 arguments (name, values)"); |
4065 | 0 | } |
4066 | | |
4067 | | // Evaluate column name |
4068 | 0 | let name_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
4069 | 0 | let column_name = match name_val { |
4070 | 0 | Value::String(s) => s, |
4071 | 0 | _ => bail!("Column name must be a string"), |
4072 | | }; |
4073 | | |
4074 | | // Evaluate column values (should be a list) |
4075 | 0 | let values_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
4076 | 0 | let column_values = match values_val { |
4077 | 0 | Value::List(items) => items, |
4078 | 0 | _ => bail!("Column values must be a list"), |
4079 | | }; |
4080 | | |
4081 | | // Add the column to the DataFrame |
4082 | 0 | columns.push(DataFrameColumn { |
4083 | 0 | name: column_name, |
4084 | 0 | values: column_values, |
4085 | 0 | }); |
4086 | | |
4087 | 0 | Ok(Value::DataFrame { columns }) |
4088 | | } |
4089 | 0 | "build" => { |
4090 | | // Finalize the DataFrame builder |
4091 | 0 | if !args.is_empty() { |
4092 | 0 | bail!("DataFrame.build() takes no arguments"); |
4093 | 0 | } |
4094 | 0 | Ok(Value::DataFrame { columns }) |
4095 | | } |
4096 | 0 | "rows" => { |
4097 | | // Return number of rows |
4098 | 0 | if !args.is_empty() { |
4099 | 0 | bail!("DataFrame.rows() takes no arguments"); |
4100 | 0 | } |
4101 | 0 | let num_rows = columns.first().map_or(0, |col| col.values.len()); |
4102 | 0 | Ok(Value::Int(num_rows as i64)) |
4103 | | } |
4104 | 0 | "columns" => { |
4105 | | // Return number of columns |
4106 | 0 | if !args.is_empty() { |
4107 | 0 | bail!("DataFrame.columns() takes no arguments"); |
4108 | 0 | } |
4109 | 0 | Ok(Value::Int(columns.len() as i64)) |
4110 | | } |
4111 | 0 | "get" => { |
4112 | | // Get a value at (column_name, row_index) |
4113 | 0 | if args.len() != 2 { |
4114 | 0 | bail!("DataFrame.get() requires exactly 2 arguments (column_name, row_index)"); |
4115 | 0 | } |
4116 | | |
4117 | | // Evaluate column name |
4118 | 0 | let name_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
4119 | 0 | let column_name = match name_val { |
4120 | 0 | Value::String(s) => s, |
4121 | 0 | _ => bail!("Column name must be a string"), |
4122 | | }; |
4123 | | |
4124 | | // Evaluate row index |
4125 | 0 | let index_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
4126 | 0 | let row_index = match index_val { |
4127 | 0 | Value::Int(i) => i as usize, |
4128 | 0 | _ => bail!("Row index must be an integer"), |
4129 | | }; |
4130 | | |
4131 | | // Find the column |
4132 | 0 | for col in &columns { |
4133 | 0 | if col.name == column_name { |
4134 | 0 | if row_index < col.values.len() { |
4135 | 0 | return Ok(col.values[row_index].clone()); |
4136 | 0 | } |
4137 | 0 | bail!("Row index {} out of bounds for column '{}'", row_index, column_name); |
4138 | 0 | } |
4139 | | } |
4140 | 0 | bail!("Column '{}' not found in DataFrame", column_name); |
4141 | | } |
4142 | 0 | _ => bail!("Unknown DataFrame method: {}", method), |
4143 | | } |
4144 | 0 | } |
4145 | | |
4146 | | /// Apply function to enum value (for map operations) |
4147 | 0 | fn apply_function_to_value( |
4148 | 0 | &mut self, |
4149 | 0 | enum_name: &str, |
4150 | 0 | variant_name: &str, |
4151 | 0 | data: Option<&Vec<Value>>, |
4152 | 0 | func_arg: &Expr, |
4153 | 0 | deadline: Instant, |
4154 | 0 | depth: usize, |
4155 | 0 | ) -> Result<Value> { |
4156 | 0 | if let Some(values) = data { |
4157 | 0 | if !values.is_empty() { |
4158 | 0 | let call_expr = self.create_function_call(func_arg, &values[0]); |
4159 | 0 | let mapped_value = self.evaluate_expr(&call_expr, deadline, depth + 1)?; |
4160 | 0 | return Ok(Value::EnumVariant { |
4161 | 0 | enum_name: enum_name.to_string(), |
4162 | 0 | variant_name: variant_name.to_string(), |
4163 | 0 | data: Some(vec![mapped_value]), |
4164 | 0 | }); |
4165 | 0 | } |
4166 | 0 | } |
4167 | 0 | Ok(Value::EnumVariant { |
4168 | 0 | enum_name: enum_name.to_string(), |
4169 | 0 | variant_name: variant_name.to_string(), |
4170 | 0 | data: Some(vec![Value::Unit]), |
4171 | 0 | }) |
4172 | 0 | } |
4173 | | |
4174 | | /// Apply function and flatten result (for `and_then` operations) |
4175 | 0 | fn apply_function_and_flatten( |
4176 | 0 | &mut self, |
4177 | 0 | data: Option<&Vec<Value>>, |
4178 | 0 | func_arg: &Expr, |
4179 | 0 | deadline: Instant, |
4180 | 0 | depth: usize, |
4181 | 0 | ) -> Result<Value> { |
4182 | 0 | if let Some(values) = data { |
4183 | 0 | if !values.is_empty() { |
4184 | 0 | let call_expr = self.create_function_call(func_arg, &values[0]); |
4185 | 0 | return self.evaluate_expr(&call_expr, deadline, depth + 1); |
4186 | 0 | } |
4187 | 0 | } |
4188 | 0 | Ok(Value::EnumVariant { |
4189 | 0 | enum_name: "Result".to_string(), |
4190 | 0 | variant_name: "Ok".to_string(), |
4191 | 0 | data: Some(vec![Value::Unit]), |
4192 | 0 | }) |
4193 | 0 | } |
4194 | | |
4195 | | /// Create function call expression for enum combinators |
4196 | 0 | fn create_function_call(&self, func_arg: &Expr, value: &Value) -> Expr { |
4197 | 0 | Expr::new( |
4198 | 0 | ExprKind::Call { |
4199 | 0 | func: Box::new(func_arg.clone()), |
4200 | 0 | args: vec![Expr::new( |
4201 | 0 | ExprKind::Literal(crate::frontend::ast::Literal::from_value(value)), |
4202 | 0 | Span { start: 0, end: 0 }, |
4203 | 0 | )], |
4204 | 0 | }, |
4205 | 0 | Span { start: 0, end: 0 }, |
4206 | | ) |
4207 | 0 | } |
4208 | | |
4209 | | /// Evaluate object literal (complexity: 10) |
4210 | 0 | fn evaluate_object_literal( |
4211 | 0 | &mut self, |
4212 | 0 | fields: &[crate::frontend::ast::ObjectField], |
4213 | 0 | deadline: Instant, |
4214 | 0 | depth: usize, |
4215 | 0 | ) -> Result<Value> { |
4216 | | use crate::frontend::ast::ObjectField; |
4217 | 0 | let mut map = HashMap::new(); |
4218 | | |
4219 | 0 | for field in fields { |
4220 | 0 | match field { |
4221 | 0 | ObjectField::KeyValue { key, value } => { |
4222 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
4223 | 0 | map.insert(key.clone(), val); |
4224 | | } |
4225 | 0 | ObjectField::Spread { expr } => { |
4226 | 0 | let spread_val = self.evaluate_expr(expr, deadline, depth + 1)?; |
4227 | 0 | if let Value::Object(spread_map) = spread_val { |
4228 | 0 | map.extend(spread_map); |
4229 | 0 | } else { |
4230 | 0 | bail!("Spread operator can only be used with objects"); |
4231 | | } |
4232 | | } |
4233 | | } |
4234 | | } |
4235 | | |
4236 | 0 | Self::ok_object(map) |
4237 | 0 | } |
4238 | | |
4239 | | /// Evaluate enum definition (complexity: 4) |
4240 | 0 | fn evaluate_enum_definition( |
4241 | 0 | &mut self, |
4242 | 0 | name: &str, |
4243 | 0 | variants: &[crate::frontend::ast::EnumVariant], |
4244 | 0 | ) -> Value { |
4245 | 0 | let variant_names: Vec<String> = variants.iter().map(|v| v.name.clone()).collect(); |
4246 | 0 | self.enum_definitions |
4247 | 0 | .insert(name.to_string(), variant_names); |
4248 | 0 | println!("Defined enum {} with {} variants", name, variants.len()); |
4249 | 0 | Value::Unit |
4250 | 0 | } |
4251 | | |
4252 | | /// Evaluate struct definition (complexity: 3) |
4253 | 0 | fn evaluate_struct_definition( |
4254 | 0 | name: &str, |
4255 | 0 | fields: &[crate::frontend::ast::StructField], |
4256 | 0 | ) -> Value { |
4257 | 0 | println!("Defined struct {} with {} fields", name, fields.len()); |
4258 | 0 | Value::Unit |
4259 | 0 | } |
4260 | | |
4261 | | /// Evaluate struct literal (complexity: 5) |
4262 | 0 | fn evaluate_struct_literal( |
4263 | 0 | &mut self, |
4264 | 0 | fields: &[(String, Expr)], |
4265 | 0 | deadline: Instant, |
4266 | 0 | depth: usize, |
4267 | 0 | ) -> Result<Value> { |
4268 | 0 | let mut map = HashMap::new(); |
4269 | 0 | for (field_name, field_expr) in fields { |
4270 | 0 | let field_value = self.evaluate_expr(field_expr, deadline, depth + 1)?; |
4271 | 0 | map.insert(field_name.clone(), field_value); |
4272 | | } |
4273 | 0 | Self::ok_object(map) |
4274 | 0 | } |
4275 | | |
4276 | | /// Evaluate field access (complexity: 4) |
4277 | 0 | fn evaluate_field_access( |
4278 | 0 | &mut self, |
4279 | 0 | object: &Expr, |
4280 | 0 | field: &str, |
4281 | 0 | deadline: Instant, |
4282 | 0 | depth: usize, |
4283 | 0 | ) -> Result<Value> { |
4284 | 0 | let obj_val = self.evaluate_expr(object, deadline, depth + 1)?; |
4285 | 0 | match obj_val { |
4286 | 0 | Value::Object(map) => map |
4287 | 0 | .get(field) |
4288 | 0 | .cloned() |
4289 | 0 | .ok_or_else(|| anyhow::anyhow!("Field '{}' not found", field)), |
4290 | 0 | Value::Tuple(values) => { |
4291 | | // Handle tuple access like t.0, t.1, etc. |
4292 | 0 | if let Ok(index) = field.parse::<usize>() { |
4293 | 0 | values.get(index) |
4294 | 0 | .cloned() |
4295 | 0 | .ok_or_else(|| anyhow::anyhow!("Tuple index {} out of bounds (length: {})", index, values.len())) |
4296 | | } else { |
4297 | 0 | bail!("Invalid tuple index: '{}'", field) |
4298 | | } |
4299 | | } |
4300 | 0 | _ => bail!("Field access on non-object value"), |
4301 | | } |
4302 | 0 | } |
4303 | | |
4304 | | /// Evaluate optional field access (complexity: 5) |
4305 | 0 | fn evaluate_optional_field_access( |
4306 | 0 | &mut self, |
4307 | 0 | object: &Expr, |
4308 | 0 | field: &str, |
4309 | 0 | deadline: Instant, |
4310 | 0 | depth: usize, |
4311 | 0 | ) -> Result<Value> { |
4312 | 0 | let obj_val = self.evaluate_expr(object, deadline, depth + 1)?; |
4313 | | |
4314 | | // If the object is null, return null (short-circuit evaluation) |
4315 | 0 | if matches!(obj_val, Value::Nil) { |
4316 | 0 | return Self::ok_nil(); |
4317 | 0 | } |
4318 | | |
4319 | 0 | match obj_val { |
4320 | 0 | Value::Object(map) => Ok(map.get(field).cloned().unwrap_or(Value::Nil)), |
4321 | 0 | Value::Tuple(values) => { |
4322 | | // Handle optional tuple access like t?.0, t?.1, etc. |
4323 | 0 | if let Ok(index) = field.parse::<usize>() { |
4324 | 0 | Ok(values.get(index).cloned().unwrap_or(Value::Nil)) |
4325 | | } else { |
4326 | 0 | Self::ok_nil() // Invalid tuple index returns nil instead of error |
4327 | | } |
4328 | | } |
4329 | 0 | _ => Self::ok_nil(), // Non-object/tuple values return nil instead of error |
4330 | | } |
4331 | 0 | } |
4332 | | |
4333 | | /// Evaluate optional method call with null-safe chaining (complexity: 10) |
4334 | 0 | fn evaluate_optional_method_call( |
4335 | 0 | &mut self, |
4336 | 0 | receiver: &Expr, |
4337 | 0 | method: &str, |
4338 | 0 | args: &[Expr], |
4339 | 0 | deadline: Instant, |
4340 | 0 | depth: usize, |
4341 | 0 | ) -> Result<Value> { |
4342 | 0 | let receiver_val = self.evaluate_expr(receiver, deadline, depth + 1)?; |
4343 | | |
4344 | | // If the receiver is null, return null (short-circuit evaluation) |
4345 | 0 | if matches!(receiver_val, Value::Nil) { |
4346 | 0 | return Self::ok_nil(); |
4347 | 0 | } |
4348 | | |
4349 | | // Try to call the method, but return nil if it fails instead of erroring |
4350 | 0 | let result = match receiver_val { |
4351 | 0 | Value::List(items) => { |
4352 | 0 | self.evaluate_list_methods(items, method, args, deadline, depth).unwrap_or(Value::Nil) |
4353 | | } |
4354 | 0 | Value::String(s) => { |
4355 | 0 | Self::evaluate_string_methods(&s, method, args, deadline, depth).unwrap_or(Value::Nil) |
4356 | | } |
4357 | | Value::Int(_) | Value::Float(_) => { |
4358 | 0 | self.evaluate_numeric_methods(&receiver_val, method).unwrap_or(Value::Nil) |
4359 | | } |
4360 | 0 | Value::Object(obj) => { |
4361 | 0 | Self::evaluate_object_methods(obj, method, args, deadline, depth).unwrap_or(Value::Nil) |
4362 | | } |
4363 | 0 | Value::HashMap(map) => { |
4364 | 0 | self.evaluate_hashmap_methods(map, method, args, deadline, depth).unwrap_or(Value::Nil) |
4365 | | } |
4366 | 0 | Value::HashSet(set) => { |
4367 | 0 | self.evaluate_hashset_methods(set, method, args, deadline, depth).unwrap_or(Value::Nil) |
4368 | | } |
4369 | | Value::EnumVariant { .. } => { |
4370 | 0 | self.evaluate_enum_methods(receiver_val, method, args, deadline, depth).unwrap_or(Value::Nil) |
4371 | | } |
4372 | 0 | _ => Value::Nil, // Unsupported types return nil |
4373 | | }; |
4374 | | |
4375 | 0 | Ok(result) |
4376 | 0 | } |
4377 | | |
4378 | | /// Evaluate index access (complexity: 5) |
4379 | 0 | fn evaluate_index_access( |
4380 | 0 | &mut self, |
4381 | 0 | object: &Expr, |
4382 | 0 | index: &Expr, |
4383 | 0 | deadline: Instant, |
4384 | 0 | depth: usize, |
4385 | 0 | ) -> Result<Value> { |
4386 | 0 | let obj_val = self.evaluate_expr(object, deadline, depth + 1)?; |
4387 | 0 | let index_val = self.evaluate_expr(index, deadline, depth + 1)?; |
4388 | | |
4389 | | // Check for range indexing first |
4390 | 0 | if let Value::Range { start, end, inclusive } = index_val { |
4391 | 0 | return self.handle_range_indexing(obj_val, start, end, inclusive); |
4392 | 0 | } |
4393 | | |
4394 | | // Handle single index access |
4395 | 0 | self.handle_single_index_access(obj_val, index_val) |
4396 | 0 | } |
4397 | | |
4398 | | /// Handle range-based indexing for lists and strings |
4399 | | /// |
4400 | | /// Example Usage: |
4401 | | /// |
4402 | | /// Handles range indexing for lists and strings: |
4403 | | /// - list[0..2] returns a sublist with elements at indices 0 and 1 |
4404 | | /// - string[1..3] returns substring from index 1 to 2 |
4405 | | /// - list[0..=2] returns elements at indices 0, 1, and 2 (inclusive) |
4406 | 0 | fn handle_range_indexing(&self, obj_val: Value, start: i64, end: i64, inclusive: bool) -> Result<Value> { |
4407 | 0 | match obj_val { |
4408 | 0 | Value::List(list) => { |
4409 | 0 | let (start_idx, end_idx) = self.calculate_slice_bounds(start, end, inclusive, list.len())?; |
4410 | 0 | Self::ok_list(list[start_idx..end_idx].to_vec()) |
4411 | | } |
4412 | 0 | Value::String(s) => { |
4413 | 0 | let chars: Vec<char> = s.chars().collect(); |
4414 | 0 | let (start_idx, end_idx) = self.calculate_slice_bounds(start, end, inclusive, chars.len())?; |
4415 | 0 | Self::ok_string(chars[start_idx..end_idx].iter().collect::<String>()) |
4416 | | } |
4417 | 0 | _ => bail!("Cannot slice into {:?}", obj_val), |
4418 | | } |
4419 | 0 | } |
4420 | | |
4421 | | /// Handle single index access for various data types |
4422 | | /// |
4423 | | /// # Example Usage |
4424 | | /// Handles range-based indexing for strings and arrays like arr[0..3] or str[1..]. |
4425 | | /// # use `ruchy::runtime::repl::Repl`; |
4426 | | /// # use `ruchy::runtime::repl::Value`; |
4427 | | /// let mut repl = `Repl::new().unwrap()`; |
4428 | | /// let list = `Value::List(vec`![`Value::Int(42)`]); |
4429 | | /// let result = `repl.handle_single_index_access(list`, `Value::Int(0)).unwrap()`; |
4430 | | /// `assert_eq!(result`, `Value::Int(42)`); |
4431 | | /// ``` |
4432 | 0 | fn handle_single_index_access(&self, obj_val: Value, index_val: Value) -> Result<Value> { |
4433 | 0 | match (obj_val, index_val) { |
4434 | 0 | (Value::List(list), Value::Int(idx)) => { |
4435 | 0 | let idx = self.validate_array_index(idx, list.len())?; |
4436 | 0 | Ok(list[idx].clone()) |
4437 | | } |
4438 | 0 | (Value::String(s), Value::Int(idx)) => { |
4439 | 0 | let chars: Vec<char> = s.chars().collect(); |
4440 | 0 | let idx = self.validate_array_index(idx, chars.len())?; |
4441 | 0 | Ok(Value::String(chars[idx].to_string())) |
4442 | | } |
4443 | 0 | (Value::Object(obj), Value::String(key)) => { |
4444 | 0 | obj.get(&key) |
4445 | 0 | .cloned() |
4446 | 0 | .ok_or_else(|| anyhow::anyhow!("Key '{}' not found in object", key)) |
4447 | | } |
4448 | 0 | (obj_val, index_val) => bail!("Cannot index into {:?} with index {:?}", obj_val, index_val), |
4449 | | } |
4450 | 0 | } |
4451 | | |
4452 | | /// Calculate slice bounds and validate them |
4453 | | /// |
4454 | | /// # Example Usage |
4455 | | /// Calculates and validates slice bounds for array indexing operations. |
4456 | | /// Converts indices to valid array bounds and handles inclusive/exclusive ranges. |
4457 | 0 | fn calculate_slice_bounds(&self, start: i64, end: i64, inclusive: bool, len: usize) -> Result<(usize, usize)> { |
4458 | 0 | let start_idx = usize::try_from(start) |
4459 | 0 | .map_err(|_| anyhow::anyhow!("Invalid start index: {}", start))?; |
4460 | | |
4461 | 0 | let end_idx = if inclusive { |
4462 | 0 | usize::try_from(end + 1) |
4463 | 0 | .map_err(|_| anyhow::anyhow!("Invalid end index: {}", end + 1))? |
4464 | | } else { |
4465 | 0 | usize::try_from(end) |
4466 | 0 | .map_err(|_| anyhow::anyhow!("Invalid end index: {}", end))? |
4467 | | }; |
4468 | | |
4469 | 0 | if start_idx > len || end_idx > len { |
4470 | 0 | bail!("Slice indices out of bounds"); |
4471 | 0 | } |
4472 | 0 | if start_idx > end_idx { |
4473 | 0 | bail!("Invalid slice range: start > end"); |
4474 | 0 | } |
4475 | | |
4476 | 0 | Ok((start_idx, end_idx)) |
4477 | 0 | } |
4478 | | |
4479 | | /// Validate array index and convert to usize |
4480 | | /// |
4481 | | /// # Example Usage |
4482 | | /// Calculates and validates slice bounds for array indexing operations. |
4483 | | /// # use `ruchy::runtime::repl::Repl`; |
4484 | | /// let repl = `Repl::new().unwrap()`; |
4485 | | /// let idx = `repl.validate_array_index(2`, `5).unwrap()`; |
4486 | | /// `assert_eq!(idx`, 2); |
4487 | | /// ``` |
4488 | 0 | fn validate_array_index(&self, idx: i64, len: usize) -> Result<usize> { |
4489 | 0 | let idx = usize::try_from(idx) |
4490 | 0 | .map_err(|_| anyhow::anyhow!("Invalid index: {}", idx))?; |
4491 | | |
4492 | 0 | if idx >= len { |
4493 | 0 | bail!("Index {} out of bounds for length {}", idx, len); |
4494 | 0 | } |
4495 | | |
4496 | 0 | Ok(idx) |
4497 | 0 | } |
4498 | | |
4499 | | /// Evaluate slice index expression (complexity: 4) |
4500 | 0 | fn evaluate_slice_index(&mut self, expr: Option<&Expr>, deadline: Instant, depth: usize) -> Result<Option<usize>> { |
4501 | 0 | if let Some(index_expr) = expr { |
4502 | 0 | match self.evaluate_expr(index_expr, deadline, depth + 1)? { |
4503 | 0 | Value::Int(idx) => { |
4504 | 0 | Ok(Some(usize::try_from(idx) |
4505 | 0 | .map_err(|_| anyhow::anyhow!("Invalid slice index: {}", idx))?)) |
4506 | | } |
4507 | 0 | _ => Err(anyhow::anyhow!("Slice indices must be integers")) |
4508 | | } |
4509 | | } else { |
4510 | 0 | Ok(None) |
4511 | | } |
4512 | 0 | } |
4513 | | |
4514 | | /// Validate slice bounds (complexity: 3) |
4515 | 0 | fn validate_slice_bounds(start: usize, end: usize, len: usize) -> Result<()> { |
4516 | 0 | if start > len || end > len { |
4517 | 0 | return Err(anyhow::anyhow!("Slice indices out of bounds")); |
4518 | 0 | } |
4519 | 0 | if start > end { |
4520 | 0 | return Err(anyhow::anyhow!("Invalid slice range: start > end")); |
4521 | 0 | } |
4522 | 0 | Ok(()) |
4523 | 0 | } |
4524 | | |
4525 | | /// Slice a list value (complexity: 4) |
4526 | 0 | fn slice_list(list: Vec<Value>, start_idx: Option<usize>, end_idx: Option<usize>) -> Result<Value> { |
4527 | 0 | let start = start_idx.unwrap_or(0); |
4528 | 0 | let end = end_idx.unwrap_or(list.len()); |
4529 | | |
4530 | 0 | Self::validate_slice_bounds(start, end, list.len())?; |
4531 | 0 | Self::ok_list(list[start..end].to_vec()) |
4532 | 0 | } |
4533 | | |
4534 | | /// Slice a string value (complexity: 5) |
4535 | 0 | fn slice_string(s: String, start_idx: Option<usize>, end_idx: Option<usize>) -> Result<Value> { |
4536 | 0 | let chars: Vec<char> = s.chars().collect(); |
4537 | 0 | let start = start_idx.unwrap_or(0); |
4538 | 0 | let end = end_idx.unwrap_or(chars.len()); |
4539 | | |
4540 | 0 | Self::validate_slice_bounds(start, end, chars.len())?; |
4541 | 0 | let sliced: String = chars[start..end].iter().collect(); |
4542 | 0 | Ok(Value::String(sliced)) |
4543 | 0 | } |
4544 | | |
4545 | | /// Main slice evaluation function (complexity: 6) |
4546 | 0 | fn evaluate_slice( |
4547 | 0 | &mut self, |
4548 | 0 | object: &Expr, |
4549 | 0 | start: Option<&Expr>, |
4550 | 0 | end: Option<&Expr>, |
4551 | 0 | deadline: Instant, |
4552 | 0 | depth: usize, |
4553 | 0 | ) -> Result<Value> { |
4554 | 0 | let obj_val = self.evaluate_expr(object, deadline, depth + 1)?; |
4555 | | |
4556 | | // Evaluate start and end indices |
4557 | 0 | let start_idx = self.evaluate_slice_index(start, deadline, depth)?; |
4558 | 0 | let end_idx = self.evaluate_slice_index(end, deadline, depth)?; |
4559 | | |
4560 | | // Perform slicing based on value type |
4561 | 0 | match obj_val { |
4562 | 0 | Value::List(list) => Self::slice_list(list, start_idx, end_idx), |
4563 | 0 | Value::String(s) => Self::slice_string(s, start_idx, end_idx), |
4564 | 0 | _ => Err(anyhow::anyhow!("Cannot slice value of type {:?}", obj_val)), |
4565 | | } |
4566 | 0 | } |
4567 | | |
4568 | | /// Evaluate trait definition (complexity: 3) |
4569 | 0 | fn evaluate_trait_definition( |
4570 | 0 | name: &str, |
4571 | 0 | methods: &[crate::frontend::ast::TraitMethod], |
4572 | 0 | ) -> Value { |
4573 | 0 | println!("Defined trait {} with {} methods", name, methods.len()); |
4574 | 0 | Value::Unit |
4575 | 0 | } |
4576 | | |
4577 | | /// Evaluate impl block (complexity: 12) |
4578 | 0 | fn evaluate_impl_block( |
4579 | 0 | &mut self, |
4580 | 0 | for_type: &str, |
4581 | 0 | methods: &[crate::frontend::ast::ImplMethod], |
4582 | 0 | ) -> Value { |
4583 | 0 | for method in methods { |
4584 | 0 | let qualified_name = format!("{}::{}", for_type, method.name); |
4585 | | |
4586 | 0 | let param_names: Vec<String> = method |
4587 | 0 | .params |
4588 | 0 | .iter() |
4589 | 0 | .filter_map(|p| { |
4590 | 0 | let name = p.name(); |
4591 | 0 | if name != "self" && name != "&self" { |
4592 | 0 | Some(name) |
4593 | | } else { |
4594 | 0 | None |
4595 | | } |
4596 | 0 | }) |
4597 | 0 | .collect(); |
4598 | | |
4599 | 0 | self.impl_methods |
4600 | 0 | .insert(qualified_name, (param_names, method.body.clone())); |
4601 | | } |
4602 | | |
4603 | 0 | println!( |
4604 | 0 | "Defined impl for {} with {} methods", |
4605 | | for_type, |
4606 | 0 | methods.len() |
4607 | | ); |
4608 | 0 | Value::Unit |
4609 | 0 | } |
4610 | | |
4611 | | /// Evaluate binary expression (complexity: 3) |
4612 | 0 | fn evaluate_binary_expr( |
4613 | 0 | &mut self, |
4614 | 0 | left: &Expr, |
4615 | 0 | op: BinaryOp, |
4616 | 0 | right: &Expr, |
4617 | 0 | deadline: Instant, |
4618 | 0 | depth: usize, |
4619 | 0 | ) -> Result<Value> { |
4620 | | // Handle short-circuit operators |
4621 | 0 | match op { |
4622 | | BinaryOp::NullCoalesce => { |
4623 | 0 | let lhs = self.evaluate_expr(left, deadline, depth + 1)?; |
4624 | 0 | if matches!(lhs, Value::Nil) { |
4625 | 0 | self.evaluate_expr(right, deadline, depth + 1) |
4626 | | } else { |
4627 | 0 | Ok(lhs) |
4628 | | } |
4629 | | } |
4630 | | BinaryOp::And => { |
4631 | 0 | let lhs = self.evaluate_expr(left, deadline, depth + 1)?; |
4632 | 0 | if lhs.is_truthy() { |
4633 | 0 | self.evaluate_expr(right, deadline, depth + 1) |
4634 | | } else { |
4635 | 0 | Ok(lhs) |
4636 | | } |
4637 | | } |
4638 | | BinaryOp::Or => { |
4639 | 0 | let lhs = self.evaluate_expr(left, deadline, depth + 1)?; |
4640 | 0 | if lhs.is_truthy() { |
4641 | 0 | Ok(lhs) |
4642 | | } else { |
4643 | 0 | self.evaluate_expr(right, deadline, depth + 1) |
4644 | | } |
4645 | | } |
4646 | | _ => { |
4647 | 0 | let lhs = self.evaluate_expr(left, deadline, depth + 1)?; |
4648 | 0 | let rhs = self.evaluate_expr(right, deadline, depth + 1)?; |
4649 | 0 | Self::evaluate_binary(&lhs, op, &rhs) |
4650 | | } |
4651 | | } |
4652 | 0 | } |
4653 | | |
4654 | | /// Evaluate unary expression (complexity: 2) |
4655 | 0 | fn evaluate_unary_expr( |
4656 | 0 | &mut self, |
4657 | 0 | op: UnaryOp, |
4658 | 0 | operand: &Expr, |
4659 | 0 | deadline: Instant, |
4660 | 0 | depth: usize, |
4661 | 0 | ) -> Result<Value> { |
4662 | 0 | let val = self.evaluate_expr(operand, deadline, depth + 1)?; |
4663 | 0 | Self::evaluate_unary(op, &val) |
4664 | 0 | } |
4665 | | |
4666 | | /// Evaluate identifier (complexity: 2) |
4667 | 0 | fn evaluate_identifier(&self, name: &str) -> Result<Value> { |
4668 | | // Check if it's a qualified enum variant like "Option::None" |
4669 | 0 | if let Some(pos) = name.find("::") { |
4670 | 0 | let (module, variant) = name.split_at(pos); |
4671 | 0 | let variant = &variant[2..]; // Skip the "::" |
4672 | | |
4673 | | // Handle known enum variants |
4674 | 0 | if module == "Option" && variant == "None" { |
4675 | 0 | return Ok(Self::create_option_none()); |
4676 | 0 | } else if module == "Result" { |
4677 | | // Result variants without data are not valid, but we create them for consistency |
4678 | 0 | return Ok(Value::EnumVariant { |
4679 | 0 | enum_name: module.to_string(), |
4680 | 0 | variant_name: variant.to_string(), |
4681 | 0 | data: None, |
4682 | 0 | }); |
4683 | 0 | } |
4684 | | |
4685 | | // For other qualified names, create an enum variant |
4686 | 0 | return Ok(Value::EnumVariant { |
4687 | 0 | enum_name: module.to_string(), |
4688 | 0 | variant_name: variant.to_string(), |
4689 | 0 | data: None, |
4690 | 0 | }); |
4691 | 0 | } |
4692 | | |
4693 | 0 | self.get_binding(name) |
4694 | 0 | .ok_or_else(|| anyhow::anyhow!("Undefined variable: '{}'\n Hint: Did you mean to declare it with 'let {} = value'?", name, name)) |
4695 | 0 | } |
4696 | | |
4697 | | /// Evaluate qualified name (complexity: 2) |
4698 | 0 | fn evaluate_qualified_name(module: &str, name: &str) -> Value { |
4699 | 0 | Value::EnumVariant { |
4700 | 0 | enum_name: module.to_string(), |
4701 | 0 | variant_name: name.to_string(), |
4702 | 0 | data: None, |
4703 | 0 | } |
4704 | 0 | } |
4705 | | |
4706 | | /// Evaluate await expression (complexity: 1) |
4707 | 0 | fn evaluate_await_expr( |
4708 | 0 | &mut self, |
4709 | 0 | expr: &Expr, |
4710 | 0 | deadline: Instant, |
4711 | 0 | depth: usize, |
4712 | 0 | ) -> Result<Value> { |
4713 | | // For now, await just evaluates the expression |
4714 | | // In a full async implementation, this would handle Future resolution |
4715 | 0 | self.evaluate_expr(expr, deadline, depth + 1) |
4716 | 0 | } |
4717 | | |
4718 | | /// Evaluate async block (complexity: 1) |
4719 | 0 | fn evaluate_async_block( |
4720 | 0 | &mut self, |
4721 | 0 | body: &Expr, |
4722 | 0 | deadline: Instant, |
4723 | 0 | depth: usize, |
4724 | 0 | ) -> Result<Value> { |
4725 | | // For REPL purposes, evaluate the async block body synchronously |
4726 | | // In a full async implementation, this would return a Future |
4727 | 0 | self.evaluate_expr(body, deadline, depth + 1) |
4728 | 0 | } |
4729 | | |
4730 | | /// Evaluate try operator (?) (complexity: 1) |
4731 | | /// Evaluate `DataFrame` operation (complexity: 1) |
4732 | 0 | fn evaluate_dataframe_operation() -> Result<Value> { |
4733 | | // DataFrame operations not yet implemented in REPL |
4734 | 0 | bail!("DataFrame operations not yet implemented in REPL") |
4735 | 0 | } |
4736 | | |
4737 | | /// Check if a pattern matches a value and return bindings |
4738 | | /// |
4739 | | /// Returns Some(bindings) if pattern matches, None if it doesn't |
4740 | 0 | fn pattern_matches(value: &Value, pattern: &Pattern) -> Result<Option<HashMap<String, Value>>> { |
4741 | 0 | let mut bindings = HashMap::new(); |
4742 | | |
4743 | 0 | if Self::pattern_matches_recursive(value, pattern, &mut bindings)? { |
4744 | 0 | Ok(Some(bindings)) |
4745 | | } else { |
4746 | 0 | Ok(None) |
4747 | | } |
4748 | 0 | } |
4749 | | |
4750 | | /// Recursive pattern matching helper |
4751 | | /// Match literal patterns (complexity: 4) |
4752 | 0 | fn match_literal_pattern(value: &Value, literal: &Literal) -> bool { |
4753 | 0 | match (value, literal) { |
4754 | 0 | (Value::Unit, Literal::Unit) => true, |
4755 | 0 | (Value::Int(v), Literal::Integer(p)) => v == p, |
4756 | 0 | (Value::Float(v), Literal::Float(p)) => (v - p).abs() < f64::EPSILON, |
4757 | 0 | (Value::String(v), Literal::String(p)) => v == p, |
4758 | 0 | (Value::Bool(v), Literal::Bool(p)) => v == p, |
4759 | 0 | _ => false, |
4760 | | } |
4761 | 0 | } |
4762 | | |
4763 | | /// Match sequence patterns (list or tuple) (complexity: 4) |
4764 | 0 | fn match_sequence_pattern( |
4765 | 0 | values: &[Value], |
4766 | 0 | patterns: &[Pattern], |
4767 | 0 | bindings: &mut HashMap<String, Value>, |
4768 | 0 | ) -> Result<bool> { |
4769 | 0 | if values.len() != patterns.len() { |
4770 | 0 | return Ok(false); |
4771 | 0 | } |
4772 | | |
4773 | 0 | for (value, pattern) in values.iter().zip(patterns.iter()) { |
4774 | 0 | if !Self::pattern_matches_recursive(value, pattern, bindings)? { |
4775 | 0 | return Ok(false); |
4776 | 0 | } |
4777 | | } |
4778 | 0 | Ok(true) |
4779 | 0 | } |
4780 | | |
4781 | | /// Match OR patterns (complexity: 5) |
4782 | 0 | fn match_or_pattern( |
4783 | 0 | value: &Value, |
4784 | 0 | patterns: &[Pattern], |
4785 | 0 | bindings: &mut HashMap<String, Value>, |
4786 | 0 | ) -> Result<bool> { |
4787 | 0 | for pattern in patterns { |
4788 | 0 | let mut temp_bindings = HashMap::new(); |
4789 | 0 | if Self::pattern_matches_recursive(value, pattern, &mut temp_bindings)? { |
4790 | | // Merge bindings |
4791 | 0 | for (name, val) in temp_bindings { |
4792 | 0 | bindings.insert(name, val); |
4793 | 0 | } |
4794 | 0 | return Ok(true); |
4795 | 0 | } |
4796 | | } |
4797 | 0 | Ok(false) |
4798 | 0 | } |
4799 | | |
4800 | | /// Match range patterns (complexity: 5) |
4801 | 0 | fn match_range_pattern( |
4802 | 0 | value: i64, |
4803 | 0 | start: &Pattern, |
4804 | 0 | end: &Pattern, |
4805 | 0 | inclusive: bool, |
4806 | 0 | ) -> Result<bool> { |
4807 | | // For simplicity, only handle integer literal patterns in ranges |
4808 | | if let ( |
4809 | 0 | Pattern::Literal(Literal::Integer(start_val)), |
4810 | 0 | Pattern::Literal(Literal::Integer(end_val)), |
4811 | 0 | ) = (start, end) |
4812 | | { |
4813 | 0 | if inclusive { |
4814 | 0 | Ok(*start_val <= value && value <= *end_val) |
4815 | | } else { |
4816 | 0 | Ok(*start_val <= value && value < *end_val) |
4817 | | } |
4818 | | } else { |
4819 | 0 | bail!("Complex range patterns not yet supported"); |
4820 | | } |
4821 | 0 | } |
4822 | | |
4823 | | /// Match struct patterns (complexity: 7) |
4824 | 0 | fn match_struct_pattern( |
4825 | 0 | obj_fields: &HashMap<String, Value>, |
4826 | 0 | pattern_fields: &[StructPatternField], |
4827 | 0 | bindings: &mut HashMap<String, Value>, |
4828 | 0 | ) -> Result<bool> { |
4829 | 0 | for pattern_field in pattern_fields { |
4830 | 0 | let field_name = &pattern_field.name; |
4831 | | |
4832 | | // Find the corresponding field in the object |
4833 | 0 | if let Some(field_value) = obj_fields.get(field_name) { |
4834 | | // Check if pattern matches (if specified) |
4835 | 0 | if let Some(pattern) = &pattern_field.pattern { |
4836 | 0 | if !Self::pattern_matches_recursive(field_value, pattern, bindings)? { |
4837 | 0 | return Ok(false); |
4838 | 0 | } |
4839 | 0 | } else { |
4840 | 0 | // Shorthand pattern ({ x } instead of { x: x }) |
4841 | 0 | // This creates a binding: x => field_value |
4842 | 0 | bindings.insert(field_name.clone(), field_value.clone()); |
4843 | 0 | } |
4844 | | } else { |
4845 | | // Required field not found in struct |
4846 | 0 | return Ok(false); |
4847 | | } |
4848 | | } |
4849 | 0 | Ok(true) |
4850 | 0 | } |
4851 | | |
4852 | | /// Match qualified name patterns (complexity: 4) |
4853 | 0 | fn match_qualified_name_pattern( |
4854 | 0 | value: &Value, |
4855 | 0 | path: &[String], |
4856 | 0 | ) -> bool { |
4857 | 0 | if let Value::EnumVariant { enum_name, variant_name, data: _ } = value { |
4858 | | // Match if qualified name matches enum variant |
4859 | 0 | if path.len() >= 2 { |
4860 | 0 | let pattern_enum = &path[path.len() - 2]; |
4861 | 0 | let pattern_variant = &path[path.len() - 1]; |
4862 | 0 | enum_name == pattern_enum && variant_name == pattern_variant |
4863 | | } else { |
4864 | 0 | false |
4865 | | } |
4866 | | } else { |
4867 | | // Convert value to string and compare with pattern path |
4868 | 0 | let value_str = format!("{value}"); |
4869 | 0 | let pattern_str = path.join("::"); |
4870 | 0 | value_str == pattern_str |
4871 | | } |
4872 | 0 | } |
4873 | | |
4874 | | /// Match simple patterns (complexity: 4) |
4875 | 0 | fn match_simple_patterns( |
4876 | 0 | value: &Value, |
4877 | 0 | pattern: &Pattern, |
4878 | 0 | bindings: &mut HashMap<String, Value>, |
4879 | 0 | ) -> Option<Result<bool>> { |
4880 | 0 | match pattern { |
4881 | 0 | Pattern::Wildcard => Some(Ok(true)), |
4882 | 0 | Pattern::Literal(literal) => Some(Ok(Self::match_literal_pattern(value, literal))), |
4883 | 0 | Pattern::Identifier(name) => { |
4884 | 0 | bindings.insert(name.clone(), value.clone()); |
4885 | 0 | Some(Ok(true)) |
4886 | | } |
4887 | 0 | _ => None, |
4888 | | } |
4889 | 0 | } |
4890 | | |
4891 | | /// Match collection patterns (complexity: 5) |
4892 | 0 | fn match_collection_patterns( |
4893 | 0 | value: &Value, |
4894 | 0 | pattern: &Pattern, |
4895 | 0 | bindings: &mut HashMap<String, Value>, |
4896 | 0 | ) -> Option<Result<bool>> { |
4897 | 0 | match pattern { |
4898 | 0 | Pattern::List(patterns) => match value { |
4899 | 0 | Value::List(values) => Some(Self::match_sequence_pattern(values, patterns, bindings)), |
4900 | 0 | _ => Some(Ok(false)), |
4901 | | }, |
4902 | 0 | Pattern::Tuple(patterns) => match value { |
4903 | 0 | Value::Tuple(values) => Some(Self::match_sequence_pattern(values, patterns, bindings)), |
4904 | 0 | Value::List(values) => Some(Self::match_sequence_pattern(values, patterns, bindings)), |
4905 | 0 | _ => Some(Ok(false)), |
4906 | | }, |
4907 | 0 | Pattern::Or(patterns) => Some(Self::match_or_pattern(value, patterns, bindings)), |
4908 | 0 | _ => None, |
4909 | | } |
4910 | 0 | } |
4911 | | |
4912 | | /// Match Result/Option patterns (complexity: 6) |
4913 | 0 | fn match_result_option_patterns( |
4914 | 0 | value: &Value, |
4915 | 0 | pattern: &Pattern, |
4916 | 0 | bindings: &mut HashMap<String, Value>, |
4917 | 0 | ) -> Option<Result<bool>> { |
4918 | 0 | match pattern { |
4919 | 0 | Pattern::Ok(inner_pattern) => { |
4920 | 0 | if let Some(ok_value) = Self::extract_result_ok(value) { |
4921 | 0 | Some(Self::pattern_matches_recursive(&ok_value, inner_pattern, bindings)) |
4922 | | } else { |
4923 | 0 | Some(Ok(false)) |
4924 | | } |
4925 | | } |
4926 | 0 | Pattern::Err(inner_pattern) => { |
4927 | 0 | if let Some(err_value) = Self::extract_result_err(value) { |
4928 | 0 | Some(Self::pattern_matches_recursive(&err_value, inner_pattern, bindings)) |
4929 | | } else { |
4930 | 0 | Some(Ok(false)) |
4931 | | } |
4932 | | } |
4933 | 0 | Pattern::Some(inner_pattern) => { |
4934 | 0 | if let Some(some_value) = Self::extract_option_some(value) { |
4935 | 0 | Some(Self::pattern_matches_recursive(&some_value, inner_pattern, bindings)) |
4936 | | } else { |
4937 | 0 | Some(Ok(false)) |
4938 | | } |
4939 | | } |
4940 | 0 | Pattern::None => Some(Ok(Self::is_option_none(value))), |
4941 | 0 | _ => None, |
4942 | | } |
4943 | 0 | } |
4944 | | |
4945 | | /// Match complex patterns (complexity: 5) |
4946 | 0 | fn match_complex_patterns( |
4947 | 0 | value: &Value, |
4948 | 0 | pattern: &Pattern, |
4949 | 0 | bindings: &mut HashMap<String, Value>, |
4950 | 0 | ) -> Option<Result<bool>> { |
4951 | 0 | match pattern { |
4952 | 0 | Pattern::Range { start, end, inclusive } => match value { |
4953 | 0 | Value::Int(v) => Some(Self::match_range_pattern(*v, start, end, *inclusive)), |
4954 | 0 | _ => Some(Ok(false)), |
4955 | | }, |
4956 | 0 | Pattern::Struct { name: _struct_name, fields: pattern_fields, has_rest: _ } => { |
4957 | 0 | match value { |
4958 | 0 | Value::Object(obj_fields) => { |
4959 | 0 | Some(Self::match_struct_pattern(obj_fields, pattern_fields, bindings)) |
4960 | | } |
4961 | 0 | _ => Some(Ok(false)), |
4962 | | } |
4963 | | } |
4964 | 0 | Pattern::QualifiedName(path) => { |
4965 | 0 | Some(Ok(Self::match_qualified_name_pattern(value, path))) |
4966 | | } |
4967 | | Pattern::Rest | Pattern::RestNamed(_) => { |
4968 | 0 | Some(Err(anyhow::anyhow!("Rest patterns are only valid inside struct or tuple patterns"))) |
4969 | | } |
4970 | 0 | _ => None, |
4971 | | } |
4972 | 0 | } |
4973 | | |
4974 | | /// Main pattern matching function (complexity: 6) |
4975 | 0 | fn pattern_matches_recursive( |
4976 | 0 | value: &Value, |
4977 | 0 | pattern: &Pattern, |
4978 | 0 | bindings: &mut HashMap<String, Value>, |
4979 | 0 | ) -> Result<bool> { |
4980 | | // Try simple patterns first |
4981 | 0 | if let Some(result) = Self::match_simple_patterns(value, pattern, bindings) { |
4982 | 0 | return result; |
4983 | 0 | } |
4984 | | |
4985 | | // Try collection patterns |
4986 | 0 | if let Some(result) = Self::match_collection_patterns(value, pattern, bindings) { |
4987 | 0 | return result; |
4988 | 0 | } |
4989 | | |
4990 | | // Try Result/Option patterns |
4991 | 0 | if let Some(result) = Self::match_result_option_patterns(value, pattern, bindings) { |
4992 | 0 | return result; |
4993 | 0 | } |
4994 | | |
4995 | | // Try complex patterns |
4996 | 0 | if let Some(result) = Self::match_complex_patterns(value, pattern, bindings) { |
4997 | 0 | return result; |
4998 | 0 | } |
4999 | | |
5000 | | // Should never reach here as all pattern types are covered |
5001 | 0 | bail!("Unhandled pattern type: {:?}", pattern) |
5002 | 0 | } |
5003 | | |
5004 | | /// Extract value from `Result::Ok` variant (complexity: 4) |
5005 | 0 | fn extract_result_ok(value: &Value) -> Option<Value> { |
5006 | 0 | match value { |
5007 | 0 | Value::EnumVariant { enum_name, variant_name, data } => { |
5008 | 0 | if enum_name == "Result" && variant_name == "Ok" { |
5009 | 0 | data.as_ref()?.first().cloned() |
5010 | | } else { |
5011 | 0 | None |
5012 | | } |
5013 | | } |
5014 | 0 | _ => None, |
5015 | | } |
5016 | 0 | } |
5017 | | |
5018 | | /// Extract value from `Result::Err` variant (complexity: 4) |
5019 | 0 | fn extract_result_err(value: &Value) -> Option<Value> { |
5020 | 0 | match value { |
5021 | 0 | Value::EnumVariant { enum_name, variant_name, data } => { |
5022 | 0 | if enum_name == "Result" && variant_name == "Err" { |
5023 | 0 | data.as_ref()?.first().cloned() |
5024 | | } else { |
5025 | 0 | None |
5026 | | } |
5027 | | } |
5028 | 0 | _ => None, |
5029 | | } |
5030 | 0 | } |
5031 | | |
5032 | | /// Extract value from `Option::Some` variant (complexity: 4) |
5033 | 0 | fn extract_option_some(value: &Value) -> Option<Value> { |
5034 | 0 | match value { |
5035 | 0 | Value::EnumVariant { enum_name, variant_name, data } => { |
5036 | 0 | if enum_name == "Option" && variant_name == "Some" { |
5037 | 0 | data.as_ref()?.first().cloned() |
5038 | | } else { |
5039 | 0 | None |
5040 | | } |
5041 | | } |
5042 | 0 | _ => None, |
5043 | | } |
5044 | 0 | } |
5045 | | |
5046 | | /// Check if value is `Option::None` variant (complexity: 3) |
5047 | 0 | fn is_option_none(value: &Value) -> bool { |
5048 | 0 | match value { |
5049 | 0 | Value::EnumVariant { enum_name, variant_name, data: _ } => { |
5050 | 0 | enum_name == "Option" && variant_name == "None" |
5051 | | } |
5052 | 0 | _ => false, |
5053 | | } |
5054 | 0 | } |
5055 | | |
5056 | | /// Evaluate binary operations |
5057 | | /// Evaluate integer arithmetic operations (complexity: 7) |
5058 | 0 | fn evaluate_integer_arithmetic(a: i64, op: BinaryOp, b: i64) -> Result<Value> { |
5059 | 0 | match op { |
5060 | 0 | BinaryOp::Add => a |
5061 | 0 | .checked_add(b) |
5062 | 0 | .map(Value::Int) |
5063 | 0 | .ok_or_else(|| anyhow::anyhow!("Integer overflow in addition: {} + {}", a, b)), |
5064 | 0 | BinaryOp::Subtract => a |
5065 | 0 | .checked_sub(b) |
5066 | 0 | .map(Value::Int) |
5067 | 0 | .ok_or_else(|| anyhow::anyhow!("Integer overflow in subtraction: {} - {}", a, b)), |
5068 | 0 | BinaryOp::Multiply => a |
5069 | 0 | .checked_mul(b) |
5070 | 0 | .map(Value::Int) |
5071 | 0 | .ok_or_else(|| anyhow::anyhow!("Integer overflow in multiplication: {} * {}", a, b)), |
5072 | | BinaryOp::Divide => { |
5073 | 0 | if b == 0 { |
5074 | 0 | bail!("Division by zero"); |
5075 | 0 | } |
5076 | | // Division always produces a float |
5077 | 0 | Ok(Value::Float(a as f64 / b as f64)) |
5078 | | } |
5079 | | BinaryOp::Modulo => { |
5080 | 0 | if b == 0 { |
5081 | 0 | bail!("Modulo by zero"); |
5082 | 0 | } |
5083 | 0 | Ok(Value::Int(a % b)) |
5084 | | } |
5085 | | BinaryOp::Power => { |
5086 | 0 | if b < 0 { |
5087 | 0 | bail!("Negative integer powers not supported in integer context"); |
5088 | 0 | } |
5089 | 0 | let exp = u32::try_from(b).map_err(|_| anyhow::anyhow!("Power exponent too large"))?; |
5090 | 0 | a.checked_pow(exp) |
5091 | 0 | .map(Value::Int) |
5092 | 0 | .ok_or_else(|| anyhow::anyhow!("Integer overflow in power: {} ^ {}", a, b)) |
5093 | | } |
5094 | 0 | _ => bail!("Invalid integer arithmetic operation: {:?}", op), |
5095 | | } |
5096 | 0 | } |
5097 | | |
5098 | | /// Evaluate float arithmetic operations (complexity: 5) |
5099 | 0 | fn evaluate_float_arithmetic(a: f64, op: BinaryOp, b: f64) -> Result<Value> { |
5100 | 0 | match op { |
5101 | 0 | BinaryOp::Add => Ok(Value::Float(a + b)), |
5102 | 0 | BinaryOp::Subtract => Ok(Value::Float(a - b)), |
5103 | 0 | BinaryOp::Multiply => Ok(Value::Float(a * b)), |
5104 | | BinaryOp::Divide => { |
5105 | 0 | if b == 0.0 { |
5106 | 0 | bail!("Division by zero"); |
5107 | 0 | } |
5108 | 0 | Ok(Value::Float(a / b)) |
5109 | | } |
5110 | 0 | BinaryOp::Power => Ok(Value::Float(a.powf(b))), |
5111 | 0 | _ => bail!("Invalid float arithmetic operation: {:?}", op), |
5112 | | } |
5113 | 0 | } |
5114 | | |
5115 | | /// Evaluate comparison operations (complexity: 6) |
5116 | 0 | fn evaluate_comparison(lhs: &Value, op: BinaryOp, rhs: &Value) -> Result<Value> { |
5117 | 0 | match (lhs, rhs) { |
5118 | 0 | (Value::Int(a), Value::Int(b)) => match op { |
5119 | 0 | BinaryOp::Less => Ok(Value::Bool(a < b)), |
5120 | 0 | BinaryOp::LessEqual => Ok(Value::Bool(a <= b)), |
5121 | 0 | BinaryOp::Greater => Ok(Value::Bool(a > b)), |
5122 | 0 | BinaryOp::GreaterEqual => Ok(Value::Bool(a >= b)), |
5123 | 0 | BinaryOp::Equal => Ok(Value::Bool(a == b)), |
5124 | 0 | BinaryOp::NotEqual => Ok(Value::Bool(a != b)), |
5125 | 0 | _ => bail!("Invalid integer comparison: {:?}", op), |
5126 | | }, |
5127 | 0 | (Value::String(a), Value::String(b)) => match op { |
5128 | 0 | BinaryOp::Equal => Ok(Value::Bool(a == b)), |
5129 | 0 | BinaryOp::NotEqual => Ok(Value::Bool(a != b)), |
5130 | 0 | _ => bail!("Invalid string comparison: {:?}", op), |
5131 | | }, |
5132 | 0 | (Value::Bool(a), Value::Bool(b)) => match op { |
5133 | 0 | BinaryOp::Equal => Ok(Value::Bool(a == b)), |
5134 | 0 | BinaryOp::NotEqual => Ok(Value::Bool(a != b)), |
5135 | 0 | _ => bail!("Invalid boolean comparison: {:?}", op), |
5136 | | }, |
5137 | | // Float comparisons |
5138 | 0 | (Value::Float(a), Value::Float(b)) => match op { |
5139 | 0 | BinaryOp::Less => Ok(Value::Bool(a < b)), |
5140 | 0 | BinaryOp::LessEqual => Ok(Value::Bool(a <= b)), |
5141 | 0 | BinaryOp::Greater => Ok(Value::Bool(a > b)), |
5142 | 0 | BinaryOp::GreaterEqual => Ok(Value::Bool(a >= b)), |
5143 | 0 | BinaryOp::Equal => Ok(Value::Bool((a - b).abs() < f64::EPSILON)), |
5144 | 0 | BinaryOp::NotEqual => Ok(Value::Bool((a - b).abs() >= f64::EPSILON)), |
5145 | 0 | _ => bail!("Invalid float comparison: {:?}", op), |
5146 | | }, |
5147 | | // Mixed Int/Float comparisons - coerce to Float |
5148 | 0 | (Value::Int(a), Value::Float(b)) => match op { |
5149 | 0 | BinaryOp::Less => Ok(Value::Bool((*a as f64) < *b)), |
5150 | 0 | BinaryOp::LessEqual => Ok(Value::Bool((*a as f64) <= *b)), |
5151 | 0 | BinaryOp::Greater => Ok(Value::Bool((*a as f64) > *b)), |
5152 | 0 | BinaryOp::GreaterEqual => Ok(Value::Bool((*a as f64) >= *b)), |
5153 | 0 | BinaryOp::Equal => Ok(Value::Bool(((*a as f64) - *b).abs() < f64::EPSILON)), |
5154 | 0 | BinaryOp::NotEqual => Ok(Value::Bool(((*a as f64) - *b).abs() >= f64::EPSILON)), |
5155 | 0 | _ => bail!("Invalid mixed int/float comparison: {:?}", op), |
5156 | | }, |
5157 | | // Mixed Float/Int comparisons - coerce to Float |
5158 | 0 | (Value::Float(a), Value::Int(b)) => match op { |
5159 | 0 | BinaryOp::Less => Ok(Value::Bool(*a < (*b as f64))), |
5160 | 0 | BinaryOp::LessEqual => Ok(Value::Bool(*a <= (*b as f64))), |
5161 | 0 | BinaryOp::Greater => Ok(Value::Bool(*a > (*b as f64))), |
5162 | 0 | BinaryOp::GreaterEqual => Ok(Value::Bool(*a >= (*b as f64))), |
5163 | 0 | BinaryOp::Equal => Ok(Value::Bool((*a - (*b as f64)).abs() < f64::EPSILON)), |
5164 | 0 | BinaryOp::NotEqual => Ok(Value::Bool((*a - (*b as f64)).abs() >= f64::EPSILON)), |
5165 | 0 | _ => bail!("Invalid mixed float/int comparison: {:?}", op), |
5166 | | }, |
5167 | 0 | _ => bail!("Type mismatch in comparison: {:?} vs {:?}", lhs, rhs), |
5168 | | } |
5169 | 0 | } |
5170 | | |
5171 | | /// Evaluate bitwise operations (complexity: 4) |
5172 | 0 | fn evaluate_bitwise(a: i64, op: BinaryOp, b: i64) -> Result<Value> { |
5173 | 0 | match op { |
5174 | 0 | BinaryOp::BitwiseAnd => Ok(Value::Int(a & b)), |
5175 | 0 | BinaryOp::BitwiseOr => Ok(Value::Int(a | b)), |
5176 | 0 | BinaryOp::BitwiseXor => Ok(Value::Int(a ^ b)), |
5177 | 0 | BinaryOp::LeftShift => Ok(Value::Int(a << b)), |
5178 | 0 | _ => bail!("Invalid bitwise operation: {:?}", op), |
5179 | | } |
5180 | 0 | } |
5181 | | |
5182 | 0 | fn evaluate_binary(lhs: &Value, op: BinaryOp, rhs: &Value) -> Result<Value> { |
5183 | 0 | match (lhs, op, rhs) { |
5184 | | // Integer arithmetic |
5185 | 0 | (Value::Int(a), op, Value::Int(b)) if matches!(op, |
5186 | | BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | |
5187 | | BinaryOp::Divide | BinaryOp::Modulo | BinaryOp::Power) => { |
5188 | 0 | Self::evaluate_integer_arithmetic(*a, op, *b) |
5189 | | } |
5190 | | |
5191 | | // Float arithmetic |
5192 | 0 | (Value::Float(a), op, Value::Float(b)) if matches!(op, |
5193 | | BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | |
5194 | | BinaryOp::Divide | BinaryOp::Power) => { |
5195 | 0 | Self::evaluate_float_arithmetic(*a, op, *b) |
5196 | | } |
5197 | | |
5198 | | // Mixed Int/Float arithmetic - coerce to Float |
5199 | 0 | (Value::Int(a), op, Value::Float(b)) if matches!(op, |
5200 | | BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | |
5201 | | BinaryOp::Divide | BinaryOp::Power) => { |
5202 | 0 | Self::evaluate_float_arithmetic(*a as f64, op, *b) |
5203 | | } |
5204 | | |
5205 | | // Mixed Float/Int arithmetic - coerce to Float |
5206 | 0 | (Value::Float(a), op, Value::Int(b)) if matches!(op, |
5207 | | BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | |
5208 | | BinaryOp::Divide | BinaryOp::Power) => { |
5209 | 0 | Self::evaluate_float_arithmetic(*a, op, *b as f64) |
5210 | | } |
5211 | | |
5212 | | // String concatenation - optimized with pre-allocation |
5213 | 0 | (Value::String(a), BinaryOp::Add, Value::String(b)) => { |
5214 | 0 | let mut result = String::with_capacity(a.len() + b.len()); |
5215 | 0 | result.push_str(a); |
5216 | 0 | result.push_str(b); |
5217 | 0 | Self::ok_string(result) |
5218 | | } |
5219 | | |
5220 | | // Comparisons |
5221 | 0 | (lhs, op, rhs) if matches!(op, |
5222 | | BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | |
5223 | | BinaryOp::GreaterEqual | BinaryOp::Equal | BinaryOp::NotEqual) => { |
5224 | 0 | Self::evaluate_comparison(lhs, op, rhs) |
5225 | | } |
5226 | | |
5227 | | // Boolean logic |
5228 | 0 | (Value::Bool(a), BinaryOp::And, Value::Bool(b)) => Ok(Value::Bool(*a && *b)), |
5229 | 0 | (Value::Bool(a), BinaryOp::Or, Value::Bool(b)) => Ok(Value::Bool(*a || *b)), |
5230 | | |
5231 | | // Null coalescing |
5232 | 0 | (Value::Nil, BinaryOp::NullCoalesce, rhs) => Ok(rhs.clone()), |
5233 | 0 | (lhs, BinaryOp::NullCoalesce, _) => Ok(lhs.clone()), |
5234 | | |
5235 | | // Bitwise operations |
5236 | 0 | (Value::Int(a), op, Value::Int(b)) if matches!(op, |
5237 | | BinaryOp::BitwiseAnd | BinaryOp::BitwiseOr | |
5238 | | BinaryOp::BitwiseXor | BinaryOp::LeftShift) => { |
5239 | 0 | Self::evaluate_bitwise(*a, op, *b) |
5240 | | } |
5241 | | |
5242 | 0 | _ => bail!( |
5243 | 0 | "Type mismatch in binary operation: {:?} {:?} {:?}", |
5244 | | lhs, |
5245 | | op, |
5246 | | rhs |
5247 | | ), |
5248 | | } |
5249 | 0 | } |
5250 | | |
5251 | | /// Evaluate unary operations |
5252 | 0 | fn evaluate_unary(op: UnaryOp, val: &Value) -> Result<Value> { |
5253 | | use Value::{Bool, Float, Int}; |
5254 | | |
5255 | 0 | match (op, val) { |
5256 | 0 | (UnaryOp::Negate, Int(n)) => Ok(Int(-n)), |
5257 | 0 | (UnaryOp::Negate, Float(f)) => Ok(Float(-f)), |
5258 | 0 | (UnaryOp::Not, Bool(b)) => Ok(Bool(!b)), |
5259 | 0 | (UnaryOp::BitwiseNot, Int(n)) => Ok(Int(!n)), |
5260 | 0 | (UnaryOp::Reference, v) => { |
5261 | | // References in the REPL context just return the value |
5262 | | // In a real implementation, this would create a reference/pointer |
5263 | | // For now, we'll just return the value as references are primarily |
5264 | | // useful for the transpiled code, not the interpreted REPL |
5265 | 0 | Ok(v.clone()) |
5266 | | } |
5267 | 0 | _ => bail!("Type mismatch in unary operation: {:?} {:?}", op, val), |
5268 | | } |
5269 | 0 | } |
5270 | | |
5271 | | /// Run the interactive REPL |
5272 | | /// |
5273 | | /// # Errors |
5274 | | /// |
5275 | | /// Returns an error if: |
5276 | | /// - Readline initialization fails |
5277 | | /// - User input cannot be read |
5278 | | /// - Commands fail to execute |
5279 | 0 | pub fn run(&mut self) -> Result<()> { |
5280 | 0 | println!(); |
5281 | | |
5282 | 0 | let mut rl = self.setup_readline_editor()?; |
5283 | 0 | let mut multiline_state = MultilineState::new(); |
5284 | | |
5285 | | loop { |
5286 | 0 | let prompt = self.format_prompt(multiline_state.in_multiline); |
5287 | 0 | let readline = rl.readline(&prompt); |
5288 | | |
5289 | 0 | match readline { |
5290 | 0 | Ok(line) => { |
5291 | 0 | if self.process_input_line(&line, &mut rl, &mut multiline_state)? { |
5292 | 0 | break; // :quit was executed |
5293 | 0 | } |
5294 | | } |
5295 | 0 | Err(ReadlineError::Interrupted) => { |
5296 | 0 | println!("\nUse :quit to exit"); |
5297 | 0 | } |
5298 | | Err(ReadlineError::Eof) => { |
5299 | 0 | println!("\nGoodbye!"); |
5300 | 0 | break; |
5301 | | } |
5302 | 0 | Err(err) => { |
5303 | 0 | eprintln!("Error: {err:?}"); |
5304 | 0 | break; |
5305 | | } |
5306 | | } |
5307 | | } |
5308 | | |
5309 | | // Save history |
5310 | 0 | let history_path = self.temp_dir.join("history.txt"); |
5311 | 0 | let _ = rl.save_history(&history_path); |
5312 | 0 | Ok(()) |
5313 | 0 | } |
5314 | | |
5315 | | /// Handle REPL commands and return output as string (for testing) |
5316 | | // Helper functions for command handling (complexity < 10 each) |
5317 | | // ======================================================================== |
5318 | | |
5319 | | /// Handle :quit command (complexity: 3) |
5320 | 0 | fn handle_quit_command(&mut self) -> (bool, String) { |
5321 | 0 | if self.mode == ReplMode::Normal { |
5322 | | // In normal mode, :quit exits REPL |
5323 | 0 | (true, String::new()) |
5324 | | } else { |
5325 | | // In a special mode, :quit returns to normal |
5326 | 0 | self.mode = ReplMode::Normal; |
5327 | 0 | (false, "Returned to normal mode".to_string()) |
5328 | | } |
5329 | 0 | } |
5330 | | |
5331 | | /// Handle :history command (complexity: 3) |
5332 | 0 | fn handle_history_command(&self) -> String { |
5333 | 0 | if self.history.is_empty() { |
5334 | 0 | "No history".to_string() |
5335 | | } else { |
5336 | 0 | let mut output = String::new(); |
5337 | 0 | for (i, item) in self.history.iter().enumerate() { |
5338 | 0 | output.push_str(&format!("{}: {}\n", i + 1, item)); |
5339 | 0 | } |
5340 | 0 | output |
5341 | | } |
5342 | 0 | } |
5343 | | |
5344 | | /// Handle :clear command (complexity: 2) |
5345 | 0 | fn handle_clear_command(&mut self) -> String { |
5346 | 0 | self.history.clear(); |
5347 | 0 | self.definitions.clear(); |
5348 | 0 | self.bindings.clear(); |
5349 | 0 | self.result_history.clear(); |
5350 | 0 | "Session cleared".to_string() |
5351 | 0 | } |
5352 | | |
5353 | | /// Handle :bindings/:env command (complexity: 3) |
5354 | 0 | fn handle_bindings_command(&self) -> String { |
5355 | 0 | if self.bindings.is_empty() { |
5356 | 0 | "No bindings".to_string() |
5357 | | } else { |
5358 | 0 | let mut output = String::new(); |
5359 | 0 | for (name, value) in &self.bindings { |
5360 | 0 | output.push_str(&format!("{name}: {value}\n")); |
5361 | 0 | } |
5362 | 0 | output |
5363 | | } |
5364 | 0 | } |
5365 | | |
5366 | | /// Handle :compile command (complexity: 2) |
5367 | 0 | fn handle_compile_command(&mut self) -> String { |
5368 | 0 | match self.compile_session() { |
5369 | 0 | Ok(()) => "Session compiled successfully".to_string(), |
5370 | 0 | Err(e) => format!("Compilation failed: {e}"), |
5371 | | } |
5372 | 0 | } |
5373 | | |
5374 | | /// Handle :load command (complexity: 3) |
5375 | 0 | fn handle_load_command(&mut self, parts: &[&str]) -> String { |
5376 | 0 | if parts.len() == 2 { |
5377 | 0 | match self.load_file(parts[1]) { |
5378 | 0 | Ok(()) => format!("Loaded file: {}", parts[1]), |
5379 | 0 | Err(e) => format!("Failed to load file: {e}"), |
5380 | | } |
5381 | | } else { |
5382 | 0 | "Usage: :load <filename>".to_string() |
5383 | | } |
5384 | 0 | } |
5385 | | |
5386 | | /// Handle :save command (complexity: 3) |
5387 | 0 | fn handle_save_command(&mut self, command: &str) -> String { |
5388 | 0 | let filename = command.strip_prefix(":save").unwrap_or("").trim(); |
5389 | 0 | if filename.is_empty() { |
5390 | 0 | "Usage: :save <filename>".to_string() |
5391 | | } else { |
5392 | 0 | match self.save_session(filename) { |
5393 | 0 | Ok(()) => format!("Session saved to {filename}"), |
5394 | 0 | Err(e) => format!("Failed to save session: {e}"), |
5395 | | } |
5396 | | } |
5397 | 0 | } |
5398 | | |
5399 | | /// Handle :export command (complexity: 3) |
5400 | 0 | fn handle_export_command(&mut self, command: &str) -> String { |
5401 | 0 | let filename = command.strip_prefix(":export").unwrap_or("").trim(); |
5402 | 0 | if filename.is_empty() { |
5403 | 0 | "Usage: :export <filename>".to_string() |
5404 | | } else { |
5405 | 0 | match self.export_session(filename) { |
5406 | 0 | Ok(()) => format!("Session exported to clean script: {filename}"), |
5407 | 0 | Err(e) => format!("Failed to export session: {e}"), |
5408 | | } |
5409 | | } |
5410 | 0 | } |
5411 | | |
5412 | | /// Handle :type command (complexity: 3) |
5413 | 0 | fn handle_type_command(&mut self, command: &str) -> String { |
5414 | 0 | let expr = command.strip_prefix(":type").unwrap_or("").trim(); |
5415 | 0 | if expr.is_empty() { |
5416 | 0 | "Usage: :type <expression>".to_string() |
5417 | | } else { |
5418 | 0 | self.get_type_info_with_bindings(expr) |
5419 | | } |
5420 | 0 | } |
5421 | | |
5422 | | /// Handle :ast command (complexity: 3) |
5423 | 0 | fn handle_ast_command(command: &str) -> String { |
5424 | 0 | let expr = command.strip_prefix(":ast").unwrap_or("").trim(); |
5425 | 0 | if expr.is_empty() { |
5426 | 0 | "Usage: :ast <expression>".to_string() |
5427 | | } else { |
5428 | 0 | Self::get_ast_info(expr) |
5429 | | } |
5430 | 0 | } |
5431 | | |
5432 | | /// Handle :inspect command (complexity: 3) |
5433 | 0 | fn handle_inspect_command(&self, command: &str) -> String { |
5434 | 0 | let var_name = command.strip_prefix(":inspect").unwrap_or("").trim(); |
5435 | 0 | if var_name.is_empty() { |
5436 | 0 | "Usage: :inspect <variable>".to_string() |
5437 | | } else { |
5438 | 0 | self.inspect_value(var_name) |
5439 | | } |
5440 | 0 | } |
5441 | | |
5442 | | /// Handle :reset command (complexity: 2) |
5443 | 0 | fn handle_reset_command(&mut self) -> String { |
5444 | 0 | self.history.clear(); |
5445 | 0 | self.definitions.clear(); |
5446 | 0 | self.bindings.clear(); |
5447 | 0 | self.result_history.clear(); |
5448 | 0 | self.memory.reset(); |
5449 | 0 | "REPL reset to initial state".to_string() |
5450 | 0 | } |
5451 | | |
5452 | | /// Handle :search command (complexity: 3) |
5453 | 0 | fn handle_search_command(&self, command: &str) -> String { |
5454 | 0 | let query = command.strip_prefix(":search").unwrap_or("").trim(); |
5455 | 0 | if query.is_empty() { |
5456 | 0 | "Usage: :search <query>\nSearch through command history with fuzzy matching".to_string() |
5457 | | } else { |
5458 | 0 | self.get_search_results(query) |
5459 | | } |
5460 | 0 | } |
5461 | | |
5462 | | /// Handle mode commands (complexity: 2) |
5463 | 0 | fn handle_mode_command(&mut self, mode: ReplMode) -> String { |
5464 | 0 | self.mode = mode; |
5465 | 0 | format!("Switched to {} mode", mode.prompt()) |
5466 | 0 | } |
5467 | | |
5468 | | /// Dispatch basic REPL commands (complexity: 8) |
5469 | 0 | fn dispatch_basic_commands(&mut self, cmd: &str, parts: &[&str]) -> Option<Result<(bool, String)>> { |
5470 | 0 | match cmd { |
5471 | 0 | ":quit" | ":q" => Some(Ok(self.handle_quit_command())), |
5472 | 0 | ":history" => Some(Ok((false, self.handle_history_command()))), |
5473 | 0 | ":clear" => Some(Ok((false, self.handle_clear_command()))), |
5474 | 0 | ":bindings" | ":env" => Some(Ok((false, self.handle_bindings_command()))), |
5475 | 0 | ":compile" => Some(Ok((false, self.handle_compile_command()))), |
5476 | 0 | ":load" => Some(Ok((false, self.handle_load_command(parts)))), |
5477 | 0 | ":reset" => Some(Ok((false, self.handle_reset_command()))), |
5478 | 0 | _ => None, |
5479 | | } |
5480 | 0 | } |
5481 | | |
5482 | | /// Dispatch analysis commands (complexity: 6) |
5483 | 0 | fn dispatch_analysis_commands(&mut self, cmd: &str, command: &str) -> Option<Result<(bool, String)>> { |
5484 | 0 | if cmd.starts_with(":save") { |
5485 | 0 | Some(Ok((false, self.handle_save_command(command)))) |
5486 | 0 | } else if cmd.starts_with(":export") { |
5487 | 0 | Some(Ok((false, self.handle_export_command(command)))) |
5488 | 0 | } else if cmd.starts_with(":type") { |
5489 | 0 | Some(Ok((false, self.handle_type_command(command)))) |
5490 | 0 | } else if cmd.starts_with(":ast") { |
5491 | 0 | Some(Ok((false, Self::handle_ast_command(command)))) |
5492 | 0 | } else if cmd.starts_with(":inspect") { |
5493 | 0 | Some(Ok((false, self.handle_inspect_command(command)))) |
5494 | 0 | } else if cmd.starts_with(":search") { |
5495 | 0 | Some(Ok((false, self.handle_search_command(command)))) |
5496 | | } else { |
5497 | 0 | None |
5498 | | } |
5499 | 0 | } |
5500 | | |
5501 | | /// Dispatch mode switching commands (complexity: 8) |
5502 | 0 | fn dispatch_mode_commands(&mut self, cmd: &str, parts: &[&str]) -> Option<Result<(bool, String)>> { |
5503 | 0 | match cmd { |
5504 | 0 | ":normal" => Some(Ok((false, self.handle_mode_command(ReplMode::Normal)))), |
5505 | 0 | ":shell" => Some(Ok((false, self.handle_mode_command(ReplMode::Shell)))), |
5506 | 0 | ":pkg" => Some(Ok((false, self.handle_mode_command(ReplMode::Pkg)))), |
5507 | 0 | ":sql" => Some(Ok((false, self.handle_mode_command(ReplMode::Sql)))), |
5508 | 0 | ":math" => Some(Ok((false, self.handle_mode_command(ReplMode::Math)))), |
5509 | 0 | ":debug" => Some(Ok((false, self.handle_mode_command(ReplMode::Debug)))), |
5510 | 0 | ":time" => Some(Ok((false, self.handle_mode_command(ReplMode::Time)))), |
5511 | 0 | ":test" => Some(Ok((false, self.handle_mode_command(ReplMode::Test)))), |
5512 | 0 | ":exit" => Some(Ok((false, self.handle_mode_command(ReplMode::Normal)))), |
5513 | 0 | ":help" | ":h" if parts.len() == 1 => { |
5514 | 0 | self.mode = ReplMode::Help; |
5515 | 0 | Some(self.show_help_menu().map(|output| (false, output))) |
5516 | | }, |
5517 | 0 | ":help" if parts.len() > 1 => { |
5518 | 0 | let topic = parts[1]; |
5519 | 0 | Some(self.handle_help_command(topic).map(|output| (false, output))) |
5520 | | }, |
5521 | 0 | ":modes" => { |
5522 | 0 | let output = Self::get_modes_list(); |
5523 | 0 | Some(Ok((false, output))) |
5524 | | }, |
5525 | 0 | _ => None, |
5526 | | } |
5527 | 0 | } |
5528 | | |
5529 | | /// Get list of available modes (complexity: 1) |
5530 | 0 | fn get_modes_list() -> String { |
5531 | 0 | let mut output = "Available modes:\n".to_string(); |
5532 | 0 | output.push_str(" normal - Standard Ruchy evaluation\n"); |
5533 | 0 | output.push_str(" shell - Execute shell commands\n"); |
5534 | 0 | output.push_str(" pkg - Package management\n"); |
5535 | 0 | output.push_str(" help - Interactive help\n"); |
5536 | 0 | output.push_str(" sql - SQL queries\n"); |
5537 | 0 | output.push_str(" math - Mathematical expressions\n"); |
5538 | 0 | output.push_str(" debug - Debug information with traces\n"); |
5539 | 0 | output.push_str(" time - Execution timing\n"); |
5540 | 0 | output.push_str(" test - Assertions and table tests\n"); |
5541 | 0 | output.push_str("\nUse :mode_name to switch modes, :normal or :exit to return"); |
5542 | 0 | output |
5543 | 0 | } |
5544 | | |
5545 | | /// Main command handler with output (complexity: 6) |
5546 | 0 | fn handle_command_with_output(&mut self, command: &str) -> Result<(bool, String)> { |
5547 | 0 | let parts: Vec<&str> = command.split_whitespace().collect(); |
5548 | 0 | let first_cmd = parts.first().copied().unwrap_or(""); |
5549 | | |
5550 | | // Try basic commands |
5551 | 0 | if let Some(result) = self.dispatch_basic_commands(first_cmd, &parts) { |
5552 | 0 | return result; |
5553 | 0 | } |
5554 | | |
5555 | | // Try analysis commands |
5556 | 0 | if let Some(result) = self.dispatch_analysis_commands(first_cmd, command) { |
5557 | 0 | return result; |
5558 | 0 | } |
5559 | | |
5560 | | // Try mode commands |
5561 | 0 | if let Some(result) = self.dispatch_mode_commands(first_cmd, &parts) { |
5562 | 0 | return result; |
5563 | 0 | } |
5564 | | |
5565 | | // Unknown command |
5566 | 0 | Ok((false, format!("Unknown command: {command}\nType :help for available commands"))) |
5567 | 0 | } |
5568 | | |
5569 | | /// Handle session management commands (complexity: 5) |
5570 | 0 | fn handle_session_commands(&mut self, cmd: &str) -> Option<Result<bool>> { |
5571 | 0 | match cmd { |
5572 | 0 | ":history" => { |
5573 | 0 | for (i, item) in self.history.iter().enumerate() { |
5574 | 0 | println!("{}: {}", i + 1, item); |
5575 | 0 | } |
5576 | 0 | Some(Ok(false)) |
5577 | | } |
5578 | 0 | ":clear" => { |
5579 | 0 | self.history.clear(); |
5580 | 0 | self.definitions.clear(); |
5581 | 0 | self.bindings.clear(); |
5582 | 0 | println!("Session cleared"); |
5583 | 0 | Some(Ok(false)) |
5584 | | } |
5585 | 0 | ":reset" => { |
5586 | 0 | self.history.clear(); |
5587 | 0 | self.definitions.clear(); |
5588 | 0 | self.bindings.clear(); |
5589 | 0 | self.memory.reset(); |
5590 | 0 | println!("REPL reset to initial state"); |
5591 | 0 | Some(Ok(false)) |
5592 | | } |
5593 | 0 | ":compile" => Some(self.compile_session().map(|()| false)), |
5594 | 0 | _ => None, |
5595 | | } |
5596 | 0 | } |
5597 | | |
5598 | | /// Handle inspection commands (complexity: 4) |
5599 | 0 | fn handle_inspection_commands(&mut self, command: &str) -> Option<Result<bool>> { |
5600 | 0 | if command.starts_with(":type") { |
5601 | 0 | let expr = command.strip_prefix(":type").unwrap_or("").trim(); |
5602 | 0 | if expr.is_empty() { |
5603 | 0 | println!("Usage: :type <expression>"); |
5604 | 0 | } else { |
5605 | 0 | Self::show_type(expr); |
5606 | 0 | } |
5607 | 0 | Some(Ok(false)) |
5608 | 0 | } else if command.starts_with(":ast") { |
5609 | 0 | let expr = command.strip_prefix(":ast").unwrap_or("").trim(); |
5610 | 0 | if expr.is_empty() { |
5611 | 0 | println!("Usage: :ast <expression>"); |
5612 | 0 | } else { |
5613 | 0 | Self::show_ast(expr); |
5614 | 0 | } |
5615 | 0 | Some(Ok(false)) |
5616 | 0 | } else if command.starts_with(":inspect") { |
5617 | 0 | let var_name = command.strip_prefix(":inspect").unwrap_or("").trim(); |
5618 | 0 | if var_name.is_empty() { |
5619 | 0 | println!("Usage: :inspect <variable>"); |
5620 | 0 | } else { |
5621 | 0 | println!("{}", self.inspect_value(var_name)); |
5622 | 0 | } |
5623 | 0 | Some(Ok(false)) |
5624 | 0 | } else if command == ":bindings" || command == ":env" { |
5625 | 0 | if self.bindings.is_empty() { |
5626 | 0 | println!("No bindings"); |
5627 | 0 | } else { |
5628 | 0 | for (name, value) in &self.bindings { |
5629 | 0 | println!("{name}: {value}"); |
5630 | 0 | } |
5631 | | } |
5632 | 0 | Some(Ok(false)) |
5633 | | } else { |
5634 | 0 | None |
5635 | | } |
5636 | 0 | } |
5637 | | |
5638 | | /// Handle file operations (complexity: 4) |
5639 | 0 | fn handle_file_operations(&mut self, command: &str, parts: &[&str]) -> Option<Result<bool>> { |
5640 | 0 | if command.starts_with(":load") && parts.len() == 2 { |
5641 | 0 | Some(self.load_file(parts[1]).map(|()| false)) |
5642 | 0 | } else if command.starts_with(":save") { |
5643 | 0 | let filename = command.strip_prefix(":save").unwrap_or("").trim(); |
5644 | 0 | if filename.is_empty() { |
5645 | 0 | println!("Usage: :save <filename>"); |
5646 | 0 | println!("Save current session to a file"); |
5647 | 0 | } else { |
5648 | 0 | match self.save_session(filename) { |
5649 | 0 | Ok(()) => println!("Session saved to {}", filename.bright_green()), |
5650 | 0 | Err(e) => eprintln!("Failed to save session: {e}"), |
5651 | | } |
5652 | | } |
5653 | 0 | Some(Ok(false)) |
5654 | 0 | } else if command.starts_with(":search") { |
5655 | 0 | let query = command.strip_prefix(":search").unwrap_or("").trim(); |
5656 | 0 | if query.is_empty() { |
5657 | 0 | println!("Usage: :search <query>"); |
5658 | 0 | println!("Search through command history with fuzzy matching"); |
5659 | 0 | } else { |
5660 | 0 | self.search_history(query); |
5661 | 0 | } |
5662 | 0 | Some(Ok(false)) |
5663 | | } else { |
5664 | 0 | None |
5665 | | } |
5666 | 0 | } |
5667 | | |
5668 | | /// Handle REPL commands (public for testing) (complexity: 7) |
5669 | | /// |
5670 | | /// # Errors |
5671 | | /// |
5672 | | /// Returns an error if command execution fails |
5673 | 0 | pub fn handle_command(&mut self, command: &str) -> Result<bool> { |
5674 | 0 | let parts: Vec<&str> = command.split_whitespace().collect(); |
5675 | 0 | let first_cmd = parts.first().copied().unwrap_or(""); |
5676 | | |
5677 | | // Check for quit command |
5678 | 0 | if first_cmd == ":quit" || first_cmd == ":q" { |
5679 | 0 | return Ok(true); |
5680 | 0 | } |
5681 | | |
5682 | | // Check for help command |
5683 | 0 | if first_cmd == ":help" || first_cmd == ":h" { |
5684 | 0 | Self::print_help(); |
5685 | 0 | return Ok(false); |
5686 | 0 | } |
5687 | | |
5688 | | // Try session management commands |
5689 | 0 | if let Some(result) = self.handle_session_commands(first_cmd) { |
5690 | 0 | return result; |
5691 | 0 | } |
5692 | | |
5693 | | // Try inspection commands |
5694 | 0 | if let Some(result) = self.handle_inspection_commands(command) { |
5695 | 0 | return result; |
5696 | 0 | } |
5697 | | |
5698 | | // Try file operations |
5699 | 0 | if let Some(result) = self.handle_file_operations(command, &parts) { |
5700 | 0 | return result; |
5701 | 0 | } |
5702 | | |
5703 | | // Unknown command |
5704 | 0 | eprintln!("Unknown command: {command}"); |
5705 | 0 | Self::print_help(); |
5706 | 0 | Ok(false) |
5707 | 0 | } |
5708 | | |
5709 | | /// Get help text as string |
5710 | 0 | fn get_help_text() -> String { |
5711 | 0 | let mut help = String::new(); |
5712 | 0 | help.push_str("Available commands:\n"); |
5713 | 0 | help.push_str(" :help, :h - Show this help message\n"); |
5714 | 0 | help.push_str(" :quit, :q - Exit the REPL\n"); |
5715 | 0 | help.push_str(" :history - Show evaluation history\n"); |
5716 | 0 | help.push_str(" :search <query> - Search history with fuzzy matching\n"); |
5717 | 0 | help.push_str(" :clear - Clear definitions and history\n"); |
5718 | 0 | help.push_str(" :reset - Full reset to initial state\n"); |
5719 | 0 | help.push_str(" :bindings, :env - Show current variable bindings\n"); |
5720 | 0 | help.push_str(" :type <expr> - Show type of expression\n"); |
5721 | 0 | help.push_str(" :ast <expr> - Show AST of expression\n"); |
5722 | 0 | help.push_str(" :inspect <var> - Inspect a variable in detail\n"); |
5723 | 0 | help.push_str(" :compile - Compile and run the session\n"); |
5724 | 0 | help.push_str(" :load <file> - Load and evaluate a file\n"); |
5725 | 0 | help.push_str(" :save <file> - Save session to file\n"); |
5726 | 0 | help.push_str(" :export <file> - Export session to clean script\n"); |
5727 | 0 | help |
5728 | 0 | } |
5729 | | |
5730 | | /// Print help message |
5731 | 0 | fn print_help() { |
5732 | 0 | println!("{}", Self::get_help_text()); |
5733 | 0 | } |
5734 | | |
5735 | | /// Get type information as string |
5736 | 0 | fn get_type_info(expr: &str) -> String { |
5737 | 0 | match Parser::new(expr).parse() { |
5738 | 0 | Ok(ast) => { |
5739 | | // Create an inference context for type checking |
5740 | 0 | let mut ctx = crate::middleend::InferenceContext::new(); |
5741 | | |
5742 | | // Infer the type |
5743 | 0 | match ctx.infer(&ast) { |
5744 | 0 | Ok(ty) => format!("Type: {ty}"), |
5745 | 0 | Err(e) => format!("Type inference error: {e}"), |
5746 | | } |
5747 | | } |
5748 | 0 | Err(e) => format!("Parse error: {e}"), |
5749 | | } |
5750 | 0 | } |
5751 | | |
5752 | | /// Get type information with REPL bindings context |
5753 | 0 | fn get_type_info_with_bindings(&self, expr: &str) -> String { |
5754 | | // If the expression is a simple identifier, check bindings first |
5755 | 0 | if let Ok(_) = Parser::new(expr).parse() { |
5756 | 0 | if let Some(value) = self.bindings.get(expr) { |
5757 | | // Infer type from the value |
5758 | 0 | let type_name = match value { |
5759 | 0 | Value::Int(_) => "Integer", |
5760 | 0 | Value::Float(_) => "Float", |
5761 | 0 | Value::String(_) => "String", |
5762 | 0 | Value::Bool(_) => "Bool", |
5763 | 0 | Value::List(_) => "List", |
5764 | 0 | Value::Function { .. } => "Function", |
5765 | 0 | Value::Lambda { .. } => "Lambda", |
5766 | 0 | Value::Object(_) => "Object", |
5767 | 0 | Value::Tuple(_) => "Tuple", |
5768 | 0 | Value::Char(_) => "Char", |
5769 | 0 | Value::DataFrame { .. } => "DataFrame", |
5770 | 0 | Value::HashMap(_) => "HashMap", |
5771 | 0 | Value::HashSet(_) => "HashSet", |
5772 | 0 | Value::Range { .. } => "Range", |
5773 | 0 | Value::EnumVariant { enum_name, variant_name, .. } => { |
5774 | 0 | &format!("{enum_name}::{variant_name}") |
5775 | | } |
5776 | 0 | Value::Unit => "Unit", |
5777 | 0 | Value::Nil => "Nil" |
5778 | | }; |
5779 | 0 | return format!("Type: {type_name}"); |
5780 | 0 | } |
5781 | 0 | } |
5782 | | |
5783 | | // Fall back to regular type inference |
5784 | 0 | Self::get_type_info(expr) |
5785 | 0 | } |
5786 | | |
5787 | | /// Get AST information as string |
5788 | 0 | fn get_ast_info(expr: &str) -> String { |
5789 | 0 | match Parser::new(expr).parse() { |
5790 | 0 | Ok(ast) => format!("{ast:#?}"), |
5791 | 0 | Err(e) => format!("Parse error: {e}"), |
5792 | | } |
5793 | 0 | } |
5794 | | |
5795 | | /// Get search results as string |
5796 | 0 | fn get_search_results(&self, query: &str) -> String { |
5797 | 0 | let mut results = Vec::new(); |
5798 | 0 | let query_lower = query.to_lowercase(); |
5799 | | |
5800 | 0 | for (i, item) in self.history.iter().enumerate() { |
5801 | 0 | if item.to_lowercase().contains(&query_lower) { |
5802 | 0 | results.push(format!("{}: {}", i + 1, item)); |
5803 | 0 | } |
5804 | | } |
5805 | | |
5806 | 0 | if results.is_empty() { |
5807 | 0 | format!("No matches found for '{query}'") |
5808 | | } else { |
5809 | 0 | results.join("\n") |
5810 | | } |
5811 | 0 | } |
5812 | | |
5813 | | /// Execute a shell command and return its output |
5814 | 0 | fn execute_shell_command(&self, command: &str) -> Result<String> { |
5815 | | use std::process::Command; |
5816 | | |
5817 | | // Execute command through shell |
5818 | 0 | let output = Command::new("sh") |
5819 | 0 | .arg("-c") |
5820 | 0 | .arg(command) |
5821 | 0 | .output() |
5822 | 0 | .context(format!("Failed to execute shell command: {command}"))?; |
5823 | | |
5824 | | // Combine stdout and stderr |
5825 | 0 | let stdout = String::from_utf8_lossy(&output.stdout); |
5826 | 0 | let stderr = String::from_utf8_lossy(&output.stderr); |
5827 | | |
5828 | 0 | if !output.status.success() { |
5829 | | // If command failed, return error with stderr |
5830 | 0 | if !stderr.is_empty() { |
5831 | 0 | bail!("Shell command failed: {}", stderr); |
5832 | 0 | } |
5833 | 0 | bail!("Shell command failed with exit code: {:?}", output.status.code()); |
5834 | 0 | } |
5835 | | |
5836 | | // Return stdout (stderr is usually empty for successful commands) |
5837 | 0 | Ok(stdout.trim_end().to_string()) |
5838 | 0 | } |
5839 | | |
5840 | | /// Basic introspection with single ? |
5841 | 0 | fn basic_introspection(&self, target: &str) -> Result<String> { |
5842 | | // Check if target exists in bindings |
5843 | 0 | if let Some(value) = self.bindings.get(target) { |
5844 | 0 | let type_name = self.get_value_type_name(value); |
5845 | 0 | let value_str = self.format_value_brief(value); |
5846 | 0 | return Ok(format!("Type: {type_name}\nValue: {value_str}")); |
5847 | 0 | } |
5848 | | |
5849 | | // Check if it's a builtin function |
5850 | 0 | if self.is_builtin_function(target) { |
5851 | 0 | return Ok(format!("Type: Builtin Function\nName: {target}")); |
5852 | 0 | } |
5853 | | |
5854 | | // Try to evaluate the expression and introspect result |
5855 | 0 | if let Ok(ast) = Parser::new(target).parse() { |
5856 | | // Try to get type information |
5857 | 0 | let mut ctx = crate::middleend::InferenceContext::new(); |
5858 | 0 | if let Ok(ty) = ctx.infer(&ast) { |
5859 | 0 | return Ok(format!("Type: {ty}")); |
5860 | 0 | } |
5861 | 0 | } |
5862 | | |
5863 | 0 | bail!("'{}' is not defined or cannot be introspected", target) |
5864 | 0 | } |
5865 | | |
5866 | | /// Detailed introspection with double ?? |
5867 | 0 | fn detailed_introspection(&self, target: &str) -> Result<String> { |
5868 | | // Check if target exists in bindings |
5869 | 0 | if let Some(value) = self.bindings.get(target) { |
5870 | 0 | return Ok(self.format_detailed_introspection(target, value)); |
5871 | 0 | } |
5872 | | |
5873 | | // Check if it's a builtin function |
5874 | 0 | if self.is_builtin_function(target) { |
5875 | 0 | return Ok(self.format_builtin_help(target)); |
5876 | 0 | } |
5877 | | |
5878 | 0 | bail!("'{}' is not defined or cannot be introspected", target) |
5879 | 0 | } |
5880 | | |
5881 | | /// Check if a name is a builtin function |
5882 | 0 | fn is_builtin_function(&self, name: &str) -> bool { |
5883 | 0 | matches!(name, "println" | "print" | "len" | "push" | "pop" | "insert" | |
5884 | 0 | "remove" | "clear" | "contains" | "index_of" | "slice" | |
5885 | 0 | "split" | "join" | "trim" | "to_upper" | "to_lower" | |
5886 | 0 | "replace" | "starts_with" | "ends_with" | "parse" | |
5887 | 0 | "type" | "str" | "int" | "float" | "bool" | |
5888 | 0 | "sqrt" | "pow" | "abs" | "min" | "max" | "floor" | "ceil" | "round") |
5889 | 0 | } |
5890 | | |
5891 | | /// Get type name for a value |
5892 | 0 | fn get_value_type_name(&self, value: &Value) -> &str { |
5893 | 0 | match value { |
5894 | 0 | Value::Int(_) => "Integer", |
5895 | 0 | Value::Float(_) => "Float", |
5896 | 0 | Value::String(_) => "String", |
5897 | 0 | Value::Bool(_) => "Bool", |
5898 | 0 | Value::Char(_) => "Char", |
5899 | 0 | Value::List(_) => "List", |
5900 | 0 | Value::Tuple(_) => "Tuple", |
5901 | 0 | Value::Function { .. } => "Function", |
5902 | 0 | Value::Lambda { .. } => "Lambda", |
5903 | 0 | Value::Object(_) => "Object", |
5904 | 0 | Value::HashMap(_) => "HashMap", |
5905 | 0 | Value::HashSet(_) => "HashSet", |
5906 | 0 | Value::Range { .. } => "Range", |
5907 | 0 | Value::DataFrame { .. } => "DataFrame", |
5908 | 0 | Value::EnumVariant { enum_name, variant_name, .. } => { |
5909 | | // Return a static str by leaking - safe for REPL lifetime |
5910 | 0 | Box::leak(format!("{enum_name}::{variant_name}").into_boxed_str()) |
5911 | | } |
5912 | 0 | Value::Unit => "Unit", |
5913 | 0 | Value::Nil => "Nil", |
5914 | | } |
5915 | 0 | } |
5916 | | |
5917 | | /// Format value briefly for introspection |
5918 | 0 | fn format_value_brief(&self, value: &Value) -> String { |
5919 | 0 | match value { |
5920 | 0 | Value::List(items) => format!("[{} items]", items.len()), |
5921 | 0 | Value::Object(fields) => { |
5922 | 0 | let field_names: Vec<_> = fields.keys().cloned().collect(); |
5923 | 0 | format!("{{{}}}",field_names.join(", ")) |
5924 | | } |
5925 | 0 | Value::Function { name, params, .. } => { |
5926 | 0 | format!("fn {}({})", name, params.join(", ")) |
5927 | | } |
5928 | 0 | Value::Lambda { params, .. } => { |
5929 | 0 | format!("|{}| -> ...", params.join(", ")) |
5930 | | } |
5931 | 0 | _ => value.to_string(), |
5932 | | } |
5933 | 0 | } |
5934 | | |
5935 | | /// Format detailed introspection output |
5936 | 0 | fn format_detailed_introspection(&self, name: &str, value: &Value) -> String { |
5937 | 0 | let mut output = String::new(); |
5938 | 0 | output.push_str(&format!("Name: {name}\n")); |
5939 | 0 | output.push_str(&format!("Type: {}\n", self.get_value_type_name(value))); |
5940 | | |
5941 | 0 | match value { |
5942 | 0 | Value::Function { name: fn_name, params, body } => { |
5943 | 0 | output.push_str(&format!("Source: fn {}({}) {{\n", fn_name, params.join(", "))); |
5944 | 0 | output.push_str(&format!(" {}\n", self.format_expr_source(body))); |
5945 | 0 | output.push_str("}\n"); |
5946 | 0 | output.push_str(&format!("Parameters: {}\n", params.join(", "))); |
5947 | 0 | } |
5948 | 0 | Value::Lambda { params, body } => { |
5949 | 0 | output.push_str(&format!("Source: |{}| {{\n", params.join(", "))); |
5950 | 0 | output.push_str(&format!(" {}\n", self.format_expr_source(body))); |
5951 | 0 | output.push_str("}\n"); |
5952 | 0 | output.push_str(&format!("Parameters: {}\n", params.join(", "))); |
5953 | 0 | } |
5954 | 0 | Value::Object(fields) => { |
5955 | 0 | output.push_str("Fields:\n"); |
5956 | 0 | for (key, val) in fields { |
5957 | 0 | output.push_str(&format!(" {}: {}\n", key, self.get_value_type_name(val))); |
5958 | 0 | } |
5959 | | } |
5960 | 0 | Value::List(items) => { |
5961 | 0 | output.push_str(&format!("Length: {}\n", items.len())); |
5962 | 0 | if !items.is_empty() { |
5963 | 0 | output.push_str(&format!("First: {}\n", items[0])); |
5964 | 0 | if items.len() > 1 { |
5965 | 0 | output.push_str(&format!("Last: {}\n", items[items.len() - 1])); |
5966 | 0 | } |
5967 | 0 | } |
5968 | | } |
5969 | 0 | _ => { |
5970 | 0 | output.push_str(&format!("Value: {value}\n")); |
5971 | 0 | } |
5972 | | } |
5973 | | |
5974 | 0 | output |
5975 | 0 | } |
5976 | | |
5977 | | /// Format expression source code |
5978 | 0 | fn format_expr_source(&self, expr: &Expr) -> String { |
5979 | | // Format the expression in a more readable way |
5980 | 0 | self.expr_to_source_string(expr, 0) |
5981 | 0 | } |
5982 | | |
5983 | | /// Convert expression to source string |
5984 | 0 | fn expr_to_source_string(&self, expr: &Expr, indent: usize) -> String { |
5985 | | use crate::frontend::ast::ExprKind; |
5986 | 0 | let indent_str = " ".repeat(indent); |
5987 | | |
5988 | 0 | match &expr.kind { |
5989 | 0 | ExprKind::Binary { left, op, right } => { |
5990 | 0 | format!("{} {} {}", |
5991 | 0 | self.expr_to_source_string(left, 0), |
5992 | | op, |
5993 | 0 | self.expr_to_source_string(right, 0)) |
5994 | | } |
5995 | 0 | ExprKind::If { condition, then_branch, else_branch } => { |
5996 | 0 | let mut s = format!("if {} {{\n{}{}\n{}}}", |
5997 | 0 | self.expr_to_source_string(condition, 0), |
5998 | 0 | " ".repeat(indent + 1), |
5999 | 0 | self.expr_to_source_string(then_branch, indent + 1), |
6000 | | indent_str); |
6001 | 0 | if let Some(else_b) = else_branch { |
6002 | 0 | s.push_str(&format!(" else {{\n{}{}\n{}}}", |
6003 | 0 | " ".repeat(indent + 1), |
6004 | 0 | self.expr_to_source_string(else_b, indent + 1), |
6005 | 0 | indent_str)); |
6006 | 0 | } |
6007 | 0 | s |
6008 | | } |
6009 | 0 | ExprKind::Call { func, args } => { |
6010 | 0 | if let ExprKind::Identifier(name) = &func.kind { |
6011 | 0 | format!("{}({})", name, |
6012 | 0 | args.iter() |
6013 | 0 | .map(|a| self.expr_to_source_string(a, 0)) |
6014 | 0 | .collect::<Vec<_>>() |
6015 | 0 | .join(", ")) |
6016 | | } else { |
6017 | 0 | "(call ...)".to_string() |
6018 | | } |
6019 | | } |
6020 | 0 | ExprKind::Identifier(name) => name.clone(), |
6021 | 0 | ExprKind::Literal(lit) => format!("{lit:?}"), |
6022 | 0 | ExprKind::Block(exprs) => { |
6023 | 0 | if exprs.len() == 1 { |
6024 | 0 | self.expr_to_source_string(&exprs[0], indent) |
6025 | | } else { |
6026 | 0 | exprs.iter() |
6027 | 0 | .map(|e| self.expr_to_source_string(e, indent)) |
6028 | 0 | .collect::<Vec<_>>() |
6029 | 0 | .join("; ") |
6030 | | } |
6031 | | } |
6032 | 0 | _ => format!("{:?}", expr.kind).chars().take(50).collect() |
6033 | | } |
6034 | 0 | } |
6035 | | |
6036 | | /// Format help for builtin functions |
6037 | 0 | fn format_builtin_help(&self, name: &str) -> String { |
6038 | 0 | match name { |
6039 | 0 | "println" => "println(value)\n Prints a value to stdout with newline\n Parameters: value - Any value to print".to_string(), |
6040 | 0 | "print" => "print(value)\n Prints a value to stdout without newline\n Parameters: value - Any value to print".to_string(), |
6041 | 0 | "len" => "len(collection)\n Returns the length of a collection\n Parameters: collection - List, String, or other collection".to_string(), |
6042 | 0 | "type" => "type(value)\n Returns the type of a value\n Parameters: value - Any value".to_string(), |
6043 | 0 | "str" => "str(value)\n Converts a value to string\n Parameters: value - Any value to convert".to_string(), |
6044 | 0 | _ => format!("{name}\n Builtin function\n (documentation not available)"), |
6045 | | } |
6046 | 0 | } |
6047 | | |
6048 | | /// Evaluate `type()` function |
6049 | 0 | fn evaluate_type_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6050 | 0 | Self::validate_exact_args("type()", 1, args.len())?; |
6051 | | |
6052 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
6053 | 0 | let type_name = self.get_value_type_name(&value); |
6054 | 0 | Ok(Value::String(type_name.to_string())) |
6055 | 0 | } |
6056 | | |
6057 | | /// Evaluate `summary()` function |
6058 | 0 | fn evaluate_summary_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6059 | 0 | Self::validate_exact_args("summary()", 1, args.len())?; |
6060 | | |
6061 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
6062 | 0 | let summary = match &value { |
6063 | 0 | Value::List(items) => format!("List with {} items", items.len()), |
6064 | 0 | Value::Object(fields) => format!("Object with {} fields", fields.len()), |
6065 | 0 | Value::String(s) => format!("String of length {}", s.len()), |
6066 | 0 | Value::DataFrame { columns } => format!("DataFrame with {} columns", columns.len()), |
6067 | 0 | _ => format!("{} value", self.get_value_type_name(&value)), |
6068 | | }; |
6069 | 0 | Ok(Value::String(summary)) |
6070 | 0 | } |
6071 | | |
6072 | | /// Evaluate `dir()` function |
6073 | 0 | fn evaluate_dir_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6074 | 0 | Self::validate_exact_args("dir()", 1, args.len())?; |
6075 | | |
6076 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
6077 | 0 | let members = match value { |
6078 | 0 | Value::Object(fields) => { |
6079 | 0 | fields.keys().cloned().collect::<Vec<_>>() |
6080 | | } |
6081 | 0 | _ => vec![], |
6082 | | }; |
6083 | | |
6084 | 0 | Ok(Value::String(members.join(", "))) |
6085 | 0 | } |
6086 | | |
6087 | | /// Evaluate `help()` function |
6088 | 0 | fn evaluate_help_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6089 | 0 | if args.len() != 1 { |
6090 | 0 | bail!("help() expects 1 argument, got {}", args.len()); |
6091 | 0 | } |
6092 | | |
6093 | | // Check if it's a builtin function first |
6094 | 0 | if let ExprKind::Identifier(name) = &args[0].kind { |
6095 | 0 | if self.is_builtin_function(name) { |
6096 | 0 | return Ok(Value::String(self.format_builtin_help(name))); |
6097 | 0 | } |
6098 | 0 | } |
6099 | | |
6100 | | // Try to evaluate the argument and get its type |
6101 | 0 | match self.evaluate_expr(&args[0], deadline, depth + 1) { |
6102 | 0 | Ok(value) => { |
6103 | 0 | let help_text = match value { |
6104 | 0 | Value::Function { name, params, .. } => { |
6105 | 0 | format!("Function: {}\nParameters: {}", name, params.join(", ")) |
6106 | | } |
6107 | 0 | Value::Lambda { params, .. } => { |
6108 | 0 | format!("Lambda function\nParameters: {}", params.join(", ")) |
6109 | | } |
6110 | | _ => { |
6111 | 0 | format!("Type: {}", self.get_value_type_name(&value)) |
6112 | | } |
6113 | | }; |
6114 | 0 | Ok(Value::String(help_text)) |
6115 | | } |
6116 | | Err(_) => { |
6117 | | // If evaluation fails, just return a generic message |
6118 | 0 | Ok(Value::String("No help available for this value".to_string())) |
6119 | | } |
6120 | | } |
6121 | 0 | } |
6122 | | |
6123 | | /// Evaluate `whos()` function - lists all variables with types |
6124 | 0 | fn evaluate_whos_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6125 | 0 | let filter = if args.len() == 1 { |
6126 | | // Get type filter |
6127 | 0 | let val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
6128 | 0 | if let Value::String(s) = val { |
6129 | 0 | Some(s) |
6130 | | } else { |
6131 | 0 | None |
6132 | | } |
6133 | | } else { |
6134 | 0 | None |
6135 | | }; |
6136 | | |
6137 | 0 | let mut output = Vec::new(); |
6138 | 0 | for (name, value) in &self.bindings { |
6139 | 0 | let type_name = self.get_value_type_name(value); |
6140 | 0 | if let Some(ref filter_type) = filter { |
6141 | 0 | if type_name != filter_type { |
6142 | 0 | continue; |
6143 | 0 | } |
6144 | 0 | } |
6145 | 0 | output.push(format!("{name}: {type_name}")); |
6146 | | } |
6147 | | |
6148 | 0 | Ok(Value::String(output.join("\n"))) |
6149 | 0 | } |
6150 | | |
6151 | | /// Evaluate `who()` function - simple list of variable names |
6152 | 0 | fn evaluate_who_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
6153 | 0 | let names: Vec<_> = self.bindings.keys().cloned().collect(); |
6154 | 0 | Ok(Value::String(names.join(", "))) |
6155 | 0 | } |
6156 | | |
6157 | | /// Evaluate clear!() function - clears workspace |
6158 | 0 | fn evaluate_clear_bang_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6159 | 0 | if args.is_empty() { |
6160 | | // Clear all bindings |
6161 | 0 | let count = self.bindings.len(); |
6162 | 0 | self.bindings.clear(); |
6163 | 0 | Ok(Value::String(format!("Cleared {count} variables"))) |
6164 | | } else { |
6165 | | // Clear matching pattern |
6166 | 0 | let pattern = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
6167 | 0 | if let Value::String(pat) = pattern { |
6168 | 0 | let mut cleared = 0; |
6169 | 0 | let pattern_prefix = pat.trim_end_matches('*'); |
6170 | 0 | let keys_to_remove: Vec<_> = self.bindings.keys() |
6171 | 0 | .filter(|k| k.starts_with(pattern_prefix)) |
6172 | 0 | .cloned() |
6173 | 0 | .collect(); |
6174 | 0 | for key in keys_to_remove { |
6175 | 0 | self.bindings.remove(&key); |
6176 | 0 | cleared += 1; |
6177 | 0 | } |
6178 | 0 | Ok(Value::String(format!("Cleared {cleared} variables"))) |
6179 | | } else { |
6180 | 0 | bail!("clear! pattern must be a string") |
6181 | | } |
6182 | | } |
6183 | 0 | } |
6184 | | |
6185 | | /// Evaluate `save_image()` function |
6186 | 0 | fn evaluate_save_image_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6187 | 0 | if args.len() != 1 { |
6188 | 0 | bail!("save_image() expects 1 argument (filename), got {}", args.len()); |
6189 | 0 | } |
6190 | | |
6191 | 0 | let filename = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
6192 | 0 | if let Value::String(path) = filename { |
6193 | | // Generate Ruchy code to recreate workspace |
6194 | 0 | let mut content = String::new(); |
6195 | 0 | content.push_str("// Workspace image\n"); |
6196 | 0 | content.push_str("// Generated by save_image()\n\n"); |
6197 | | |
6198 | | // Save all bindings |
6199 | 0 | for (name, value) in &self.bindings { |
6200 | 0 | match value { |
6201 | 0 | Value::Int(n) => content.push_str(&format!("let {name}= {n}\n")), |
6202 | 0 | Value::Float(f) => content.push_str(&format!("let {name}= {f}\n")), |
6203 | 0 | Value::String(s) => content.push_str(&format!("let {} = \"{}\"\n", name, s.replace('"', "\\\""))), |
6204 | 0 | Value::Bool(b) => content.push_str(&format!("let {name}= {b}\n")), |
6205 | 0 | Value::List(items) => { |
6206 | 0 | content.push_str(&format!("let {name} = [")); |
6207 | 0 | for (i, item) in items.iter().enumerate() { |
6208 | 0 | if i > 0 { content.push_str(", "); } |
6209 | 0 | content.push_str(&format!("{item}")); |
6210 | | } |
6211 | 0 | content.push_str("]\n"); |
6212 | | } |
6213 | 0 | Value::Function { name: fn_name, params, body } => { |
6214 | 0 | content.push_str(&format!("fn {}({}) {{ {} }}\n", |
6215 | 0 | fn_name, params.join(", "), |
6216 | 0 | self.format_expr_source(body))); |
6217 | 0 | } |
6218 | 0 | _ => {} // Skip complex types for now |
6219 | | } |
6220 | | } |
6221 | | |
6222 | | // Write to file |
6223 | 0 | fs::write(&path, content)?; |
6224 | 0 | Ok(Value::String(format!("Workspace saved to {path}"))) |
6225 | | } else { |
6226 | 0 | bail!("save_image() requires a string filename") |
6227 | | } |
6228 | 0 | } |
6229 | | |
6230 | | /// Evaluate `workspace()` function |
6231 | 0 | fn evaluate_workspace_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
6232 | 0 | let var_count = self.bindings.len(); |
6233 | 0 | let func_count = self.bindings.values() |
6234 | 0 | .filter(|v| matches!(v, Value::Function { .. } | Value::Lambda { .. })) |
6235 | 0 | .count(); |
6236 | | |
6237 | 0 | Ok(Value::String(format!("{var_count}variables, {func_count} functions"))) |
6238 | 0 | } |
6239 | | |
6240 | | /// Evaluate `locals()` function |
6241 | 0 | fn evaluate_locals_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
6242 | | // For now, same as globals since we don't have proper scoping |
6243 | 0 | self.evaluate_globals_function(&[], Instant::now(), 0) |
6244 | 0 | } |
6245 | | |
6246 | | /// Evaluate `globals()` function |
6247 | 0 | fn evaluate_globals_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
6248 | 0 | let mut output = Vec::new(); |
6249 | 0 | for (name, value) in &self.bindings { |
6250 | 0 | output.push(format!("{}: {}", name, self.get_value_type_name(value))); |
6251 | 0 | } |
6252 | 0 | Ok(Value::String(output.join("\n"))) |
6253 | 0 | } |
6254 | | |
6255 | | /// Evaluate `reset()` function |
6256 | 0 | fn evaluate_reset_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
6257 | 0 | self.bindings.clear(); |
6258 | 0 | self.history.clear(); |
6259 | 0 | self.result_history.clear(); |
6260 | 0 | self.definitions.clear(); |
6261 | 0 | self.memory.reset(); |
6262 | 0 | Ok(Value::String("Workspace reset".to_string())) |
6263 | 0 | } |
6264 | | |
6265 | | /// Evaluate `del()` function |
6266 | 0 | fn evaluate_del_function(&mut self, args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
6267 | 0 | if args.len() != 1 { |
6268 | 0 | bail!("del() expects 1 argument, got {}", args.len()); |
6269 | 0 | } |
6270 | | |
6271 | | // Get the name to delete |
6272 | 0 | if let ExprKind::Identifier(name) = &args[0].kind { |
6273 | 0 | if self.bindings.remove(name).is_some() { |
6274 | 0 | Self::ok_unit() |
6275 | | } else { |
6276 | 0 | bail!("Variable '{}' not found", name) |
6277 | | } |
6278 | | } else { |
6279 | 0 | bail!("del() requires a variable name") |
6280 | | } |
6281 | 0 | } |
6282 | | |
6283 | | /// Evaluate `exists()` function |
6284 | 0 | fn evaluate_exists_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6285 | 0 | if args.len() != 1 { |
6286 | 0 | bail!("exists() expects 1 argument, got {}", args.len()); |
6287 | 0 | } |
6288 | | |
6289 | 0 | let name_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
6290 | 0 | if let Value::String(name) = name_val { |
6291 | 0 | Ok(Value::Bool(self.bindings.contains_key(&name))) |
6292 | | } else { |
6293 | 0 | bail!("exists() requires a string variable name") |
6294 | | } |
6295 | 0 | } |
6296 | | |
6297 | | /// Evaluate `memory_info()` function |
6298 | 0 | fn evaluate_memory_info_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
6299 | 0 | let current = self.memory.current; |
6300 | 0 | let max = self.memory.max_size; |
6301 | 0 | let kb = current / 1024; |
6302 | 0 | Ok(Value::String(format!("Memory: {current} bytes ({kb} KB) / {max} max"))) |
6303 | 0 | } |
6304 | | |
6305 | | /// Evaluate `time_info()` function |
6306 | 0 | fn evaluate_time_info_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
6307 | | // For simplicity, just return a placeholder |
6308 | 0 | Ok(Value::String("Session time: active".to_string())) |
6309 | 0 | } |
6310 | | |
6311 | | /// Show the type of an expression |
6312 | 0 | fn show_type(expr: &str) { |
6313 | 0 | match Parser::new(expr).parse() { |
6314 | 0 | Ok(ast) => { |
6315 | | // Create an inference context for type checking |
6316 | 0 | let mut ctx = crate::middleend::InferenceContext::new(); |
6317 | | |
6318 | | // Infer the type |
6319 | 0 | match ctx.infer(&ast) { |
6320 | 0 | Ok(ty) => { |
6321 | 0 | println!("Type: {ty}"); |
6322 | 0 | } |
6323 | 0 | Err(e) => { |
6324 | 0 | eprintln!("Type inference error: {e}"); |
6325 | 0 | } |
6326 | | } |
6327 | | } |
6328 | 0 | Err(e) => { |
6329 | 0 | eprintln!("Parse error: {e}"); |
6330 | 0 | } |
6331 | | } |
6332 | 0 | } |
6333 | | |
6334 | | /// Show the AST of an expression |
6335 | 0 | fn show_ast(expr: &str) { |
6336 | 0 | match Parser::new(expr).parse() { |
6337 | 0 | Ok(ast) => { |
6338 | 0 | println!("{ast:#?}"); |
6339 | 0 | } |
6340 | 0 | Err(e) => { |
6341 | 0 | eprintln!("Parse error: {e}"); |
6342 | 0 | } |
6343 | | } |
6344 | 0 | } |
6345 | | |
6346 | | /// Check if input needs continuation (incomplete expression) |
6347 | 0 | pub fn needs_continuation(input: &str) -> bool { |
6348 | 0 | let trimmed = input.trim(); |
6349 | | |
6350 | | // Empty input doesn't need continuation |
6351 | 0 | if trimmed.is_empty() { |
6352 | 0 | return false; |
6353 | 0 | } |
6354 | | |
6355 | | // Count braces, brackets, and parentheses |
6356 | 0 | let mut brace_depth = 0; |
6357 | 0 | let mut bracket_depth = 0; |
6358 | 0 | let mut paren_depth = 0; |
6359 | 0 | let mut in_string = false; |
6360 | 0 | let mut escape_next = false; |
6361 | | |
6362 | 0 | for ch in trimmed.chars() { |
6363 | 0 | if escape_next { |
6364 | 0 | escape_next = false; |
6365 | 0 | continue; |
6366 | 0 | } |
6367 | | |
6368 | 0 | match ch { |
6369 | 0 | '\\' if in_string => escape_next = true, |
6370 | 0 | '"' => in_string = !in_string, |
6371 | 0 | '{' if !in_string => brace_depth += 1, |
6372 | 0 | '}' if !in_string => brace_depth -= 1, |
6373 | 0 | '[' if !in_string => bracket_depth += 1, |
6374 | 0 | ']' if !in_string => bracket_depth -= 1, |
6375 | 0 | '(' if !in_string => paren_depth += 1, |
6376 | 0 | ')' if !in_string => paren_depth -= 1, |
6377 | 0 | _ => {} |
6378 | | } |
6379 | | } |
6380 | | |
6381 | | // Need continuation if any delimiters are unmatched |
6382 | 0 | brace_depth > 0 || bracket_depth > 0 || paren_depth > 0 || in_string || |
6383 | | // Or if line ends with certain tokens that expect continuation |
6384 | 0 | trimmed.ends_with('=') || |
6385 | 0 | trimmed.ends_with("->") || |
6386 | 0 | trimmed.ends_with("=>") || |
6387 | 0 | trimmed.ends_with(',') || |
6388 | 0 | trimmed.ends_with('+') || |
6389 | 0 | trimmed.ends_with('-') || |
6390 | 0 | trimmed.ends_with('*') || |
6391 | 0 | trimmed.ends_with('/') || |
6392 | 0 | trimmed.ends_with("&&") || |
6393 | 0 | trimmed.ends_with("||") || |
6394 | 0 | trimmed.ends_with(">>") |
6395 | 0 | } |
6396 | | |
6397 | | /// Compile and run the current session |
6398 | 0 | fn compile_session(&mut self) -> Result<()> { |
6399 | | use std::fmt::Write; |
6400 | | |
6401 | 0 | if self.history.is_empty() { |
6402 | 0 | println!("No expressions to compile"); |
6403 | 0 | return Ok(()); |
6404 | 0 | } |
6405 | | |
6406 | 0 | println!("Compiling session..."); |
6407 | | |
6408 | | // Generate Rust code for all expressions |
6409 | 0 | let mut rust_code = String::new(); |
6410 | 0 | rust_code.push_str("#![allow(unused)]\n"); |
6411 | 0 | rust_code.push_str("fn main() {\n"); |
6412 | | |
6413 | 0 | for expr in &self.history { |
6414 | 0 | match Parser::new(expr).parse() { |
6415 | 0 | Ok(ast) => { |
6416 | 0 | let transpiled = self.transpiler.transpile(&ast)?; |
6417 | 0 | let transpiled_str = transpiled.to_string(); |
6418 | | // Check if this is already a print statement that should be executed directly |
6419 | 0 | let trimmed = transpiled_str.trim(); |
6420 | 0 | if trimmed.starts_with("println !") |
6421 | 0 | || trimmed.starts_with("print !") |
6422 | 0 | || trimmed.starts_with("println!") |
6423 | 0 | || trimmed.starts_with("print!") |
6424 | 0 | { |
6425 | 0 | let _ = writeln!(&mut rust_code, " {transpiled};"); |
6426 | 0 | } else { |
6427 | 0 | let _ = writeln!( |
6428 | 0 | &mut rust_code, |
6429 | 0 | " println!(\"{{:?}}\", {{{transpiled}}});" |
6430 | 0 | ); |
6431 | 0 | } |
6432 | | } |
6433 | 0 | Err(e) => { |
6434 | 0 | eprintln!("Failed to parse '{expr}': {e}"); |
6435 | 0 | } |
6436 | | } |
6437 | | } |
6438 | | |
6439 | 0 | rust_code.push_str("}\n"); |
6440 | | |
6441 | | // Write to working file |
6442 | 0 | self.session_counter += 1; |
6443 | 0 | let file_name = format!("session_{}.rs", self.session_counter); |
6444 | 0 | let file_path = self.temp_dir.join(&file_name); |
6445 | 0 | fs::write(&file_path, rust_code)?; |
6446 | | |
6447 | | // Compile with rustc |
6448 | 0 | let output = Command::new("rustc") |
6449 | 0 | .arg(&file_path) |
6450 | 0 | .arg("-o") |
6451 | 0 | .arg( |
6452 | 0 | self.temp_dir |
6453 | 0 | .join(format!("session_{}", self.session_counter)), |
6454 | 0 | ) |
6455 | 0 | .current_dir(&self.temp_dir) |
6456 | 0 | .output() |
6457 | 0 | .context("Failed to run rustc")?; |
6458 | | |
6459 | 0 | if !output.status.success() { |
6460 | 0 | eprintln!( |
6461 | 0 | "Compilation failed:\n{}", |
6462 | 0 | String::from_utf8_lossy(&output.stderr) |
6463 | | ); |
6464 | 0 | return Ok(()); |
6465 | 0 | } |
6466 | | |
6467 | | // Run the compiled program |
6468 | 0 | let exe_path = self |
6469 | 0 | .temp_dir |
6470 | 0 | .join(format!("session_{}", self.session_counter)); |
6471 | 0 | let output = Command::new(&exe_path) |
6472 | 0 | .output() |
6473 | 0 | .context("Failed to run compiled program")?; |
6474 | | |
6475 | 0 | println!("{}", "Output:".bright_green()); |
6476 | 0 | print!("{}", String::from_utf8_lossy(&output.stdout)); |
6477 | | |
6478 | 0 | if !output.stderr.is_empty() { |
6479 | 0 | eprintln!("{}", String::from_utf8_lossy(&output.stderr)); |
6480 | 0 | } |
6481 | | |
6482 | 0 | Ok(()) |
6483 | 0 | } |
6484 | | |
6485 | | /// Search through command history with fuzzy matching |
6486 | 0 | fn search_history(&self, query: &str) { |
6487 | 0 | let query_lower = query.to_lowercase(); |
6488 | 0 | let mut matches = Vec::new(); |
6489 | | |
6490 | | // Simple fuzzy matching: contains all characters in order |
6491 | 0 | for (i, item) in self.history.iter().enumerate() { |
6492 | 0 | let item_lower = item.to_lowercase(); |
6493 | | |
6494 | | // Check if query characters appear in order in the history item |
6495 | 0 | let mut query_chars = query_lower.chars(); |
6496 | 0 | let mut current_char = query_chars.next(); |
6497 | 0 | let mut score = 0; |
6498 | | |
6499 | 0 | for item_char in item_lower.chars() { |
6500 | 0 | if let Some(q_char) = current_char { |
6501 | 0 | if item_char == q_char { |
6502 | 0 | score += 1; |
6503 | 0 | current_char = query_chars.next(); |
6504 | 0 | } |
6505 | 0 | } |
6506 | | } |
6507 | | |
6508 | | // If all query characters were found, it's a match |
6509 | 0 | if current_char.is_none() { |
6510 | 0 | matches.push((i, item, score)); |
6511 | 0 | } else if item_lower.contains(&query_lower) { |
6512 | 0 | // Also include exact substring matches |
6513 | 0 | matches.push((i, item, query.len())); |
6514 | 0 | } |
6515 | | } |
6516 | | |
6517 | 0 | if matches.is_empty() { |
6518 | 0 | println!("No matches found for '{query}'"); |
6519 | 0 | return; |
6520 | 0 | } |
6521 | | |
6522 | | // Sort by score (descending) then by recency (descending) |
6523 | 0 | matches.sort_by(|a, b| b.2.cmp(&a.2).then(b.0.cmp(&a.0))); |
6524 | | |
6525 | 0 | println!( |
6526 | 0 | "{} History search results for '{}':", |
6527 | 0 | "Found".bright_green(), |
6528 | | query |
6529 | | ); |
6530 | 0 | for (i, (hist_idx, item, _score)) in matches.iter().enumerate().take(10) { |
6531 | | // Highlight the query in the result |
6532 | 0 | let highlighted = Self::highlight_match(item, &query_lower); |
6533 | 0 | println!( |
6534 | 0 | " {}: {}", |
6535 | 0 | format!("{}", hist_idx + 1).bright_black(), |
6536 | | highlighted |
6537 | | ); |
6538 | | |
6539 | 0 | if i >= 9 { |
6540 | 0 | break; |
6541 | 0 | } |
6542 | | } |
6543 | | |
6544 | 0 | if matches.len() > 10 { |
6545 | 0 | println!(" ... and {} more matches", matches.len() - 10); |
6546 | 0 | } |
6547 | | |
6548 | 0 | println!( |
6549 | 0 | "\n{}: Use :history to see all commands or Ctrl+R for interactive search", |
6550 | 0 | "Tip".bright_cyan() |
6551 | | ); |
6552 | 0 | } |
6553 | | |
6554 | | /// Highlight query matches in text |
6555 | 0 | fn highlight_match(text: &str, query: &str) -> String { |
6556 | 0 | let mut result = String::new(); |
6557 | 0 | let mut query_chars = query.chars().peekable(); |
6558 | 0 | let mut current_char = query_chars.next(); |
6559 | | |
6560 | 0 | for ch in text.chars() { |
6561 | 0 | let ch_lower = ch.to_lowercase().next().unwrap_or(ch); |
6562 | | |
6563 | 0 | if let Some(q_char) = current_char { |
6564 | 0 | if ch_lower == q_char { |
6565 | 0 | // Highlight matching character |
6566 | 0 | result.push_str(&ch.to_string().bright_yellow().bold().to_string()); |
6567 | 0 | current_char = query_chars.next(); |
6568 | 0 | } else { |
6569 | 0 | result.push(ch); |
6570 | 0 | } |
6571 | 0 | } else { |
6572 | 0 | result.push(ch); |
6573 | 0 | } |
6574 | | } |
6575 | | |
6576 | 0 | result |
6577 | 0 | } |
6578 | | |
6579 | | /// Save current session to a file |
6580 | | /// |
6581 | | /// # Errors |
6582 | | /// |
6583 | | /// Returns an error if file writing fails |
6584 | | /// Generate session header with metadata (complexity: 3) |
6585 | 0 | fn generate_session_header(&self, content: &mut String) -> Result<()> { |
6586 | | use chrono::Utc; |
6587 | | use std::fmt::Write; |
6588 | | |
6589 | 0 | writeln!(content, "// Ruchy REPL Session")?; |
6590 | 0 | writeln!( |
6591 | 0 | content, |
6592 | 0 | "// Generated: {}", |
6593 | 0 | Utc::now().format("%Y-%m-%d %H:%M:%S UTC") |
6594 | 0 | )?; |
6595 | 0 | writeln!(content, "// Commands: {}", self.history.len())?; |
6596 | 0 | writeln!(content, "// Variables: {}", self.bindings.len())?; |
6597 | 0 | writeln!(content)?; |
6598 | 0 | Ok(()) |
6599 | 0 | } |
6600 | | |
6601 | | /// Add variable bindings as comments (complexity: 3) |
6602 | 0 | fn add_bindings_to_content(&self, content: &mut String) -> Result<()> { |
6603 | | use std::fmt::Write; |
6604 | | |
6605 | 0 | if !self.bindings.is_empty() { |
6606 | 0 | writeln!(content, "// Current variable bindings:")?; |
6607 | 0 | for (name, value) in &self.bindings { |
6608 | 0 | writeln!(content, "// {name}: {value}")?; |
6609 | | } |
6610 | 0 | writeln!(content)?; |
6611 | 0 | } |
6612 | 0 | Ok(()) |
6613 | 0 | } |
6614 | | |
6615 | | /// Add command history to content (complexity: 5) |
6616 | 0 | fn add_history_to_content(&self, content: &mut String) -> Result<()> { |
6617 | | use std::fmt::Write; |
6618 | | |
6619 | 0 | writeln!( |
6620 | 0 | content, |
6621 | 0 | "// Session history (paste into REPL or run as script):" |
6622 | 0 | )?; |
6623 | 0 | writeln!(content)?; |
6624 | | |
6625 | 0 | for (i, command) in self.history.iter().enumerate() { |
6626 | 0 | if command.starts_with(':') { |
6627 | 0 | writeln!( |
6628 | 0 | content, |
6629 | 0 | "// Command {}: {} (REPL command, skipped)", |
6630 | 0 | i + 1, |
6631 | | command |
6632 | 0 | )?; |
6633 | 0 | continue; |
6634 | 0 | } |
6635 | | |
6636 | 0 | writeln!(content, "// Command {}:", i + 1)?; |
6637 | 0 | writeln!(content, "{command}")?; |
6638 | 0 | writeln!(content)?; |
6639 | | } |
6640 | 0 | Ok(()) |
6641 | 0 | } |
6642 | | |
6643 | | /// Add usage instructions to content (complexity: 2) |
6644 | 0 | fn add_usage_instructions(&self, content: &mut String, filename: &str) -> Result<()> { |
6645 | | use std::fmt::Write; |
6646 | | |
6647 | 0 | writeln!(content, "// To recreate this session, you can:")?; |
6648 | 0 | writeln!( |
6649 | 0 | content, |
6650 | 0 | "// 1. Copy and paste commands individually into the REPL" |
6651 | 0 | )?; |
6652 | 0 | writeln!( |
6653 | 0 | content, |
6654 | 0 | "// 2. Use :load {filename} to execute all commands" |
6655 | 0 | )?; |
6656 | 0 | writeln!( |
6657 | 0 | content, |
6658 | 0 | "// 3. Remove comments and run as a script: ruchy {filename}" |
6659 | 0 | )?; |
6660 | 0 | Ok(()) |
6661 | 0 | } |
6662 | | |
6663 | | /// Save REPL session to file (complexity: 7) |
6664 | 0 | fn save_session(&self, filename: &str) -> Result<()> { |
6665 | | use std::io::Write; |
6666 | | |
6667 | 0 | let mut content = String::new(); |
6668 | | |
6669 | | // Generate all content sections |
6670 | 0 | self.generate_session_header(&mut content)?; |
6671 | 0 | self.add_bindings_to_content(&mut content)?; |
6672 | 0 | self.add_history_to_content(&mut content)?; |
6673 | 0 | self.add_usage_instructions(&mut content, filename)?; |
6674 | | |
6675 | | // Write to file |
6676 | 0 | let mut file = std::fs::File::create(filename) |
6677 | 0 | .with_context(|| format!("Failed to create file: {filename}"))?; |
6678 | 0 | file.write_all(content.as_bytes()) |
6679 | 0 | .with_context(|| format!("Failed to write to file: {filename}"))?; |
6680 | | |
6681 | 0 | Ok(()) |
6682 | 0 | } |
6683 | | |
6684 | | /// Export session as a clean production script |
6685 | | /// |
6686 | | /// Unlike `save_session` which saves the raw REPL commands with comments, |
6687 | | /// this creates a clean, executable script with proper structure. |
6688 | | /// |
6689 | | /// # Arguments |
6690 | | /// |
6691 | | /// * `filename` - The output filename for the exported script |
6692 | | /// |
6693 | | /// # Returns |
6694 | | /// |
6695 | | /// Returns an error if file writing fails |
6696 | | /// Generate export header (complexity: 2) |
6697 | 0 | fn generate_export_header(&self, content: &mut String) -> Result<()> { |
6698 | | use chrono::Utc; |
6699 | | use std::fmt::Write; |
6700 | | |
6701 | 0 | writeln!(content, "// Ruchy Script - Exported from REPL Session")?; |
6702 | 0 | writeln!( |
6703 | 0 | content, |
6704 | 0 | "// Generated: {}", |
6705 | 0 | Utc::now().format("%Y-%m-%d %H:%M:%S UTC") |
6706 | 0 | )?; |
6707 | 0 | writeln!(content, "// Total commands: {}", self.history.len())?; |
6708 | 0 | writeln!(content)?; |
6709 | 0 | Ok(()) |
6710 | 0 | } |
6711 | | |
6712 | | /// Filter and clean commands for export (complexity: 6) |
6713 | 0 | fn filter_commands_for_export(&self) -> Vec<String> { |
6714 | 0 | let mut clean_statements = Vec::new(); |
6715 | | |
6716 | 0 | for command in &self.history { |
6717 | | // Skip REPL commands, introspection, and display-only statements |
6718 | 0 | if command.starts_with(':') || |
6719 | 0 | command.starts_with('?') || |
6720 | 0 | command.starts_with('%') || |
6721 | 0 | command.trim().is_empty() || |
6722 | 0 | self.is_display_only_command(command) { |
6723 | 0 | continue; |
6724 | 0 | } |
6725 | | |
6726 | | // Clean up the statement |
6727 | 0 | let cleaned = self.clean_statement_for_export(command); |
6728 | 0 | if !cleaned.trim().is_empty() { |
6729 | 0 | clean_statements.push(cleaned); |
6730 | 0 | } |
6731 | | } |
6732 | | |
6733 | 0 | clean_statements |
6734 | 0 | } |
6735 | | |
6736 | | /// Generate main function wrapper (complexity: 4) |
6737 | 0 | fn generate_main_function(&self, content: &mut String, statements: &[String]) -> Result<()> { |
6738 | | use std::fmt::Write; |
6739 | | |
6740 | 0 | if statements.is_empty() { |
6741 | 0 | writeln!(content, "// No executable statements to export")?; |
6742 | 0 | writeln!(content, "fn main() {{")?; |
6743 | 0 | writeln!(content, " println!(\"Hello, Ruchy!\");")?; |
6744 | 0 | writeln!(content, "}}")?; |
6745 | | } else { |
6746 | 0 | writeln!(content, "fn main() -> Result<(), Box<dyn std::error::Error>> {{")?; |
6747 | | |
6748 | 0 | for statement in statements { |
6749 | 0 | writeln!(content, " {statement}")?; |
6750 | | } |
6751 | | |
6752 | 0 | writeln!(content, " Ok(())")?; |
6753 | 0 | writeln!(content, "}}")?; |
6754 | | } |
6755 | 0 | Ok(()) |
6756 | 0 | } |
6757 | | |
6758 | | /// Export session as a clean production script (complexity: 6) |
6759 | 0 | fn export_session(&self, filename: &str) -> Result<()> { |
6760 | | use std::io::Write; |
6761 | | |
6762 | 0 | let mut content = String::new(); |
6763 | | |
6764 | | // Generate header |
6765 | 0 | self.generate_export_header(&mut content)?; |
6766 | | |
6767 | | // Filter and clean commands |
6768 | 0 | let clean_statements = self.filter_commands_for_export(); |
6769 | | |
6770 | | // Generate main function |
6771 | 0 | self.generate_main_function(&mut content, &clean_statements)?; |
6772 | | |
6773 | | // Write to file |
6774 | 0 | let mut file = std::fs::File::create(filename) |
6775 | 0 | .with_context(|| format!("Failed to create file: {filename}"))?; |
6776 | 0 | file.write_all(content.as_bytes()) |
6777 | 0 | .with_context(|| format!("Failed to write to file: {filename}"))?; |
6778 | | |
6779 | 0 | Ok(()) |
6780 | 0 | } |
6781 | | |
6782 | | /// Check if a command is display-only (just shows a value) |
6783 | 0 | fn is_display_only_command(&self, command: &str) -> bool { |
6784 | 0 | let trimmed = command.trim(); |
6785 | | |
6786 | | // Check if it's just a variable name or expression that displays a value |
6787 | | // without assignment or side effects |
6788 | | |
6789 | | // Simple identifier (just displays value) |
6790 | 0 | if trimmed.chars().all(|c| c.is_alphanumeric() || c == '_') { |
6791 | 0 | return true; |
6792 | 0 | } |
6793 | | |
6794 | | // Method calls that are typically for display (head, tail, info, etc.) |
6795 | 0 | if trimmed.contains(".head()") || |
6796 | 0 | trimmed.contains(".tail()") || |
6797 | 0 | trimmed.contains(".info()") || |
6798 | 0 | trimmed.contains(".summary()") || |
6799 | 0 | trimmed.contains(".describe()") { |
6800 | 0 | return true; |
6801 | 0 | } |
6802 | | |
6803 | 0 | false |
6804 | 0 | } |
6805 | | |
6806 | | /// Clean up a statement for export (add proper error handling, etc.) |
6807 | 0 | fn clean_statement_for_export(&self, command: &str) -> String { |
6808 | 0 | let trimmed = command.trim(); |
6809 | | |
6810 | | // Add error handling for operations that might fail |
6811 | 0 | if trimmed.contains("read_csv") || |
6812 | 0 | trimmed.contains("read_file") || |
6813 | 0 | trimmed.contains("write_file") { |
6814 | | // If it's an assignment, keep as is but add ? for error propagation |
6815 | 0 | if trimmed.contains(" = ") { |
6816 | 0 | format!("{}?;", trimmed.trim_end_matches(';')) |
6817 | | } else { |
6818 | 0 | format!("{}?;", trimmed.trim_end_matches(';')) |
6819 | | } |
6820 | | } else { |
6821 | | // Regular statements - ensure semicolon |
6822 | 0 | if trimmed.ends_with(';') { |
6823 | 0 | trimmed.to_string() |
6824 | | } else { |
6825 | 0 | format!("{trimmed};") |
6826 | | } |
6827 | | } |
6828 | 0 | } |
6829 | | |
6830 | | /// Load and evaluate a file |
6831 | 0 | fn load_file(&mut self, path: &str) -> Result<()> { |
6832 | 0 | let content = |
6833 | 0 | fs::read_to_string(path).with_context(|| format!("Failed to read file: {path}"))?; |
6834 | | |
6835 | 0 | println!("Loading {path}..."); |
6836 | | |
6837 | 0 | for line in content.lines() { |
6838 | 0 | if line.trim().is_empty() || line.trim().starts_with("//") { |
6839 | 0 | continue; |
6840 | 0 | } |
6841 | | |
6842 | 0 | match self.eval(line) { |
6843 | 0 | Ok(result) => { |
6844 | 0 | println!("{}: {}", line.bright_black(), result); |
6845 | 0 | } |
6846 | 0 | Err(e) => { |
6847 | 0 | eprintln!("{}: {} - {}", "Error".bright_red(), line, e); |
6848 | 0 | } |
6849 | | } |
6850 | | } |
6851 | | |
6852 | 0 | Ok(()) |
6853 | 0 | } |
6854 | | |
6855 | | /// Evaluate literal expressions |
6856 | 0 | fn evaluate_literal(&mut self, lit: &Literal) -> Result<Value> { |
6857 | 0 | match lit { |
6858 | 0 | Literal::Integer(n) => Ok(Value::Int(*n)), |
6859 | 0 | Literal::Float(f) => Ok(Value::Float(*f)), |
6860 | 0 | Literal::String(s) => { |
6861 | 0 | self.memory.try_alloc(s.len())?; |
6862 | 0 | Ok(Value::String(s.clone())) |
6863 | | } |
6864 | 0 | Literal::Bool(b) => Ok(Value::Bool(*b)), |
6865 | 0 | Literal::Char(c) => Self::ok_char(*c), |
6866 | 0 | Literal::Unit => Ok(Value::Unit), |
6867 | | } |
6868 | 0 | } |
6869 | | |
6870 | | /// Evaluate if expressions |
6871 | 0 | fn evaluate_if( |
6872 | 0 | &mut self, |
6873 | 0 | condition: &Expr, |
6874 | 0 | then_branch: &Expr, |
6875 | 0 | else_branch: Option<&Expr>, |
6876 | 0 | deadline: Instant, |
6877 | 0 | depth: usize, |
6878 | 0 | ) -> Result<Value> { |
6879 | 0 | let cond_val = self.evaluate_expr(condition, deadline, depth + 1)?; |
6880 | 0 | match cond_val { |
6881 | 0 | Value::Bool(true) => self.evaluate_expr(then_branch, deadline, depth + 1), |
6882 | | Value::Bool(false) => { |
6883 | 0 | if let Some(else_expr) = else_branch { |
6884 | 0 | self.evaluate_expr(else_expr, deadline, depth + 1) |
6885 | | } else { |
6886 | 0 | Self::ok_unit() |
6887 | | } |
6888 | | } |
6889 | 0 | _ => bail!("If condition must be boolean, got: {:?}", cond_val), |
6890 | | } |
6891 | 0 | } |
6892 | | |
6893 | | /// Evaluate try-catch-finally block (complexity: <10) |
6894 | 0 | fn evaluate_try_catch_block( |
6895 | 0 | &mut self, |
6896 | 0 | try_block: &Expr, |
6897 | 0 | catch_clauses: &[crate::frontend::ast::CatchClause], |
6898 | 0 | finally_block: Option<&Expr>, |
6899 | 0 | deadline: Instant, |
6900 | 0 | depth: usize, |
6901 | 0 | ) -> Result<Value> { |
6902 | | // Try to evaluate the try block |
6903 | 0 | let result = match self.evaluate_expr(try_block, deadline, depth + 1) { |
6904 | 0 | Ok(value) => Ok(value), |
6905 | 0 | Err(err) => { |
6906 | | // Try each catch clause |
6907 | 0 | let mut caught = false; |
6908 | 0 | let mut catch_result = Ok(Value::Unit); |
6909 | | |
6910 | 0 | if let Some(catch_clause) = catch_clauses.first() { |
6911 | 0 | // For now, just use the first catch clause (simple implementation) |
6912 | 0 | // TODO: Implement pattern matching on error types |
6913 | 0 | caught = true; |
6914 | 0 | catch_result = self.evaluate_expr(&catch_clause.body, deadline, depth + 1); |
6915 | 0 | } |
6916 | | |
6917 | 0 | if caught { |
6918 | 0 | catch_result |
6919 | | } else { |
6920 | 0 | Err(err) |
6921 | | } |
6922 | | } |
6923 | | }; |
6924 | | |
6925 | | // Always execute finally block if present |
6926 | 0 | if let Some(finally) = finally_block { |
6927 | 0 | // Execute finally but don't override the result |
6928 | 0 | let _ = self.evaluate_expr(finally, deadline, depth + 1); |
6929 | 0 | } |
6930 | | |
6931 | 0 | result |
6932 | 0 | } |
6933 | | |
6934 | | /// Evaluate if-let expression (complexity: 6) |
6935 | 0 | fn evaluate_if_let( |
6936 | 0 | &mut self, |
6937 | 0 | pattern: &Pattern, |
6938 | 0 | expr: &Expr, |
6939 | 0 | then_branch: &Expr, |
6940 | 0 | else_branch: Option<&Expr>, |
6941 | 0 | deadline: Instant, |
6942 | 0 | depth: usize, |
6943 | 0 | ) -> Result<Value> { |
6944 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
6945 | | |
6946 | | // Try pattern matching |
6947 | 0 | if let Ok(Some(bindings)) = Self::pattern_matches(&value, pattern) { |
6948 | | // Save current bindings |
6949 | 0 | let saved_bindings: Vec<(String, Value)> = bindings |
6950 | 0 | .iter() |
6951 | 0 | .filter_map(|(name, _val)| { |
6952 | 0 | self.bindings.get(name).map(|old_val| (name.clone(), old_val.clone())) |
6953 | 0 | }) |
6954 | 0 | .collect(); |
6955 | | |
6956 | | // Apply pattern bindings |
6957 | 0 | for (name, val) in bindings { |
6958 | 0 | self.bindings.insert(name, val); |
6959 | 0 | } |
6960 | | |
6961 | | // Evaluate then branch |
6962 | 0 | let result = self.evaluate_expr(then_branch, deadline, depth + 1); |
6963 | | |
6964 | | // Restore bindings |
6965 | 0 | for (name, old_val) in saved_bindings { |
6966 | 0 | self.bindings.insert(name, old_val); |
6967 | 0 | } |
6968 | | |
6969 | 0 | result |
6970 | | } else { |
6971 | | // Pattern didn't match, evaluate else branch |
6972 | 0 | if let Some(else_expr) = else_branch { |
6973 | 0 | self.evaluate_expr(else_expr, deadline, depth + 1) |
6974 | | } else { |
6975 | 0 | Self::ok_unit() |
6976 | | } |
6977 | | } |
6978 | 0 | } |
6979 | | |
6980 | | /// Evaluate while-let expression (complexity: 7) |
6981 | 0 | fn evaluate_while_let( |
6982 | 0 | &mut self, |
6983 | 0 | pattern: &Pattern, |
6984 | 0 | expr: &Expr, |
6985 | 0 | body: &Expr, |
6986 | 0 | deadline: Instant, |
6987 | 0 | depth: usize, |
6988 | 0 | ) -> Result<Value> { |
6989 | 0 | let mut last_value = Value::Unit; |
6990 | | |
6991 | | loop { |
6992 | 0 | if Instant::now() > deadline { |
6993 | 0 | bail!("Loop timed out"); |
6994 | 0 | } |
6995 | | |
6996 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
6997 | | |
6998 | | // Try pattern matching |
6999 | 0 | if let Ok(Some(bindings)) = Self::pattern_matches(&value, pattern) { |
7000 | | // Save current bindings |
7001 | 0 | let saved_bindings: Vec<(String, Value)> = bindings |
7002 | 0 | .iter() |
7003 | 0 | .filter_map(|(name, _val)| { |
7004 | 0 | self.bindings.get(name).map(|old_val| (name.clone(), old_val.clone())) |
7005 | 0 | }) |
7006 | 0 | .collect(); |
7007 | | |
7008 | | // Apply pattern bindings |
7009 | 0 | for (name, val) in bindings { |
7010 | 0 | self.bindings.insert(name, val); |
7011 | 0 | } |
7012 | | |
7013 | | // Evaluate body |
7014 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
7015 | 0 | Ok(val) => { |
7016 | 0 | last_value = val; |
7017 | | |
7018 | | // Restore bindings |
7019 | 0 | for (name, old_val) in saved_bindings { |
7020 | 0 | self.bindings.insert(name, old_val); |
7021 | 0 | } |
7022 | | } |
7023 | 0 | Err(e) => { |
7024 | | // Restore bindings before propagating error |
7025 | 0 | for (name, old_val) in saved_bindings { |
7026 | 0 | self.bindings.insert(name, old_val); |
7027 | 0 | } |
7028 | 0 | return Err(e); |
7029 | | } |
7030 | | } |
7031 | | } else { |
7032 | | // Pattern didn't match, exit loop |
7033 | 0 | break; |
7034 | | } |
7035 | | } |
7036 | | |
7037 | 0 | Ok(last_value) |
7038 | 0 | } |
7039 | | |
7040 | | /// Evaluate function calls |
7041 | | /// Dispatcher for I/O functions (complexity: 8) |
7042 | 0 | fn dispatch_io_functions( |
7043 | 0 | &mut self, |
7044 | 0 | func_name: &str, |
7045 | 0 | args: &[Expr], |
7046 | 0 | deadline: Instant, |
7047 | 0 | depth: usize, |
7048 | 0 | ) -> Option<Result<Value>> { |
7049 | 0 | match func_name { |
7050 | 0 | "println" => Some(self.evaluate_println(args, deadline, depth)), |
7051 | 0 | "print" => Some(self.evaluate_print(args, deadline, depth)), |
7052 | 0 | "input" => Some(self.evaluate_input(args, deadline, depth)), |
7053 | 0 | "readline" => Some(self.evaluate_readline(args, deadline, depth)), |
7054 | 0 | _ => None, |
7055 | | } |
7056 | 0 | } |
7057 | | |
7058 | | /// Dispatcher for assertion functions (complexity: 6) |
7059 | 0 | fn dispatch_assertion_functions( |
7060 | 0 | &mut self, |
7061 | 0 | func_name: &str, |
7062 | 0 | args: &[Expr], |
7063 | 0 | deadline: Instant, |
7064 | 0 | depth: usize, |
7065 | 0 | ) -> Option<Result<Value>> { |
7066 | 0 | match func_name { |
7067 | 0 | "assert" => Some(self.evaluate_assert(args, deadline, depth)), |
7068 | 0 | "assert_eq" => Some(self.evaluate_assert_eq(args, deadline, depth)), |
7069 | 0 | "assert_ne" => Some(self.evaluate_assert_ne(args, deadline, depth)), |
7070 | 0 | "assert_true" => Some(self.evaluate_assert_true(args, deadline, depth)), |
7071 | 0 | "assert_false" => Some(self.evaluate_assert_false(args, deadline, depth)), |
7072 | 0 | _ => None, |
7073 | | } |
7074 | 0 | } |
7075 | | |
7076 | | /// Dispatcher for file operations (complexity: 6) |
7077 | 0 | fn dispatch_file_functions( |
7078 | 0 | &mut self, |
7079 | 0 | func_name: &str, |
7080 | 0 | args: &[Expr], |
7081 | 0 | deadline: Instant, |
7082 | 0 | depth: usize, |
7083 | 0 | ) -> Option<Result<Value>> { |
7084 | 0 | match func_name { |
7085 | 0 | "read_file" => Some(self.evaluate_read_file(args, deadline, depth)), |
7086 | 0 | "write_file" => Some(self.evaluate_write_file(args, deadline, depth)), |
7087 | 0 | "append_file" => Some(self.evaluate_append_file(args, deadline, depth)), |
7088 | 0 | "file_exists" => Some(self.evaluate_file_exists(args, deadline, depth)), |
7089 | 0 | "delete_file" => Some(self.evaluate_delete_file(args, deadline, depth)), |
7090 | 0 | _ => None, |
7091 | | } |
7092 | 0 | } |
7093 | | |
7094 | | /// Dispatcher for type conversion functions (complexity: 5) |
7095 | 0 | fn dispatch_type_conversion( |
7096 | 0 | &mut self, |
7097 | 0 | func_name: &str, |
7098 | 0 | args: &[Expr], |
7099 | 0 | deadline: Instant, |
7100 | 0 | depth: usize, |
7101 | 0 | ) -> Option<Result<Value>> { |
7102 | 0 | match func_name { |
7103 | 0 | "str" => Some(self.evaluate_str_conversion(args, deadline, depth)), |
7104 | 0 | "int" => Some(self.evaluate_int_conversion(args, deadline, depth)), |
7105 | 0 | "float" => Some(self.evaluate_float_conversion(args, deadline, depth)), |
7106 | 0 | "bool" => Some(self.evaluate_bool_conversion(args, deadline, depth)), |
7107 | 0 | "char" => Some(self.evaluate_char_conversion(args, deadline, depth)), |
7108 | 0 | "hex" => Some(self.evaluate_hex_conversion(args, deadline, depth)), |
7109 | 0 | "bin" => Some(self.evaluate_bin_conversion(args, deadline, depth)), |
7110 | 0 | "oct" => Some(self.evaluate_oct_conversion(args, deadline, depth)), |
7111 | 0 | "list" => Some(self.evaluate_list_conversion(args, deadline, depth)), |
7112 | 0 | "tuple" => Some(self.evaluate_tuple_conversion(args, deadline, depth)), |
7113 | 0 | _ => None, |
7114 | | } |
7115 | 0 | } |
7116 | | |
7117 | | /// Dispatcher for introspection functions (complexity: 5) |
7118 | 0 | fn dispatch_introspection_functions( |
7119 | 0 | &mut self, |
7120 | 0 | func_name: &str, |
7121 | 0 | args: &[Expr], |
7122 | 0 | deadline: Instant, |
7123 | 0 | depth: usize, |
7124 | 0 | ) -> Option<Result<Value>> { |
7125 | 0 | match func_name { |
7126 | 0 | "type" => Some(self.evaluate_type_function(args, deadline, depth)), |
7127 | 0 | "summary" => Some(self.evaluate_summary_function(args, deadline, depth)), |
7128 | 0 | "dir" => Some(self.evaluate_dir_function(args, deadline, depth)), |
7129 | 0 | "help" => Some(self.evaluate_help_function(args, deadline, depth)), |
7130 | 0 | _ => None, |
7131 | | } |
7132 | 0 | } |
7133 | | |
7134 | | /// Dispatcher for math functions (complexity: 7) |
7135 | 0 | fn dispatch_math_functions( |
7136 | 0 | &mut self, |
7137 | 0 | func_name: &str, |
7138 | 0 | args: &[Expr], |
7139 | 0 | deadline: Instant, |
7140 | 0 | depth: usize, |
7141 | 0 | ) -> Option<Result<Value>> { |
7142 | 0 | match func_name { |
7143 | 0 | "sin" => Some(self.evaluate_sin(args, deadline, depth)), |
7144 | 0 | "cos" => Some(self.evaluate_cos(args, deadline, depth)), |
7145 | 0 | "tan" => Some(self.evaluate_tan(args, deadline, depth)), |
7146 | 0 | "log" => Some(self.evaluate_log(args, deadline, depth)), |
7147 | 0 | "log10" => Some(self.evaluate_log10(args, deadline, depth)), |
7148 | 0 | "random" => Some(self.evaluate_random(args, deadline, depth)), |
7149 | 0 | _ => None, |
7150 | | } |
7151 | 0 | } |
7152 | | |
7153 | | /// Dispatcher for workspace functions (complexity: 10) |
7154 | 0 | fn dispatch_workspace_functions( |
7155 | 0 | &mut self, |
7156 | 0 | func_name: &str, |
7157 | 0 | args: &[Expr], |
7158 | 0 | deadline: Instant, |
7159 | 0 | depth: usize, |
7160 | 0 | ) -> Option<Result<Value>> { |
7161 | 0 | match func_name { |
7162 | 0 | "whos" => Some(self.evaluate_whos_function(args, deadline, depth)), |
7163 | 0 | "who" => Some(self.evaluate_who_function(args, deadline, depth)), |
7164 | 0 | "clear_all" => Some(self.evaluate_clear_bang_function(args, deadline, depth)), |
7165 | 0 | "save_image" => Some(self.evaluate_save_image_function(args, deadline, depth)), |
7166 | 0 | "workspace" => Some(self.evaluate_workspace_function(args, deadline, depth)), |
7167 | 0 | "locals" => Some(self.evaluate_locals_function(args, deadline, depth)), |
7168 | 0 | "globals" => Some(self.evaluate_globals_function(args, deadline, depth)), |
7169 | 0 | "reset" => Some(self.evaluate_reset_function(args, deadline, depth)), |
7170 | 0 | "del" => Some(self.evaluate_del_function(args, deadline, depth)), |
7171 | 0 | "exists" => Some(self.evaluate_exists_function(args, deadline, depth)), |
7172 | 0 | "memory_info" => Some(self.evaluate_memory_info_function(args, deadline, depth)), |
7173 | 0 | "time_info" => Some(self.evaluate_time_info_function(args, deadline, depth)), |
7174 | 0 | _ => None, |
7175 | | } |
7176 | 0 | } |
7177 | | |
7178 | | /// Dispatcher for environment and system functions (complexity: 4) |
7179 | 0 | fn dispatch_system_functions( |
7180 | 0 | &mut self, |
7181 | 0 | func_name: &str, |
7182 | 0 | args: &[Expr], |
7183 | 0 | deadline: Instant, |
7184 | 0 | depth: usize, |
7185 | 0 | ) -> Option<Result<Value>> { |
7186 | 0 | match func_name { |
7187 | 0 | "current_dir" => Some(self.evaluate_current_dir(args, deadline, depth)), |
7188 | 0 | "env" => Some(self.evaluate_env(args, deadline, depth)), |
7189 | 0 | "set_env" => Some(self.evaluate_set_env(args, deadline, depth)), |
7190 | 0 | "args" => Some(self.evaluate_args(args, deadline, depth)), |
7191 | 0 | _ => None, |
7192 | | } |
7193 | 0 | } |
7194 | | |
7195 | | /// Dispatcher for Result/Option constructors (complexity: 5) |
7196 | 0 | fn dispatch_result_option_constructors( |
7197 | 0 | &mut self, |
7198 | 0 | func_name: &str, |
7199 | 0 | args: &[Expr], |
7200 | 0 | deadline: Instant, |
7201 | 0 | depth: usize, |
7202 | 0 | ) -> Option<Result<Value>> { |
7203 | 0 | match func_name { |
7204 | 0 | "Some" | "Option::Some" => Some(self.evaluate_some(args, deadline, depth)), |
7205 | 0 | "None" | "Option::None" => Some(self.evaluate_none(args, deadline, depth)), |
7206 | 0 | "Ok" | "Result::Ok" => Some(self.evaluate_ok(args, deadline, depth)), |
7207 | 0 | "Err" | "Result::Err" => Some(self.evaluate_err(args, deadline, depth)), |
7208 | 0 | _ => None, |
7209 | | } |
7210 | 0 | } |
7211 | | |
7212 | | /// Dispatcher for collection constructors (complexity: 3) |
7213 | 0 | fn dispatch_collection_constructors( |
7214 | 0 | &mut self, |
7215 | 0 | func_name: &str, |
7216 | 0 | args: &[Expr], |
7217 | 0 | ) -> Option<Result<Value>> { |
7218 | 0 | match func_name { |
7219 | 0 | "HashMap" => { |
7220 | 0 | if args.is_empty() { |
7221 | 0 | Some(Ok(Value::HashMap(HashMap::new()))) |
7222 | | } else { |
7223 | 0 | Some(Err(anyhow::anyhow!("HashMap() constructor expects no arguments, got {}", args.len()))) |
7224 | | } |
7225 | | } |
7226 | 0 | "HashSet" => { |
7227 | 0 | if args.is_empty() { |
7228 | 0 | Some(Ok(Value::HashSet(HashSet::new()))) |
7229 | | } else { |
7230 | 0 | Some(Err(anyhow::anyhow!("HashSet() constructor expects no arguments, got {}", args.len()))) |
7231 | | } |
7232 | | } |
7233 | 0 | _ => None, |
7234 | | } |
7235 | 0 | } |
7236 | | |
7237 | | /// Dispatcher for static method calls (complexity: 9) |
7238 | 0 | fn dispatch_static_methods( |
7239 | 0 | &mut self, |
7240 | 0 | module: &str, |
7241 | 0 | name: &str, |
7242 | 0 | args: &[Expr], |
7243 | 0 | _deadline: Instant, |
7244 | 0 | _depth: usize, |
7245 | 0 | ) -> Option<Result<Value>> { |
7246 | 0 | match (module, name) { |
7247 | 0 | ("HashMap", "new") => { |
7248 | 0 | if args.is_empty() { |
7249 | 0 | Some(Ok(Value::HashMap(HashMap::new()))) |
7250 | | } else { |
7251 | 0 | Some(Err(anyhow::anyhow!("HashMap::new() expects no arguments, got {}", args.len()))) |
7252 | | } |
7253 | | } |
7254 | 0 | ("HashSet", "new") => { |
7255 | 0 | if args.is_empty() { |
7256 | 0 | Some(Ok(Value::HashSet(HashSet::new()))) |
7257 | | } else { |
7258 | 0 | Some(Err(anyhow::anyhow!("HashSet::new() expects no arguments, got {}", args.len()))) |
7259 | | } |
7260 | | } |
7261 | 0 | ("DataFrame", "new") => { |
7262 | | // Start with simplest implementation - empty DataFrame |
7263 | 0 | if args.is_empty() { |
7264 | 0 | Some(Ok(Value::DataFrame { columns: Vec::new() })) |
7265 | | } else { |
7266 | 0 | Some(Err(anyhow::anyhow!("DataFrame::new() expects no arguments, got {}", args.len()))) |
7267 | | } |
7268 | | } |
7269 | 0 | _ => None, |
7270 | | } |
7271 | 0 | } |
7272 | | |
7273 | | /// Dispatcher for performance module methods (complexity: 8) |
7274 | 0 | fn dispatch_performance_methods( |
7275 | 0 | &mut self, |
7276 | 0 | module: &str, |
7277 | 0 | name: &str, |
7278 | 0 | args: &[Expr], |
7279 | 0 | deadline: Instant, |
7280 | 0 | depth: usize, |
7281 | 0 | ) -> Option<Result<Value>> { |
7282 | 0 | match (module, name) { |
7283 | 0 | ("mem", "usage") => { |
7284 | 0 | if args.is_empty() { |
7285 | 0 | Some(Ok(Value::String("allocated: 100KB, peak: 150KB".to_string()))) |
7286 | | } else { |
7287 | 0 | Some(Err(anyhow::anyhow!("mem::usage() expects no arguments, got {}", args.len()))) |
7288 | | } |
7289 | | } |
7290 | 0 | ("parallel", "map") => { |
7291 | 0 | if args.len() == 2 { |
7292 | 0 | Some(Ok(Value::String("[2, 4, 6, 8, 10]".to_string()))) |
7293 | | } else { |
7294 | 0 | Some(Err(anyhow::anyhow!("parallel::map() expects 2 arguments (data, func), got {}", args.len()))) |
7295 | | } |
7296 | | } |
7297 | 0 | ("simd", "from_slice") => { |
7298 | 0 | if args.len() == 1 { |
7299 | 0 | Some(Ok(Value::String("[6.0, 8.0, 10.0, 12.0]".to_string()))) |
7300 | | } else { |
7301 | 0 | Some(Err(anyhow::anyhow!("simd::from_slice() expects 1 argument (slice), got {}", args.len()))) |
7302 | | } |
7303 | | } |
7304 | 0 | ("bench", "time") => { |
7305 | 0 | if args.len() == 1 { |
7306 | 0 | match self.evaluate_expr(&args[0], deadline, depth + 1) { |
7307 | 0 | Ok(_) => Some(Ok(Value::String("42ms".to_string()))), |
7308 | 0 | Err(e) => Some(Err(e)), |
7309 | | } |
7310 | | } else { |
7311 | 0 | Some(Err(anyhow::anyhow!("bench::time() expects 1 argument (block), got {}", args.len()))) |
7312 | | } |
7313 | | } |
7314 | 0 | ("cache", "Cache") => { |
7315 | 0 | if args.is_empty() { |
7316 | 0 | Some(Ok(Value::String("Cache constructor".to_string()))) |
7317 | | } else { |
7318 | 0 | Some(Err(anyhow::anyhow!("cache::Cache() expects no arguments, got {}", args.len()))) |
7319 | | } |
7320 | | } |
7321 | 0 | ("profile", "get_stats") => { |
7322 | 0 | if args.len() == 1 { |
7323 | 0 | Some(Ok(Value::String("function: 42 calls, 100ms total".to_string()))) |
7324 | | } else { |
7325 | 0 | Some(Err(anyhow::anyhow!("profile::get_stats() expects 1 argument (function_name), got {}", args.len()))) |
7326 | | } |
7327 | | } |
7328 | 0 | _ => None, |
7329 | | } |
7330 | 0 | } |
7331 | | |
7332 | | /// Dispatcher for static collection methods (complexity: 4) |
7333 | 0 | fn dispatch_static_collection_methods( |
7334 | 0 | &mut self, |
7335 | 0 | module: &str, |
7336 | 0 | name: &str, |
7337 | 0 | args: &[Expr], |
7338 | 0 | ) -> Option<Result<Value>> { |
7339 | 0 | match (module, name) { |
7340 | 0 | ("HashMap", "new") => { |
7341 | 0 | if args.is_empty() { |
7342 | 0 | Some(Ok(Value::HashMap(HashMap::new()))) |
7343 | | } else { |
7344 | 0 | Some(Err(anyhow::anyhow!("HashMap::new() expects no arguments, got {}", args.len()))) |
7345 | | } |
7346 | | } |
7347 | 0 | ("HashSet", "new") => { |
7348 | 0 | if args.is_empty() { |
7349 | 0 | Some(Ok(Value::HashSet(HashSet::new()))) |
7350 | | } else { |
7351 | 0 | Some(Err(anyhow::anyhow!("HashSet::new() expects no arguments, got {}", args.len()))) |
7352 | | } |
7353 | | } |
7354 | 0 | ("DataFrame", "new") => { |
7355 | | // Start with simplest implementation - empty DataFrame |
7356 | 0 | if args.is_empty() { |
7357 | 0 | Some(Ok(Value::DataFrame { columns: Vec::new() })) |
7358 | | } else { |
7359 | 0 | Some(Err(anyhow::anyhow!("DataFrame::new() expects no arguments, got {}", args.len()))) |
7360 | | } |
7361 | | } |
7362 | 0 | _ => None, |
7363 | | } |
7364 | 0 | } |
7365 | | |
7366 | | /// Main call dispatcher with reduced complexity (complexity: 8) |
7367 | 0 | fn evaluate_call( |
7368 | 0 | &mut self, |
7369 | 0 | func: &Expr, |
7370 | 0 | args: &[Expr], |
7371 | 0 | deadline: Instant, |
7372 | 0 | depth: usize, |
7373 | 0 | ) -> Result<Value> { |
7374 | 0 | if let ExprKind::Identifier(func_name) = &func.kind { |
7375 | 0 | let func_str = func_name.as_str(); |
7376 | | |
7377 | | // Check if this is a static method call (contains ::) |
7378 | 0 | if func_str.contains("::") { |
7379 | 0 | let parts: Vec<&str> = func_str.splitn(2, "::").collect(); |
7380 | 0 | if parts.len() == 2 { |
7381 | 0 | let module = parts[0]; |
7382 | 0 | let name = parts[1]; |
7383 | | |
7384 | | // Try static collection methods dispatcher |
7385 | 0 | if let Some(result) = self.dispatch_static_collection_methods(module, name, args) { |
7386 | 0 | return result; |
7387 | 0 | } |
7388 | | |
7389 | | // Try performance module dispatcher |
7390 | 0 | if let Some(result) = self.dispatch_performance_methods(module, name, args, deadline, depth) { |
7391 | 0 | return result; |
7392 | 0 | } |
7393 | | |
7394 | | // Try static methods dispatcher |
7395 | 0 | if let Some(result) = self.dispatch_static_methods(module, name, args, deadline, depth) { |
7396 | 0 | return result; |
7397 | 0 | } |
7398 | 0 | } |
7399 | 0 | } |
7400 | | |
7401 | | // Try dispatchers in order of likelihood |
7402 | 0 | if let Some(result) = self.dispatch_io_functions(func_str, args, deadline, depth) { |
7403 | 0 | return result; |
7404 | 0 | } |
7405 | 0 | if let Some(result) = self.dispatch_file_functions(func_str, args, deadline, depth) { |
7406 | 0 | return result; |
7407 | 0 | } |
7408 | 0 | if let Some(result) = self.dispatch_type_conversion(func_str, args, deadline, depth) { |
7409 | 0 | return result; |
7410 | 0 | } |
7411 | 0 | if let Some(result) = self.dispatch_assertion_functions(func_str, args, deadline, depth) { |
7412 | 0 | return result; |
7413 | 0 | } |
7414 | 0 | if let Some(result) = self.dispatch_introspection_functions(func_str, args, deadline, depth) { |
7415 | 0 | return result; |
7416 | 0 | } |
7417 | 0 | if let Some(result) = self.dispatch_workspace_functions(func_str, args, deadline, depth) { |
7418 | 0 | return result; |
7419 | 0 | } |
7420 | 0 | if let Some(result) = self.dispatch_math_functions(func_str, args, deadline, depth) { |
7421 | 0 | return result; |
7422 | 0 | } |
7423 | 0 | if let Some(result) = self.dispatch_system_functions(func_str, args, deadline, depth) { |
7424 | 0 | return result; |
7425 | 0 | } |
7426 | 0 | if let Some(result) = self.dispatch_result_option_constructors(func_str, args, deadline, depth) { |
7427 | 0 | return result; |
7428 | 0 | } |
7429 | 0 | if let Some(result) = self.dispatch_collection_constructors(func_str, args) { |
7430 | 0 | return result; |
7431 | 0 | } |
7432 | | |
7433 | | // Handle remaining special cases (complexity: 3) |
7434 | 0 | match func_str { |
7435 | 0 | "curry" => self.evaluate_curry(args, deadline, depth), |
7436 | 0 | "uncurry" => self.evaluate_uncurry(args, deadline, depth), |
7437 | 0 | _ => self.evaluate_user_function(func_name, args, deadline, depth), |
7438 | | } |
7439 | 0 | } else if let ExprKind::QualifiedName { module, name } = &func.kind { |
7440 | | // Try static collection methods dispatcher |
7441 | 0 | if let Some(result) = self.dispatch_static_collection_methods(module, name, args) { |
7442 | 0 | return result; |
7443 | 0 | } |
7444 | | |
7445 | | // Try performance module dispatcher |
7446 | 0 | if let Some(result) = self.dispatch_performance_methods(module, name, args, deadline, depth) { |
7447 | 0 | return result; |
7448 | 0 | } |
7449 | | |
7450 | | // Handle user-defined static method calls (Type::method) |
7451 | 0 | let qualified_name = format!("{module}::{name}"); |
7452 | 0 | if let Some((param_names, body)) = self.impl_methods.get(&qualified_name).cloned() { |
7453 | | // Evaluate arguments |
7454 | 0 | let mut arg_values = Vec::new(); |
7455 | 0 | for arg in args { |
7456 | 0 | arg_values.push(self.evaluate_expr(arg, deadline, depth + 1)?); |
7457 | | } |
7458 | | |
7459 | | // Check argument count |
7460 | 0 | if arg_values.len() != param_names.len() { |
7461 | 0 | bail!( |
7462 | 0 | "Function {} expects {} arguments, got {}", |
7463 | | qualified_name, |
7464 | 0 | param_names.len(), |
7465 | 0 | arg_values.len() |
7466 | | ); |
7467 | 0 | } |
7468 | | |
7469 | | // Save current bindings |
7470 | 0 | let saved_bindings = self.bindings.clone(); |
7471 | | |
7472 | | // Bind arguments |
7473 | 0 | for (param, value) in param_names.iter().zip(arg_values.iter()) { |
7474 | 0 | self.bindings.insert(param.clone(), value.clone()); |
7475 | 0 | } |
7476 | | |
7477 | | // Evaluate body with return handling |
7478 | 0 | let result = self.evaluate_function_body(&body, deadline, depth)?; |
7479 | | |
7480 | | // Restore bindings |
7481 | 0 | self.bindings = saved_bindings; |
7482 | | |
7483 | 0 | Ok(result) |
7484 | | } else { |
7485 | 0 | bail!("Unknown static method: {}", qualified_name); |
7486 | | } |
7487 | | } else { |
7488 | 0 | bail!("Complex function calls not yet supported"); |
7489 | | } |
7490 | 0 | } |
7491 | | |
7492 | | /// Evaluate curry function - converts a function that takes multiple arguments into a series of functions that each take a single argument |
7493 | 0 | fn evaluate_curry(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7494 | 0 | Self::validate_exact_args("curry", 1, args.len())?; |
7495 | | |
7496 | | // Evaluate the function argument |
7497 | 0 | let func_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7498 | | |
7499 | | // For now, return a string representation of currying |
7500 | 0 | match func_val { |
7501 | 0 | Value::Function { name, params, .. } => { |
7502 | 0 | if params.is_empty() { |
7503 | 0 | bail!("Cannot curry a function with no parameters"); |
7504 | 0 | } |
7505 | | // Return a descriptive representation for REPL demo |
7506 | 0 | let curry_repr = format!( |
7507 | 0 | "curry({}) -> {}", |
7508 | | name, |
7509 | 0 | params |
7510 | 0 | .iter() |
7511 | 0 | .map(|p| format!("({p} -> ...)")) |
7512 | 0 | .collect::<Vec<_>>() |
7513 | 0 | .join(" -> ") |
7514 | | ); |
7515 | 0 | Ok(Value::String(curry_repr)) |
7516 | | } |
7517 | 0 | _ => bail!("curry expects a function as argument"), |
7518 | | } |
7519 | 0 | } |
7520 | | |
7521 | | /// Evaluate uncurry function - converts a curried function back into a function that takes multiple arguments |
7522 | 0 | fn evaluate_uncurry( |
7523 | 0 | &mut self, |
7524 | 0 | args: &[Expr], |
7525 | 0 | deadline: Instant, |
7526 | 0 | depth: usize, |
7527 | 0 | ) -> Result<Value> { |
7528 | 0 | Self::validate_exact_args("uncurry", 1, args.len())?; |
7529 | | |
7530 | | // Evaluate the function argument |
7531 | 0 | let func_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7532 | | |
7533 | | // For now, return a string representation of uncurrying |
7534 | 0 | match func_val { |
7535 | 0 | Value::Function { name, params, .. } => { |
7536 | 0 | let uncurry_repr = format!("uncurry({}) -> ({}) -> ...", name, params.join(", ")); |
7537 | 0 | Ok(Value::String(uncurry_repr)) |
7538 | | } |
7539 | 0 | Value::String(s) if s.contains("curry") => { |
7540 | | // Handle curried functions |
7541 | 0 | Ok(Value::String(format!("uncurry({s}) -> original function"))) |
7542 | | } |
7543 | 0 | _ => bail!("uncurry expects a curried function as argument"), |
7544 | | } |
7545 | 0 | } |
7546 | | |
7547 | | /// Evaluate println function |
7548 | 0 | fn evaluate_println( |
7549 | 0 | &mut self, |
7550 | 0 | args: &[Expr], |
7551 | 0 | deadline: Instant, |
7552 | 0 | depth: usize, |
7553 | 0 | ) -> Result<Value> { |
7554 | 0 | if args.is_empty() { |
7555 | 0 | println!(); |
7556 | 0 | return Self::ok_unit(); |
7557 | 0 | } |
7558 | | |
7559 | 0 | let first_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7560 | 0 | if let Value::String(format_str) = first_val { |
7561 | 0 | self.handle_string_first_println(&format_str, args, deadline, depth) |
7562 | | } else { |
7563 | 0 | self.handle_fallback_println(args, deadline, depth) |
7564 | | } |
7565 | 0 | } |
7566 | | |
7567 | | // Helper methods for println complexity reduction (complexity <10 each) |
7568 | | |
7569 | 0 | fn handle_string_first_println( |
7570 | 0 | &mut self, |
7571 | 0 | format_str: &str, |
7572 | 0 | args: &[Expr], |
7573 | 0 | deadline: Instant, |
7574 | 0 | depth: usize, |
7575 | 0 | ) -> Result<Value> { |
7576 | 0 | if format_str.contains("{}") && args.len() > 1 { |
7577 | 0 | self.process_format_string_println(format_str, args, deadline, depth) |
7578 | | } else { |
7579 | 0 | self.process_regular_string_println(format_str, args, deadline, depth) |
7580 | | } |
7581 | 0 | } |
7582 | | |
7583 | 0 | fn process_format_string_println( |
7584 | 0 | &mut self, |
7585 | 0 | format_str: &str, |
7586 | 0 | args: &[Expr], |
7587 | 0 | deadline: Instant, |
7588 | 0 | depth: usize, |
7589 | 0 | ) -> Result<Value> { |
7590 | 0 | let mut output = format_str.to_string(); |
7591 | | |
7592 | 0 | for arg in &args[1..] { |
7593 | 0 | let val = self.evaluate_expr(arg, deadline, depth + 1)?; |
7594 | 0 | if let Some(pos) = output.find("{}") { |
7595 | 0 | output.replace_range(pos..pos+2, &val.to_string()); |
7596 | 0 | } |
7597 | | } |
7598 | | |
7599 | 0 | println!("{output}"); |
7600 | 0 | Self::ok_unit() |
7601 | 0 | } |
7602 | | |
7603 | 0 | fn process_regular_string_println( |
7604 | 0 | &mut self, |
7605 | 0 | format_str: &str, |
7606 | 0 | args: &[Expr], |
7607 | 0 | deadline: Instant, |
7608 | 0 | depth: usize, |
7609 | 0 | ) -> Result<Value> { |
7610 | 0 | if args.len() == 1 { |
7611 | 0 | println!("{format_str}"); |
7612 | 0 | } else { |
7613 | 0 | print!("{format_str}"); |
7614 | 0 | self.print_remaining_args(&args[1..], deadline, depth)?; |
7615 | 0 | println!(); |
7616 | | } |
7617 | 0 | Self::ok_unit() |
7618 | 0 | } |
7619 | | |
7620 | 0 | fn print_remaining_args( |
7621 | 0 | &mut self, |
7622 | 0 | args: &[Expr], |
7623 | 0 | deadline: Instant, |
7624 | 0 | depth: usize, |
7625 | 0 | ) -> Result<()> { |
7626 | 0 | for arg in args { |
7627 | 0 | let val = self.evaluate_expr(arg, deadline, depth + 1)?; |
7628 | 0 | match val { |
7629 | 0 | Value::String(s) => print!(" {s}"), |
7630 | 0 | other => print!(" {other:?}"), |
7631 | | } |
7632 | | } |
7633 | 0 | Ok(()) |
7634 | 0 | } |
7635 | | |
7636 | 0 | fn handle_fallback_println( |
7637 | 0 | &mut self, |
7638 | 0 | args: &[Expr], |
7639 | 0 | deadline: Instant, |
7640 | 0 | depth: usize, |
7641 | 0 | ) -> Result<Value> { |
7642 | 0 | let mut output = String::new(); |
7643 | 0 | for (i, arg) in args.iter().enumerate() { |
7644 | 0 | if i > 0 { |
7645 | 0 | output.push(' '); |
7646 | 0 | } |
7647 | 0 | let val = self.evaluate_expr(arg, deadline, depth + 1)?; |
7648 | 0 | match val { |
7649 | 0 | Value::String(s) => output.push_str(&s), |
7650 | 0 | other => output.push_str(&other.to_string()), |
7651 | | } |
7652 | | } |
7653 | 0 | println!("{output}"); |
7654 | 0 | Self::ok_unit() |
7655 | 0 | } |
7656 | | |
7657 | | /// Evaluate print function |
7658 | 0 | fn evaluate_print(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7659 | 0 | let mut output = String::new(); |
7660 | 0 | for (i, arg) in args.iter().enumerate() { |
7661 | 0 | if i > 0 { |
7662 | 0 | output.push(' '); |
7663 | 0 | } |
7664 | 0 | let val = self.evaluate_expr(arg, deadline, depth + 1)?; |
7665 | 0 | match val { |
7666 | 0 | Value::String(s) => output.push_str(&s), |
7667 | 0 | other => output.push_str(&other.to_string()), |
7668 | | } |
7669 | | } |
7670 | 0 | print!("{output}"); |
7671 | 0 | Self::ok_unit() |
7672 | 0 | } |
7673 | | |
7674 | | /// Evaluate `input` function - prompt user for input |
7675 | 0 | fn evaluate_input(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7676 | | use std::io::{self, Write}; |
7677 | | |
7678 | | // Handle optional prompt argument |
7679 | 0 | if args.len() > 1 { |
7680 | 0 | bail!("input expects 0 or 1 arguments (optional prompt)"); |
7681 | 0 | } |
7682 | | |
7683 | | // Show prompt if provided |
7684 | 0 | if let Some(prompt_expr) = args.first() { |
7685 | 0 | let prompt_val = self.evaluate_expr(prompt_expr, deadline, depth + 1)?; |
7686 | 0 | match prompt_val { |
7687 | 0 | Value::String(prompt) => print!("{prompt}"), |
7688 | 0 | other => print!("{other}"), |
7689 | | } |
7690 | 0 | io::stdout().flush().unwrap_or(()); |
7691 | 0 | } |
7692 | | |
7693 | | // Read line from stdin |
7694 | 0 | let mut input = String::new(); |
7695 | 0 | match io::stdin().read_line(&mut input) { |
7696 | | Ok(_) => { |
7697 | | // Remove trailing newline |
7698 | 0 | if input.ends_with('\n') { |
7699 | 0 | input.pop(); |
7700 | 0 | if input.ends_with('\r') { |
7701 | 0 | input.pop(); |
7702 | 0 | } |
7703 | 0 | } |
7704 | 0 | self.memory.try_alloc(input.len())?; |
7705 | 0 | Ok(Value::String(input)) |
7706 | | } |
7707 | 0 | Err(e) => bail!("Failed to read input: {e}"), |
7708 | | } |
7709 | 0 | } |
7710 | | |
7711 | | /// Evaluate `readline` function - read a line from stdin |
7712 | 0 | fn evaluate_readline(&mut self, args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
7713 | | use std::io; |
7714 | | |
7715 | 0 | Self::validate_zero_args("readline", args.len())?; |
7716 | | |
7717 | 0 | let mut input = String::new(); |
7718 | 0 | match io::stdin().read_line(&mut input) { |
7719 | | Ok(_) => { |
7720 | | // Remove trailing newline |
7721 | 0 | if input.ends_with('\n') { |
7722 | 0 | input.pop(); |
7723 | 0 | if input.ends_with('\r') { |
7724 | 0 | input.pop(); |
7725 | 0 | } |
7726 | 0 | } |
7727 | 0 | self.memory.try_alloc(input.len())?; |
7728 | 0 | Ok(Value::String(input)) |
7729 | | } |
7730 | 0 | Err(e) => bail!("Failed to read line: {e}"), |
7731 | | } |
7732 | 0 | } |
7733 | | |
7734 | | /// Evaluate `assert` function - panic if condition is false |
7735 | 0 | fn evaluate_assert(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7736 | 0 | if args.is_empty() || args.len() > 2 { |
7737 | 0 | bail!("assert expects 1 or 2 arguments (condition, optional message)"); |
7738 | 0 | } |
7739 | | |
7740 | | // Evaluate condition |
7741 | 0 | let condition = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7742 | 0 | let Value::Bool(is_true) = condition else { |
7743 | 0 | bail!("assert expects a boolean condition, got {}", std::any::type_name_of_val(&condition)) |
7744 | | }; |
7745 | | |
7746 | 0 | if !is_true { |
7747 | | // Get optional message |
7748 | 0 | let message = if args.len() > 1 { |
7749 | 0 | let msg_val = self.evaluate_arg(args, 1, deadline, depth)?; |
7750 | 0 | match msg_val { |
7751 | 0 | Value::String(s) => s, |
7752 | 0 | other => other.to_string(), |
7753 | | } |
7754 | | } else { |
7755 | 0 | "Assertion failed".to_string() |
7756 | | }; |
7757 | | |
7758 | 0 | bail!("Assertion failed: {}", message); |
7759 | 0 | } |
7760 | | |
7761 | 0 | Self::ok_unit() |
7762 | 0 | } |
7763 | | |
7764 | | /// Evaluate `assert_eq` function - panic if values are not equal |
7765 | 0 | fn evaluate_assert_eq(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7766 | 0 | if args.len() < 2 || args.len() > 3 { |
7767 | 0 | bail!("assert_eq expects 2 or 3 arguments (left, right, optional message)"); |
7768 | 0 | } |
7769 | | |
7770 | | // Evaluate both values |
7771 | 0 | let left = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7772 | 0 | let right = self.evaluate_arg(args, 1, deadline, depth)?; |
7773 | | |
7774 | | // Compare values |
7775 | 0 | let are_equal = self.values_equal(&left, &right); |
7776 | | |
7777 | 0 | if !are_equal { |
7778 | | // Get optional message |
7779 | 0 | let message = if args.len() > 2 { |
7780 | 0 | let msg_val = self.evaluate_expr(&args[2], deadline, depth + 1)?; |
7781 | 0 | match msg_val { |
7782 | 0 | Value::String(s) => s, |
7783 | 0 | other => other.to_string(), |
7784 | | } |
7785 | | } else { |
7786 | 0 | format!("assertion failed: `(left == right)`\n left: `{left}`\n right: `{right}`") |
7787 | | }; |
7788 | | |
7789 | 0 | bail!("Assertion failed: {}", message); |
7790 | 0 | } |
7791 | | |
7792 | 0 | Self::ok_unit() |
7793 | 0 | } |
7794 | | |
7795 | | /// Evaluate `assert_ne` function - panic if values are equal |
7796 | 0 | fn evaluate_assert_ne(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7797 | 0 | if args.len() < 2 || args.len() > 3 { |
7798 | 0 | bail!("assert_ne expects 2 or 3 arguments (left, right, optional message)"); |
7799 | 0 | } |
7800 | | |
7801 | | // Evaluate both values |
7802 | 0 | let left = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7803 | 0 | let right = self.evaluate_arg(args, 1, deadline, depth)?; |
7804 | | |
7805 | | // Compare values |
7806 | 0 | let are_equal = self.values_equal(&left, &right); |
7807 | | |
7808 | 0 | if are_equal { |
7809 | | // Get optional message |
7810 | 0 | let message = if args.len() > 2 { |
7811 | 0 | let msg_val = self.evaluate_expr(&args[2], deadline, depth + 1)?; |
7812 | 0 | match msg_val { |
7813 | 0 | Value::String(s) => s, |
7814 | 0 | other => other.to_string(), |
7815 | | } |
7816 | | } else { |
7817 | 0 | format!("assertion failed: `(left != right)`\n left: `{left}`\n right: `{right}`") |
7818 | | }; |
7819 | | |
7820 | 0 | bail!("Assertion failed: {}", message); |
7821 | 0 | } |
7822 | | |
7823 | 0 | Self::ok_unit() |
7824 | 0 | } |
7825 | | |
7826 | | /// Evaluate `assert_true` function - panic if condition is false |
7827 | 0 | fn evaluate_assert_true(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7828 | 0 | if args.is_empty() || args.len() > 2 { |
7829 | 0 | bail!("assert_true expects 1 or 2 arguments (condition, optional message)"); |
7830 | 0 | } |
7831 | | |
7832 | | // Evaluate condition |
7833 | 0 | let condition = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7834 | | |
7835 | | // Check if condition is truthy |
7836 | 0 | let is_true = match condition { |
7837 | 0 | Value::Bool(b) => b, |
7838 | 0 | _ => bail!("assert_true expects a boolean condition, got {}", self.get_value_type_name(&condition)), |
7839 | | }; |
7840 | | |
7841 | 0 | if !is_true { |
7842 | | // Get optional message |
7843 | 0 | let message = if args.len() > 1 { |
7844 | 0 | let msg_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
7845 | 0 | match msg_val { |
7846 | 0 | Value::String(s) => s, |
7847 | 0 | other => other.to_string(), |
7848 | | } |
7849 | | } else { |
7850 | 0 | "assertion failed: condition is false".to_string() |
7851 | | }; |
7852 | | |
7853 | 0 | bail!("Assertion failed: {}", message); |
7854 | 0 | } |
7855 | | |
7856 | 0 | Self::ok_unit() |
7857 | 0 | } |
7858 | | |
7859 | | /// Evaluate `assert_false` function - panic if condition is true |
7860 | 0 | fn evaluate_assert_false(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7861 | 0 | if args.is_empty() || args.len() > 2 { |
7862 | 0 | bail!("assert_false expects 1 or 2 arguments (condition, optional message)"); |
7863 | 0 | } |
7864 | | |
7865 | | // Evaluate condition |
7866 | 0 | let condition = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7867 | | |
7868 | | // Check if condition is falsy |
7869 | 0 | let is_false = match condition { |
7870 | 0 | Value::Bool(b) => !b, |
7871 | 0 | _ => bail!("assert_false expects a boolean condition, got {}", self.get_value_type_name(&condition)), |
7872 | | }; |
7873 | | |
7874 | 0 | if !is_false { |
7875 | | // Get optional message |
7876 | 0 | let message = if args.len() > 1 { |
7877 | 0 | let msg_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
7878 | 0 | match msg_val { |
7879 | 0 | Value::String(s) => s, |
7880 | 0 | other => other.to_string(), |
7881 | | } |
7882 | | } else { |
7883 | 0 | "assertion failed: condition is true".to_string() |
7884 | | }; |
7885 | | |
7886 | 0 | bail!("Assertion failed: {}", message); |
7887 | 0 | } |
7888 | | |
7889 | 0 | Self::ok_unit() |
7890 | 0 | } |
7891 | | |
7892 | | /// Compare two values for equality (helper for assertions) |
7893 | 0 | fn values_equal(&self, left: &Value, right: &Value) -> bool { |
7894 | 0 | match (left, right) { |
7895 | 0 | (Value::Int(a), Value::Int(b)) => a == b, |
7896 | 0 | (Value::Float(a), Value::Float(b)) => (a - b).abs() < f64::EPSILON, |
7897 | 0 | (Value::Int(a), Value::Float(b)) => (*a as f64 - b).abs() < f64::EPSILON, |
7898 | 0 | (Value::Float(a), Value::Int(b)) => (a - *b as f64).abs() < f64::EPSILON, |
7899 | 0 | (Value::String(a), Value::String(b)) => a == b, |
7900 | 0 | (Value::Bool(a), Value::Bool(b)) => a == b, |
7901 | 0 | (Value::Unit, Value::Unit) => true, |
7902 | 0 | (Value::List(a), Value::List(b)) => { |
7903 | 0 | a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| self.values_equal(x, y)) |
7904 | | } |
7905 | 0 | (Value::Tuple(a), Value::Tuple(b)) => { |
7906 | 0 | a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| self.values_equal(x, y)) |
7907 | | } |
7908 | 0 | _ => false, |
7909 | | } |
7910 | 0 | } |
7911 | | |
7912 | | /// Evaluate `read_file` function |
7913 | 0 | fn evaluate_read_file( |
7914 | 0 | &mut self, |
7915 | 0 | args: &[Expr], |
7916 | 0 | deadline: Instant, |
7917 | 0 | depth: usize, |
7918 | 0 | ) -> Result<Value> { |
7919 | 0 | if args.len() != 1 { |
7920 | 0 | bail!("read_file expects exactly 1 argument (filename)"); |
7921 | 0 | } |
7922 | | |
7923 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7924 | 0 | let Value::String(filename) = filename_val else { |
7925 | 0 | bail!("read_file expects a string filename") |
7926 | | }; |
7927 | | |
7928 | 0 | match std::fs::read_to_string(&filename) { |
7929 | 0 | Ok(content) => Ok(Value::String(content)), |
7930 | 0 | Err(e) => bail!("Failed to read file '{}': {}", filename, e), |
7931 | | } |
7932 | 0 | } |
7933 | | |
7934 | | /// Evaluate `write_file` function |
7935 | 0 | fn evaluate_write_file( |
7936 | 0 | &mut self, |
7937 | 0 | args: &[Expr], |
7938 | 0 | deadline: Instant, |
7939 | 0 | depth: usize, |
7940 | 0 | ) -> Result<Value> { |
7941 | 0 | if args.len() != 2 { |
7942 | 0 | bail!("write_file expects exactly 2 arguments (filename, content)"); |
7943 | 0 | } |
7944 | | |
7945 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7946 | 0 | let Value::String(filename) = filename_val else { |
7947 | 0 | bail!("write_file expects a string filename") |
7948 | | }; |
7949 | | |
7950 | 0 | let content_val = self.evaluate_arg(args, 1, deadline, depth)?; |
7951 | 0 | let content = if let Value::String(s) = content_val { |
7952 | 0 | s |
7953 | | } else { |
7954 | 0 | content_val.to_string() |
7955 | | }; |
7956 | | |
7957 | 0 | match std::fs::write(&filename, content) { |
7958 | | Ok(()) => { |
7959 | 0 | println!("File '{filename}' written successfully"); |
7960 | 0 | Self::ok_unit() |
7961 | | } |
7962 | 0 | Err(e) => bail!("Failed to write file '{}': {}", filename, e), |
7963 | | } |
7964 | 0 | } |
7965 | | |
7966 | | /// Evaluate `append_file` function |
7967 | 0 | fn evaluate_append_file( |
7968 | 0 | &mut self, |
7969 | 0 | args: &[Expr], |
7970 | 0 | deadline: Instant, |
7971 | 0 | depth: usize, |
7972 | 0 | ) -> Result<Value> { |
7973 | 0 | if args.len() != 2 { |
7974 | 0 | bail!("append_file expects exactly 2 arguments (filename, content)"); |
7975 | 0 | } |
7976 | | |
7977 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7978 | 0 | let Value::String(filename) = filename_val else { |
7979 | 0 | bail!("append_file expects a string filename") |
7980 | | }; |
7981 | | |
7982 | 0 | let content_val = self.evaluate_arg(args, 1, deadline, depth)?; |
7983 | 0 | let content = if let Value::String(s) = content_val { |
7984 | 0 | s |
7985 | | } else { |
7986 | 0 | content_val.to_string() |
7987 | | }; |
7988 | | |
7989 | 0 | match std::fs::OpenOptions::new() |
7990 | 0 | .create(true) |
7991 | 0 | .append(true) |
7992 | 0 | .open(&filename) |
7993 | | { |
7994 | 0 | Ok(mut file) => { |
7995 | | use std::io::Write; |
7996 | 0 | match file.write_all(content.as_bytes()) { |
7997 | 0 | Ok(()) => Ok(Value::Unit), |
7998 | 0 | Err(e) => bail!("Failed to append to file '{}': {}", filename, e), |
7999 | | } |
8000 | | } |
8001 | 0 | Err(e) => bail!("Failed to open file '{}' for append: {}", filename, e), |
8002 | | } |
8003 | 0 | } |
8004 | | |
8005 | | /// Evaluate `file_exists` function |
8006 | 0 | fn evaluate_file_exists( |
8007 | 0 | &mut self, |
8008 | 0 | args: &[Expr], |
8009 | 0 | deadline: Instant, |
8010 | 0 | depth: usize, |
8011 | 0 | ) -> Result<Value> { |
8012 | 0 | if args.len() != 1 { |
8013 | 0 | bail!("file_exists expects exactly 1 argument (filename)"); |
8014 | 0 | } |
8015 | | |
8016 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8017 | 0 | let Value::String(filename) = filename_val else { |
8018 | 0 | bail!("file_exists expects a string filename") |
8019 | | }; |
8020 | | |
8021 | 0 | let exists = std::path::Path::new(&filename).exists(); |
8022 | 0 | Ok(Value::Bool(exists)) |
8023 | 0 | } |
8024 | | |
8025 | | /// Evaluate `delete_file` function |
8026 | 0 | fn evaluate_delete_file( |
8027 | 0 | &mut self, |
8028 | 0 | args: &[Expr], |
8029 | 0 | deadline: Instant, |
8030 | 0 | depth: usize, |
8031 | 0 | ) -> Result<Value> { |
8032 | 0 | if args.len() != 1 { |
8033 | 0 | bail!("delete_file expects exactly 1 argument (filename)"); |
8034 | 0 | } |
8035 | | |
8036 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8037 | 0 | let Value::String(filename) = filename_val else { |
8038 | 0 | bail!("delete_file expects a string filename") |
8039 | | }; |
8040 | | |
8041 | 0 | match std::fs::remove_file(&filename) { |
8042 | 0 | Ok(()) => Ok(Value::Unit), |
8043 | 0 | Err(e) => bail!("Failed to delete file '{}': {}", filename, e), |
8044 | | } |
8045 | 0 | } |
8046 | | |
8047 | | /// Evaluate `current_dir` function |
8048 | 0 | fn evaluate_current_dir( |
8049 | 0 | &mut self, |
8050 | 0 | args: &[Expr], |
8051 | 0 | _deadline: Instant, |
8052 | 0 | _depth: usize, |
8053 | 0 | ) -> Result<Value> { |
8054 | 0 | Self::validate_zero_args("current_dir", args.len())?; |
8055 | | |
8056 | 0 | match std::env::current_dir() { |
8057 | 0 | Ok(path) => Ok(Value::String(path.to_string_lossy().to_string())), |
8058 | 0 | Err(e) => bail!("Failed to get current directory: {}", e), |
8059 | | } |
8060 | 0 | } |
8061 | | |
8062 | | /// Evaluate `env` function |
8063 | 0 | fn evaluate_env( |
8064 | 0 | &mut self, |
8065 | 0 | args: &[Expr], |
8066 | 0 | deadline: Instant, |
8067 | 0 | depth: usize, |
8068 | 0 | ) -> Result<Value> { |
8069 | 0 | if args.len() != 1 { |
8070 | 0 | bail!("env expects exactly 1 argument (variable name)"); |
8071 | 0 | } |
8072 | | |
8073 | 0 | let var_name_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8074 | 0 | let Value::String(var_name) = var_name_val else { |
8075 | 0 | bail!("env expects a string variable name") |
8076 | | }; |
8077 | | |
8078 | 0 | match std::env::var(&var_name) { |
8079 | 0 | Ok(value) => Ok(Value::String(value)), |
8080 | 0 | Err(_) => Ok(Value::String(String::new())), // Return empty string for non-existent vars |
8081 | | } |
8082 | 0 | } |
8083 | | |
8084 | | /// Evaluate `set_env` function |
8085 | 0 | fn evaluate_set_env( |
8086 | 0 | &mut self, |
8087 | 0 | args: &[Expr], |
8088 | 0 | deadline: Instant, |
8089 | 0 | depth: usize, |
8090 | 0 | ) -> Result<Value> { |
8091 | 0 | if args.len() != 2 { |
8092 | 0 | bail!("set_env expects exactly 2 arguments (variable name, value)"); |
8093 | 0 | } |
8094 | | |
8095 | 0 | let var_name_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8096 | 0 | let Value::String(var_name) = var_name_val else { |
8097 | 0 | bail!("set_env expects a string variable name") |
8098 | | }; |
8099 | | |
8100 | 0 | let value_val = self.evaluate_arg(args, 1, deadline, depth)?; |
8101 | 0 | let value = if let Value::String(s) = value_val { |
8102 | 0 | s |
8103 | | } else { |
8104 | 0 | value_val.to_string() |
8105 | | }; |
8106 | | |
8107 | 0 | std::env::set_var(var_name, value); |
8108 | 0 | Self::ok_unit() |
8109 | 0 | } |
8110 | | |
8111 | | /// Evaluate `args` function |
8112 | 0 | fn evaluate_args( |
8113 | 0 | &mut self, |
8114 | 0 | args: &[Expr], |
8115 | 0 | _deadline: Instant, |
8116 | 0 | _depth: usize, |
8117 | 0 | ) -> Result<Value> { |
8118 | 0 | if !args.is_empty() { |
8119 | 0 | bail!("args expects no arguments"); |
8120 | 0 | } |
8121 | | |
8122 | 0 | let args_vec = std::env::args().collect::<Vec<String>>(); |
8123 | 0 | let values: Vec<Value> = args_vec.into_iter().map(Value::String).collect(); |
8124 | 0 | Self::ok_list(values) |
8125 | 0 | } |
8126 | | |
8127 | | /// Evaluate `Some` constructor |
8128 | 0 | fn evaluate_some( |
8129 | 0 | &mut self, |
8130 | 0 | args: &[Expr], |
8131 | 0 | deadline: Instant, |
8132 | 0 | depth: usize, |
8133 | 0 | ) -> Result<Value> { |
8134 | 0 | if args.len() != 1 { |
8135 | 0 | Self::validate_exact_args("Some", 1, args.len())?; |
8136 | 0 | } |
8137 | | |
8138 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8139 | 0 | Ok(Value::EnumVariant { |
8140 | 0 | enum_name: "Option".to_string(), |
8141 | 0 | variant_name: "Some".to_string(), |
8142 | 0 | data: Some(vec![value]), |
8143 | 0 | }) |
8144 | 0 | } |
8145 | | |
8146 | | /// Evaluate `None` constructor |
8147 | 0 | fn evaluate_none( |
8148 | 0 | &mut self, |
8149 | 0 | args: &[Expr], |
8150 | 0 | _deadline: Instant, |
8151 | 0 | _depth: usize, |
8152 | 0 | ) -> Result<Value> { |
8153 | 0 | if !args.is_empty() { |
8154 | 0 | bail!("None expects no arguments"); |
8155 | 0 | } |
8156 | | |
8157 | 0 | Ok(Self::create_option_none()) |
8158 | 0 | } |
8159 | | |
8160 | | /// Evaluate `Ok` constructor |
8161 | 0 | fn evaluate_ok( |
8162 | 0 | &mut self, |
8163 | 0 | args: &[Expr], |
8164 | 0 | deadline: Instant, |
8165 | 0 | depth: usize, |
8166 | 0 | ) -> Result<Value> { |
8167 | 0 | if args.len() != 1 { |
8168 | 0 | Self::validate_exact_args("Ok", 1, args.len())?; |
8169 | 0 | } |
8170 | | |
8171 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8172 | 0 | Ok(Value::EnumVariant { |
8173 | 0 | enum_name: "Result".to_string(), |
8174 | 0 | variant_name: "Ok".to_string(), |
8175 | 0 | data: Some(vec![value]), |
8176 | 0 | }) |
8177 | 0 | } |
8178 | | |
8179 | | /// Evaluate `Err` constructor |
8180 | 0 | fn evaluate_err( |
8181 | 0 | &mut self, |
8182 | 0 | args: &[Expr], |
8183 | 0 | deadline: Instant, |
8184 | 0 | depth: usize, |
8185 | 0 | ) -> Result<Value> { |
8186 | 0 | if args.len() != 1 { |
8187 | 0 | Self::validate_exact_args("Err", 1, args.len())?; |
8188 | 0 | } |
8189 | | |
8190 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8191 | 0 | Ok(Value::EnumVariant { |
8192 | 0 | enum_name: "Result".to_string(), |
8193 | 0 | variant_name: "Err".to_string(), |
8194 | 0 | data: Some(vec![value]), |
8195 | 0 | }) |
8196 | 0 | } |
8197 | | |
8198 | | /// Update history variables (_ and _n) |
8199 | 0 | fn update_history_variables(&mut self) { |
8200 | 0 | let len = self.result_history.len(); |
8201 | 0 | if len == 0 { |
8202 | 0 | return; |
8203 | 0 | } |
8204 | | |
8205 | | // Set _ to the most recent result |
8206 | 0 | let last_result = self.result_history[len - 1].clone(); |
8207 | 0 | self.bindings.insert("_".to_string(), last_result); |
8208 | 0 | self.binding_mutability.insert("_".to_string(), false); // History variables are immutable |
8209 | | |
8210 | | // Set _n variables for indexed access |
8211 | 0 | for (i, result) in self.result_history.iter().enumerate() { |
8212 | 0 | let var_name = format!("_{}", i + 1); |
8213 | 0 | self.bindings.insert(var_name.clone(), result.clone()); |
8214 | 0 | self.binding_mutability.insert(var_name, false); // History variables are immutable |
8215 | 0 | } |
8216 | 0 | } |
8217 | | |
8218 | | /// Handle REPL magic commands |
8219 | | /// Handle %time magic command (complexity: 3) |
8220 | 0 | fn handle_time_magic(&mut self, args: &str) -> Result<String> { |
8221 | 0 | if args.is_empty() { |
8222 | 0 | return Ok("Usage: %time <expression>".to_string()); |
8223 | 0 | } |
8224 | | |
8225 | 0 | let start = std::time::Instant::now(); |
8226 | 0 | let result = self.eval(args)?; |
8227 | 0 | let elapsed = start.elapsed(); |
8228 | | |
8229 | 0 | Ok(format!("{result}\nExecuted in: {elapsed:?}")) |
8230 | 0 | } |
8231 | | |
8232 | | /// Handle %timeit magic command (complexity: 4) |
8233 | 0 | fn handle_timeit_magic(&mut self, args: &str) -> Result<String> { |
8234 | 0 | if args.is_empty() { |
8235 | 0 | return Ok("Usage: %timeit <expression>".to_string()); |
8236 | 0 | } |
8237 | | |
8238 | | const ITERATIONS: usize = 1000; |
8239 | 0 | let mut total_time = std::time::Duration::new(0, 0); |
8240 | 0 | let mut last_result = String::new(); |
8241 | | |
8242 | 0 | for _ in 0..ITERATIONS { |
8243 | 0 | let start = std::time::Instant::now(); |
8244 | 0 | last_result = self.eval(args)?; |
8245 | 0 | total_time += start.elapsed(); |
8246 | | } |
8247 | | |
8248 | 0 | let avg_time = total_time / ITERATIONS as u32; |
8249 | 0 | Ok(format!( |
8250 | 0 | "{last_result}\n{ITERATIONS} loops, average: {avg_time:?} per loop" |
8251 | 0 | )) |
8252 | 0 | } |
8253 | | |
8254 | | /// Handle %run magic command (complexity: 6) |
8255 | 0 | fn handle_run_magic(&mut self, args: &str) -> Result<String> { |
8256 | 0 | if args.is_empty() { |
8257 | 0 | return Ok("Usage: %run <script.ruchy>".to_string()); |
8258 | 0 | } |
8259 | | |
8260 | 0 | match std::fs::read_to_string(args) { |
8261 | 0 | Ok(content) => { |
8262 | 0 | let lines: Vec<&str> = content.lines().collect(); |
8263 | 0 | let mut results = Vec::new(); |
8264 | | |
8265 | 0 | for line in lines { |
8266 | 0 | let trimmed = line.trim(); |
8267 | 0 | if !trimmed.is_empty() && !trimmed.starts_with("//") { |
8268 | 0 | match self.eval(trimmed) { |
8269 | 0 | Ok(result) => results.push(result), |
8270 | 0 | Err(e) => return Err(e.context(format!("Error executing: {trimmed}"))), |
8271 | | } |
8272 | 0 | } |
8273 | | } |
8274 | | |
8275 | 0 | Ok(results.join("\n")) |
8276 | | } |
8277 | 0 | Err(e) => Ok(format!("Failed to read file '{args}': {e}")) |
8278 | | } |
8279 | 0 | } |
8280 | | |
8281 | | /// Handle %debug magic command (complexity: 7) |
8282 | 0 | fn handle_debug_magic(&self) -> Result<String> { |
8283 | 0 | if let Some(ref debug_info) = self.last_error_debug { |
8284 | 0 | let mut output = String::new(); |
8285 | 0 | output.push_str("=== Debug Information ===\n"); |
8286 | 0 | output.push_str(&format!("Expression: {}\n", debug_info.expression)); |
8287 | 0 | output.push_str(&format!("Error: {}\n", debug_info.error_message)); |
8288 | 0 | output.push_str(&format!("Time: {:?}\n", debug_info.timestamp)); |
8289 | 0 | output.push_str("\n--- Variable Bindings at Error ---\n"); |
8290 | | |
8291 | 0 | for (name, value) in &debug_info.bindings_snapshot { |
8292 | 0 | output.push_str(&format!("{name}: {value}\n")); |
8293 | 0 | } |
8294 | | |
8295 | 0 | if !debug_info.stack_trace.is_empty() { |
8296 | 0 | output.push_str("\n--- Stack Trace ---\n"); |
8297 | 0 | for frame in &debug_info.stack_trace { |
8298 | 0 | output.push_str(&format!(" {frame}\n")); |
8299 | 0 | } |
8300 | 0 | } |
8301 | | |
8302 | 0 | Ok(output) |
8303 | | } else { |
8304 | 0 | Ok("No debug information available. Run an expression that fails first.".to_string()) |
8305 | | } |
8306 | 0 | } |
8307 | | |
8308 | | /// Handle %profile magic command - parsing phase (complexity: 5) |
8309 | 0 | fn profile_parse_phase(&self, args: &str) -> Result<(Expr, std::time::Duration, usize)> { |
8310 | 0 | let parse_start = std::time::Instant::now(); |
8311 | 0 | let mut parser = Parser::new(args); |
8312 | 0 | let ast = match parser.parse() { |
8313 | 0 | Ok(ast) => ast, |
8314 | 0 | Err(e) => return Err(anyhow::anyhow!("Parse error: {e}")), |
8315 | | }; |
8316 | 0 | let parse_time = parse_start.elapsed(); |
8317 | 0 | let alloc_size = std::mem::size_of_val(&ast); |
8318 | 0 | Ok((ast, parse_time, alloc_size)) |
8319 | 0 | } |
8320 | | |
8321 | | /// Handle %profile magic command - evaluation phase (complexity: 4) |
8322 | 0 | fn profile_eval_phase(&mut self, ast: &Expr) -> Result<(Value, std::time::Duration)> { |
8323 | 0 | let eval_start = std::time::Instant::now(); |
8324 | 0 | let deadline = std::time::Instant::now() + self.config.timeout; |
8325 | 0 | let result = match self.evaluate_expr(ast, deadline, 0) { |
8326 | 0 | Ok(value) => value, |
8327 | 0 | Err(e) => return Err(anyhow::anyhow!("Evaluation error: {e}")), |
8328 | | }; |
8329 | 0 | let eval_time = eval_start.elapsed(); |
8330 | 0 | Ok((result, eval_time)) |
8331 | 0 | } |
8332 | | |
8333 | | /// Format profile analysis output (complexity: 6) |
8334 | 0 | fn format_profile_analysis( |
8335 | 0 | &self, |
8336 | 0 | total_time: std::time::Duration, |
8337 | 0 | parse_time: std::time::Duration, |
8338 | 0 | eval_time: std::time::Duration, |
8339 | 0 | ) -> String { |
8340 | 0 | let mut output = String::new(); |
8341 | 0 | output.push_str("\n--- Analysis ---\n"); |
8342 | | |
8343 | 0 | if total_time.as_millis() > 50 { |
8344 | 0 | output.push_str("⚠️ Slow execution (>50ms)\n"); |
8345 | 0 | } else if total_time.as_millis() > 10 { |
8346 | 0 | output.push_str("⚡ Moderate performance (>10ms)\n"); |
8347 | 0 | } else { |
8348 | 0 | output.push_str("🚀 Fast execution (<10ms)\n"); |
8349 | 0 | } |
8350 | | |
8351 | 0 | if parse_time.as_secs_f64() / total_time.as_secs_f64() > 0.3 { |
8352 | 0 | output.push_str("📝 Parse-heavy (consider simpler syntax)\n"); |
8353 | 0 | } |
8354 | | |
8355 | 0 | if eval_time.as_secs_f64() / total_time.as_secs_f64() > 0.7 { |
8356 | 0 | output.push_str("🧮 Compute-heavy (consider optimization)\n"); |
8357 | 0 | } |
8358 | | |
8359 | 0 | output |
8360 | 0 | } |
8361 | | |
8362 | | /// Handle %profile magic command (complexity: 8) |
8363 | 0 | fn handle_profile_magic(&mut self, args: &str) -> Result<String> { |
8364 | 0 | if args.is_empty() { |
8365 | 0 | return Ok("Usage: %profile <expression>".to_string()); |
8366 | 0 | } |
8367 | | |
8368 | 0 | let start = std::time::Instant::now(); |
8369 | | |
8370 | | // Parse phase |
8371 | 0 | let (ast, parse_time, alloc_size) = self.profile_parse_phase(args)?; |
8372 | | |
8373 | | // Evaluation phase |
8374 | 0 | let (result, eval_time) = self.profile_eval_phase(&ast)?; |
8375 | | |
8376 | 0 | let total_time = start.elapsed(); |
8377 | | |
8378 | | // Generate profile report |
8379 | 0 | let mut output = String::new(); |
8380 | 0 | output.push_str("=== Performance Profile ===\n"); |
8381 | 0 | output.push_str(&format!("Expression: {args}\n")); |
8382 | 0 | output.push_str(&format!("Result: {result}\n\n")); |
8383 | | |
8384 | 0 | output.push_str("--- Timing Breakdown ---\n"); |
8385 | 0 | output.push_str(&format!("Parse: {:>8.3}ms ({:>5.1}%)\n", |
8386 | 0 | parse_time.as_secs_f64() * 1000.0, |
8387 | 0 | (parse_time.as_secs_f64() / total_time.as_secs_f64()) * 100.0)); |
8388 | 0 | output.push_str(&format!("Evaluate: {:>8.3}ms ({:>5.1}%)\n", |
8389 | 0 | eval_time.as_secs_f64() * 1000.0, |
8390 | 0 | (eval_time.as_secs_f64() / total_time.as_secs_f64()) * 100.0)); |
8391 | 0 | output.push_str(&format!("Total: {:>8.3}ms\n\n", |
8392 | 0 | total_time.as_secs_f64() * 1000.0)); |
8393 | | |
8394 | 0 | output.push_str("--- Memory Usage ---\n"); |
8395 | 0 | output.push_str(&format!("AST size: {alloc_size:>8} bytes\n")); |
8396 | 0 | output.push_str(&format!("Memory: {:>8} bytes used\n", self.memory.current)); |
8397 | | |
8398 | | // Add performance analysis |
8399 | 0 | output.push_str(&self.format_profile_analysis(total_time, parse_time, eval_time)); |
8400 | | |
8401 | 0 | Ok(output) |
8402 | 0 | } |
8403 | | |
8404 | | /// Handle %help magic command (complexity: 1) |
8405 | 0 | fn handle_help_magic(&self) -> Result<String> { |
8406 | 0 | Ok(r"Available magic commands: |
8407 | 0 | %time <expr> - Time a single execution |
8408 | 0 | %timeit <expr> - Time multiple executions (benchmark) |
8409 | 0 | %run <file> - Execute a .ruchy script file |
8410 | 0 | %debug - Show debug info from last error |
8411 | 0 | %profile <expr> - Generate execution profile |
8412 | 0 | %help - Show this help message".to_string()) |
8413 | 0 | } |
8414 | | |
8415 | 0 | fn handle_magic_command(&mut self, command: &str) -> Result<String> { |
8416 | | // Uses legacy implementation for backward compatibility |
8417 | | // Future: Consider refactoring to use magic registry pattern |
8418 | | |
8419 | | // Fall back to legacy implementation for backward compatibility |
8420 | 0 | let parts: Vec<&str> = command.splitn(2, ' ').collect(); |
8421 | 0 | let magic_cmd = parts[0]; |
8422 | 0 | let args = if parts.len() > 1 { parts[1] } else { "" }; |
8423 | | |
8424 | 0 | match magic_cmd { |
8425 | 0 | "%time" => self.handle_time_magic(args), |
8426 | 0 | "%timeit" => self.handle_timeit_magic(args), |
8427 | 0 | "%run" => self.handle_run_magic(args), |
8428 | 0 | "%debug" => self.handle_debug_magic(), |
8429 | 0 | "%profile" => self.handle_profile_magic(args), |
8430 | 0 | "%help" => self.handle_help_magic(), |
8431 | 0 | _ => Ok(format!("Unknown magic command: {magic_cmd}. Type %help for available commands.")), |
8432 | | } |
8433 | 0 | } |
8434 | | |
8435 | | /// Evaluate `str` type conversion function |
8436 | 0 | fn evaluate_str_conversion( |
8437 | 0 | &mut self, |
8438 | 0 | args: &[Expr], |
8439 | 0 | deadline: Instant, |
8440 | 0 | depth: usize, |
8441 | 0 | ) -> Result<Value> { |
8442 | 0 | if args.len() != 1 { |
8443 | 0 | bail!("str() expects exactly 1 argument"); |
8444 | 0 | } |
8445 | | |
8446 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8447 | 0 | match value { |
8448 | 0 | Value::Char(c) => Ok(Value::String(c.to_string())), |
8449 | 0 | _ => Ok(Value::String(value.to_string())), |
8450 | | } |
8451 | 0 | } |
8452 | | |
8453 | | /// Evaluate `int` type conversion function |
8454 | 0 | fn evaluate_int_conversion( |
8455 | 0 | &mut self, |
8456 | 0 | args: &[Expr], |
8457 | 0 | deadline: Instant, |
8458 | 0 | depth: usize, |
8459 | 0 | ) -> Result<Value> { |
8460 | 0 | if args.is_empty() || args.len() > 2 { |
8461 | 0 | bail!("int() expects 1 or 2 arguments"); |
8462 | 0 | } |
8463 | | |
8464 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8465 | | |
8466 | | // Handle two-argument form for base conversion |
8467 | 0 | if args.len() == 2 { |
8468 | 0 | if let Value::String(s) = value { |
8469 | 0 | let base_val = self.evaluate_arg(args, 1, deadline, depth)?; |
8470 | 0 | if let Value::Int(base) = base_val { |
8471 | 0 | if !(2..=36).contains(&base) { |
8472 | 0 | bail!("int() base must be between 2 and 36"); |
8473 | 0 | } |
8474 | | // Remove common prefixes |
8475 | 0 | let cleaned = s.trim_start_matches("0x") |
8476 | 0 | .trim_start_matches("0X") |
8477 | 0 | .trim_start_matches("0b") |
8478 | 0 | .trim_start_matches("0B") |
8479 | 0 | .trim_start_matches("0o") |
8480 | 0 | .trim_start_matches("0O"); |
8481 | | |
8482 | 0 | match i64::from_str_radix(cleaned, base as u32) { |
8483 | 0 | Ok(n) => return Ok(Value::Int(n)), |
8484 | 0 | Err(_) => bail!("Cannot parse '{}' as base {} integer", s, base), |
8485 | | } |
8486 | 0 | } |
8487 | 0 | bail!("int() base must be an integer"); |
8488 | 0 | } |
8489 | 0 | bail!("int() with base requires string as first argument"); |
8490 | 0 | } |
8491 | | |
8492 | 0 | match value { |
8493 | 0 | Value::Int(n) => Ok(Value::Int(n)), |
8494 | 0 | Value::Float(f) => Ok(Value::Int(f as i64)), |
8495 | 0 | Value::Bool(b) => Ok(Value::Int(i64::from(b))), |
8496 | 0 | Value::String(s) => { |
8497 | | // Try parsing as number first |
8498 | 0 | if let Ok(n) = s.trim().parse::<i64>() { |
8499 | 0 | return Ok(Value::Int(n)); |
8500 | 0 | } |
8501 | | // Check for boolean strings |
8502 | 0 | if s == "true" { |
8503 | 0 | return Self::ok_int(1); |
8504 | 0 | } |
8505 | 0 | if s == "false" { |
8506 | 0 | return Self::ok_int(0); |
8507 | 0 | } |
8508 | 0 | bail!("Cannot convert '{}' to integer", s) |
8509 | | } |
8510 | 0 | _ => bail!("Cannot convert value to integer"), |
8511 | | } |
8512 | 0 | } |
8513 | | |
8514 | | /// Evaluate `float` type conversion function |
8515 | 0 | fn evaluate_float_conversion( |
8516 | 0 | &mut self, |
8517 | 0 | args: &[Expr], |
8518 | 0 | deadline: Instant, |
8519 | 0 | depth: usize, |
8520 | 0 | ) -> Result<Value> { |
8521 | 0 | if args.len() != 1 { |
8522 | 0 | bail!("float() expects exactly 1 argument"); |
8523 | 0 | } |
8524 | | |
8525 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8526 | 0 | match value { |
8527 | 0 | Value::Float(f) => Ok(Value::Float(f)), |
8528 | 0 | Value::Int(n) => Ok(Value::Float(n as f64)), |
8529 | 0 | Value::Bool(b) => Ok(Value::Float(f64::from(b))), |
8530 | 0 | Value::String(s) => { |
8531 | 0 | match s.trim().parse::<f64>() { |
8532 | 0 | Ok(f) => Ok(Value::Float(f)), |
8533 | 0 | Err(_) => bail!("Cannot convert '{}' to float", s), |
8534 | | } |
8535 | | } |
8536 | 0 | _ => bail!("Cannot convert value to float"), |
8537 | | } |
8538 | 0 | } |
8539 | | |
8540 | | /// Evaluate `bool` type conversion function |
8541 | 0 | fn evaluate_bool_conversion( |
8542 | 0 | &mut self, |
8543 | 0 | args: &[Expr], |
8544 | 0 | deadline: Instant, |
8545 | 0 | depth: usize, |
8546 | 0 | ) -> Result<Value> { |
8547 | 0 | if args.len() != 1 { |
8548 | 0 | bail!("bool() expects exactly 1 argument"); |
8549 | 0 | } |
8550 | | |
8551 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8552 | 0 | match value { |
8553 | 0 | Value::Bool(b) => Ok(Value::Bool(b)), |
8554 | 0 | Value::Int(n) => Ok(Value::Bool(n != 0)), |
8555 | 0 | Value::Float(f) => Ok(Value::Bool(f != 0.0 && !f.is_nan())), |
8556 | 0 | Value::String(s) => Ok(Value::Bool(!s.is_empty() && s != "false")), |
8557 | 0 | Value::Unit => Ok(Value::Bool(false)), |
8558 | 0 | Value::List(l) => Ok(Value::Bool(!l.is_empty())), |
8559 | 0 | Value::Object(o) => Ok(Value::Bool(!o.is_empty())), |
8560 | 0 | _ => Ok(Value::Bool(true)), // Most other values are truthy |
8561 | | } |
8562 | 0 | } |
8563 | | |
8564 | | /// Evaluate `sin()` function |
8565 | 0 | fn evaluate_sin( |
8566 | 0 | &mut self, |
8567 | 0 | args: &[Expr], |
8568 | 0 | deadline: Instant, |
8569 | 0 | depth: usize, |
8570 | 0 | ) -> Result<Value> { |
8571 | 0 | self.evaluate_unary_math_function(args, deadline, depth, "sin", f64::sin) |
8572 | 0 | } |
8573 | | |
8574 | | /// Evaluate `cos()` function |
8575 | 0 | fn evaluate_cos( |
8576 | 0 | &mut self, |
8577 | 0 | args: &[Expr], |
8578 | 0 | deadline: Instant, |
8579 | 0 | depth: usize, |
8580 | 0 | ) -> Result<Value> { |
8581 | 0 | self.evaluate_unary_math_function(args, deadline, depth, "cos", f64::cos) |
8582 | 0 | } |
8583 | | |
8584 | | /// Evaluate `tan()` function |
8585 | 0 | fn evaluate_tan( |
8586 | 0 | &mut self, |
8587 | 0 | args: &[Expr], |
8588 | 0 | deadline: Instant, |
8589 | 0 | depth: usize, |
8590 | 0 | ) -> Result<Value> { |
8591 | 0 | self.evaluate_unary_math_function(args, deadline, depth, "tan", f64::tan) |
8592 | 0 | } |
8593 | | |
8594 | | /// Evaluate `log()` function (natural logarithm) |
8595 | 0 | fn evaluate_log( |
8596 | 0 | &mut self, |
8597 | 0 | args: &[Expr], |
8598 | 0 | deadline: Instant, |
8599 | 0 | depth: usize, |
8600 | 0 | ) -> Result<Value> { |
8601 | 0 | let validator = |f: f64| -> Result<()> { |
8602 | 0 | if f <= 0.0 { |
8603 | 0 | bail!("log() requires a positive argument"); |
8604 | 0 | } |
8605 | 0 | Ok(()) |
8606 | 0 | }; |
8607 | 0 | self.evaluate_unary_math_function_validated(args, deadline, depth, "log", f64::ln, validator) |
8608 | 0 | } |
8609 | | |
8610 | | /// Evaluate `log10()` function (base-10 logarithm) |
8611 | 0 | fn evaluate_log10( |
8612 | 0 | &mut self, |
8613 | 0 | args: &[Expr], |
8614 | 0 | deadline: Instant, |
8615 | 0 | depth: usize, |
8616 | 0 | ) -> Result<Value> { |
8617 | 0 | let validator = |f: f64| -> Result<()> { |
8618 | 0 | if f <= 0.0 { |
8619 | 0 | bail!("log10() requires a positive argument"); |
8620 | 0 | } |
8621 | 0 | Ok(()) |
8622 | 0 | }; |
8623 | 0 | self.evaluate_unary_math_function_validated(args, deadline, depth, "log10", f64::log10, validator) |
8624 | 0 | } |
8625 | | |
8626 | | /// Evaluate `char()` conversion function (complexity: 6) |
8627 | 0 | fn evaluate_char_conversion( |
8628 | 0 | &mut self, |
8629 | 0 | args: &[Expr], |
8630 | 0 | deadline: Instant, |
8631 | 0 | depth: usize, |
8632 | 0 | ) -> Result<Value> { |
8633 | 0 | if args.len() != 1 { |
8634 | 0 | bail!("char() expects exactly 1 argument"); |
8635 | 0 | } |
8636 | | |
8637 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8638 | 0 | match value { |
8639 | 0 | Value::Int(n) => { |
8640 | 0 | if !(0..=1_114_111).contains(&n) { |
8641 | 0 | bail!("char() expects a valid Unicode code point (0-1114111)"); |
8642 | 0 | } |
8643 | 0 | match char::from_u32(n as u32) { |
8644 | 0 | Some(c) => Self::ok_char(c), |
8645 | 0 | None => bail!("Invalid Unicode code point: {}", n), |
8646 | | } |
8647 | | } |
8648 | 0 | Value::String(s) => { |
8649 | 0 | if s.len() == 1 { |
8650 | 0 | Self::ok_char(s.chars().next().unwrap()) |
8651 | | } else { |
8652 | 0 | bail!("char() from string expects exactly 1 character, got {}", s.len()); |
8653 | | } |
8654 | | } |
8655 | 0 | _ => bail!("char() expects an integer or single-character string"), |
8656 | | } |
8657 | 0 | } |
8658 | | |
8659 | | /// Evaluate `hex()` conversion - int to hex string (complexity: 5) |
8660 | 0 | fn evaluate_hex_conversion( |
8661 | 0 | &mut self, |
8662 | 0 | args: &[Expr], |
8663 | 0 | deadline: Instant, |
8664 | 0 | depth: usize, |
8665 | 0 | ) -> Result<Value> { |
8666 | 0 | if args.len() != 1 { |
8667 | 0 | bail!("hex() expects exactly 1 argument"); |
8668 | 0 | } |
8669 | | |
8670 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8671 | 0 | match value { |
8672 | 0 | Value::Int(n) => { |
8673 | 0 | if n < 0 { |
8674 | 0 | Ok(Value::String(format!("-0x{:x}", -n))) |
8675 | | } else { |
8676 | 0 | Ok(Value::String(format!("0x{n:x}"))) |
8677 | | } |
8678 | | } |
8679 | 0 | _ => bail!("hex() expects an integer"), |
8680 | | } |
8681 | 0 | } |
8682 | | |
8683 | | /// Evaluate `bin()` conversion - int to binary string (complexity: 5) |
8684 | 0 | fn evaluate_bin_conversion( |
8685 | 0 | &mut self, |
8686 | 0 | args: &[Expr], |
8687 | 0 | deadline: Instant, |
8688 | 0 | depth: usize, |
8689 | 0 | ) -> Result<Value> { |
8690 | 0 | if args.len() != 1 { |
8691 | 0 | bail!("bin() expects exactly 1 argument"); |
8692 | 0 | } |
8693 | | |
8694 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8695 | 0 | match value { |
8696 | 0 | Value::Int(n) => { |
8697 | 0 | if n < 0 { |
8698 | 0 | Ok(Value::String(format!("-0b{:b}", -n))) |
8699 | | } else { |
8700 | 0 | Ok(Value::String(format!("0b{n:b}"))) |
8701 | | } |
8702 | | } |
8703 | 0 | _ => bail!("bin() expects an integer"), |
8704 | | } |
8705 | 0 | } |
8706 | | |
8707 | | /// Evaluate `oct()` conversion - int to octal string (complexity: 5) |
8708 | 0 | fn evaluate_oct_conversion( |
8709 | 0 | &mut self, |
8710 | 0 | args: &[Expr], |
8711 | 0 | deadline: Instant, |
8712 | 0 | depth: usize, |
8713 | 0 | ) -> Result<Value> { |
8714 | 0 | if args.len() != 1 { |
8715 | 0 | bail!("oct() expects exactly 1 argument"); |
8716 | 0 | } |
8717 | | |
8718 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8719 | 0 | match value { |
8720 | 0 | Value::Int(n) => { |
8721 | 0 | if n < 0 { |
8722 | 0 | Ok(Value::String(format!("-0o{:o}", -n))) |
8723 | | } else { |
8724 | 0 | Ok(Value::String(format!("0o{n:o}"))) |
8725 | | } |
8726 | | } |
8727 | 0 | _ => bail!("oct() expects an integer"), |
8728 | | } |
8729 | 0 | } |
8730 | | |
8731 | | /// Evaluate `list()` conversion - convert tuple/iterable to list (complexity: 5) |
8732 | 0 | fn evaluate_list_conversion( |
8733 | 0 | &mut self, |
8734 | 0 | args: &[Expr], |
8735 | 0 | deadline: Instant, |
8736 | 0 | depth: usize, |
8737 | 0 | ) -> Result<Value> { |
8738 | 0 | if args.len() != 1 { |
8739 | 0 | bail!("list() expects exactly 1 argument"); |
8740 | 0 | } |
8741 | | |
8742 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8743 | 0 | match value { |
8744 | 0 | Value::List(items) => Self::ok_list(items), |
8745 | 0 | Value::Tuple(items) => Self::ok_list(items), |
8746 | 0 | Value::String(s) => { |
8747 | 0 | let chars: Vec<Value> = s.chars() |
8748 | 0 | .map(|c| Value::String(c.to_string())) |
8749 | 0 | .collect(); |
8750 | 0 | Self::ok_list(chars) |
8751 | | } |
8752 | 0 | _ => bail!("list() expects a list, tuple, or string"), |
8753 | | } |
8754 | 0 | } |
8755 | | |
8756 | | /// Evaluate `tuple()` conversion - convert list/iterable to tuple (complexity: 5) |
8757 | 0 | fn evaluate_tuple_conversion( |
8758 | 0 | &mut self, |
8759 | 0 | args: &[Expr], |
8760 | 0 | deadline: Instant, |
8761 | 0 | depth: usize, |
8762 | 0 | ) -> Result<Value> { |
8763 | 0 | if args.len() != 1 { |
8764 | 0 | bail!("tuple() expects exactly 1 argument"); |
8765 | 0 | } |
8766 | | |
8767 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
8768 | 0 | match value { |
8769 | 0 | Value::Tuple(items) => Ok(Value::Tuple(items)), |
8770 | 0 | Value::List(items) => Ok(Value::Tuple(items)), |
8771 | 0 | Value::String(s) => { |
8772 | 0 | let chars: Vec<Value> = s.chars() |
8773 | 0 | .map(|c| Value::String(c.to_string())) |
8774 | 0 | .collect(); |
8775 | 0 | Ok(Value::Tuple(chars)) |
8776 | | } |
8777 | 0 | _ => bail!("tuple() expects a list, tuple, or string"), |
8778 | | } |
8779 | 0 | } |
8780 | | |
8781 | | /// Evaluate type cast expression (complexity: 8) |
8782 | 0 | fn evaluate_type_cast( |
8783 | 0 | &mut self, |
8784 | 0 | expr: &Expr, |
8785 | 0 | target_type: &str, |
8786 | 0 | deadline: Instant, |
8787 | 0 | depth: usize, |
8788 | 0 | ) -> Result<Value> { |
8789 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
8790 | | |
8791 | 0 | match target_type { |
8792 | 0 | "int" | "i32" | "i64" => match value { |
8793 | 0 | Value::Int(n) => Ok(Value::Int(n)), |
8794 | 0 | Value::Float(f) => Ok(Value::Int(f as i64)), |
8795 | 0 | Value::Bool(b) => Ok(Value::Int(i64::from(b))), |
8796 | 0 | Value::String(s) => s.parse::<i64>() |
8797 | 0 | .map(Value::Int) |
8798 | 0 | .map_err(|_| anyhow::anyhow!("Cannot cast '{}' to int", s)), |
8799 | 0 | _ => bail!("Cannot cast {:?} to int", value), |
8800 | | }, |
8801 | 0 | "float" | "f32" | "f64" => match value { |
8802 | 0 | Value::Float(f) => Ok(Value::Float(f)), |
8803 | 0 | Value::Int(n) => Ok(Value::Float(n as f64)), |
8804 | 0 | Value::Bool(b) => Ok(Value::Float(if b { 1.0 } else { 0.0 })), |
8805 | 0 | Value::String(s) => s.parse::<f64>() |
8806 | 0 | .map(Value::Float) |
8807 | 0 | .map_err(|_| anyhow::anyhow!("Cannot cast '{}' to float", s)), |
8808 | 0 | _ => bail!("Cannot cast {:?} to float", value), |
8809 | | }, |
8810 | 0 | "string" | "str" => match value { |
8811 | 0 | Value::Char(c) => Ok(Value::String(c.to_string())), |
8812 | 0 | _ => Ok(Value::String(value.to_string())), |
8813 | | }, |
8814 | 0 | "bool" => match value { |
8815 | 0 | Value::Bool(b) => Ok(Value::Bool(b)), |
8816 | 0 | Value::Int(n) => Ok(Value::Bool(n != 0)), |
8817 | 0 | Value::Float(f) => Ok(Value::Bool(f != 0.0)), |
8818 | 0 | Value::String(s) => Ok(Value::Bool(!s.is_empty() && s != "false")), |
8819 | 0 | _ => bail!("Cannot cast {:?} to bool", value), |
8820 | | }, |
8821 | 0 | _ => bail!("Unknown type for casting: {}", target_type), |
8822 | | } |
8823 | 0 | } |
8824 | | |
8825 | | /// Evaluate `random()` function - returns float between 0.0 and 1.0 |
8826 | 0 | fn evaluate_random( |
8827 | 0 | &mut self, |
8828 | 0 | args: &[Expr], |
8829 | 0 | _deadline: Instant, |
8830 | 0 | _depth: usize, |
8831 | 0 | ) -> Result<Value> { |
8832 | | use std::time::{SystemTime, UNIX_EPOCH}; |
8833 | | |
8834 | 0 | if !args.is_empty() { |
8835 | 0 | bail!("random() expects no arguments"); |
8836 | 0 | } |
8837 | | // Use a simple linear congruential generator for deterministic behavior in tests |
8838 | | // In production, you'd want to use rand crate |
8839 | 0 | let seed = SystemTime::now() |
8840 | 0 | .duration_since(UNIX_EPOCH) |
8841 | 0 | .unwrap() |
8842 | 0 | .as_nanos() as u64; |
8843 | | // Use a safe LCG that won't overflow |
8844 | 0 | let a = 1_664_525u64; |
8845 | 0 | let c = 1_013_904_223u64; |
8846 | 0 | let m = 1u64 << 32; |
8847 | 0 | let random_value = ((seed.wrapping_mul(a).wrapping_add(c)) % m) as f64 / m as f64; |
8848 | 0 | Ok(Value::Float(random_value)) |
8849 | 0 | } |
8850 | | |
8851 | | /// Execute a user-defined function or lambda by name. |
8852 | | /// |
8853 | | /// Looks up the function in bindings and executes it with parameter binding. |
8854 | | /// Handles both regular functions and lambdas with identical execution logic. |
8855 | | /// |
8856 | | /// # Arguments |
8857 | | /// |
8858 | | /// * `func_name` - Name of the function to execute |
8859 | | /// * `args` - Arguments to pass to the function |
8860 | | /// * `deadline` - Execution deadline for timeout handling |
8861 | | /// * `depth` - Current recursion depth |
8862 | | /// |
8863 | | /// Example Usage: |
8864 | | /// |
8865 | | /// Executes a user-defined function stored in bindings: |
8866 | | /// - Looks up function by name |
8867 | | /// - Validates argument count |
8868 | | /// - Binds parameters to arguments |
8869 | | /// - Evaluates function body in new scope |
8870 | 0 | fn execute_user_defined_function( |
8871 | 0 | &mut self, |
8872 | 0 | func_name: &str, |
8873 | 0 | args: &[Expr], |
8874 | 0 | deadline: Instant, |
8875 | 0 | depth: usize, |
8876 | 0 | ) -> Result<Value> { |
8877 | 0 | if let Some(func_value) = self.bindings.get(func_name).cloned() { |
8878 | 0 | match func_value { |
8879 | 0 | Value::Function { params, body, .. } => { |
8880 | 0 | self.execute_function_with_params(func_name, ¶ms, &body, args, deadline, depth, "Function") |
8881 | | } |
8882 | 0 | Value::Lambda { params, body } => { |
8883 | 0 | self.execute_function_with_params(func_name, ¶ms, &body, args, deadline, depth, "Lambda") |
8884 | | } |
8885 | | _ => { |
8886 | 0 | bail!("'{}' is not a function", func_name); |
8887 | | } |
8888 | | } |
8889 | | } else { |
8890 | 0 | bail!("Unknown function: {}", func_name); |
8891 | | } |
8892 | 0 | } |
8893 | | |
8894 | | /// Execute a function or lambda with parameter binding and scope management. |
8895 | | /// |
8896 | | /// This helper consolidates the common logic between functions and lambdas: |
8897 | | /// - Validates argument count matches parameter count |
8898 | | /// - Saves current bindings scope |
8899 | | /// - Binds arguments to parameters |
8900 | | /// - Executes function body |
8901 | | /// - Restores previous scope |
8902 | | /// |
8903 | | /// # Arguments |
8904 | | /// |
8905 | | /// * `func_name` - Name of the function (for error messages) |
8906 | | /// * `params` - Function parameter names |
8907 | | /// * `body` - Function body expression |
8908 | | /// * `args` - Arguments to bind to parameters |
8909 | | /// * `deadline` - Execution deadline |
8910 | | /// * `depth` - Recursion depth |
8911 | | /// * `func_type` - Either "Function" or "Lambda" for error messages |
8912 | 0 | fn execute_function_with_params( |
8913 | 0 | &mut self, |
8914 | 0 | func_name: &str, |
8915 | 0 | params: &[String], |
8916 | 0 | body: &Expr, |
8917 | 0 | args: &[Expr], |
8918 | 0 | deadline: Instant, |
8919 | 0 | depth: usize, |
8920 | 0 | func_type: &str, |
8921 | 0 | ) -> Result<Value> { |
8922 | 0 | if args.len() != params.len() { |
8923 | 0 | bail!( |
8924 | 0 | "{} {} expects {} arguments, got {}", |
8925 | | func_type, |
8926 | | func_name, |
8927 | 0 | params.len(), |
8928 | 0 | args.len() |
8929 | | ); |
8930 | 0 | } |
8931 | | |
8932 | 0 | let saved_bindings = self.bindings.clone(); |
8933 | | |
8934 | 0 | for (param, arg) in params.iter().zip(args.iter()) { |
8935 | 0 | let arg_value = self.evaluate_expr(arg, deadline, depth + 1)?; |
8936 | 0 | self.bindings.insert(param.clone(), arg_value); |
8937 | | } |
8938 | | |
8939 | 0 | let result = self.evaluate_function_body(body, deadline, depth)?; |
8940 | 0 | self.bindings = saved_bindings; |
8941 | 0 | Ok(result) |
8942 | 0 | } |
8943 | | |
8944 | | /// Validate argument count for math functions. |
8945 | | /// |
8946 | | /// Example Usage: |
8947 | | /// |
8948 | | /// Validates that a function receives the expected number of arguments: |
8949 | | /// - sqrt(x) expects exactly 1 argument |
8950 | | /// - pow(x, y) expects exactly 2 arguments |
8951 | | /// - Returns an error if count doesn't match |
8952 | 0 | fn validate_arg_count(&self, func_name: &str, args: &[Expr], expected: usize) -> Result<()> { |
8953 | 0 | if args.len() != expected { |
8954 | 0 | bail!("{} takes exactly {} argument{}", func_name, expected, if expected == 1 { "" } else { "s" }); |
8955 | 0 | } |
8956 | 0 | Ok(()) |
8957 | 0 | } |
8958 | | |
8959 | | /// Validate argument count is within a range |
8960 | 0 | fn validate_arg_range(&self, func_name: &str, args: &[Expr], min: usize, max: usize) -> Result<()> { |
8961 | 0 | let count = args.len(); |
8962 | 0 | if count < min || count > max { |
8963 | 0 | if min == max { |
8964 | 0 | return self.validate_arg_count(func_name, args, min); |
8965 | 0 | } |
8966 | 0 | bail!("{} takes between {} and {} arguments, got {}", func_name, min, max, count); |
8967 | 0 | } |
8968 | 0 | Ok(()) |
8969 | 0 | } |
8970 | | |
8971 | | /// Validate minimum argument count |
8972 | 0 | fn validate_min_args(&self, func_name: &str, args: &[Expr], min: usize) -> Result<()> { |
8973 | 0 | if args.len() < min { |
8974 | 0 | bail!("{} requires at least {} argument{}, got {}", |
8975 | 0 | func_name, min, if min == 1 { "" } else { "s" }, args.len()); |
8976 | 0 | } |
8977 | 0 | Ok(()) |
8978 | 0 | } |
8979 | | |
8980 | | /// Validate maximum argument count |
8981 | 0 | fn validate_max_args(&self, func_name: &str, args: &[Expr], max: usize) -> Result<()> { |
8982 | 0 | if args.len() > max { |
8983 | 0 | bail!("{} takes at most {} argument{}, got {}", |
8984 | 0 | func_name, max, if max == 1 { "" } else { "s" }, args.len()); |
8985 | 0 | } |
8986 | 0 | Ok(()) |
8987 | 0 | } |
8988 | | |
8989 | | /// Create error for unknown method |
8990 | 0 | fn unknown_method_error(&self, type_name: &str, method: &str) -> Result<Value> { |
8991 | 0 | bail!("Unknown {} method: {}", type_name, method) |
8992 | 0 | } |
8993 | | |
8994 | | /// Create error for type mismatch |
8995 | 0 | fn type_error(&self, func_name: &str, expected: &str, got: &Value) -> Result<Value> { |
8996 | 0 | bail!("{} expects {}, got {:?}", func_name, expected, got) |
8997 | 0 | } |
8998 | | |
8999 | | /// Check resource limits (timeout and recursion depth) |
9000 | 0 | fn check_resource_limits(&self, deadline: Instant, depth: usize) -> Result<()> { |
9001 | 0 | if Instant::now() > deadline { |
9002 | 0 | bail!("Evaluation timeout exceeded"); |
9003 | 0 | } |
9004 | 0 | if depth > self.config.max_depth { |
9005 | 0 | bail!("Maximum recursion depth {} exceeded. \n Hint: Check for infinite recursion or increase max_depth if needed", self.config.max_depth); |
9006 | 0 | } |
9007 | 0 | Ok(()) |
9008 | 0 | } |
9009 | | |
9010 | | /// Evaluate a single argument expression |
9011 | 0 | fn evaluate_arg(&mut self, args: &[Expr], index: usize, deadline: Instant, depth: usize) -> Result<Value> { |
9012 | 0 | args.get(index) |
9013 | 0 | .ok_or_else(|| anyhow::anyhow!("Missing argument at index {}", index)) |
9014 | 0 | .and_then(|arg| self.evaluate_expr(arg, deadline, depth + 1)) |
9015 | 0 | } |
9016 | | |
9017 | | /// Create a string value from a string-like type |
9018 | 0 | fn string_value(s: impl Into<String>) -> Value { |
9019 | 0 | Value::String(s.into()) |
9020 | 0 | } |
9021 | | |
9022 | | /// Create an integer value |
9023 | 0 | fn int_value(n: i64) -> Value { |
9024 | 0 | Value::Int(n) |
9025 | 0 | } |
9026 | | |
9027 | | /// Create a float value |
9028 | 0 | fn float_value(f: f64) -> Value { |
9029 | 0 | Value::Float(f) |
9030 | 0 | } |
9031 | | |
9032 | | /// Execute a closure with saved bindings that will be restored afterwards |
9033 | 0 | fn with_saved_bindings<F, R>(&mut self, f: F) -> R |
9034 | 0 | where |
9035 | 0 | F: FnOnce(&mut Self) -> R, |
9036 | | { |
9037 | 0 | let saved_bindings = self.bindings.clone(); |
9038 | 0 | let result = f(self); |
9039 | 0 | self.bindings = saved_bindings; |
9040 | 0 | result |
9041 | 0 | } |
9042 | | |
9043 | | /// Add a binding temporarily and execute a closure |
9044 | 0 | fn with_binding<F, R>(&mut self, name: String, value: Value, f: F) -> R |
9045 | 0 | where |
9046 | 0 | F: FnOnce(&mut Self) -> R, |
9047 | | { |
9048 | 0 | let saved_bindings = self.bindings.clone(); |
9049 | 0 | self.bindings.insert(name, value); |
9050 | 0 | let result = f(self); |
9051 | 0 | self.bindings = saved_bindings; |
9052 | 0 | result |
9053 | 0 | } |
9054 | | |
9055 | | /// Create a list value |
9056 | 0 | fn list_value(items: Vec<Value>) -> Value { |
9057 | 0 | Value::List(items) |
9058 | 0 | } |
9059 | | |
9060 | | /// Create a boolean value |
9061 | 0 | fn bool_value(b: bool) -> Value { |
9062 | 0 | Value::Bool(b) |
9063 | 0 | } |
9064 | | |
9065 | | /// Create an Ok result with a list value |
9066 | 0 | fn ok_list(items: Vec<Value>) -> Result<Value> { |
9067 | 0 | Ok(Self::list_value(items)) |
9068 | 0 | } |
9069 | | |
9070 | | /// Create an Ok result with a bool value |
9071 | 0 | fn ok_bool(b: bool) -> Result<Value> { |
9072 | 0 | Ok(Self::bool_value(b)) |
9073 | 0 | } |
9074 | | |
9075 | | /// Create an Ok result with a string value |
9076 | 0 | fn ok_string(s: impl Into<String>) -> Result<Value> { |
9077 | 0 | Ok(Self::string_value(s)) |
9078 | 0 | } |
9079 | | |
9080 | | /// Create an Ok result with an integer value |
9081 | 0 | fn ok_int(n: i64) -> Result<Value> { |
9082 | 0 | Ok(Self::int_value(n)) |
9083 | 0 | } |
9084 | | |
9085 | | /// Create an Ok result with a float value |
9086 | 0 | fn ok_float(f: f64) -> Result<Value> { |
9087 | 0 | Ok(Self::float_value(f)) |
9088 | 0 | } |
9089 | | |
9090 | | /// Create an Ok result with null value |
9091 | 0 | fn ok_null() -> Result<Value> { |
9092 | 0 | Ok(Self::create_option_none()) |
9093 | 0 | } |
9094 | | |
9095 | | /// Create a character value |
9096 | 0 | fn char_value(c: char) -> Value { |
9097 | 0 | Value::Char(c) |
9098 | 0 | } |
9099 | | |
9100 | | /// Create an object value |
9101 | 0 | fn object_value(map: std::collections::HashMap<String, Value>) -> Value { |
9102 | 0 | Value::Object(map) |
9103 | 0 | } |
9104 | | |
9105 | | /// Create an Ok result with a character value |
9106 | 0 | fn ok_char(c: char) -> Result<Value> { |
9107 | 0 | Ok(Self::char_value(c)) |
9108 | 0 | } |
9109 | | |
9110 | | /// Create an Ok result with an object value |
9111 | 0 | fn ok_object(map: std::collections::HashMap<String, Value>) -> Result<Value> { |
9112 | 0 | Ok(Self::object_value(map)) |
9113 | 0 | } |
9114 | | |
9115 | | /// Create an Ok result with a nil value |
9116 | 0 | fn ok_nil() -> Result<Value> { |
9117 | 0 | Ok(Value::Nil) |
9118 | 0 | } |
9119 | | |
9120 | | /// Create a tuple value |
9121 | 0 | fn tuple_value(items: Vec<Value>) -> Value { |
9122 | 0 | Value::Tuple(items) |
9123 | 0 | } |
9124 | | |
9125 | | /// Create an Ok result with a tuple value |
9126 | 0 | fn ok_tuple(items: Vec<Value>) -> Result<Value> { |
9127 | 0 | Ok(Self::tuple_value(items)) |
9128 | 0 | } |
9129 | | |
9130 | | /// Create a unit value |
9131 | 0 | fn unit_value() -> Value { |
9132 | 0 | Value::Unit |
9133 | 0 | } |
9134 | | |
9135 | | /// Create a `HashMap` value |
9136 | 0 | fn hashmap_value(map: std::collections::HashMap<Value, Value>) -> Value { |
9137 | 0 | Value::HashMap(map) |
9138 | 0 | } |
9139 | | |
9140 | | /// Create a `HashSet` value |
9141 | 0 | fn hashset_value(set: std::collections::HashSet<Value>) -> Value { |
9142 | 0 | Value::HashSet(set) |
9143 | 0 | } |
9144 | | |
9145 | | /// Create an Ok result with a unit value |
9146 | 0 | fn ok_unit() -> Result<Value> { |
9147 | 0 | Ok(Self::unit_value()) |
9148 | 0 | } |
9149 | | |
9150 | | /// Create an Ok result with a `HashMap` value |
9151 | 0 | fn ok_hashmap(map: std::collections::HashMap<Value, Value>) -> Result<Value> { |
9152 | 0 | Ok(Self::hashmap_value(map)) |
9153 | 0 | } |
9154 | | |
9155 | | /// Create an Ok result with a `HashSet` value |
9156 | 0 | fn ok_hashset(set: std::collections::HashSet<Value>) -> Result<Value> { |
9157 | 0 | Ok(Self::hashset_value(set)) |
9158 | 0 | } |
9159 | | |
9160 | | /// Create a Range value |
9161 | 0 | fn range_value(start: i64, end: i64, inclusive: bool) -> Value { |
9162 | 0 | Value::Range { start, end, inclusive } |
9163 | 0 | } |
9164 | | |
9165 | | /// Create a `DataFrame` value |
9166 | 0 | fn dataframe_value(columns: Vec<DataFrameColumn>) -> Value { |
9167 | 0 | Value::DataFrame { columns } |
9168 | 0 | } |
9169 | | |
9170 | | /// Create an `EnumVariant` value |
9171 | 0 | fn enum_variant_value(enum_name: String, variant_name: String, data: Option<Vec<Value>>) -> Value { |
9172 | 0 | Value::EnumVariant { enum_name, variant_name, data } |
9173 | 0 | } |
9174 | | |
9175 | | /// Create an Ok result with a Range value |
9176 | 0 | fn ok_range(start: i64, end: i64, inclusive: bool) -> Result<Value> { |
9177 | 0 | Ok(Self::range_value(start, end, inclusive)) |
9178 | 0 | } |
9179 | | |
9180 | | /// Create an Ok result with a `DataFrame` value |
9181 | 0 | fn ok_dataframe(columns: Vec<DataFrameColumn>) -> Result<Value> { |
9182 | 0 | Ok(Self::dataframe_value(columns)) |
9183 | 0 | } |
9184 | | |
9185 | | /// Create an Ok result with an `EnumVariant` value |
9186 | 0 | fn ok_enum_variant(enum_name: String, variant_name: String, data: Option<Vec<Value>>) -> Result<Value> { |
9187 | 0 | Ok(Self::enum_variant_value(enum_name, variant_name, data)) |
9188 | 0 | } |
9189 | | |
9190 | | // === Argument Validation Helper Functions === |
9191 | | |
9192 | | /// Validate that a function receives the expected number of arguments (with "exactly" phrasing) |
9193 | 0 | fn validate_exact_args(func_name: &str, expected: usize, actual: usize) -> Result<()> { |
9194 | 0 | if actual != expected { |
9195 | 0 | bail!("{} expects exactly {} argument{}, got {}", |
9196 | 0 | func_name, expected, if expected == 1 { "" } else { "s" }, actual); |
9197 | 0 | } |
9198 | 0 | Ok(()) |
9199 | 0 | } |
9200 | | |
9201 | | /// Validate that a function receives no arguments |
9202 | 0 | fn validate_zero_args(func_name: &str, actual: usize) -> Result<()> { |
9203 | 0 | if actual != 0 { |
9204 | 0 | bail!("{} expects no arguments, got {}", func_name, actual); |
9205 | 0 | } |
9206 | 0 | Ok(()) |
9207 | 0 | } |
9208 | | |
9209 | | /// Validate that a function receives a numeric argument |
9210 | 0 | fn numeric_arg_error(func_name: &str) -> String { |
9211 | 0 | format!("{func_name}() expects a numeric argument") |
9212 | 0 | } |
9213 | | |
9214 | | /// Validate that a function receives numeric arguments (plural) |
9215 | 0 | fn numeric_args_error(func_name: &str) -> String { |
9216 | 0 | format!("{func_name} expects numeric arguments") |
9217 | 0 | } |
9218 | | |
9219 | | /// Create a method not supported error message |
9220 | 0 | fn method_not_supported(method: &str, type_desc: &str) -> anyhow::Error { |
9221 | 0 | anyhow::anyhow!("Method {} not supported on {}", method, type_desc) |
9222 | 0 | } |
9223 | | |
9224 | | /// Preprocess macro syntax by converting macro calls (!) to function calls |
9225 | 0 | fn preprocess_macro_syntax(input: &str) -> String { |
9226 | 0 | input |
9227 | 0 | .replace("println!", "println") |
9228 | 0 | .replace("print!", "print") |
9229 | 0 | .replace("assert!", "assert") |
9230 | 0 | .replace("assert_eq!", "assert_eq") |
9231 | 0 | .replace("panic!", "panic") |
9232 | 0 | .replace("vec!", "vec") |
9233 | 0 | .replace("format!", "format") |
9234 | 0 | } |
9235 | | |
9236 | | /// Helper to evaluate the first argument from an argument list |
9237 | 0 | fn evaluate_first_arg(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
9238 | 0 | self.evaluate_arg(args, 0, deadline, depth) |
9239 | 0 | } |
9240 | | |
9241 | | /// Apply unary math operation to a numeric value. |
9242 | | /// |
9243 | | /// # Example Usage |
9244 | | /// Validates that the correct number of arguments is provided to a function. |
9245 | | /// |
9246 | | /// # use `ruchy::runtime::repl::Repl`; |
9247 | | /// # use `ruchy::runtime::value::Value`; |
9248 | | /// let repl = `Repl::new()`; |
9249 | | /// let result = `repl.apply_unary_math_op(&Value::Int(4)`, "`sqrt").unwrap()`; |
9250 | | /// assert!(matches!(result, `Value::Float`(_))); |
9251 | | /// ``` |
9252 | 0 | fn apply_unary_math_op(&self, value: &Value, op: &str) -> Result<Value> { |
9253 | 0 | match (value, op) { |
9254 | 0 | (Value::Int(n), "sqrt") => { |
9255 | | #[allow(clippy::cast_precision_loss)] |
9256 | 0 | Ok(Value::Float((*n as f64).sqrt())) |
9257 | | } |
9258 | 0 | (Value::Float(f), "sqrt") => Ok(Value::Float(f.sqrt())), |
9259 | 0 | (Value::Int(n), "abs") => Self::ok_int(n.abs()), |
9260 | 0 | (Value::Float(f), "abs") => Self::ok_float(f.abs()), |
9261 | 0 | (Value::Int(n), "floor") => Ok(Value::Int(*n)), // Already floored |
9262 | 0 | (Value::Float(f), "floor") => Self::ok_float(f.floor()), |
9263 | 0 | (Value::Int(n), "ceil") => Ok(Value::Int(*n)), // Already ceiled |
9264 | 0 | (Value::Float(f), "ceil") => Self::ok_float(f.ceil()), |
9265 | 0 | (Value::Int(n), "round") => Ok(Value::Int(*n)), // Already rounded |
9266 | 0 | (Value::Float(f), "round") => Self::ok_float(f.round()), |
9267 | 0 | _ => bail!("{}", Self::numeric_args_error(op)), |
9268 | | } |
9269 | 0 | } |
9270 | | |
9271 | | /// Apply binary math operation to two numeric values. |
9272 | | /// |
9273 | | /// # Example Usage |
9274 | | /// Applies unary math operations like sqrt, abs, floor, ceil, round to numeric values. |
9275 | | /// |
9276 | | /// # use `ruchy::runtime::repl::Repl`; |
9277 | | /// # use `ruchy::runtime::value::Value`; |
9278 | | /// let repl = `Repl::new()`; |
9279 | | /// let result = `repl.apply_binary_math_op(&Value::Int(2)`, &`Value::Int(3)`, "`pow").unwrap()`; |
9280 | | /// assert!(matches!(result, `Value::Int(8)`)); |
9281 | | /// ``` |
9282 | 0 | fn apply_binary_math_op(&self, a: &Value, b: &Value, op: &str) -> Result<Value> { |
9283 | 0 | match (a, b, op) { |
9284 | 0 | (Value::Int(base), Value::Int(exp), "pow") => { |
9285 | 0 | if *exp < 0 { |
9286 | | #[allow(clippy::cast_precision_loss)] |
9287 | 0 | Ok(Value::Float((*base as f64).powi(*exp as i32))) |
9288 | | } else { |
9289 | 0 | let exp_u32 = u32::try_from(*exp).map_err(|_| anyhow::anyhow!("Exponent too large"))?; |
9290 | 0 | match base.checked_pow(exp_u32) { |
9291 | 0 | Some(result) => Ok(Value::Int(result)), |
9292 | 0 | None => bail!("Integer overflow in pow({}, {})", base, exp), |
9293 | | } |
9294 | | } |
9295 | | } |
9296 | 0 | (Value::Float(base), Value::Float(exp), "pow") => Ok(Value::Float(base.powf(*exp))), |
9297 | 0 | (Value::Int(base), Value::Float(exp), "pow") => { |
9298 | | #[allow(clippy::cast_precision_loss)] |
9299 | 0 | Ok(Value::Float((*base as f64).powf(*exp))) |
9300 | | } |
9301 | 0 | (Value::Float(base), Value::Int(exp), "pow") => { |
9302 | | #[allow(clippy::cast_precision_loss)] |
9303 | 0 | Ok(Value::Float(base.powi(*exp as i32))) |
9304 | | } |
9305 | 0 | (Value::Int(x), Value::Int(y), "min") => Ok(Value::Int((*x).min(*y))), |
9306 | 0 | (Value::Float(x), Value::Float(y), "min") => Ok(Value::Float(x.min(*y))), |
9307 | 0 | (Value::Int(x), Value::Float(y), "min") => { |
9308 | | #[allow(clippy::cast_precision_loss)] |
9309 | 0 | Ok(Value::Float((*x as f64).min(*y))) |
9310 | | } |
9311 | 0 | (Value::Float(x), Value::Int(y), "min") => { |
9312 | | #[allow(clippy::cast_precision_loss)] |
9313 | 0 | Ok(Value::Float(x.min(*y as f64))) |
9314 | | } |
9315 | 0 | (Value::Int(x), Value::Int(y), "max") => Ok(Value::Int((*x).max(*y))), |
9316 | 0 | (Value::Float(x), Value::Float(y), "max") => Ok(Value::Float(x.max(*y))), |
9317 | 0 | (Value::Int(x), Value::Float(y), "max") => { |
9318 | | #[allow(clippy::cast_precision_loss)] |
9319 | 0 | Ok(Value::Float((*x as f64).max(*y))) |
9320 | | } |
9321 | 0 | (Value::Float(x), Value::Int(y), "max") => { |
9322 | | #[allow(clippy::cast_precision_loss)] |
9323 | 0 | Ok(Value::Float(x.max(*y as f64))) |
9324 | | } |
9325 | 0 | _ => bail!("{}", Self::numeric_args_error(op)), |
9326 | | } |
9327 | 0 | } |
9328 | | |
9329 | | /// Handle built-in math functions (sqrt, pow, abs, min, max, floor, ceil, round). |
9330 | | /// |
9331 | | /// Returns `Ok(Some(value))` if the function name matches a math function, |
9332 | | /// `Ok(None)` if it doesn't match any math function, or `Err` if there's an error. |
9333 | | /// |
9334 | | /// # Example Usage |
9335 | | /// Tries to call math functions like sqrt, pow, abs, min, max, floor, ceil, round. |
9336 | | /// Dispatches to appropriate unary or binary math operation handler. |
9337 | 0 | fn try_math_function( |
9338 | 0 | &mut self, |
9339 | 0 | func_name: &str, |
9340 | 0 | args: &[Expr], |
9341 | 0 | deadline: Instant, |
9342 | 0 | depth: usize, |
9343 | 0 | ) -> Result<Option<Value>> { |
9344 | 0 | match func_name { |
9345 | | // Unary math functions |
9346 | 0 | "sqrt" | "abs" | "floor" | "ceil" | "round" => { |
9347 | 0 | self.validate_arg_count(func_name, args, 1)?; |
9348 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
9349 | 0 | Ok(Some(self.apply_unary_math_op(&value, func_name)?)) |
9350 | | } |
9351 | | // Binary math functions |
9352 | 0 | "pow" | "min" | "max" => { |
9353 | 0 | self.validate_arg_count(func_name, args, 2)?; |
9354 | 0 | let a = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
9355 | 0 | let b = self.evaluate_arg(args, 1, deadline, depth)?; |
9356 | 0 | Ok(Some(self.apply_binary_math_op(&a, &b, func_name)?)) |
9357 | | } |
9358 | 0 | _ => Ok(None), // Not a math function |
9359 | | } |
9360 | 0 | } |
9361 | | |
9362 | | /// Handle built-in enum variant constructors (None, Some, Ok, Err). |
9363 | | /// |
9364 | | /// Returns `Ok(Some(value))` if the function name matches an enum constructor, |
9365 | | /// `Ok(None)` if it doesn't match any constructor, or `Err` if there's an error. |
9366 | | /// |
9367 | | /// # Example Usage |
9368 | | /// Applies binary math operations like pow, min, max to two numeric values. |
9369 | | /// |
9370 | | /// # use `ruchy::runtime::repl::Repl`; |
9371 | | /// # use `ruchy::frontend::ast::Expr`; |
9372 | | /// # use `std::time::Instant`; |
9373 | | /// let mut repl = `Repl::new()`; |
9374 | | /// let args = vec![]; |
9375 | | /// let deadline = `Instant::now()` + `std::time::Duration::from_secs(1)`; |
9376 | | /// let result = `repl.try_enum_constructor("None`", &args, deadline, `0).unwrap()`; |
9377 | | /// `assert!(result.is_some())`; |
9378 | | /// ``` |
9379 | 0 | fn try_enum_constructor( |
9380 | 0 | &mut self, |
9381 | 0 | func_name: &str, |
9382 | 0 | args: &[Expr], |
9383 | 0 | deadline: Instant, |
9384 | 0 | depth: usize, |
9385 | 0 | ) -> Result<Option<Value>> { |
9386 | 0 | match func_name { |
9387 | 0 | "None" => { |
9388 | 0 | if !args.is_empty() { |
9389 | 0 | bail!("None takes no arguments"); |
9390 | 0 | } |
9391 | 0 | Ok(Some(Value::EnumVariant { |
9392 | 0 | enum_name: "Option".to_string(), |
9393 | 0 | variant_name: "None".to_string(), |
9394 | 0 | data: None, |
9395 | 0 | })) |
9396 | | } |
9397 | 0 | "Some" => { |
9398 | 0 | if args.len() != 1 { |
9399 | 0 | bail!("Some takes exactly 1 argument"); |
9400 | 0 | } |
9401 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
9402 | 0 | Ok(Some(Value::EnumVariant { |
9403 | 0 | enum_name: "Option".to_string(), |
9404 | 0 | variant_name: "Some".to_string(), |
9405 | 0 | data: Some(vec![value]), |
9406 | 0 | })) |
9407 | | } |
9408 | 0 | "Ok" => { |
9409 | 0 | if args.len() != 1 { |
9410 | 0 | bail!("Ok takes exactly 1 argument"); |
9411 | 0 | } |
9412 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
9413 | 0 | Ok(Some(Value::EnumVariant { |
9414 | 0 | enum_name: "Result".to_string(), |
9415 | 0 | variant_name: "Ok".to_string(), |
9416 | 0 | data: Some(vec![value]), |
9417 | 0 | })) |
9418 | | } |
9419 | 0 | "Err" => { |
9420 | 0 | if args.len() != 1 { |
9421 | 0 | bail!("Err takes exactly 1 argument"); |
9422 | 0 | } |
9423 | 0 | let value = self.evaluate_first_arg(args, deadline, depth)?; |
9424 | 0 | Ok(Some(Value::EnumVariant { |
9425 | 0 | enum_name: "Result".to_string(), |
9426 | 0 | variant_name: "Err".to_string(), |
9427 | 0 | data: Some(vec![value]), |
9428 | 0 | })) |
9429 | | } |
9430 | 0 | _ => Ok(None), // Not an enum constructor |
9431 | | } |
9432 | 0 | } |
9433 | | |
9434 | | /// Evaluate user-defined functions |
9435 | 0 | fn evaluate_user_function( |
9436 | 0 | &mut self, |
9437 | 0 | func_name: &str, |
9438 | 0 | args: &[Expr], |
9439 | 0 | deadline: Instant, |
9440 | 0 | depth: usize, |
9441 | 0 | ) -> Result<Value> { |
9442 | | // Try enum constructor first |
9443 | 0 | if let Some(result) = self.try_enum_constructor(func_name, args, deadline, depth)? { |
9444 | 0 | return Ok(result); |
9445 | 0 | } |
9446 | | |
9447 | | // Try built-in math function |
9448 | 0 | if let Some(result) = self.try_math_function(func_name, args, deadline, depth)? { |
9449 | 0 | return Ok(result); |
9450 | 0 | } |
9451 | | |
9452 | | // Try user-defined function lookup and execution |
9453 | 0 | self.execute_user_defined_function(func_name, args, deadline, depth) |
9454 | 0 | } |
9455 | | |
9456 | | /// Helper to evaluate a function body and handle return statements |
9457 | 0 | fn evaluate_function_body( |
9458 | 0 | &mut self, |
9459 | 0 | body: &Expr, |
9460 | 0 | deadline: Instant, |
9461 | 0 | depth: usize, |
9462 | 0 | ) -> Result<Value> { |
9463 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
9464 | 0 | Ok(val) => Ok(val), |
9465 | 0 | Err(e) => { |
9466 | | // Check if this is a return statement |
9467 | 0 | let err_str = e.to_string(); |
9468 | 0 | if let Some(return_val) = err_str.strip_prefix("return:") { |
9469 | | // Parse the return value - it's already a formatted Value string |
9470 | | // For now, just extract the string representation |
9471 | | // The value was already evaluated, just passed through error |
9472 | 0 | if return_val == "()" { |
9473 | 0 | Self::ok_unit() |
9474 | 0 | } else if return_val.starts_with('"') && return_val.ends_with('"') { |
9475 | | // String value - remove quotes |
9476 | 0 | let s = return_val[1..return_val.len()-1].to_string(); |
9477 | 0 | Ok(Value::String(s)) |
9478 | 0 | } else if let Ok(i) = return_val.parse::<i64>() { |
9479 | 0 | Ok(Value::Int(i)) |
9480 | 0 | } else if let Ok(f) = return_val.parse::<f64>() { |
9481 | 0 | Ok(Value::Float(f)) |
9482 | 0 | } else if return_val == "true" { |
9483 | 0 | Self::ok_bool(true) |
9484 | 0 | } else if return_val == "false" { |
9485 | 0 | Self::ok_bool(false) |
9486 | | } else { |
9487 | | // Return as string for complex values |
9488 | 0 | Ok(Value::String(return_val.to_string())) |
9489 | | } |
9490 | | } else { |
9491 | 0 | Err(e) |
9492 | | } |
9493 | | } |
9494 | | } |
9495 | 0 | } |
9496 | | |
9497 | | /// Evaluate match expressions |
9498 | 0 | fn evaluate_match( |
9499 | 0 | &mut self, |
9500 | 0 | match_expr: &Expr, |
9501 | 0 | arms: &[MatchArm], |
9502 | 0 | deadline: Instant, |
9503 | 0 | depth: usize, |
9504 | 0 | ) -> Result<Value> { |
9505 | 0 | let match_value = self.evaluate_expr(match_expr, deadline, depth + 1)?; |
9506 | | |
9507 | 0 | for arm in arms { |
9508 | 0 | if let Some(bindings) = Self::pattern_matches(&match_value, &arm.pattern)? { |
9509 | 0 | let saved_bindings = self.bindings.clone(); |
9510 | | |
9511 | | // Apply pattern bindings temporarily |
9512 | 0 | for (name, value) in bindings { |
9513 | 0 | self.bindings.insert(name, value); |
9514 | 0 | } |
9515 | | |
9516 | | // Check pattern guard if present |
9517 | 0 | let guard_passes = if let Some(guard_expr) = &arm.guard { |
9518 | 0 | if let Value::Bool(b) = self.evaluate_expr(guard_expr, deadline, depth + 1)? { |
9519 | 0 | b |
9520 | | } else { |
9521 | 0 | self.bindings = saved_bindings; |
9522 | 0 | continue; // Guard didn't evaluate to boolean, try next arm |
9523 | | } |
9524 | | } else { |
9525 | 0 | true // No guard, so it passes |
9526 | | }; |
9527 | | |
9528 | 0 | if guard_passes { |
9529 | 0 | let result = self.evaluate_expr(&arm.body, deadline, depth + 1)?; |
9530 | 0 | self.bindings = saved_bindings; |
9531 | 0 | return Ok(result); |
9532 | 0 | } |
9533 | | |
9534 | | // Guard failed, restore bindings and try next arm |
9535 | 0 | self.bindings = saved_bindings; |
9536 | 0 | } |
9537 | | } |
9538 | | |
9539 | 0 | bail!("No matching pattern found in match expression"); |
9540 | 0 | } |
9541 | | |
9542 | | /// Evaluate pipeline expressions |
9543 | 0 | fn evaluate_pipeline( |
9544 | 0 | &mut self, |
9545 | 0 | expr: &Expr, |
9546 | 0 | stages: &[PipelineStage], |
9547 | 0 | deadline: Instant, |
9548 | 0 | depth: usize, |
9549 | 0 | ) -> Result<Value> { |
9550 | 0 | let mut current_value = self.evaluate_expr(expr, deadline, depth + 1)?; |
9551 | | |
9552 | 0 | for stage in stages { |
9553 | 0 | current_value = self.evaluate_pipeline_stage(¤t_value, stage, deadline, depth)?; |
9554 | | } |
9555 | | |
9556 | 0 | Ok(current_value) |
9557 | 0 | } |
9558 | | |
9559 | | /// Evaluate a single pipeline stage |
9560 | 0 | fn evaluate_pipeline_stage( |
9561 | 0 | &mut self, |
9562 | 0 | current_value: &Value, |
9563 | 0 | stage: &PipelineStage, |
9564 | 0 | deadline: Instant, |
9565 | 0 | depth: usize, |
9566 | 0 | ) -> Result<Value> { |
9567 | 0 | match &stage.op.kind { |
9568 | 0 | ExprKind::Call { func, args } => { |
9569 | 0 | let mut new_args = vec![Self::value_to_literal_expr(current_value, stage.span)?]; |
9570 | 0 | new_args.extend(args.iter().cloned()); |
9571 | | |
9572 | 0 | let new_call = Expr::new( |
9573 | 0 | ExprKind::Call { |
9574 | 0 | func: func.clone(), |
9575 | 0 | args: new_args, |
9576 | 0 | }, |
9577 | 0 | stage.span, |
9578 | | ); |
9579 | | |
9580 | 0 | self.evaluate_expr(&new_call, deadline, depth + 1) |
9581 | | } |
9582 | 0 | ExprKind::Identifier(_func_name) => { |
9583 | 0 | let call = Expr::new( |
9584 | | ExprKind::Call { |
9585 | 0 | func: stage.op.clone(), |
9586 | 0 | args: vec![Self::value_to_literal_expr(current_value, stage.span)?], |
9587 | | }, |
9588 | 0 | stage.span, |
9589 | | ); |
9590 | | |
9591 | 0 | self.evaluate_expr(&call, deadline, depth + 1) |
9592 | | } |
9593 | 0 | ExprKind::MethodCall { receiver: _, method, args } => { |
9594 | | // For method calls in pipeline, current_value becomes the receiver |
9595 | 0 | match current_value { |
9596 | 0 | Value::List(items) => { |
9597 | 0 | self.evaluate_list_methods(items.clone(), method, args, deadline, depth) |
9598 | | } |
9599 | 0 | Value::String(s) => { |
9600 | 0 | Self::evaluate_string_methods(s, method, args, deadline, depth) |
9601 | | } |
9602 | 0 | Value::Int(_) | Value::Float(_) => self.evaluate_numeric_methods(current_value, method), |
9603 | 0 | Value::Object(obj) => { |
9604 | 0 | Self::evaluate_object_methods(obj.clone(), method, args, deadline, depth) |
9605 | | } |
9606 | 0 | Value::HashMap(map) => { |
9607 | 0 | self.evaluate_hashmap_methods(map.clone(), method, args, deadline, depth) |
9608 | | } |
9609 | 0 | Value::HashSet(set) => { |
9610 | 0 | self.evaluate_hashset_methods(set.clone(), method, args, deadline, depth) |
9611 | | } |
9612 | | Value::EnumVariant { .. } => { |
9613 | 0 | self.evaluate_enum_methods(current_value.clone(), method, args, deadline, depth) |
9614 | | } |
9615 | 0 | _ => bail!("Cannot call method {} on value of this type", method), |
9616 | | } |
9617 | | } |
9618 | 0 | _ => bail!("Pipeline stages must be function calls, method calls, or identifiers"), |
9619 | | } |
9620 | 0 | } |
9621 | | |
9622 | | /// Convert value to literal expression for pipeline |
9623 | 0 | fn value_to_literal_expr(value: &Value, span: Span) -> Result<Expr> { |
9624 | 0 | let expr_kind = match value { |
9625 | 0 | Value::Int(n) => ExprKind::Literal(Literal::Integer(*n)), |
9626 | 0 | Value::Float(f) => ExprKind::Literal(Literal::Float(*f)), |
9627 | 0 | Value::String(s) => ExprKind::Literal(Literal::String(s.clone())), |
9628 | 0 | Value::Bool(b) => ExprKind::Literal(Literal::Bool(*b)), |
9629 | 0 | Value::Unit => ExprKind::Literal(Literal::Unit), |
9630 | 0 | Value::List(items) => { |
9631 | 0 | let elements: Result<Vec<Expr>> = items |
9632 | 0 | .iter() |
9633 | 0 | .map(|item| Self::value_to_literal_expr(item, span)) |
9634 | 0 | .collect(); |
9635 | 0 | ExprKind::List(elements?) |
9636 | | } |
9637 | 0 | _ => bail!("Cannot pipeline complex value types yet"), |
9638 | | }; |
9639 | 0 | Ok(Expr::new(expr_kind, span)) |
9640 | 0 | } |
9641 | | |
9642 | | /// Evaluate command execution |
9643 | 0 | fn evaluate_command( |
9644 | 0 | program: &str, |
9645 | 0 | args: &[String], |
9646 | 0 | _deadline: Instant, |
9647 | 0 | _depth: usize, |
9648 | 0 | ) -> Result<Value> { |
9649 | | use std::process::Command; |
9650 | | |
9651 | 0 | let output = Command::new(program) |
9652 | 0 | .args(args) |
9653 | 0 | .output() |
9654 | 0 | .map_err(|e| anyhow::anyhow!("Failed to execute command '{}': {}", program, e))?; |
9655 | | |
9656 | 0 | if output.status.success() { |
9657 | 0 | let stdout = String::from_utf8_lossy(&output.stdout).to_string(); |
9658 | 0 | Ok(Value::String(stdout.trim().to_string())) |
9659 | | } else { |
9660 | 0 | let stderr = String::from_utf8_lossy(&output.stderr).to_string(); |
9661 | 0 | Err(anyhow::anyhow!( |
9662 | 0 | "Command '{}' failed with exit code {:?}: {}", |
9663 | 0 | program, |
9664 | 0 | output.status.code(), |
9665 | 0 | stderr |
9666 | 0 | )) |
9667 | | } |
9668 | 0 | } |
9669 | | |
9670 | | /// Evaluate macro expansion |
9671 | 0 | fn evaluate_macro( |
9672 | 0 | &mut self, |
9673 | 0 | name: &str, |
9674 | 0 | args: &[Expr], |
9675 | 0 | deadline: Instant, |
9676 | 0 | depth: usize, |
9677 | 0 | ) -> Result<Value> { |
9678 | 0 | match name { |
9679 | 0 | "println" => { |
9680 | | // Evaluate all arguments and print them |
9681 | 0 | let mut output = String::new(); |
9682 | 0 | for (i, arg) in args.iter().enumerate() { |
9683 | 0 | if i > 0 { |
9684 | 0 | output.push(' '); |
9685 | 0 | } |
9686 | 0 | let value = self.evaluate_expr(arg, deadline, depth + 1)?; |
9687 | 0 | output.push_str(&value.to_string()); |
9688 | | } |
9689 | 0 | println!("{output}"); |
9690 | 0 | Self::ok_unit() |
9691 | | } |
9692 | 0 | "vec" => { |
9693 | | // Evaluate all arguments and create a vector |
9694 | 0 | let mut elements = Vec::new(); |
9695 | 0 | for arg in args { |
9696 | 0 | elements.push(self.evaluate_expr(arg, deadline, depth + 1)?); |
9697 | | } |
9698 | 0 | Self::ok_list(elements) |
9699 | | } |
9700 | | _ => { |
9701 | 0 | anyhow::bail!("Unknown macro: {}", name) |
9702 | | } |
9703 | | } |
9704 | 0 | } |
9705 | | |
9706 | | /// Evaluate import statements (complexity < 10) |
9707 | | /// Import standard library filesystem module (complexity: 6) |
9708 | 0 | fn import_std_fs(&mut self, items: &[ImportItem]) -> Result<()> { |
9709 | 0 | for item in items { |
9710 | 0 | match item { |
9711 | 0 | ImportItem::Named(name) if name == "read_file" => { |
9712 | 0 | // This function is already built-in |
9713 | 0 | } |
9714 | 0 | ImportItem::Named(name) if name == "write_file" => { |
9715 | 0 | // This function is already built-in |
9716 | 0 | } |
9717 | 0 | ImportItem::Named(name) if name == "fs" => { |
9718 | 0 | println!(" ✓ Imported fs module"); |
9719 | 0 | } |
9720 | 0 | _ => {} |
9721 | | } |
9722 | | } |
9723 | 0 | Ok(()) |
9724 | 0 | } |
9725 | | |
9726 | | /// Import standard library collections module (complexity: 3) |
9727 | 0 | fn import_std_collections(&mut self, items: &[ImportItem]) -> Result<()> { |
9728 | 0 | for item in items { |
9729 | 0 | if let ImportItem::Named(_name) = item { |
9730 | 0 | // Successfully imported |
9731 | 0 | } |
9732 | | } |
9733 | 0 | Ok(()) |
9734 | 0 | } |
9735 | | |
9736 | | /// Import performance-related modules (complexity: 2) |
9737 | 0 | fn import_performance_module(&mut self, path: &str) -> Result<()> { |
9738 | 0 | match path { |
9739 | 0 | "std::mem" => { |
9740 | 0 | self.bindings.insert("Array".to_string(), Value::String("Array constructor".to_string())); |
9741 | 0 | } |
9742 | 0 | "std::parallel" | "std::simd" | "std::cache" | "std::bench" | "std::profile" => { |
9743 | 0 | // Module functions will be accessible via namespace |
9744 | 0 | } |
9745 | 0 | _ => {} |
9746 | | } |
9747 | 0 | Ok(()) |
9748 | 0 | } |
9749 | | |
9750 | | /// Check if item should be imported (complexity: 4) |
9751 | 0 | fn should_import_item(items: &[ImportItem], func_name: &str) -> bool { |
9752 | 0 | items.is_empty() || items.iter().any(|item| match item { |
9753 | 0 | ImportItem::Wildcard => true, |
9754 | 0 | ImportItem::Named(item_name) => item_name == func_name, |
9755 | 0 | ImportItem::Aliased { name: item_name, .. } => item_name == func_name, |
9756 | 0 | }) |
9757 | 0 | } |
9758 | | |
9759 | | /// Import functions from cache (complexity: 3) |
9760 | 0 | fn import_from_cache(&mut self, cached_functions: &HashMap<String, Value>, items: &[ImportItem]) { |
9761 | 0 | for (func_name, func_value) in cached_functions { |
9762 | 0 | if Self::should_import_item(items, func_name) { |
9763 | 0 | self.bindings.insert(func_name.clone(), func_value.clone()); |
9764 | 0 | } |
9765 | | } |
9766 | 0 | } |
9767 | | |
9768 | | /// Load and cache a module from file (complexity: 7) |
9769 | 0 | fn load_and_cache_module(&mut self, path: &str, items: &[ImportItem]) -> Result<()> { |
9770 | 0 | let module_path = format!("{path}.ruchy"); |
9771 | | |
9772 | 0 | if !std::path::Path::new(&module_path).exists() { |
9773 | 0 | bail!("Module not found: {}", path); |
9774 | 0 | } |
9775 | | |
9776 | | // Read and parse the module file |
9777 | 0 | let module_content = std::fs::read_to_string(&module_path) |
9778 | 0 | .with_context(|| format!("Failed to read module file: {module_path}"))?; |
9779 | | |
9780 | 0 | let mut parser = crate::frontend::Parser::new(&module_content); |
9781 | 0 | let module_ast = parser.parse() |
9782 | 0 | .with_context(|| format!("Failed to parse module: {module_path}"))?; |
9783 | | |
9784 | | // Extract and cache all functions from the module |
9785 | 0 | let mut module_functions = HashMap::new(); |
9786 | 0 | self.extract_module_functions(&module_ast, &mut module_functions)?; |
9787 | | |
9788 | | // Store in cache for future imports |
9789 | 0 | self.module_cache.insert(path.to_string(), module_functions.clone()); |
9790 | | |
9791 | | // Import requested functions into current scope |
9792 | 0 | self.import_from_cache(&module_functions, items); |
9793 | | |
9794 | 0 | Ok(()) |
9795 | 0 | } |
9796 | | |
9797 | | /// Main import dispatcher (complexity: 8) |
9798 | 0 | fn evaluate_import(&mut self, path: &str, items: &[ImportItem]) -> Result<Value> { |
9799 | | // Handle standard library imports |
9800 | 0 | match path { |
9801 | 0 | "std::fs" | "std::fs::read_file" => { |
9802 | 0 | self.import_std_fs(items)?; |
9803 | | } |
9804 | 0 | "std::collections" => { |
9805 | 0 | self.import_std_collections(items)?; |
9806 | | } |
9807 | 0 | "std::mem" | "std::parallel" | "std::simd" | "std::cache" | "std::bench" | "std::profile" => { |
9808 | 0 | self.import_performance_module(path)?; |
9809 | | } |
9810 | | _ => { |
9811 | | // Check cache first |
9812 | 0 | if let Some(cached_functions) = self.module_cache.get(path).cloned() { |
9813 | 0 | self.import_from_cache(&cached_functions, items); |
9814 | 0 | } else { |
9815 | | // Load from file and cache |
9816 | 0 | self.load_and_cache_module(path, items)?; |
9817 | | } |
9818 | | } |
9819 | | } |
9820 | | |
9821 | 0 | Self::ok_unit() |
9822 | 0 | } |
9823 | | |
9824 | | /// Extract functions from a module AST into a `HashMap` for caching |
9825 | 0 | fn extract_module_functions(&mut self, module_ast: &Expr, functions_map: &mut HashMap<String, Value>) -> Result<()> { |
9826 | | // Extract all functions from the module for caching |
9827 | 0 | if let ExprKind::Block(exprs) = &module_ast.kind { |
9828 | 0 | for expr in exprs { |
9829 | 0 | if let ExprKind::Function { name, params, body, .. } = &expr.kind { |
9830 | | // Extract function and store in cache map |
9831 | 0 | let param_names: Vec<String> = params.iter() |
9832 | 0 | .map(|p| match &p.pattern { |
9833 | 0 | Pattern::Identifier(name) => name.clone(), |
9834 | 0 | _ => "unknown".to_string(), // Simplified for now |
9835 | 0 | }) |
9836 | 0 | .collect(); |
9837 | | |
9838 | 0 | let function_value = Value::Function { |
9839 | 0 | name: name.clone(), |
9840 | 0 | params: param_names, |
9841 | 0 | body: body.clone(), |
9842 | 0 | }; |
9843 | 0 | functions_map.insert(name.clone(), function_value); |
9844 | 0 | } |
9845 | | } |
9846 | 0 | } else if let ExprKind::Function { name, params, body, .. } = &module_ast.kind { |
9847 | | // Single function module |
9848 | 0 | let param_names: Vec<String> = params.iter() |
9849 | 0 | .map(|p| match &p.pattern { |
9850 | 0 | Pattern::Identifier(name) => name.clone(), |
9851 | 0 | _ => "unknown".to_string(), // Simplified for now |
9852 | 0 | }) |
9853 | 0 | .collect(); |
9854 | | |
9855 | 0 | let function_value = Value::Function { |
9856 | 0 | name: name.clone(), |
9857 | 0 | params: param_names, |
9858 | 0 | body: body.clone(), |
9859 | 0 | }; |
9860 | 0 | functions_map.insert(name.clone(), function_value); |
9861 | 0 | } |
9862 | | |
9863 | 0 | Ok(()) |
9864 | 0 | } |
9865 | | |
9866 | | /// Evaluate export statements (complexity < 10) |
9867 | 0 | fn evaluate_export(&mut self, _items: &[String]) -> Result<Value> { |
9868 | | // For now, just track the export |
9869 | | // Real implementation would: |
9870 | | // 1. Mark items for export |
9871 | | // 2. Make them available to importing modules |
9872 | | |
9873 | | // Export handling |
9874 | | |
9875 | 0 | Self::ok_unit() |
9876 | 0 | } |
9877 | | |
9878 | | /// Handle package mode commands |
9879 | 0 | fn handle_pkg_command(&mut self, input: &str) -> Result<String> { |
9880 | 0 | let parts: Vec<&str> = input.split_whitespace().collect(); |
9881 | 0 | match parts.first().copied() { |
9882 | 0 | Some("search") if parts.len() > 1 => { |
9883 | 0 | Ok(format!("Searching for packages matching '{}'...", parts[1])) |
9884 | | } |
9885 | 0 | Some("install") if parts.len() > 1 => { |
9886 | 0 | Ok(format!("Installing package '{}'...", parts[1])) |
9887 | | } |
9888 | 0 | Some("list") => { |
9889 | 0 | Ok("Installed packages:\n(Package management not yet implemented)".to_string()) |
9890 | | } |
9891 | | _ => { |
9892 | 0 | Ok("Package commands: search <query>, install <package>, list".to_string()) |
9893 | | } |
9894 | | } |
9895 | 0 | } |
9896 | | |
9897 | | /// Show comprehensive help menu |
9898 | 0 | fn show_help_menu(&self) -> Result<String> { |
9899 | 0 | Ok(r"🔧 Ruchy REPL Help Menu |
9900 | 0 |
|
9901 | 0 | 📋 COMMANDS: |
9902 | 0 | :help [topic] - Show help for specific topic or this menu |
9903 | 0 | :quit, :q - Exit the REPL |
9904 | 0 | :clear - Clear variables and history |
9905 | 0 | :history - Show command history |
9906 | 0 | :env - Show environment variables |
9907 | 0 | :type <expr> - Show type of expression |
9908 | 0 | :ast <expr> - Show abstract syntax tree |
9909 | 0 | :inspect <var> - Detailed variable inspection |
9910 | 0 |
|
9911 | 0 | 🎯 MODES: |
9912 | 0 | :normal - Standard evaluation mode |
9913 | 0 | :help - Help documentation mode (current) |
9914 | 0 | :debug - Debug mode with detailed output |
9915 | 0 | :time - Time mode showing execution duration |
9916 | 0 | :test - Test mode with assertions |
9917 | 0 | :math - Enhanced math mode |
9918 | 0 | :sql - SQL query mode (experimental) |
9919 | 0 | :shell - Shell command mode |
9920 | 0 |
|
9921 | 0 | 💡 LANGUAGE TOPICS (type topic name for details): |
9922 | 0 | fn - Function definitions |
9923 | 0 | let - Variable declarations |
9924 | 0 | if - Conditional expressions |
9925 | 0 | for - Loop constructs |
9926 | 0 | match - Pattern matching |
9927 | 0 | while - While loops |
9928 | 0 |
|
9929 | 0 | 🚀 FEATURES: |
9930 | 0 | • Arithmetic: +, -, *, /, %, ** |
9931 | 0 | • Comparisons: ==, !=, <, >, <=, >= |
9932 | 0 | • Logical: &&, ||, ! |
9933 | 0 | • Arrays: [1, 2, 3], indexing with arr[0] |
9934 | 0 | • Objects: {key: value}, access with obj.key |
9935 | 0 | • String methods: .length(), .to_upper(), .to_lower() |
9936 | 0 | • Math functions: sqrt(), pow(), abs(), sin(), cos(), etc. |
9937 | 0 | • History: _1, _2, _3 (previous results) |
9938 | 0 | • Shell commands: !ls, !pwd, !echo hello |
9939 | 0 | • Introspection: ?variable, ??variable (detailed) |
9940 | 0 |
|
9941 | 0 | Type :normal to exit help mode. |
9942 | 0 | ".to_string()) |
9943 | 0 | } |
9944 | | |
9945 | | /// Handle help mode commands |
9946 | 0 | fn handle_help_command(&mut self, keyword: &str) -> Result<String> { |
9947 | 0 | let help_text = match keyword { |
9948 | 0 | "fn" | "function" => "fn - Define a function\nSyntax: fn name(params) { body }\nExample: fn add(a, b) { a + b }".to_string(), |
9949 | 0 | "let" | "variable" | "var" => "let - Bind a value to a variable\nSyntax: let name = value\nExample: let x = 42\nMutable: let mut x = 42".to_string(), |
9950 | 0 | "if" | "conditional" => "if - Conditional execution\nSyntax: if condition { then } else { otherwise }\nExample: if x > 0 { \"positive\" } else { \"negative\" }".to_string(), |
9951 | 0 | "for" | "loop" => "for - Loop over a collection\nSyntax: for item in collection { body }\nExample: for x in [1,2,3] { println(x) }\nRange: for i in 1..5 { println(i) }".to_string(), |
9952 | 0 | "while" => "while - Loop with condition\nSyntax: while condition { body }\nExample: while x < 10 { x = x + 1 }".to_string(), |
9953 | 0 | "match" | "pattern" => "match - Pattern matching\nSyntax: match value { pattern => result, ... }\nExample: match x { 0 => \"zero\", _ => \"nonzero\" }\nGuards: match x { n if n > 0 => \"positive\", _ => \"other\" }".to_string(), |
9954 | 0 | "array" | "list" => "Arrays - Collections of values\nSyntax: [item1, item2, ...]\nExample: let arr = [1, 2, 3]\nAccess: arr[0], arr.length(), arr.first(), arr.last()".to_string(), |
9955 | 0 | "object" | "dict" => "Objects - Key-value pairs\nSyntax: {key: value, ...}\nExample: let obj = {name: \"Alice\", age: 30}\nAccess: obj.name, obj.age".to_string(), |
9956 | 0 | "string" => "Strings - Text values\nSyntax: \"text\" or 'text'\nMethods: .length(), .to_upper(), .to_lower()\nConcatenation: \"hello\" + \" world\"".to_string(), |
9957 | 0 | "math" => "Math Functions - Mathematical operations\nBasic: +, -, *, /, %, **\nFunctions: sqrt(x), pow(x,y), abs(x), min(x,y), max(x,y)\nTrig: sin(x), cos(x), tan(x)\nRounding: floor(x), ceil(x), round(x)".to_string(), |
9958 | 0 | "commands" | ":" => self.show_help_menu()?, |
9959 | 0 | _ => format!("No help available for '{keyword}'\n\nAvailable topics:\nfn, let, if, for, while, match, array, object, string, math\n\nType 'commands' or ':' for command help.\nType :normal to exit help mode."), |
9960 | | }; |
9961 | 0 | Ok(help_text) |
9962 | 0 | } |
9963 | | |
9964 | | /// Handle math mode commands |
9965 | 0 | fn handle_math_command(&mut self, expr: &str) -> Result<String> { |
9966 | | // For now, just evaluate normally but could add special math functions |
9967 | 0 | let deadline = Instant::now() + self.config.timeout; |
9968 | 0 | let mut parser = Parser::new(expr); |
9969 | 0 | let ast = parser.parse().context("Failed to parse math expression")?; |
9970 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
9971 | 0 | Ok(format!("= {value}")) |
9972 | 0 | } |
9973 | | |
9974 | | /// Handle debug mode evaluation |
9975 | 0 | fn handle_debug_evaluation(&mut self, input: &str) -> Result<String> { |
9976 | | // Use enhanced debug evaluation per progressive modes specification |
9977 | 0 | self.handle_enhanced_debug_evaluation(input) |
9978 | 0 | } |
9979 | | |
9980 | | /// Enhanced debug mode evaluation with detailed traces |
9981 | 0 | fn handle_enhanced_debug_evaluation(&mut self, input: &str) -> Result<String> { |
9982 | 0 | let start = Instant::now(); |
9983 | | |
9984 | | // Parse timing |
9985 | 0 | let parse_start = Instant::now(); |
9986 | 0 | let mut parser = Parser::new(input); |
9987 | 0 | let ast = parser.parse().context("Failed to parse input")?; |
9988 | 0 | let parse_time = parse_start.elapsed(); |
9989 | | |
9990 | | // Type checking timing (placeholder) |
9991 | 0 | let type_start = Instant::now(); |
9992 | | // Type checking would go here |
9993 | 0 | let type_time = type_start.elapsed(); |
9994 | | |
9995 | | // Evaluation timing |
9996 | 0 | let eval_start = Instant::now(); |
9997 | 0 | let deadline = Instant::now() + self.config.timeout; |
9998 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
9999 | 0 | let eval_time = eval_start.elapsed(); |
10000 | | |
10001 | | // Memory allocation (simplified) |
10002 | 0 | let alloc_bytes = 64; // Placeholder |
10003 | | |
10004 | 0 | let _total_time = start.elapsed(); |
10005 | | |
10006 | | // Format trace according to specification |
10007 | 0 | let trace = format!( |
10008 | 0 | "┌─ Trace ────────┐\n\ |
10009 | 0 | │ parse: {:>5.1}ms │\n\ |
10010 | 0 | │ type: {:>5.1}ms │\n\ |
10011 | 0 | │ eval: {:>5.1}ms │\n\ |
10012 | 0 | │ alloc: {:>5}B │\n\ |
10013 | 0 | └────────────────┘\n\ |
10014 | 0 | {}: {} = {}", |
10015 | 0 | parse_time.as_secs_f64() * 1000.0, |
10016 | 0 | type_time.as_secs_f64() * 1000.0, |
10017 | 0 | eval_time.as_secs_f64() * 1000.0, |
10018 | | alloc_bytes, |
10019 | 0 | input.trim(), |
10020 | 0 | self.infer_type(&value), |
10021 | | value |
10022 | | ); |
10023 | | |
10024 | 0 | Ok(trace) |
10025 | 0 | } |
10026 | | |
10027 | | /// Handle timed evaluation |
10028 | 0 | fn handle_timed_evaluation(&mut self, input: &str) -> Result<String> { |
10029 | 0 | let start = Instant::now(); |
10030 | | |
10031 | | // Parse |
10032 | 0 | let mut parser = Parser::new(input); |
10033 | 0 | let ast = parser.parse().context("Failed to parse input")?; |
10034 | | |
10035 | | // Evaluate |
10036 | 0 | let deadline = Instant::now() + self.config.timeout; |
10037 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
10038 | | |
10039 | 0 | let elapsed = start.elapsed(); |
10040 | 0 | Ok(format!("{value}\n⏱ Time: {elapsed:?}")) |
10041 | 0 | } |
10042 | | |
10043 | | /// Generate a stack trace from an error |
10044 | 0 | fn generate_stack_trace(&self, error: &anyhow::Error) -> Vec<String> { |
10045 | 0 | let mut stack_trace = Vec::new(); |
10046 | | |
10047 | | // Add the main error |
10048 | 0 | stack_trace.push(format!("Error: {error}")); |
10049 | | |
10050 | | // Add error chain |
10051 | 0 | let mut current = error.source(); |
10052 | 0 | while let Some(err) = current { |
10053 | 0 | stack_trace.push(format!("Caused by: {err}")); |
10054 | 0 | current = err.source(); |
10055 | 0 | } |
10056 | | |
10057 | | // Add current evaluation context if available |
10058 | 0 | if let Some(last_expr) = self.history.last() { |
10059 | 0 | stack_trace.push(format!("Last successful expression: {last_expr}")); |
10060 | 0 | } |
10061 | | |
10062 | 0 | stack_trace |
10063 | 0 | } |
10064 | | |
10065 | | /// Detect progressive mode activation via attributes like #[test] and #[debug] |
10066 | 0 | fn detect_mode_activation(&self, input: &str) -> Option<ReplMode> { |
10067 | 0 | let trimmed = input.trim(); |
10068 | | |
10069 | 0 | if trimmed.starts_with("#[test]") { |
10070 | 0 | Some(ReplMode::Test) |
10071 | 0 | } else if trimmed.starts_with("#[debug]") { |
10072 | 0 | Some(ReplMode::Debug) |
10073 | | } else { |
10074 | 0 | None |
10075 | | } |
10076 | 0 | } |
10077 | | |
10078 | | /// Handle test mode evaluation with assertions and table tests |
10079 | 0 | fn handle_test_evaluation(&mut self, input: &str) -> Result<String> { |
10080 | 0 | let trimmed = input.trim(); |
10081 | | |
10082 | | // Handle assert statements |
10083 | 0 | if let Some(stripped) = trimmed.strip_prefix("assert ") { |
10084 | 0 | return self.handle_assertion(stripped); |
10085 | 0 | } |
10086 | | |
10087 | | // Handle table_test! macro |
10088 | 0 | if trimmed.starts_with("table_test!(") { |
10089 | 0 | return self.handle_table_test(trimmed); |
10090 | 0 | } |
10091 | | |
10092 | | // Regular evaluation with test result formatting |
10093 | 0 | let result = self.eval_internal(input)?; |
10094 | 0 | Ok(format!("✓ {result}")) |
10095 | 0 | } |
10096 | | |
10097 | | /// Handle assertion statements in test mode |
10098 | 0 | fn handle_assertion(&mut self, assertion: &str) -> Result<String> { |
10099 | | // Parse and evaluate the assertion |
10100 | 0 | let mut parser = Parser::new(assertion); |
10101 | 0 | let expr = parser.parse().context("Failed to parse assertion")?; |
10102 | | |
10103 | 0 | let deadline = Instant::now() + self.config.timeout; |
10104 | 0 | let result = self.evaluate_expr(&expr, deadline, 0)?; |
10105 | | |
10106 | 0 | match result { |
10107 | 0 | Value::Bool(true) => Ok("✓ Pass".to_string()), |
10108 | 0 | Value::Bool(false) => Ok("✗ Fail: assertion failed".to_string()), |
10109 | 0 | _ => Ok(format!("✗ Fail: assertion must be boolean, got {result}")), |
10110 | | } |
10111 | 0 | } |
10112 | | |
10113 | | /// Handle table test macro |
10114 | 0 | fn handle_table_test(&mut self, _input: &str) -> Result<String> { |
10115 | | // This is a simplified implementation - in a full version you'd parse the table_test! macro properly |
10116 | | // For now, just indicate successful parsing |
10117 | 0 | Ok("✓ Table test recognized (full implementation pending)".to_string()) |
10118 | 0 | } |
10119 | | |
10120 | | /// Simple type inference for display purposes |
10121 | 0 | fn infer_type(&self, value: &Value) -> &'static str { |
10122 | 0 | match value { |
10123 | 0 | Value::Int(_) => "Int", |
10124 | 0 | Value::Float(_) => "Float", |
10125 | 0 | Value::String(_) => "String", |
10126 | 0 | Value::Bool(_) => "Bool", |
10127 | 0 | Value::Char(_) => "Char", |
10128 | 0 | Value::Unit => "Unit", |
10129 | 0 | Value::List(_) => "List", |
10130 | 0 | Value::Tuple(_) => "Tuple", |
10131 | 0 | Value::Object(_) => "Object", |
10132 | 0 | Value::Function { .. } => "Function", |
10133 | 0 | Value::Lambda { .. } => "Lambda", |
10134 | 0 | Value::DataFrame { .. } => "DataFrame", |
10135 | 0 | Value::HashMap(_) => "HashMap", |
10136 | 0 | Value::HashSet(_) => "HashSet", |
10137 | 0 | Value::Range { .. } => "Range", |
10138 | 0 | Value::EnumVariant { .. } => "EnumVariant", |
10139 | 0 | Value::Nil => "Nil", |
10140 | | } |
10141 | 0 | } |
10142 | | |
10143 | | /// Format size/length information for value (complexity: 8) |
10144 | 0 | fn format_value_size(&self, value: &Value) -> String { |
10145 | 0 | match value { |
10146 | 0 | Value::List(l) => format!("│ Length: {:<20} │\n", l.len()), |
10147 | 0 | Value::String(s) => format!("│ Length: {} chars{:<11} │\n", s.len(), ""), |
10148 | 0 | Value::Object(o) => format!("│ Fields: {:<20} │\n", o.len()), |
10149 | 0 | Value::HashMap(m) => format!("│ Entries: {:<19} │\n", m.len()), |
10150 | 0 | Value::HashSet(s) => format!("│ Size: {:<22} │\n", s.len()), |
10151 | 0 | Value::Tuple(t) => format!("│ Elements: {:<18} │\n", t.len()), |
10152 | 0 | Value::DataFrame { columns, .. } => { |
10153 | 0 | if let Some(first_col) = columns.first() { |
10154 | 0 | let row_count = first_col.values.len(); |
10155 | 0 | format!("│ Columns: {:<19} │\n│ Rows: {row_count:<22} │\n", columns.len()) |
10156 | | } else { |
10157 | 0 | String::new() |
10158 | | } |
10159 | | } |
10160 | | _ => { |
10161 | | // Show value preview for simple types |
10162 | 0 | let preview = format!("{value}"); |
10163 | 0 | if preview.len() <= 24 { |
10164 | 0 | format!("│ Value: {preview:<21} │\n") |
10165 | | } else { |
10166 | 0 | let truncated = &preview[..21]; |
10167 | 0 | format!("│ Value: {truncated}... │\n") |
10168 | | } |
10169 | | } |
10170 | | } |
10171 | 0 | } |
10172 | | |
10173 | | /// Format interactive options for value (complexity: 3) |
10174 | 0 | fn format_value_options(&self, value: &Value) -> String { |
10175 | 0 | let mut output = String::new(); |
10176 | 0 | output.push_str("│ Options: │\n"); |
10177 | | |
10178 | 0 | match value { |
10179 | 0 | Value::List(_) | Value::Object(_) | Value::HashMap(_) => { |
10180 | 0 | output.push_str("│ [Enter] Browse entries │\n"); |
10181 | 0 | output.push_str("│ [S] Statistics │\n"); |
10182 | 0 | } |
10183 | 0 | Value::Function { .. } | Value::Lambda { .. } => { |
10184 | 0 | output.push_str("│ [P] Show parameters │\n"); |
10185 | 0 | output.push_str("│ [B] Show body │\n"); |
10186 | 0 | } |
10187 | 0 | _ => { |
10188 | 0 | output.push_str("│ [V] Show full value │\n"); |
10189 | 0 | output.push_str("│ [T] Type details │\n"); |
10190 | 0 | } |
10191 | | } |
10192 | | |
10193 | 0 | output.push_str("│ [M] Memory layout │\n"); |
10194 | 0 | output |
10195 | 0 | } |
10196 | | |
10197 | | /// Create inspector header (complexity: 2) |
10198 | 0 | fn create_inspector_header(&self, var_name: &str, value: &Value) -> String { |
10199 | 0 | let mut output = String::new(); |
10200 | 0 | output.push_str("┌─ Inspector ────────────────┐\n"); |
10201 | 0 | output.push_str(&format!("│ Variable: {var_name:<17} │\n")); |
10202 | 0 | output.push_str(&format!("│ Type: {:<22} │\n", self.infer_type(value))); |
10203 | 0 | output |
10204 | 0 | } |
10205 | | |
10206 | | /// Inspect a value in detail (for :inspect command) (complexity: 4) |
10207 | 0 | fn inspect_value(&self, var_name: &str) -> String { |
10208 | 0 | if let Some(value) = self.bindings.get(var_name) { |
10209 | 0 | let mut output = String::new(); |
10210 | | |
10211 | | // Header with variable name and type |
10212 | 0 | output.push_str(&self.create_inspector_header(var_name, value)); |
10213 | | |
10214 | | // Size/length information |
10215 | 0 | output.push_str(&self.format_value_size(value)); |
10216 | | |
10217 | | // Memory estimation |
10218 | 0 | let memory_size = self.estimate_memory_size(value); |
10219 | 0 | output.push_str(&format!("│ Memory: ~{:<18} │\n", format!("{memory_size} bytes"))); |
10220 | | |
10221 | | // Separator line |
10222 | 0 | output.push_str("│ │\n"); |
10223 | | |
10224 | | // Interactive options |
10225 | 0 | output.push_str(&self.format_value_options(value)); |
10226 | | |
10227 | | // Footer |
10228 | 0 | output.push_str("└────────────────────────────┘"); |
10229 | | |
10230 | 0 | output |
10231 | | } else { |
10232 | 0 | format!("Variable '{var_name}' not found. Use :env to list all variables.") |
10233 | | } |
10234 | 0 | } |
10235 | | |
10236 | | /// Estimate memory size of a value (simplified) |
10237 | 0 | fn estimate_memory_size(&self, value: &Value) -> usize { |
10238 | 0 | match value { |
10239 | 0 | Value::Int(_) => 8, |
10240 | 0 | Value::Float(_) => 8, |
10241 | 0 | Value::Bool(_) => 1, |
10242 | 0 | Value::Char(_) => 4, |
10243 | 0 | Value::Unit => 0, |
10244 | 0 | Value::String(s) => s.len() + 24, // String overhead + content |
10245 | 0 | Value::List(l) => 24 + l.len() * 8, // Vec overhead + pointers |
10246 | 0 | Value::Tuple(t) => 8 + t.len() * 8, |
10247 | 0 | Value::Object(o) => 24 + o.len() * 32, // HashMap overhead |
10248 | 0 | Value::HashMap(m) => 24 + m.len() * 48, |
10249 | 0 | Value::HashSet(s) => 24 + s.len() * 16, |
10250 | 0 | Value::Function { .. } | Value::Lambda { .. } => 64, // Simplified |
10251 | 0 | Value::DataFrame { columns, .. } => { |
10252 | 0 | 24 + columns.len() * 64 // Simplified estimate |
10253 | | } |
10254 | 0 | Value::Range { .. } => 16, |
10255 | 0 | Value::EnumVariant { .. } => 32, |
10256 | 0 | Value::Nil => 0, |
10257 | | } |
10258 | 0 | } |
10259 | | |
10260 | | /// Run the REPL with session recording enabled |
10261 | | /// |
10262 | | /// This method creates a session recorder that tracks all inputs, outputs, |
10263 | | /// and state changes during the REPL session. The recorded session can be |
10264 | | /// replayed later for testing or educational purposes. |
10265 | | /// |
10266 | | /// # Arguments |
10267 | | /// * `record_file` - Path to save the recorded session |
10268 | | /// |
10269 | | /// # Returns |
10270 | | /// Returns `Ok(())` on successful completion, or an error if recording fails |
10271 | | /// |
10272 | | /// # Errors |
10273 | | /// Returns error if recording initialization fails or I/O operations fail |
10274 | 0 | pub fn run_with_recording(&mut self, record_file: &Path) -> Result<()> { |
10275 | | // Delegate to refactored version with reduced complexity |
10276 | | // Original complexity: 44, New complexity: 15 |
10277 | 0 | self.run_with_recording_refactored(record_file) |
10278 | 0 | } |
10279 | | |
10280 | | // Helper methods for reduced complexity REPL::run |
10281 | | |
10282 | 0 | fn setup_readline_editor(&self) -> Result<rustyline::Editor<RuchyCompleter, DefaultHistory>> { |
10283 | 0 | let config = Config::builder() |
10284 | 0 | .history_ignore_space(true) |
10285 | 0 | .history_ignore_dups(true)? |
10286 | 0 | .completion_type(CompletionType::List) |
10287 | 0 | .edit_mode(EditMode::Emacs) |
10288 | 0 | .build(); |
10289 | | |
10290 | 0 | let mut rl = rustyline::Editor::<RuchyCompleter, DefaultHistory>::with_config(config)?; |
10291 | | |
10292 | 0 | let completer = RuchyCompleter::new(); |
10293 | 0 | rl.set_helper(Some(completer)); |
10294 | | |
10295 | 0 | let history_path = self.temp_dir.join("history.txt"); |
10296 | 0 | let _ = rl.load_history(&history_path); |
10297 | | |
10298 | 0 | Ok(rl) |
10299 | 0 | } |
10300 | | |
10301 | 0 | fn format_prompt(&self, in_multiline: bool) -> String { |
10302 | 0 | if in_multiline { |
10303 | 0 | format!("{} ", " ...".bright_black()) |
10304 | | } else { |
10305 | 0 | format!("{} ", self.get_prompt().bright_green()) |
10306 | | } |
10307 | 0 | } |
10308 | | |
10309 | 0 | fn process_input_line( |
10310 | 0 | &mut self, |
10311 | 0 | line: &str, |
10312 | 0 | rl: &mut rustyline::Editor<RuchyCompleter, DefaultHistory>, |
10313 | 0 | multiline_state: &mut MultilineState |
10314 | 0 | ) -> Result<bool> { |
10315 | | // Skip empty lines unless in multiline mode |
10316 | 0 | if line.trim().is_empty() && !multiline_state.in_multiline { |
10317 | 0 | return Ok(false); |
10318 | 0 | } |
10319 | | |
10320 | | // Handle commands (only when not in multiline mode) |
10321 | 0 | if !multiline_state.in_multiline && line.starts_with(':') { |
10322 | 0 | return self.process_command(line); |
10323 | 0 | } |
10324 | | |
10325 | | // Process regular expression input |
10326 | 0 | self.process_expression_input(line, rl, multiline_state) |
10327 | 0 | } |
10328 | | |
10329 | 0 | fn process_command(&mut self, line: &str) -> Result<bool> { |
10330 | 0 | let (should_quit, output) = self.handle_command_with_output(line)?; |
10331 | 0 | if !output.is_empty() { |
10332 | 0 | println!("{output}"); |
10333 | 0 | } |
10334 | 0 | Ok(should_quit) |
10335 | 0 | } |
10336 | | |
10337 | 0 | fn process_expression_input( |
10338 | 0 | &mut self, |
10339 | 0 | line: &str, |
10340 | 0 | rl: &mut rustyline::Editor<RuchyCompleter, DefaultHistory>, |
10341 | 0 | multiline_state: &mut MultilineState |
10342 | 0 | ) -> Result<bool> { |
10343 | | // Check if this starts a multiline expression |
10344 | 0 | if !multiline_state.in_multiline && Self::needs_continuation(line) { |
10345 | 0 | multiline_state.start_multiline(line); |
10346 | 0 | return Ok(false); |
10347 | 0 | } |
10348 | | |
10349 | 0 | if multiline_state.in_multiline { |
10350 | 0 | self.process_multiline_input(line, rl, multiline_state) |
10351 | | } else { |
10352 | 0 | self.process_single_line_input(line, rl) |
10353 | | } |
10354 | 0 | } |
10355 | | |
10356 | 0 | fn process_multiline_input( |
10357 | 0 | &mut self, |
10358 | 0 | line: &str, |
10359 | 0 | rl: &mut rustyline::Editor<RuchyCompleter, DefaultHistory>, |
10360 | 0 | multiline_state: &mut MultilineState |
10361 | 0 | ) -> Result<bool> { |
10362 | 0 | multiline_state.accumulate_line(line); |
10363 | | |
10364 | 0 | if !Self::needs_continuation(&multiline_state.buffer) { |
10365 | 0 | let _ = rl.add_history_entry(multiline_state.buffer.as_str()); |
10366 | 0 | self.evaluate_and_print(&multiline_state.buffer); |
10367 | 0 | multiline_state.reset(); |
10368 | 0 | } |
10369 | | |
10370 | 0 | Ok(false) |
10371 | 0 | } |
10372 | | |
10373 | 0 | fn process_single_line_input( |
10374 | 0 | &mut self, |
10375 | 0 | line: &str, |
10376 | 0 | rl: &mut rustyline::Editor<RuchyCompleter, DefaultHistory> |
10377 | 0 | ) -> Result<bool> { |
10378 | 0 | let _ = rl.add_history_entry(line); |
10379 | 0 | self.evaluate_and_print(line); |
10380 | 0 | Ok(false) |
10381 | 0 | } |
10382 | | |
10383 | 0 | fn evaluate_and_print(&mut self, expression: &str) { |
10384 | 0 | match self.eval(expression) { |
10385 | 0 | Ok(result) => { |
10386 | 0 | println!("{}", result.bright_white()); |
10387 | 0 | } |
10388 | 0 | Err(e) => { |
10389 | 0 | eprintln!("{}: {}", "Error".bright_red().bold(), e); |
10390 | 0 | } |
10391 | | } |
10392 | 0 | } |
10393 | | } |
10394 | | |
10395 | | // Helper struct for managing multiline state |
10396 | | #[derive(Debug)] |
10397 | | struct MultilineState { |
10398 | | buffer: String, |
10399 | | in_multiline: bool, |
10400 | | } |
10401 | | |
10402 | | impl MultilineState { |
10403 | 0 | fn new() -> Self { |
10404 | 0 | Self { |
10405 | 0 | buffer: String::new(), |
10406 | 0 | in_multiline: false, |
10407 | 0 | } |
10408 | 0 | } |
10409 | | |
10410 | 0 | fn start_multiline(&mut self, line: &str) { |
10411 | 0 | self.buffer = line.to_string(); |
10412 | 0 | self.in_multiline = true; |
10413 | 0 | } |
10414 | | |
10415 | 0 | fn accumulate_line(&mut self, line: &str) { |
10416 | 0 | self.buffer.push('\n'); |
10417 | 0 | self.buffer.push_str(line); |
10418 | 0 | } |
10419 | | |
10420 | 0 | fn reset(&mut self) { |
10421 | 0 | self.buffer.clear(); |
10422 | 0 | self.in_multiline = false; |
10423 | 0 | } |
10424 | | } |