/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 | 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
168 | 55 | match self { |
169 | 39 | Value::Int(n) => write!(f, "{n}"), |
170 | 3 | Value::Float(x) => write!(f, "{x}"), |
171 | 3 | Value::String(s) => write!(f, "\"{s}\""), |
172 | 10 | 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 | 55 | } |
258 | | } |
259 | | |
260 | | impl Value { |
261 | | /// Check if the value is considered truthy in boolean contexts |
262 | 2 | fn is_truthy(&self) -> bool { |
263 | 2 | match self { |
264 | 2 | 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 | 2 | } |
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 | 2 | fn from_repl(repl: &Repl) -> Self { |
404 | 2 | let bindings = repl.bindings.iter() |
405 | 9 | .map2 (|(k, v)| (k.clone(), v.clone())) |
406 | 2 | .collect(); |
407 | | |
408 | 2 | let mutability = repl.binding_mutability.iter() |
409 | 7 | .map2 (|(k, v)| (k.clone(), *v)) |
410 | 2 | .collect(); |
411 | | |
412 | 2 | let result_history = repl.result_history.iter().cloned().collect(); |
413 | | |
414 | 2 | let enum_definitions = repl.enum_definitions.iter() |
415 | 4 | .map2 (|(k, v)| (k.clone(), v.iter().cloned().collect())) |
416 | 2 | .collect(); |
417 | | |
418 | 2 | Self { |
419 | 2 | bindings, |
420 | 2 | mutability, |
421 | 2 | result_history, |
422 | 2 | enum_definitions, |
423 | 2 | timestamp: SystemTime::now(), |
424 | 2 | _pc: repl.history.len(), |
425 | 2 | } |
426 | 2 | } |
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 | 24 | fn default() -> Self { |
499 | 24 | Self { |
500 | 24 | max_memory: 10 * 1024 * 1024, // 10MB arena allocation limit |
501 | 24 | timeout: Duration::from_millis(100), // 100ms hard limit per spec |
502 | 24 | max_depth: 1000, // 1000 frame maximum per spec |
503 | 24 | debug: false, |
504 | 24 | } |
505 | 24 | } |
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 | 24 | fn new(max_size: usize) -> Self { |
529 | 24 | Self { |
530 | 24 | max_size, |
531 | 24 | current: 0, |
532 | 24 | peak_usage: 0, |
533 | 24 | allocation_count: 0, |
534 | 24 | } |
535 | 24 | } |
536 | | |
537 | | /// Reset arena for new evaluation (O(1) operation) |
538 | 69 | fn reset(&mut self) { |
539 | 69 | self.current = 0; |
540 | 69 | self.allocation_count = 0; |
541 | 69 | } |
542 | | |
543 | | /// Track memory usage during evaluation |
544 | 169 | fn try_alloc(&mut self, size: usize) -> Result<()> { |
545 | 169 | 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 | 169 | } |
555 | 169 | self.current += size; |
556 | 169 | self.allocation_count += 1; |
557 | | |
558 | | // Track peak usage |
559 | 169 | if self.current > self.peak_usage { |
560 | 87 | self.peak_usage = self.current; |
561 | 87 | }82 |
562 | | |
563 | 169 | Ok(()) |
564 | 169 | } |
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 | 24 | pub fn new() -> Result<Self> { |
649 | 24 | Self::with_config(ReplConfig::default()) |
650 | 24 | } |
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 | 24 | pub fn with_config(config: ReplConfig) -> Result<Self> { |
658 | 24 | let temp_dir = std::env::temp_dir().join("ruchy_repl"); |
659 | 24 | fs::create_dir_all(&temp_dir)?0 ; |
660 | | |
661 | 24 | let memory = MemoryTracker::new(config.max_memory); |
662 | | |
663 | 24 | let mut repl = Self { |
664 | 24 | history: Vec::new(), |
665 | 24 | result_history: Vec::new(), |
666 | 24 | definitions: Vec::new(), |
667 | 24 | bindings: HashMap::new(), |
668 | 24 | binding_mutability: HashMap::new(), |
669 | 24 | impl_methods: HashMap::new(), |
670 | 24 | enum_definitions: HashMap::new(), |
671 | 24 | transpiler: Transpiler::new(), |
672 | 24 | temp_dir, |
673 | 24 | session_counter: 0, |
674 | 24 | memory, |
675 | 24 | module_cache: HashMap::new(), |
676 | 24 | mode: ReplMode::Normal, |
677 | 24 | last_error_debug: None, |
678 | 24 | error_recovery: None, |
679 | 24 | state: ReplState::Ready, |
680 | 24 | magic_registry: MagicRegistry::new(), |
681 | 24 | unicode_expander: UnicodeExpander::new(), |
682 | 24 | tx_state: TransactionalState::new(config.max_memory), |
683 | 24 | config, |
684 | 24 | }; |
685 | | |
686 | | // Initialize built-in types |
687 | 24 | repl.init_builtins(); |
688 | | |
689 | 24 | Ok(repl) |
690 | 24 | } |
691 | | |
692 | | /// Initialize built-in enum types (Option, Result) |
693 | 24 | fn init_builtins(&mut self) { |
694 | | // Register Option enum |
695 | 24 | self.enum_definitions.insert( |
696 | 24 | "Option".to_string(), |
697 | 24 | vec!["None".to_string(), "Some".to_string()], |
698 | | ); |
699 | | |
700 | | // Register Result enum |
701 | 24 | self.enum_definitions.insert( |
702 | 24 | "Result".to_string(), |
703 | 24 | vec!["Ok".to_string(), "Err".to_string()], |
704 | | ); |
705 | | |
706 | | // Add Option and Result to definitions for transpiler |
707 | 24 | self.definitions |
708 | 24 | .push("enum Option<T> { None, Some(T) }".to_string()); |
709 | 24 | self.definitions |
710 | 24 | .push("enum Result<T, E> { Ok(T), Err(E) }".to_string()); |
711 | 24 | } |
712 | | |
713 | | // === Resource-Bounded Evaluation API === |
714 | | |
715 | | /// Create a sandboxed REPL instance for testing/fuzzing |
716 | | /// Uses minimal resource limits for safety |
717 | 0 | pub fn sandboxed() -> Result<Self> { |
718 | 0 | let config = ReplConfig { |
719 | 0 | max_memory: 1024 * 1024, // 1MB limit for sandbox |
720 | 0 | timeout: Duration::from_millis(10), // Very short timeout |
721 | 0 | max_depth: 100, // Limited recursion |
722 | 0 | debug: false, |
723 | 0 | }; |
724 | 0 | Self::with_config(config) |
725 | 0 | } |
726 | | |
727 | | /// Get current memory usage in bytes |
728 | 0 | pub fn memory_used(&self) -> usize { |
729 | 0 | self.memory.memory_used() |
730 | 0 | } |
731 | | |
732 | | /// Get peak memory usage since last evaluation |
733 | 0 | pub fn peak_memory(&self) -> usize { |
734 | 0 | self.memory.peak_memory() |
735 | 0 | } |
736 | | |
737 | | /// Get memory pressure (0.0 to 1.0) |
738 | 0 | pub fn memory_pressure(&self) -> f64 { |
739 | 0 | self.memory.memory_pressure() |
740 | 0 | } |
741 | | |
742 | | /// Check if REPL can accept new input (not at resource limits) |
743 | 0 | pub fn can_accept_input(&self) -> bool { |
744 | 0 | self.memory_pressure() < 0.95 // Less than 95% memory usage |
745 | 0 | } |
746 | | |
747 | | /// Validate that all bindings are still valid (no corruption) |
748 | 0 | pub fn bindings_valid(&self) -> bool { |
749 | | // Check that mutability tracking doesn't have orphaned entries |
750 | | // (bindings without mutability entries are allowed - they default to immutable) |
751 | 0 | for name in self.binding_mutability.keys() { |
752 | 0 | if !self.bindings.contains_key(name) { |
753 | 0 | return false; |
754 | 0 | } |
755 | | } |
756 | 0 | true |
757 | 0 | } |
758 | | |
759 | | /// Evaluate with explicit resource bounds (for testing) |
760 | 0 | pub fn eval_bounded(&mut self, input: &str, max_memory: usize, timeout: Duration) -> Result<String> { |
761 | | // Save current config |
762 | 0 | let old_config = self.config.clone(); |
763 | | |
764 | | // Apply working bounds |
765 | 0 | self.config.max_memory = max_memory; |
766 | 0 | self.config.timeout = timeout; |
767 | | |
768 | | // Update memory tracker limit |
769 | 0 | self.memory.max_size = max_memory; |
770 | | |
771 | | // Evaluate |
772 | 0 | let result = self.eval(input); |
773 | | |
774 | | // Restore original config |
775 | 0 | self.config = old_config; |
776 | 0 | self.memory.max_size = self.config.max_memory; |
777 | | |
778 | 0 | result |
779 | 0 | } |
780 | | |
781 | | /// Evaluate an expression string and return the Value |
782 | | /// |
783 | | /// This is used for one-liner evaluation from CLI |
784 | | /// |
785 | | /// # Errors |
786 | | /// |
787 | | /// Returns an error if parsing or evaluation fails |
788 | 0 | pub fn evaluate_expr_str(&mut self, input: &str, deadline: Option<Instant>) -> Result<Value> { |
789 | | // Reset memory tracker for fresh evaluation |
790 | 0 | self.memory.reset(); |
791 | | |
792 | | // Track input memory |
793 | 0 | self.memory.try_alloc(input.len())?; |
794 | | |
795 | | // Use provided deadline or default timeout |
796 | 0 | let deadline = deadline.unwrap_or_else(|| Instant::now() + self.config.timeout); |
797 | | |
798 | | // Parse the input |
799 | 0 | let mut parser = Parser::new(input); |
800 | 0 | let ast = parser.parse().context("Failed to parse input")?; |
801 | | |
802 | | // Check memory for AST |
803 | 0 | self.memory.try_alloc(std::mem::size_of_val(&ast))?; |
804 | | |
805 | | // Evaluate the expression |
806 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
807 | | |
808 | | // Handle let bindings specially (for backward compatibility) |
809 | 0 | if let ExprKind::Let { name, type_annotation: _, is_mutable, .. } = &ast.kind { |
810 | 0 | self.create_binding(name.clone(), value.clone(), *is_mutable); |
811 | 0 | } |
812 | | |
813 | 0 | Ok(value) |
814 | 0 | } |
815 | | |
816 | | // === Transactional Evaluation API === |
817 | | |
818 | | /// Evaluate with transactional state machine |
819 | 0 | pub fn eval_transactional(&mut self, input: &str) -> Result<String> { |
820 | 0 | let (new_state, result) = std::mem::take(&mut self.state).eval(self, input); |
821 | 0 | self.state = new_state; |
822 | 0 | result |
823 | 0 | } |
824 | | |
825 | | /// Create checkpoint of current state |
826 | | /// |
827 | | /// # Example |
828 | | /// ``` |
829 | | /// use ruchy::runtime::Repl; |
830 | | /// |
831 | | /// let mut repl = Repl::new().unwrap(); |
832 | | /// repl.eval("let x = 42").unwrap(); |
833 | | /// let checkpoint = repl.checkpoint(); |
834 | | /// repl.eval("let x = 100").unwrap(); |
835 | | /// repl.restore_checkpoint(&checkpoint); |
836 | | /// assert_eq!(repl.eval("x").unwrap(), "42"); |
837 | | /// ``` |
838 | 2 | pub fn checkpoint(&self) -> Checkpoint { |
839 | 2 | Checkpoint::from_repl(self) |
840 | 2 | } |
841 | | |
842 | | /// Restore from checkpoint |
843 | | /// |
844 | | /// # Example |
845 | | /// ``` |
846 | | /// use ruchy::runtime::Repl; |
847 | | /// |
848 | | /// let mut repl = Repl::new().unwrap(); |
849 | | /// let checkpoint = repl.checkpoint(); |
850 | | /// repl.eval("let y = 100").unwrap(); |
851 | | /// repl.restore_checkpoint(&checkpoint); |
852 | | /// // y is no longer defined after restore |
853 | | /// ``` |
854 | 0 | pub fn restore_checkpoint(&mut self, checkpoint: &Checkpoint) { |
855 | 0 | checkpoint.restore_to(self); |
856 | 0 | self.state = ReplState::Ready; |
857 | 0 | } |
858 | | |
859 | | /// Get current state |
860 | 0 | pub fn get_state(&self) -> &ReplState { |
861 | 0 | &self.state |
862 | 0 | } |
863 | | |
864 | | /// Set state (for testing purposes only - do not use in production) |
865 | 0 | pub fn set_state_for_testing(&mut self, state: ReplState) { |
866 | 0 | self.state = state; |
867 | 0 | } |
868 | | |
869 | | /// Get result history length (for debugging) |
870 | 0 | pub fn result_history_len(&self) -> usize { |
871 | 0 | self.result_history.len() |
872 | 0 | } |
873 | | |
874 | | /// Get bindings (for replay testing) |
875 | 34 | pub fn get_bindings(&self) -> &HashMap<String, Value> { |
876 | 34 | &self.bindings |
877 | 34 | } |
878 | | |
879 | | /// Get mutable bindings (for replay testing) |
880 | 1 | pub fn get_bindings_mut(&mut self) -> &mut HashMap<String, Value> { |
881 | 1 | &mut self.bindings |
882 | 1 | } |
883 | | |
884 | | /// Clear bindings (for replay testing) |
885 | 1 | pub fn clear_bindings(&mut self) { |
886 | 1 | self.bindings.clear(); |
887 | 1 | self.binding_mutability.clear(); |
888 | 1 | } |
889 | | |
890 | | /// Get last error (for magic commands) |
891 | 0 | pub fn get_last_error(&self) -> Option<&DebugInfo> { |
892 | 0 | self.last_error_debug.as_ref() |
893 | 0 | } |
894 | | |
895 | | /// Check if REPL is in failed state |
896 | 0 | pub fn is_failed(&self) -> bool { |
897 | 0 | matches!(self.state, ReplState::Failed(_)) |
898 | 0 | } |
899 | | |
900 | | /// Recover from failed state (if applicable) |
901 | 0 | pub fn recover(&mut self) -> Result<String> { |
902 | 0 | match std::mem::take(&mut self.state) { |
903 | 0 | ReplState::Failed(checkpoint) => { |
904 | 0 | checkpoint.restore_to(self); |
905 | 0 | self.state = ReplState::Ready; |
906 | 0 | Ok("Recovered from previous failure".to_string()) |
907 | | } |
908 | 0 | state => { |
909 | | // Restore original state if not failed |
910 | 0 | self.state = state; |
911 | 0 | Err(anyhow::anyhow!("REPL is not in failed state")) |
912 | | } |
913 | | } |
914 | 0 | } |
915 | | |
916 | | // === Interactive Error Recovery System === |
917 | | |
918 | | /// Create error recovery context when evaluation fails |
919 | 2 | pub fn create_error_recovery(&mut self, failed_expr: &str, error_msg: &str) -> ErrorRecovery { |
920 | 2 | let checkpoint = self.checkpoint(); |
921 | | |
922 | | // Parse error message to determine position if possible |
923 | 2 | let position = self.parse_error_position(error_msg); |
924 | | |
925 | | // Determine appropriate recovery options based on error type |
926 | 2 | let options = self.suggest_recovery_options(failed_expr, error_msg); |
927 | | |
928 | | // Generate completions if appropriate |
929 | 2 | let completions = if failed_expr.trim().is_empty() || error_msg.contains("expected expression") { |
930 | 0 | self.generate_completions_for_error(failed_expr) |
931 | | } else { |
932 | 2 | Vec::new() |
933 | | }; |
934 | | |
935 | 2 | let recovery = ErrorRecovery { |
936 | 2 | failed_expression: failed_expr.to_string(), |
937 | 2 | error_message: error_msg.to_string(), |
938 | 2 | position, |
939 | 2 | options, |
940 | 2 | completions, |
941 | 2 | error_checkpoint: checkpoint, |
942 | 2 | }; |
943 | | |
944 | 2 | self.error_recovery = Some(recovery.clone()); |
945 | 2 | recovery |
946 | 2 | } |
947 | | |
948 | | /// Parse error position from error message |
949 | 2 | fn parse_error_position(&self, error_msg: &str) -> Option<(usize, usize)> { |
950 | | // Try to extract line:column from common error formats |
951 | 2 | if let Some(caps0 ) = regex::Regex::new(r"line (\d+):(\d+)") |
952 | 2 | .ok() |
953 | 2 | .and_then(|re| re.captures(error_msg)) |
954 | | { |
955 | 0 | if let (Ok(line), Ok(col)) = ( |
956 | 0 | caps.get(1)?.as_str().parse::<usize>(), |
957 | 0 | caps.get(2)?.as_str().parse::<usize>() |
958 | | ) { |
959 | 0 | return Some((line, col)); |
960 | 0 | } |
961 | 2 | } |
962 | 2 | None |
963 | 2 | } |
964 | | |
965 | | /// Suggest appropriate recovery options based on error type |
966 | 2 | fn suggest_recovery_options(&self, failed_expr: &str, error_msg: &str) -> Vec<RecoveryOption> { |
967 | 2 | let mut options = Vec::new(); |
968 | | |
969 | | // Common recovery options based on error patterns |
970 | 2 | if error_msg.contains("Unexpected EOF") || error_msg.contains("expected expression") || |
971 | 2 | error_msg.contains("Unexpected end of input") || error_msg.contains("end of input") { |
972 | 0 | if failed_expr.starts_with("let ") && failed_expr.ends_with(" = ") { |
973 | 0 | let var_name = failed_expr.strip_prefix("let ").unwrap() |
974 | 0 | .strip_suffix(" = ").unwrap(); |
975 | 0 | options.push(RecoveryOption::ContinueWithDefault( |
976 | 0 | format!("let {var_name} = ()") |
977 | 0 | )); |
978 | 0 | options.push(RecoveryOption::RetryWith( |
979 | 0 | format!("let {var_name} = 0") |
980 | 0 | )); |
981 | 0 | } |
982 | 0 | options.push(RecoveryOption::ShowCompletions); |
983 | 2 | } |
984 | | |
985 | 2 | if error_msg.to_lowercase().contains("undefined variable") || error_msg1 .contains1 ("not found") { |
986 | | // Suggest similar variable names |
987 | 1 | if let Some(undefined_var) = self.extract_undefined_variable(error_msg) { |
988 | 1 | let similar_vars = self.find_similar_variables(&undefined_var); |
989 | 6 | for similar_var5 in &similar_vars { |
990 | 5 | options.push(RecoveryOption::RetryWith( |
991 | 5 | failed_expr.replace(&undefined_var, similar_var) |
992 | 5 | )); |
993 | 5 | } |
994 | | |
995 | | // If no similar variables found, provide a default fallback |
996 | 1 | if similar_vars.is_empty() { |
997 | 0 | options.push(RecoveryOption::ContinueWithDefault(format!("let {undefined_var} = ()"))); |
998 | 0 | options.push(RecoveryOption::RetryWith("0".to_string())); // Simple default value |
999 | 1 | } |
1000 | 0 | } |
1001 | 1 | } |
1002 | | |
1003 | 2 | if error_msg.contains("type mismatch") || error_msg.contains("cannot convert") { |
1004 | 0 | // Suggest type conversions |
1005 | 0 | options.push(RecoveryOption::RetryWith( |
1006 | 0 | format!("{}.to_string()", failed_expr.trim()) |
1007 | 0 | )); |
1008 | 2 | } |
1009 | | |
1010 | | // Always provide these standard options |
1011 | 2 | options.push(RecoveryOption::Abort); |
1012 | 2 | options.push(RecoveryOption::RestoreCheckpoint); |
1013 | | |
1014 | | // Suggest using recent history values |
1015 | 2 | if !self.result_history.is_empty() { |
1016 | 3 | for (i, _) in self.result_history.iter()1 .enumerate1 ().take1 (3) { |
1017 | 3 | options.push(RecoveryOption::UseHistoryValue(i + 1)); |
1018 | 3 | } |
1019 | 1 | } |
1020 | | |
1021 | 2 | options |
1022 | 2 | } |
1023 | | |
1024 | | /// Extract undefined variable name from error message |
1025 | 1 | pub fn extract_undefined_variable(&self, error_msg: &str) -> Option<String> { |
1026 | | // Try to find variable name in various error message formats |
1027 | | // Pattern for "Undefined variable: name" |
1028 | 1 | if let Some(caps) = regex::Regex::new(r"Undefined variable: ([a-zA-Z_][a-zA-Z0-9_]*)") |
1029 | 1 | .ok() |
1030 | 1 | .and_then(|re| re.captures(error_msg)) |
1031 | | { |
1032 | 1 | return Some(caps.get(1)?0 .as_str().to_string()); |
1033 | 0 | } |
1034 | | |
1035 | | // Pattern for "undefined variable name" or "undefined variable 'name'" |
1036 | 0 | if let Some(caps) = regex::Regex::new(r#"undefined variable[: ]+['"]?([a-zA-Z_][a-zA-Z0-9_]*)['"]?"#) |
1037 | 0 | .ok() |
1038 | 0 | .and_then(|re| re.captures(error_msg)) |
1039 | | { |
1040 | 0 | return Some(caps.get(1)?.as_str().to_string()); |
1041 | 0 | } |
1042 | | |
1043 | | // Pattern for "variable name not found" |
1044 | 0 | if let Some(caps) = regex::Regex::new(r#"variable[: ]+['"]?([a-zA-Z_][a-zA-Z0-9_]*)['"]? not found"#) |
1045 | 0 | .ok() |
1046 | 0 | .and_then(|re| re.captures(error_msg)) |
1047 | | { |
1048 | 0 | return Some(caps.get(1)?.as_str().to_string()); |
1049 | 0 | } |
1050 | | |
1051 | 0 | None |
1052 | 1 | } |
1053 | | |
1054 | | /// Find variables similar to the undefined one (for typo correction) |
1055 | 1 | pub fn find_similar_variables(&self, target: &str) -> Vec<String> { |
1056 | 1 | let mut similar = Vec::new(); |
1057 | | |
1058 | 9 | for var_name in self.bindings1 .keys1 () { |
1059 | 9 | let distance = self.edit_distance(target, var_name); |
1060 | | // Suggest variables with edit distance <= 2 |
1061 | 9 | if distance <= 2 && distance > 0 { |
1062 | 9 | similar.push(var_name.clone()); |
1063 | 9 | }0 |
1064 | | } |
1065 | | |
1066 | | // Sort by similarity (lower distance first) |
1067 | 26 | similar1 .sort_by_key1 (|var| self.edit_distance(target, var)); |
1068 | 1 | similar.truncate(5); // Limit to top 5 suggestions |
1069 | 1 | similar |
1070 | 1 | } |
1071 | | |
1072 | | /// Calculate edit distance between two strings (Levenshtein distance) |
1073 | 35 | pub fn edit_distance(&self, a: &str, b: &str) -> usize { |
1074 | 35 | let a_chars: Vec<char> = a.chars().collect(); |
1075 | 35 | let b_chars: Vec<char> = b.chars().collect(); |
1076 | 35 | let mut matrix = vec![vec![0; b_chars.len() + 1]; a_chars.len() + 1]; |
1077 | | |
1078 | | // Initialize first row and column |
1079 | 70 | for (i, row) in matrix.iter_mut()35 .enumerate35 ().take35 (a_chars.len() + 135 ) { |
1080 | 70 | row[0] = i; |
1081 | 70 | } |
1082 | 92 | for j in 0..=b_chars35 .len35 () { |
1083 | 92 | matrix[0][j] = j; |
1084 | 92 | } |
1085 | | |
1086 | | // Fill the matrix |
1087 | 35 | for i in 1..=a_chars.len() { |
1088 | 57 | for j in 1..=b_chars35 .len35 () { |
1089 | 57 | let cost = usize::from(a_chars[i-1] != b_chars[j-1]); |
1090 | 57 | matrix[i][j] = std::cmp::min( |
1091 | 57 | std::cmp::min( |
1092 | 57 | matrix[i-1][j] + 1, // deletion |
1093 | 57 | matrix[i][j-1] + 1 // insertion |
1094 | 57 | ), |
1095 | 57 | matrix[i-1][j-1] + cost // substitution |
1096 | 57 | ); |
1097 | 57 | } |
1098 | | } |
1099 | | |
1100 | 35 | matrix[a_chars.len()][b_chars.len()] |
1101 | 35 | } |
1102 | | |
1103 | | /// Generate completions for incomplete expressions |
1104 | 0 | pub fn generate_completions_for_error(&self, partial_expr: &str) -> Vec<String> { |
1105 | 0 | let mut completions = Vec::new(); |
1106 | | |
1107 | 0 | if partial_expr.trim().is_empty() { |
1108 | | // Suggest common starting patterns |
1109 | 0 | completions.extend(vec![ |
1110 | 0 | "let ".to_string(), |
1111 | 0 | "if ".to_string(), |
1112 | 0 | "match ".to_string(), |
1113 | 0 | "for ".to_string(), |
1114 | 0 | "while ".to_string(), |
1115 | 0 | "fun ".to_string(), |
1116 | | ]); |
1117 | | |
1118 | | // Add some variable names |
1119 | 0 | for var_name in self.bindings.keys().take(10) { |
1120 | 0 | completions.push(var_name.clone()); |
1121 | 0 | } |
1122 | 0 | } else if partial_expr.starts_with("let ") && partial_expr.contains(" = ") { |
1123 | 0 | // Suggest values for let bindings |
1124 | 0 | completions.extend(vec![ |
1125 | 0 | "0".to_string(), |
1126 | 0 | "true".to_string(), |
1127 | 0 | "false".to_string(), |
1128 | 0 | "[]".to_string(), |
1129 | 0 | "{}".to_string(), |
1130 | 0 | "\"\"".to_string(), |
1131 | 0 | ]); |
1132 | 0 | } else { |
1133 | | // Context-sensitive completions based on available variables |
1134 | 0 | let prefix = partial_expr.trim(); |
1135 | 0 | for var_name in self.bindings.keys() { |
1136 | 0 | if var_name.starts_with(prefix) { |
1137 | 0 | completions.push(var_name.clone()); |
1138 | 0 | } |
1139 | | } |
1140 | | } |
1141 | | |
1142 | 0 | completions.sort(); |
1143 | 0 | completions.dedup(); |
1144 | 0 | completions.truncate(10); // Limit suggestions |
1145 | 0 | completions |
1146 | 0 | } |
1147 | | |
1148 | | /// Apply a recovery option and return the result |
1149 | 0 | pub fn apply_recovery(&mut self, option: RecoveryOption) -> Result<RecoveryResult> { |
1150 | 0 | match option { |
1151 | 0 | RecoveryOption::ContinueWithDefault(expr) => { |
1152 | 0 | self.error_recovery = None; |
1153 | 0 | Ok(RecoveryResult::Recovered(expr)) |
1154 | | } |
1155 | 0 | RecoveryOption::RetryWith(expr) => { |
1156 | 0 | self.error_recovery = None; |
1157 | 0 | Ok(RecoveryResult::Recovered(expr)) |
1158 | | } |
1159 | | RecoveryOption::ShowCompletions => { |
1160 | 0 | if let Some(recovery) = &self.error_recovery { |
1161 | 0 | Ok(RecoveryResult::ShowCompletions(recovery.completions.clone())) |
1162 | | } else { |
1163 | 0 | Ok(RecoveryResult::ShowCompletions(Vec::new())) |
1164 | | } |
1165 | | } |
1166 | | RecoveryOption::Abort => { |
1167 | 0 | self.error_recovery = None; |
1168 | 0 | Ok(RecoveryResult::Aborted) |
1169 | | } |
1170 | | RecoveryOption::RestoreCheckpoint => { |
1171 | 0 | if let Some(recovery) = self.error_recovery.take() { |
1172 | 0 | recovery.error_checkpoint.restore_to(self); |
1173 | 0 | Ok(RecoveryResult::Restored) |
1174 | | } else { |
1175 | 0 | Err(anyhow::anyhow!("No error recovery context available")) |
1176 | | } |
1177 | | } |
1178 | 0 | RecoveryOption::UseHistoryValue(index) => { |
1179 | 0 | if index > 0 && index <= self.result_history.len() { |
1180 | | // Check that history value exists |
1181 | 0 | let expr = format!("_{index}"); |
1182 | 0 | self.error_recovery = None; |
1183 | 0 | Ok(RecoveryResult::Recovered(expr)) |
1184 | | } else { |
1185 | 0 | Err(anyhow::anyhow!("History index {} not available", index)) |
1186 | | } |
1187 | | } |
1188 | | } |
1189 | 0 | } |
1190 | | |
1191 | | /// Get current error recovery context |
1192 | 0 | pub fn get_error_recovery(&self) -> Option<&ErrorRecovery> { |
1193 | 0 | self.error_recovery.as_ref() |
1194 | 0 | } |
1195 | | |
1196 | | /// Clear error recovery context |
1197 | 0 | pub fn clear_error_recovery(&mut self) { |
1198 | 0 | self.error_recovery = None; |
1199 | 0 | } |
1200 | | |
1201 | | /// Format error recovery options for display |
1202 | 0 | pub fn format_error_recovery(&self, recovery: &ErrorRecovery) -> String { |
1203 | 0 | let mut output = String::new(); |
1204 | | |
1205 | 0 | output.push_str(&format!("Error: {}\n", recovery.error_message)); |
1206 | | |
1207 | 0 | if let Some((line, col)) = recovery.position { |
1208 | 0 | output.push_str(&format!(" │ {} \n", recovery.failed_expression)); |
1209 | 0 | output.push_str(&format!(" │ {}↑ at line {}:{}\n", |
1210 | 0 | " ".repeat(col.saturating_sub(1)), line, col)); |
1211 | 0 | } |
1212 | | |
1213 | 0 | output.push_str("\nRecovery Options:\n"); |
1214 | | |
1215 | 0 | for (i, option) in recovery.options.iter().enumerate() { |
1216 | 0 | match option { |
1217 | 0 | RecoveryOption::ContinueWithDefault(expr) => { |
1218 | 0 | output.push_str(&format!(" {}. Continue with: {}\n", i + 1, expr)); |
1219 | 0 | } |
1220 | 0 | RecoveryOption::RetryWith(expr) => { |
1221 | 0 | output.push_str(&format!(" {}. Retry with: {}\n", i + 1, expr)); |
1222 | 0 | } |
1223 | 0 | RecoveryOption::ShowCompletions => { |
1224 | 0 | output.push_str(&format!(" {}. Show completions\n", i + 1)); |
1225 | 0 | } |
1226 | 0 | RecoveryOption::Abort => { |
1227 | 0 | output.push_str(&format!(" {}. Abort operation\n", i + 1)); |
1228 | 0 | } |
1229 | 0 | RecoveryOption::RestoreCheckpoint => { |
1230 | 0 | output.push_str(&format!(" {}. Restore from checkpoint\n", i + 1)); |
1231 | 0 | } |
1232 | 0 | RecoveryOption::UseHistoryValue(index) => { |
1233 | 0 | output.push_str(&format!(" {}. Use history value _{}\n", i + 1, index)); |
1234 | 0 | } |
1235 | | } |
1236 | | } |
1237 | | |
1238 | 0 | if !recovery.completions.is_empty() { |
1239 | 0 | output.push_str("\nSuggestions: "); |
1240 | 0 | output.push_str(&recovery.completions.join(", ")); |
1241 | 0 | output.push('\n'); |
1242 | 0 | } |
1243 | | |
1244 | 0 | output.push_str("\nEnter option number, or press Ctrl+C to abort."); |
1245 | 0 | output |
1246 | 0 | } |
1247 | | |
1248 | | /// Check if error recovery is available |
1249 | 0 | pub fn has_error_recovery(&self) -> bool { |
1250 | 0 | self.error_recovery.is_some() |
1251 | 0 | } |
1252 | | |
1253 | | /// Get a formatted error recovery prompt if available |
1254 | 0 | pub fn get_error_recovery_prompt(&self) -> Option<String> { |
1255 | 0 | self.error_recovery.as_ref().map(|recovery| self.format_error_recovery(recovery)) |
1256 | 0 | } |
1257 | | |
1258 | | /// Internal evaluation method (called by state machine) |
1259 | 0 | fn eval_internal(&mut self, input: &str) -> Result<String> { |
1260 | | // This will be the core evaluation logic without state machine overhead |
1261 | | // For now, use a simplified approach that bypasses the state machine |
1262 | | |
1263 | | // Reset memory tracker for fresh evaluation |
1264 | 0 | self.memory.reset(); |
1265 | | |
1266 | | // Track input memory |
1267 | 0 | self.memory.try_alloc(input.len())?; |
1268 | | |
1269 | | // Check for magic commands |
1270 | 0 | let trimmed = input.trim(); |
1271 | | |
1272 | 0 | if trimmed.starts_with('%') { |
1273 | 0 | return self.handle_magic_command(trimmed); |
1274 | 0 | } |
1275 | | |
1276 | | // Check for REPL commands |
1277 | 0 | if trimmed.starts_with(':') { |
1278 | 0 | let (should_quit, output) = self.handle_command_with_output(trimmed)?; |
1279 | 0 | if should_quit { |
1280 | 0 | return Ok("Exiting REPL...".to_string()); |
1281 | 0 | } |
1282 | 0 | return Ok(output); |
1283 | 0 | } |
1284 | | |
1285 | | // Set evaluation deadline |
1286 | 0 | let deadline = Instant::now() + self.config.timeout; |
1287 | | |
1288 | | // Try to parse the input as an expression |
1289 | 0 | let mut parser = Parser::new(trimmed); |
1290 | 0 | let ast = parser.parse().context("Failed to parse input")?; |
1291 | | |
1292 | | // Track AST memory |
1293 | 0 | self.memory.try_alloc(std::mem::size_of_val(&ast))?; |
1294 | | |
1295 | | // Evaluate the expression |
1296 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
1297 | | |
1298 | | // Add to history and update variables |
1299 | 0 | self.history.push(input.to_string()); |
1300 | 0 | self.result_history.push(value.clone()); |
1301 | 0 | self.update_history_variables(); |
1302 | | |
1303 | | // Return string representation (suppress Unit values from loops/statements) |
1304 | 0 | match value { |
1305 | 0 | Value::Unit => Ok(String::new()), |
1306 | 0 | _ => Ok(value.to_string()) |
1307 | | } |
1308 | 0 | } |
1309 | | |
1310 | | /// Evaluate an expression with resource bounds |
1311 | | /// |
1312 | | /// # Errors |
1313 | | /// |
1314 | | /// Returns an error if: |
1315 | | /// - Memory limit is exceeded |
1316 | | /// - Timeout is reached |
1317 | | /// - Stack depth limit is exceeded |
1318 | | /// - Parse or evaluation fails |
1319 | | /// |
1320 | | /// # Example |
1321 | | /// ``` |
1322 | | /// use ruchy::runtime::Repl; |
1323 | | /// |
1324 | | /// let mut repl = Repl::new().unwrap(); |
1325 | | /// let result = repl.eval("1 + 1"); |
1326 | | /// assert_eq!(result.unwrap(), "2"); |
1327 | | /// ``` |
1328 | | /// Handle mode-specific evaluation (complexity: 9) |
1329 | 69 | fn handle_mode_evaluation(&mut self, trimmed: &str) -> Option<Result<String>> { |
1330 | 69 | if trimmed.starts_with(':') { |
1331 | 0 | return None; // Colon commands are handled normally |
1332 | 69 | } |
1333 | | |
1334 | 69 | match self.mode { |
1335 | 0 | ReplMode::Shell => Some(self.execute_shell_command(trimmed)), |
1336 | 0 | ReplMode::Pkg => Some(self.handle_pkg_command(trimmed)), |
1337 | 0 | ReplMode::Help => Some(self.handle_help_command(trimmed)), |
1338 | 0 | ReplMode::Sql => Some(Ok(format!("SQL mode not yet implemented: {trimmed}"))), |
1339 | 0 | ReplMode::Math => Some(self.handle_math_command(trimmed)), |
1340 | 0 | ReplMode::Debug => Some(self.handle_debug_evaluation(trimmed)), |
1341 | 0 | ReplMode::Time => Some(self.handle_timed_evaluation(trimmed)), |
1342 | 0 | ReplMode::Test => Some(self.handle_test_evaluation(trimmed)), |
1343 | 69 | ReplMode::Normal => None, |
1344 | | } |
1345 | 69 | } |
1346 | | |
1347 | | /// Check if input is a shell command (complexity: 5) |
1348 | 69 | fn is_shell_command(&self, trimmed: &str) -> bool { |
1349 | 69 | if let Some(stripped0 ) = trimmed.strip_prefix('!') { |
1350 | | // Not a shell command if it's a unary expression |
1351 | 0 | !(stripped.starts_with("true") || |
1352 | 0 | stripped.starts_with("false") || |
1353 | 0 | stripped.starts_with('(') || |
1354 | 0 | (stripped.chars().next().is_some_and(char::is_lowercase) && |
1355 | 0 | stripped.chars().all(|c| c.is_alphanumeric() || c == '_'))) |
1356 | | } else { |
1357 | 69 | false |
1358 | | } |
1359 | 69 | } |
1360 | | |
1361 | | /// Handle shell substitution in let bindings (complexity: 6) |
1362 | 69 | fn handle_shell_substitution(&mut self, input: &str, trimmed: &str) -> Option<Result<String>> { |
1363 | 69 | if !trimmed.starts_with("let ") { |
1364 | 52 | return None; |
1365 | 17 | } |
1366 | | |
1367 | 17 | if let Some(bang_pos0 ) = trimmed.find(" = !") { |
1368 | 0 | let var_part = &trimmed[4..bang_pos]; |
1369 | 0 | let command_part = &trimmed[bang_pos + 4..]; |
1370 | | |
1371 | | // Execute the shell command |
1372 | 0 | let result = match self.execute_shell_command(command_part) { |
1373 | 0 | Ok(r) => r, |
1374 | 0 | Err(e) => return Some(Err(e)), |
1375 | | }; |
1376 | | |
1377 | | // Create a let binding with the result |
1378 | 0 | let modified_input = format!("let {} = \"{}\"", var_part, result.replace('"', "\\\"")); |
1379 | | |
1380 | | // Parse and evaluate the modified input |
1381 | 0 | let deadline = Instant::now() + self.config.timeout; |
1382 | 0 | let mut parser = Parser::new(&modified_input); |
1383 | 0 | let ast = match parser.parse() { |
1384 | 0 | Ok(a) => a, |
1385 | 0 | Err(e) => return Some(Err(e.context("Failed to parse shell substitution"))), |
1386 | | }; |
1387 | | |
1388 | 0 | if let Err(e) = self.memory.try_alloc(std::mem::size_of_val(&ast)) { |
1389 | 0 | return Some(Err(e)); |
1390 | 0 | } |
1391 | | |
1392 | 0 | match self.evaluate_expr(&ast, deadline, 0) { |
1393 | 0 | Ok(value) => { |
1394 | 0 | self.history.push(input.to_string()); |
1395 | 0 | self.result_history.push(value); |
1396 | 0 | self.update_history_variables(); |
1397 | 0 | Some(Ok(String::new())) |
1398 | | } |
1399 | 0 | Err(e) => Some(Err(e)), |
1400 | | } |
1401 | | } else { |
1402 | 17 | None |
1403 | | } |
1404 | 69 | } |
1405 | | |
1406 | | /// Main eval function with reduced complexity (complexity: 10) |
1407 | 69 | pub fn eval(&mut self, input: &str) -> Result<String> { |
1408 | | // Reset memory tracker for fresh evaluation |
1409 | 69 | self.memory.reset(); |
1410 | 69 | self.memory.try_alloc(input.len())?0 ; |
1411 | | |
1412 | 69 | let trimmed = input.trim(); |
1413 | | |
1414 | | // Handle progressive mode activation via attributes |
1415 | 69 | if let Some(activated_mode0 ) = self.detect_mode_activation(trimmed) { |
1416 | 0 | self.mode = activated_mode; |
1417 | 0 | return Ok(format!("Activated {} mode", self.get_mode())); |
1418 | 69 | } |
1419 | | |
1420 | | // Handle mode-specific evaluation |
1421 | 69 | if let Some(result0 ) = self.handle_mode_evaluation(trimmed) { |
1422 | 0 | return result; |
1423 | 69 | } |
1424 | | // Handle magic commands |
1425 | 69 | if trimmed.starts_with('%') { |
1426 | 0 | return self.handle_magic_command(trimmed); |
1427 | 69 | } |
1428 | | |
1429 | | // Check for REPL commands |
1430 | 69 | if trimmed.starts_with(':') { |
1431 | 0 | let (should_quit, output) = self.handle_command_with_output(trimmed)?; |
1432 | 0 | if should_quit { |
1433 | 0 | return Ok("Exiting REPL...".to_string()); |
1434 | 0 | } |
1435 | 0 | return Ok(output); |
1436 | 69 | } |
1437 | | |
1438 | | // Check for shell commands |
1439 | 69 | if self.is_shell_command(trimmed) { |
1440 | 0 | let stripped = trimmed.strip_prefix('!').unwrap(); |
1441 | 0 | return self.execute_shell_command(stripped); |
1442 | 69 | } |
1443 | | |
1444 | | // Check for introspection commands |
1445 | 69 | if let Some(stripped0 ) = trimmed.strip_prefix("??") { |
1446 | 0 | return self.detailed_introspection(stripped.trim()); |
1447 | 69 | } else if trimmed.starts_with('?') && !trimmed.starts_with("?:")0 { |
1448 | 0 | return self.basic_introspection(trimmed[1..].trim()); |
1449 | 69 | } |
1450 | | |
1451 | | // Handle shell substitution in let bindings |
1452 | 69 | if let Some(result0 ) = self.handle_shell_substitution(input, trimmed) { |
1453 | 0 | return result; |
1454 | 69 | } |
1455 | | |
1456 | | // Set evaluation deadline |
1457 | 69 | let deadline = Instant::now() + self.config.timeout; |
1458 | | |
1459 | | // Parse the input |
1460 | 69 | let mut parser = Parser::new(input); |
1461 | 69 | let ast = match parser.parse() { |
1462 | 69 | Ok(ast) => ast, |
1463 | 0 | Err(e) => { |
1464 | | // Create error recovery context for parse errors using original error message |
1465 | 0 | let _recovery = self.create_error_recovery(input, &e.to_string()); |
1466 | 0 | let parse_error = e.context("Failed to parse input"); |
1467 | 0 | return Err(parse_error); |
1468 | | } |
1469 | | }; |
1470 | | |
1471 | | // Check memory for AST |
1472 | 69 | self.memory.try_alloc(std::mem::size_of_val(&ast))?0 ; |
1473 | | |
1474 | | // Evaluate the expression with debug capture |
1475 | 69 | let value67 = match self.evaluate_expr(&ast, deadline, 0) { |
1476 | 67 | Ok(value) => { |
1477 | | // Clear debug info on successful evaluation |
1478 | 67 | self.last_error_debug = None; |
1479 | 67 | value |
1480 | | } |
1481 | 2 | Err(e) => { |
1482 | | // Capture debug information |
1483 | 2 | self.last_error_debug = Some(DebugInfo { |
1484 | 2 | expression: input.to_string(), |
1485 | 2 | error_message: e.to_string(), |
1486 | 2 | stack_trace: self.generate_stack_trace(&e), |
1487 | 2 | bindings_snapshot: self.bindings.clone(), |
1488 | 2 | timestamp: std::time::SystemTime::now(), |
1489 | 2 | }); |
1490 | | |
1491 | | // Create error recovery context for interactive recovery |
1492 | 2 | let _recovery = self.create_error_recovery(input, &e.to_string()); |
1493 | | |
1494 | 2 | return Err(e); |
1495 | | } |
1496 | | }; |
1497 | | |
1498 | | // Store successful evaluation |
1499 | 67 | self.history.push(input.to_string()); |
1500 | 67 | self.result_history.push(value.clone()); |
1501 | | |
1502 | | // Update history variables |
1503 | 67 | self.update_history_variables(); |
1504 | | |
1505 | | // Let bindings are handled in evaluate_expr, no need to duplicate here |
1506 | | |
1507 | | // Return string representation (suppress Unit values from loops/statements) |
1508 | 67 | match value { |
1509 | 49 | Value::Unit => Ok(String::new()), |
1510 | 18 | _ => Ok(value.to_string()) |
1511 | | } |
1512 | 69 | } |
1513 | | |
1514 | | /// Get tab completions for the given input at the cursor position |
1515 | 0 | pub fn complete(&self, input: &str) -> Vec<String> { |
1516 | 0 | let pos = input.len(); |
1517 | 0 | let mut completer = RuchyCompleter::new(); |
1518 | 0 | completer.get_completions(input, pos, &self.bindings) |
1519 | 0 | } |
1520 | | |
1521 | | /// Get the current REPL mode |
1522 | 0 | pub fn get_mode(&self) -> &str { |
1523 | 0 | match self.mode { |
1524 | 0 | ReplMode::Normal => "normal", |
1525 | 0 | ReplMode::Shell => "shell", |
1526 | 0 | ReplMode::Pkg => "pkg", |
1527 | 0 | ReplMode::Help => "help", |
1528 | 0 | ReplMode::Sql => "sql", |
1529 | 0 | ReplMode::Math => "math", |
1530 | 0 | ReplMode::Debug => "debug", |
1531 | 0 | ReplMode::Time => "time", |
1532 | 0 | ReplMode::Test => "test", |
1533 | | } |
1534 | 0 | } |
1535 | | |
1536 | | /// Get the current prompt |
1537 | 0 | pub fn get_prompt(&self) -> String { |
1538 | 0 | self.mode.prompt() |
1539 | 0 | } |
1540 | | |
1541 | | /// Create a new binding (for let/var) - handles shadowing |
1542 | 17 | fn create_binding(&mut self, name: String, value: Value, is_mutable: bool) { |
1543 | 17 | self.bindings.insert(name.clone(), value); |
1544 | 17 | self.binding_mutability.insert(name, is_mutable); |
1545 | 17 | } |
1546 | | |
1547 | | /// Try to update an existing binding (for assignment) |
1548 | 0 | fn update_binding(&mut self, name: &str, value: Value) -> Result<()> { |
1549 | 0 | if !self.bindings.contains_key(name) { |
1550 | 0 | bail!("Cannot assign to undefined variable '{}'. Declare it first with 'let' or 'var'", name) |
1551 | 0 | } |
1552 | | |
1553 | 0 | let is_mutable = self.binding_mutability.get(name).copied().unwrap_or(false); |
1554 | 0 | if !is_mutable { |
1555 | 0 | bail!("Cannot assign to immutable binding '{}'. Use 'var' for mutable bindings or shadow with 'let'", name) |
1556 | 0 | } |
1557 | | |
1558 | 0 | self.bindings.insert(name.to_string(), value); |
1559 | 0 | Ok(()) |
1560 | 0 | } |
1561 | | |
1562 | | /// Get the value of a binding |
1563 | 34 | fn get_binding(&self, name: &str) -> Option<Value> { |
1564 | 34 | self.bindings.get(name).cloned() |
1565 | 34 | } |
1566 | | |
1567 | | /// Check if a binding exists |
1568 | | #[allow(dead_code)] |
1569 | 0 | fn has_binding(&self, name: &str) -> bool { |
1570 | 0 | self.bindings.contains_key(name) |
1571 | 0 | } |
1572 | | |
1573 | | /// Evaluate an expression to a value |
1574 | | #[allow(clippy::too_many_lines)] |
1575 | | #[allow(clippy::cognitive_complexity)] |
1576 | 220 | fn evaluate_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1577 | | // Check resource bounds |
1578 | 220 | if Instant::now() > deadline { |
1579 | 0 | bail!("Evaluation timeout exceeded"); |
1580 | 220 | } |
1581 | 220 | if depth > self.config.max_depth { |
1582 | 0 | bail!("Maximum recursion depth {} exceeded", self.config.max_depth); |
1583 | 220 | } |
1584 | | |
1585 | | // COMPLEXITY REDUCTION: Dispatcher pattern by expression category |
1586 | 220 | match &expr.kind { |
1587 | | // Basic expressions (literals, identifiers, binaries, unaries) |
1588 | | ExprKind::Literal(_) | ExprKind::Binary { .. } | ExprKind::Unary { .. } |
1589 | | | ExprKind::Identifier(_) | ExprKind::QualifiedName { .. } => { |
1590 | 151 | self.evaluate_basic_expr(expr, deadline, depth) |
1591 | | } |
1592 | | |
1593 | | // Control flow expressions |
1594 | | ExprKind::If { .. } | ExprKind::Match { .. } | ExprKind::For { .. } |
1595 | | | ExprKind::While { .. } | ExprKind::IfLet { .. } | ExprKind::WhileLet { .. } |
1596 | | | ExprKind::Loop { .. } | ExprKind::Break { .. } | ExprKind::Continue { .. } |
1597 | | | ExprKind::TryCatch { .. } => { |
1598 | 1 | self.evaluate_control_flow_expr(expr, deadline, depth) |
1599 | | } |
1600 | | |
1601 | | // Data structure expressions |
1602 | | ExprKind::List(_) | ExprKind::Tuple(_) | ExprKind::ObjectLiteral { .. } |
1603 | | | ExprKind::Range { .. } | ExprKind::FieldAccess { .. } | ExprKind::OptionalFieldAccess { .. } |
1604 | | | ExprKind::IndexAccess { .. } | ExprKind::Slice { .. } => { |
1605 | 0 | self.evaluate_data_structure_expr(expr, deadline, depth) |
1606 | | } |
1607 | | |
1608 | | // Function and call expressions |
1609 | | ExprKind::Function { .. } | ExprKind::Lambda { .. } | ExprKind::Call { .. } |
1610 | | | ExprKind::MethodCall { .. } | ExprKind::OptionalMethodCall { .. } => { |
1611 | 50 | self.evaluate_function_expr(expr, deadline, depth) |
1612 | | } |
1613 | | |
1614 | | // Advanced language features |
1615 | 18 | _ => self.evaluate_advanced_expr(expr, deadline, depth) |
1616 | | } |
1617 | 220 | } |
1618 | | |
1619 | | // COMPLEXITY REDUCTION: Basic expressions dispatcher |
1620 | 151 | fn evaluate_basic_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1621 | 151 | match &expr.kind { |
1622 | 96 | ExprKind::Literal(lit) => self.evaluate_literal(lit), |
1623 | 21 | ExprKind::Binary { left, op, right } => { |
1624 | 21 | self.evaluate_binary_expr(left, *op, right, deadline, depth) |
1625 | | } |
1626 | 0 | ExprKind::Unary { op, operand } => { |
1627 | 0 | self.evaluate_unary_expr(*op, operand, deadline, depth) |
1628 | | } |
1629 | 34 | ExprKind::Identifier(name) => self.evaluate_identifier(name), |
1630 | 0 | ExprKind::QualifiedName { module, name } => { |
1631 | 0 | Ok(Self::evaluate_qualified_name(module, name)) |
1632 | | } |
1633 | 0 | _ => bail!("Non-basic expression in basic dispatcher"), |
1634 | | } |
1635 | 151 | } |
1636 | | |
1637 | | // COMPLEXITY REDUCTION: Control flow expressions dispatcher |
1638 | 1 | fn evaluate_control_flow_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1639 | 1 | match &expr.kind { |
1640 | 1 | ExprKind::If { condition, then_branch, else_branch } => { |
1641 | 1 | self.evaluate_if(condition, then_branch, else_branch.as_deref(), deadline, depth) |
1642 | | } |
1643 | 0 | ExprKind::Match { expr: match_expr, arms } => { |
1644 | 0 | self.evaluate_match(match_expr, arms, deadline, depth) |
1645 | | } |
1646 | 0 | ExprKind::For { var, pattern, iter, body } => { |
1647 | 0 | self.evaluate_for_loop(var, pattern.as_ref(), iter, body, deadline, depth) |
1648 | | } |
1649 | 0 | ExprKind::While { condition, body } => { |
1650 | 0 | self.evaluate_while_loop(condition, body, deadline, depth) |
1651 | | } |
1652 | 0 | ExprKind::IfLet { pattern, expr, then_branch, else_branch } => { |
1653 | 0 | self.evaluate_if_let(pattern, expr, then_branch, else_branch.as_deref(), deadline, depth) |
1654 | | } |
1655 | 0 | ExprKind::WhileLet { pattern, expr, body } => { |
1656 | 0 | self.evaluate_while_let(pattern, expr, body, deadline, depth) |
1657 | | } |
1658 | 0 | ExprKind::Loop { body } => self.evaluate_loop(body, deadline, depth), |
1659 | 0 | ExprKind::Break { .. } => Err(anyhow::anyhow!("break")), |
1660 | 0 | ExprKind::Continue { .. } => Err(anyhow::anyhow!("continue")), |
1661 | 0 | ExprKind::TryCatch { try_expr, catch_expr } => { |
1662 | 0 | self.evaluate_try_catch(try_expr, catch_expr, deadline, depth) |
1663 | | } |
1664 | 0 | _ => bail!("Non-control-flow expression in control flow dispatcher"), |
1665 | | } |
1666 | 1 | } |
1667 | | |
1668 | | // COMPLEXITY REDUCTION: Data structure expressions dispatcher |
1669 | 0 | fn evaluate_data_structure_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1670 | 0 | match &expr.kind { |
1671 | 0 | ExprKind::List(elements) => self.evaluate_list_literal(elements, deadline, depth), |
1672 | 0 | ExprKind::Tuple(elements) => self.evaluate_tuple_literal(elements, deadline, depth), |
1673 | 0 | ExprKind::ObjectLiteral { fields } => self.evaluate_object_literal(fields, deadline, depth), |
1674 | 0 | ExprKind::Range { start, end, inclusive } => { |
1675 | 0 | self.evaluate_range_literal(start, end, *inclusive, deadline, depth) |
1676 | | } |
1677 | 0 | ExprKind::FieldAccess { object, field } => { |
1678 | 0 | self.evaluate_field_access(object, field, deadline, depth) |
1679 | | } |
1680 | 0 | ExprKind::OptionalFieldAccess { object, field } => { |
1681 | 0 | self.evaluate_optional_field_access(object, field, deadline, depth) |
1682 | | } |
1683 | 0 | ExprKind::IndexAccess { object, index } => { |
1684 | 0 | self.evaluate_index_access(object, index, deadline, depth) |
1685 | | } |
1686 | 0 | ExprKind::Slice { object, start, end } => { |
1687 | 0 | self.evaluate_slice(object, start.as_deref(), end.as_deref(), deadline, depth) |
1688 | | } |
1689 | 0 | _ => bail!("Non-data-structure expression in data structure dispatcher"), |
1690 | | } |
1691 | 0 | } |
1692 | | |
1693 | | // COMPLEXITY REDUCTION: Function expressions dispatcher |
1694 | 50 | fn evaluate_function_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1695 | 50 | match &expr.kind { |
1696 | 0 | ExprKind::Function { name, params, body, .. } => { |
1697 | 0 | Ok(self.evaluate_function_definition(name, params, body)) |
1698 | | } |
1699 | 0 | ExprKind::Lambda { params, body } => Ok(Self::evaluate_lambda_expression(params, body)), |
1700 | 50 | ExprKind::Call { func, args } => self.evaluate_call(func, args, deadline, depth), |
1701 | 0 | ExprKind::MethodCall { receiver, method, args } => { |
1702 | 0 | let receiver_val = self.evaluate_expr(receiver, deadline, depth + 1)?; |
1703 | 0 | match receiver_val { |
1704 | 0 | Value::List(items) => { |
1705 | 0 | self.evaluate_list_methods(items, method, args, deadline, depth) |
1706 | | } |
1707 | 0 | Value::String(s) => { |
1708 | | // Special case for Array constructor |
1709 | 0 | if s == "Array constructor" && method == "new" { |
1710 | 0 | if args.len() != 2 { |
1711 | 0 | bail!("Array.new() expects 2 arguments (size, default_value), got {}", args.len()); |
1712 | 0 | } |
1713 | | // Evaluate arguments |
1714 | 0 | let size_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
1715 | 0 | let default_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
1716 | | |
1717 | | // Return a stub Array representation |
1718 | 0 | return Ok(Value::String(format!("Array(size: {size_val}, default: {default_val})"))); |
1719 | 0 | } |
1720 | 0 | Self::evaluate_string_methods(&s, method, args, deadline, depth) |
1721 | | } |
1722 | 0 | Value::Int(n) => Self::evaluate_int_methods(n, method), |
1723 | 0 | Value::Float(f) => Self::evaluate_float_methods(f, method), |
1724 | 0 | Value::Object(obj) => { |
1725 | 0 | Self::evaluate_object_methods(obj, method, args, deadline, depth) |
1726 | | } |
1727 | 0 | Value::HashMap(map) => { |
1728 | 0 | self.evaluate_hashmap_methods(map, method, args, deadline, depth) |
1729 | | } |
1730 | 0 | Value::HashSet(set) => { |
1731 | 0 | self.evaluate_hashset_methods(set, method, args, deadline, depth) |
1732 | | } |
1733 | | Value::EnumVariant { .. } => { |
1734 | 0 | self.evaluate_enum_methods(receiver_val, method, args, deadline, depth) |
1735 | | } |
1736 | 0 | _ => bail!("Method {} not supported on this type", method), |
1737 | | } |
1738 | | } |
1739 | 0 | ExprKind::OptionalMethodCall { receiver, method, args } => { |
1740 | 0 | self.evaluate_optional_method_call(receiver, method, args, deadline, depth) |
1741 | | } |
1742 | 0 | _ => bail!("Non-function expression in function dispatcher"), |
1743 | | } |
1744 | 50 | } |
1745 | | |
1746 | | // COMPLEXITY REDUCTION: Advanced expressions dispatcher |
1747 | | /// Dispatch binding and assignment expressions (complexity: 6) |
1748 | 18 | fn dispatch_binding_exprs(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Option<Result<Value>> { |
1749 | 18 | match &expr.kind { |
1750 | 17 | ExprKind::Let { name, type_annotation: _, value, body, is_mutable } => { |
1751 | 17 | Some(self.evaluate_let_binding(name, value, body, *is_mutable, deadline, depth)) |
1752 | | } |
1753 | 0 | ExprKind::LetPattern { pattern, type_annotation: _, value, body, is_mutable } => { |
1754 | 0 | Some(self.evaluate_let_pattern(pattern, value, body, *is_mutable, deadline, depth)) |
1755 | | } |
1756 | 0 | ExprKind::Assign { target, value } => { |
1757 | 0 | Some(self.evaluate_assignment(target, value, deadline, depth)) |
1758 | | } |
1759 | 1 | ExprKind::Block(exprs) => Some(self.evaluate_block(exprs, deadline, depth)), |
1760 | 0 | ExprKind::Module { name: _name, body } => { |
1761 | 0 | Some(self.evaluate_expr(body, deadline, depth + 1)) |
1762 | | } |
1763 | 0 | _ => None, |
1764 | | } |
1765 | 18 | } |
1766 | | |
1767 | | /// Dispatch data structure expressions (complexity: 5) |
1768 | 0 | fn dispatch_data_exprs(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Option<Result<Value>> { |
1769 | 0 | match &expr.kind { |
1770 | 0 | ExprKind::DataFrame { columns } => { |
1771 | 0 | Some(self.evaluate_dataframe_literal(columns, deadline, depth)) |
1772 | | } |
1773 | 0 | ExprKind::DataFrameOperation { .. } => Some(Self::evaluate_dataframe_operation()), |
1774 | 0 | ExprKind::StructLiteral { name: _, fields } => { |
1775 | 0 | Some(self.evaluate_struct_literal(fields, deadline, depth)) |
1776 | | } |
1777 | 0 | ExprKind::Pipeline { expr, stages } => { |
1778 | 0 | Some(self.evaluate_pipeline(expr, stages, deadline, depth)) |
1779 | | } |
1780 | 0 | ExprKind::StringInterpolation { parts } => { |
1781 | 0 | Some(self.evaluate_string_interpolation(parts, deadline, depth)) |
1782 | | } |
1783 | 0 | _ => None, |
1784 | | } |
1785 | 0 | } |
1786 | | |
1787 | | /// Dispatch type definition expressions (complexity: 5) |
1788 | 0 | fn dispatch_type_definitions(&mut self, expr: &Expr) -> Option<Result<Value>> { |
1789 | 0 | match &expr.kind { |
1790 | 0 | ExprKind::Enum { name, variants, .. } => { |
1791 | 0 | Some(Ok(self.evaluate_enum_definition(name, variants))) |
1792 | | } |
1793 | 0 | ExprKind::Struct { name, fields, .. } => { |
1794 | 0 | Some(Ok(Self::evaluate_struct_definition(name, fields))) |
1795 | | } |
1796 | 0 | ExprKind::Trait { name, methods, .. } => { |
1797 | 0 | Some(Ok(Self::evaluate_trait_definition(name, methods))) |
1798 | | } |
1799 | 0 | ExprKind::Impl { for_type, methods, .. } => { |
1800 | 0 | Some(Ok(self.evaluate_impl_block(for_type, methods))) |
1801 | | } |
1802 | 0 | _ => None, |
1803 | | } |
1804 | 0 | } |
1805 | | |
1806 | | /// Dispatch Result/Option expressions (complexity: 5) |
1807 | 0 | fn dispatch_result_option_exprs(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Option<Result<Value>> { |
1808 | 0 | match &expr.kind { |
1809 | 0 | ExprKind::Ok { value } => Some(self.evaluate_result_ok(value, deadline, depth)), |
1810 | 0 | ExprKind::Err { error } => Some(self.evaluate_result_err(error, deadline, depth)), |
1811 | 0 | ExprKind::Some { value } => Some(self.evaluate_option_some(value, deadline, depth)), |
1812 | 0 | ExprKind::None => Some(Ok(Self::evaluate_option_none())), |
1813 | 0 | ExprKind::Try { expr } => Some(self.evaluate_try_operator(expr, deadline, depth)), |
1814 | 0 | _ => None, |
1815 | | } |
1816 | 0 | } |
1817 | | |
1818 | | /// Dispatch control flow expressions (complexity: 4) |
1819 | 0 | fn dispatch_control_flow_exprs(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Option<Result<Value>> { |
1820 | 0 | match &expr.kind { |
1821 | 0 | ExprKind::Return { value } => { |
1822 | 0 | if let Some(val) = value { |
1823 | 0 | let result = self.evaluate_expr(val, deadline, depth + 1); |
1824 | 0 | Some(result.and_then(|v| Err(anyhow::anyhow!("return:{}", v)))) |
1825 | | } else { |
1826 | 0 | Some(Err(anyhow::anyhow!("return:()"))) |
1827 | | } |
1828 | | } |
1829 | 0 | ExprKind::Await { expr } => Some(self.evaluate_await_expr(expr, deadline, depth)), |
1830 | 0 | ExprKind::AsyncBlock { body } => Some(self.evaluate_async_block(body, deadline, depth)), |
1831 | 0 | _ => None, |
1832 | | } |
1833 | 0 | } |
1834 | | |
1835 | | /// Main advanced expression dispatcher (complexity: 8) |
1836 | 18 | fn evaluate_advanced_expr(&mut self, expr: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
1837 | | // Try binding and assignment expressions |
1838 | 18 | if let Some(result) = self.dispatch_binding_exprs(expr, deadline, depth) { |
1839 | 18 | return result; |
1840 | 0 | } |
1841 | | |
1842 | | // Try data structure expressions |
1843 | 0 | if let Some(result) = self.dispatch_data_exprs(expr, deadline, depth) { |
1844 | 0 | return result; |
1845 | 0 | } |
1846 | | |
1847 | | // Try type definitions |
1848 | 0 | if let Some(result) = self.dispatch_type_definitions(expr) { |
1849 | 0 | return result; |
1850 | 0 | } |
1851 | | |
1852 | | // Try Result/Option expressions |
1853 | 0 | if let Some(result) = self.dispatch_result_option_exprs(expr, deadline, depth) { |
1854 | 0 | return result; |
1855 | 0 | } |
1856 | | |
1857 | | // Try control flow expressions |
1858 | 0 | if let Some(result) = self.dispatch_control_flow_exprs(expr, deadline, depth) { |
1859 | 0 | return result; |
1860 | 0 | } |
1861 | | |
1862 | | // Handle remaining cases |
1863 | 0 | match &expr.kind { |
1864 | 0 | ExprKind::Command { program, args, env: _, working_dir: _ } => { |
1865 | 0 | Self::evaluate_command(program, args, deadline, depth) |
1866 | | } |
1867 | 0 | ExprKind::Macro { name, args } => { |
1868 | 0 | self.evaluate_macro(name, args, deadline, depth) |
1869 | | } |
1870 | 0 | ExprKind::Import { path, items } => { |
1871 | 0 | self.evaluate_import(path, items) |
1872 | | } |
1873 | 0 | ExprKind::Export { items } => { |
1874 | 0 | self.evaluate_export(items) |
1875 | | } |
1876 | | ExprKind::Spread { .. } => { |
1877 | 0 | bail!("Spread operator (...) can only be used inside array literals") |
1878 | | } |
1879 | 0 | _ => bail!("Expression type not yet implemented: {:?}", expr.kind), |
1880 | | } |
1881 | 18 | } |
1882 | | |
1883 | | // ======================================================================== |
1884 | | // Helper methods extracted to reduce evaluate_expr complexity |
1885 | | // Following Toyota Way: Each function has single responsibility |
1886 | | // Target: All functions < 50 cyclomatic complexity |
1887 | | // ======================================================================== |
1888 | | |
1889 | | /// Handle method calls on list values (complexity < 20) |
1890 | 0 | fn evaluate_list_methods( |
1891 | 0 | &mut self, |
1892 | 0 | items: Vec<Value>, |
1893 | 0 | method: &str, |
1894 | 0 | args: &[Expr], |
1895 | 0 | deadline: Instant, |
1896 | 0 | depth: usize, |
1897 | 0 | ) -> Result<Value> { |
1898 | 0 | match method { |
1899 | 0 | "map" => self.evaluate_list_map(items, args, deadline, depth), |
1900 | 0 | "filter" => self.evaluate_list_filter(items, args, deadline, depth), |
1901 | 0 | "reduce" => self.evaluate_list_reduce(items, args, deadline, depth), |
1902 | 0 | "len" | "length" => Self::evaluate_list_length(&items), |
1903 | 0 | "head" | "first" => Self::evaluate_list_head(&items), |
1904 | 0 | "last" => Self::evaluate_list_last(&items), |
1905 | 0 | "tail" | "rest" => Self::evaluate_list_tail(items), |
1906 | 0 | "reverse" => Self::evaluate_list_reverse(items), |
1907 | 0 | "sum" => Self::evaluate_list_sum(&items), |
1908 | 0 | "push" => self.evaluate_list_push(items, args, deadline, depth), |
1909 | 0 | "pop" => Self::evaluate_list_pop(items, args), |
1910 | 0 | "append" => self.evaluate_list_append(items, args, deadline, depth), |
1911 | 0 | "insert" => self.evaluate_list_insert(items, args, deadline, depth), |
1912 | 0 | "remove" => self.evaluate_list_remove(items, args, deadline, depth), |
1913 | 0 | "slice" => self.evaluate_list_slice(items, args, deadline, depth), |
1914 | 0 | "concat" => self.evaluate_list_concat(items, args, deadline, depth), |
1915 | 0 | "flatten" => Self::evaluate_list_flatten(items, args), |
1916 | 0 | "unique" => Self::evaluate_list_unique(items, args), |
1917 | 0 | "join" => self.evaluate_list_join(items, args, deadline, depth), |
1918 | 0 | _ => bail!("Unknown list method: {}", method), |
1919 | | } |
1920 | 0 | } |
1921 | | |
1922 | | /// Evaluate `list.map()` operation (complexity: 8) |
1923 | 0 | fn evaluate_list_map( |
1924 | 0 | &mut self, |
1925 | 0 | items: Vec<Value>, |
1926 | 0 | args: &[Expr], |
1927 | 0 | deadline: Instant, |
1928 | 0 | depth: usize, |
1929 | 0 | ) -> Result<Value> { |
1930 | 0 | if args.len() != 1 { |
1931 | 0 | bail!("map expects 1 argument"); |
1932 | 0 | } |
1933 | | |
1934 | 0 | if let ExprKind::Lambda { params, body } = &args[0].kind { |
1935 | 0 | if params.len() != 1 { |
1936 | 0 | bail!("map lambda must take exactly 1 parameter"); |
1937 | 0 | } |
1938 | | |
1939 | 0 | let saved_bindings = self.bindings.clone(); |
1940 | 0 | let mut results = Vec::new(); |
1941 | | |
1942 | 0 | for item in items { |
1943 | 0 | self.bindings.insert(params[0].name(), item); |
1944 | 0 | let result = self.evaluate_expr(body, deadline, depth + 1)?; |
1945 | 0 | results.push(result); |
1946 | | } |
1947 | | |
1948 | 0 | self.bindings = saved_bindings; |
1949 | 0 | Ok(Value::List(results)) |
1950 | | } else { |
1951 | 0 | bail!("map currently only supports lambda expressions"); |
1952 | | } |
1953 | 0 | } |
1954 | | |
1955 | | /// Evaluate `list.filter()` operation (complexity: 9) |
1956 | 0 | fn evaluate_list_filter( |
1957 | 0 | &mut self, |
1958 | 0 | items: Vec<Value>, |
1959 | 0 | args: &[Expr], |
1960 | 0 | deadline: Instant, |
1961 | 0 | depth: usize, |
1962 | 0 | ) -> Result<Value> { |
1963 | 0 | if args.len() != 1 { |
1964 | 0 | bail!("filter expects 1 argument"); |
1965 | 0 | } |
1966 | | |
1967 | 0 | if let ExprKind::Lambda { params, body } = &args[0].kind { |
1968 | 0 | if params.len() != 1 { |
1969 | 0 | bail!("filter lambda must take exactly 1 parameter"); |
1970 | 0 | } |
1971 | | |
1972 | 0 | let saved_bindings = self.bindings.clone(); |
1973 | 0 | let mut results = Vec::new(); |
1974 | | |
1975 | 0 | for item in items { |
1976 | 0 | self.bindings.insert(params[0].name(), item.clone()); |
1977 | 0 | let predicate_result = self.evaluate_expr(body, deadline, depth + 1)?; |
1978 | | |
1979 | 0 | if let Value::Bool(true) = predicate_result { |
1980 | 0 | results.push(item); |
1981 | 0 | } |
1982 | | } |
1983 | | |
1984 | 0 | self.bindings = saved_bindings; |
1985 | 0 | Ok(Value::List(results)) |
1986 | | } else { |
1987 | 0 | bail!("filter currently only supports lambda expressions"); |
1988 | | } |
1989 | 0 | } |
1990 | | |
1991 | | /// Evaluate `list.reduce()` operation (complexity: 10) |
1992 | 0 | fn evaluate_list_reduce( |
1993 | 0 | &mut self, |
1994 | 0 | items: Vec<Value>, |
1995 | 0 | args: &[Expr], |
1996 | 0 | deadline: Instant, |
1997 | 0 | depth: usize, |
1998 | 0 | ) -> Result<Value> { |
1999 | 0 | if args.len() != 2 { |
2000 | 0 | bail!("reduce expects 2 arguments: lambda and initial value"); |
2001 | 0 | } |
2002 | | |
2003 | | // Args are now: [lambda, initial_value] to match JS/Ruby style |
2004 | 0 | let mut accumulator = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
2005 | | |
2006 | | // Debug: Check what type of expression args[0] is |
2007 | 0 | match &args[0].kind { |
2008 | 0 | ExprKind::Lambda { params, body } => { |
2009 | 0 | if params.len() != 2 { |
2010 | 0 | bail!("reduce lambda must take exactly 2 parameters"); |
2011 | 0 | } |
2012 | | |
2013 | 0 | let saved_bindings = self.bindings.clone(); |
2014 | | |
2015 | 0 | for item in items { |
2016 | 0 | self.bindings.insert(params[0].name(), accumulator); |
2017 | 0 | self.bindings.insert(params[1].name(), item); |
2018 | 0 | accumulator = self.evaluate_expr(body, deadline, depth + 1)?; |
2019 | | } |
2020 | | |
2021 | 0 | self.bindings = saved_bindings; |
2022 | 0 | Ok(accumulator) |
2023 | | } |
2024 | 0 | other => { |
2025 | | // Debug: Check the actual expression kind |
2026 | 0 | match other { |
2027 | 0 | ExprKind::Call { .. } => bail!("reduce first argument is a function call, not a lambda"), |
2028 | 0 | ExprKind::Identifier(..) => bail!("reduce first argument is an identifier, not a lambda"), |
2029 | 0 | ExprKind::Literal(..) => bail!("reduce first argument is a literal, not a lambda"), |
2030 | 0 | ExprKind::Binary { .. } => bail!("reduce first argument is a binary expression, not a lambda"), |
2031 | 0 | ExprKind::Unary { .. } => bail!("reduce first argument is a unary expression, not a lambda"), |
2032 | 0 | _ => bail!("reduce first argument is not a lambda expression (some other type)"), |
2033 | | } |
2034 | | } |
2035 | | } |
2036 | 0 | } |
2037 | | |
2038 | | /// Evaluate `list.len()` and `list.length()` operations (complexity: 3) |
2039 | 0 | fn evaluate_list_length(items: &[Value]) -> Result<Value> { |
2040 | 0 | let len = items.len(); |
2041 | 0 | i64::try_from(len) |
2042 | 0 | .map(Value::Int) |
2043 | 0 | .map_err(|_| anyhow::anyhow!("List length too large to represent as i64")) |
2044 | 0 | } |
2045 | | |
2046 | | /// Evaluate `list.head()` and `list.first()` operations (complexity: 2) |
2047 | 0 | fn evaluate_list_head(items: &[Value]) -> Result<Value> { |
2048 | 0 | items |
2049 | 0 | .first() |
2050 | 0 | .cloned() |
2051 | 0 | .ok_or_else(|| anyhow::anyhow!("Empty list")) |
2052 | 0 | } |
2053 | | |
2054 | | /// Evaluate `list.last()` operation (complexity: 2) |
2055 | 0 | fn evaluate_list_last(items: &[Value]) -> Result<Value> { |
2056 | 0 | items |
2057 | 0 | .last() |
2058 | 0 | .cloned() |
2059 | 0 | .ok_or_else(|| anyhow::anyhow!("Empty list")) |
2060 | 0 | } |
2061 | | |
2062 | | /// Evaluate `list.tail()` and `list.rest()` operations (complexity: 2) |
2063 | 0 | fn evaluate_list_tail(items: Vec<Value>) -> Result<Value> { |
2064 | 0 | if items.is_empty() { |
2065 | 0 | Ok(Value::List(Vec::new())) |
2066 | | } else { |
2067 | 0 | Ok(Value::List(items[1..].to_vec())) |
2068 | | } |
2069 | 0 | } |
2070 | | |
2071 | | /// Evaluate `list.reverse()` operation (complexity: 2) |
2072 | 0 | fn evaluate_list_reverse(mut items: Vec<Value>) -> Result<Value> { |
2073 | 0 | items.reverse(); |
2074 | 0 | Ok(Value::List(items)) |
2075 | 0 | } |
2076 | | |
2077 | | /// Evaluate `list.sum()` operation (complexity: 4) |
2078 | 0 | fn evaluate_list_sum(items: &[Value]) -> Result<Value> { |
2079 | 0 | let mut sum = 0i64; |
2080 | 0 | for item in items { |
2081 | 0 | if let Value::Int(n) = item { |
2082 | 0 | sum += n; |
2083 | 0 | } else { |
2084 | 0 | bail!("sum requires all integers"); |
2085 | | } |
2086 | | } |
2087 | 0 | Ok(Value::Int(sum)) |
2088 | 0 | } |
2089 | | |
2090 | | /// Evaluate `list.push()` operation (complexity: 4) |
2091 | 0 | fn evaluate_list_push( |
2092 | 0 | &mut self, |
2093 | 0 | mut items: Vec<Value>, |
2094 | 0 | args: &[Expr], |
2095 | 0 | deadline: Instant, |
2096 | 0 | depth: usize, |
2097 | 0 | ) -> Result<Value> { |
2098 | 0 | if args.len() != 1 { |
2099 | 0 | bail!("push requires exactly 1 argument"); |
2100 | 0 | } |
2101 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2102 | 0 | items.push(value); |
2103 | 0 | Ok(Value::List(items)) |
2104 | 0 | } |
2105 | | |
2106 | | /// Evaluate `list.pop()` operation (complexity: 3) |
2107 | 0 | fn evaluate_list_pop(mut items: Vec<Value>, args: &[Expr]) -> Result<Value> { |
2108 | 0 | if !args.is_empty() { |
2109 | 0 | bail!("pop requires no arguments"); |
2110 | 0 | } |
2111 | 0 | if let Some(popped) = items.pop() { |
2112 | 0 | Ok(popped) |
2113 | | } else { |
2114 | 0 | bail!("Cannot pop from empty list") |
2115 | | } |
2116 | 0 | } |
2117 | | |
2118 | | /// Evaluate `list.append()` operation (complexity: 5) |
2119 | 0 | fn evaluate_list_append( |
2120 | 0 | &mut self, |
2121 | 0 | mut items: Vec<Value>, |
2122 | 0 | args: &[Expr], |
2123 | 0 | deadline: Instant, |
2124 | 0 | depth: usize, |
2125 | 0 | ) -> Result<Value> { |
2126 | 0 | if args.len() != 1 { |
2127 | 0 | bail!("append requires exactly 1 argument"); |
2128 | 0 | } |
2129 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2130 | 0 | if let Value::List(other_items) = value { |
2131 | 0 | items.extend(other_items); |
2132 | 0 | Ok(Value::List(items)) |
2133 | | } else { |
2134 | 0 | bail!("append requires a list argument"); |
2135 | | } |
2136 | 0 | } |
2137 | | |
2138 | | /// Evaluate `list.insert()` operation (complexity: 6) |
2139 | 0 | fn evaluate_list_insert( |
2140 | 0 | &mut self, |
2141 | 0 | mut items: Vec<Value>, |
2142 | 0 | args: &[Expr], |
2143 | 0 | deadline: Instant, |
2144 | 0 | depth: usize, |
2145 | 0 | ) -> Result<Value> { |
2146 | 0 | if args.len() != 2 { |
2147 | 0 | bail!("insert requires exactly 2 arguments (index, value)"); |
2148 | 0 | } |
2149 | 0 | let index = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2150 | 0 | let value = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
2151 | 0 | if let Value::Int(idx) = index { |
2152 | 0 | if idx < 0 || idx as usize > items.len() { |
2153 | 0 | bail!("Insert index out of bounds"); |
2154 | 0 | } |
2155 | 0 | items.insert(idx as usize, value); |
2156 | 0 | Ok(Value::List(items)) |
2157 | | } else { |
2158 | 0 | bail!("Insert index must be an integer"); |
2159 | | } |
2160 | 0 | } |
2161 | | |
2162 | | /// Evaluate `list.remove()` operation (complexity: 6) |
2163 | 0 | fn evaluate_list_remove( |
2164 | 0 | &mut self, |
2165 | 0 | mut items: Vec<Value>, |
2166 | 0 | args: &[Expr], |
2167 | 0 | deadline: Instant, |
2168 | 0 | depth: usize, |
2169 | 0 | ) -> Result<Value> { |
2170 | 0 | if args.len() != 1 { |
2171 | 0 | bail!("remove requires exactly 1 argument (index)"); |
2172 | 0 | } |
2173 | 0 | let index = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2174 | 0 | if let Value::Int(idx) = index { |
2175 | 0 | if idx < 0 || idx as usize >= items.len() { |
2176 | 0 | bail!("Remove index out of bounds"); |
2177 | 0 | } |
2178 | 0 | let removed = items.remove(idx as usize); |
2179 | 0 | Ok(removed) |
2180 | | } else { |
2181 | 0 | bail!("Remove index must be an integer"); |
2182 | | } |
2183 | 0 | } |
2184 | | |
2185 | | /// Evaluate `list.slice()` operation (complexity: 7) |
2186 | 0 | fn evaluate_list_slice( |
2187 | 0 | &mut self, |
2188 | 0 | items: Vec<Value>, |
2189 | 0 | args: &[Expr], |
2190 | 0 | deadline: Instant, |
2191 | 0 | depth: usize, |
2192 | 0 | ) -> Result<Value> { |
2193 | 0 | if args.len() != 2 { |
2194 | 0 | bail!("slice requires exactly 2 arguments (start, end)"); |
2195 | 0 | } |
2196 | 0 | let start_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2197 | 0 | let end_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
2198 | | |
2199 | 0 | if let (Value::Int(start), Value::Int(end)) = (start_val, end_val) { |
2200 | 0 | let start = start as usize; |
2201 | 0 | let end = end as usize; |
2202 | | |
2203 | 0 | if start > items.len() || end > items.len() || start > end { |
2204 | 0 | Ok(Value::List(Vec::new())) // Return empty for out of bounds |
2205 | | } else { |
2206 | 0 | Ok(Value::List(items[start..end].to_vec())) |
2207 | | } |
2208 | | } else { |
2209 | 0 | bail!("slice arguments must be integers"); |
2210 | | } |
2211 | 0 | } |
2212 | | |
2213 | | /// Evaluate `list.concat()` operation (complexity: 5) |
2214 | 0 | fn evaluate_list_concat( |
2215 | 0 | &mut self, |
2216 | 0 | mut items: Vec<Value>, |
2217 | 0 | args: &[Expr], |
2218 | 0 | deadline: Instant, |
2219 | 0 | depth: usize, |
2220 | 0 | ) -> Result<Value> { |
2221 | 0 | if args.len() != 1 { |
2222 | 0 | bail!("concat requires exactly 1 argument"); |
2223 | 0 | } |
2224 | 0 | let other_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2225 | | |
2226 | 0 | if let Value::List(other_items) = other_val { |
2227 | 0 | items.extend(other_items); |
2228 | 0 | Ok(Value::List(items)) |
2229 | | } else { |
2230 | 0 | bail!("concat argument must be a list"); |
2231 | | } |
2232 | 0 | } |
2233 | | |
2234 | | /// Evaluate `list.flatten()` operation (complexity: 4) |
2235 | 0 | fn evaluate_list_flatten(items: Vec<Value>, args: &[Expr]) -> Result<Value> { |
2236 | 0 | if !args.is_empty() { |
2237 | 0 | bail!("flatten requires no arguments"); |
2238 | 0 | } |
2239 | 0 | let mut result = Vec::new(); |
2240 | 0 | for item in items { |
2241 | 0 | if let Value::List(inner_items) = item { |
2242 | 0 | result.extend(inner_items); |
2243 | 0 | } else { |
2244 | 0 | result.push(item); |
2245 | 0 | } |
2246 | | } |
2247 | 0 | Ok(Value::List(result)) |
2248 | 0 | } |
2249 | | |
2250 | | /// Evaluate `list.unique()` operation (complexity: 5) |
2251 | 0 | fn evaluate_list_unique(items: Vec<Value>, args: &[Expr]) -> Result<Value> { |
2252 | | use std::collections::HashSet; |
2253 | 0 | if !args.is_empty() { |
2254 | 0 | bail!("unique requires no arguments"); |
2255 | 0 | } |
2256 | 0 | let mut seen = HashSet::new(); |
2257 | 0 | let mut result = Vec::new(); |
2258 | | |
2259 | 0 | for item in items { |
2260 | | // Use string representation for hashing since Value doesn't implement Hash |
2261 | 0 | let key = format!("{item:?}"); |
2262 | 0 | if seen.insert(key) { |
2263 | 0 | result.push(item); |
2264 | 0 | } |
2265 | | } |
2266 | 0 | Ok(Value::List(result)) |
2267 | 0 | } |
2268 | | |
2269 | | /// Evaluate `list.join()` operation (complexity: 7) |
2270 | 0 | fn evaluate_list_join( |
2271 | 0 | &mut self, |
2272 | 0 | items: Vec<Value>, |
2273 | 0 | args: &[Expr], |
2274 | 0 | deadline: Instant, |
2275 | 0 | depth: usize, |
2276 | 0 | ) -> Result<Value> { |
2277 | 0 | if args.len() != 1 { |
2278 | 0 | bail!("join requires exactly 1 argument (separator)"); |
2279 | 0 | } |
2280 | 0 | let sep_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2281 | | |
2282 | 0 | if let Value::String(separator) = sep_val { |
2283 | 0 | let strings: Result<Vec<String>, _> = items.iter().map(|item| { |
2284 | 0 | if let Value::String(s) = item { |
2285 | 0 | Ok(s.clone()) |
2286 | | } else { |
2287 | 0 | bail!("join requires a list of strings"); |
2288 | | } |
2289 | 0 | }).collect(); |
2290 | | |
2291 | 0 | match strings { |
2292 | 0 | Ok(string_vec) => Ok(Value::String(string_vec.join(&separator))), |
2293 | 0 | Err(e) => Err(e), |
2294 | | } |
2295 | | } else { |
2296 | 0 | bail!("join separator must be a string"); |
2297 | | } |
2298 | 0 | } |
2299 | | |
2300 | | /// Handle method calls on string values (complexity < 10) |
2301 | | /// Handle simple string transformation methods (complexity: 5) |
2302 | 0 | fn handle_string_transforms(s: &str, method: &str) -> Option<Result<Value>> { |
2303 | 0 | match method { |
2304 | 0 | "len" | "length" => { |
2305 | 0 | let len = s.len(); |
2306 | 0 | Some(i64::try_from(len) |
2307 | 0 | .map(Value::Int) |
2308 | 0 | .map_err(|_| anyhow::anyhow!("String length too large to represent as i64"))) |
2309 | | } |
2310 | 0 | "upper" | "to_upper" | "to_uppercase" => Some(Ok(Value::String(s.to_uppercase()))), |
2311 | 0 | "lower" | "to_lower" | "to_lowercase" => Some(Ok(Value::String(s.to_lowercase()))), |
2312 | 0 | "trim" => Some(Ok(Value::String(s.trim().to_string()))), |
2313 | 0 | "chars" => { |
2314 | 0 | let chars: Vec<Value> = s.chars() |
2315 | 0 | .map(|c| Value::String(c.to_string())) |
2316 | 0 | .collect(); |
2317 | 0 | Some(Ok(Value::List(chars))) |
2318 | | } |
2319 | 0 | "reverse" => { |
2320 | 0 | let reversed: String = s.chars().rev().collect(); |
2321 | 0 | Some(Ok(Value::String(reversed))) |
2322 | | } |
2323 | 0 | _ => None, |
2324 | | } |
2325 | 0 | } |
2326 | | |
2327 | | /// Handle string search methods (complexity: 6) |
2328 | 0 | fn handle_string_search(s: &str, method: &str, args: &[Expr]) -> Option<Result<Value>> { |
2329 | 0 | match method { |
2330 | 0 | "contains" => { |
2331 | 0 | if args.len() != 1 { |
2332 | 0 | return Some(Err(anyhow::anyhow!("contains expects 1 argument"))); |
2333 | 0 | } |
2334 | 0 | if let ExprKind::Literal(Literal::String(needle)) = &args[0].kind { |
2335 | 0 | Some(Ok(Value::Bool(s.contains(needle)))) |
2336 | | } else { |
2337 | 0 | Some(Err(anyhow::anyhow!("contains argument must be a string literal"))) |
2338 | | } |
2339 | | } |
2340 | 0 | "starts_with" => { |
2341 | 0 | if args.len() != 1 { |
2342 | 0 | return Some(Err(anyhow::anyhow!("starts_with expects 1 argument"))); |
2343 | 0 | } |
2344 | 0 | if let ExprKind::Literal(Literal::String(prefix)) = &args[0].kind { |
2345 | 0 | Some(Ok(Value::Bool(s.starts_with(prefix)))) |
2346 | | } else { |
2347 | 0 | Some(Err(anyhow::anyhow!("starts_with argument must be a string literal"))) |
2348 | | } |
2349 | | } |
2350 | 0 | "ends_with" => { |
2351 | 0 | if args.len() != 1 { |
2352 | 0 | return Some(Err(anyhow::anyhow!("ends_with expects 1 argument"))); |
2353 | 0 | } |
2354 | 0 | if let ExprKind::Literal(Literal::String(suffix)) = &args[0].kind { |
2355 | 0 | Some(Ok(Value::Bool(s.ends_with(suffix)))) |
2356 | | } else { |
2357 | 0 | Some(Err(anyhow::anyhow!("ends_with argument must be a string literal"))) |
2358 | | } |
2359 | | } |
2360 | 0 | _ => None, |
2361 | | } |
2362 | 0 | } |
2363 | | |
2364 | | /// Handle string manipulation methods (complexity: 8) |
2365 | 0 | fn handle_string_manipulation(s: &str, method: &str, args: &[Expr]) -> Option<Result<Value>> { |
2366 | 0 | match method { |
2367 | 0 | "split" => { |
2368 | 0 | if args.len() != 1 { |
2369 | 0 | return Some(Err(anyhow::anyhow!("split expects 1 argument"))); |
2370 | 0 | } |
2371 | 0 | if let ExprKind::Literal(Literal::String(sep)) = &args[0].kind { |
2372 | 0 | let parts: Vec<Value> = s.split(sep) |
2373 | 0 | .map(|p| Value::String(p.to_string())) |
2374 | 0 | .collect(); |
2375 | 0 | Some(Ok(Value::List(parts))) |
2376 | | } else { |
2377 | 0 | Some(Err(anyhow::anyhow!("split separator must be a string literal"))) |
2378 | | } |
2379 | | } |
2380 | 0 | "replace" => { |
2381 | 0 | if args.len() != 2 { |
2382 | 0 | return Some(Err(anyhow::anyhow!("replace expects 2 arguments (from, to)"))); |
2383 | 0 | } |
2384 | 0 | if let (ExprKind::Literal(Literal::String(from)), ExprKind::Literal(Literal::String(to))) = |
2385 | 0 | (&args[0].kind, &args[1].kind) { |
2386 | 0 | Some(Ok(Value::String(s.replace(from, to)))) |
2387 | | } else { |
2388 | 0 | Some(Err(anyhow::anyhow!("replace arguments must be string literals"))) |
2389 | | } |
2390 | | } |
2391 | 0 | "repeat" => { |
2392 | 0 | if args.len() != 1 { |
2393 | 0 | return Some(Err(anyhow::anyhow!("repeat expects 1 argument"))); |
2394 | 0 | } |
2395 | 0 | if let ExprKind::Literal(Literal::Integer(count)) = &args[0].kind { |
2396 | 0 | if *count < 0 { |
2397 | 0 | Some(Err(anyhow::anyhow!("repeat count cannot be negative"))) |
2398 | | } else { |
2399 | 0 | Some(Ok(Value::String(s.repeat(*count as usize)))) |
2400 | | } |
2401 | | } else { |
2402 | 0 | Some(Err(anyhow::anyhow!("repeat argument must be an integer"))) |
2403 | | } |
2404 | | } |
2405 | 0 | _ => None, |
2406 | | } |
2407 | 0 | } |
2408 | | |
2409 | | /// Handle substring extraction (complexity: 7) |
2410 | 0 | fn handle_substring(s: &str, args: &[Expr]) -> Result<Value> { |
2411 | 0 | if args.len() == 2 { |
2412 | | // substring(start, end) |
2413 | 0 | if let (ExprKind::Literal(Literal::Integer(start)), ExprKind::Literal(Literal::Integer(end))) = |
2414 | 0 | (&args[0].kind, &args[1].kind) { |
2415 | 0 | let start_idx = (*start as usize).min(s.len()); |
2416 | 0 | let end_idx = (*end as usize).min(s.len()); |
2417 | 0 | if start_idx <= end_idx { |
2418 | 0 | Ok(Value::String(s[start_idx..end_idx].to_string())) |
2419 | | } else { |
2420 | 0 | Ok(Value::String(String::new())) |
2421 | | } |
2422 | | } else { |
2423 | 0 | bail!("substring arguments must be integers"); |
2424 | | } |
2425 | 0 | } else if args.len() == 1 { |
2426 | | // substring(start) - to end of string |
2427 | 0 | if let ExprKind::Literal(Literal::Integer(start)) = &args[0].kind { |
2428 | 0 | let start_idx = (*start as usize).min(s.len()); |
2429 | 0 | Ok(Value::String(s[start_idx..].to_string())) |
2430 | | } else { |
2431 | 0 | bail!("substring argument must be an integer"); |
2432 | | } |
2433 | | } else { |
2434 | 0 | bail!("substring expects 1 or 2 arguments"); |
2435 | | } |
2436 | 0 | } |
2437 | | |
2438 | | /// Main string methods dispatcher (complexity: 6) |
2439 | 0 | fn evaluate_string_methods( |
2440 | 0 | s: &str, |
2441 | 0 | method: &str, |
2442 | 0 | args: &[Expr], |
2443 | 0 | _deadline: Instant, |
2444 | 0 | _depth: usize, |
2445 | 0 | ) -> Result<Value> { |
2446 | | // Try simple transforms first |
2447 | 0 | if let Some(result) = Self::handle_string_transforms(s, method) { |
2448 | 0 | return result; |
2449 | 0 | } |
2450 | | |
2451 | | // Try search methods |
2452 | 0 | if let Some(result) = Self::handle_string_search(s, method, args) { |
2453 | 0 | return result; |
2454 | 0 | } |
2455 | | |
2456 | | // Try manipulation methods |
2457 | 0 | if let Some(result) = Self::handle_string_manipulation(s, method, args) { |
2458 | 0 | return result; |
2459 | 0 | } |
2460 | | |
2461 | | // Handle substring specially |
2462 | 0 | if method == "substring" || method == "substr" { |
2463 | 0 | return Self::handle_substring(s, args); |
2464 | 0 | } |
2465 | | |
2466 | 0 | bail!("Unknown string method: {}", method) |
2467 | 0 | } |
2468 | | |
2469 | | /// Handle method calls on integer values (complexity < 10) |
2470 | 0 | fn evaluate_int_methods(n: i64, method: &str) -> Result<Value> { |
2471 | 0 | match method { |
2472 | 0 | "abs" => Ok(Value::Int(n.abs())), |
2473 | | #[allow(clippy::cast_precision_loss)] |
2474 | 0 | "sqrt" => Ok(Value::Float((n as f64).sqrt())), |
2475 | | #[allow(clippy::cast_precision_loss)] |
2476 | 0 | "sin" => Ok(Value::Float((n as f64).sin())), |
2477 | | #[allow(clippy::cast_precision_loss)] |
2478 | 0 | "cos" => Ok(Value::Float((n as f64).cos())), |
2479 | | #[allow(clippy::cast_precision_loss)] |
2480 | 0 | "tan" => Ok(Value::Float((n as f64).tan())), |
2481 | | #[allow(clippy::cast_precision_loss)] |
2482 | 0 | "log" => Ok(Value::Float((n as f64).ln())), |
2483 | | #[allow(clippy::cast_precision_loss)] |
2484 | 0 | "log10" => Ok(Value::Float((n as f64).log10())), |
2485 | | #[allow(clippy::cast_precision_loss)] |
2486 | 0 | "exp" => Ok(Value::Float((n as f64).exp())), |
2487 | 0 | "to_string" => Ok(Value::String(n.to_string())), |
2488 | 0 | _ => bail!("Unknown integer method: {}", method), |
2489 | | } |
2490 | 0 | } |
2491 | | |
2492 | | /// Handle method calls on float values (complexity < 10) |
2493 | 0 | fn evaluate_float_methods(f: f64, method: &str) -> Result<Value> { |
2494 | 0 | match method { |
2495 | 0 | "abs" => Ok(Value::Float(f.abs())), |
2496 | 0 | "sqrt" => Ok(Value::Float(f.sqrt())), |
2497 | 0 | "sin" => Ok(Value::Float(f.sin())), |
2498 | 0 | "cos" => Ok(Value::Float(f.cos())), |
2499 | 0 | "tan" => Ok(Value::Float(f.tan())), |
2500 | 0 | "log" => Ok(Value::Float(f.ln())), |
2501 | 0 | "log10" => Ok(Value::Float(f.log10())), |
2502 | 0 | "exp" => Ok(Value::Float(f.exp())), |
2503 | 0 | "floor" => Ok(Value::Float(f.floor())), |
2504 | 0 | "ceil" => Ok(Value::Float(f.ceil())), |
2505 | 0 | "round" => Ok(Value::Float(f.round())), |
2506 | 0 | _ => bail!("Unknown float method: {}", method), |
2507 | | } |
2508 | 0 | } |
2509 | | |
2510 | | /// Handle method calls on object values (complexity < 10) |
2511 | 0 | fn evaluate_object_methods( |
2512 | 0 | obj: HashMap<String, Value>, |
2513 | 0 | method: &str, |
2514 | 0 | _args: &[Expr], |
2515 | 0 | _deadline: Instant, |
2516 | 0 | _depth: usize, |
2517 | 0 | ) -> Result<Value> { |
2518 | 0 | match method { |
2519 | 0 | "items" => { |
2520 | | // Return list of (key, value) tuples |
2521 | 0 | let mut items = Vec::new(); |
2522 | 0 | for (key, value) in obj { |
2523 | 0 | let tuple = Value::Tuple(vec![Value::String(key), value]); |
2524 | 0 | items.push(tuple); |
2525 | 0 | } |
2526 | 0 | Ok(Value::List(items)) |
2527 | | } |
2528 | 0 | "keys" => { |
2529 | | // Return list of keys |
2530 | 0 | let keys: Vec<Value> = obj.keys().map(|k| Value::String(k.clone())).collect(); |
2531 | 0 | Ok(Value::List(keys)) |
2532 | | } |
2533 | 0 | "values" => { |
2534 | | // Return list of values |
2535 | 0 | let values: Vec<Value> = obj.values().cloned().collect(); |
2536 | 0 | Ok(Value::List(values)) |
2537 | | } |
2538 | 0 | "len" => { |
2539 | | // Return length of object |
2540 | 0 | Ok(Value::Int(obj.len() as i64)) |
2541 | | } |
2542 | 0 | "has_key" => { |
2543 | | // This would need args handling - simplified for now |
2544 | 0 | bail!("has_key method requires arguments") |
2545 | | } |
2546 | 0 | _ => bail!("Unknown object method: {}", method), |
2547 | | } |
2548 | 0 | } |
2549 | | |
2550 | | /// Handle method calls on `HashMap` values (complexity < 10) |
2551 | 0 | fn evaluate_hashmap_methods( |
2552 | 0 | &mut self, |
2553 | 0 | mut map: HashMap<Value, Value>, |
2554 | 0 | method: &str, |
2555 | 0 | args: &[Expr], |
2556 | 0 | deadline: Instant, |
2557 | 0 | depth: usize, |
2558 | 0 | ) -> Result<Value> { |
2559 | 0 | match method { |
2560 | 0 | "insert" => { |
2561 | 0 | if args.len() != 2 { |
2562 | 0 | bail!("insert requires exactly 2 arguments (key, value)"); |
2563 | 0 | } |
2564 | 0 | let key = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2565 | 0 | let value = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
2566 | 0 | map.insert(key, value); |
2567 | 0 | Ok(Value::HashMap(map)) |
2568 | | } |
2569 | 0 | "get" => { |
2570 | 0 | if args.len() != 1 { |
2571 | 0 | bail!("get requires exactly 1 argument (key)"); |
2572 | 0 | } |
2573 | 0 | let key = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2574 | 0 | match map.get(&key) { |
2575 | 0 | Some(value) => Ok(value.clone()), |
2576 | 0 | None => Ok(Value::Unit), // Could return Option::None in future |
2577 | | } |
2578 | | } |
2579 | 0 | "contains_key" => { |
2580 | 0 | if args.len() != 1 { |
2581 | 0 | bail!("contains_key requires exactly 1 argument (key)"); |
2582 | 0 | } |
2583 | 0 | let key = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2584 | 0 | Ok(Value::Bool(map.contains_key(&key))) |
2585 | | } |
2586 | 0 | "remove" => { |
2587 | 0 | if args.len() != 1 { |
2588 | 0 | bail!("remove requires exactly 1 argument (key)"); |
2589 | 0 | } |
2590 | 0 | let key = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
2591 | 0 | let removed_value = map.remove(&key); |
2592 | 0 | match removed_value { |
2593 | 0 | Some(value) => Ok(Value::Tuple(vec![Value::HashMap(map), value])), |
2594 | 0 | None => Ok(Value::Tuple(vec![Value::HashMap(map), Value::Unit])), |
2595 | | } |
2596 | | } |
2597 | 0 | "len" => Ok(Value::Int(map.len() as i64)), |
2598 | 0 | "is_empty" => Ok(Value::Bool(map.is_empty())), |
2599 | 0 | "clear" => { |
2600 | 0 | map.clear(); |
2601 | 0 | Ok(Value::HashMap(map)) |
2602 | | } |
2603 | 0 | _ => bail!("Unknown HashMap method: {}", method), |
2604 | | } |
2605 | 0 | } |
2606 | | |
2607 | | /// Handle basic `HashSet` methods (complexity: 6) |
2608 | 0 | fn handle_basic_hashset_methods( |
2609 | 0 | &mut self, |
2610 | 0 | mut set: HashSet<Value>, |
2611 | 0 | method: &str, |
2612 | 0 | args: &[Expr], |
2613 | 0 | deadline: Instant, |
2614 | 0 | depth: usize, |
2615 | 0 | ) -> Option<Result<Value>> { |
2616 | 0 | match method { |
2617 | 0 | "insert" => { |
2618 | 0 | if args.len() != 1 { |
2619 | 0 | return Some(Err(anyhow::anyhow!("insert requires exactly 1 argument (value)"))); |
2620 | 0 | } |
2621 | 0 | let value = match self.evaluate_expr(&args[0], deadline, depth + 1) { |
2622 | 0 | Ok(v) => v, |
2623 | 0 | Err(e) => return Some(Err(e)), |
2624 | | }; |
2625 | 0 | let was_new = set.insert(value); |
2626 | 0 | Some(Ok(Value::Tuple(vec![Value::HashSet(set), Value::Bool(was_new)]))) |
2627 | | } |
2628 | 0 | "contains" => { |
2629 | 0 | if args.len() != 1 { |
2630 | 0 | return Some(Err(anyhow::anyhow!("contains requires exactly 1 argument (value)"))); |
2631 | 0 | } |
2632 | 0 | let value = match self.evaluate_expr(&args[0], deadline, depth + 1) { |
2633 | 0 | Ok(v) => v, |
2634 | 0 | Err(e) => return Some(Err(e)), |
2635 | | }; |
2636 | 0 | Some(Ok(Value::Bool(set.contains(&value)))) |
2637 | | } |
2638 | 0 | "remove" => { |
2639 | 0 | if args.len() != 1 { |
2640 | 0 | return Some(Err(anyhow::anyhow!("remove requires exactly 1 argument (value)"))); |
2641 | 0 | } |
2642 | 0 | let value = match self.evaluate_expr(&args[0], deadline, depth + 1) { |
2643 | 0 | Ok(v) => v, |
2644 | 0 | Err(e) => return Some(Err(e)), |
2645 | | }; |
2646 | 0 | let was_present = set.remove(&value); |
2647 | 0 | Some(Ok(Value::Tuple(vec![Value::HashSet(set), Value::Bool(was_present)]))) |
2648 | | } |
2649 | 0 | "len" => Some(Ok(Value::Int(set.len() as i64))), |
2650 | 0 | "is_empty" => Some(Ok(Value::Bool(set.is_empty()))), |
2651 | 0 | "clear" => { |
2652 | 0 | set.clear(); |
2653 | 0 | Some(Ok(Value::HashSet(set))) |
2654 | | } |
2655 | 0 | _ => None, |
2656 | | } |
2657 | 0 | } |
2658 | | |
2659 | | /// Handle set operation methods (complexity: 8) |
2660 | 0 | fn handle_set_operation_methods( |
2661 | 0 | &mut self, |
2662 | 0 | set: HashSet<Value>, |
2663 | 0 | method: &str, |
2664 | 0 | args: &[Expr], |
2665 | 0 | deadline: Instant, |
2666 | 0 | depth: usize, |
2667 | 0 | ) -> Option<Result<Value>> { |
2668 | 0 | if args.len() != 1 { |
2669 | 0 | return Some(Err(anyhow::anyhow!("{} requires exactly 1 argument (other set)", method))); |
2670 | 0 | } |
2671 | | |
2672 | 0 | let other_val = match self.evaluate_expr(&args[0], deadline, depth + 1) { |
2673 | 0 | Ok(v) => v, |
2674 | 0 | Err(e) => return Some(Err(e)), |
2675 | | }; |
2676 | | |
2677 | 0 | if let Value::HashSet(other_set) = other_val { |
2678 | 0 | match method { |
2679 | 0 | "union" => { |
2680 | 0 | let union_set = set.union(&other_set).cloned().collect(); |
2681 | 0 | Some(Ok(Value::HashSet(union_set))) |
2682 | | } |
2683 | 0 | "intersection" => { |
2684 | 0 | let intersection_set = set.intersection(&other_set).cloned().collect(); |
2685 | 0 | Some(Ok(Value::HashSet(intersection_set))) |
2686 | | } |
2687 | 0 | "difference" => { |
2688 | 0 | let difference_set = set.difference(&other_set).cloned().collect(); |
2689 | 0 | Some(Ok(Value::HashSet(difference_set))) |
2690 | | } |
2691 | 0 | _ => None, |
2692 | | } |
2693 | | } else { |
2694 | 0 | Some(Err(anyhow::anyhow!("{} argument must be a HashSet", method))) |
2695 | | } |
2696 | 0 | } |
2697 | | |
2698 | | /// Handle method calls on `HashSet` values (complexity: 5) |
2699 | 0 | fn evaluate_hashset_methods( |
2700 | 0 | &mut self, |
2701 | 0 | set: HashSet<Value>, |
2702 | 0 | method: &str, |
2703 | 0 | args: &[Expr], |
2704 | 0 | deadline: Instant, |
2705 | 0 | depth: usize, |
2706 | 0 | ) -> Result<Value> { |
2707 | | // Try basic methods first |
2708 | 0 | if let Some(result) = self.handle_basic_hashset_methods(set.clone(), method, args, deadline, depth) { |
2709 | 0 | return result; |
2710 | 0 | } |
2711 | | |
2712 | | // Try set operation methods |
2713 | 0 | if let Some(result) = self.handle_set_operation_methods(set, method, args, deadline, depth) { |
2714 | 0 | return result; |
2715 | 0 | } |
2716 | | |
2717 | | // Unknown method |
2718 | 0 | bail!("Unknown HashSet method: {}", method) |
2719 | 0 | } |
2720 | | |
2721 | | // ======================================================================== |
2722 | | // Additional helper methods to further reduce evaluate_expr complexity |
2723 | | // Phase 2: Control flow extraction (Target: < 50 total complexity) |
2724 | | // ======================================================================== |
2725 | | |
2726 | | /// Evaluate for loop (complexity: 10) |
2727 | 0 | fn evaluate_for_loop( |
2728 | 0 | &mut self, |
2729 | 0 | var: &str, |
2730 | 0 | pattern: Option<&Pattern>, |
2731 | 0 | iter: &Expr, |
2732 | 0 | body: &Expr, |
2733 | 0 | deadline: Instant, |
2734 | 0 | depth: usize, |
2735 | 0 | ) -> Result<Value> { |
2736 | | // Evaluate the iterable |
2737 | 0 | let iterable = self.evaluate_expr(iter, deadline, depth + 1)?; |
2738 | | |
2739 | | // Save the previous value of the loop variable (if any) |
2740 | 0 | let saved_loop_var = self.bindings.get(var).cloned(); |
2741 | | |
2742 | | // If we have a pattern, save all variables it will bind |
2743 | 0 | let saved_pattern_vars = if let Some(pat) = pattern { |
2744 | 0 | self.save_pattern_variables(pat) |
2745 | | } else { |
2746 | 0 | HashMap::new() |
2747 | | }; |
2748 | | |
2749 | | // Execute the loop based on iterable type |
2750 | 0 | let result = match iterable { |
2751 | 0 | Value::List(items) => { |
2752 | 0 | if let Some(pat) = pattern { |
2753 | 0 | self.iterate_list_with_pattern(pat, items, body, deadline, depth) |
2754 | | } else { |
2755 | 0 | self.iterate_list(var, items, body, deadline, depth) |
2756 | | } |
2757 | | }, |
2758 | | Value::Range { |
2759 | 0 | start, |
2760 | 0 | end, |
2761 | 0 | inclusive, |
2762 | 0 | } => self.iterate_range(var, start, end, inclusive, body, deadline, depth), |
2763 | 0 | Value::String(s) => self.iterate_string(var, &s, body, deadline, depth), |
2764 | 0 | _ => bail!( |
2765 | 0 | "For loops only support lists, ranges, and strings, got: {:?}", |
2766 | | iterable |
2767 | | ), |
2768 | | }; |
2769 | | |
2770 | | // Restore the loop variable |
2771 | 0 | if let Some(prev_value) = saved_loop_var { |
2772 | 0 | self.bindings.insert(var.to_string(), prev_value); |
2773 | 0 | } else { |
2774 | 0 | self.bindings.remove(var); |
2775 | 0 | } |
2776 | | |
2777 | | // Restore pattern variables |
2778 | 0 | for (name, value) in saved_pattern_vars { |
2779 | 0 | if let Some(val) = value { |
2780 | 0 | self.bindings.insert(name, val); |
2781 | 0 | } else { |
2782 | 0 | self.bindings.remove(&name); |
2783 | 0 | } |
2784 | | } |
2785 | | |
2786 | 0 | result |
2787 | 0 | } |
2788 | | |
2789 | | /// Helper: Iterate over a list (complexity: 4) |
2790 | 0 | fn iterate_list( |
2791 | 0 | &mut self, |
2792 | 0 | var: &str, |
2793 | 0 | items: Vec<Value>, |
2794 | 0 | body: &Expr, |
2795 | 0 | deadline: Instant, |
2796 | 0 | depth: usize, |
2797 | 0 | ) -> Result<Value> { |
2798 | 0 | let mut result = Value::Unit; |
2799 | 0 | for item in items { |
2800 | 0 | self.bindings.insert(var.to_string(), item); |
2801 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
2802 | 0 | Ok(value) => result = value, |
2803 | 0 | Err(e) if e.to_string() == "break" => break, |
2804 | 0 | Err(e) if e.to_string() == "continue" => {}, |
2805 | 0 | Err(e) => return Err(e), |
2806 | | } |
2807 | | } |
2808 | 0 | Ok(result) |
2809 | 0 | } |
2810 | | |
2811 | | /// Helper: Iterate over a range (complexity: 5) |
2812 | | #[allow(clippy::too_many_arguments)] |
2813 | 0 | fn iterate_range( |
2814 | 0 | &mut self, |
2815 | 0 | var: &str, |
2816 | 0 | start: i64, |
2817 | 0 | end: i64, |
2818 | 0 | inclusive: bool, |
2819 | 0 | body: &Expr, |
2820 | 0 | deadline: Instant, |
2821 | 0 | depth: usize, |
2822 | 0 | ) -> Result<Value> { |
2823 | 0 | let mut result = Value::Unit; |
2824 | 0 | let actual_end = if inclusive { end + 1 } else { end }; |
2825 | 0 | for i in start..actual_end { |
2826 | 0 | self.bindings.insert(var.to_string(), Value::Int(i)); |
2827 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
2828 | 0 | Ok(value) => result = value, |
2829 | 0 | Err(e) if e.to_string() == "break" => break, |
2830 | 0 | Err(e) if e.to_string() == "continue" => {}, |
2831 | 0 | Err(e) => return Err(e), |
2832 | | } |
2833 | | } |
2834 | 0 | Ok(result) |
2835 | 0 | } |
2836 | | |
2837 | | /// Helper: Iterate over a string (as characters) |
2838 | 0 | fn iterate_string( |
2839 | 0 | &mut self, |
2840 | 0 | var: &str, |
2841 | 0 | s: &str, |
2842 | 0 | body: &Expr, |
2843 | 0 | deadline: Instant, |
2844 | 0 | depth: usize, |
2845 | 0 | ) -> Result<Value> { |
2846 | 0 | let mut result = Value::Unit; |
2847 | 0 | for ch in s.chars() { |
2848 | 0 | self.bindings.insert(var.to_string(), Value::String(ch.to_string())); |
2849 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
2850 | 0 | Ok(value) => result = value, |
2851 | 0 | Err(e) if e.to_string() == "break" => break, |
2852 | 0 | Err(e) if e.to_string() == "continue" => {}, |
2853 | 0 | Err(e) => return Err(e), |
2854 | | } |
2855 | | } |
2856 | 0 | Ok(result) |
2857 | 0 | } |
2858 | | |
2859 | | /// Helper: Save pattern variables for restoration |
2860 | 0 | fn save_pattern_variables(&self, pattern: &Pattern) -> HashMap<String, Option<Value>> { |
2861 | 0 | let mut saved = HashMap::new(); |
2862 | 0 | self.collect_pattern_vars(pattern, &mut saved); |
2863 | 0 | saved |
2864 | 0 | } |
2865 | | |
2866 | | /// Helper: Collect all variables from a pattern |
2867 | 0 | fn collect_pattern_vars(&self, pattern: &Pattern, saved: &mut HashMap<String, Option<Value>>) { |
2868 | 0 | match pattern { |
2869 | 0 | Pattern::Identifier(name) => { |
2870 | 0 | saved.insert(name.clone(), self.bindings.get(name).cloned()); |
2871 | 0 | } |
2872 | 0 | Pattern::Tuple(patterns) => { |
2873 | 0 | for p in patterns { |
2874 | 0 | self.collect_pattern_vars(p, saved); |
2875 | 0 | } |
2876 | | } |
2877 | 0 | _ => {} // Other patterns don't bind variables |
2878 | | } |
2879 | 0 | } |
2880 | | |
2881 | | /// Helper: Iterate over a list with pattern destructuring |
2882 | 0 | fn iterate_list_with_pattern( |
2883 | 0 | &mut self, |
2884 | 0 | pattern: &Pattern, |
2885 | 0 | items: Vec<Value>, |
2886 | 0 | body: &Expr, |
2887 | 0 | deadline: Instant, |
2888 | 0 | depth: usize, |
2889 | 0 | ) -> Result<Value> { |
2890 | 0 | let mut result = Value::Unit; |
2891 | 0 | for item in items { |
2892 | | // Bind the pattern variables |
2893 | 0 | self.bind_pattern(pattern, &item)?; |
2894 | | |
2895 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
2896 | 0 | Ok(value) => result = value, |
2897 | 0 | Err(e) if e.to_string() == "break" => break, |
2898 | 0 | Err(e) if e.to_string() == "continue" => {}, |
2899 | 0 | Err(e) => return Err(e), |
2900 | | } |
2901 | | } |
2902 | 0 | Ok(result) |
2903 | 0 | } |
2904 | | |
2905 | | /// Helper: Bind pattern variables from a value |
2906 | 0 | fn bind_pattern(&mut self, pattern: &Pattern, value: &Value) -> Result<()> { |
2907 | 0 | match (pattern, value) { |
2908 | 0 | (Pattern::Identifier(name), val) => { |
2909 | 0 | self.bindings.insert(name.clone(), val.clone()); |
2910 | 0 | Ok(()) |
2911 | | } |
2912 | 0 | (Pattern::Tuple(patterns), Value::Tuple(values)) => { |
2913 | 0 | if patterns.len() != values.len() { |
2914 | 0 | bail!("Pattern tuple has {} elements but value has {}", patterns.len(), values.len()); |
2915 | 0 | } |
2916 | 0 | for (p, v) in patterns.iter().zip(values.iter()) { |
2917 | 0 | self.bind_pattern(p, v)?; |
2918 | | } |
2919 | 0 | Ok(()) |
2920 | | } |
2921 | 0 | _ => bail!("Pattern does not match value") |
2922 | | } |
2923 | 0 | } |
2924 | | |
2925 | | /// Evaluate while loop (complexity: 7) |
2926 | | /// |
2927 | | /// While loops always return Unit, regardless of body expression. |
2928 | | /// |
2929 | | /// # Example |
2930 | | /// ``` |
2931 | | /// use ruchy::runtime::Repl; |
2932 | | /// let mut repl = Repl::new().unwrap(); |
2933 | | /// |
2934 | | /// // While loops return Unit, not the last body value |
2935 | | /// let result = repl.eval("let i = 0; while i < 3 { i = i + 1 }; i").unwrap(); |
2936 | | /// assert_eq!(result.to_string(), "3"); // i is 3 after loop |
2937 | | /// |
2938 | | /// // While loop doesn't return body value |
2939 | | /// let result = repl.eval("let i = 0; while i < 1 { i = i + 1; 42 }").unwrap(); |
2940 | | /// assert_eq!(result.to_string(), "()"); // Returns Unit, not 42 |
2941 | | /// ``` |
2942 | 0 | fn evaluate_while_loop( |
2943 | 0 | &mut self, |
2944 | 0 | condition: &Expr, |
2945 | 0 | body: &Expr, |
2946 | 0 | deadline: Instant, |
2947 | 0 | depth: usize, |
2948 | 0 | ) -> Result<Value> { |
2949 | 0 | let max_iterations = 1000; // Prevent infinite loops in REPL |
2950 | 0 | let mut iterations = 0; |
2951 | | |
2952 | | loop { |
2953 | 0 | if iterations >= max_iterations { |
2954 | 0 | bail!( |
2955 | 0 | "While loop exceeded maximum iterations ({})", |
2956 | | max_iterations |
2957 | | ); |
2958 | 0 | } |
2959 | | |
2960 | | // Evaluate condition |
2961 | 0 | let cond_val = self.evaluate_expr(condition, deadline, depth + 1)?; |
2962 | 0 | match cond_val { |
2963 | | Value::Bool(true) => { |
2964 | | // Execute body but don't save result - while loops return Unit |
2965 | 0 | self.evaluate_expr(body, deadline, depth + 1)?; |
2966 | 0 | iterations += 1; |
2967 | | } |
2968 | 0 | Value::Bool(false) => break, |
2969 | 0 | _ => bail!("While condition must be boolean, got: {:?}", cond_val), |
2970 | | } |
2971 | | } |
2972 | | // While loops always return Unit |
2973 | 0 | Ok(Value::Unit) |
2974 | 0 | } |
2975 | | |
2976 | | /// Evaluate loop expression (complexity: 6) |
2977 | 0 | fn evaluate_loop(&mut self, body: &Expr, deadline: Instant, depth: usize) -> Result<Value> { |
2978 | 0 | let mut result = Value::Unit; |
2979 | 0 | let max_iterations = 1000; // Prevent infinite loops in REPL |
2980 | 0 | let mut iterations = 0; |
2981 | | |
2982 | | loop { |
2983 | 0 | if iterations >= max_iterations { |
2984 | 0 | bail!("Loop exceeded maximum iterations ({})", max_iterations); |
2985 | 0 | } |
2986 | | |
2987 | | // Evaluate body, catching break |
2988 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
2989 | 0 | Ok(val) => { |
2990 | 0 | result = val; |
2991 | 0 | iterations += 1; |
2992 | 0 | } |
2993 | 0 | Err(e) if e.to_string() == "break" => { |
2994 | 0 | break; |
2995 | | } |
2996 | 0 | Err(e) if e.to_string() == "continue" => { |
2997 | 0 | iterations += 1; |
2998 | 0 | } |
2999 | 0 | Err(e) => return Err(e), |
3000 | | } |
3001 | | } |
3002 | | |
3003 | 0 | Ok(result) |
3004 | 0 | } |
3005 | | |
3006 | | /// Evaluate block expression (complexity: 4) |
3007 | 1 | fn evaluate_block(&mut self, exprs: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
3008 | 1 | if exprs.is_empty() { |
3009 | 0 | return Ok(Value::Unit); |
3010 | 1 | } |
3011 | | |
3012 | 1 | let mut result = Value::Unit; |
3013 | 2 | for expr1 in exprs { |
3014 | 1 | result = self.evaluate_expr(expr, deadline, depth + 1)?0 ; |
3015 | | } |
3016 | 1 | Ok(result) |
3017 | 1 | } |
3018 | | |
3019 | | /// Evaluate list literal (complexity: 4) |
3020 | 0 | fn evaluate_list_literal( |
3021 | 0 | &mut self, |
3022 | 0 | elements: &[Expr], |
3023 | 0 | deadline: Instant, |
3024 | 0 | depth: usize, |
3025 | 0 | ) -> Result<Value> { |
3026 | 0 | let mut results = Vec::new(); |
3027 | 0 | for elem in elements { |
3028 | 0 | if let ExprKind::Spread { expr } = &elem.kind { |
3029 | | // Evaluate the spread expression and expand it into the array |
3030 | 0 | let val = self.evaluate_expr(expr, deadline, depth + 1)?; |
3031 | 0 | match val { |
3032 | 0 | Value::List(items) => { |
3033 | 0 | // Spread the items into the result |
3034 | 0 | results.extend(items); |
3035 | 0 | } |
3036 | 0 | Value::Tuple(items) => { |
3037 | 0 | // Also allow spreading tuples |
3038 | 0 | results.extend(items); |
3039 | 0 | } |
3040 | 0 | Value::Range { start, end, inclusive } => { |
3041 | | // Spread range values into individual integers |
3042 | 0 | let range_values = self.expand_range_to_values(start, end, inclusive)?; |
3043 | 0 | results.extend(range_values); |
3044 | | } |
3045 | | _ => { |
3046 | 0 | bail!("Cannot spread non-iterable value: {}", self.get_value_type_name(&val)); |
3047 | | } |
3048 | | } |
3049 | | } else { |
3050 | | // Regular element |
3051 | 0 | let val = self.evaluate_expr(elem, deadline, depth + 1)?; |
3052 | 0 | results.push(val); |
3053 | | } |
3054 | | } |
3055 | 0 | Ok(Value::List(results)) |
3056 | 0 | } |
3057 | | |
3058 | | /// Expand a range into individual `Value::Int` items for spreading |
3059 | 0 | fn expand_range_to_values(&self, start: i64, end: i64, inclusive: bool) -> Result<Vec<Value>> { |
3060 | 0 | let actual_end = if inclusive { end } else { end - 1 }; |
3061 | | |
3062 | 0 | if start > actual_end { |
3063 | 0 | return Ok(Vec::new()); // Empty range |
3064 | 0 | } |
3065 | | |
3066 | | // Prevent excessive memory allocation for very large ranges |
3067 | 0 | let range_size = (actual_end - start + 1) as usize; |
3068 | 0 | if range_size > 10000 { |
3069 | 0 | bail!("Range too large to expand: {} elements (limit: 10000)", range_size); |
3070 | 0 | } |
3071 | | |
3072 | 0 | let mut values = Vec::with_capacity(range_size); |
3073 | 0 | for i in start..=actual_end { |
3074 | 0 | values.push(Value::Int(i)); |
3075 | 0 | } |
3076 | | |
3077 | 0 | Ok(values) |
3078 | 0 | } |
3079 | | |
3080 | | /// Evaluate tuple literal (complexity: 4) |
3081 | 0 | fn evaluate_tuple_literal( |
3082 | 0 | &mut self, |
3083 | 0 | elements: &[Expr], |
3084 | 0 | deadline: Instant, |
3085 | 0 | depth: usize, |
3086 | 0 | ) -> Result<Value> { |
3087 | 0 | let mut results = Vec::new(); |
3088 | 0 | for elem in elements { |
3089 | 0 | let val = self.evaluate_expr(elem, deadline, depth + 1)?; |
3090 | 0 | results.push(val); |
3091 | | } |
3092 | 0 | Ok(Value::Tuple(results)) |
3093 | 0 | } |
3094 | | |
3095 | | /// Evaluate range literal (complexity: 5) |
3096 | 0 | fn evaluate_range_literal( |
3097 | 0 | &mut self, |
3098 | 0 | start: &Expr, |
3099 | 0 | end: &Expr, |
3100 | 0 | inclusive: bool, |
3101 | 0 | deadline: Instant, |
3102 | 0 | depth: usize, |
3103 | 0 | ) -> Result<Value> { |
3104 | 0 | let start_val = self.evaluate_expr(start, deadline, depth + 1)?; |
3105 | 0 | let end_val = self.evaluate_expr(end, deadline, depth + 1)?; |
3106 | | |
3107 | 0 | match (start_val, end_val) { |
3108 | 0 | (Value::Int(s), Value::Int(e)) => Ok(Value::Range { |
3109 | 0 | start: s, |
3110 | 0 | end: e, |
3111 | 0 | inclusive, |
3112 | 0 | }), |
3113 | 0 | _ => bail!("Range endpoints must be integers"), |
3114 | | } |
3115 | 0 | } |
3116 | | |
3117 | | /// Evaluate assignment expression (complexity: 5) |
3118 | 0 | fn evaluate_assignment( |
3119 | 0 | &mut self, |
3120 | 0 | target: &Expr, |
3121 | 0 | value: &Expr, |
3122 | 0 | deadline: Instant, |
3123 | 0 | depth: usize, |
3124 | 0 | ) -> Result<Value> { |
3125 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3126 | | |
3127 | | // For now, only support simple variable assignment |
3128 | 0 | if let ExprKind::Identifier(name) = &target.kind { |
3129 | | // Use update_binding which checks mutability |
3130 | 0 | self.update_binding(name, val.clone())?; |
3131 | 0 | Ok(val) |
3132 | | } else { |
3133 | 0 | bail!( |
3134 | 0 | "Only simple variable assignment is supported, got: {:?}", |
3135 | | target.kind |
3136 | | ); |
3137 | | } |
3138 | 0 | } |
3139 | | |
3140 | | /// Evaluate let binding (complexity: 5) |
3141 | 17 | fn evaluate_let_binding( |
3142 | 17 | &mut self, |
3143 | 17 | name: &str, |
3144 | 17 | value: &Expr, |
3145 | 17 | body: &Expr, |
3146 | 17 | is_mutable: bool, |
3147 | 17 | deadline: Instant, |
3148 | 17 | depth: usize, |
3149 | 17 | ) -> Result<Value> { |
3150 | 17 | let val = self.evaluate_expr(value, deadline, depth + 1)?0 ; |
3151 | 17 | self.create_binding(name.to_string(), val.clone(), is_mutable); |
3152 | | |
3153 | | // If there's a body, evaluate it; otherwise return the value |
3154 | 17 | match &body.kind { |
3155 | 17 | ExprKind::Literal(Literal::Unit) => Ok(val), |
3156 | 0 | _ => self.evaluate_expr(body, deadline, depth + 1), |
3157 | | } |
3158 | 17 | } |
3159 | | |
3160 | | /// Evaluate let pattern binding (destructuring assignment) |
3161 | 0 | fn evaluate_let_pattern( |
3162 | 0 | &mut self, |
3163 | 0 | pattern: &crate::frontend::ast::Pattern, |
3164 | 0 | value: &Expr, |
3165 | 0 | body: &Expr, |
3166 | 0 | is_mutable: bool, |
3167 | 0 | deadline: Instant, |
3168 | 0 | depth: usize, |
3169 | 0 | ) -> Result<Value> { |
3170 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3171 | | |
3172 | | // Use existing pattern matching logic |
3173 | 0 | if let Some(bindings) = Self::pattern_matches(&val, pattern)? { |
3174 | 0 | let _saved_bindings = self.bindings.clone(); |
3175 | | |
3176 | | // Apply all pattern bindings |
3177 | 0 | for (name, binding_val) in bindings { |
3178 | 0 | self.create_binding(name, binding_val, is_mutable); |
3179 | 0 | } |
3180 | | |
3181 | | // Evaluate the body expression |
3182 | | |
3183 | | |
3184 | | // Pattern matching succeeded, keep the new bindings |
3185 | 0 | match &body.kind { |
3186 | 0 | ExprKind::Literal(Literal::Unit) => Ok(val), |
3187 | 0 | _ => self.evaluate_expr(body, deadline, depth + 1), |
3188 | | } |
3189 | | } else { |
3190 | 0 | bail!("Pattern does not match value in let binding"); |
3191 | | } |
3192 | 0 | } |
3193 | | |
3194 | | /// Evaluate string interpolation (complexity: 7) |
3195 | 0 | fn evaluate_string_interpolation( |
3196 | 0 | &mut self, |
3197 | 0 | parts: &[crate::frontend::ast::StringPart], |
3198 | 0 | deadline: Instant, |
3199 | 0 | depth: usize, |
3200 | 0 | ) -> Result<Value> { |
3201 | | use crate::frontend::ast::StringPart; |
3202 | | |
3203 | 0 | let mut result = String::new(); |
3204 | 0 | for part in parts { |
3205 | 0 | match part { |
3206 | 0 | StringPart::Text(text) => result.push_str(text), |
3207 | 0 | StringPart::Expr(expr) => { |
3208 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
3209 | | // Format the value for interpolation (without quotes for strings) |
3210 | 0 | match value { |
3211 | 0 | Value::String(s) => result.push_str(&s), |
3212 | 0 | Value::Char(c) => result.push(c), |
3213 | 0 | other => result.push_str(&other.to_string()), |
3214 | | } |
3215 | | } |
3216 | 0 | StringPart::ExprWithFormat { expr, format_spec } => { |
3217 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
3218 | | // Apply format specifier for REPL |
3219 | 0 | let formatted = Self::format_value_with_spec(&value, format_spec); |
3220 | 0 | result.push_str(&formatted); |
3221 | | } |
3222 | | } |
3223 | | } |
3224 | 0 | Ok(Value::String(result)) |
3225 | 0 | } |
3226 | | |
3227 | | /// Format a value with a format specifier like :.2 for floats |
3228 | 0 | fn format_value_with_spec(value: &Value, spec: &str) -> String { |
3229 | | // Parse format specifier (e.g., ":.2" -> precision 2) |
3230 | 0 | if let Some(stripped) = spec.strip_prefix(":.") { |
3231 | 0 | if let Ok(precision) = stripped.parse::<usize>() { |
3232 | 0 | match value { |
3233 | 0 | Value::Float(f) => return format!("{f:.precision$}"), |
3234 | 0 | Value::Int(i) => return format!("{:.precision$}", *i as f64, precision = precision), |
3235 | 0 | _ => {} |
3236 | | } |
3237 | 0 | } |
3238 | 0 | } |
3239 | | // Default formatting if spec doesn't match or isn't supported |
3240 | 0 | value.to_string() |
3241 | 0 | } |
3242 | | |
3243 | | /// Evaluate function definition (complexity: 5) |
3244 | 0 | fn evaluate_function_definition( |
3245 | 0 | &mut self, |
3246 | 0 | name: &str, |
3247 | 0 | params: &[crate::frontend::ast::Param], |
3248 | 0 | body: &Expr, |
3249 | 0 | ) -> Value { |
3250 | 0 | let param_names: Vec<String> = params |
3251 | 0 | .iter() |
3252 | 0 | .map(crate::frontend::ast::Param::name) |
3253 | 0 | .collect(); |
3254 | 0 | let func_value = Value::Function { |
3255 | 0 | name: name.to_string(), |
3256 | 0 | params: param_names, |
3257 | 0 | body: Box::new(body.clone()), |
3258 | 0 | }; |
3259 | | |
3260 | | // Store the function in bindings |
3261 | 0 | self.bindings.insert(name.to_string(), func_value.clone()); |
3262 | 0 | func_value |
3263 | 0 | } |
3264 | | |
3265 | | /// Evaluate lambda expression (complexity: 3) |
3266 | 0 | fn evaluate_lambda_expression(params: &[crate::frontend::ast::Param], body: &Expr) -> Value { |
3267 | 0 | let param_names: Vec<String> = params |
3268 | 0 | .iter() |
3269 | 0 | .map(crate::frontend::ast::Param::name) |
3270 | 0 | .collect(); |
3271 | 0 | Value::Lambda { |
3272 | 0 | params: param_names, |
3273 | 0 | body: Box::new(body.clone()), |
3274 | 0 | } |
3275 | 0 | } |
3276 | | |
3277 | | /// Evaluate `DataFrame` literal (complexity: 6) |
3278 | 0 | fn evaluate_dataframe_literal( |
3279 | 0 | &mut self, |
3280 | 0 | columns: &[crate::frontend::ast::DataFrameColumn], |
3281 | 0 | deadline: Instant, |
3282 | 0 | depth: usize, |
3283 | 0 | ) -> Result<Value> { |
3284 | 0 | let mut df_columns = Vec::new(); |
3285 | 0 | for col in columns { |
3286 | 0 | let mut values = Vec::new(); |
3287 | 0 | for val_expr in &col.values { |
3288 | 0 | let val = self.evaluate_expr(val_expr, deadline, depth + 1)?; |
3289 | 0 | values.push(val); |
3290 | | } |
3291 | 0 | df_columns.push(DataFrameColumn { |
3292 | 0 | name: col.name.clone(), |
3293 | 0 | values, |
3294 | 0 | }); |
3295 | | } |
3296 | 0 | Ok(Value::DataFrame { |
3297 | 0 | columns: df_columns, |
3298 | 0 | }) |
3299 | 0 | } |
3300 | | |
3301 | | |
3302 | | /// Evaluate `Result::Ok` constructor (complexity: 3) |
3303 | 0 | fn evaluate_result_ok( |
3304 | 0 | &mut self, |
3305 | 0 | value: &Expr, |
3306 | 0 | deadline: Instant, |
3307 | 0 | depth: usize, |
3308 | 0 | ) -> Result<Value> { |
3309 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3310 | 0 | Ok(Value::EnumVariant { |
3311 | 0 | enum_name: "Result".to_string(), |
3312 | 0 | variant_name: "Ok".to_string(), |
3313 | 0 | data: Some(vec![val]), |
3314 | 0 | }) |
3315 | 0 | } |
3316 | | |
3317 | | /// Evaluate `Result::Err` constructor (complexity: 3) |
3318 | 0 | fn evaluate_result_err( |
3319 | 0 | &mut self, |
3320 | 0 | error: &Expr, |
3321 | 0 | deadline: Instant, |
3322 | 0 | depth: usize, |
3323 | 0 | ) -> Result<Value> { |
3324 | 0 | let err = self.evaluate_expr(error, deadline, depth + 1)?; |
3325 | 0 | Ok(Value::EnumVariant { |
3326 | 0 | enum_name: "Result".to_string(), |
3327 | 0 | variant_name: "Err".to_string(), |
3328 | 0 | data: Some(vec![err]), |
3329 | 0 | }) |
3330 | 0 | } |
3331 | | |
3332 | | /// Evaluate `Option::Some` constructor (complexity: 3) |
3333 | 0 | fn evaluate_option_some( |
3334 | 0 | &mut self, |
3335 | 0 | value: &Expr, |
3336 | 0 | deadline: Instant, |
3337 | 0 | depth: usize, |
3338 | 0 | ) -> Result<Value> { |
3339 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3340 | 0 | Ok(Value::EnumVariant { |
3341 | 0 | enum_name: "Option".to_string(), |
3342 | 0 | variant_name: "Some".to_string(), |
3343 | 0 | data: Some(vec![val]), |
3344 | 0 | }) |
3345 | 0 | } |
3346 | | |
3347 | | /// Evaluate `Option::None` constructor (complexity: 1) |
3348 | 0 | fn evaluate_option_none() -> Value { |
3349 | 0 | Value::EnumVariant { |
3350 | 0 | enum_name: "Option".to_string(), |
3351 | 0 | variant_name: "None".to_string(), |
3352 | 0 | data: None, |
3353 | 0 | } |
3354 | 0 | } |
3355 | | |
3356 | | /// Evaluate try operator (?) - early return on Err or None |
3357 | 0 | fn evaluate_try_operator( |
3358 | 0 | &mut self, |
3359 | 0 | expr: &Expr, |
3360 | 0 | deadline: Instant, |
3361 | 0 | depth: usize, |
3362 | 0 | ) -> Result<Value> { |
3363 | 0 | let val = self.evaluate_expr(expr, deadline, depth + 1)?; |
3364 | | |
3365 | | // Check if it's a Result::Err or Option::None and propagate |
3366 | 0 | if let Value::EnumVariant { enum_name, variant_name, data } = &val { |
3367 | 0 | if enum_name == "Result" && variant_name == "Err" { |
3368 | | // For Result::Err, propagate the error |
3369 | 0 | return Ok(val.clone()); |
3370 | 0 | } else if enum_name == "Option" && variant_name == "None" { |
3371 | | // For Option::None, propagate None |
3372 | 0 | return Ok(val.clone()); |
3373 | 0 | } else if enum_name == "Result" && variant_name == "Ok" { |
3374 | | // For Result::Ok, unwrap the value |
3375 | 0 | if let Some(values) = data { |
3376 | 0 | if !values.is_empty() { |
3377 | 0 | return Ok(values[0].clone()); |
3378 | 0 | } |
3379 | 0 | } |
3380 | 0 | } else if enum_name == "Option" && variant_name == "Some" { |
3381 | | // For Option::Some, unwrap the value |
3382 | 0 | if let Some(values) = data { |
3383 | 0 | if !values.is_empty() { |
3384 | 0 | return Ok(values[0].clone()); |
3385 | 0 | } |
3386 | 0 | } |
3387 | 0 | } |
3388 | 0 | } |
3389 | | |
3390 | | // If not a Result or Option, return as-is (this might be an error case) |
3391 | 0 | Ok(val) |
3392 | 0 | } |
3393 | | |
3394 | | /// Evaluate methods on enum variants (Result/Option types) |
3395 | | #[allow(clippy::too_many_lines)] |
3396 | | /// Evaluate enum methods with complexity <10 |
3397 | | /// |
3398 | | /// Delegates to specialized handlers for each enum type |
3399 | | /// |
3400 | | /// Example Usage: |
3401 | | /// |
3402 | | /// Handles methods on Result and Option enums: |
3403 | | /// - `Result::unwrap()` - Returns Ok value or panics on Err |
3404 | | /// - `Result::unwrap_or(default)` - Returns Ok value or default |
3405 | | /// - `Option::unwrap()` - Returns Some value or panics on None |
3406 | | /// - `Option::unwrap_or(default)` - Returns Some value or default |
3407 | 0 | fn evaluate_enum_methods( |
3408 | 0 | &mut self, |
3409 | 0 | receiver: Value, |
3410 | 0 | method: &str, |
3411 | 0 | args: &[Expr], |
3412 | 0 | deadline: Instant, |
3413 | 0 | depth: usize, |
3414 | 0 | ) -> Result<Value> { |
3415 | 0 | if let Value::EnumVariant { enum_name, variant_name, data } = receiver { |
3416 | 0 | match enum_name.as_str() { |
3417 | 0 | "Result" => self.evaluate_result_methods(&variant_name, method, data.as_ref(), args, deadline, depth), |
3418 | 0 | "Option" => self.evaluate_option_methods(&variant_name, method, data.as_ref(), args, deadline, depth), |
3419 | 0 | "Vec" => self.evaluate_vec_methods(&variant_name, method, data.as_ref(), args, deadline, depth), |
3420 | 0 | _ => bail!("Method {} not supported on {}", method, enum_name), |
3421 | | } |
3422 | | } else { |
3423 | 0 | bail!("evaluate_enum_methods called on non-enum variant") |
3424 | | } |
3425 | 0 | } |
3426 | | |
3427 | | /// Handle Result enum methods (unwrap, expect, map, `and_then`) |
3428 | | /// |
3429 | | /// # Example Usage |
3430 | | /// Evaluates methods on enum variants like `Some(x).unwrap()` or `Ok(v).is_ok()`. |
3431 | | /// |
3432 | | /// use `ruchy::runtime::{Repl`, Value}; |
3433 | | /// use `std::time::{Duration`, Instant}; |
3434 | | /// |
3435 | | /// let mut repl = `Repl::new().unwrap()`; |
3436 | | /// let deadline = `Instant::now()` + `Duration::from_secs(1)`; |
3437 | | /// let data = Some(vec![`Value::Int(42)`]); |
3438 | | /// |
3439 | | /// // Test Ok unwrap |
3440 | | /// let result = `repl.evaluate_result_methods("Ok`", "unwrap", &data, &[], deadline, `0).unwrap()`; |
3441 | | /// `assert_eq!(result`, `Value::Int(42)`); |
3442 | | /// ``` |
3443 | 0 | fn evaluate_result_methods( |
3444 | 0 | &mut self, |
3445 | 0 | variant_name: &str, |
3446 | 0 | method: &str, |
3447 | 0 | data: Option<&Vec<Value>>, |
3448 | 0 | args: &[Expr], |
3449 | 0 | deadline: Instant, |
3450 | 0 | depth: usize, |
3451 | 0 | ) -> Result<Value> { |
3452 | 0 | match (variant_name, method) { |
3453 | 0 | ("Ok", "unwrap" | "expect") if args.is_empty() || args.len() == 1 => { |
3454 | 0 | self.extract_value_or_unit(data) |
3455 | | } |
3456 | 0 | ("Err", "unwrap") if args.is_empty() => { |
3457 | 0 | let error_msg = self.format_error_message("Result::unwrap()", "Err", data); |
3458 | 0 | bail!(error_msg) |
3459 | | } |
3460 | 0 | ("Err", "expect") if args.len() == 1 => { |
3461 | 0 | let custom_msg = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3462 | 0 | let msg = self.value_to_string(custom_msg); |
3463 | 0 | bail!(msg) |
3464 | | } |
3465 | 0 | ("Ok", "map") if args.len() == 1 => { |
3466 | 0 | self.apply_function_to_value("Result", "Ok", data, &args[0], deadline, depth) |
3467 | | } |
3468 | 0 | ("Err", "map") if args.len() == 1 => { |
3469 | 0 | Ok(Value::EnumVariant { |
3470 | 0 | enum_name: "Result".to_string(), |
3471 | 0 | variant_name: variant_name.to_string(), |
3472 | 0 | data: data.cloned(), |
3473 | 0 | }) |
3474 | | } |
3475 | 0 | ("Ok", "and_then") if args.len() == 1 => { |
3476 | 0 | self.apply_function_and_flatten(data, &args[0], deadline, depth) |
3477 | | } |
3478 | 0 | ("Err", "and_then") if args.len() == 1 => { |
3479 | 0 | Ok(Value::EnumVariant { |
3480 | 0 | enum_name: "Result".to_string(), |
3481 | 0 | variant_name: variant_name.to_string(), |
3482 | 0 | data: data.cloned(), |
3483 | 0 | }) |
3484 | | } |
3485 | 0 | _ => bail!("Method {} not supported on Result::{}", method, variant_name), |
3486 | | } |
3487 | 0 | } |
3488 | | |
3489 | | /// Handle Option enum methods (unwrap, expect, map, `and_then`) |
3490 | 0 | fn evaluate_option_methods( |
3491 | 0 | &mut self, |
3492 | 0 | variant_name: &str, |
3493 | 0 | method: &str, |
3494 | 0 | data: Option<&Vec<Value>>, |
3495 | 0 | args: &[Expr], |
3496 | 0 | deadline: Instant, |
3497 | 0 | depth: usize, |
3498 | 0 | ) -> Result<Value> { |
3499 | 0 | match (variant_name, method) { |
3500 | 0 | ("Some", "unwrap" | "expect") if args.is_empty() || args.len() == 1 => { |
3501 | 0 | self.extract_value_or_unit(data) |
3502 | | } |
3503 | 0 | ("None", "unwrap") if args.is_empty() => { |
3504 | 0 | bail!("called `Option::unwrap()` on a `None` value") |
3505 | | } |
3506 | 0 | ("None", "expect") if args.len() == 1 => { |
3507 | 0 | let custom_msg = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3508 | 0 | let msg = self.value_to_string(custom_msg); |
3509 | 0 | bail!(msg) |
3510 | | } |
3511 | 0 | ("Some", "map") if args.len() == 1 => { |
3512 | 0 | self.apply_function_to_value("Option", "Some", data, &args[0], deadline, depth) |
3513 | | } |
3514 | 0 | ("None", "map" | "and_then") if args.len() == 1 => { |
3515 | 0 | Ok(Value::EnumVariant { |
3516 | 0 | enum_name: "Option".to_string(), |
3517 | 0 | variant_name: variant_name.to_string(), |
3518 | 0 | data: data.cloned(), |
3519 | 0 | }) |
3520 | | } |
3521 | 0 | ("Some", "and_then") if args.len() == 1 => { |
3522 | 0 | self.apply_function_and_flatten(data, &args[0], deadline, depth) |
3523 | | } |
3524 | 0 | _ => bail!("Method {} not supported on Option::{}", method, variant_name), |
3525 | | } |
3526 | 0 | } |
3527 | | |
3528 | | /// Handle Vec enum methods (placeholder for future Vec methods) |
3529 | 0 | fn evaluate_vec_methods( |
3530 | 0 | &mut self, |
3531 | 0 | variant_name: &str, |
3532 | 0 | method: &str, |
3533 | 0 | data: Option<&Vec<Value>>, |
3534 | 0 | args: &[Expr], |
3535 | 0 | deadline: Instant, |
3536 | 0 | depth: usize, |
3537 | 0 | ) -> Result<Value> { |
3538 | 0 | match method { |
3539 | 0 | "len" => Ok(Value::Int(data.as_ref().map_or(0, |v| v.len() as i64))), |
3540 | 0 | "push" if args.len() == 1 => { |
3541 | 0 | let new_elem = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
3542 | 0 | let mut vec_data = data.cloned().unwrap_or_default(); |
3543 | 0 | vec_data.push(new_elem); |
3544 | 0 | Ok(Value::EnumVariant { |
3545 | 0 | enum_name: "Vec".to_string(), |
3546 | 0 | variant_name: variant_name.to_string(), |
3547 | 0 | data: Some(vec_data), |
3548 | 0 | }) |
3549 | | } |
3550 | 0 | _ => bail!("Method {} not supported on Vec", method), |
3551 | | } |
3552 | 0 | } |
3553 | | |
3554 | | /// Extract value from enum data or return Unit |
3555 | 0 | fn extract_value_or_unit(&self, data: Option<&Vec<Value>>) -> Result<Value> { |
3556 | 0 | if let Some(values) = data { |
3557 | 0 | if !values.is_empty() { |
3558 | 0 | return Ok(values[0].clone()); |
3559 | 0 | } |
3560 | 0 | } |
3561 | 0 | Ok(Value::Unit) |
3562 | 0 | } |
3563 | | |
3564 | | /// Format error message for unwrap operations |
3565 | 0 | fn format_error_message(&self, method: &str, variant: &str, data: Option<&Vec<Value>>) -> String { |
3566 | 0 | if let Some(values) = data { |
3567 | 0 | if values.is_empty() { |
3568 | 0 | format!("called `{method}` on an `{variant}` value") |
3569 | | } else { |
3570 | 0 | format!("called `{}` on an `{}` value: {}", method, variant, values[0]) |
3571 | | } |
3572 | | } else { |
3573 | 0 | format!("called `{method}` on an `{variant}` value") |
3574 | | } |
3575 | 0 | } |
3576 | | |
3577 | | /// Convert Value to string representation |
3578 | 0 | fn value_to_string(&self, value: Value) -> String { |
3579 | 0 | match value { |
3580 | 0 | Value::String(s) => s, |
3581 | 0 | other => format!("{other}"), |
3582 | | } |
3583 | 0 | } |
3584 | | |
3585 | | /// Apply function to enum value (for map operations) |
3586 | 0 | fn apply_function_to_value( |
3587 | 0 | &mut self, |
3588 | 0 | enum_name: &str, |
3589 | 0 | variant_name: &str, |
3590 | 0 | data: Option<&Vec<Value>>, |
3591 | 0 | func_arg: &Expr, |
3592 | 0 | deadline: Instant, |
3593 | 0 | depth: usize, |
3594 | 0 | ) -> Result<Value> { |
3595 | 0 | if let Some(values) = data { |
3596 | 0 | if !values.is_empty() { |
3597 | 0 | let call_expr = self.create_function_call(func_arg, &values[0]); |
3598 | 0 | let mapped_value = self.evaluate_expr(&call_expr, deadline, depth + 1)?; |
3599 | 0 | return Ok(Value::EnumVariant { |
3600 | 0 | enum_name: enum_name.to_string(), |
3601 | 0 | variant_name: variant_name.to_string(), |
3602 | 0 | data: Some(vec![mapped_value]), |
3603 | 0 | }); |
3604 | 0 | } |
3605 | 0 | } |
3606 | 0 | Ok(Value::EnumVariant { |
3607 | 0 | enum_name: enum_name.to_string(), |
3608 | 0 | variant_name: variant_name.to_string(), |
3609 | 0 | data: Some(vec![Value::Unit]), |
3610 | 0 | }) |
3611 | 0 | } |
3612 | | |
3613 | | /// Apply function and flatten result (for `and_then` operations) |
3614 | 0 | fn apply_function_and_flatten( |
3615 | 0 | &mut self, |
3616 | 0 | data: Option<&Vec<Value>>, |
3617 | 0 | func_arg: &Expr, |
3618 | 0 | deadline: Instant, |
3619 | 0 | depth: usize, |
3620 | 0 | ) -> Result<Value> { |
3621 | 0 | if let Some(values) = data { |
3622 | 0 | if !values.is_empty() { |
3623 | 0 | let call_expr = self.create_function_call(func_arg, &values[0]); |
3624 | 0 | return self.evaluate_expr(&call_expr, deadline, depth + 1); |
3625 | 0 | } |
3626 | 0 | } |
3627 | 0 | Ok(Value::EnumVariant { |
3628 | 0 | enum_name: "Result".to_string(), |
3629 | 0 | variant_name: "Ok".to_string(), |
3630 | 0 | data: Some(vec![Value::Unit]), |
3631 | 0 | }) |
3632 | 0 | } |
3633 | | |
3634 | | /// Create function call expression for enum combinators |
3635 | 0 | fn create_function_call(&self, func_arg: &Expr, value: &Value) -> Expr { |
3636 | 0 | Expr::new( |
3637 | 0 | ExprKind::Call { |
3638 | 0 | func: Box::new(func_arg.clone()), |
3639 | 0 | args: vec![Expr::new( |
3640 | 0 | ExprKind::Literal(crate::frontend::ast::Literal::from_value(value)), |
3641 | 0 | Span { start: 0, end: 0 }, |
3642 | 0 | )], |
3643 | 0 | }, |
3644 | 0 | Span { start: 0, end: 0 }, |
3645 | | ) |
3646 | 0 | } |
3647 | | |
3648 | | /// Evaluate object literal (complexity: 10) |
3649 | 0 | fn evaluate_object_literal( |
3650 | 0 | &mut self, |
3651 | 0 | fields: &[crate::frontend::ast::ObjectField], |
3652 | 0 | deadline: Instant, |
3653 | 0 | depth: usize, |
3654 | 0 | ) -> Result<Value> { |
3655 | | use crate::frontend::ast::ObjectField; |
3656 | 0 | let mut map = HashMap::new(); |
3657 | | |
3658 | 0 | for field in fields { |
3659 | 0 | match field { |
3660 | 0 | ObjectField::KeyValue { key, value } => { |
3661 | 0 | let val = self.evaluate_expr(value, deadline, depth + 1)?; |
3662 | 0 | map.insert(key.clone(), val); |
3663 | | } |
3664 | 0 | ObjectField::Spread { expr } => { |
3665 | 0 | let spread_val = self.evaluate_expr(expr, deadline, depth + 1)?; |
3666 | 0 | if let Value::Object(spread_map) = spread_val { |
3667 | 0 | map.extend(spread_map); |
3668 | 0 | } else { |
3669 | 0 | bail!("Spread operator can only be used with objects"); |
3670 | | } |
3671 | | } |
3672 | | } |
3673 | | } |
3674 | | |
3675 | 0 | Ok(Value::Object(map)) |
3676 | 0 | } |
3677 | | |
3678 | | /// Evaluate enum definition (complexity: 4) |
3679 | 0 | fn evaluate_enum_definition( |
3680 | 0 | &mut self, |
3681 | 0 | name: &str, |
3682 | 0 | variants: &[crate::frontend::ast::EnumVariant], |
3683 | 0 | ) -> Value { |
3684 | 0 | let variant_names: Vec<String> = variants.iter().map(|v| v.name.clone()).collect(); |
3685 | 0 | self.enum_definitions |
3686 | 0 | .insert(name.to_string(), variant_names); |
3687 | 0 | println!("Defined enum {} with {} variants", name, variants.len()); |
3688 | 0 | Value::Unit |
3689 | 0 | } |
3690 | | |
3691 | | /// Evaluate struct definition (complexity: 3) |
3692 | 0 | fn evaluate_struct_definition( |
3693 | 0 | name: &str, |
3694 | 0 | fields: &[crate::frontend::ast::StructField], |
3695 | 0 | ) -> Value { |
3696 | 0 | println!("Defined struct {} with {} fields", name, fields.len()); |
3697 | 0 | Value::Unit |
3698 | 0 | } |
3699 | | |
3700 | | /// Evaluate struct literal (complexity: 5) |
3701 | 0 | fn evaluate_struct_literal( |
3702 | 0 | &mut self, |
3703 | 0 | fields: &[(String, Expr)], |
3704 | 0 | deadline: Instant, |
3705 | 0 | depth: usize, |
3706 | 0 | ) -> Result<Value> { |
3707 | 0 | let mut map = HashMap::new(); |
3708 | 0 | for (field_name, field_expr) in fields { |
3709 | 0 | let field_value = self.evaluate_expr(field_expr, deadline, depth + 1)?; |
3710 | 0 | map.insert(field_name.clone(), field_value); |
3711 | | } |
3712 | 0 | Ok(Value::Object(map)) |
3713 | 0 | } |
3714 | | |
3715 | | /// Evaluate field access (complexity: 4) |
3716 | 0 | fn evaluate_field_access( |
3717 | 0 | &mut self, |
3718 | 0 | object: &Expr, |
3719 | 0 | field: &str, |
3720 | 0 | deadline: Instant, |
3721 | 0 | depth: usize, |
3722 | 0 | ) -> Result<Value> { |
3723 | 0 | let obj_val = self.evaluate_expr(object, deadline, depth + 1)?; |
3724 | 0 | match obj_val { |
3725 | 0 | Value::Object(map) => map |
3726 | 0 | .get(field) |
3727 | 0 | .cloned() |
3728 | 0 | .ok_or_else(|| anyhow::anyhow!("Field '{}' not found", field)), |
3729 | 0 | Value::Tuple(values) => { |
3730 | | // Handle tuple access like t.0, t.1, etc. |
3731 | 0 | if let Ok(index) = field.parse::<usize>() { |
3732 | 0 | values.get(index) |
3733 | 0 | .cloned() |
3734 | 0 | .ok_or_else(|| anyhow::anyhow!("Tuple index {} out of bounds (length: {})", index, values.len())) |
3735 | | } else { |
3736 | 0 | bail!("Invalid tuple index: '{}'", field) |
3737 | | } |
3738 | | } |
3739 | 0 | _ => bail!("Field access on non-object value"), |
3740 | | } |
3741 | 0 | } |
3742 | | |
3743 | | /// Evaluate optional field access (complexity: 5) |
3744 | 0 | fn evaluate_optional_field_access( |
3745 | 0 | &mut self, |
3746 | 0 | object: &Expr, |
3747 | 0 | field: &str, |
3748 | 0 | deadline: Instant, |
3749 | 0 | depth: usize, |
3750 | 0 | ) -> Result<Value> { |
3751 | 0 | let obj_val = self.evaluate_expr(object, deadline, depth + 1)?; |
3752 | | |
3753 | | // If the object is null, return null (short-circuit evaluation) |
3754 | 0 | if matches!(obj_val, Value::Nil) { |
3755 | 0 | return Ok(Value::Nil); |
3756 | 0 | } |
3757 | | |
3758 | 0 | match obj_val { |
3759 | 0 | Value::Object(map) => Ok(map.get(field).cloned().unwrap_or(Value::Nil)), |
3760 | 0 | Value::Tuple(values) => { |
3761 | | // Handle optional tuple access like t?.0, t?.1, etc. |
3762 | 0 | if let Ok(index) = field.parse::<usize>() { |
3763 | 0 | Ok(values.get(index).cloned().unwrap_or(Value::Nil)) |
3764 | | } else { |
3765 | 0 | Ok(Value::Nil) // Invalid tuple index returns nil instead of error |
3766 | | } |
3767 | | } |
3768 | 0 | _ => Ok(Value::Nil), // Non-object/tuple values return nil instead of error |
3769 | | } |
3770 | 0 | } |
3771 | | |
3772 | | /// Evaluate optional method call with null-safe chaining (complexity: 10) |
3773 | 0 | fn evaluate_optional_method_call( |
3774 | 0 | &mut self, |
3775 | 0 | receiver: &Expr, |
3776 | 0 | method: &str, |
3777 | 0 | args: &[Expr], |
3778 | 0 | deadline: Instant, |
3779 | 0 | depth: usize, |
3780 | 0 | ) -> Result<Value> { |
3781 | 0 | let receiver_val = self.evaluate_expr(receiver, deadline, depth + 1)?; |
3782 | | |
3783 | | // If the receiver is null, return null (short-circuit evaluation) |
3784 | 0 | if matches!(receiver_val, Value::Nil) { |
3785 | 0 | return Ok(Value::Nil); |
3786 | 0 | } |
3787 | | |
3788 | | // Try to call the method, but return nil if it fails instead of erroring |
3789 | 0 | let result = match receiver_val { |
3790 | 0 | Value::List(items) => { |
3791 | 0 | self.evaluate_list_methods(items, method, args, deadline, depth).unwrap_or(Value::Nil) |
3792 | | } |
3793 | 0 | Value::String(s) => { |
3794 | 0 | Self::evaluate_string_methods(&s, method, args, deadline, depth).unwrap_or(Value::Nil) |
3795 | | } |
3796 | 0 | Value::Int(n) => { |
3797 | 0 | Self::evaluate_int_methods(n, method).unwrap_or(Value::Nil) |
3798 | | } |
3799 | 0 | Value::Float(f) => { |
3800 | 0 | Self::evaluate_float_methods(f, method).unwrap_or(Value::Nil) |
3801 | | } |
3802 | 0 | Value::Object(obj) => { |
3803 | 0 | Self::evaluate_object_methods(obj, method, args, deadline, depth).unwrap_or(Value::Nil) |
3804 | | } |
3805 | 0 | Value::HashMap(map) => { |
3806 | 0 | self.evaluate_hashmap_methods(map, method, args, deadline, depth).unwrap_or(Value::Nil) |
3807 | | } |
3808 | 0 | Value::HashSet(set) => { |
3809 | 0 | self.evaluate_hashset_methods(set, method, args, deadline, depth).unwrap_or(Value::Nil) |
3810 | | } |
3811 | | Value::EnumVariant { .. } => { |
3812 | 0 | self.evaluate_enum_methods(receiver_val, method, args, deadline, depth).unwrap_or(Value::Nil) |
3813 | | } |
3814 | 0 | _ => Value::Nil, // Unsupported types return nil |
3815 | | }; |
3816 | | |
3817 | 0 | Ok(result) |
3818 | 0 | } |
3819 | | |
3820 | | /// Evaluate index access (complexity: 5) |
3821 | 0 | fn evaluate_index_access( |
3822 | 0 | &mut self, |
3823 | 0 | object: &Expr, |
3824 | 0 | index: &Expr, |
3825 | 0 | deadline: Instant, |
3826 | 0 | depth: usize, |
3827 | 0 | ) -> Result<Value> { |
3828 | 0 | let obj_val = self.evaluate_expr(object, deadline, depth + 1)?; |
3829 | 0 | let index_val = self.evaluate_expr(index, deadline, depth + 1)?; |
3830 | | |
3831 | | // Check for range indexing first |
3832 | 0 | if let Value::Range { start, end, inclusive } = index_val { |
3833 | 0 | return self.handle_range_indexing(obj_val, start, end, inclusive); |
3834 | 0 | } |
3835 | | |
3836 | | // Handle single index access |
3837 | 0 | self.handle_single_index_access(obj_val, index_val) |
3838 | 0 | } |
3839 | | |
3840 | | /// Handle range-based indexing for lists and strings |
3841 | | /// |
3842 | | /// Example Usage: |
3843 | | /// |
3844 | | /// Handles range indexing for lists and strings: |
3845 | | /// - list[0..2] returns a sublist with elements at indices 0 and 1 |
3846 | | /// - string[1..3] returns substring from index 1 to 2 |
3847 | | /// - list[0..=2] returns elements at indices 0, 1, and 2 (inclusive) |
3848 | 0 | fn handle_range_indexing(&self, obj_val: Value, start: i64, end: i64, inclusive: bool) -> Result<Value> { |
3849 | 0 | match obj_val { |
3850 | 0 | Value::List(list) => { |
3851 | 0 | let (start_idx, end_idx) = self.calculate_slice_bounds(start, end, inclusive, list.len())?; |
3852 | 0 | Ok(Value::List(list[start_idx..end_idx].to_vec())) |
3853 | | } |
3854 | 0 | Value::String(s) => { |
3855 | 0 | let chars: Vec<char> = s.chars().collect(); |
3856 | 0 | let (start_idx, end_idx) = self.calculate_slice_bounds(start, end, inclusive, chars.len())?; |
3857 | 0 | Ok(Value::String(chars[start_idx..end_idx].iter().collect())) |
3858 | | } |
3859 | 0 | _ => bail!("Cannot slice into {:?}", obj_val), |
3860 | | } |
3861 | 0 | } |
3862 | | |
3863 | | /// Handle single index access for various data types |
3864 | | /// |
3865 | | /// # Example Usage |
3866 | | /// Handles range-based indexing for strings and arrays like arr[0..3] or str[1..]. |
3867 | | /// # use `ruchy::runtime::repl::Repl`; |
3868 | | /// # use `ruchy::runtime::repl::Value`; |
3869 | | /// let mut repl = `Repl::new().unwrap()`; |
3870 | | /// let list = `Value::List(vec`![`Value::Int(42)`]); |
3871 | | /// let result = `repl.handle_single_index_access(list`, `Value::Int(0)).unwrap()`; |
3872 | | /// `assert_eq!(result`, `Value::Int(42)`); |
3873 | | /// ``` |
3874 | 0 | fn handle_single_index_access(&self, obj_val: Value, index_val: Value) -> Result<Value> { |
3875 | 0 | match (obj_val, index_val) { |
3876 | 0 | (Value::List(list), Value::Int(idx)) => { |
3877 | 0 | let idx = self.validate_array_index(idx, list.len())?; |
3878 | 0 | Ok(list[idx].clone()) |
3879 | | } |
3880 | 0 | (Value::String(s), Value::Int(idx)) => { |
3881 | 0 | let chars: Vec<char> = s.chars().collect(); |
3882 | 0 | let idx = self.validate_array_index(idx, chars.len())?; |
3883 | 0 | Ok(Value::String(chars[idx].to_string())) |
3884 | | } |
3885 | 0 | (Value::Object(obj), Value::String(key)) => { |
3886 | 0 | obj.get(&key) |
3887 | 0 | .cloned() |
3888 | 0 | .ok_or_else(|| anyhow::anyhow!("Key '{}' not found in object", key)) |
3889 | | } |
3890 | 0 | (obj_val, index_val) => bail!("Cannot index into {:?} with index {:?}", obj_val, index_val), |
3891 | | } |
3892 | 0 | } |
3893 | | |
3894 | | /// Calculate slice bounds and validate them |
3895 | | /// |
3896 | | /// # Example Usage |
3897 | | /// Calculates and validates slice bounds for array indexing operations. |
3898 | | /// Converts indices to valid array bounds and handles inclusive/exclusive ranges. |
3899 | 0 | fn calculate_slice_bounds(&self, start: i64, end: i64, inclusive: bool, len: usize) -> Result<(usize, usize)> { |
3900 | 0 | let start_idx = usize::try_from(start) |
3901 | 0 | .map_err(|_| anyhow::anyhow!("Invalid start index: {}", start))?; |
3902 | | |
3903 | 0 | let end_idx = if inclusive { |
3904 | 0 | usize::try_from(end + 1) |
3905 | 0 | .map_err(|_| anyhow::anyhow!("Invalid end index: {}", end + 1))? |
3906 | | } else { |
3907 | 0 | usize::try_from(end) |
3908 | 0 | .map_err(|_| anyhow::anyhow!("Invalid end index: {}", end))? |
3909 | | }; |
3910 | | |
3911 | 0 | if start_idx > len || end_idx > len { |
3912 | 0 | bail!("Slice indices out of bounds"); |
3913 | 0 | } |
3914 | 0 | if start_idx > end_idx { |
3915 | 0 | bail!("Invalid slice range: start > end"); |
3916 | 0 | } |
3917 | | |
3918 | 0 | Ok((start_idx, end_idx)) |
3919 | 0 | } |
3920 | | |
3921 | | /// Validate array index and convert to usize |
3922 | | /// |
3923 | | /// # Example Usage |
3924 | | /// Calculates and validates slice bounds for array indexing operations. |
3925 | | /// # use `ruchy::runtime::repl::Repl`; |
3926 | | /// let repl = `Repl::new().unwrap()`; |
3927 | | /// let idx = `repl.validate_array_index(2`, `5).unwrap()`; |
3928 | | /// `assert_eq!(idx`, 2); |
3929 | | /// ``` |
3930 | 0 | fn validate_array_index(&self, idx: i64, len: usize) -> Result<usize> { |
3931 | 0 | let idx = usize::try_from(idx) |
3932 | 0 | .map_err(|_| anyhow::anyhow!("Invalid index: {}", idx))?; |
3933 | | |
3934 | 0 | if idx >= len { |
3935 | 0 | bail!("Index {} out of bounds for length {}", idx, len); |
3936 | 0 | } |
3937 | | |
3938 | 0 | Ok(idx) |
3939 | 0 | } |
3940 | | |
3941 | | /// Evaluate slice index expression (complexity: 4) |
3942 | 0 | fn evaluate_slice_index(&mut self, expr: Option<&Expr>, deadline: Instant, depth: usize) -> Result<Option<usize>> { |
3943 | 0 | if let Some(index_expr) = expr { |
3944 | 0 | match self.evaluate_expr(index_expr, deadline, depth + 1)? { |
3945 | 0 | Value::Int(idx) => { |
3946 | 0 | Ok(Some(usize::try_from(idx) |
3947 | 0 | .map_err(|_| anyhow::anyhow!("Invalid slice index: {}", idx))?)) |
3948 | | } |
3949 | 0 | _ => Err(anyhow::anyhow!("Slice indices must be integers")) |
3950 | | } |
3951 | | } else { |
3952 | 0 | Ok(None) |
3953 | | } |
3954 | 0 | } |
3955 | | |
3956 | | /// Validate slice bounds (complexity: 3) |
3957 | 0 | fn validate_slice_bounds(start: usize, end: usize, len: usize) -> Result<()> { |
3958 | 0 | if start > len || end > len { |
3959 | 0 | return Err(anyhow::anyhow!("Slice indices out of bounds")); |
3960 | 0 | } |
3961 | 0 | if start > end { |
3962 | 0 | return Err(anyhow::anyhow!("Invalid slice range: start > end")); |
3963 | 0 | } |
3964 | 0 | Ok(()) |
3965 | 0 | } |
3966 | | |
3967 | | /// Slice a list value (complexity: 4) |
3968 | 0 | fn slice_list(list: Vec<Value>, start_idx: Option<usize>, end_idx: Option<usize>) -> Result<Value> { |
3969 | 0 | let start = start_idx.unwrap_or(0); |
3970 | 0 | let end = end_idx.unwrap_or(list.len()); |
3971 | | |
3972 | 0 | Self::validate_slice_bounds(start, end, list.len())?; |
3973 | 0 | Ok(Value::List(list[start..end].to_vec())) |
3974 | 0 | } |
3975 | | |
3976 | | /// Slice a string value (complexity: 5) |
3977 | 0 | fn slice_string(s: String, start_idx: Option<usize>, end_idx: Option<usize>) -> Result<Value> { |
3978 | 0 | let chars: Vec<char> = s.chars().collect(); |
3979 | 0 | let start = start_idx.unwrap_or(0); |
3980 | 0 | let end = end_idx.unwrap_or(chars.len()); |
3981 | | |
3982 | 0 | Self::validate_slice_bounds(start, end, chars.len())?; |
3983 | 0 | let sliced: String = chars[start..end].iter().collect(); |
3984 | 0 | Ok(Value::String(sliced)) |
3985 | 0 | } |
3986 | | |
3987 | | /// Main slice evaluation function (complexity: 6) |
3988 | 0 | fn evaluate_slice( |
3989 | 0 | &mut self, |
3990 | 0 | object: &Expr, |
3991 | 0 | start: Option<&Expr>, |
3992 | 0 | end: Option<&Expr>, |
3993 | 0 | deadline: Instant, |
3994 | 0 | depth: usize, |
3995 | 0 | ) -> Result<Value> { |
3996 | 0 | let obj_val = self.evaluate_expr(object, deadline, depth + 1)?; |
3997 | | |
3998 | | // Evaluate start and end indices |
3999 | 0 | let start_idx = self.evaluate_slice_index(start, deadline, depth)?; |
4000 | 0 | let end_idx = self.evaluate_slice_index(end, deadline, depth)?; |
4001 | | |
4002 | | // Perform slicing based on value type |
4003 | 0 | match obj_val { |
4004 | 0 | Value::List(list) => Self::slice_list(list, start_idx, end_idx), |
4005 | 0 | Value::String(s) => Self::slice_string(s, start_idx, end_idx), |
4006 | 0 | _ => Err(anyhow::anyhow!("Cannot slice value of type {:?}", obj_val)), |
4007 | | } |
4008 | 0 | } |
4009 | | |
4010 | | /// Evaluate trait definition (complexity: 3) |
4011 | 0 | fn evaluate_trait_definition( |
4012 | 0 | name: &str, |
4013 | 0 | methods: &[crate::frontend::ast::TraitMethod], |
4014 | 0 | ) -> Value { |
4015 | 0 | println!("Defined trait {} with {} methods", name, methods.len()); |
4016 | 0 | Value::Unit |
4017 | 0 | } |
4018 | | |
4019 | | /// Evaluate impl block (complexity: 12) |
4020 | 0 | fn evaluate_impl_block( |
4021 | 0 | &mut self, |
4022 | 0 | for_type: &str, |
4023 | 0 | methods: &[crate::frontend::ast::ImplMethod], |
4024 | 0 | ) -> Value { |
4025 | 0 | for method in methods { |
4026 | 0 | let qualified_name = format!("{}::{}", for_type, method.name); |
4027 | | |
4028 | 0 | let param_names: Vec<String> = method |
4029 | 0 | .params |
4030 | 0 | .iter() |
4031 | 0 | .filter_map(|p| { |
4032 | 0 | let name = p.name(); |
4033 | 0 | if name != "self" && name != "&self" { |
4034 | 0 | Some(name) |
4035 | | } else { |
4036 | 0 | None |
4037 | | } |
4038 | 0 | }) |
4039 | 0 | .collect(); |
4040 | | |
4041 | 0 | self.impl_methods |
4042 | 0 | .insert(qualified_name, (param_names, method.body.clone())); |
4043 | | } |
4044 | | |
4045 | 0 | println!( |
4046 | 0 | "Defined impl for {} with {} methods", |
4047 | | for_type, |
4048 | 0 | methods.len() |
4049 | | ); |
4050 | 0 | Value::Unit |
4051 | 0 | } |
4052 | | |
4053 | | /// Evaluate binary expression (complexity: 3) |
4054 | 21 | fn evaluate_binary_expr( |
4055 | 21 | &mut self, |
4056 | 21 | left: &Expr, |
4057 | 21 | op: BinaryOp, |
4058 | 21 | right: &Expr, |
4059 | 21 | deadline: Instant, |
4060 | 21 | depth: usize, |
4061 | 21 | ) -> Result<Value> { |
4062 | | // Handle short-circuit operators |
4063 | 21 | match op { |
4064 | | BinaryOp::NullCoalesce => { |
4065 | 0 | let lhs = self.evaluate_expr(left, deadline, depth + 1)?; |
4066 | 0 | if matches!(lhs, Value::Nil) { |
4067 | 0 | self.evaluate_expr(right, deadline, depth + 1) |
4068 | | } else { |
4069 | 0 | Ok(lhs) |
4070 | | } |
4071 | | } |
4072 | | BinaryOp::And => { |
4073 | 2 | let lhs = self.evaluate_expr(left, deadline, depth + 1)?0 ; |
4074 | 2 | if lhs.is_truthy() { |
4075 | 2 | self.evaluate_expr(right, deadline, depth + 1) |
4076 | | } else { |
4077 | 0 | Ok(lhs) |
4078 | | } |
4079 | | } |
4080 | | BinaryOp::Or => { |
4081 | 0 | let lhs = self.evaluate_expr(left, deadline, depth + 1)?; |
4082 | 0 | if lhs.is_truthy() { |
4083 | 0 | Ok(lhs) |
4084 | | } else { |
4085 | 0 | self.evaluate_expr(right, deadline, depth + 1) |
4086 | | } |
4087 | | } |
4088 | | _ => { |
4089 | 19 | let lhs = self.evaluate_expr(left, deadline, depth + 1)?0 ; |
4090 | 19 | let rhs = self.evaluate_expr(right, deadline, depth + 1)?0 ; |
4091 | 19 | Self::evaluate_binary(&lhs, op, &rhs) |
4092 | | } |
4093 | | } |
4094 | 21 | } |
4095 | | |
4096 | | /// Evaluate unary expression (complexity: 2) |
4097 | 0 | fn evaluate_unary_expr( |
4098 | 0 | &mut self, |
4099 | 0 | op: UnaryOp, |
4100 | 0 | operand: &Expr, |
4101 | 0 | deadline: Instant, |
4102 | 0 | depth: usize, |
4103 | 0 | ) -> Result<Value> { |
4104 | 0 | let val = self.evaluate_expr(operand, deadline, depth + 1)?; |
4105 | 0 | Self::evaluate_unary(op, &val) |
4106 | 0 | } |
4107 | | |
4108 | | /// Evaluate identifier (complexity: 2) |
4109 | 34 | fn evaluate_identifier(&self, name: &str) -> Result<Value> { |
4110 | 34 | self.get_binding(name) |
4111 | 34 | .ok_or_else(|| anyhow::anyhow!("Undefined variable: {}"1 , name)) |
4112 | 34 | } |
4113 | | |
4114 | | /// Evaluate qualified name (complexity: 2) |
4115 | 0 | fn evaluate_qualified_name(module: &str, name: &str) -> Value { |
4116 | 0 | Value::EnumVariant { |
4117 | 0 | enum_name: module.to_string(), |
4118 | 0 | variant_name: name.to_string(), |
4119 | 0 | data: None, |
4120 | 0 | } |
4121 | 0 | } |
4122 | | |
4123 | | /// Evaluate await expression (complexity: 1) |
4124 | 0 | fn evaluate_await_expr( |
4125 | 0 | &mut self, |
4126 | 0 | expr: &Expr, |
4127 | 0 | deadline: Instant, |
4128 | 0 | depth: usize, |
4129 | 0 | ) -> Result<Value> { |
4130 | | // For now, await just evaluates the expression |
4131 | | // In a full async implementation, this would handle Future resolution |
4132 | 0 | self.evaluate_expr(expr, deadline, depth + 1) |
4133 | 0 | } |
4134 | | |
4135 | | /// Evaluate async block (complexity: 1) |
4136 | 0 | fn evaluate_async_block( |
4137 | 0 | &mut self, |
4138 | 0 | body: &Expr, |
4139 | 0 | deadline: Instant, |
4140 | 0 | depth: usize, |
4141 | 0 | ) -> Result<Value> { |
4142 | | // For REPL purposes, evaluate the async block body synchronously |
4143 | | // In a full async implementation, this would return a Future |
4144 | 0 | self.evaluate_expr(body, deadline, depth + 1) |
4145 | 0 | } |
4146 | | |
4147 | | /// Evaluate try operator (?) (complexity: 1) |
4148 | | /// Evaluate `DataFrame` operation (complexity: 1) |
4149 | 0 | fn evaluate_dataframe_operation() -> Result<Value> { |
4150 | | // DataFrame operations not yet implemented in REPL |
4151 | 0 | bail!("DataFrame operations not yet implemented in REPL") |
4152 | 0 | } |
4153 | | |
4154 | | /// Check if a pattern matches a value and return bindings |
4155 | | /// |
4156 | | /// Returns Some(bindings) if pattern matches, None if it doesn't |
4157 | 0 | fn pattern_matches(value: &Value, pattern: &Pattern) -> Result<Option<HashMap<String, Value>>> { |
4158 | 0 | let mut bindings = HashMap::new(); |
4159 | | |
4160 | 0 | if Self::pattern_matches_recursive(value, pattern, &mut bindings)? { |
4161 | 0 | Ok(Some(bindings)) |
4162 | | } else { |
4163 | 0 | Ok(None) |
4164 | | } |
4165 | 0 | } |
4166 | | |
4167 | | /// Recursive pattern matching helper |
4168 | | /// Match literal patterns (complexity: 4) |
4169 | 0 | fn match_literal_pattern(value: &Value, literal: &Literal) -> bool { |
4170 | 0 | match (value, literal) { |
4171 | 0 | (Value::Unit, Literal::Unit) => true, |
4172 | 0 | (Value::Int(v), Literal::Integer(p)) => v == p, |
4173 | 0 | (Value::Float(v), Literal::Float(p)) => (v - p).abs() < f64::EPSILON, |
4174 | 0 | (Value::String(v), Literal::String(p)) => v == p, |
4175 | 0 | (Value::Bool(v), Literal::Bool(p)) => v == p, |
4176 | 0 | _ => false, |
4177 | | } |
4178 | 0 | } |
4179 | | |
4180 | | /// Match sequence patterns (list or tuple) (complexity: 4) |
4181 | 0 | fn match_sequence_pattern( |
4182 | 0 | values: &[Value], |
4183 | 0 | patterns: &[Pattern], |
4184 | 0 | bindings: &mut HashMap<String, Value>, |
4185 | 0 | ) -> Result<bool> { |
4186 | 0 | if values.len() != patterns.len() { |
4187 | 0 | return Ok(false); |
4188 | 0 | } |
4189 | | |
4190 | 0 | for (value, pattern) in values.iter().zip(patterns.iter()) { |
4191 | 0 | if !Self::pattern_matches_recursive(value, pattern, bindings)? { |
4192 | 0 | return Ok(false); |
4193 | 0 | } |
4194 | | } |
4195 | 0 | Ok(true) |
4196 | 0 | } |
4197 | | |
4198 | | /// Match OR patterns (complexity: 5) |
4199 | 0 | fn match_or_pattern( |
4200 | 0 | value: &Value, |
4201 | 0 | patterns: &[Pattern], |
4202 | 0 | bindings: &mut HashMap<String, Value>, |
4203 | 0 | ) -> Result<bool> { |
4204 | 0 | for pattern in patterns { |
4205 | 0 | let mut temp_bindings = HashMap::new(); |
4206 | 0 | if Self::pattern_matches_recursive(value, pattern, &mut temp_bindings)? { |
4207 | | // Merge bindings |
4208 | 0 | for (name, val) in temp_bindings { |
4209 | 0 | bindings.insert(name, val); |
4210 | 0 | } |
4211 | 0 | return Ok(true); |
4212 | 0 | } |
4213 | | } |
4214 | 0 | Ok(false) |
4215 | 0 | } |
4216 | | |
4217 | | /// Match range patterns (complexity: 5) |
4218 | 0 | fn match_range_pattern( |
4219 | 0 | value: i64, |
4220 | 0 | start: &Pattern, |
4221 | 0 | end: &Pattern, |
4222 | 0 | inclusive: bool, |
4223 | 0 | ) -> Result<bool> { |
4224 | | // For simplicity, only handle integer literal patterns in ranges |
4225 | | if let ( |
4226 | 0 | Pattern::Literal(Literal::Integer(start_val)), |
4227 | 0 | Pattern::Literal(Literal::Integer(end_val)), |
4228 | 0 | ) = (start, end) |
4229 | | { |
4230 | 0 | if inclusive { |
4231 | 0 | Ok(*start_val <= value && value <= *end_val) |
4232 | | } else { |
4233 | 0 | Ok(*start_val <= value && value < *end_val) |
4234 | | } |
4235 | | } else { |
4236 | 0 | bail!("Complex range patterns not yet supported"); |
4237 | | } |
4238 | 0 | } |
4239 | | |
4240 | | /// Match struct patterns (complexity: 7) |
4241 | 0 | fn match_struct_pattern( |
4242 | 0 | obj_fields: &HashMap<String, Value>, |
4243 | 0 | pattern_fields: &[StructPatternField], |
4244 | 0 | bindings: &mut HashMap<String, Value>, |
4245 | 0 | ) -> Result<bool> { |
4246 | 0 | for pattern_field in pattern_fields { |
4247 | 0 | let field_name = &pattern_field.name; |
4248 | | |
4249 | | // Find the corresponding field in the object |
4250 | 0 | if let Some(field_value) = obj_fields.get(field_name) { |
4251 | | // Check if pattern matches (if specified) |
4252 | 0 | if let Some(pattern) = &pattern_field.pattern { |
4253 | 0 | if !Self::pattern_matches_recursive(field_value, pattern, bindings)? { |
4254 | 0 | return Ok(false); |
4255 | 0 | } |
4256 | 0 | } else { |
4257 | 0 | // Shorthand pattern ({ x } instead of { x: x }) |
4258 | 0 | // This creates a binding: x => field_value |
4259 | 0 | bindings.insert(field_name.clone(), field_value.clone()); |
4260 | 0 | } |
4261 | | } else { |
4262 | | // Required field not found in struct |
4263 | 0 | return Ok(false); |
4264 | | } |
4265 | | } |
4266 | 0 | Ok(true) |
4267 | 0 | } |
4268 | | |
4269 | | /// Match qualified name patterns (complexity: 4) |
4270 | 0 | fn match_qualified_name_pattern( |
4271 | 0 | value: &Value, |
4272 | 0 | path: &[String], |
4273 | 0 | ) -> bool { |
4274 | 0 | if let Value::EnumVariant { enum_name, variant_name, data: _ } = value { |
4275 | | // Match if qualified name matches enum variant |
4276 | 0 | if path.len() >= 2 { |
4277 | 0 | let pattern_enum = &path[path.len() - 2]; |
4278 | 0 | let pattern_variant = &path[path.len() - 1]; |
4279 | 0 | enum_name == pattern_enum && variant_name == pattern_variant |
4280 | | } else { |
4281 | 0 | false |
4282 | | } |
4283 | | } else { |
4284 | | // Convert value to string and compare with pattern path |
4285 | 0 | let value_str = format!("{value}"); |
4286 | 0 | let pattern_str = path.join("::"); |
4287 | 0 | value_str == pattern_str |
4288 | | } |
4289 | 0 | } |
4290 | | |
4291 | | /// Match simple patterns (complexity: 4) |
4292 | 0 | fn match_simple_patterns( |
4293 | 0 | value: &Value, |
4294 | 0 | pattern: &Pattern, |
4295 | 0 | bindings: &mut HashMap<String, Value>, |
4296 | 0 | ) -> Option<Result<bool>> { |
4297 | 0 | match pattern { |
4298 | 0 | Pattern::Wildcard => Some(Ok(true)), |
4299 | 0 | Pattern::Literal(literal) => Some(Ok(Self::match_literal_pattern(value, literal))), |
4300 | 0 | Pattern::Identifier(name) => { |
4301 | 0 | bindings.insert(name.clone(), value.clone()); |
4302 | 0 | Some(Ok(true)) |
4303 | | } |
4304 | 0 | _ => None, |
4305 | | } |
4306 | 0 | } |
4307 | | |
4308 | | /// Match collection patterns (complexity: 5) |
4309 | 0 | fn match_collection_patterns( |
4310 | 0 | value: &Value, |
4311 | 0 | pattern: &Pattern, |
4312 | 0 | bindings: &mut HashMap<String, Value>, |
4313 | 0 | ) -> Option<Result<bool>> { |
4314 | 0 | match pattern { |
4315 | 0 | Pattern::List(patterns) => match value { |
4316 | 0 | Value::List(values) => Some(Self::match_sequence_pattern(values, patterns, bindings)), |
4317 | 0 | _ => Some(Ok(false)), |
4318 | | }, |
4319 | 0 | Pattern::Tuple(patterns) => match value { |
4320 | 0 | Value::Tuple(values) => Some(Self::match_sequence_pattern(values, patterns, bindings)), |
4321 | 0 | Value::List(values) => Some(Self::match_sequence_pattern(values, patterns, bindings)), |
4322 | 0 | _ => Some(Ok(false)), |
4323 | | }, |
4324 | 0 | Pattern::Or(patterns) => Some(Self::match_or_pattern(value, patterns, bindings)), |
4325 | 0 | _ => None, |
4326 | | } |
4327 | 0 | } |
4328 | | |
4329 | | /// Match Result/Option patterns (complexity: 6) |
4330 | 0 | fn match_result_option_patterns( |
4331 | 0 | value: &Value, |
4332 | 0 | pattern: &Pattern, |
4333 | 0 | bindings: &mut HashMap<String, Value>, |
4334 | 0 | ) -> Option<Result<bool>> { |
4335 | 0 | match pattern { |
4336 | 0 | Pattern::Ok(inner_pattern) => { |
4337 | 0 | if let Some(ok_value) = Self::extract_result_ok(value) { |
4338 | 0 | Some(Self::pattern_matches_recursive(&ok_value, inner_pattern, bindings)) |
4339 | | } else { |
4340 | 0 | Some(Ok(false)) |
4341 | | } |
4342 | | } |
4343 | 0 | Pattern::Err(inner_pattern) => { |
4344 | 0 | if let Some(err_value) = Self::extract_result_err(value) { |
4345 | 0 | Some(Self::pattern_matches_recursive(&err_value, inner_pattern, bindings)) |
4346 | | } else { |
4347 | 0 | Some(Ok(false)) |
4348 | | } |
4349 | | } |
4350 | 0 | Pattern::Some(inner_pattern) => { |
4351 | 0 | if let Some(some_value) = Self::extract_option_some(value) { |
4352 | 0 | Some(Self::pattern_matches_recursive(&some_value, inner_pattern, bindings)) |
4353 | | } else { |
4354 | 0 | Some(Ok(false)) |
4355 | | } |
4356 | | } |
4357 | 0 | Pattern::None => Some(Ok(Self::is_option_none(value))), |
4358 | 0 | _ => None, |
4359 | | } |
4360 | 0 | } |
4361 | | |
4362 | | /// Match complex patterns (complexity: 5) |
4363 | 0 | fn match_complex_patterns( |
4364 | 0 | value: &Value, |
4365 | 0 | pattern: &Pattern, |
4366 | 0 | bindings: &mut HashMap<String, Value>, |
4367 | 0 | ) -> Option<Result<bool>> { |
4368 | 0 | match pattern { |
4369 | 0 | Pattern::Range { start, end, inclusive } => match value { |
4370 | 0 | Value::Int(v) => Some(Self::match_range_pattern(*v, start, end, *inclusive)), |
4371 | 0 | _ => Some(Ok(false)), |
4372 | | }, |
4373 | 0 | Pattern::Struct { name: _struct_name, fields: pattern_fields, has_rest: _ } => { |
4374 | 0 | match value { |
4375 | 0 | Value::Object(obj_fields) => { |
4376 | 0 | Some(Self::match_struct_pattern(obj_fields, pattern_fields, bindings)) |
4377 | | } |
4378 | 0 | _ => Some(Ok(false)), |
4379 | | } |
4380 | | } |
4381 | 0 | Pattern::QualifiedName(path) => { |
4382 | 0 | Some(Ok(Self::match_qualified_name_pattern(value, path))) |
4383 | | } |
4384 | | Pattern::Rest | Pattern::RestNamed(_) => { |
4385 | 0 | Some(Err(anyhow::anyhow!("Rest patterns are only valid inside struct or tuple patterns"))) |
4386 | | } |
4387 | 0 | _ => None, |
4388 | | } |
4389 | 0 | } |
4390 | | |
4391 | | /// Main pattern matching function (complexity: 6) |
4392 | 0 | fn pattern_matches_recursive( |
4393 | 0 | value: &Value, |
4394 | 0 | pattern: &Pattern, |
4395 | 0 | bindings: &mut HashMap<String, Value>, |
4396 | 0 | ) -> Result<bool> { |
4397 | | // Try simple patterns first |
4398 | 0 | if let Some(result) = Self::match_simple_patterns(value, pattern, bindings) { |
4399 | 0 | return result; |
4400 | 0 | } |
4401 | | |
4402 | | // Try collection patterns |
4403 | 0 | if let Some(result) = Self::match_collection_patterns(value, pattern, bindings) { |
4404 | 0 | return result; |
4405 | 0 | } |
4406 | | |
4407 | | // Try Result/Option patterns |
4408 | 0 | if let Some(result) = Self::match_result_option_patterns(value, pattern, bindings) { |
4409 | 0 | return result; |
4410 | 0 | } |
4411 | | |
4412 | | // Try complex patterns |
4413 | 0 | if let Some(result) = Self::match_complex_patterns(value, pattern, bindings) { |
4414 | 0 | return result; |
4415 | 0 | } |
4416 | | |
4417 | | // Should never reach here as all pattern types are covered |
4418 | 0 | bail!("Unhandled pattern type: {:?}", pattern) |
4419 | 0 | } |
4420 | | |
4421 | | /// Extract value from `Result::Ok` variant (complexity: 4) |
4422 | 0 | fn extract_result_ok(value: &Value) -> Option<Value> { |
4423 | 0 | match value { |
4424 | 0 | Value::EnumVariant { enum_name, variant_name, data } => { |
4425 | 0 | if enum_name == "Result" && variant_name == "Ok" { |
4426 | 0 | data.as_ref()?.first().cloned() |
4427 | | } else { |
4428 | 0 | None |
4429 | | } |
4430 | | } |
4431 | 0 | _ => None, |
4432 | | } |
4433 | 0 | } |
4434 | | |
4435 | | /// Extract value from `Result::Err` variant (complexity: 4) |
4436 | 0 | fn extract_result_err(value: &Value) -> Option<Value> { |
4437 | 0 | match value { |
4438 | 0 | Value::EnumVariant { enum_name, variant_name, data } => { |
4439 | 0 | if enum_name == "Result" && variant_name == "Err" { |
4440 | 0 | data.as_ref()?.first().cloned() |
4441 | | } else { |
4442 | 0 | None |
4443 | | } |
4444 | | } |
4445 | 0 | _ => None, |
4446 | | } |
4447 | 0 | } |
4448 | | |
4449 | | /// Extract value from `Option::Some` variant (complexity: 4) |
4450 | 0 | fn extract_option_some(value: &Value) -> Option<Value> { |
4451 | 0 | match value { |
4452 | 0 | Value::EnumVariant { enum_name, variant_name, data } => { |
4453 | 0 | if enum_name == "Option" && variant_name == "Some" { |
4454 | 0 | data.as_ref()?.first().cloned() |
4455 | | } else { |
4456 | 0 | None |
4457 | | } |
4458 | | } |
4459 | 0 | _ => None, |
4460 | | } |
4461 | 0 | } |
4462 | | |
4463 | | /// Check if value is `Option::None` variant (complexity: 3) |
4464 | 0 | fn is_option_none(value: &Value) -> bool { |
4465 | 0 | match value { |
4466 | 0 | Value::EnumVariant { enum_name, variant_name, data: _ } => { |
4467 | 0 | enum_name == "Option" && variant_name == "None" |
4468 | | } |
4469 | 0 | _ => false, |
4470 | | } |
4471 | 0 | } |
4472 | | |
4473 | | /// Evaluate binary operations |
4474 | | /// Evaluate integer arithmetic operations (complexity: 7) |
4475 | 12 | fn evaluate_integer_arithmetic(a: i64, op: BinaryOp, b: i64) -> Result<Value> { |
4476 | 12 | match op { |
4477 | 7 | BinaryOp::Add => a |
4478 | 7 | .checked_add(b) |
4479 | 7 | .map(Value::Int) |
4480 | 7 | .ok_or_else(|| anyhow::anyhow!("Integer overflow in addition: {} + {}"0 , a, b)), |
4481 | 1 | BinaryOp::Subtract => a |
4482 | 1 | .checked_sub(b) |
4483 | 1 | .map(Value::Int) |
4484 | 1 | .ok_or_else(|| anyhow::anyhow!("Integer overflow in subtraction: {} - {}"0 , a, b)), |
4485 | 4 | BinaryOp::Multiply => a |
4486 | 4 | .checked_mul(b) |
4487 | 4 | .map(Value::Int) |
4488 | 4 | .ok_or_else(|| anyhow::anyhow!("Integer overflow in multiplication: {} * {}"0 , a, b)), |
4489 | | BinaryOp::Divide => { |
4490 | 0 | if b == 0 { |
4491 | 0 | bail!("Division by zero"); |
4492 | 0 | } |
4493 | 0 | Ok(Value::Int(a / b)) |
4494 | | } |
4495 | | BinaryOp::Modulo => { |
4496 | 0 | if b == 0 { |
4497 | 0 | bail!("Modulo by zero"); |
4498 | 0 | } |
4499 | 0 | Ok(Value::Int(a % b)) |
4500 | | } |
4501 | | BinaryOp::Power => { |
4502 | 0 | if b < 0 { |
4503 | 0 | bail!("Negative integer powers not supported in integer context"); |
4504 | 0 | } |
4505 | 0 | let exp = u32::try_from(b).map_err(|_| anyhow::anyhow!("Power exponent too large"))?; |
4506 | 0 | a.checked_pow(exp) |
4507 | 0 | .map(Value::Int) |
4508 | 0 | .ok_or_else(|| anyhow::anyhow!("Integer overflow in power: {} ^ {}", a, b)) |
4509 | | } |
4510 | 0 | _ => bail!("Invalid integer arithmetic operation: {:?}", op), |
4511 | | } |
4512 | 12 | } |
4513 | | |
4514 | | /// Evaluate float arithmetic operations (complexity: 5) |
4515 | 0 | fn evaluate_float_arithmetic(a: f64, op: BinaryOp, b: f64) -> Result<Value> { |
4516 | 0 | match op { |
4517 | 0 | BinaryOp::Add => Ok(Value::Float(a + b)), |
4518 | 0 | BinaryOp::Subtract => Ok(Value::Float(a - b)), |
4519 | 0 | BinaryOp::Multiply => Ok(Value::Float(a * b)), |
4520 | | BinaryOp::Divide => { |
4521 | 0 | if b == 0.0 { |
4522 | 0 | bail!("Division by zero"); |
4523 | 0 | } |
4524 | 0 | Ok(Value::Float(a / b)) |
4525 | | } |
4526 | 0 | BinaryOp::Power => Ok(Value::Float(a.powf(b))), |
4527 | 0 | _ => bail!("Invalid float arithmetic operation: {:?}", op), |
4528 | | } |
4529 | 0 | } |
4530 | | |
4531 | | /// Evaluate comparison operations (complexity: 6) |
4532 | 7 | fn evaluate_comparison(lhs: &Value, op: BinaryOp, rhs: &Value) -> Result<Value> { |
4533 | 7 | match (lhs, rhs) { |
4534 | 7 | (Value::Int(a), Value::Int(b)) => match op { |
4535 | 1 | BinaryOp::Less => Ok(Value::Bool(a < b)), |
4536 | 0 | BinaryOp::LessEqual => Ok(Value::Bool(a <= b)), |
4537 | 2 | BinaryOp::Greater => Ok(Value::Bool(a > b)), |
4538 | 0 | BinaryOp::GreaterEqual => Ok(Value::Bool(a >= b)), |
4539 | 4 | BinaryOp::Equal => Ok(Value::Bool(a == b)), |
4540 | 0 | BinaryOp::NotEqual => Ok(Value::Bool(a != b)), |
4541 | 0 | _ => bail!("Invalid integer comparison: {:?}", op), |
4542 | | }, |
4543 | 0 | (Value::String(a), Value::String(b)) => match op { |
4544 | 0 | BinaryOp::Equal => Ok(Value::Bool(a == b)), |
4545 | 0 | BinaryOp::NotEqual => Ok(Value::Bool(a != b)), |
4546 | 0 | _ => bail!("Invalid string comparison: {:?}", op), |
4547 | | }, |
4548 | 0 | (Value::Bool(a), Value::Bool(b)) => match op { |
4549 | 0 | BinaryOp::Equal => Ok(Value::Bool(a == b)), |
4550 | 0 | BinaryOp::NotEqual => Ok(Value::Bool(a != b)), |
4551 | 0 | _ => bail!("Invalid boolean comparison: {:?}", op), |
4552 | | }, |
4553 | 0 | _ => bail!("Type mismatch in comparison: {:?} vs {:?}", lhs, rhs), |
4554 | | } |
4555 | 7 | } |
4556 | | |
4557 | | /// Evaluate bitwise operations (complexity: 4) |
4558 | 0 | fn evaluate_bitwise(a: i64, op: BinaryOp, b: i64) -> Result<Value> { |
4559 | 0 | match op { |
4560 | 0 | BinaryOp::BitwiseAnd => Ok(Value::Int(a & b)), |
4561 | 0 | BinaryOp::BitwiseOr => Ok(Value::Int(a | b)), |
4562 | 0 | BinaryOp::BitwiseXor => Ok(Value::Int(a ^ b)), |
4563 | 0 | BinaryOp::LeftShift => Ok(Value::Int(a << b)), |
4564 | 0 | _ => bail!("Invalid bitwise operation: {:?}", op), |
4565 | | } |
4566 | 0 | } |
4567 | | |
4568 | 19 | fn evaluate_binary(lhs: &Value, op: BinaryOp, rhs: &Value) -> Result<Value> { |
4569 | 19 | match (lhs, op, rhs) { |
4570 | | // Integer arithmetic |
4571 | 19 | (Value::Int(a), op12 , Value::Int(b)) if matches!7 (op, |
4572 | | BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | |
4573 | | BinaryOp::Divide | BinaryOp::Modulo | BinaryOp::Power) => { |
4574 | 12 | Self::evaluate_integer_arithmetic(*a, op, *b) |
4575 | | } |
4576 | | |
4577 | | // Float arithmetic |
4578 | 0 | (Value::Float(a), op, Value::Float(b)) if matches!(op, |
4579 | | BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | |
4580 | | BinaryOp::Divide | BinaryOp::Power) => { |
4581 | 0 | Self::evaluate_float_arithmetic(*a, op, *b) |
4582 | | } |
4583 | | |
4584 | | // String concatenation |
4585 | 0 | (Value::String(a), BinaryOp::Add, Value::String(b)) => { |
4586 | 0 | Ok(Value::String(format!("{a}{b}"))) |
4587 | | } |
4588 | | |
4589 | | // Comparisons |
4590 | 7 | (lhs, op, rhs) if matches!0 (op, |
4591 | | BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | |
4592 | | BinaryOp::GreaterEqual | BinaryOp::Equal | BinaryOp::NotEqual) => { |
4593 | 7 | Self::evaluate_comparison(lhs, op, rhs) |
4594 | | } |
4595 | | |
4596 | | // Boolean logic |
4597 | 0 | (Value::Bool(a), BinaryOp::And, Value::Bool(b)) => Ok(Value::Bool(*a && *b)), |
4598 | 0 | (Value::Bool(a), BinaryOp::Or, Value::Bool(b)) => Ok(Value::Bool(*a || *b)), |
4599 | | |
4600 | | // Null coalescing |
4601 | 0 | (Value::Nil, BinaryOp::NullCoalesce, rhs) => Ok(rhs.clone()), |
4602 | 0 | (lhs, BinaryOp::NullCoalesce, _) => Ok(lhs.clone()), |
4603 | | |
4604 | | // Bitwise operations |
4605 | 0 | (Value::Int(a), op, Value::Int(b)) if matches!(op, |
4606 | | BinaryOp::BitwiseAnd | BinaryOp::BitwiseOr | |
4607 | | BinaryOp::BitwiseXor | BinaryOp::LeftShift) => { |
4608 | 0 | Self::evaluate_bitwise(*a, op, *b) |
4609 | | } |
4610 | | |
4611 | 0 | _ => bail!( |
4612 | 0 | "Type mismatch in binary operation: {:?} {:?} {:?}", |
4613 | | lhs, |
4614 | | op, |
4615 | | rhs |
4616 | | ), |
4617 | | } |
4618 | 19 | } |
4619 | | |
4620 | | /// Evaluate unary operations |
4621 | 0 | fn evaluate_unary(op: UnaryOp, val: &Value) -> Result<Value> { |
4622 | | use Value::{Bool, Float, Int}; |
4623 | | |
4624 | 0 | match (op, val) { |
4625 | 0 | (UnaryOp::Negate, Int(n)) => Ok(Int(-n)), |
4626 | 0 | (UnaryOp::Negate, Float(f)) => Ok(Float(-f)), |
4627 | 0 | (UnaryOp::Not, Bool(b)) => Ok(Bool(!b)), |
4628 | 0 | (UnaryOp::BitwiseNot, Int(n)) => Ok(Int(!n)), |
4629 | 0 | (UnaryOp::Reference, v) => { |
4630 | | // References in the REPL context just return the value |
4631 | | // In a real implementation, this would create a reference/pointer |
4632 | | // For now, we'll just return the value as references are primarily |
4633 | | // useful for the transpiled code, not the interpreted REPL |
4634 | 0 | Ok(v.clone()) |
4635 | | } |
4636 | 0 | _ => bail!("Type mismatch in unary operation: {:?} {:?}", op, val), |
4637 | | } |
4638 | 0 | } |
4639 | | |
4640 | | /// Run the interactive REPL |
4641 | | /// |
4642 | | /// # Errors |
4643 | | /// |
4644 | | /// Returns an error if: |
4645 | | /// - Readline initialization fails |
4646 | | /// - User input cannot be read |
4647 | | /// - Commands fail to execute |
4648 | 0 | pub fn run(&mut self) -> Result<()> { |
4649 | 0 | println!(); |
4650 | | |
4651 | 0 | let mut rl = self.setup_readline_editor()?; |
4652 | 0 | let mut multiline_state = MultilineState::new(); |
4653 | | |
4654 | | loop { |
4655 | 0 | let prompt = self.format_prompt(multiline_state.in_multiline); |
4656 | 0 | let readline = rl.readline(&prompt); |
4657 | | |
4658 | 0 | match readline { |
4659 | 0 | Ok(line) => { |
4660 | 0 | if self.process_input_line(&line, &mut rl, &mut multiline_state)? { |
4661 | 0 | break; // :quit was executed |
4662 | 0 | } |
4663 | | } |
4664 | 0 | Err(ReadlineError::Interrupted) => { |
4665 | 0 | println!("\nUse :quit to exit"); |
4666 | 0 | } |
4667 | | Err(ReadlineError::Eof) => { |
4668 | 0 | println!("\nGoodbye!"); |
4669 | 0 | break; |
4670 | | } |
4671 | 0 | Err(err) => { |
4672 | 0 | eprintln!("Error: {err:?}"); |
4673 | 0 | break; |
4674 | | } |
4675 | | } |
4676 | | } |
4677 | | |
4678 | | // Save history |
4679 | 0 | let history_path = self.temp_dir.join("history.txt"); |
4680 | 0 | let _ = rl.save_history(&history_path); |
4681 | 0 | Ok(()) |
4682 | 0 | } |
4683 | | |
4684 | | /// Handle REPL commands and return output as string (for testing) |
4685 | | // Helper functions for command handling (complexity < 10 each) |
4686 | | // ======================================================================== |
4687 | | |
4688 | | /// Handle :quit command (complexity: 3) |
4689 | 0 | fn handle_quit_command(&mut self) -> (bool, String) { |
4690 | 0 | if self.mode == ReplMode::Normal { |
4691 | | // In normal mode, :quit exits REPL |
4692 | 0 | (true, String::new()) |
4693 | | } else { |
4694 | | // In a special mode, :quit returns to normal |
4695 | 0 | self.mode = ReplMode::Normal; |
4696 | 0 | (false, "Returned to normal mode".to_string()) |
4697 | | } |
4698 | 0 | } |
4699 | | |
4700 | | /// Handle :history command (complexity: 3) |
4701 | 0 | fn handle_history_command(&self) -> String { |
4702 | 0 | if self.history.is_empty() { |
4703 | 0 | "No history".to_string() |
4704 | | } else { |
4705 | 0 | let mut output = String::new(); |
4706 | 0 | for (i, item) in self.history.iter().enumerate() { |
4707 | 0 | output.push_str(&format!("{}: {}\n", i + 1, item)); |
4708 | 0 | } |
4709 | 0 | output |
4710 | | } |
4711 | 0 | } |
4712 | | |
4713 | | /// Handle :clear command (complexity: 2) |
4714 | 0 | fn handle_clear_command(&mut self) -> String { |
4715 | 0 | self.history.clear(); |
4716 | 0 | self.definitions.clear(); |
4717 | 0 | self.bindings.clear(); |
4718 | 0 | self.result_history.clear(); |
4719 | 0 | "Session cleared".to_string() |
4720 | 0 | } |
4721 | | |
4722 | | /// Handle :bindings/:env command (complexity: 3) |
4723 | 0 | fn handle_bindings_command(&self) -> String { |
4724 | 0 | if self.bindings.is_empty() { |
4725 | 0 | "No bindings".to_string() |
4726 | | } else { |
4727 | 0 | let mut output = String::new(); |
4728 | 0 | for (name, value) in &self.bindings { |
4729 | 0 | output.push_str(&format!("{name}: {value}\n")); |
4730 | 0 | } |
4731 | 0 | output |
4732 | | } |
4733 | 0 | } |
4734 | | |
4735 | | /// Handle :compile command (complexity: 2) |
4736 | 0 | fn handle_compile_command(&mut self) -> String { |
4737 | 0 | match self.compile_session() { |
4738 | 0 | Ok(()) => "Session compiled successfully".to_string(), |
4739 | 0 | Err(e) => format!("Compilation failed: {e}"), |
4740 | | } |
4741 | 0 | } |
4742 | | |
4743 | | /// Handle :load command (complexity: 3) |
4744 | 0 | fn handle_load_command(&mut self, parts: &[&str]) -> String { |
4745 | 0 | if parts.len() == 2 { |
4746 | 0 | match self.load_file(parts[1]) { |
4747 | 0 | Ok(()) => format!("Loaded file: {}", parts[1]), |
4748 | 0 | Err(e) => format!("Failed to load file: {e}"), |
4749 | | } |
4750 | | } else { |
4751 | 0 | "Usage: :load <filename>".to_string() |
4752 | | } |
4753 | 0 | } |
4754 | | |
4755 | | /// Handle :save command (complexity: 3) |
4756 | 0 | fn handle_save_command(&mut self, command: &str) -> String { |
4757 | 0 | let filename = command.strip_prefix(":save").unwrap_or("").trim(); |
4758 | 0 | if filename.is_empty() { |
4759 | 0 | "Usage: :save <filename>".to_string() |
4760 | | } else { |
4761 | 0 | match self.save_session(filename) { |
4762 | 0 | Ok(()) => format!("Session saved to {filename}"), |
4763 | 0 | Err(e) => format!("Failed to save session: {e}"), |
4764 | | } |
4765 | | } |
4766 | 0 | } |
4767 | | |
4768 | | /// Handle :export command (complexity: 3) |
4769 | 0 | fn handle_export_command(&mut self, command: &str) -> String { |
4770 | 0 | let filename = command.strip_prefix(":export").unwrap_or("").trim(); |
4771 | 0 | if filename.is_empty() { |
4772 | 0 | "Usage: :export <filename>".to_string() |
4773 | | } else { |
4774 | 0 | match self.export_session(filename) { |
4775 | 0 | Ok(()) => format!("Session exported to clean script: {filename}"), |
4776 | 0 | Err(e) => format!("Failed to export session: {e}"), |
4777 | | } |
4778 | | } |
4779 | 0 | } |
4780 | | |
4781 | | /// Handle :type command (complexity: 3) |
4782 | 0 | fn handle_type_command(&mut self, command: &str) -> String { |
4783 | 0 | let expr = command.strip_prefix(":type").unwrap_or("").trim(); |
4784 | 0 | if expr.is_empty() { |
4785 | 0 | "Usage: :type <expression>".to_string() |
4786 | | } else { |
4787 | 0 | self.get_type_info_with_bindings(expr) |
4788 | | } |
4789 | 0 | } |
4790 | | |
4791 | | /// Handle :ast command (complexity: 3) |
4792 | 0 | fn handle_ast_command(command: &str) -> String { |
4793 | 0 | let expr = command.strip_prefix(":ast").unwrap_or("").trim(); |
4794 | 0 | if expr.is_empty() { |
4795 | 0 | "Usage: :ast <expression>".to_string() |
4796 | | } else { |
4797 | 0 | Self::get_ast_info(expr) |
4798 | | } |
4799 | 0 | } |
4800 | | |
4801 | | /// Handle :inspect command (complexity: 3) |
4802 | 0 | fn handle_inspect_command(&self, command: &str) -> String { |
4803 | 0 | let var_name = command.strip_prefix(":inspect").unwrap_or("").trim(); |
4804 | 0 | if var_name.is_empty() { |
4805 | 0 | "Usage: :inspect <variable>".to_string() |
4806 | | } else { |
4807 | 0 | self.inspect_value(var_name) |
4808 | | } |
4809 | 0 | } |
4810 | | |
4811 | | /// Handle :reset command (complexity: 2) |
4812 | 0 | fn handle_reset_command(&mut self) -> String { |
4813 | 0 | self.history.clear(); |
4814 | 0 | self.definitions.clear(); |
4815 | 0 | self.bindings.clear(); |
4816 | 0 | self.result_history.clear(); |
4817 | 0 | self.memory.reset(); |
4818 | 0 | "REPL reset to initial state".to_string() |
4819 | 0 | } |
4820 | | |
4821 | | /// Handle :search command (complexity: 3) |
4822 | 0 | fn handle_search_command(&self, command: &str) -> String { |
4823 | 0 | let query = command.strip_prefix(":search").unwrap_or("").trim(); |
4824 | 0 | if query.is_empty() { |
4825 | 0 | "Usage: :search <query>\nSearch through command history with fuzzy matching".to_string() |
4826 | | } else { |
4827 | 0 | self.get_search_results(query) |
4828 | | } |
4829 | 0 | } |
4830 | | |
4831 | | /// Handle mode commands (complexity: 2) |
4832 | 0 | fn handle_mode_command(&mut self, mode: ReplMode) -> String { |
4833 | 0 | self.mode = mode; |
4834 | 0 | format!("Switched to {} mode", mode.prompt()) |
4835 | 0 | } |
4836 | | |
4837 | | /// Dispatch basic REPL commands (complexity: 8) |
4838 | 0 | fn dispatch_basic_commands(&mut self, cmd: &str, parts: &[&str]) -> Option<Result<(bool, String)>> { |
4839 | 0 | match cmd { |
4840 | 0 | ":quit" | ":q" => Some(Ok(self.handle_quit_command())), |
4841 | 0 | ":history" => Some(Ok((false, self.handle_history_command()))), |
4842 | 0 | ":clear" => Some(Ok((false, self.handle_clear_command()))), |
4843 | 0 | ":bindings" | ":env" => Some(Ok((false, self.handle_bindings_command()))), |
4844 | 0 | ":compile" => Some(Ok((false, self.handle_compile_command()))), |
4845 | 0 | ":load" => Some(Ok((false, self.handle_load_command(parts)))), |
4846 | 0 | ":reset" => Some(Ok((false, self.handle_reset_command()))), |
4847 | 0 | _ => None, |
4848 | | } |
4849 | 0 | } |
4850 | | |
4851 | | /// Dispatch analysis commands (complexity: 6) |
4852 | 0 | fn dispatch_analysis_commands(&mut self, cmd: &str, command: &str) -> Option<Result<(bool, String)>> { |
4853 | 0 | if cmd.starts_with(":save") { |
4854 | 0 | Some(Ok((false, self.handle_save_command(command)))) |
4855 | 0 | } else if cmd.starts_with(":export") { |
4856 | 0 | Some(Ok((false, self.handle_export_command(command)))) |
4857 | 0 | } else if cmd.starts_with(":type") { |
4858 | 0 | Some(Ok((false, self.handle_type_command(command)))) |
4859 | 0 | } else if cmd.starts_with(":ast") { |
4860 | 0 | Some(Ok((false, Self::handle_ast_command(command)))) |
4861 | 0 | } else if cmd.starts_with(":inspect") { |
4862 | 0 | Some(Ok((false, self.handle_inspect_command(command)))) |
4863 | 0 | } else if cmd.starts_with(":search") { |
4864 | 0 | Some(Ok((false, self.handle_search_command(command)))) |
4865 | | } else { |
4866 | 0 | None |
4867 | | } |
4868 | 0 | } |
4869 | | |
4870 | | /// Dispatch mode switching commands (complexity: 8) |
4871 | 0 | fn dispatch_mode_commands(&mut self, cmd: &str, parts: &[&str]) -> Option<Result<(bool, String)>> { |
4872 | 0 | match cmd { |
4873 | 0 | ":normal" => Some(Ok((false, self.handle_mode_command(ReplMode::Normal)))), |
4874 | 0 | ":shell" => Some(Ok((false, self.handle_mode_command(ReplMode::Shell)))), |
4875 | 0 | ":pkg" => Some(Ok((false, self.handle_mode_command(ReplMode::Pkg)))), |
4876 | 0 | ":sql" => Some(Ok((false, self.handle_mode_command(ReplMode::Sql)))), |
4877 | 0 | ":math" => Some(Ok((false, self.handle_mode_command(ReplMode::Math)))), |
4878 | 0 | ":debug" => Some(Ok((false, self.handle_mode_command(ReplMode::Debug)))), |
4879 | 0 | ":time" => Some(Ok((false, self.handle_mode_command(ReplMode::Time)))), |
4880 | 0 | ":test" => Some(Ok((false, self.handle_mode_command(ReplMode::Test)))), |
4881 | 0 | ":exit" => Some(Ok((false, self.handle_mode_command(ReplMode::Normal)))), |
4882 | 0 | ":help" | ":h" if parts.len() == 1 => { |
4883 | 0 | self.mode = ReplMode::Help; |
4884 | 0 | Some(self.show_help_menu().map(|output| (false, output))) |
4885 | | }, |
4886 | 0 | ":help" if parts.len() > 1 => { |
4887 | 0 | let topic = parts[1]; |
4888 | 0 | Some(self.handle_help_command(topic).map(|output| (false, output))) |
4889 | | }, |
4890 | 0 | ":modes" => { |
4891 | 0 | let output = Self::get_modes_list(); |
4892 | 0 | Some(Ok((false, output))) |
4893 | | }, |
4894 | 0 | _ => None, |
4895 | | } |
4896 | 0 | } |
4897 | | |
4898 | | /// Get list of available modes (complexity: 1) |
4899 | 0 | fn get_modes_list() -> String { |
4900 | 0 | let mut output = "Available modes:\n".to_string(); |
4901 | 0 | output.push_str(" normal - Standard Ruchy evaluation\n"); |
4902 | 0 | output.push_str(" shell - Execute shell commands\n"); |
4903 | 0 | output.push_str(" pkg - Package management\n"); |
4904 | 0 | output.push_str(" help - Interactive help\n"); |
4905 | 0 | output.push_str(" sql - SQL queries\n"); |
4906 | 0 | output.push_str(" math - Mathematical expressions\n"); |
4907 | 0 | output.push_str(" debug - Debug information with traces\n"); |
4908 | 0 | output.push_str(" time - Execution timing\n"); |
4909 | 0 | output.push_str(" test - Assertions and table tests\n"); |
4910 | 0 | output.push_str("\nUse :mode_name to switch modes, :normal or :exit to return"); |
4911 | 0 | output |
4912 | 0 | } |
4913 | | |
4914 | | /// Main command handler with output (complexity: 6) |
4915 | 0 | fn handle_command_with_output(&mut self, command: &str) -> Result<(bool, String)> { |
4916 | 0 | let parts: Vec<&str> = command.split_whitespace().collect(); |
4917 | 0 | let first_cmd = parts.first().copied().unwrap_or(""); |
4918 | | |
4919 | | // Try basic commands |
4920 | 0 | if let Some(result) = self.dispatch_basic_commands(first_cmd, &parts) { |
4921 | 0 | return result; |
4922 | 0 | } |
4923 | | |
4924 | | // Try analysis commands |
4925 | 0 | if let Some(result) = self.dispatch_analysis_commands(first_cmd, command) { |
4926 | 0 | return result; |
4927 | 0 | } |
4928 | | |
4929 | | // Try mode commands |
4930 | 0 | if let Some(result) = self.dispatch_mode_commands(first_cmd, &parts) { |
4931 | 0 | return result; |
4932 | 0 | } |
4933 | | |
4934 | | // Unknown command |
4935 | 0 | Ok((false, format!("Unknown command: {command}\nType :help for available commands"))) |
4936 | 0 | } |
4937 | | |
4938 | | /// Handle session management commands (complexity: 5) |
4939 | 0 | fn handle_session_commands(&mut self, cmd: &str) -> Option<Result<bool>> { |
4940 | 0 | match cmd { |
4941 | 0 | ":history" => { |
4942 | 0 | for (i, item) in self.history.iter().enumerate() { |
4943 | 0 | println!("{}: {}", i + 1, item); |
4944 | 0 | } |
4945 | 0 | Some(Ok(false)) |
4946 | | } |
4947 | 0 | ":clear" => { |
4948 | 0 | self.history.clear(); |
4949 | 0 | self.definitions.clear(); |
4950 | 0 | self.bindings.clear(); |
4951 | 0 | println!("Session cleared"); |
4952 | 0 | Some(Ok(false)) |
4953 | | } |
4954 | 0 | ":reset" => { |
4955 | 0 | self.history.clear(); |
4956 | 0 | self.definitions.clear(); |
4957 | 0 | self.bindings.clear(); |
4958 | 0 | self.memory.reset(); |
4959 | 0 | println!("REPL reset to initial state"); |
4960 | 0 | Some(Ok(false)) |
4961 | | } |
4962 | 0 | ":compile" => Some(self.compile_session().map(|()| false)), |
4963 | 0 | _ => None, |
4964 | | } |
4965 | 0 | } |
4966 | | |
4967 | | /// Handle inspection commands (complexity: 4) |
4968 | 0 | fn handle_inspection_commands(&mut self, command: &str) -> Option<Result<bool>> { |
4969 | 0 | if command.starts_with(":type") { |
4970 | 0 | let expr = command.strip_prefix(":type").unwrap_or("").trim(); |
4971 | 0 | if expr.is_empty() { |
4972 | 0 | println!("Usage: :type <expression>"); |
4973 | 0 | } else { |
4974 | 0 | Self::show_type(expr); |
4975 | 0 | } |
4976 | 0 | Some(Ok(false)) |
4977 | 0 | } else if command.starts_with(":ast") { |
4978 | 0 | let expr = command.strip_prefix(":ast").unwrap_or("").trim(); |
4979 | 0 | if expr.is_empty() { |
4980 | 0 | println!("Usage: :ast <expression>"); |
4981 | 0 | } else { |
4982 | 0 | Self::show_ast(expr); |
4983 | 0 | } |
4984 | 0 | Some(Ok(false)) |
4985 | 0 | } else if command.starts_with(":inspect") { |
4986 | 0 | let var_name = command.strip_prefix(":inspect").unwrap_or("").trim(); |
4987 | 0 | if var_name.is_empty() { |
4988 | 0 | println!("Usage: :inspect <variable>"); |
4989 | 0 | } else { |
4990 | 0 | println!("{}", self.inspect_value(var_name)); |
4991 | 0 | } |
4992 | 0 | Some(Ok(false)) |
4993 | 0 | } else if command == ":bindings" || command == ":env" { |
4994 | 0 | if self.bindings.is_empty() { |
4995 | 0 | println!("No bindings"); |
4996 | 0 | } else { |
4997 | 0 | for (name, value) in &self.bindings { |
4998 | 0 | println!("{name}: {value}"); |
4999 | 0 | } |
5000 | | } |
5001 | 0 | Some(Ok(false)) |
5002 | | } else { |
5003 | 0 | None |
5004 | | } |
5005 | 0 | } |
5006 | | |
5007 | | /// Handle file operations (complexity: 4) |
5008 | 0 | fn handle_file_operations(&mut self, command: &str, parts: &[&str]) -> Option<Result<bool>> { |
5009 | 0 | if command.starts_with(":load") && parts.len() == 2 { |
5010 | 0 | Some(self.load_file(parts[1]).map(|()| false)) |
5011 | 0 | } else if command.starts_with(":save") { |
5012 | 0 | let filename = command.strip_prefix(":save").unwrap_or("").trim(); |
5013 | 0 | if filename.is_empty() { |
5014 | 0 | println!("Usage: :save <filename>"); |
5015 | 0 | println!("Save current session to a file"); |
5016 | 0 | } else { |
5017 | 0 | match self.save_session(filename) { |
5018 | 0 | Ok(()) => println!("Session saved to {}", filename.bright_green()), |
5019 | 0 | Err(e) => eprintln!("Failed to save session: {e}"), |
5020 | | } |
5021 | | } |
5022 | 0 | Some(Ok(false)) |
5023 | 0 | } else if command.starts_with(":search") { |
5024 | 0 | let query = command.strip_prefix(":search").unwrap_or("").trim(); |
5025 | 0 | if query.is_empty() { |
5026 | 0 | println!("Usage: :search <query>"); |
5027 | 0 | println!("Search through command history with fuzzy matching"); |
5028 | 0 | } else { |
5029 | 0 | self.search_history(query); |
5030 | 0 | } |
5031 | 0 | Some(Ok(false)) |
5032 | | } else { |
5033 | 0 | None |
5034 | | } |
5035 | 0 | } |
5036 | | |
5037 | | /// Handle REPL commands (public for testing) (complexity: 7) |
5038 | | /// |
5039 | | /// # Errors |
5040 | | /// |
5041 | | /// Returns an error if command execution fails |
5042 | 0 | pub fn handle_command(&mut self, command: &str) -> Result<bool> { |
5043 | 0 | let parts: Vec<&str> = command.split_whitespace().collect(); |
5044 | 0 | let first_cmd = parts.first().copied().unwrap_or(""); |
5045 | | |
5046 | | // Check for quit command |
5047 | 0 | if first_cmd == ":quit" || first_cmd == ":q" { |
5048 | 0 | return Ok(true); |
5049 | 0 | } |
5050 | | |
5051 | | // Check for help command |
5052 | 0 | if first_cmd == ":help" || first_cmd == ":h" { |
5053 | 0 | Self::print_help(); |
5054 | 0 | return Ok(false); |
5055 | 0 | } |
5056 | | |
5057 | | // Try session management commands |
5058 | 0 | if let Some(result) = self.handle_session_commands(first_cmd) { |
5059 | 0 | return result; |
5060 | 0 | } |
5061 | | |
5062 | | // Try inspection commands |
5063 | 0 | if let Some(result) = self.handle_inspection_commands(command) { |
5064 | 0 | return result; |
5065 | 0 | } |
5066 | | |
5067 | | // Try file operations |
5068 | 0 | if let Some(result) = self.handle_file_operations(command, &parts) { |
5069 | 0 | return result; |
5070 | 0 | } |
5071 | | |
5072 | | // Unknown command |
5073 | 0 | eprintln!("Unknown command: {command}"); |
5074 | 0 | Self::print_help(); |
5075 | 0 | Ok(false) |
5076 | 0 | } |
5077 | | |
5078 | | /// Get help text as string |
5079 | 0 | fn get_help_text() -> String { |
5080 | 0 | let mut help = String::new(); |
5081 | 0 | help.push_str("Available commands:\n"); |
5082 | 0 | help.push_str(" :help, :h - Show this help message\n"); |
5083 | 0 | help.push_str(" :quit, :q - Exit the REPL\n"); |
5084 | 0 | help.push_str(" :history - Show evaluation history\n"); |
5085 | 0 | help.push_str(" :search <query> - Search history with fuzzy matching\n"); |
5086 | 0 | help.push_str(" :clear - Clear definitions and history\n"); |
5087 | 0 | help.push_str(" :reset - Full reset to initial state\n"); |
5088 | 0 | help.push_str(" :bindings, :env - Show current variable bindings\n"); |
5089 | 0 | help.push_str(" :type <expr> - Show type of expression\n"); |
5090 | 0 | help.push_str(" :ast <expr> - Show AST of expression\n"); |
5091 | 0 | help.push_str(" :inspect <var> - Inspect a variable in detail\n"); |
5092 | 0 | help.push_str(" :compile - Compile and run the session\n"); |
5093 | 0 | help.push_str(" :load <file> - Load and evaluate a file\n"); |
5094 | 0 | help.push_str(" :save <file> - Save session to file\n"); |
5095 | 0 | help.push_str(" :export <file> - Export session to clean script\n"); |
5096 | 0 | help |
5097 | 0 | } |
5098 | | |
5099 | | /// Print help message |
5100 | 0 | fn print_help() { |
5101 | 0 | println!("{}", Self::get_help_text()); |
5102 | 0 | } |
5103 | | |
5104 | | /// Get type information as string |
5105 | 0 | fn get_type_info(expr: &str) -> String { |
5106 | 0 | match Parser::new(expr).parse() { |
5107 | 0 | Ok(ast) => { |
5108 | | // Create an inference context for type checking |
5109 | 0 | let mut ctx = crate::middleend::InferenceContext::new(); |
5110 | | |
5111 | | // Infer the type |
5112 | 0 | match ctx.infer(&ast) { |
5113 | 0 | Ok(ty) => format!("Type: {ty}"), |
5114 | 0 | Err(e) => format!("Type inference error: {e}"), |
5115 | | } |
5116 | | } |
5117 | 0 | Err(e) => format!("Parse error: {e}"), |
5118 | | } |
5119 | 0 | } |
5120 | | |
5121 | | /// Get type information with REPL bindings context |
5122 | 0 | fn get_type_info_with_bindings(&self, expr: &str) -> String { |
5123 | | // If the expression is a simple identifier, check bindings first |
5124 | 0 | if let Ok(_) = Parser::new(expr).parse() { |
5125 | 0 | if let Some(value) = self.bindings.get(expr) { |
5126 | | // Infer type from the value |
5127 | 0 | let type_name = match value { |
5128 | 0 | Value::Int(_) => "Integer", |
5129 | 0 | Value::Float(_) => "Float", |
5130 | 0 | Value::String(_) => "String", |
5131 | 0 | Value::Bool(_) => "Bool", |
5132 | 0 | Value::List(_) => "List", |
5133 | 0 | Value::Function { .. } => "Function", |
5134 | 0 | Value::Lambda { .. } => "Lambda", |
5135 | 0 | Value::Object(_) => "Object", |
5136 | 0 | Value::Tuple(_) => "Tuple", |
5137 | 0 | Value::Char(_) => "Char", |
5138 | 0 | Value::DataFrame { .. } => "DataFrame", |
5139 | 0 | Value::HashMap(_) => "HashMap", |
5140 | 0 | Value::HashSet(_) => "HashSet", |
5141 | 0 | Value::Range { .. } => "Range", |
5142 | 0 | Value::EnumVariant { enum_name, variant_name, .. } => { |
5143 | 0 | &format!("{enum_name}::{variant_name}") |
5144 | | } |
5145 | 0 | Value::Unit => "Unit", |
5146 | 0 | Value::Nil => "Nil" |
5147 | | }; |
5148 | 0 | return format!("Type: {type_name}"); |
5149 | 0 | } |
5150 | 0 | } |
5151 | | |
5152 | | // Fall back to regular type inference |
5153 | 0 | Self::get_type_info(expr) |
5154 | 0 | } |
5155 | | |
5156 | | /// Get AST information as string |
5157 | 0 | fn get_ast_info(expr: &str) -> String { |
5158 | 0 | match Parser::new(expr).parse() { |
5159 | 0 | Ok(ast) => format!("{ast:#?}"), |
5160 | 0 | Err(e) => format!("Parse error: {e}"), |
5161 | | } |
5162 | 0 | } |
5163 | | |
5164 | | /// Get search results as string |
5165 | 0 | fn get_search_results(&self, query: &str) -> String { |
5166 | 0 | let mut results = Vec::new(); |
5167 | 0 | let query_lower = query.to_lowercase(); |
5168 | | |
5169 | 0 | for (i, item) in self.history.iter().enumerate() { |
5170 | 0 | if item.to_lowercase().contains(&query_lower) { |
5171 | 0 | results.push(format!("{}: {}", i + 1, item)); |
5172 | 0 | } |
5173 | | } |
5174 | | |
5175 | 0 | if results.is_empty() { |
5176 | 0 | format!("No matches found for '{query}'") |
5177 | | } else { |
5178 | 0 | results.join("\n") |
5179 | | } |
5180 | 0 | } |
5181 | | |
5182 | | /// Execute a shell command and return its output |
5183 | 0 | fn execute_shell_command(&self, command: &str) -> Result<String> { |
5184 | | use std::process::Command; |
5185 | | |
5186 | | // Execute command through shell |
5187 | 0 | let output = Command::new("sh") |
5188 | 0 | .arg("-c") |
5189 | 0 | .arg(command) |
5190 | 0 | .output() |
5191 | 0 | .context(format!("Failed to execute shell command: {command}"))?; |
5192 | | |
5193 | | // Combine stdout and stderr |
5194 | 0 | let stdout = String::from_utf8_lossy(&output.stdout); |
5195 | 0 | let stderr = String::from_utf8_lossy(&output.stderr); |
5196 | | |
5197 | 0 | if !output.status.success() { |
5198 | | // If command failed, return error with stderr |
5199 | 0 | if !stderr.is_empty() { |
5200 | 0 | bail!("Shell command failed: {}", stderr); |
5201 | 0 | } |
5202 | 0 | bail!("Shell command failed with exit code: {:?}", output.status.code()); |
5203 | 0 | } |
5204 | | |
5205 | | // Return stdout (stderr is usually empty for successful commands) |
5206 | 0 | Ok(stdout.trim_end().to_string()) |
5207 | 0 | } |
5208 | | |
5209 | | /// Basic introspection with single ? |
5210 | 0 | fn basic_introspection(&self, target: &str) -> Result<String> { |
5211 | | // Check if target exists in bindings |
5212 | 0 | if let Some(value) = self.bindings.get(target) { |
5213 | 0 | let type_name = self.get_value_type_name(value); |
5214 | 0 | let value_str = self.format_value_brief(value); |
5215 | 0 | return Ok(format!("Type: {type_name}\nValue: {value_str}")); |
5216 | 0 | } |
5217 | | |
5218 | | // Check if it's a builtin function |
5219 | 0 | if self.is_builtin_function(target) { |
5220 | 0 | return Ok(format!("Type: Builtin Function\nName: {target}")); |
5221 | 0 | } |
5222 | | |
5223 | | // Try to evaluate the expression and introspect result |
5224 | 0 | if let Ok(ast) = Parser::new(target).parse() { |
5225 | | // Try to get type information |
5226 | 0 | let mut ctx = crate::middleend::InferenceContext::new(); |
5227 | 0 | if let Ok(ty) = ctx.infer(&ast) { |
5228 | 0 | return Ok(format!("Type: {ty}")); |
5229 | 0 | } |
5230 | 0 | } |
5231 | | |
5232 | 0 | bail!("'{}' is not defined or cannot be introspected", target) |
5233 | 0 | } |
5234 | | |
5235 | | /// Detailed introspection with double ?? |
5236 | 0 | fn detailed_introspection(&self, target: &str) -> Result<String> { |
5237 | | // Check if target exists in bindings |
5238 | 0 | if let Some(value) = self.bindings.get(target) { |
5239 | 0 | return Ok(self.format_detailed_introspection(target, value)); |
5240 | 0 | } |
5241 | | |
5242 | | // Check if it's a builtin function |
5243 | 0 | if self.is_builtin_function(target) { |
5244 | 0 | return Ok(self.format_builtin_help(target)); |
5245 | 0 | } |
5246 | | |
5247 | 0 | bail!("'{}' is not defined or cannot be introspected", target) |
5248 | 0 | } |
5249 | | |
5250 | | /// Check if a name is a builtin function |
5251 | 0 | fn is_builtin_function(&self, name: &str) -> bool { |
5252 | 0 | matches!(name, "println" | "print" | "len" | "push" | "pop" | "insert" | |
5253 | 0 | "remove" | "clear" | "contains" | "index_of" | "slice" | |
5254 | 0 | "split" | "join" | "trim" | "to_upper" | "to_lower" | |
5255 | 0 | "replace" | "starts_with" | "ends_with" | "parse" | |
5256 | 0 | "type" | "str" | "int" | "float" | "bool" | |
5257 | 0 | "sqrt" | "pow" | "abs" | "min" | "max" | "floor" | "ceil" | "round") |
5258 | 0 | } |
5259 | | |
5260 | | /// Get type name for a value |
5261 | 0 | fn get_value_type_name(&self, value: &Value) -> &str { |
5262 | 0 | match value { |
5263 | 0 | Value::Int(_) => "Integer", |
5264 | 0 | Value::Float(_) => "Float", |
5265 | 0 | Value::String(_) => "String", |
5266 | 0 | Value::Bool(_) => "Bool", |
5267 | 0 | Value::Char(_) => "Char", |
5268 | 0 | Value::List(_) => "List", |
5269 | 0 | Value::Tuple(_) => "Tuple", |
5270 | 0 | Value::Function { .. } => "Function", |
5271 | 0 | Value::Lambda { .. } => "Lambda", |
5272 | 0 | Value::Object(_) => "Object", |
5273 | 0 | Value::HashMap(_) => "HashMap", |
5274 | 0 | Value::HashSet(_) => "HashSet", |
5275 | 0 | Value::Range { .. } => "Range", |
5276 | 0 | Value::DataFrame { .. } => "DataFrame", |
5277 | 0 | Value::EnumVariant { enum_name, variant_name, .. } => { |
5278 | | // Return a static str by leaking - safe for REPL lifetime |
5279 | 0 | Box::leak(format!("{enum_name}::{variant_name}").into_boxed_str()) |
5280 | | } |
5281 | 0 | Value::Unit => "Unit", |
5282 | 0 | Value::Nil => "Nil", |
5283 | | } |
5284 | 0 | } |
5285 | | |
5286 | | /// Format value briefly for introspection |
5287 | 0 | fn format_value_brief(&self, value: &Value) -> String { |
5288 | 0 | match value { |
5289 | 0 | Value::List(items) => format!("[{} items]", items.len()), |
5290 | 0 | Value::Object(fields) => { |
5291 | 0 | let field_names: Vec<_> = fields.keys().cloned().collect(); |
5292 | 0 | format!("{{{}}}",field_names.join(", ")) |
5293 | | } |
5294 | 0 | Value::Function { name, params, .. } => { |
5295 | 0 | format!("fn {}({})", name, params.join(", ")) |
5296 | | } |
5297 | 0 | Value::Lambda { params, .. } => { |
5298 | 0 | format!("|{}| -> ...", params.join(", ")) |
5299 | | } |
5300 | 0 | _ => value.to_string(), |
5301 | | } |
5302 | 0 | } |
5303 | | |
5304 | | /// Format detailed introspection output |
5305 | 0 | fn format_detailed_introspection(&self, name: &str, value: &Value) -> String { |
5306 | 0 | let mut output = String::new(); |
5307 | 0 | output.push_str(&format!("Name: {name}\n")); |
5308 | 0 | output.push_str(&format!("Type: {}\n", self.get_value_type_name(value))); |
5309 | | |
5310 | 0 | match value { |
5311 | 0 | Value::Function { name: fn_name, params, body } => { |
5312 | 0 | output.push_str(&format!("Source: fn {}({}) {{\n", fn_name, params.join(", "))); |
5313 | 0 | output.push_str(&format!(" {}\n", self.format_expr_source(body))); |
5314 | 0 | output.push_str("}\n"); |
5315 | 0 | output.push_str(&format!("Parameters: {}\n", params.join(", "))); |
5316 | 0 | } |
5317 | 0 | Value::Lambda { params, body } => { |
5318 | 0 | output.push_str(&format!("Source: |{}| {{\n", params.join(", "))); |
5319 | 0 | output.push_str(&format!(" {}\n", self.format_expr_source(body))); |
5320 | 0 | output.push_str("}\n"); |
5321 | 0 | output.push_str(&format!("Parameters: {}\n", params.join(", "))); |
5322 | 0 | } |
5323 | 0 | Value::Object(fields) => { |
5324 | 0 | output.push_str("Fields:\n"); |
5325 | 0 | for (key, val) in fields { |
5326 | 0 | output.push_str(&format!(" {}: {}\n", key, self.get_value_type_name(val))); |
5327 | 0 | } |
5328 | | } |
5329 | 0 | Value::List(items) => { |
5330 | 0 | output.push_str(&format!("Length: {}\n", items.len())); |
5331 | 0 | if !items.is_empty() { |
5332 | 0 | output.push_str(&format!("First: {}\n", items[0])); |
5333 | 0 | if items.len() > 1 { |
5334 | 0 | output.push_str(&format!("Last: {}\n", items[items.len() - 1])); |
5335 | 0 | } |
5336 | 0 | } |
5337 | | } |
5338 | 0 | _ => { |
5339 | 0 | output.push_str(&format!("Value: {value}\n")); |
5340 | 0 | } |
5341 | | } |
5342 | | |
5343 | 0 | output |
5344 | 0 | } |
5345 | | |
5346 | | /// Format expression source code |
5347 | 0 | fn format_expr_source(&self, expr: &Expr) -> String { |
5348 | | // Format the expression in a more readable way |
5349 | 0 | self.expr_to_source_string(expr, 0) |
5350 | 0 | } |
5351 | | |
5352 | | /// Convert expression to source string |
5353 | 0 | fn expr_to_source_string(&self, expr: &Expr, indent: usize) -> String { |
5354 | | use crate::frontend::ast::ExprKind; |
5355 | 0 | let indent_str = " ".repeat(indent); |
5356 | | |
5357 | 0 | match &expr.kind { |
5358 | 0 | ExprKind::Binary { left, op, right } => { |
5359 | 0 | format!("{} {} {}", |
5360 | 0 | self.expr_to_source_string(left, 0), |
5361 | | op, |
5362 | 0 | self.expr_to_source_string(right, 0)) |
5363 | | } |
5364 | 0 | ExprKind::If { condition, then_branch, else_branch } => { |
5365 | 0 | let mut s = format!("if {} {{\n{}{}\n{}}}", |
5366 | 0 | self.expr_to_source_string(condition, 0), |
5367 | 0 | " ".repeat(indent + 1), |
5368 | 0 | self.expr_to_source_string(then_branch, indent + 1), |
5369 | | indent_str); |
5370 | 0 | if let Some(else_b) = else_branch { |
5371 | 0 | s.push_str(&format!(" else {{\n{}{}\n{}}}", |
5372 | 0 | " ".repeat(indent + 1), |
5373 | 0 | self.expr_to_source_string(else_b, indent + 1), |
5374 | 0 | indent_str)); |
5375 | 0 | } |
5376 | 0 | s |
5377 | | } |
5378 | 0 | ExprKind::Call { func, args } => { |
5379 | 0 | if let ExprKind::Identifier(name) = &func.kind { |
5380 | 0 | format!("{}({})", name, |
5381 | 0 | args.iter() |
5382 | 0 | .map(|a| self.expr_to_source_string(a, 0)) |
5383 | 0 | .collect::<Vec<_>>() |
5384 | 0 | .join(", ")) |
5385 | | } else { |
5386 | 0 | "(call ...)".to_string() |
5387 | | } |
5388 | | } |
5389 | 0 | ExprKind::Identifier(name) => name.clone(), |
5390 | 0 | ExprKind::Literal(lit) => format!("{lit:?}"), |
5391 | 0 | ExprKind::Block(exprs) => { |
5392 | 0 | if exprs.len() == 1 { |
5393 | 0 | self.expr_to_source_string(&exprs[0], indent) |
5394 | | } else { |
5395 | 0 | exprs.iter() |
5396 | 0 | .map(|e| self.expr_to_source_string(e, indent)) |
5397 | 0 | .collect::<Vec<_>>() |
5398 | 0 | .join("; ") |
5399 | | } |
5400 | | } |
5401 | 0 | _ => format!("{:?}", expr.kind).chars().take(50).collect() |
5402 | | } |
5403 | 0 | } |
5404 | | |
5405 | | /// Format help for builtin functions |
5406 | 0 | fn format_builtin_help(&self, name: &str) -> String { |
5407 | 0 | match name { |
5408 | 0 | "println" => "println(value)\n Prints a value to stdout with newline\n Parameters: value - Any value to print".to_string(), |
5409 | 0 | "print" => "print(value)\n Prints a value to stdout without newline\n Parameters: value - Any value to print".to_string(), |
5410 | 0 | "len" => "len(collection)\n Returns the length of a collection\n Parameters: collection - List, String, or other collection".to_string(), |
5411 | 0 | "type" => "type(value)\n Returns the type of a value\n Parameters: value - Any value".to_string(), |
5412 | 0 | "str" => "str(value)\n Converts a value to string\n Parameters: value - Any value to convert".to_string(), |
5413 | 0 | _ => format!("{name}\n Builtin function\n (documentation not available)"), |
5414 | | } |
5415 | 0 | } |
5416 | | |
5417 | | /// Evaluate `type()` function |
5418 | 0 | fn evaluate_type_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
5419 | 0 | if args.len() != 1 { |
5420 | 0 | bail!("type() expects 1 argument, got {}", args.len()); |
5421 | 0 | } |
5422 | | |
5423 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
5424 | 0 | let type_name = self.get_value_type_name(&value); |
5425 | 0 | Ok(Value::String(type_name.to_string())) |
5426 | 0 | } |
5427 | | |
5428 | | /// Evaluate `summary()` function |
5429 | 0 | fn evaluate_summary_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
5430 | 0 | if args.len() != 1 { |
5431 | 0 | bail!("summary() expects 1 argument, got {}", args.len()); |
5432 | 0 | } |
5433 | | |
5434 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
5435 | 0 | let summary = match &value { |
5436 | 0 | Value::List(items) => format!("List with {} items", items.len()), |
5437 | 0 | Value::Object(fields) => format!("Object with {} fields", fields.len()), |
5438 | 0 | Value::String(s) => format!("String of length {}", s.len()), |
5439 | 0 | Value::DataFrame { columns } => format!("DataFrame with {} columns", columns.len()), |
5440 | 0 | _ => format!("{} value", self.get_value_type_name(&value)), |
5441 | | }; |
5442 | 0 | Ok(Value::String(summary)) |
5443 | 0 | } |
5444 | | |
5445 | | /// Evaluate `dir()` function |
5446 | 0 | fn evaluate_dir_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
5447 | 0 | if args.len() != 1 { |
5448 | 0 | bail!("dir() expects 1 argument, got {}", args.len()); |
5449 | 0 | } |
5450 | | |
5451 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
5452 | 0 | let members = match value { |
5453 | 0 | Value::Object(fields) => { |
5454 | 0 | fields.keys().cloned().collect::<Vec<_>>() |
5455 | | } |
5456 | 0 | _ => vec![], |
5457 | | }; |
5458 | | |
5459 | 0 | Ok(Value::String(members.join(", "))) |
5460 | 0 | } |
5461 | | |
5462 | | /// Evaluate `help()` function |
5463 | 0 | fn evaluate_help_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
5464 | 0 | if args.len() != 1 { |
5465 | 0 | bail!("help() expects 1 argument, got {}", args.len()); |
5466 | 0 | } |
5467 | | |
5468 | | // Check if it's a builtin function first |
5469 | 0 | if let ExprKind::Identifier(name) = &args[0].kind { |
5470 | 0 | if self.is_builtin_function(name) { |
5471 | 0 | return Ok(Value::String(self.format_builtin_help(name))); |
5472 | 0 | } |
5473 | 0 | } |
5474 | | |
5475 | | // Try to evaluate the argument and get its type |
5476 | 0 | match self.evaluate_expr(&args[0], deadline, depth + 1) { |
5477 | 0 | Ok(value) => { |
5478 | 0 | let help_text = match value { |
5479 | 0 | Value::Function { name, params, .. } => { |
5480 | 0 | format!("Function: {}\nParameters: {}", name, params.join(", ")) |
5481 | | } |
5482 | 0 | Value::Lambda { params, .. } => { |
5483 | 0 | format!("Lambda function\nParameters: {}", params.join(", ")) |
5484 | | } |
5485 | | _ => { |
5486 | 0 | format!("Type: {}", self.get_value_type_name(&value)) |
5487 | | } |
5488 | | }; |
5489 | 0 | Ok(Value::String(help_text)) |
5490 | | } |
5491 | | Err(_) => { |
5492 | | // If evaluation fails, just return a generic message |
5493 | 0 | Ok(Value::String("No help available for this value".to_string())) |
5494 | | } |
5495 | | } |
5496 | 0 | } |
5497 | | |
5498 | | /// Evaluate `whos()` function - lists all variables with types |
5499 | 0 | fn evaluate_whos_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
5500 | 0 | let filter = if args.len() == 1 { |
5501 | | // Get type filter |
5502 | 0 | let val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
5503 | 0 | if let Value::String(s) = val { |
5504 | 0 | Some(s) |
5505 | | } else { |
5506 | 0 | None |
5507 | | } |
5508 | | } else { |
5509 | 0 | None |
5510 | | }; |
5511 | | |
5512 | 0 | let mut output = Vec::new(); |
5513 | 0 | for (name, value) in &self.bindings { |
5514 | 0 | let type_name = self.get_value_type_name(value); |
5515 | 0 | if let Some(ref filter_type) = filter { |
5516 | 0 | if type_name != filter_type { |
5517 | 0 | continue; |
5518 | 0 | } |
5519 | 0 | } |
5520 | 0 | output.push(format!("{name}: {type_name}")); |
5521 | | } |
5522 | | |
5523 | 0 | Ok(Value::String(output.join("\n"))) |
5524 | 0 | } |
5525 | | |
5526 | | /// Evaluate `who()` function - simple list of variable names |
5527 | 0 | fn evaluate_who_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
5528 | 0 | let names: Vec<_> = self.bindings.keys().cloned().collect(); |
5529 | 0 | Ok(Value::String(names.join(", "))) |
5530 | 0 | } |
5531 | | |
5532 | | /// Evaluate clear!() function - clears workspace |
5533 | 0 | fn evaluate_clear_bang_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
5534 | 0 | if args.is_empty() { |
5535 | | // Clear all bindings |
5536 | 0 | let count = self.bindings.len(); |
5537 | 0 | self.bindings.clear(); |
5538 | 0 | Ok(Value::String(format!("Cleared {count} variables"))) |
5539 | | } else { |
5540 | | // Clear matching pattern |
5541 | 0 | let pattern = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
5542 | 0 | if let Value::String(pat) = pattern { |
5543 | 0 | let mut cleared = 0; |
5544 | 0 | let pattern_prefix = pat.trim_end_matches('*'); |
5545 | 0 | let keys_to_remove: Vec<_> = self.bindings.keys() |
5546 | 0 | .filter(|k| k.starts_with(pattern_prefix)) |
5547 | 0 | .cloned() |
5548 | 0 | .collect(); |
5549 | 0 | for key in keys_to_remove { |
5550 | 0 | self.bindings.remove(&key); |
5551 | 0 | cleared += 1; |
5552 | 0 | } |
5553 | 0 | Ok(Value::String(format!("Cleared {cleared} variables"))) |
5554 | | } else { |
5555 | 0 | bail!("clear! pattern must be a string") |
5556 | | } |
5557 | | } |
5558 | 0 | } |
5559 | | |
5560 | | /// Evaluate `save_image()` function |
5561 | 0 | fn evaluate_save_image_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
5562 | 0 | if args.len() != 1 { |
5563 | 0 | bail!("save_image() expects 1 argument (filename), got {}", args.len()); |
5564 | 0 | } |
5565 | | |
5566 | 0 | let filename = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
5567 | 0 | if let Value::String(path) = filename { |
5568 | | // Generate Ruchy code to recreate workspace |
5569 | 0 | let mut content = String::new(); |
5570 | 0 | content.push_str("// Workspace image\n"); |
5571 | 0 | content.push_str("// Generated by save_image()\n\n"); |
5572 | | |
5573 | | // Save all bindings |
5574 | 0 | for (name, value) in &self.bindings { |
5575 | 0 | match value { |
5576 | 0 | Value::Int(n) => content.push_str(&format!("let {name}= {n}\n")), |
5577 | 0 | Value::Float(f) => content.push_str(&format!("let {name}= {f}\n")), |
5578 | 0 | Value::String(s) => content.push_str(&format!("let {} = \"{}\"\n", name, s.replace('"', "\\\""))), |
5579 | 0 | Value::Bool(b) => content.push_str(&format!("let {name}= {b}\n")), |
5580 | 0 | Value::List(items) => { |
5581 | 0 | content.push_str(&format!("let {name} = [")); |
5582 | 0 | for (i, item) in items.iter().enumerate() { |
5583 | 0 | if i > 0 { content.push_str(", "); } |
5584 | 0 | content.push_str(&format!("{item}")); |
5585 | | } |
5586 | 0 | content.push_str("]\n"); |
5587 | | } |
5588 | 0 | Value::Function { name: fn_name, params, body } => { |
5589 | 0 | content.push_str(&format!("fn {}({}) {{ {} }}\n", |
5590 | 0 | fn_name, params.join(", "), |
5591 | 0 | self.format_expr_source(body))); |
5592 | 0 | } |
5593 | 0 | _ => {} // Skip complex types for now |
5594 | | } |
5595 | | } |
5596 | | |
5597 | | // Write to file |
5598 | 0 | fs::write(&path, content)?; |
5599 | 0 | Ok(Value::String(format!("Workspace saved to {path}"))) |
5600 | | } else { |
5601 | 0 | bail!("save_image() requires a string filename") |
5602 | | } |
5603 | 0 | } |
5604 | | |
5605 | | /// Evaluate `workspace()` function |
5606 | 0 | fn evaluate_workspace_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
5607 | 0 | let var_count = self.bindings.len(); |
5608 | 0 | let func_count = self.bindings.values() |
5609 | 0 | .filter(|v| matches!(v, Value::Function { .. } | Value::Lambda { .. })) |
5610 | 0 | .count(); |
5611 | | |
5612 | 0 | Ok(Value::String(format!("{var_count}variables, {func_count} functions"))) |
5613 | 0 | } |
5614 | | |
5615 | | /// Evaluate `locals()` function |
5616 | 0 | fn evaluate_locals_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
5617 | | // For now, same as globals since we don't have proper scoping |
5618 | 0 | self.evaluate_globals_function(&[], Instant::now(), 0) |
5619 | 0 | } |
5620 | | |
5621 | | /// Evaluate `globals()` function |
5622 | 0 | fn evaluate_globals_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
5623 | 0 | let mut output = Vec::new(); |
5624 | 0 | for (name, value) in &self.bindings { |
5625 | 0 | output.push(format!("{}: {}", name, self.get_value_type_name(value))); |
5626 | 0 | } |
5627 | 0 | Ok(Value::String(output.join("\n"))) |
5628 | 0 | } |
5629 | | |
5630 | | /// Evaluate `reset()` function |
5631 | 0 | fn evaluate_reset_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
5632 | 0 | self.bindings.clear(); |
5633 | 0 | self.history.clear(); |
5634 | 0 | self.result_history.clear(); |
5635 | 0 | self.definitions.clear(); |
5636 | 0 | self.memory.reset(); |
5637 | 0 | Ok(Value::String("Workspace reset".to_string())) |
5638 | 0 | } |
5639 | | |
5640 | | /// Evaluate `del()` function |
5641 | 0 | fn evaluate_del_function(&mut self, args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
5642 | 0 | if args.len() != 1 { |
5643 | 0 | bail!("del() expects 1 argument, got {}", args.len()); |
5644 | 0 | } |
5645 | | |
5646 | | // Get the name to delete |
5647 | 0 | if let ExprKind::Identifier(name) = &args[0].kind { |
5648 | 0 | if self.bindings.remove(name).is_some() { |
5649 | 0 | Ok(Value::Unit) |
5650 | | } else { |
5651 | 0 | bail!("Variable '{}' not found", name) |
5652 | | } |
5653 | | } else { |
5654 | 0 | bail!("del() requires a variable name") |
5655 | | } |
5656 | 0 | } |
5657 | | |
5658 | | /// Evaluate `exists()` function |
5659 | 0 | fn evaluate_exists_function(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
5660 | 0 | if args.len() != 1 { |
5661 | 0 | bail!("exists() expects 1 argument, got {}", args.len()); |
5662 | 0 | } |
5663 | | |
5664 | 0 | let name_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
5665 | 0 | if let Value::String(name) = name_val { |
5666 | 0 | Ok(Value::Bool(self.bindings.contains_key(&name))) |
5667 | | } else { |
5668 | 0 | bail!("exists() requires a string variable name") |
5669 | | } |
5670 | 0 | } |
5671 | | |
5672 | | /// Evaluate `memory_info()` function |
5673 | 0 | fn evaluate_memory_info_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
5674 | 0 | let current = self.memory.current; |
5675 | 0 | let max = self.memory.max_size; |
5676 | 0 | let kb = current / 1024; |
5677 | 0 | Ok(Value::String(format!("Memory: {current} bytes ({kb} KB) / {max} max"))) |
5678 | 0 | } |
5679 | | |
5680 | | /// Evaluate `time_info()` function |
5681 | 0 | fn evaluate_time_info_function(&mut self, _args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
5682 | | // For simplicity, just return a placeholder |
5683 | 0 | Ok(Value::String("Session time: active".to_string())) |
5684 | 0 | } |
5685 | | |
5686 | | /// Show the type of an expression |
5687 | 0 | fn show_type(expr: &str) { |
5688 | 0 | match Parser::new(expr).parse() { |
5689 | 0 | Ok(ast) => { |
5690 | | // Create an inference context for type checking |
5691 | 0 | let mut ctx = crate::middleend::InferenceContext::new(); |
5692 | | |
5693 | | // Infer the type |
5694 | 0 | match ctx.infer(&ast) { |
5695 | 0 | Ok(ty) => { |
5696 | 0 | println!("Type: {ty}"); |
5697 | 0 | } |
5698 | 0 | Err(e) => { |
5699 | 0 | eprintln!("Type inference error: {e}"); |
5700 | 0 | } |
5701 | | } |
5702 | | } |
5703 | 0 | Err(e) => { |
5704 | 0 | eprintln!("Parse error: {e}"); |
5705 | 0 | } |
5706 | | } |
5707 | 0 | } |
5708 | | |
5709 | | /// Show the AST of an expression |
5710 | 0 | fn show_ast(expr: &str) { |
5711 | 0 | match Parser::new(expr).parse() { |
5712 | 0 | Ok(ast) => { |
5713 | 0 | println!("{ast:#?}"); |
5714 | 0 | } |
5715 | 0 | Err(e) => { |
5716 | 0 | eprintln!("Parse error: {e}"); |
5717 | 0 | } |
5718 | | } |
5719 | 0 | } |
5720 | | |
5721 | | /// Check if input needs continuation (incomplete expression) |
5722 | 0 | pub fn needs_continuation(input: &str) -> bool { |
5723 | 0 | let trimmed = input.trim(); |
5724 | | |
5725 | | // Empty input doesn't need continuation |
5726 | 0 | if trimmed.is_empty() { |
5727 | 0 | return false; |
5728 | 0 | } |
5729 | | |
5730 | | // Count braces, brackets, and parentheses |
5731 | 0 | let mut brace_depth = 0; |
5732 | 0 | let mut bracket_depth = 0; |
5733 | 0 | let mut paren_depth = 0; |
5734 | 0 | let mut in_string = false; |
5735 | 0 | let mut escape_next = false; |
5736 | | |
5737 | 0 | for ch in trimmed.chars() { |
5738 | 0 | if escape_next { |
5739 | 0 | escape_next = false; |
5740 | 0 | continue; |
5741 | 0 | } |
5742 | | |
5743 | 0 | match ch { |
5744 | 0 | '\\' if in_string => escape_next = true, |
5745 | 0 | '"' => in_string = !in_string, |
5746 | 0 | '{' if !in_string => brace_depth += 1, |
5747 | 0 | '}' if !in_string => brace_depth -= 1, |
5748 | 0 | '[' if !in_string => bracket_depth += 1, |
5749 | 0 | ']' if !in_string => bracket_depth -= 1, |
5750 | 0 | '(' if !in_string => paren_depth += 1, |
5751 | 0 | ')' if !in_string => paren_depth -= 1, |
5752 | 0 | _ => {} |
5753 | | } |
5754 | | } |
5755 | | |
5756 | | // Need continuation if any delimiters are unmatched |
5757 | 0 | brace_depth > 0 || bracket_depth > 0 || paren_depth > 0 || in_string || |
5758 | | // Or if line ends with certain tokens that expect continuation |
5759 | 0 | trimmed.ends_with('=') || |
5760 | 0 | trimmed.ends_with("->") || |
5761 | 0 | trimmed.ends_with("=>") || |
5762 | 0 | trimmed.ends_with(',') || |
5763 | 0 | trimmed.ends_with('+') || |
5764 | 0 | trimmed.ends_with('-') || |
5765 | 0 | trimmed.ends_with('*') || |
5766 | 0 | trimmed.ends_with('/') || |
5767 | 0 | trimmed.ends_with("&&") || |
5768 | 0 | trimmed.ends_with("||") || |
5769 | 0 | trimmed.ends_with(">>") |
5770 | 0 | } |
5771 | | |
5772 | | /// Compile and run the current session |
5773 | 0 | fn compile_session(&mut self) -> Result<()> { |
5774 | | use std::fmt::Write; |
5775 | | |
5776 | 0 | if self.history.is_empty() { |
5777 | 0 | println!("No expressions to compile"); |
5778 | 0 | return Ok(()); |
5779 | 0 | } |
5780 | | |
5781 | 0 | println!("Compiling session..."); |
5782 | | |
5783 | | // Generate Rust code for all expressions |
5784 | 0 | let mut rust_code = String::new(); |
5785 | 0 | rust_code.push_str("#![allow(unused)]\n"); |
5786 | 0 | rust_code.push_str("fn main() {\n"); |
5787 | | |
5788 | 0 | for expr in &self.history { |
5789 | 0 | match Parser::new(expr).parse() { |
5790 | 0 | Ok(ast) => { |
5791 | 0 | let transpiled = self.transpiler.transpile(&ast)?; |
5792 | 0 | let transpiled_str = transpiled.to_string(); |
5793 | | // Check if this is already a print statement that should be executed directly |
5794 | 0 | let trimmed = transpiled_str.trim(); |
5795 | 0 | if trimmed.starts_with("println !") |
5796 | 0 | || trimmed.starts_with("print !") |
5797 | 0 | || trimmed.starts_with("println!") |
5798 | 0 | || trimmed.starts_with("print!") |
5799 | 0 | { |
5800 | 0 | let _ = writeln!(&mut rust_code, " {transpiled};"); |
5801 | 0 | } else { |
5802 | 0 | let _ = writeln!( |
5803 | 0 | &mut rust_code, |
5804 | 0 | " println!(\"{{:?}}\", {{{transpiled}}});" |
5805 | 0 | ); |
5806 | 0 | } |
5807 | | } |
5808 | 0 | Err(e) => { |
5809 | 0 | eprintln!("Failed to parse '{expr}': {e}"); |
5810 | 0 | } |
5811 | | } |
5812 | | } |
5813 | | |
5814 | 0 | rust_code.push_str("}\n"); |
5815 | | |
5816 | | // Write to working file |
5817 | 0 | self.session_counter += 1; |
5818 | 0 | let file_name = format!("session_{}.rs", self.session_counter); |
5819 | 0 | let file_path = self.temp_dir.join(&file_name); |
5820 | 0 | fs::write(&file_path, rust_code)?; |
5821 | | |
5822 | | // Compile with rustc |
5823 | 0 | let output = Command::new("rustc") |
5824 | 0 | .arg(&file_path) |
5825 | 0 | .arg("-o") |
5826 | 0 | .arg( |
5827 | 0 | self.temp_dir |
5828 | 0 | .join(format!("session_{}", self.session_counter)), |
5829 | 0 | ) |
5830 | 0 | .current_dir(&self.temp_dir) |
5831 | 0 | .output() |
5832 | 0 | .context("Failed to run rustc")?; |
5833 | | |
5834 | 0 | if !output.status.success() { |
5835 | 0 | eprintln!( |
5836 | 0 | "Compilation failed:\n{}", |
5837 | 0 | String::from_utf8_lossy(&output.stderr) |
5838 | | ); |
5839 | 0 | return Ok(()); |
5840 | 0 | } |
5841 | | |
5842 | | // Run the compiled program |
5843 | 0 | let exe_path = self |
5844 | 0 | .temp_dir |
5845 | 0 | .join(format!("session_{}", self.session_counter)); |
5846 | 0 | let output = Command::new(&exe_path) |
5847 | 0 | .output() |
5848 | 0 | .context("Failed to run compiled program")?; |
5849 | | |
5850 | 0 | println!("{}", "Output:".bright_green()); |
5851 | 0 | print!("{}", String::from_utf8_lossy(&output.stdout)); |
5852 | | |
5853 | 0 | if !output.stderr.is_empty() { |
5854 | 0 | eprintln!("{}", String::from_utf8_lossy(&output.stderr)); |
5855 | 0 | } |
5856 | | |
5857 | 0 | Ok(()) |
5858 | 0 | } |
5859 | | |
5860 | | /// Search through command history with fuzzy matching |
5861 | 0 | fn search_history(&self, query: &str) { |
5862 | 0 | let query_lower = query.to_lowercase(); |
5863 | 0 | let mut matches = Vec::new(); |
5864 | | |
5865 | | // Simple fuzzy matching: contains all characters in order |
5866 | 0 | for (i, item) in self.history.iter().enumerate() { |
5867 | 0 | let item_lower = item.to_lowercase(); |
5868 | | |
5869 | | // Check if query characters appear in order in the history item |
5870 | 0 | let mut query_chars = query_lower.chars(); |
5871 | 0 | let mut current_char = query_chars.next(); |
5872 | 0 | let mut score = 0; |
5873 | | |
5874 | 0 | for item_char in item_lower.chars() { |
5875 | 0 | if let Some(q_char) = current_char { |
5876 | 0 | if item_char == q_char { |
5877 | 0 | score += 1; |
5878 | 0 | current_char = query_chars.next(); |
5879 | 0 | } |
5880 | 0 | } |
5881 | | } |
5882 | | |
5883 | | // If all query characters were found, it's a match |
5884 | 0 | if current_char.is_none() { |
5885 | 0 | matches.push((i, item, score)); |
5886 | 0 | } else if item_lower.contains(&query_lower) { |
5887 | 0 | // Also include exact substring matches |
5888 | 0 | matches.push((i, item, query.len())); |
5889 | 0 | } |
5890 | | } |
5891 | | |
5892 | 0 | if matches.is_empty() { |
5893 | 0 | println!("No matches found for '{query}'"); |
5894 | 0 | return; |
5895 | 0 | } |
5896 | | |
5897 | | // Sort by score (descending) then by recency (descending) |
5898 | 0 | matches.sort_by(|a, b| b.2.cmp(&a.2).then(b.0.cmp(&a.0))); |
5899 | | |
5900 | 0 | println!( |
5901 | 0 | "{} History search results for '{}':", |
5902 | 0 | "Found".bright_green(), |
5903 | | query |
5904 | | ); |
5905 | 0 | for (i, (hist_idx, item, _score)) in matches.iter().enumerate().take(10) { |
5906 | | // Highlight the query in the result |
5907 | 0 | let highlighted = Self::highlight_match(item, &query_lower); |
5908 | 0 | println!( |
5909 | 0 | " {}: {}", |
5910 | 0 | format!("{}", hist_idx + 1).bright_black(), |
5911 | | highlighted |
5912 | | ); |
5913 | | |
5914 | 0 | if i >= 9 { |
5915 | 0 | break; |
5916 | 0 | } |
5917 | | } |
5918 | | |
5919 | 0 | if matches.len() > 10 { |
5920 | 0 | println!(" ... and {} more matches", matches.len() - 10); |
5921 | 0 | } |
5922 | | |
5923 | 0 | println!( |
5924 | 0 | "\n{}: Use :history to see all commands or Ctrl+R for interactive search", |
5925 | 0 | "Tip".bright_cyan() |
5926 | | ); |
5927 | 0 | } |
5928 | | |
5929 | | /// Highlight query matches in text |
5930 | 0 | fn highlight_match(text: &str, query: &str) -> String { |
5931 | 0 | let mut result = String::new(); |
5932 | 0 | let mut query_chars = query.chars().peekable(); |
5933 | 0 | let mut current_char = query_chars.next(); |
5934 | | |
5935 | 0 | for ch in text.chars() { |
5936 | 0 | let ch_lower = ch.to_lowercase().next().unwrap_or(ch); |
5937 | | |
5938 | 0 | if let Some(q_char) = current_char { |
5939 | 0 | if ch_lower == q_char { |
5940 | 0 | // Highlight matching character |
5941 | 0 | result.push_str(&ch.to_string().bright_yellow().bold().to_string()); |
5942 | 0 | current_char = query_chars.next(); |
5943 | 0 | } else { |
5944 | 0 | result.push(ch); |
5945 | 0 | } |
5946 | 0 | } else { |
5947 | 0 | result.push(ch); |
5948 | 0 | } |
5949 | | } |
5950 | | |
5951 | 0 | result |
5952 | 0 | } |
5953 | | |
5954 | | /// Save current session to a file |
5955 | | /// |
5956 | | /// # Errors |
5957 | | /// |
5958 | | /// Returns an error if file writing fails |
5959 | | /// Generate session header with metadata (complexity: 3) |
5960 | 0 | fn generate_session_header(&self, content: &mut String) -> Result<()> { |
5961 | | use chrono::Utc; |
5962 | | use std::fmt::Write; |
5963 | | |
5964 | 0 | writeln!(content, "// Ruchy REPL Session")?; |
5965 | 0 | writeln!( |
5966 | 0 | content, |
5967 | 0 | "// Generated: {}", |
5968 | 0 | Utc::now().format("%Y-%m-%d %H:%M:%S UTC") |
5969 | 0 | )?; |
5970 | 0 | writeln!(content, "// Commands: {}", self.history.len())?; |
5971 | 0 | writeln!(content, "// Variables: {}", self.bindings.len())?; |
5972 | 0 | writeln!(content)?; |
5973 | 0 | Ok(()) |
5974 | 0 | } |
5975 | | |
5976 | | /// Add variable bindings as comments (complexity: 3) |
5977 | 0 | fn add_bindings_to_content(&self, content: &mut String) -> Result<()> { |
5978 | | use std::fmt::Write; |
5979 | | |
5980 | 0 | if !self.bindings.is_empty() { |
5981 | 0 | writeln!(content, "// Current variable bindings:")?; |
5982 | 0 | for (name, value) in &self.bindings { |
5983 | 0 | writeln!(content, "// {name}: {value}")?; |
5984 | | } |
5985 | 0 | writeln!(content)?; |
5986 | 0 | } |
5987 | 0 | Ok(()) |
5988 | 0 | } |
5989 | | |
5990 | | /// Add command history to content (complexity: 5) |
5991 | 0 | fn add_history_to_content(&self, content: &mut String) -> Result<()> { |
5992 | | use std::fmt::Write; |
5993 | | |
5994 | 0 | writeln!( |
5995 | 0 | content, |
5996 | 0 | "// Session history (paste into REPL or run as script):" |
5997 | 0 | )?; |
5998 | 0 | writeln!(content)?; |
5999 | | |
6000 | 0 | for (i, command) in self.history.iter().enumerate() { |
6001 | 0 | if command.starts_with(':') { |
6002 | 0 | writeln!( |
6003 | 0 | content, |
6004 | 0 | "// Command {}: {} (REPL command, skipped)", |
6005 | 0 | i + 1, |
6006 | | command |
6007 | 0 | )?; |
6008 | 0 | continue; |
6009 | 0 | } |
6010 | | |
6011 | 0 | writeln!(content, "// Command {}:", i + 1)?; |
6012 | 0 | writeln!(content, "{command}")?; |
6013 | 0 | writeln!(content)?; |
6014 | | } |
6015 | 0 | Ok(()) |
6016 | 0 | } |
6017 | | |
6018 | | /// Add usage instructions to content (complexity: 2) |
6019 | 0 | fn add_usage_instructions(&self, content: &mut String, filename: &str) -> Result<()> { |
6020 | | use std::fmt::Write; |
6021 | | |
6022 | 0 | writeln!(content, "// To recreate this session, you can:")?; |
6023 | 0 | writeln!( |
6024 | 0 | content, |
6025 | 0 | "// 1. Copy and paste commands individually into the REPL" |
6026 | 0 | )?; |
6027 | 0 | writeln!( |
6028 | 0 | content, |
6029 | 0 | "// 2. Use :load {filename} to execute all commands" |
6030 | 0 | )?; |
6031 | 0 | writeln!( |
6032 | 0 | content, |
6033 | 0 | "// 3. Remove comments and run as a script: ruchy {filename}" |
6034 | 0 | )?; |
6035 | 0 | Ok(()) |
6036 | 0 | } |
6037 | | |
6038 | | /// Save REPL session to file (complexity: 7) |
6039 | 0 | fn save_session(&self, filename: &str) -> Result<()> { |
6040 | | use std::io::Write; |
6041 | | |
6042 | 0 | let mut content = String::new(); |
6043 | | |
6044 | | // Generate all content sections |
6045 | 0 | self.generate_session_header(&mut content)?; |
6046 | 0 | self.add_bindings_to_content(&mut content)?; |
6047 | 0 | self.add_history_to_content(&mut content)?; |
6048 | 0 | self.add_usage_instructions(&mut content, filename)?; |
6049 | | |
6050 | | // Write to file |
6051 | 0 | let mut file = std::fs::File::create(filename) |
6052 | 0 | .with_context(|| format!("Failed to create file: {filename}"))?; |
6053 | 0 | file.write_all(content.as_bytes()) |
6054 | 0 | .with_context(|| format!("Failed to write to file: {filename}"))?; |
6055 | | |
6056 | 0 | Ok(()) |
6057 | 0 | } |
6058 | | |
6059 | | /// Export session as a clean production script |
6060 | | /// |
6061 | | /// Unlike `save_session` which saves the raw REPL commands with comments, |
6062 | | /// this creates a clean, executable script with proper structure. |
6063 | | /// |
6064 | | /// # Arguments |
6065 | | /// |
6066 | | /// * `filename` - The output filename for the exported script |
6067 | | /// |
6068 | | /// # Returns |
6069 | | /// |
6070 | | /// Returns an error if file writing fails |
6071 | | /// Generate export header (complexity: 2) |
6072 | 0 | fn generate_export_header(&self, content: &mut String) -> Result<()> { |
6073 | | use chrono::Utc; |
6074 | | use std::fmt::Write; |
6075 | | |
6076 | 0 | writeln!(content, "// Ruchy Script - Exported from REPL Session")?; |
6077 | 0 | writeln!( |
6078 | 0 | content, |
6079 | 0 | "// Generated: {}", |
6080 | 0 | Utc::now().format("%Y-%m-%d %H:%M:%S UTC") |
6081 | 0 | )?; |
6082 | 0 | writeln!(content, "// Total commands: {}", self.history.len())?; |
6083 | 0 | writeln!(content)?; |
6084 | 0 | Ok(()) |
6085 | 0 | } |
6086 | | |
6087 | | /// Filter and clean commands for export (complexity: 6) |
6088 | 0 | fn filter_commands_for_export(&self) -> Vec<String> { |
6089 | 0 | let mut clean_statements = Vec::new(); |
6090 | | |
6091 | 0 | for command in &self.history { |
6092 | | // Skip REPL commands, introspection, and display-only statements |
6093 | 0 | if command.starts_with(':') || |
6094 | 0 | command.starts_with('?') || |
6095 | 0 | command.starts_with('%') || |
6096 | 0 | command.trim().is_empty() || |
6097 | 0 | self.is_display_only_command(command) { |
6098 | 0 | continue; |
6099 | 0 | } |
6100 | | |
6101 | | // Clean up the statement |
6102 | 0 | let cleaned = self.clean_statement_for_export(command); |
6103 | 0 | if !cleaned.trim().is_empty() { |
6104 | 0 | clean_statements.push(cleaned); |
6105 | 0 | } |
6106 | | } |
6107 | | |
6108 | 0 | clean_statements |
6109 | 0 | } |
6110 | | |
6111 | | /// Generate main function wrapper (complexity: 4) |
6112 | 0 | fn generate_main_function(&self, content: &mut String, statements: &[String]) -> Result<()> { |
6113 | | use std::fmt::Write; |
6114 | | |
6115 | 0 | if statements.is_empty() { |
6116 | 0 | writeln!(content, "// No executable statements to export")?; |
6117 | 0 | writeln!(content, "fn main() {{")?; |
6118 | 0 | writeln!(content, " println!(\"Hello, Ruchy!\");")?; |
6119 | 0 | writeln!(content, "}}")?; |
6120 | | } else { |
6121 | 0 | writeln!(content, "fn main() -> Result<(), Box<dyn std::error::Error>> {{")?; |
6122 | | |
6123 | 0 | for statement in statements { |
6124 | 0 | writeln!(content, " {statement}")?; |
6125 | | } |
6126 | | |
6127 | 0 | writeln!(content, " Ok(())")?; |
6128 | 0 | writeln!(content, "}}")?; |
6129 | | } |
6130 | 0 | Ok(()) |
6131 | 0 | } |
6132 | | |
6133 | | /// Export session as a clean production script (complexity: 6) |
6134 | 0 | fn export_session(&self, filename: &str) -> Result<()> { |
6135 | | use std::io::Write; |
6136 | | |
6137 | 0 | let mut content = String::new(); |
6138 | | |
6139 | | // Generate header |
6140 | 0 | self.generate_export_header(&mut content)?; |
6141 | | |
6142 | | // Filter and clean commands |
6143 | 0 | let clean_statements = self.filter_commands_for_export(); |
6144 | | |
6145 | | // Generate main function |
6146 | 0 | self.generate_main_function(&mut content, &clean_statements)?; |
6147 | | |
6148 | | // Write to file |
6149 | 0 | let mut file = std::fs::File::create(filename) |
6150 | 0 | .with_context(|| format!("Failed to create file: {filename}"))?; |
6151 | 0 | file.write_all(content.as_bytes()) |
6152 | 0 | .with_context(|| format!("Failed to write to file: {filename}"))?; |
6153 | | |
6154 | 0 | Ok(()) |
6155 | 0 | } |
6156 | | |
6157 | | /// Check if a command is display-only (just shows a value) |
6158 | 0 | fn is_display_only_command(&self, command: &str) -> bool { |
6159 | 0 | let trimmed = command.trim(); |
6160 | | |
6161 | | // Check if it's just a variable name or expression that displays a value |
6162 | | // without assignment or side effects |
6163 | | |
6164 | | // Simple identifier (just displays value) |
6165 | 0 | if trimmed.chars().all(|c| c.is_alphanumeric() || c == '_') { |
6166 | 0 | return true; |
6167 | 0 | } |
6168 | | |
6169 | | // Method calls that are typically for display (head, tail, info, etc.) |
6170 | 0 | if trimmed.contains(".head()") || |
6171 | 0 | trimmed.contains(".tail()") || |
6172 | 0 | trimmed.contains(".info()") || |
6173 | 0 | trimmed.contains(".summary()") || |
6174 | 0 | trimmed.contains(".describe()") { |
6175 | 0 | return true; |
6176 | 0 | } |
6177 | | |
6178 | 0 | false |
6179 | 0 | } |
6180 | | |
6181 | | /// Clean up a statement for export (add proper error handling, etc.) |
6182 | 0 | fn clean_statement_for_export(&self, command: &str) -> String { |
6183 | 0 | let trimmed = command.trim(); |
6184 | | |
6185 | | // Add error handling for operations that might fail |
6186 | 0 | if trimmed.contains("read_csv") || |
6187 | 0 | trimmed.contains("read_file") || |
6188 | 0 | trimmed.contains("write_file") { |
6189 | | // If it's an assignment, keep as is but add ? for error propagation |
6190 | 0 | if trimmed.contains(" = ") { |
6191 | 0 | format!("{}?;", trimmed.trim_end_matches(';')) |
6192 | | } else { |
6193 | 0 | format!("{}?;", trimmed.trim_end_matches(';')) |
6194 | | } |
6195 | | } else { |
6196 | | // Regular statements - ensure semicolon |
6197 | 0 | if trimmed.ends_with(';') { |
6198 | 0 | trimmed.to_string() |
6199 | | } else { |
6200 | 0 | format!("{trimmed};") |
6201 | | } |
6202 | | } |
6203 | 0 | } |
6204 | | |
6205 | | /// Load and evaluate a file |
6206 | 0 | fn load_file(&mut self, path: &str) -> Result<()> { |
6207 | 0 | let content = |
6208 | 0 | fs::read_to_string(path).with_context(|| format!("Failed to read file: {path}"))?; |
6209 | | |
6210 | 0 | println!("Loading {path}..."); |
6211 | | |
6212 | 0 | for line in content.lines() { |
6213 | 0 | if line.trim().is_empty() || line.trim().starts_with("//") { |
6214 | 0 | continue; |
6215 | 0 | } |
6216 | | |
6217 | 0 | match self.eval(line) { |
6218 | 0 | Ok(result) => { |
6219 | 0 | println!("{}: {}", line.bright_black(), result); |
6220 | 0 | } |
6221 | 0 | Err(e) => { |
6222 | 0 | eprintln!("{}: {} - {}", "Error".bright_red(), line, e); |
6223 | 0 | } |
6224 | | } |
6225 | | } |
6226 | | |
6227 | 0 | Ok(()) |
6228 | 0 | } |
6229 | | |
6230 | | /// Evaluate literal expressions |
6231 | 96 | fn evaluate_literal(&mut self, lit: &Literal) -> Result<Value> { |
6232 | 96 | match lit { |
6233 | 47 | Literal::Integer(n) => Ok(Value::Int(*n)), |
6234 | 5 | Literal::Float(f) => Ok(Value::Float(*f)), |
6235 | 31 | Literal::String(s) => { |
6236 | 31 | self.memory.try_alloc(s.len())?0 ; |
6237 | 31 | Ok(Value::String(s.clone())) |
6238 | | } |
6239 | 13 | Literal::Bool(b) => Ok(Value::Bool(*b)), |
6240 | 0 | Literal::Char(c) => Ok(Value::Char(*c)), |
6241 | 0 | Literal::Unit => Ok(Value::Unit), |
6242 | | } |
6243 | 96 | } |
6244 | | |
6245 | | /// Evaluate if expressions |
6246 | 1 | fn evaluate_if( |
6247 | 1 | &mut self, |
6248 | 1 | condition: &Expr, |
6249 | 1 | then_branch: &Expr, |
6250 | 1 | else_branch: Option<&Expr>, |
6251 | 1 | deadline: Instant, |
6252 | 1 | depth: usize, |
6253 | 1 | ) -> Result<Value> { |
6254 | 1 | let cond_val = self.evaluate_expr(condition, deadline, depth + 1)?0 ; |
6255 | 1 | match cond_val { |
6256 | 1 | Value::Bool(true) => self.evaluate_expr(then_branch, deadline, depth + 1), |
6257 | | Value::Bool(false) => { |
6258 | 0 | if let Some(else_expr) = else_branch { |
6259 | 0 | self.evaluate_expr(else_expr, deadline, depth + 1) |
6260 | | } else { |
6261 | 0 | Ok(Value::Unit) |
6262 | | } |
6263 | | } |
6264 | 0 | _ => bail!("If condition must be boolean, got: {:?}", cond_val), |
6265 | | } |
6266 | 1 | } |
6267 | | |
6268 | | /// Evaluate try-catch expression (complexity: 4) |
6269 | 0 | fn evaluate_try_catch( |
6270 | 0 | &mut self, |
6271 | 0 | try_expr: &Expr, |
6272 | 0 | catch_expr: &Expr, |
6273 | 0 | deadline: Instant, |
6274 | 0 | depth: usize, |
6275 | 0 | ) -> Result<Value> { |
6276 | | // Try to evaluate the try expression |
6277 | 0 | match self.evaluate_expr(try_expr, deadline, depth + 1) { |
6278 | 0 | Ok(value) => Ok(value), |
6279 | | Err(_) => { |
6280 | | // If the try expression fails, evaluate the catch expression |
6281 | 0 | self.evaluate_expr(catch_expr, deadline, depth + 1) |
6282 | | } |
6283 | | } |
6284 | 0 | } |
6285 | | |
6286 | | /// Evaluate if-let expression (complexity: 6) |
6287 | 0 | fn evaluate_if_let( |
6288 | 0 | &mut self, |
6289 | 0 | pattern: &Pattern, |
6290 | 0 | expr: &Expr, |
6291 | 0 | then_branch: &Expr, |
6292 | 0 | else_branch: Option<&Expr>, |
6293 | 0 | deadline: Instant, |
6294 | 0 | depth: usize, |
6295 | 0 | ) -> Result<Value> { |
6296 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
6297 | | |
6298 | | // Try pattern matching |
6299 | 0 | if let Ok(Some(bindings)) = Self::pattern_matches(&value, pattern) { |
6300 | | // Save current bindings |
6301 | 0 | let saved_bindings: Vec<(String, Value)> = bindings |
6302 | 0 | .iter() |
6303 | 0 | .filter_map(|(name, _val)| { |
6304 | 0 | self.bindings.get(name).map(|old_val| (name.clone(), old_val.clone())) |
6305 | 0 | }) |
6306 | 0 | .collect(); |
6307 | | |
6308 | | // Apply pattern bindings |
6309 | 0 | for (name, val) in bindings { |
6310 | 0 | self.bindings.insert(name, val); |
6311 | 0 | } |
6312 | | |
6313 | | // Evaluate then branch |
6314 | 0 | let result = self.evaluate_expr(then_branch, deadline, depth + 1); |
6315 | | |
6316 | | // Restore bindings |
6317 | 0 | for (name, old_val) in saved_bindings { |
6318 | 0 | self.bindings.insert(name, old_val); |
6319 | 0 | } |
6320 | | |
6321 | 0 | result |
6322 | | } else { |
6323 | | // Pattern didn't match, evaluate else branch |
6324 | 0 | if let Some(else_expr) = else_branch { |
6325 | 0 | self.evaluate_expr(else_expr, deadline, depth + 1) |
6326 | | } else { |
6327 | 0 | Ok(Value::Unit) |
6328 | | } |
6329 | | } |
6330 | 0 | } |
6331 | | |
6332 | | /// Evaluate while-let expression (complexity: 7) |
6333 | 0 | fn evaluate_while_let( |
6334 | 0 | &mut self, |
6335 | 0 | pattern: &Pattern, |
6336 | 0 | expr: &Expr, |
6337 | 0 | body: &Expr, |
6338 | 0 | deadline: Instant, |
6339 | 0 | depth: usize, |
6340 | 0 | ) -> Result<Value> { |
6341 | 0 | let mut last_value = Value::Unit; |
6342 | | |
6343 | | loop { |
6344 | 0 | if Instant::now() > deadline { |
6345 | 0 | bail!("Loop timed out"); |
6346 | 0 | } |
6347 | | |
6348 | 0 | let value = self.evaluate_expr(expr, deadline, depth + 1)?; |
6349 | | |
6350 | | // Try pattern matching |
6351 | 0 | if let Ok(Some(bindings)) = Self::pattern_matches(&value, pattern) { |
6352 | | // Save current bindings |
6353 | 0 | let saved_bindings: Vec<(String, Value)> = bindings |
6354 | 0 | .iter() |
6355 | 0 | .filter_map(|(name, _val)| { |
6356 | 0 | self.bindings.get(name).map(|old_val| (name.clone(), old_val.clone())) |
6357 | 0 | }) |
6358 | 0 | .collect(); |
6359 | | |
6360 | | // Apply pattern bindings |
6361 | 0 | for (name, val) in bindings { |
6362 | 0 | self.bindings.insert(name, val); |
6363 | 0 | } |
6364 | | |
6365 | | // Evaluate body |
6366 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
6367 | 0 | Ok(val) => { |
6368 | 0 | last_value = val; |
6369 | | |
6370 | | // Restore bindings |
6371 | 0 | for (name, old_val) in saved_bindings { |
6372 | 0 | self.bindings.insert(name, old_val); |
6373 | 0 | } |
6374 | | } |
6375 | 0 | Err(e) => { |
6376 | | // Restore bindings before propagating error |
6377 | 0 | for (name, old_val) in saved_bindings { |
6378 | 0 | self.bindings.insert(name, old_val); |
6379 | 0 | } |
6380 | 0 | return Err(e); |
6381 | | } |
6382 | | } |
6383 | | } else { |
6384 | | // Pattern didn't match, exit loop |
6385 | 0 | break; |
6386 | | } |
6387 | | } |
6388 | | |
6389 | 0 | Ok(last_value) |
6390 | 0 | } |
6391 | | |
6392 | | /// Evaluate function calls |
6393 | | /// Dispatcher for I/O functions (complexity: 8) |
6394 | 50 | fn dispatch_io_functions( |
6395 | 50 | &mut self, |
6396 | 50 | func_name: &str, |
6397 | 50 | args: &[Expr], |
6398 | 50 | deadline: Instant, |
6399 | 50 | depth: usize, |
6400 | 50 | ) -> Option<Result<Value>> { |
6401 | 50 | match func_name { |
6402 | 50 | "println" => Some(36 self36 .evaluate_println36 (args36 , deadline, depth)), |
6403 | 14 | "print" => Some(13 self13 .evaluate_print13 (args13 , deadline, depth)), |
6404 | 1 | "input" => Some(0 self0 .evaluate_input0 (args0 , deadline, depth)), |
6405 | 1 | "readline" => Some(0 self0 .evaluate_readline0 (args0 , deadline, depth)), |
6406 | 1 | _ => None, |
6407 | | } |
6408 | 50 | } |
6409 | | |
6410 | | /// Dispatcher for assertion functions (complexity: 4) |
6411 | 1 | fn dispatch_assertion_functions( |
6412 | 1 | &mut self, |
6413 | 1 | func_name: &str, |
6414 | 1 | args: &[Expr], |
6415 | 1 | deadline: Instant, |
6416 | 1 | depth: usize, |
6417 | 1 | ) -> Option<Result<Value>> { |
6418 | 1 | match func_name { |
6419 | 1 | "assert" => Some(0 self0 .evaluate_assert0 (args0 , deadline, depth)), |
6420 | 1 | "assert_eq" => Some(0 self0 .evaluate_assert_eq0 (args0 , deadline, depth)), |
6421 | 1 | "assert_ne" => Some(0 self0 .evaluate_assert_ne0 (args0 , deadline, depth)), |
6422 | 1 | _ => None, |
6423 | | } |
6424 | 1 | } |
6425 | | |
6426 | | /// Dispatcher for file operations (complexity: 6) |
6427 | 1 | fn dispatch_file_functions( |
6428 | 1 | &mut self, |
6429 | 1 | func_name: &str, |
6430 | 1 | args: &[Expr], |
6431 | 1 | deadline: Instant, |
6432 | 1 | depth: usize, |
6433 | 1 | ) -> Option<Result<Value>> { |
6434 | 1 | match func_name { |
6435 | 1 | "read_file" => Some(0 self0 .evaluate_read_file0 (args0 , deadline, depth)), |
6436 | 1 | "write_file" => Some(0 self0 .evaluate_write_file0 (args0 , deadline, depth)), |
6437 | 1 | "append_file" => Some(0 self0 .evaluate_append_file0 (args0 , deadline, depth)), |
6438 | 1 | "file_exists" => Some(0 self0 .evaluate_file_exists0 (args0 , deadline, depth)), |
6439 | 1 | "delete_file" => Some(0 self0 .evaluate_delete_file0 (args0 , deadline, depth)), |
6440 | 1 | _ => None, |
6441 | | } |
6442 | 1 | } |
6443 | | |
6444 | | /// Dispatcher for type conversion functions (complexity: 5) |
6445 | 1 | fn dispatch_type_conversion( |
6446 | 1 | &mut self, |
6447 | 1 | func_name: &str, |
6448 | 1 | args: &[Expr], |
6449 | 1 | deadline: Instant, |
6450 | 1 | depth: usize, |
6451 | 1 | ) -> Option<Result<Value>> { |
6452 | 1 | match func_name { |
6453 | 1 | "str" => Some(0 self0 .evaluate_str_conversion0 (args0 , deadline, depth)), |
6454 | 1 | "int" => Some(0 self0 .evaluate_int_conversion0 (args0 , deadline, depth)), |
6455 | 1 | "float" => Some(0 self0 .evaluate_float_conversion0 (args0 , deadline, depth)), |
6456 | 1 | "bool" => Some(0 self0 .evaluate_bool_conversion0 (args0 , deadline, depth)), |
6457 | 1 | _ => None, |
6458 | | } |
6459 | 1 | } |
6460 | | |
6461 | | /// Dispatcher for introspection functions (complexity: 5) |
6462 | 1 | fn dispatch_introspection_functions( |
6463 | 1 | &mut self, |
6464 | 1 | func_name: &str, |
6465 | 1 | args: &[Expr], |
6466 | 1 | deadline: Instant, |
6467 | 1 | depth: usize, |
6468 | 1 | ) -> Option<Result<Value>> { |
6469 | 1 | match func_name { |
6470 | 1 | "type" => Some(0 self0 .evaluate_type_function0 (args0 , deadline, depth)), |
6471 | 1 | "summary" => Some(0 self0 .evaluate_summary_function0 (args0 , deadline, depth)), |
6472 | 1 | "dir" => Some(0 self0 .evaluate_dir_function0 (args0 , deadline, depth)), |
6473 | 1 | "help" => Some(0 self0 .evaluate_help_function0 (args0 , deadline, depth)), |
6474 | 1 | _ => None, |
6475 | | } |
6476 | 1 | } |
6477 | | |
6478 | | /// Dispatcher for math functions (complexity: 7) |
6479 | 1 | fn dispatch_math_functions( |
6480 | 1 | &mut self, |
6481 | 1 | func_name: &str, |
6482 | 1 | args: &[Expr], |
6483 | 1 | deadline: Instant, |
6484 | 1 | depth: usize, |
6485 | 1 | ) -> Option<Result<Value>> { |
6486 | 1 | match func_name { |
6487 | 1 | "sin" => Some(0 self0 .evaluate_sin0 (args0 , deadline, depth)), |
6488 | 1 | "cos" => Some(0 self0 .evaluate_cos0 (args0 , deadline, depth)), |
6489 | 1 | "tan" => Some(0 self0 .evaluate_tan0 (args0 , deadline, depth)), |
6490 | 1 | "log" => Some(0 self0 .evaluate_log0 (args0 , deadline, depth)), |
6491 | 1 | "log10" => Some(0 self0 .evaluate_log100 (args0 , deadline, depth)), |
6492 | 1 | "random" => Some(0 self0 .evaluate_random0 (args0 , deadline, depth)), |
6493 | 1 | _ => None, |
6494 | | } |
6495 | 1 | } |
6496 | | |
6497 | | /// Dispatcher for workspace functions (complexity: 10) |
6498 | 1 | fn dispatch_workspace_functions( |
6499 | 1 | &mut self, |
6500 | 1 | func_name: &str, |
6501 | 1 | args: &[Expr], |
6502 | 1 | deadline: Instant, |
6503 | 1 | depth: usize, |
6504 | 1 | ) -> Option<Result<Value>> { |
6505 | 1 | match func_name { |
6506 | 1 | "whos" => Some(0 self0 .evaluate_whos_function0 (args0 , deadline, depth)), |
6507 | 1 | "who" => Some(0 self0 .evaluate_who_function0 (args0 , deadline, depth)), |
6508 | 1 | "clear_all" => Some(0 self0 .evaluate_clear_bang_function0 (args0 , deadline, depth)), |
6509 | 1 | "save_image" => Some(0 self0 .evaluate_save_image_function0 (args0 , deadline, depth)), |
6510 | 1 | "workspace" => Some(0 self0 .evaluate_workspace_function0 (args0 , deadline, depth)), |
6511 | 1 | "locals" => Some(0 self0 .evaluate_locals_function0 (args0 , deadline, depth)), |
6512 | 1 | "globals" => Some(0 self0 .evaluate_globals_function0 (args0 , deadline, depth)), |
6513 | 1 | "reset" => Some(0 self0 .evaluate_reset_function0 (args0 , deadline, depth)), |
6514 | 1 | "del" => Some(0 self0 .evaluate_del_function0 (args0 , deadline, depth)), |
6515 | 1 | "exists" => Some(0 self0 .evaluate_exists_function0 (args0 , deadline, depth)), |
6516 | 1 | "memory_info" => Some(0 self0 .evaluate_memory_info_function0 (args0 , deadline, depth)), |
6517 | 1 | "time_info" => Some(0 self0 .evaluate_time_info_function0 (args0 , deadline, depth)), |
6518 | 1 | _ => None, |
6519 | | } |
6520 | 1 | } |
6521 | | |
6522 | | /// Dispatcher for environment and system functions (complexity: 4) |
6523 | 1 | fn dispatch_system_functions( |
6524 | 1 | &mut self, |
6525 | 1 | func_name: &str, |
6526 | 1 | args: &[Expr], |
6527 | 1 | deadline: Instant, |
6528 | 1 | depth: usize, |
6529 | 1 | ) -> Option<Result<Value>> { |
6530 | 1 | match func_name { |
6531 | 1 | "current_dir" => Some(0 self0 .evaluate_current_dir0 (args0 , deadline, depth)), |
6532 | 1 | "env" => Some(0 self0 .evaluate_env0 (args0 , deadline, depth)), |
6533 | 1 | "set_env" => Some(0 self0 .evaluate_set_env0 (args0 , deadline, depth)), |
6534 | 1 | "args" => Some(0 self0 .evaluate_args0 (args0 , deadline, depth)), |
6535 | 1 | _ => None, |
6536 | | } |
6537 | 1 | } |
6538 | | |
6539 | | /// Dispatcher for Result/Option constructors (complexity: 5) |
6540 | 1 | fn dispatch_result_option_constructors( |
6541 | 1 | &mut self, |
6542 | 1 | func_name: &str, |
6543 | 1 | args: &[Expr], |
6544 | 1 | deadline: Instant, |
6545 | 1 | depth: usize, |
6546 | 1 | ) -> Option<Result<Value>> { |
6547 | 1 | match func_name { |
6548 | 1 | "Some" => Some(0 self0 .evaluate_some0 (args0 , deadline, depth)), |
6549 | 1 | "None" => Some(0 self0 .evaluate_none0 (args0 , deadline, depth)), |
6550 | 1 | "Ok" => Some(0 self0 .evaluate_ok0 (args0 , deadline, depth)), |
6551 | 1 | "Err" => Some(0 self0 .evaluate_err0 (args0 , deadline, depth)), |
6552 | 1 | _ => None, |
6553 | | } |
6554 | 1 | } |
6555 | | |
6556 | | /// Dispatcher for collection constructors (complexity: 3) |
6557 | 1 | fn dispatch_collection_constructors( |
6558 | 1 | &mut self, |
6559 | 1 | func_name: &str, |
6560 | 1 | args: &[Expr], |
6561 | 1 | ) -> Option<Result<Value>> { |
6562 | 1 | match func_name { |
6563 | 1 | "HashMap" => { |
6564 | 0 | if args.is_empty() { |
6565 | 0 | Some(Ok(Value::HashMap(HashMap::new()))) |
6566 | | } else { |
6567 | 0 | Some(Err(anyhow::anyhow!("HashMap() constructor expects no arguments, got {}", args.len()))) |
6568 | | } |
6569 | | } |
6570 | 1 | "HashSet" => { |
6571 | 0 | if args.is_empty() { |
6572 | 0 | Some(Ok(Value::HashSet(HashSet::new()))) |
6573 | | } else { |
6574 | 0 | Some(Err(anyhow::anyhow!("HashSet() constructor expects no arguments, got {}", args.len()))) |
6575 | | } |
6576 | | } |
6577 | 1 | _ => None, |
6578 | | } |
6579 | 1 | } |
6580 | | |
6581 | | /// Dispatcher for static method calls (complexity: 9) |
6582 | 0 | fn dispatch_static_methods( |
6583 | 0 | &mut self, |
6584 | 0 | module: &str, |
6585 | 0 | name: &str, |
6586 | 0 | args: &[Expr], |
6587 | 0 | _deadline: Instant, |
6588 | 0 | _depth: usize, |
6589 | 0 | ) -> Option<Result<Value>> { |
6590 | 0 | match (module, name) { |
6591 | 0 | ("HashMap", "new") => { |
6592 | 0 | if args.is_empty() { |
6593 | 0 | Some(Ok(Value::HashMap(HashMap::new()))) |
6594 | | } else { |
6595 | 0 | Some(Err(anyhow::anyhow!("HashMap::new() expects no arguments, got {}", args.len()))) |
6596 | | } |
6597 | | } |
6598 | 0 | ("HashSet", "new") => { |
6599 | 0 | if args.is_empty() { |
6600 | 0 | Some(Ok(Value::HashSet(HashSet::new()))) |
6601 | | } else { |
6602 | 0 | Some(Err(anyhow::anyhow!("HashSet::new() expects no arguments, got {}", args.len()))) |
6603 | | } |
6604 | | } |
6605 | 0 | _ => None, |
6606 | | } |
6607 | 0 | } |
6608 | | |
6609 | | /// Dispatcher for performance module methods (complexity: 8) |
6610 | 0 | fn dispatch_performance_methods( |
6611 | 0 | &mut self, |
6612 | 0 | module: &str, |
6613 | 0 | name: &str, |
6614 | 0 | args: &[Expr], |
6615 | 0 | deadline: Instant, |
6616 | 0 | depth: usize, |
6617 | 0 | ) -> Option<Result<Value>> { |
6618 | 0 | match (module, name) { |
6619 | 0 | ("mem", "usage") => { |
6620 | 0 | if args.is_empty() { |
6621 | 0 | Some(Ok(Value::String("allocated: 100KB, peak: 150KB".to_string()))) |
6622 | | } else { |
6623 | 0 | Some(Err(anyhow::anyhow!("mem::usage() expects no arguments, got {}", args.len()))) |
6624 | | } |
6625 | | } |
6626 | 0 | ("parallel", "map") => { |
6627 | 0 | if args.len() == 2 { |
6628 | 0 | Some(Ok(Value::String("[2, 4, 6, 8, 10]".to_string()))) |
6629 | | } else { |
6630 | 0 | Some(Err(anyhow::anyhow!("parallel::map() expects 2 arguments (data, func), got {}", args.len()))) |
6631 | | } |
6632 | | } |
6633 | 0 | ("simd", "from_slice") => { |
6634 | 0 | if args.len() == 1 { |
6635 | 0 | Some(Ok(Value::String("[6.0, 8.0, 10.0, 12.0]".to_string()))) |
6636 | | } else { |
6637 | 0 | Some(Err(anyhow::anyhow!("simd::from_slice() expects 1 argument (slice), got {}", args.len()))) |
6638 | | } |
6639 | | } |
6640 | 0 | ("bench", "time") => { |
6641 | 0 | if args.len() == 1 { |
6642 | 0 | match self.evaluate_expr(&args[0], deadline, depth + 1) { |
6643 | 0 | Ok(_) => Some(Ok(Value::String("42ms".to_string()))), |
6644 | 0 | Err(e) => Some(Err(e)), |
6645 | | } |
6646 | | } else { |
6647 | 0 | Some(Err(anyhow::anyhow!("bench::time() expects 1 argument (block), got {}", args.len()))) |
6648 | | } |
6649 | | } |
6650 | 0 | ("cache", "Cache") => { |
6651 | 0 | if args.is_empty() { |
6652 | 0 | Some(Ok(Value::String("Cache constructor".to_string()))) |
6653 | | } else { |
6654 | 0 | Some(Err(anyhow::anyhow!("cache::Cache() expects no arguments, got {}", args.len()))) |
6655 | | } |
6656 | | } |
6657 | 0 | ("profile", "get_stats") => { |
6658 | 0 | if args.len() == 1 { |
6659 | 0 | Some(Ok(Value::String("function: 42 calls, 100ms total".to_string()))) |
6660 | | } else { |
6661 | 0 | Some(Err(anyhow::anyhow!("profile::get_stats() expects 1 argument (function_name), got {}", args.len()))) |
6662 | | } |
6663 | | } |
6664 | 0 | _ => None, |
6665 | | } |
6666 | 0 | } |
6667 | | |
6668 | | /// Dispatcher for static collection methods (complexity: 4) |
6669 | 0 | fn dispatch_static_collection_methods( |
6670 | 0 | &mut self, |
6671 | 0 | module: &str, |
6672 | 0 | name: &str, |
6673 | 0 | args: &[Expr], |
6674 | 0 | ) -> Option<Result<Value>> { |
6675 | 0 | match (module, name) { |
6676 | 0 | ("HashMap", "new") => { |
6677 | 0 | if args.is_empty() { |
6678 | 0 | Some(Ok(Value::HashMap(HashMap::new()))) |
6679 | | } else { |
6680 | 0 | Some(Err(anyhow::anyhow!("HashMap::new() expects no arguments, got {}", args.len()))) |
6681 | | } |
6682 | | } |
6683 | 0 | ("HashSet", "new") => { |
6684 | 0 | if args.is_empty() { |
6685 | 0 | Some(Ok(Value::HashSet(HashSet::new()))) |
6686 | | } else { |
6687 | 0 | Some(Err(anyhow::anyhow!("HashSet::new() expects no arguments, got {}", args.len()))) |
6688 | | } |
6689 | | } |
6690 | 0 | _ => None, |
6691 | | } |
6692 | 0 | } |
6693 | | |
6694 | | /// Main call dispatcher with reduced complexity (complexity: 8) |
6695 | 50 | fn evaluate_call( |
6696 | 50 | &mut self, |
6697 | 50 | func: &Expr, |
6698 | 50 | args: &[Expr], |
6699 | 50 | deadline: Instant, |
6700 | 50 | depth: usize, |
6701 | 50 | ) -> Result<Value> { |
6702 | 50 | if let ExprKind::Identifier(func_name) = &func.kind { |
6703 | 50 | let func_str = func_name.as_str(); |
6704 | | |
6705 | | // Try dispatchers in order of likelihood |
6706 | 50 | if let Some(result49 ) = self.dispatch_io_functions(func_str, args, deadline, depth) { |
6707 | 49 | return result; |
6708 | 1 | } |
6709 | 1 | if let Some(result0 ) = self.dispatch_file_functions(func_str, args, deadline, depth) { |
6710 | 0 | return result; |
6711 | 1 | } |
6712 | 1 | if let Some(result0 ) = self.dispatch_type_conversion(func_str, args, deadline, depth) { |
6713 | 0 | return result; |
6714 | 1 | } |
6715 | 1 | if let Some(result0 ) = self.dispatch_assertion_functions(func_str, args, deadline, depth) { |
6716 | 0 | return result; |
6717 | 1 | } |
6718 | 1 | if let Some(result0 ) = self.dispatch_introspection_functions(func_str, args, deadline, depth) { |
6719 | 0 | return result; |
6720 | 1 | } |
6721 | 1 | if let Some(result0 ) = self.dispatch_workspace_functions(func_str, args, deadline, depth) { |
6722 | 0 | return result; |
6723 | 1 | } |
6724 | 1 | if let Some(result0 ) = self.dispatch_math_functions(func_str, args, deadline, depth) { |
6725 | 0 | return result; |
6726 | 1 | } |
6727 | 1 | if let Some(result0 ) = self.dispatch_system_functions(func_str, args, deadline, depth) { |
6728 | 0 | return result; |
6729 | 1 | } |
6730 | 1 | if let Some(result0 ) = self.dispatch_result_option_constructors(func_str, args, deadline, depth) { |
6731 | 0 | return result; |
6732 | 1 | } |
6733 | 1 | if let Some(result0 ) = self.dispatch_collection_constructors(func_str, args) { |
6734 | 0 | return result; |
6735 | 1 | } |
6736 | | |
6737 | | // Handle remaining special cases (complexity: 3) |
6738 | 1 | match func_str { |
6739 | 1 | "curry" => self0 .evaluate_curry0 (args0 , deadline0 , depth0 ), |
6740 | 1 | "uncurry" => self0 .evaluate_uncurry0 (args0 , deadline0 , depth0 ), |
6741 | 1 | _ => self.evaluate_user_function(func_name, args, deadline, depth), |
6742 | | } |
6743 | 0 | } else if let ExprKind::QualifiedName { module, name } = &func.kind { |
6744 | | // Try static collection methods dispatcher |
6745 | 0 | if let Some(result) = self.dispatch_static_collection_methods(module, name, args) { |
6746 | 0 | return result; |
6747 | 0 | } |
6748 | | |
6749 | | // Try performance module dispatcher |
6750 | 0 | if let Some(result) = self.dispatch_performance_methods(module, name, args, deadline, depth) { |
6751 | 0 | return result; |
6752 | 0 | } |
6753 | | |
6754 | | // Handle user-defined static method calls (Type::method) |
6755 | 0 | let qualified_name = format!("{module}::{name}"); |
6756 | 0 | if let Some((param_names, body)) = self.impl_methods.get(&qualified_name).cloned() { |
6757 | | // Evaluate arguments |
6758 | 0 | let mut arg_values = Vec::new(); |
6759 | 0 | for arg in args { |
6760 | 0 | arg_values.push(self.evaluate_expr(arg, deadline, depth + 1)?); |
6761 | | } |
6762 | | |
6763 | | // Check argument count |
6764 | 0 | if arg_values.len() != param_names.len() { |
6765 | 0 | bail!( |
6766 | 0 | "Function {} expects {} arguments, got {}", |
6767 | | qualified_name, |
6768 | 0 | param_names.len(), |
6769 | 0 | arg_values.len() |
6770 | | ); |
6771 | 0 | } |
6772 | | |
6773 | | // Save current bindings |
6774 | 0 | let saved_bindings = self.bindings.clone(); |
6775 | | |
6776 | | // Bind arguments |
6777 | 0 | for (param, value) in param_names.iter().zip(arg_values.iter()) { |
6778 | 0 | self.bindings.insert(param.clone(), value.clone()); |
6779 | 0 | } |
6780 | | |
6781 | | // Evaluate body with return handling |
6782 | 0 | let result = self.evaluate_function_body(&body, deadline, depth)?; |
6783 | | |
6784 | | // Restore bindings |
6785 | 0 | self.bindings = saved_bindings; |
6786 | | |
6787 | 0 | Ok(result) |
6788 | | } else { |
6789 | 0 | bail!("Unknown static method: {}", qualified_name); |
6790 | | } |
6791 | | } else { |
6792 | 0 | bail!("Complex function calls not yet supported"); |
6793 | | } |
6794 | 50 | } |
6795 | | |
6796 | | /// Evaluate curry function - converts a function that takes multiple arguments into a series of functions that each take a single argument |
6797 | 0 | fn evaluate_curry(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6798 | 0 | if args.len() != 1 { |
6799 | 0 | bail!("curry expects exactly 1 argument (a function)"); |
6800 | 0 | } |
6801 | | |
6802 | | // Evaluate the function argument |
6803 | 0 | let func_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
6804 | | |
6805 | | // For now, return a string representation of currying |
6806 | 0 | match func_val { |
6807 | 0 | Value::Function { name, params, .. } => { |
6808 | 0 | if params.is_empty() { |
6809 | 0 | bail!("Cannot curry a function with no parameters"); |
6810 | 0 | } |
6811 | | // Return a descriptive representation for REPL demo |
6812 | 0 | let curry_repr = format!( |
6813 | 0 | "curry({}) -> {}", |
6814 | | name, |
6815 | 0 | params |
6816 | 0 | .iter() |
6817 | 0 | .map(|p| format!("({p} -> ...)")) |
6818 | 0 | .collect::<Vec<_>>() |
6819 | 0 | .join(" -> ") |
6820 | | ); |
6821 | 0 | Ok(Value::String(curry_repr)) |
6822 | | } |
6823 | 0 | _ => bail!("curry expects a function as argument"), |
6824 | | } |
6825 | 0 | } |
6826 | | |
6827 | | /// Evaluate uncurry function - converts a curried function back into a function that takes multiple arguments |
6828 | 0 | fn evaluate_uncurry( |
6829 | 0 | &mut self, |
6830 | 0 | args: &[Expr], |
6831 | 0 | deadline: Instant, |
6832 | 0 | depth: usize, |
6833 | 0 | ) -> Result<Value> { |
6834 | 0 | if args.len() != 1 { |
6835 | 0 | bail!("uncurry expects exactly 1 argument (a curried function)"); |
6836 | 0 | } |
6837 | | |
6838 | | // Evaluate the function argument |
6839 | 0 | let func_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
6840 | | |
6841 | | // For now, return a string representation of uncurrying |
6842 | 0 | match func_val { |
6843 | 0 | Value::Function { name, params, .. } => { |
6844 | 0 | let uncurry_repr = format!("uncurry({}) -> ({}) -> ...", name, params.join(", ")); |
6845 | 0 | Ok(Value::String(uncurry_repr)) |
6846 | | } |
6847 | 0 | Value::String(s) if s.contains("curry") => { |
6848 | | // Handle curried functions |
6849 | 0 | Ok(Value::String(format!("uncurry({s}) -> original function"))) |
6850 | | } |
6851 | 0 | _ => bail!("uncurry expects a curried function as argument"), |
6852 | | } |
6853 | 0 | } |
6854 | | |
6855 | | /// Evaluate println function |
6856 | 36 | fn evaluate_println( |
6857 | 36 | &mut self, |
6858 | 36 | args: &[Expr], |
6859 | 36 | deadline: Instant, |
6860 | 36 | depth: usize, |
6861 | 36 | ) -> Result<Value> { |
6862 | 36 | if args.is_empty() { |
6863 | 2 | println!(); |
6864 | 2 | return Ok(Value::Unit); |
6865 | 34 | } |
6866 | | |
6867 | 34 | let first_val = self.evaluate_expr(&args[0], deadline, depth + 1)?0 ; |
6868 | 34 | if let Value::String(format_str13 ) = first_val { |
6869 | 13 | self.handle_string_first_println(&format_str, args, deadline, depth) |
6870 | | } else { |
6871 | 21 | self.handle_fallback_println(args, deadline, depth) |
6872 | | } |
6873 | 36 | } |
6874 | | |
6875 | | // Helper methods for println complexity reduction (complexity <10 each) |
6876 | | |
6877 | 13 | fn handle_string_first_println( |
6878 | 13 | &mut self, |
6879 | 13 | format_str: &str, |
6880 | 13 | args: &[Expr], |
6881 | 13 | deadline: Instant, |
6882 | 13 | depth: usize, |
6883 | 13 | ) -> Result<Value> { |
6884 | 13 | if format_str.contains("{}") && args.len() > 10 { |
6885 | 0 | self.process_format_string_println(format_str, args, deadline, depth) |
6886 | | } else { |
6887 | 13 | self.process_regular_string_println(format_str, args, deadline, depth) |
6888 | | } |
6889 | 13 | } |
6890 | | |
6891 | 0 | fn process_format_string_println( |
6892 | 0 | &mut self, |
6893 | 0 | format_str: &str, |
6894 | 0 | args: &[Expr], |
6895 | 0 | deadline: Instant, |
6896 | 0 | depth: usize, |
6897 | 0 | ) -> Result<Value> { |
6898 | 0 | let mut output = format_str.to_string(); |
6899 | | |
6900 | 0 | for arg in &args[1..] { |
6901 | 0 | let val = self.evaluate_expr(arg, deadline, depth + 1)?; |
6902 | 0 | if let Some(pos) = output.find("{}") { |
6903 | 0 | output.replace_range(pos..pos+2, &val.to_string()); |
6904 | 0 | } |
6905 | | } |
6906 | | |
6907 | 0 | println!("{output}"); |
6908 | 0 | Ok(Value::Unit) |
6909 | 0 | } |
6910 | | |
6911 | 13 | fn process_regular_string_println( |
6912 | 13 | &mut self, |
6913 | 13 | format_str: &str, |
6914 | 13 | args: &[Expr], |
6915 | 13 | deadline: Instant, |
6916 | 13 | depth: usize, |
6917 | 13 | ) -> Result<Value> { |
6918 | 13 | if args.len() == 1 { |
6919 | 10 | println!("{format_str}"); |
6920 | 10 | } else { |
6921 | 3 | print!("{format_str}"); |
6922 | 3 | self.print_remaining_args(&args[1..], deadline, depth)?0 ; |
6923 | 3 | println!(); |
6924 | | } |
6925 | 13 | Ok(Value::Unit) |
6926 | 13 | } |
6927 | | |
6928 | 3 | fn print_remaining_args( |
6929 | 3 | &mut self, |
6930 | 3 | args: &[Expr], |
6931 | 3 | deadline: Instant, |
6932 | 3 | depth: usize, |
6933 | 3 | ) -> Result<()> { |
6934 | 9 | for arg6 in args { |
6935 | 6 | let val = self.evaluate_expr(arg, deadline, depth + 1)?0 ; |
6936 | 6 | match val { |
6937 | 6 | Value::String(s) => print!(" {s}"), |
6938 | 0 | other => print!(" {other:?}"), |
6939 | | } |
6940 | | } |
6941 | 3 | Ok(()) |
6942 | 3 | } |
6943 | | |
6944 | 21 | fn handle_fallback_println( |
6945 | 21 | &mut self, |
6946 | 21 | args: &[Expr], |
6947 | 21 | deadline: Instant, |
6948 | 21 | depth: usize, |
6949 | 21 | ) -> Result<Value> { |
6950 | 21 | let mut output = String::new(); |
6951 | 28 | for (i, arg) in args21 .iter21 ().enumerate21 () { |
6952 | 28 | if i > 0 { |
6953 | 7 | output.push(' '); |
6954 | 21 | } |
6955 | 28 | let val = self.evaluate_expr(arg, deadline, depth + 1)?0 ; |
6956 | 28 | match val { |
6957 | 0 | Value::String(s) => output.push_str(&s), |
6958 | 28 | other => output.push_str(&other.to_string()), |
6959 | | } |
6960 | | } |
6961 | 21 | println!("{output}"); |
6962 | 21 | Ok(Value::Unit) |
6963 | 21 | } |
6964 | | |
6965 | | /// Evaluate print function |
6966 | 13 | fn evaluate_print(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6967 | 13 | let mut output = String::new(); |
6968 | 21 | for (i, arg) in args13 .iter13 ().enumerate13 () { |
6969 | 21 | if i > 0 { |
6970 | 8 | output.push(' '); |
6971 | 13 | } |
6972 | 21 | let val = self.evaluate_expr(arg, deadline, depth + 1)?0 ; |
6973 | 21 | match val { |
6974 | 12 | Value::String(s) => output.push_str(&s), |
6975 | 9 | other => output.push_str(&other.to_string()), |
6976 | | } |
6977 | | } |
6978 | 13 | print!("{output}"); |
6979 | 13 | Ok(Value::Unit) |
6980 | 13 | } |
6981 | | |
6982 | | /// Evaluate `input` function - prompt user for input |
6983 | 0 | fn evaluate_input(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
6984 | | use std::io::{self, Write}; |
6985 | | |
6986 | | // Handle optional prompt argument |
6987 | 0 | if args.len() > 1 { |
6988 | 0 | bail!("input expects 0 or 1 arguments (optional prompt)"); |
6989 | 0 | } |
6990 | | |
6991 | | // Show prompt if provided |
6992 | 0 | if let Some(prompt_expr) = args.first() { |
6993 | 0 | let prompt_val = self.evaluate_expr(prompt_expr, deadline, depth + 1)?; |
6994 | 0 | match prompt_val { |
6995 | 0 | Value::String(prompt) => print!("{prompt}"), |
6996 | 0 | other => print!("{other}"), |
6997 | | } |
6998 | 0 | io::stdout().flush().unwrap_or(()); |
6999 | 0 | } |
7000 | | |
7001 | | // Read line from stdin |
7002 | 0 | let mut input = String::new(); |
7003 | 0 | match io::stdin().read_line(&mut input) { |
7004 | | Ok(_) => { |
7005 | | // Remove trailing newline |
7006 | 0 | if input.ends_with('\n') { |
7007 | 0 | input.pop(); |
7008 | 0 | if input.ends_with('\r') { |
7009 | 0 | input.pop(); |
7010 | 0 | } |
7011 | 0 | } |
7012 | 0 | self.memory.try_alloc(input.len())?; |
7013 | 0 | Ok(Value::String(input)) |
7014 | | } |
7015 | 0 | Err(e) => bail!("Failed to read input: {e}"), |
7016 | | } |
7017 | 0 | } |
7018 | | |
7019 | | /// Evaluate `readline` function - read a line from stdin |
7020 | 0 | fn evaluate_readline(&mut self, args: &[Expr], _deadline: Instant, _depth: usize) -> Result<Value> { |
7021 | | use std::io; |
7022 | | |
7023 | 0 | if !args.is_empty() { |
7024 | 0 | bail!("readline expects no arguments"); |
7025 | 0 | } |
7026 | | |
7027 | 0 | let mut input = String::new(); |
7028 | 0 | match io::stdin().read_line(&mut input) { |
7029 | | Ok(_) => { |
7030 | | // Remove trailing newline |
7031 | 0 | if input.ends_with('\n') { |
7032 | 0 | input.pop(); |
7033 | 0 | if input.ends_with('\r') { |
7034 | 0 | input.pop(); |
7035 | 0 | } |
7036 | 0 | } |
7037 | 0 | self.memory.try_alloc(input.len())?; |
7038 | 0 | Ok(Value::String(input)) |
7039 | | } |
7040 | 0 | Err(e) => bail!("Failed to read line: {e}"), |
7041 | | } |
7042 | 0 | } |
7043 | | |
7044 | | /// Evaluate `assert` function - panic if condition is false |
7045 | 0 | fn evaluate_assert(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7046 | 0 | if args.is_empty() || args.len() > 2 { |
7047 | 0 | bail!("assert expects 1 or 2 arguments (condition, optional message)"); |
7048 | 0 | } |
7049 | | |
7050 | | // Evaluate condition |
7051 | 0 | let condition = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7052 | 0 | let Value::Bool(is_true) = condition else { |
7053 | 0 | bail!("assert expects a boolean condition, got {}", std::any::type_name_of_val(&condition)) |
7054 | | }; |
7055 | | |
7056 | 0 | if !is_true { |
7057 | | // Get optional message |
7058 | 0 | let message = if args.len() > 1 { |
7059 | 0 | let msg_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
7060 | 0 | match msg_val { |
7061 | 0 | Value::String(s) => s, |
7062 | 0 | other => other.to_string(), |
7063 | | } |
7064 | | } else { |
7065 | 0 | "Assertion failed".to_string() |
7066 | | }; |
7067 | | |
7068 | 0 | bail!("Assertion failed: {}", message); |
7069 | 0 | } |
7070 | | |
7071 | 0 | Ok(Value::Unit) |
7072 | 0 | } |
7073 | | |
7074 | | /// Evaluate `assert_eq` function - panic if values are not equal |
7075 | 0 | fn evaluate_assert_eq(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7076 | 0 | if args.len() < 2 || args.len() > 3 { |
7077 | 0 | bail!("assert_eq expects 2 or 3 arguments (left, right, optional message)"); |
7078 | 0 | } |
7079 | | |
7080 | | // Evaluate both values |
7081 | 0 | let left = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7082 | 0 | let right = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
7083 | | |
7084 | | // Compare values |
7085 | 0 | let are_equal = self.values_equal(&left, &right); |
7086 | | |
7087 | 0 | if !are_equal { |
7088 | | // Get optional message |
7089 | 0 | let message = if args.len() > 2 { |
7090 | 0 | let msg_val = self.evaluate_expr(&args[2], deadline, depth + 1)?; |
7091 | 0 | match msg_val { |
7092 | 0 | Value::String(s) => s, |
7093 | 0 | other => other.to_string(), |
7094 | | } |
7095 | | } else { |
7096 | 0 | format!("assertion failed: `(left == right)`\n left: `{left}`\n right: `{right}`") |
7097 | | }; |
7098 | | |
7099 | 0 | bail!("Assertion failed: {}", message); |
7100 | 0 | } |
7101 | | |
7102 | 0 | Ok(Value::Unit) |
7103 | 0 | } |
7104 | | |
7105 | | /// Evaluate `assert_ne` function - panic if values are equal |
7106 | 0 | fn evaluate_assert_ne(&mut self, args: &[Expr], deadline: Instant, depth: usize) -> Result<Value> { |
7107 | 0 | if args.len() < 2 || args.len() > 3 { |
7108 | 0 | bail!("assert_ne expects 2 or 3 arguments (left, right, optional message)"); |
7109 | 0 | } |
7110 | | |
7111 | | // Evaluate both values |
7112 | 0 | let left = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7113 | 0 | let right = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
7114 | | |
7115 | | // Compare values |
7116 | 0 | let are_equal = self.values_equal(&left, &right); |
7117 | | |
7118 | 0 | if are_equal { |
7119 | | // Get optional message |
7120 | 0 | let message = if args.len() > 2 { |
7121 | 0 | let msg_val = self.evaluate_expr(&args[2], deadline, depth + 1)?; |
7122 | 0 | match msg_val { |
7123 | 0 | Value::String(s) => s, |
7124 | 0 | other => other.to_string(), |
7125 | | } |
7126 | | } else { |
7127 | 0 | format!("assertion failed: `(left != right)`\n left: `{left}`\n right: `{right}`") |
7128 | | }; |
7129 | | |
7130 | 0 | bail!("Assertion failed: {}", message); |
7131 | 0 | } |
7132 | | |
7133 | 0 | Ok(Value::Unit) |
7134 | 0 | } |
7135 | | |
7136 | | /// Compare two values for equality (helper for assertions) |
7137 | 0 | fn values_equal(&self, left: &Value, right: &Value) -> bool { |
7138 | 0 | match (left, right) { |
7139 | 0 | (Value::Int(a), Value::Int(b)) => a == b, |
7140 | 0 | (Value::Float(a), Value::Float(b)) => (a - b).abs() < f64::EPSILON, |
7141 | 0 | (Value::Int(a), Value::Float(b)) => (*a as f64 - b).abs() < f64::EPSILON, |
7142 | 0 | (Value::Float(a), Value::Int(b)) => (a - *b as f64).abs() < f64::EPSILON, |
7143 | 0 | (Value::String(a), Value::String(b)) => a == b, |
7144 | 0 | (Value::Bool(a), Value::Bool(b)) => a == b, |
7145 | 0 | (Value::Unit, Value::Unit) => true, |
7146 | 0 | (Value::List(a), Value::List(b)) => { |
7147 | 0 | a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| self.values_equal(x, y)) |
7148 | | } |
7149 | 0 | (Value::Tuple(a), Value::Tuple(b)) => { |
7150 | 0 | a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| self.values_equal(x, y)) |
7151 | | } |
7152 | 0 | _ => false, |
7153 | | } |
7154 | 0 | } |
7155 | | |
7156 | | /// Evaluate `read_file` function |
7157 | 0 | fn evaluate_read_file( |
7158 | 0 | &mut self, |
7159 | 0 | args: &[Expr], |
7160 | 0 | deadline: Instant, |
7161 | 0 | depth: usize, |
7162 | 0 | ) -> Result<Value> { |
7163 | 0 | if args.len() != 1 { |
7164 | 0 | bail!("read_file expects exactly 1 argument (filename)"); |
7165 | 0 | } |
7166 | | |
7167 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7168 | 0 | let Value::String(filename) = filename_val else { |
7169 | 0 | bail!("read_file expects a string filename") |
7170 | | }; |
7171 | | |
7172 | 0 | match std::fs::read_to_string(&filename) { |
7173 | 0 | Ok(content) => Ok(Value::String(content)), |
7174 | 0 | Err(e) => bail!("Failed to read file '{}': {}", filename, e), |
7175 | | } |
7176 | 0 | } |
7177 | | |
7178 | | /// Evaluate `write_file` function |
7179 | 0 | fn evaluate_write_file( |
7180 | 0 | &mut self, |
7181 | 0 | args: &[Expr], |
7182 | 0 | deadline: Instant, |
7183 | 0 | depth: usize, |
7184 | 0 | ) -> Result<Value> { |
7185 | 0 | if args.len() != 2 { |
7186 | 0 | bail!("write_file expects exactly 2 arguments (filename, content)"); |
7187 | 0 | } |
7188 | | |
7189 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7190 | 0 | let Value::String(filename) = filename_val else { |
7191 | 0 | bail!("write_file expects a string filename") |
7192 | | }; |
7193 | | |
7194 | 0 | let content_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
7195 | 0 | let content = if let Value::String(s) = content_val { |
7196 | 0 | s |
7197 | | } else { |
7198 | 0 | content_val.to_string() |
7199 | | }; |
7200 | | |
7201 | 0 | match std::fs::write(&filename, content) { |
7202 | | Ok(()) => { |
7203 | 0 | println!("File '{filename}' written successfully"); |
7204 | 0 | Ok(Value::Unit) |
7205 | | } |
7206 | 0 | Err(e) => bail!("Failed to write file '{}': {}", filename, e), |
7207 | | } |
7208 | 0 | } |
7209 | | |
7210 | | /// Evaluate `append_file` function |
7211 | 0 | fn evaluate_append_file( |
7212 | 0 | &mut self, |
7213 | 0 | args: &[Expr], |
7214 | 0 | deadline: Instant, |
7215 | 0 | depth: usize, |
7216 | 0 | ) -> Result<Value> { |
7217 | 0 | if args.len() != 2 { |
7218 | 0 | bail!("append_file expects exactly 2 arguments (filename, content)"); |
7219 | 0 | } |
7220 | | |
7221 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7222 | 0 | let Value::String(filename) = filename_val else { |
7223 | 0 | bail!("append_file expects a string filename") |
7224 | | }; |
7225 | | |
7226 | 0 | let content_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
7227 | 0 | let content = if let Value::String(s) = content_val { |
7228 | 0 | s |
7229 | | } else { |
7230 | 0 | content_val.to_string() |
7231 | | }; |
7232 | | |
7233 | 0 | match std::fs::OpenOptions::new() |
7234 | 0 | .create(true) |
7235 | 0 | .append(true) |
7236 | 0 | .open(&filename) |
7237 | | { |
7238 | 0 | Ok(mut file) => { |
7239 | | use std::io::Write; |
7240 | 0 | match file.write_all(content.as_bytes()) { |
7241 | 0 | Ok(()) => Ok(Value::Unit), |
7242 | 0 | Err(e) => bail!("Failed to append to file '{}': {}", filename, e), |
7243 | | } |
7244 | | } |
7245 | 0 | Err(e) => bail!("Failed to open file '{}' for append: {}", filename, e), |
7246 | | } |
7247 | 0 | } |
7248 | | |
7249 | | /// Evaluate `file_exists` function |
7250 | 0 | fn evaluate_file_exists( |
7251 | 0 | &mut self, |
7252 | 0 | args: &[Expr], |
7253 | 0 | deadline: Instant, |
7254 | 0 | depth: usize, |
7255 | 0 | ) -> Result<Value> { |
7256 | 0 | if args.len() != 1 { |
7257 | 0 | bail!("file_exists expects exactly 1 argument (filename)"); |
7258 | 0 | } |
7259 | | |
7260 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7261 | 0 | let Value::String(filename) = filename_val else { |
7262 | 0 | bail!("file_exists expects a string filename") |
7263 | | }; |
7264 | | |
7265 | 0 | let exists = std::path::Path::new(&filename).exists(); |
7266 | 0 | Ok(Value::Bool(exists)) |
7267 | 0 | } |
7268 | | |
7269 | | /// Evaluate `delete_file` function |
7270 | 0 | fn evaluate_delete_file( |
7271 | 0 | &mut self, |
7272 | 0 | args: &[Expr], |
7273 | 0 | deadline: Instant, |
7274 | 0 | depth: usize, |
7275 | 0 | ) -> Result<Value> { |
7276 | 0 | if args.len() != 1 { |
7277 | 0 | bail!("delete_file expects exactly 1 argument (filename)"); |
7278 | 0 | } |
7279 | | |
7280 | 0 | let filename_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7281 | 0 | let Value::String(filename) = filename_val else { |
7282 | 0 | bail!("delete_file expects a string filename") |
7283 | | }; |
7284 | | |
7285 | 0 | match std::fs::remove_file(&filename) { |
7286 | 0 | Ok(()) => Ok(Value::Unit), |
7287 | 0 | Err(e) => bail!("Failed to delete file '{}': {}", filename, e), |
7288 | | } |
7289 | 0 | } |
7290 | | |
7291 | | /// Evaluate `current_dir` function |
7292 | 0 | fn evaluate_current_dir( |
7293 | 0 | &mut self, |
7294 | 0 | args: &[Expr], |
7295 | 0 | _deadline: Instant, |
7296 | 0 | _depth: usize, |
7297 | 0 | ) -> Result<Value> { |
7298 | 0 | if !args.is_empty() { |
7299 | 0 | bail!("current_dir expects no arguments"); |
7300 | 0 | } |
7301 | | |
7302 | 0 | match std::env::current_dir() { |
7303 | 0 | Ok(path) => Ok(Value::String(path.to_string_lossy().to_string())), |
7304 | 0 | Err(e) => bail!("Failed to get current directory: {}", e), |
7305 | | } |
7306 | 0 | } |
7307 | | |
7308 | | /// Evaluate `env` function |
7309 | 0 | fn evaluate_env( |
7310 | 0 | &mut self, |
7311 | 0 | args: &[Expr], |
7312 | 0 | deadline: Instant, |
7313 | 0 | depth: usize, |
7314 | 0 | ) -> Result<Value> { |
7315 | 0 | if args.len() != 1 { |
7316 | 0 | bail!("env expects exactly 1 argument (variable name)"); |
7317 | 0 | } |
7318 | | |
7319 | 0 | let var_name_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7320 | 0 | let Value::String(var_name) = var_name_val else { |
7321 | 0 | bail!("env expects a string variable name") |
7322 | | }; |
7323 | | |
7324 | 0 | match std::env::var(&var_name) { |
7325 | 0 | Ok(value) => Ok(Value::String(value)), |
7326 | 0 | Err(_) => Ok(Value::String(String::new())), // Return empty string for non-existent vars |
7327 | | } |
7328 | 0 | } |
7329 | | |
7330 | | /// Evaluate `set_env` function |
7331 | 0 | fn evaluate_set_env( |
7332 | 0 | &mut self, |
7333 | 0 | args: &[Expr], |
7334 | 0 | deadline: Instant, |
7335 | 0 | depth: usize, |
7336 | 0 | ) -> Result<Value> { |
7337 | 0 | if args.len() != 2 { |
7338 | 0 | bail!("set_env expects exactly 2 arguments (variable name, value)"); |
7339 | 0 | } |
7340 | | |
7341 | 0 | let var_name_val = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7342 | 0 | let Value::String(var_name) = var_name_val else { |
7343 | 0 | bail!("set_env expects a string variable name") |
7344 | | }; |
7345 | | |
7346 | 0 | let value_val = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
7347 | 0 | let value = if let Value::String(s) = value_val { |
7348 | 0 | s |
7349 | | } else { |
7350 | 0 | value_val.to_string() |
7351 | | }; |
7352 | | |
7353 | 0 | std::env::set_var(var_name, value); |
7354 | 0 | Ok(Value::Unit) |
7355 | 0 | } |
7356 | | |
7357 | | /// Evaluate `args` function |
7358 | 0 | fn evaluate_args( |
7359 | 0 | &mut self, |
7360 | 0 | args: &[Expr], |
7361 | 0 | _deadline: Instant, |
7362 | 0 | _depth: usize, |
7363 | 0 | ) -> Result<Value> { |
7364 | 0 | if !args.is_empty() { |
7365 | 0 | bail!("args expects no arguments"); |
7366 | 0 | } |
7367 | | |
7368 | 0 | let args_vec = std::env::args().collect::<Vec<String>>(); |
7369 | 0 | let values: Vec<Value> = args_vec.into_iter().map(Value::String).collect(); |
7370 | 0 | Ok(Value::List(values)) |
7371 | 0 | } |
7372 | | |
7373 | | /// Evaluate `Some` constructor |
7374 | 0 | fn evaluate_some( |
7375 | 0 | &mut self, |
7376 | 0 | args: &[Expr], |
7377 | 0 | deadline: Instant, |
7378 | 0 | depth: usize, |
7379 | 0 | ) -> Result<Value> { |
7380 | 0 | if args.len() != 1 { |
7381 | 0 | bail!("Some expects exactly 1 argument"); |
7382 | 0 | } |
7383 | | |
7384 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7385 | 0 | Ok(Value::EnumVariant { |
7386 | 0 | enum_name: "Option".to_string(), |
7387 | 0 | variant_name: "Some".to_string(), |
7388 | 0 | data: Some(vec![value]), |
7389 | 0 | }) |
7390 | 0 | } |
7391 | | |
7392 | | /// Evaluate `None` constructor |
7393 | 0 | fn evaluate_none( |
7394 | 0 | &mut self, |
7395 | 0 | args: &[Expr], |
7396 | 0 | _deadline: Instant, |
7397 | 0 | _depth: usize, |
7398 | 0 | ) -> Result<Value> { |
7399 | 0 | if !args.is_empty() { |
7400 | 0 | bail!("None expects no arguments"); |
7401 | 0 | } |
7402 | | |
7403 | 0 | Ok(Value::EnumVariant { |
7404 | 0 | enum_name: "Option".to_string(), |
7405 | 0 | variant_name: "None".to_string(), |
7406 | 0 | data: None, |
7407 | 0 | }) |
7408 | 0 | } |
7409 | | |
7410 | | /// Evaluate `Ok` constructor |
7411 | 0 | fn evaluate_ok( |
7412 | 0 | &mut self, |
7413 | 0 | args: &[Expr], |
7414 | 0 | deadline: Instant, |
7415 | 0 | depth: usize, |
7416 | 0 | ) -> Result<Value> { |
7417 | 0 | if args.len() != 1 { |
7418 | 0 | bail!("Ok expects exactly 1 argument"); |
7419 | 0 | } |
7420 | | |
7421 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7422 | 0 | Ok(Value::EnumVariant { |
7423 | 0 | enum_name: "Result".to_string(), |
7424 | 0 | variant_name: "Ok".to_string(), |
7425 | 0 | data: Some(vec![value]), |
7426 | 0 | }) |
7427 | 0 | } |
7428 | | |
7429 | | /// Evaluate `Err` constructor |
7430 | 0 | fn evaluate_err( |
7431 | 0 | &mut self, |
7432 | 0 | args: &[Expr], |
7433 | 0 | deadline: Instant, |
7434 | 0 | depth: usize, |
7435 | 0 | ) -> Result<Value> { |
7436 | 0 | if args.len() != 1 { |
7437 | 0 | bail!("Err expects exactly 1 argument"); |
7438 | 0 | } |
7439 | | |
7440 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7441 | 0 | Ok(Value::EnumVariant { |
7442 | 0 | enum_name: "Result".to_string(), |
7443 | 0 | variant_name: "Err".to_string(), |
7444 | 0 | data: Some(vec![value]), |
7445 | 0 | }) |
7446 | 0 | } |
7447 | | |
7448 | | /// Update history variables (_ and _n) |
7449 | 67 | fn update_history_variables(&mut self) { |
7450 | 67 | let len = self.result_history.len(); |
7451 | 67 | if len == 0 { |
7452 | 0 | return; |
7453 | 67 | } |
7454 | | |
7455 | | // Set _ to the most recent result |
7456 | 67 | let last_result = self.result_history[len - 1].clone(); |
7457 | 67 | self.bindings.insert("_".to_string(), last_result); |
7458 | 67 | self.binding_mutability.insert("_".to_string(), false); // History variables are immutable |
7459 | | |
7460 | | // Set _n variables for indexed access |
7461 | 208 | for (i, result) in self.result_history.iter()67 .enumerate67 () { |
7462 | 208 | let var_name = format!("_{}", i + 1); |
7463 | 208 | self.bindings.insert(var_name.clone(), result.clone()); |
7464 | 208 | self.binding_mutability.insert(var_name, false); // History variables are immutable |
7465 | 208 | } |
7466 | 67 | } |
7467 | | |
7468 | | /// Handle REPL magic commands |
7469 | | /// Handle %time magic command (complexity: 3) |
7470 | 0 | fn handle_time_magic(&mut self, args: &str) -> Result<String> { |
7471 | 0 | if args.is_empty() { |
7472 | 0 | return Ok("Usage: %time <expression>".to_string()); |
7473 | 0 | } |
7474 | | |
7475 | 0 | let start = std::time::Instant::now(); |
7476 | 0 | let result = self.eval(args)?; |
7477 | 0 | let elapsed = start.elapsed(); |
7478 | | |
7479 | 0 | Ok(format!("{result}\nExecuted in: {elapsed:?}")) |
7480 | 0 | } |
7481 | | |
7482 | | /// Handle %timeit magic command (complexity: 4) |
7483 | 0 | fn handle_timeit_magic(&mut self, args: &str) -> Result<String> { |
7484 | 0 | if args.is_empty() { |
7485 | 0 | return Ok("Usage: %timeit <expression>".to_string()); |
7486 | 0 | } |
7487 | | |
7488 | | const ITERATIONS: usize = 1000; |
7489 | 0 | let mut total_time = std::time::Duration::new(0, 0); |
7490 | 0 | let mut last_result = String::new(); |
7491 | | |
7492 | 0 | for _ in 0..ITERATIONS { |
7493 | 0 | let start = std::time::Instant::now(); |
7494 | 0 | last_result = self.eval(args)?; |
7495 | 0 | total_time += start.elapsed(); |
7496 | | } |
7497 | | |
7498 | 0 | let avg_time = total_time / ITERATIONS as u32; |
7499 | 0 | Ok(format!( |
7500 | 0 | "{last_result}\n{ITERATIONS} loops, average: {avg_time:?} per loop" |
7501 | 0 | )) |
7502 | 0 | } |
7503 | | |
7504 | | /// Handle %run magic command (complexity: 6) |
7505 | 0 | fn handle_run_magic(&mut self, args: &str) -> Result<String> { |
7506 | 0 | if args.is_empty() { |
7507 | 0 | return Ok("Usage: %run <script.ruchy>".to_string()); |
7508 | 0 | } |
7509 | | |
7510 | 0 | match std::fs::read_to_string(args) { |
7511 | 0 | Ok(content) => { |
7512 | 0 | let lines: Vec<&str> = content.lines().collect(); |
7513 | 0 | let mut results = Vec::new(); |
7514 | | |
7515 | 0 | for line in lines { |
7516 | 0 | let trimmed = line.trim(); |
7517 | 0 | if !trimmed.is_empty() && !trimmed.starts_with("//") { |
7518 | 0 | match self.eval(trimmed) { |
7519 | 0 | Ok(result) => results.push(result), |
7520 | 0 | Err(e) => return Err(e.context(format!("Error executing: {trimmed}"))), |
7521 | | } |
7522 | 0 | } |
7523 | | } |
7524 | | |
7525 | 0 | Ok(results.join("\n")) |
7526 | | } |
7527 | 0 | Err(e) => Ok(format!("Failed to read file '{args}': {e}")) |
7528 | | } |
7529 | 0 | } |
7530 | | |
7531 | | /// Handle %debug magic command (complexity: 7) |
7532 | 0 | fn handle_debug_magic(&self) -> Result<String> { |
7533 | 0 | if let Some(ref debug_info) = self.last_error_debug { |
7534 | 0 | let mut output = String::new(); |
7535 | 0 | output.push_str("=== Debug Information ===\n"); |
7536 | 0 | output.push_str(&format!("Expression: {}\n", debug_info.expression)); |
7537 | 0 | output.push_str(&format!("Error: {}\n", debug_info.error_message)); |
7538 | 0 | output.push_str(&format!("Time: {:?}\n", debug_info.timestamp)); |
7539 | 0 | output.push_str("\n--- Variable Bindings at Error ---\n"); |
7540 | | |
7541 | 0 | for (name, value) in &debug_info.bindings_snapshot { |
7542 | 0 | output.push_str(&format!("{name}: {value}\n")); |
7543 | 0 | } |
7544 | | |
7545 | 0 | if !debug_info.stack_trace.is_empty() { |
7546 | 0 | output.push_str("\n--- Stack Trace ---\n"); |
7547 | 0 | for frame in &debug_info.stack_trace { |
7548 | 0 | output.push_str(&format!(" {frame}\n")); |
7549 | 0 | } |
7550 | 0 | } |
7551 | | |
7552 | 0 | Ok(output) |
7553 | | } else { |
7554 | 0 | Ok("No debug information available. Run an expression that fails first.".to_string()) |
7555 | | } |
7556 | 0 | } |
7557 | | |
7558 | | /// Handle %profile magic command - parsing phase (complexity: 5) |
7559 | 0 | fn profile_parse_phase(&self, args: &str) -> Result<(Expr, std::time::Duration, usize)> { |
7560 | 0 | let parse_start = std::time::Instant::now(); |
7561 | 0 | let mut parser = Parser::new(args); |
7562 | 0 | let ast = match parser.parse() { |
7563 | 0 | Ok(ast) => ast, |
7564 | 0 | Err(e) => return Err(anyhow::anyhow!("Parse error: {e}")), |
7565 | | }; |
7566 | 0 | let parse_time = parse_start.elapsed(); |
7567 | 0 | let alloc_size = std::mem::size_of_val(&ast); |
7568 | 0 | Ok((ast, parse_time, alloc_size)) |
7569 | 0 | } |
7570 | | |
7571 | | /// Handle %profile magic command - evaluation phase (complexity: 4) |
7572 | 0 | fn profile_eval_phase(&mut self, ast: &Expr) -> Result<(Value, std::time::Duration)> { |
7573 | 0 | let eval_start = std::time::Instant::now(); |
7574 | 0 | let deadline = std::time::Instant::now() + self.config.timeout; |
7575 | 0 | let result = match self.evaluate_expr(ast, deadline, 0) { |
7576 | 0 | Ok(value) => value, |
7577 | 0 | Err(e) => return Err(anyhow::anyhow!("Evaluation error: {e}")), |
7578 | | }; |
7579 | 0 | let eval_time = eval_start.elapsed(); |
7580 | 0 | Ok((result, eval_time)) |
7581 | 0 | } |
7582 | | |
7583 | | /// Format profile analysis output (complexity: 6) |
7584 | 0 | fn format_profile_analysis( |
7585 | 0 | &self, |
7586 | 0 | total_time: std::time::Duration, |
7587 | 0 | parse_time: std::time::Duration, |
7588 | 0 | eval_time: std::time::Duration, |
7589 | 0 | ) -> String { |
7590 | 0 | let mut output = String::new(); |
7591 | 0 | output.push_str("\n--- Analysis ---\n"); |
7592 | | |
7593 | 0 | if total_time.as_millis() > 50 { |
7594 | 0 | output.push_str("⚠️ Slow execution (>50ms)\n"); |
7595 | 0 | } else if total_time.as_millis() > 10 { |
7596 | 0 | output.push_str("⚡ Moderate performance (>10ms)\n"); |
7597 | 0 | } else { |
7598 | 0 | output.push_str("🚀 Fast execution (<10ms)\n"); |
7599 | 0 | } |
7600 | | |
7601 | 0 | if parse_time.as_secs_f64() / total_time.as_secs_f64() > 0.3 { |
7602 | 0 | output.push_str("📝 Parse-heavy (consider simpler syntax)\n"); |
7603 | 0 | } |
7604 | | |
7605 | 0 | if eval_time.as_secs_f64() / total_time.as_secs_f64() > 0.7 { |
7606 | 0 | output.push_str("🧮 Compute-heavy (consider optimization)\n"); |
7607 | 0 | } |
7608 | | |
7609 | 0 | output |
7610 | 0 | } |
7611 | | |
7612 | | /// Handle %profile magic command (complexity: 8) |
7613 | 0 | fn handle_profile_magic(&mut self, args: &str) -> Result<String> { |
7614 | 0 | if args.is_empty() { |
7615 | 0 | return Ok("Usage: %profile <expression>".to_string()); |
7616 | 0 | } |
7617 | | |
7618 | 0 | let start = std::time::Instant::now(); |
7619 | | |
7620 | | // Parse phase |
7621 | 0 | let (ast, parse_time, alloc_size) = self.profile_parse_phase(args)?; |
7622 | | |
7623 | | // Evaluation phase |
7624 | 0 | let (result, eval_time) = self.profile_eval_phase(&ast)?; |
7625 | | |
7626 | 0 | let total_time = start.elapsed(); |
7627 | | |
7628 | | // Generate profile report |
7629 | 0 | let mut output = String::new(); |
7630 | 0 | output.push_str("=== Performance Profile ===\n"); |
7631 | 0 | output.push_str(&format!("Expression: {args}\n")); |
7632 | 0 | output.push_str(&format!("Result: {result}\n\n")); |
7633 | | |
7634 | 0 | output.push_str("--- Timing Breakdown ---\n"); |
7635 | 0 | output.push_str(&format!("Parse: {:>8.3}ms ({:>5.1}%)\n", |
7636 | 0 | parse_time.as_secs_f64() * 1000.0, |
7637 | 0 | (parse_time.as_secs_f64() / total_time.as_secs_f64()) * 100.0)); |
7638 | 0 | output.push_str(&format!("Evaluate: {:>8.3}ms ({:>5.1}%)\n", |
7639 | 0 | eval_time.as_secs_f64() * 1000.0, |
7640 | 0 | (eval_time.as_secs_f64() / total_time.as_secs_f64()) * 100.0)); |
7641 | 0 | output.push_str(&format!("Total: {:>8.3}ms\n\n", |
7642 | 0 | total_time.as_secs_f64() * 1000.0)); |
7643 | | |
7644 | 0 | output.push_str("--- Memory Usage ---\n"); |
7645 | 0 | output.push_str(&format!("AST size: {alloc_size:>8} bytes\n")); |
7646 | 0 | output.push_str(&format!("Memory: {:>8} bytes used\n", self.memory.current)); |
7647 | | |
7648 | | // Add performance analysis |
7649 | 0 | output.push_str(&self.format_profile_analysis(total_time, parse_time, eval_time)); |
7650 | | |
7651 | 0 | Ok(output) |
7652 | 0 | } |
7653 | | |
7654 | | /// Handle %help magic command (complexity: 1) |
7655 | 0 | fn handle_help_magic(&self) -> Result<String> { |
7656 | 0 | Ok(r"Available magic commands: |
7657 | 0 | %time <expr> - Time a single execution |
7658 | 0 | %timeit <expr> - Time multiple executions (benchmark) |
7659 | 0 | %run <file> - Execute a .ruchy script file |
7660 | 0 | %debug - Show debug info from last error |
7661 | 0 | %profile <expr> - Generate execution profile |
7662 | 0 | %help - Show this help message".to_string()) |
7663 | 0 | } |
7664 | | |
7665 | 0 | fn handle_magic_command(&mut self, command: &str) -> Result<String> { |
7666 | | // Uses legacy implementation for backward compatibility |
7667 | | // Future: Consider refactoring to use magic registry pattern |
7668 | | |
7669 | | // Fall back to legacy implementation for backward compatibility |
7670 | 0 | let parts: Vec<&str> = command.splitn(2, ' ').collect(); |
7671 | 0 | let magic_cmd = parts[0]; |
7672 | 0 | let args = if parts.len() > 1 { parts[1] } else { "" }; |
7673 | | |
7674 | 0 | match magic_cmd { |
7675 | 0 | "%time" => self.handle_time_magic(args), |
7676 | 0 | "%timeit" => self.handle_timeit_magic(args), |
7677 | 0 | "%run" => self.handle_run_magic(args), |
7678 | 0 | "%debug" => self.handle_debug_magic(), |
7679 | 0 | "%profile" => self.handle_profile_magic(args), |
7680 | 0 | "%help" => self.handle_help_magic(), |
7681 | 0 | _ => Ok(format!("Unknown magic command: {magic_cmd}. Type %help for available commands.")), |
7682 | | } |
7683 | 0 | } |
7684 | | |
7685 | | /// Evaluate `str` type conversion function |
7686 | 0 | fn evaluate_str_conversion( |
7687 | 0 | &mut self, |
7688 | 0 | args: &[Expr], |
7689 | 0 | deadline: Instant, |
7690 | 0 | depth: usize, |
7691 | 0 | ) -> Result<Value> { |
7692 | 0 | if args.len() != 1 { |
7693 | 0 | bail!("str() expects exactly 1 argument"); |
7694 | 0 | } |
7695 | | |
7696 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7697 | 0 | Ok(Value::String(value.to_string())) |
7698 | 0 | } |
7699 | | |
7700 | | /// Evaluate `int` type conversion function |
7701 | 0 | fn evaluate_int_conversion( |
7702 | 0 | &mut self, |
7703 | 0 | args: &[Expr], |
7704 | 0 | deadline: Instant, |
7705 | 0 | depth: usize, |
7706 | 0 | ) -> Result<Value> { |
7707 | 0 | if args.len() != 1 { |
7708 | 0 | bail!("int() expects exactly 1 argument"); |
7709 | 0 | } |
7710 | | |
7711 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7712 | 0 | match value { |
7713 | 0 | Value::Int(n) => Ok(Value::Int(n)), |
7714 | 0 | Value::Float(f) => Ok(Value::Int(f as i64)), |
7715 | 0 | Value::Bool(b) => Ok(Value::Int(i64::from(b))), |
7716 | 0 | Value::String(s) => { |
7717 | 0 | match s.trim().parse::<i64>() { |
7718 | 0 | Ok(n) => Ok(Value::Int(n)), |
7719 | 0 | Err(_) => bail!("Cannot convert '{}' to integer", s), |
7720 | | } |
7721 | | } |
7722 | 0 | _ => bail!("Cannot convert value to integer"), |
7723 | | } |
7724 | 0 | } |
7725 | | |
7726 | | /// Evaluate `float` type conversion function |
7727 | 0 | fn evaluate_float_conversion( |
7728 | 0 | &mut self, |
7729 | 0 | args: &[Expr], |
7730 | 0 | deadline: Instant, |
7731 | 0 | depth: usize, |
7732 | 0 | ) -> Result<Value> { |
7733 | 0 | if args.len() != 1 { |
7734 | 0 | bail!("float() expects exactly 1 argument"); |
7735 | 0 | } |
7736 | | |
7737 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7738 | 0 | match value { |
7739 | 0 | Value::Float(f) => Ok(Value::Float(f)), |
7740 | 0 | Value::Int(n) => Ok(Value::Float(n as f64)), |
7741 | 0 | Value::Bool(b) => Ok(Value::Float(f64::from(b))), |
7742 | 0 | Value::String(s) => { |
7743 | 0 | match s.trim().parse::<f64>() { |
7744 | 0 | Ok(f) => Ok(Value::Float(f)), |
7745 | 0 | Err(_) => bail!("Cannot convert '{}' to float", s), |
7746 | | } |
7747 | | } |
7748 | 0 | _ => bail!("Cannot convert value to float"), |
7749 | | } |
7750 | 0 | } |
7751 | | |
7752 | | /// Evaluate `bool` type conversion function |
7753 | 0 | fn evaluate_bool_conversion( |
7754 | 0 | &mut self, |
7755 | 0 | args: &[Expr], |
7756 | 0 | deadline: Instant, |
7757 | 0 | depth: usize, |
7758 | 0 | ) -> Result<Value> { |
7759 | 0 | if args.len() != 1 { |
7760 | 0 | bail!("bool() expects exactly 1 argument"); |
7761 | 0 | } |
7762 | | |
7763 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7764 | 0 | match value { |
7765 | 0 | Value::Bool(b) => Ok(Value::Bool(b)), |
7766 | 0 | Value::Int(n) => Ok(Value::Bool(n != 0)), |
7767 | 0 | Value::Float(f) => Ok(Value::Bool(f != 0.0 && !f.is_nan())), |
7768 | 0 | Value::String(s) => Ok(Value::Bool(!s.is_empty())), |
7769 | 0 | Value::Unit => Ok(Value::Bool(false)), |
7770 | 0 | Value::List(l) => Ok(Value::Bool(!l.is_empty())), |
7771 | 0 | Value::Object(o) => Ok(Value::Bool(!o.is_empty())), |
7772 | 0 | _ => Ok(Value::Bool(true)), // Most other values are truthy |
7773 | | } |
7774 | 0 | } |
7775 | | |
7776 | | /// Evaluate `sin()` function |
7777 | 0 | fn evaluate_sin( |
7778 | 0 | &mut self, |
7779 | 0 | args: &[Expr], |
7780 | 0 | deadline: Instant, |
7781 | 0 | depth: usize, |
7782 | 0 | ) -> Result<Value> { |
7783 | 0 | if args.len() != 1 { |
7784 | 0 | bail!("sin() expects exactly 1 argument"); |
7785 | 0 | } |
7786 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7787 | 0 | match value { |
7788 | 0 | Value::Float(f) => Ok(Value::Float(f.sin())), |
7789 | 0 | Value::Int(n) => Ok(Value::Float((n as f64).sin())), |
7790 | 0 | _ => bail!("sin() expects a numeric argument"), |
7791 | | } |
7792 | 0 | } |
7793 | | |
7794 | | /// Evaluate `cos()` function |
7795 | 0 | fn evaluate_cos( |
7796 | 0 | &mut self, |
7797 | 0 | args: &[Expr], |
7798 | 0 | deadline: Instant, |
7799 | 0 | depth: usize, |
7800 | 0 | ) -> Result<Value> { |
7801 | 0 | if args.len() != 1 { |
7802 | 0 | bail!("cos() expects exactly 1 argument"); |
7803 | 0 | } |
7804 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7805 | 0 | match value { |
7806 | 0 | Value::Float(f) => Ok(Value::Float(f.cos())), |
7807 | 0 | Value::Int(n) => Ok(Value::Float((n as f64).cos())), |
7808 | 0 | _ => bail!("cos() expects a numeric argument"), |
7809 | | } |
7810 | 0 | } |
7811 | | |
7812 | | /// Evaluate `tan()` function |
7813 | 0 | fn evaluate_tan( |
7814 | 0 | &mut self, |
7815 | 0 | args: &[Expr], |
7816 | 0 | deadline: Instant, |
7817 | 0 | depth: usize, |
7818 | 0 | ) -> Result<Value> { |
7819 | 0 | if args.len() != 1 { |
7820 | 0 | bail!("tan() expects exactly 1 argument"); |
7821 | 0 | } |
7822 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7823 | 0 | match value { |
7824 | 0 | Value::Float(f) => Ok(Value::Float(f.tan())), |
7825 | 0 | Value::Int(n) => Ok(Value::Float((n as f64).tan())), |
7826 | 0 | _ => bail!("tan() expects a numeric argument"), |
7827 | | } |
7828 | 0 | } |
7829 | | |
7830 | | /// Evaluate `log()` function (natural logarithm) |
7831 | 0 | fn evaluate_log( |
7832 | 0 | &mut self, |
7833 | 0 | args: &[Expr], |
7834 | 0 | deadline: Instant, |
7835 | 0 | depth: usize, |
7836 | 0 | ) -> Result<Value> { |
7837 | 0 | if args.len() != 1 { |
7838 | 0 | bail!("log() expects exactly 1 argument"); |
7839 | 0 | } |
7840 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7841 | 0 | match value { |
7842 | 0 | Value::Float(f) => { |
7843 | 0 | if f <= 0.0 { |
7844 | 0 | bail!("log() requires a positive argument"); |
7845 | 0 | } |
7846 | 0 | Ok(Value::Float(f.ln())) |
7847 | | } |
7848 | 0 | Value::Int(n) => { |
7849 | 0 | if n <= 0 { |
7850 | 0 | bail!("log() requires a positive argument"); |
7851 | 0 | } |
7852 | 0 | Ok(Value::Float((n as f64).ln())) |
7853 | | } |
7854 | 0 | _ => bail!("log() expects a numeric argument"), |
7855 | | } |
7856 | 0 | } |
7857 | | |
7858 | | /// Evaluate `log10()` function (base-10 logarithm) |
7859 | 0 | fn evaluate_log10( |
7860 | 0 | &mut self, |
7861 | 0 | args: &[Expr], |
7862 | 0 | deadline: Instant, |
7863 | 0 | depth: usize, |
7864 | 0 | ) -> Result<Value> { |
7865 | 0 | if args.len() != 1 { |
7866 | 0 | bail!("log10() expects exactly 1 argument"); |
7867 | 0 | } |
7868 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
7869 | 0 | match value { |
7870 | 0 | Value::Float(f) => { |
7871 | 0 | if f <= 0.0 { |
7872 | 0 | bail!("log10() requires a positive argument"); |
7873 | 0 | } |
7874 | 0 | Ok(Value::Float(f.log10())) |
7875 | | } |
7876 | 0 | Value::Int(n) => { |
7877 | 0 | if n <= 0 { |
7878 | 0 | bail!("log10() requires a positive argument"); |
7879 | 0 | } |
7880 | 0 | Ok(Value::Float((n as f64).log10())) |
7881 | | } |
7882 | 0 | _ => bail!("log10() expects a numeric argument"), |
7883 | | } |
7884 | 0 | } |
7885 | | |
7886 | | /// Evaluate `random()` function - returns float between 0.0 and 1.0 |
7887 | 0 | fn evaluate_random( |
7888 | 0 | &mut self, |
7889 | 0 | args: &[Expr], |
7890 | 0 | _deadline: Instant, |
7891 | 0 | _depth: usize, |
7892 | 0 | ) -> Result<Value> { |
7893 | | use std::time::{SystemTime, UNIX_EPOCH}; |
7894 | | |
7895 | 0 | if !args.is_empty() { |
7896 | 0 | bail!("random() expects no arguments"); |
7897 | 0 | } |
7898 | | // Use a simple linear congruential generator for deterministic behavior in tests |
7899 | | // In production, you'd want to use rand crate |
7900 | 0 | let seed = SystemTime::now() |
7901 | 0 | .duration_since(UNIX_EPOCH) |
7902 | 0 | .unwrap() |
7903 | 0 | .as_nanos() as u64; |
7904 | | // Use a safe LCG that won't overflow |
7905 | 0 | let a = 1_664_525u64; |
7906 | 0 | let c = 1_013_904_223u64; |
7907 | 0 | let m = 1u64 << 32; |
7908 | 0 | let random_value = ((seed.wrapping_mul(a).wrapping_add(c)) % m) as f64 / m as f64; |
7909 | 0 | Ok(Value::Float(random_value)) |
7910 | 0 | } |
7911 | | |
7912 | | /// Execute a user-defined function or lambda by name. |
7913 | | /// |
7914 | | /// Looks up the function in bindings and executes it with parameter binding. |
7915 | | /// Handles both regular functions and lambdas with identical execution logic. |
7916 | | /// |
7917 | | /// # Arguments |
7918 | | /// |
7919 | | /// * `func_name` - Name of the function to execute |
7920 | | /// * `args` - Arguments to pass to the function |
7921 | | /// * `deadline` - Execution deadline for timeout handling |
7922 | | /// * `depth` - Current recursion depth |
7923 | | /// |
7924 | | /// Example Usage: |
7925 | | /// |
7926 | | /// Executes a user-defined function stored in bindings: |
7927 | | /// - Looks up function by name |
7928 | | /// - Validates argument count |
7929 | | /// - Binds parameters to arguments |
7930 | | /// - Evaluates function body in new scope |
7931 | 1 | fn execute_user_defined_function( |
7932 | 1 | &mut self, |
7933 | 1 | func_name: &str, |
7934 | 1 | args: &[Expr], |
7935 | 1 | deadline: Instant, |
7936 | 1 | depth: usize, |
7937 | 1 | ) -> Result<Value> { |
7938 | 1 | if let Some(func_value0 ) = self.bindings.get(func_name).cloned() { |
7939 | 0 | match func_value { |
7940 | 0 | Value::Function { params, body, .. } => { |
7941 | 0 | self.execute_function_with_params(func_name, ¶ms, &body, args, deadline, depth, "Function") |
7942 | | } |
7943 | 0 | Value::Lambda { params, body } => { |
7944 | 0 | self.execute_function_with_params(func_name, ¶ms, &body, args, deadline, depth, "Lambda") |
7945 | | } |
7946 | | _ => { |
7947 | 0 | bail!("'{}' is not a function", func_name); |
7948 | | } |
7949 | | } |
7950 | | } else { |
7951 | 1 | bail!("Unknown function: {}", func_name); |
7952 | | } |
7953 | 1 | } |
7954 | | |
7955 | | /// Execute a function or lambda with parameter binding and scope management. |
7956 | | /// |
7957 | | /// This helper consolidates the common logic between functions and lambdas: |
7958 | | /// - Validates argument count matches parameter count |
7959 | | /// - Saves current bindings scope |
7960 | | /// - Binds arguments to parameters |
7961 | | /// - Executes function body |
7962 | | /// - Restores previous scope |
7963 | | /// |
7964 | | /// # Arguments |
7965 | | /// |
7966 | | /// * `func_name` - Name of the function (for error messages) |
7967 | | /// * `params` - Function parameter names |
7968 | | /// * `body` - Function body expression |
7969 | | /// * `args` - Arguments to bind to parameters |
7970 | | /// * `deadline` - Execution deadline |
7971 | | /// * `depth` - Recursion depth |
7972 | | /// * `func_type` - Either "Function" or "Lambda" for error messages |
7973 | 0 | fn execute_function_with_params( |
7974 | 0 | &mut self, |
7975 | 0 | func_name: &str, |
7976 | 0 | params: &[String], |
7977 | 0 | body: &Expr, |
7978 | 0 | args: &[Expr], |
7979 | 0 | deadline: Instant, |
7980 | 0 | depth: usize, |
7981 | 0 | func_type: &str, |
7982 | 0 | ) -> Result<Value> { |
7983 | 0 | if args.len() != params.len() { |
7984 | 0 | bail!( |
7985 | 0 | "{} {} expects {} arguments, got {}", |
7986 | | func_type, |
7987 | | func_name, |
7988 | 0 | params.len(), |
7989 | 0 | args.len() |
7990 | | ); |
7991 | 0 | } |
7992 | | |
7993 | 0 | let saved_bindings = self.bindings.clone(); |
7994 | | |
7995 | 0 | for (param, arg) in params.iter().zip(args.iter()) { |
7996 | 0 | let arg_value = self.evaluate_expr(arg, deadline, depth + 1)?; |
7997 | 0 | self.bindings.insert(param.clone(), arg_value); |
7998 | | } |
7999 | | |
8000 | 0 | let result = self.evaluate_function_body(body, deadline, depth)?; |
8001 | 0 | self.bindings = saved_bindings; |
8002 | 0 | Ok(result) |
8003 | 0 | } |
8004 | | |
8005 | | /// Validate argument count for math functions. |
8006 | | /// |
8007 | | /// Example Usage: |
8008 | | /// |
8009 | | /// Validates that a function receives the expected number of arguments: |
8010 | | /// - sqrt(x) expects exactly 1 argument |
8011 | | /// - pow(x, y) expects exactly 2 arguments |
8012 | | /// - Returns an error if count doesn't match |
8013 | 0 | fn validate_arg_count(&self, func_name: &str, args: &[Expr], expected: usize) -> Result<()> { |
8014 | 0 | if args.len() != expected { |
8015 | 0 | bail!("{} takes exactly {} argument{}", func_name, expected, if expected == 1 { "" } else { "s" }); |
8016 | 0 | } |
8017 | 0 | Ok(()) |
8018 | 0 | } |
8019 | | |
8020 | | /// Apply unary math operation to a numeric value. |
8021 | | /// |
8022 | | /// # Example Usage |
8023 | | /// Validates that the correct number of arguments is provided to a function. |
8024 | | /// |
8025 | | /// # use `ruchy::runtime::repl::Repl`; |
8026 | | /// # use `ruchy::runtime::value::Value`; |
8027 | | /// let repl = `Repl::new()`; |
8028 | | /// let result = `repl.apply_unary_math_op(&Value::Int(4)`, "`sqrt").unwrap()`; |
8029 | | /// assert!(matches!(result, `Value::Float`(_))); |
8030 | | /// ``` |
8031 | 0 | fn apply_unary_math_op(&self, value: &Value, op: &str) -> Result<Value> { |
8032 | 0 | match (value, op) { |
8033 | 0 | (Value::Int(n), "sqrt") => { |
8034 | | #[allow(clippy::cast_precision_loss)] |
8035 | 0 | Ok(Value::Float((*n as f64).sqrt())) |
8036 | | } |
8037 | 0 | (Value::Float(f), "sqrt") => Ok(Value::Float(f.sqrt())), |
8038 | 0 | (Value::Int(n), "abs") => Ok(Value::Int(n.abs())), |
8039 | 0 | (Value::Float(f), "abs") => Ok(Value::Float(f.abs())), |
8040 | 0 | (Value::Int(n), "floor") => Ok(Value::Int(*n)), // Already floored |
8041 | 0 | (Value::Float(f), "floor") => Ok(Value::Float(f.floor())), |
8042 | 0 | (Value::Int(n), "ceil") => Ok(Value::Int(*n)), // Already ceiled |
8043 | 0 | (Value::Float(f), "ceil") => Ok(Value::Float(f.ceil())), |
8044 | 0 | (Value::Int(n), "round") => Ok(Value::Int(*n)), // Already rounded |
8045 | 0 | (Value::Float(f), "round") => Ok(Value::Float(f.round())), |
8046 | 0 | _ => bail!("{} expects a numeric argument", op), |
8047 | | } |
8048 | 0 | } |
8049 | | |
8050 | | /// Apply binary math operation to two numeric values. |
8051 | | /// |
8052 | | /// # Example Usage |
8053 | | /// Applies unary math operations like sqrt, abs, floor, ceil, round to numeric values. |
8054 | | /// |
8055 | | /// # use `ruchy::runtime::repl::Repl`; |
8056 | | /// # use `ruchy::runtime::value::Value`; |
8057 | | /// let repl = `Repl::new()`; |
8058 | | /// let result = `repl.apply_binary_math_op(&Value::Int(2)`, &`Value::Int(3)`, "`pow").unwrap()`; |
8059 | | /// assert!(matches!(result, `Value::Int(8)`)); |
8060 | | /// ``` |
8061 | 0 | fn apply_binary_math_op(&self, a: &Value, b: &Value, op: &str) -> Result<Value> { |
8062 | 0 | match (a, b, op) { |
8063 | 0 | (Value::Int(base), Value::Int(exp), "pow") => { |
8064 | 0 | if *exp < 0 { |
8065 | | #[allow(clippy::cast_precision_loss)] |
8066 | 0 | Ok(Value::Float((*base as f64).powi(*exp as i32))) |
8067 | | } else { |
8068 | 0 | let exp_u32 = u32::try_from(*exp).map_err(|_| anyhow::anyhow!("Exponent too large"))?; |
8069 | 0 | match base.checked_pow(exp_u32) { |
8070 | 0 | Some(result) => Ok(Value::Int(result)), |
8071 | 0 | None => bail!("Integer overflow in pow({}, {})", base, exp), |
8072 | | } |
8073 | | } |
8074 | | } |
8075 | 0 | (Value::Float(base), Value::Float(exp), "pow") => Ok(Value::Float(base.powf(*exp))), |
8076 | 0 | (Value::Int(base), Value::Float(exp), "pow") => { |
8077 | | #[allow(clippy::cast_precision_loss)] |
8078 | 0 | Ok(Value::Float((*base as f64).powf(*exp))) |
8079 | | } |
8080 | 0 | (Value::Float(base), Value::Int(exp), "pow") => { |
8081 | | #[allow(clippy::cast_precision_loss)] |
8082 | 0 | Ok(Value::Float(base.powi(*exp as i32))) |
8083 | | } |
8084 | 0 | (Value::Int(x), Value::Int(y), "min") => Ok(Value::Int((*x).min(*y))), |
8085 | 0 | (Value::Float(x), Value::Float(y), "min") => Ok(Value::Float(x.min(*y))), |
8086 | 0 | (Value::Int(x), Value::Float(y), "min") => { |
8087 | | #[allow(clippy::cast_precision_loss)] |
8088 | 0 | Ok(Value::Float((*x as f64).min(*y))) |
8089 | | } |
8090 | 0 | (Value::Float(x), Value::Int(y), "min") => { |
8091 | | #[allow(clippy::cast_precision_loss)] |
8092 | 0 | Ok(Value::Float(x.min(*y as f64))) |
8093 | | } |
8094 | 0 | (Value::Int(x), Value::Int(y), "max") => Ok(Value::Int((*x).max(*y))), |
8095 | 0 | (Value::Float(x), Value::Float(y), "max") => Ok(Value::Float(x.max(*y))), |
8096 | 0 | (Value::Int(x), Value::Float(y), "max") => { |
8097 | | #[allow(clippy::cast_precision_loss)] |
8098 | 0 | Ok(Value::Float((*x as f64).max(*y))) |
8099 | | } |
8100 | 0 | (Value::Float(x), Value::Int(y), "max") => { |
8101 | | #[allow(clippy::cast_precision_loss)] |
8102 | 0 | Ok(Value::Float(x.max(*y as f64))) |
8103 | | } |
8104 | 0 | _ => bail!("{} expects numeric arguments", op), |
8105 | | } |
8106 | 0 | } |
8107 | | |
8108 | | /// Handle built-in math functions (sqrt, pow, abs, min, max, floor, ceil, round). |
8109 | | /// |
8110 | | /// Returns `Ok(Some(value))` if the function name matches a math function, |
8111 | | /// `Ok(None)` if it doesn't match any math function, or `Err` if there's an error. |
8112 | | /// |
8113 | | /// # Example Usage |
8114 | | /// Tries to call math functions like sqrt, pow, abs, min, max, floor, ceil, round. |
8115 | | /// Dispatches to appropriate unary or binary math operation handler. |
8116 | 1 | fn try_math_function( |
8117 | 1 | &mut self, |
8118 | 1 | func_name: &str, |
8119 | 1 | args: &[Expr], |
8120 | 1 | deadline: Instant, |
8121 | 1 | depth: usize, |
8122 | 1 | ) -> Result<Option<Value>> { |
8123 | 1 | match func_name { |
8124 | | // Unary math functions |
8125 | 1 | "sqrt" | "abs" | "floor" | "ceil" | "round" => { |
8126 | 0 | self.validate_arg_count(func_name, args, 1)?; |
8127 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8128 | 0 | Ok(Some(self.apply_unary_math_op(&value, func_name)?)) |
8129 | | } |
8130 | | // Binary math functions |
8131 | 1 | "pow" | "min" | "max" => { |
8132 | 0 | self.validate_arg_count(func_name, args, 2)?; |
8133 | 0 | let a = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8134 | 0 | let b = self.evaluate_expr(&args[1], deadline, depth + 1)?; |
8135 | 0 | Ok(Some(self.apply_binary_math_op(&a, &b, func_name)?)) |
8136 | | } |
8137 | 1 | _ => Ok(None), // Not a math function |
8138 | | } |
8139 | 1 | } |
8140 | | |
8141 | | /// Handle built-in enum variant constructors (None, Some, Ok, Err). |
8142 | | /// |
8143 | | /// Returns `Ok(Some(value))` if the function name matches an enum constructor, |
8144 | | /// `Ok(None)` if it doesn't match any constructor, or `Err` if there's an error. |
8145 | | /// |
8146 | | /// # Example Usage |
8147 | | /// Applies binary math operations like pow, min, max to two numeric values. |
8148 | | /// |
8149 | | /// # use `ruchy::runtime::repl::Repl`; |
8150 | | /// # use `ruchy::frontend::ast::Expr`; |
8151 | | /// # use `std::time::Instant`; |
8152 | | /// let mut repl = `Repl::new()`; |
8153 | | /// let args = vec![]; |
8154 | | /// let deadline = `Instant::now()` + `std::time::Duration::from_secs(1)`; |
8155 | | /// let result = `repl.try_enum_constructor("None`", &args, deadline, `0).unwrap()`; |
8156 | | /// `assert!(result.is_some())`; |
8157 | | /// ``` |
8158 | 1 | fn try_enum_constructor( |
8159 | 1 | &mut self, |
8160 | 1 | func_name: &str, |
8161 | 1 | args: &[Expr], |
8162 | 1 | deadline: Instant, |
8163 | 1 | depth: usize, |
8164 | 1 | ) -> Result<Option<Value>> { |
8165 | 1 | match func_name { |
8166 | 1 | "None" => { |
8167 | 0 | if !args.is_empty() { |
8168 | 0 | bail!("None takes no arguments"); |
8169 | 0 | } |
8170 | 0 | Ok(Some(Value::EnumVariant { |
8171 | 0 | enum_name: "Option".to_string(), |
8172 | 0 | variant_name: "None".to_string(), |
8173 | 0 | data: None, |
8174 | 0 | })) |
8175 | | } |
8176 | 1 | "Some" => { |
8177 | 0 | if args.len() != 1 { |
8178 | 0 | bail!("Some takes exactly 1 argument"); |
8179 | 0 | } |
8180 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8181 | 0 | Ok(Some(Value::EnumVariant { |
8182 | 0 | enum_name: "Option".to_string(), |
8183 | 0 | variant_name: "Some".to_string(), |
8184 | 0 | data: Some(vec![value]), |
8185 | 0 | })) |
8186 | | } |
8187 | 1 | "Ok" => { |
8188 | 0 | if args.len() != 1 { |
8189 | 0 | bail!("Ok takes exactly 1 argument"); |
8190 | 0 | } |
8191 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8192 | 0 | Ok(Some(Value::EnumVariant { |
8193 | 0 | enum_name: "Result".to_string(), |
8194 | 0 | variant_name: "Ok".to_string(), |
8195 | 0 | data: Some(vec![value]), |
8196 | 0 | })) |
8197 | | } |
8198 | 1 | "Err" => { |
8199 | 0 | if args.len() != 1 { |
8200 | 0 | bail!("Err takes exactly 1 argument"); |
8201 | 0 | } |
8202 | 0 | let value = self.evaluate_expr(&args[0], deadline, depth + 1)?; |
8203 | 0 | Ok(Some(Value::EnumVariant { |
8204 | 0 | enum_name: "Result".to_string(), |
8205 | 0 | variant_name: "Err".to_string(), |
8206 | 0 | data: Some(vec![value]), |
8207 | 0 | })) |
8208 | | } |
8209 | 1 | _ => Ok(None), // Not an enum constructor |
8210 | | } |
8211 | 1 | } |
8212 | | |
8213 | | /// Evaluate user-defined functions |
8214 | 1 | fn evaluate_user_function( |
8215 | 1 | &mut self, |
8216 | 1 | func_name: &str, |
8217 | 1 | args: &[Expr], |
8218 | 1 | deadline: Instant, |
8219 | 1 | depth: usize, |
8220 | 1 | ) -> Result<Value> { |
8221 | | // Try enum constructor first |
8222 | 1 | if let Some(result0 ) = self.try_enum_constructor(func_name, args, deadline, depth)?0 { |
8223 | 0 | return Ok(result); |
8224 | 1 | } |
8225 | | |
8226 | | // Try built-in math function |
8227 | 1 | if let Some(result0 ) = self.try_math_function(func_name, args, deadline, depth)?0 { |
8228 | 0 | return Ok(result); |
8229 | 1 | } |
8230 | | |
8231 | | // Try user-defined function lookup and execution |
8232 | 1 | self.execute_user_defined_function(func_name, args, deadline, depth) |
8233 | 1 | } |
8234 | | |
8235 | | /// Helper to evaluate a function body and handle return statements |
8236 | 0 | fn evaluate_function_body( |
8237 | 0 | &mut self, |
8238 | 0 | body: &Expr, |
8239 | 0 | deadline: Instant, |
8240 | 0 | depth: usize, |
8241 | 0 | ) -> Result<Value> { |
8242 | 0 | match self.evaluate_expr(body, deadline, depth + 1) { |
8243 | 0 | Ok(val) => Ok(val), |
8244 | 0 | Err(e) => { |
8245 | | // Check if this is a return statement |
8246 | 0 | let err_str = e.to_string(); |
8247 | 0 | if let Some(return_val) = err_str.strip_prefix("return:") { |
8248 | | // Parse the return value - it's already a formatted Value string |
8249 | | // For now, just extract the string representation |
8250 | | // The value was already evaluated, just passed through error |
8251 | 0 | if return_val == "()" { |
8252 | 0 | Ok(Value::Unit) |
8253 | 0 | } else if return_val.starts_with('"') && return_val.ends_with('"') { |
8254 | | // String value - remove quotes |
8255 | 0 | let s = return_val[1..return_val.len()-1].to_string(); |
8256 | 0 | Ok(Value::String(s)) |
8257 | 0 | } else if let Ok(i) = return_val.parse::<i64>() { |
8258 | 0 | Ok(Value::Int(i)) |
8259 | 0 | } else if let Ok(f) = return_val.parse::<f64>() { |
8260 | 0 | Ok(Value::Float(f)) |
8261 | 0 | } else if return_val == "true" { |
8262 | 0 | Ok(Value::Bool(true)) |
8263 | 0 | } else if return_val == "false" { |
8264 | 0 | Ok(Value::Bool(false)) |
8265 | | } else { |
8266 | | // Return as string for complex values |
8267 | 0 | Ok(Value::String(return_val.to_string())) |
8268 | | } |
8269 | | } else { |
8270 | 0 | Err(e) |
8271 | | } |
8272 | | } |
8273 | | } |
8274 | 0 | } |
8275 | | |
8276 | | /// Evaluate match expressions |
8277 | 0 | fn evaluate_match( |
8278 | 0 | &mut self, |
8279 | 0 | match_expr: &Expr, |
8280 | 0 | arms: &[MatchArm], |
8281 | 0 | deadline: Instant, |
8282 | 0 | depth: usize, |
8283 | 0 | ) -> Result<Value> { |
8284 | 0 | let match_value = self.evaluate_expr(match_expr, deadline, depth + 1)?; |
8285 | | |
8286 | 0 | for arm in arms { |
8287 | 0 | if let Some(bindings) = Self::pattern_matches(&match_value, &arm.pattern)? { |
8288 | 0 | let saved_bindings = self.bindings.clone(); |
8289 | | |
8290 | | // Apply pattern bindings temporarily |
8291 | 0 | for (name, value) in bindings { |
8292 | 0 | self.bindings.insert(name, value); |
8293 | 0 | } |
8294 | | |
8295 | | // Check pattern guard if present |
8296 | 0 | let guard_passes = if let Some(guard_expr) = &arm.guard { |
8297 | 0 | if let Value::Bool(b) = self.evaluate_expr(guard_expr, deadline, depth + 1)? { |
8298 | 0 | b |
8299 | | } else { |
8300 | 0 | self.bindings = saved_bindings; |
8301 | 0 | continue; // Guard didn't evaluate to boolean, try next arm |
8302 | | } |
8303 | | } else { |
8304 | 0 | true // No guard, so it passes |
8305 | | }; |
8306 | | |
8307 | 0 | if guard_passes { |
8308 | 0 | let result = self.evaluate_expr(&arm.body, deadline, depth + 1)?; |
8309 | 0 | self.bindings = saved_bindings; |
8310 | 0 | return Ok(result); |
8311 | 0 | } |
8312 | | |
8313 | | // Guard failed, restore bindings and try next arm |
8314 | 0 | self.bindings = saved_bindings; |
8315 | 0 | } |
8316 | | } |
8317 | | |
8318 | 0 | bail!("No matching pattern found in match expression"); |
8319 | 0 | } |
8320 | | |
8321 | | /// Evaluate pipeline expressions |
8322 | 0 | fn evaluate_pipeline( |
8323 | 0 | &mut self, |
8324 | 0 | expr: &Expr, |
8325 | 0 | stages: &[PipelineStage], |
8326 | 0 | deadline: Instant, |
8327 | 0 | depth: usize, |
8328 | 0 | ) -> Result<Value> { |
8329 | 0 | let mut current_value = self.evaluate_expr(expr, deadline, depth + 1)?; |
8330 | | |
8331 | 0 | for stage in stages { |
8332 | 0 | current_value = self.evaluate_pipeline_stage(¤t_value, stage, deadline, depth)?; |
8333 | | } |
8334 | | |
8335 | 0 | Ok(current_value) |
8336 | 0 | } |
8337 | | |
8338 | | /// Evaluate a single pipeline stage |
8339 | 0 | fn evaluate_pipeline_stage( |
8340 | 0 | &mut self, |
8341 | 0 | current_value: &Value, |
8342 | 0 | stage: &PipelineStage, |
8343 | 0 | deadline: Instant, |
8344 | 0 | depth: usize, |
8345 | 0 | ) -> Result<Value> { |
8346 | 0 | match &stage.op.kind { |
8347 | 0 | ExprKind::Call { func, args } => { |
8348 | 0 | let mut new_args = vec![Self::value_to_literal_expr(current_value, stage.span)?]; |
8349 | 0 | new_args.extend(args.iter().cloned()); |
8350 | | |
8351 | 0 | let new_call = Expr::new( |
8352 | 0 | ExprKind::Call { |
8353 | 0 | func: func.clone(), |
8354 | 0 | args: new_args, |
8355 | 0 | }, |
8356 | 0 | stage.span, |
8357 | | ); |
8358 | | |
8359 | 0 | self.evaluate_expr(&new_call, deadline, depth + 1) |
8360 | | } |
8361 | 0 | ExprKind::Identifier(_func_name) => { |
8362 | 0 | let call = Expr::new( |
8363 | | ExprKind::Call { |
8364 | 0 | func: stage.op.clone(), |
8365 | 0 | args: vec![Self::value_to_literal_expr(current_value, stage.span)?], |
8366 | | }, |
8367 | 0 | stage.span, |
8368 | | ); |
8369 | | |
8370 | 0 | self.evaluate_expr(&call, deadline, depth + 1) |
8371 | | } |
8372 | 0 | ExprKind::MethodCall { receiver: _, method, args } => { |
8373 | | // For method calls in pipeline, current_value becomes the receiver |
8374 | 0 | match current_value { |
8375 | 0 | Value::List(items) => { |
8376 | 0 | self.evaluate_list_methods(items.clone(), method, args, deadline, depth) |
8377 | | } |
8378 | 0 | Value::String(s) => { |
8379 | 0 | Self::evaluate_string_methods(s, method, args, deadline, depth) |
8380 | | } |
8381 | 0 | Value::Int(n) => Self::evaluate_int_methods(*n, method), |
8382 | 0 | Value::Float(f) => Self::evaluate_float_methods(*f, method), |
8383 | 0 | Value::Object(obj) => { |
8384 | 0 | Self::evaluate_object_methods(obj.clone(), method, args, deadline, depth) |
8385 | | } |
8386 | 0 | Value::HashMap(map) => { |
8387 | 0 | self.evaluate_hashmap_methods(map.clone(), method, args, deadline, depth) |
8388 | | } |
8389 | 0 | Value::HashSet(set) => { |
8390 | 0 | self.evaluate_hashset_methods(set.clone(), method, args, deadline, depth) |
8391 | | } |
8392 | | Value::EnumVariant { .. } => { |
8393 | 0 | self.evaluate_enum_methods(current_value.clone(), method, args, deadline, depth) |
8394 | | } |
8395 | 0 | _ => bail!("Cannot call method {} on value of this type", method), |
8396 | | } |
8397 | | } |
8398 | 0 | _ => bail!("Pipeline stages must be function calls, method calls, or identifiers"), |
8399 | | } |
8400 | 0 | } |
8401 | | |
8402 | | /// Convert value to literal expression for pipeline |
8403 | 0 | fn value_to_literal_expr(value: &Value, span: Span) -> Result<Expr> { |
8404 | 0 | let expr_kind = match value { |
8405 | 0 | Value::Int(n) => ExprKind::Literal(Literal::Integer(*n)), |
8406 | 0 | Value::Float(f) => ExprKind::Literal(Literal::Float(*f)), |
8407 | 0 | Value::String(s) => ExprKind::Literal(Literal::String(s.clone())), |
8408 | 0 | Value::Bool(b) => ExprKind::Literal(Literal::Bool(*b)), |
8409 | 0 | Value::Unit => ExprKind::Literal(Literal::Unit), |
8410 | 0 | Value::List(items) => { |
8411 | 0 | let elements: Result<Vec<Expr>> = items |
8412 | 0 | .iter() |
8413 | 0 | .map(|item| Self::value_to_literal_expr(item, span)) |
8414 | 0 | .collect(); |
8415 | 0 | ExprKind::List(elements?) |
8416 | | } |
8417 | 0 | _ => bail!("Cannot pipeline complex value types yet"), |
8418 | | }; |
8419 | 0 | Ok(Expr::new(expr_kind, span)) |
8420 | 0 | } |
8421 | | |
8422 | | /// Evaluate command execution |
8423 | 0 | fn evaluate_command( |
8424 | 0 | program: &str, |
8425 | 0 | args: &[String], |
8426 | 0 | _deadline: Instant, |
8427 | 0 | _depth: usize, |
8428 | 0 | ) -> Result<Value> { |
8429 | | use std::process::Command; |
8430 | | |
8431 | 0 | let output = Command::new(program) |
8432 | 0 | .args(args) |
8433 | 0 | .output() |
8434 | 0 | .map_err(|e| anyhow::anyhow!("Failed to execute command '{}': {}", program, e))?; |
8435 | | |
8436 | 0 | if output.status.success() { |
8437 | 0 | let stdout = String::from_utf8_lossy(&output.stdout).to_string(); |
8438 | 0 | Ok(Value::String(stdout.trim().to_string())) |
8439 | | } else { |
8440 | 0 | let stderr = String::from_utf8_lossy(&output.stderr).to_string(); |
8441 | 0 | Err(anyhow::anyhow!( |
8442 | 0 | "Command '{}' failed with exit code {:?}: {}", |
8443 | 0 | program, |
8444 | 0 | output.status.code(), |
8445 | 0 | stderr |
8446 | 0 | )) |
8447 | | } |
8448 | 0 | } |
8449 | | |
8450 | | /// Evaluate macro expansion |
8451 | 0 | fn evaluate_macro( |
8452 | 0 | &mut self, |
8453 | 0 | name: &str, |
8454 | 0 | args: &[Expr], |
8455 | 0 | deadline: Instant, |
8456 | 0 | depth: usize, |
8457 | 0 | ) -> Result<Value> { |
8458 | 0 | match name { |
8459 | 0 | "println" => { |
8460 | | // Evaluate all arguments and print them |
8461 | 0 | let mut output = String::new(); |
8462 | 0 | for (i, arg) in args.iter().enumerate() { |
8463 | 0 | if i > 0 { |
8464 | 0 | output.push(' '); |
8465 | 0 | } |
8466 | 0 | let value = self.evaluate_expr(arg, deadline, depth + 1)?; |
8467 | 0 | output.push_str(&value.to_string()); |
8468 | | } |
8469 | 0 | println!("{output}"); |
8470 | 0 | Ok(Value::Unit) |
8471 | | } |
8472 | 0 | "vec" => { |
8473 | | // Evaluate all arguments and create a vector |
8474 | 0 | let mut elements = Vec::new(); |
8475 | 0 | for arg in args { |
8476 | 0 | elements.push(self.evaluate_expr(arg, deadline, depth + 1)?); |
8477 | | } |
8478 | 0 | Ok(Value::List(elements)) |
8479 | | } |
8480 | | _ => { |
8481 | 0 | anyhow::bail!("Unknown macro: {}", name) |
8482 | | } |
8483 | | } |
8484 | 0 | } |
8485 | | |
8486 | | /// Evaluate import statements (complexity < 10) |
8487 | | /// Import standard library filesystem module (complexity: 6) |
8488 | 0 | fn import_std_fs(&mut self, items: &[ImportItem]) -> Result<()> { |
8489 | 0 | for item in items { |
8490 | 0 | match item { |
8491 | 0 | ImportItem::Named(name) if name == "read_file" => { |
8492 | 0 | // This function is already built-in |
8493 | 0 | } |
8494 | 0 | ImportItem::Named(name) if name == "write_file" => { |
8495 | 0 | // This function is already built-in |
8496 | 0 | } |
8497 | 0 | ImportItem::Named(name) if name == "fs" => { |
8498 | 0 | println!(" ✓ Imported fs module"); |
8499 | 0 | } |
8500 | 0 | _ => {} |
8501 | | } |
8502 | | } |
8503 | 0 | Ok(()) |
8504 | 0 | } |
8505 | | |
8506 | | /// Import standard library collections module (complexity: 3) |
8507 | 0 | fn import_std_collections(&mut self, items: &[ImportItem]) -> Result<()> { |
8508 | 0 | for item in items { |
8509 | 0 | if let ImportItem::Named(_name) = item { |
8510 | 0 | // Successfully imported |
8511 | 0 | } |
8512 | | } |
8513 | 0 | Ok(()) |
8514 | 0 | } |
8515 | | |
8516 | | /// Import performance-related modules (complexity: 2) |
8517 | 0 | fn import_performance_module(&mut self, path: &str) -> Result<()> { |
8518 | 0 | match path { |
8519 | 0 | "std::mem" => { |
8520 | 0 | self.bindings.insert("Array".to_string(), Value::String("Array constructor".to_string())); |
8521 | 0 | } |
8522 | 0 | "std::parallel" | "std::simd" | "std::cache" | "std::bench" | "std::profile" => { |
8523 | 0 | // Module functions will be accessible via namespace |
8524 | 0 | } |
8525 | 0 | _ => {} |
8526 | | } |
8527 | 0 | Ok(()) |
8528 | 0 | } |
8529 | | |
8530 | | /// Check if item should be imported (complexity: 4) |
8531 | 0 | fn should_import_item(items: &[ImportItem], func_name: &str) -> bool { |
8532 | 0 | items.is_empty() || items.iter().any(|item| match item { |
8533 | 0 | ImportItem::Wildcard => true, |
8534 | 0 | ImportItem::Named(item_name) => item_name == func_name, |
8535 | 0 | ImportItem::Aliased { name: item_name, .. } => item_name == func_name, |
8536 | 0 | }) |
8537 | 0 | } |
8538 | | |
8539 | | /// Import functions from cache (complexity: 3) |
8540 | 0 | fn import_from_cache(&mut self, cached_functions: &HashMap<String, Value>, items: &[ImportItem]) { |
8541 | 0 | for (func_name, func_value) in cached_functions { |
8542 | 0 | if Self::should_import_item(items, func_name) { |
8543 | 0 | self.bindings.insert(func_name.clone(), func_value.clone()); |
8544 | 0 | } |
8545 | | } |
8546 | 0 | } |
8547 | | |
8548 | | /// Load and cache a module from file (complexity: 7) |
8549 | 0 | fn load_and_cache_module(&mut self, path: &str, items: &[ImportItem]) -> Result<()> { |
8550 | 0 | let module_path = format!("{path}.ruchy"); |
8551 | | |
8552 | 0 | if !std::path::Path::new(&module_path).exists() { |
8553 | 0 | bail!("Module not found: {}", path); |
8554 | 0 | } |
8555 | | |
8556 | | // Read and parse the module file |
8557 | 0 | let module_content = std::fs::read_to_string(&module_path) |
8558 | 0 | .with_context(|| format!("Failed to read module file: {module_path}"))?; |
8559 | | |
8560 | 0 | let mut parser = crate::frontend::Parser::new(&module_content); |
8561 | 0 | let module_ast = parser.parse() |
8562 | 0 | .with_context(|| format!("Failed to parse module: {module_path}"))?; |
8563 | | |
8564 | | // Extract and cache all functions from the module |
8565 | 0 | let mut module_functions = HashMap::new(); |
8566 | 0 | self.extract_module_functions(&module_ast, &mut module_functions)?; |
8567 | | |
8568 | | // Store in cache for future imports |
8569 | 0 | self.module_cache.insert(path.to_string(), module_functions.clone()); |
8570 | | |
8571 | | // Import requested functions into current scope |
8572 | 0 | self.import_from_cache(&module_functions, items); |
8573 | | |
8574 | 0 | Ok(()) |
8575 | 0 | } |
8576 | | |
8577 | | /// Main import dispatcher (complexity: 8) |
8578 | 0 | fn evaluate_import(&mut self, path: &str, items: &[ImportItem]) -> Result<Value> { |
8579 | | // Handle standard library imports |
8580 | 0 | match path { |
8581 | 0 | "std::fs" | "std::fs::read_file" => { |
8582 | 0 | self.import_std_fs(items)?; |
8583 | | } |
8584 | 0 | "std::collections" => { |
8585 | 0 | self.import_std_collections(items)?; |
8586 | | } |
8587 | 0 | "std::mem" | "std::parallel" | "std::simd" | "std::cache" | "std::bench" | "std::profile" => { |
8588 | 0 | self.import_performance_module(path)?; |
8589 | | } |
8590 | | _ => { |
8591 | | // Check cache first |
8592 | 0 | if let Some(cached_functions) = self.module_cache.get(path).cloned() { |
8593 | 0 | self.import_from_cache(&cached_functions, items); |
8594 | 0 | } else { |
8595 | | // Load from file and cache |
8596 | 0 | self.load_and_cache_module(path, items)?; |
8597 | | } |
8598 | | } |
8599 | | } |
8600 | | |
8601 | 0 | Ok(Value::Unit) |
8602 | 0 | } |
8603 | | |
8604 | | /// Extract functions from a module AST into a `HashMap` for caching |
8605 | 0 | fn extract_module_functions(&mut self, module_ast: &Expr, functions_map: &mut HashMap<String, Value>) -> Result<()> { |
8606 | | // Extract all functions from the module for caching |
8607 | 0 | if let ExprKind::Block(exprs) = &module_ast.kind { |
8608 | 0 | for expr in exprs { |
8609 | 0 | if let ExprKind::Function { name, params, body, .. } = &expr.kind { |
8610 | | // Extract function and store in cache map |
8611 | 0 | let param_names: Vec<String> = params.iter() |
8612 | 0 | .map(|p| match &p.pattern { |
8613 | 0 | Pattern::Identifier(name) => name.clone(), |
8614 | 0 | _ => "unknown".to_string(), // Simplified for now |
8615 | 0 | }) |
8616 | 0 | .collect(); |
8617 | | |
8618 | 0 | let function_value = Value::Function { |
8619 | 0 | name: name.clone(), |
8620 | 0 | params: param_names, |
8621 | 0 | body: body.clone(), |
8622 | 0 | }; |
8623 | 0 | functions_map.insert(name.clone(), function_value); |
8624 | 0 | } |
8625 | | } |
8626 | 0 | } else if let ExprKind::Function { name, params, body, .. } = &module_ast.kind { |
8627 | | // Single function module |
8628 | 0 | let param_names: Vec<String> = params.iter() |
8629 | 0 | .map(|p| match &p.pattern { |
8630 | 0 | Pattern::Identifier(name) => name.clone(), |
8631 | 0 | _ => "unknown".to_string(), // Simplified for now |
8632 | 0 | }) |
8633 | 0 | .collect(); |
8634 | | |
8635 | 0 | let function_value = Value::Function { |
8636 | 0 | name: name.clone(), |
8637 | 0 | params: param_names, |
8638 | 0 | body: body.clone(), |
8639 | 0 | }; |
8640 | 0 | functions_map.insert(name.clone(), function_value); |
8641 | 0 | } |
8642 | | |
8643 | 0 | Ok(()) |
8644 | 0 | } |
8645 | | |
8646 | | /// Evaluate export statements (complexity < 10) |
8647 | 0 | fn evaluate_export(&mut self, _items: &[String]) -> Result<Value> { |
8648 | | // For now, just track the export |
8649 | | // Real implementation would: |
8650 | | // 1. Mark items for export |
8651 | | // 2. Make them available to importing modules |
8652 | | |
8653 | | // Export handling |
8654 | | |
8655 | 0 | Ok(Value::Unit) |
8656 | 0 | } |
8657 | | |
8658 | | /// Handle package mode commands |
8659 | 0 | fn handle_pkg_command(&mut self, input: &str) -> Result<String> { |
8660 | 0 | let parts: Vec<&str> = input.split_whitespace().collect(); |
8661 | 0 | match parts.first().copied() { |
8662 | 0 | Some("search") if parts.len() > 1 => { |
8663 | 0 | Ok(format!("Searching for packages matching '{}'...", parts[1])) |
8664 | | } |
8665 | 0 | Some("install") if parts.len() > 1 => { |
8666 | 0 | Ok(format!("Installing package '{}'...", parts[1])) |
8667 | | } |
8668 | 0 | Some("list") => { |
8669 | 0 | Ok("Installed packages:\n(Package management not yet implemented)".to_string()) |
8670 | | } |
8671 | | _ => { |
8672 | 0 | Ok("Package commands: search <query>, install <package>, list".to_string()) |
8673 | | } |
8674 | | } |
8675 | 0 | } |
8676 | | |
8677 | | /// Show comprehensive help menu |
8678 | 0 | fn show_help_menu(&self) -> Result<String> { |
8679 | 0 | Ok(r"🔧 Ruchy REPL Help Menu |
8680 | 0 |
|
8681 | 0 | 📋 COMMANDS: |
8682 | 0 | :help [topic] - Show help for specific topic or this menu |
8683 | 0 | :quit, :q - Exit the REPL |
8684 | 0 | :clear - Clear variables and history |
8685 | 0 | :history - Show command history |
8686 | 0 | :env - Show environment variables |
8687 | 0 | :type <expr> - Show type of expression |
8688 | 0 | :ast <expr> - Show abstract syntax tree |
8689 | 0 | :inspect <var> - Detailed variable inspection |
8690 | 0 |
|
8691 | 0 | 🎯 MODES: |
8692 | 0 | :normal - Standard evaluation mode |
8693 | 0 | :help - Help documentation mode (current) |
8694 | 0 | :debug - Debug mode with detailed output |
8695 | 0 | :time - Time mode showing execution duration |
8696 | 0 | :test - Test mode with assertions |
8697 | 0 | :math - Enhanced math mode |
8698 | 0 | :sql - SQL query mode (experimental) |
8699 | 0 | :shell - Shell command mode |
8700 | 0 |
|
8701 | 0 | 💡 LANGUAGE TOPICS (type topic name for details): |
8702 | 0 | fn - Function definitions |
8703 | 0 | let - Variable declarations |
8704 | 0 | if - Conditional expressions |
8705 | 0 | for - Loop constructs |
8706 | 0 | match - Pattern matching |
8707 | 0 | while - While loops |
8708 | 0 |
|
8709 | 0 | 🚀 FEATURES: |
8710 | 0 | • Arithmetic: +, -, *, /, %, ** |
8711 | 0 | • Comparisons: ==, !=, <, >, <=, >= |
8712 | 0 | • Logical: &&, ||, ! |
8713 | 0 | • Arrays: [1, 2, 3], indexing with arr[0] |
8714 | 0 | • Objects: {key: value}, access with obj.key |
8715 | 0 | • String methods: .length(), .to_upper(), .to_lower() |
8716 | 0 | • Math functions: sqrt(), pow(), abs(), sin(), cos(), etc. |
8717 | 0 | • History: _1, _2, _3 (previous results) |
8718 | 0 | • Shell commands: !ls, !pwd, !echo hello |
8719 | 0 | • Introspection: ?variable, ??variable (detailed) |
8720 | 0 |
|
8721 | 0 | Type :normal to exit help mode. |
8722 | 0 | ".to_string()) |
8723 | 0 | } |
8724 | | |
8725 | | /// Handle help mode commands |
8726 | 0 | fn handle_help_command(&mut self, keyword: &str) -> Result<String> { |
8727 | 0 | let help_text = match keyword { |
8728 | 0 | "fn" | "function" => "fn - Define a function\nSyntax: fn name(params) { body }\nExample: fn add(a, b) { a + b }".to_string(), |
8729 | 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(), |
8730 | 0 | "if" | "conditional" => "if - Conditional execution\nSyntax: if condition { then } else { otherwise }\nExample: if x > 0 { \"positive\" } else { \"negative\" }".to_string(), |
8731 | 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(), |
8732 | 0 | "while" => "while - Loop with condition\nSyntax: while condition { body }\nExample: while x < 10 { x = x + 1 }".to_string(), |
8733 | 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(), |
8734 | 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(), |
8735 | 0 | "object" | "dict" => "Objects - Key-value pairs\nSyntax: {key: value, ...}\nExample: let obj = {name: \"Alice\", age: 30}\nAccess: obj.name, obj.age".to_string(), |
8736 | 0 | "string" => "Strings - Text values\nSyntax: \"text\" or 'text'\nMethods: .length(), .to_upper(), .to_lower()\nConcatenation: \"hello\" + \" world\"".to_string(), |
8737 | 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(), |
8738 | 0 | "commands" | ":" => self.show_help_menu()?, |
8739 | 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."), |
8740 | | }; |
8741 | 0 | Ok(help_text) |
8742 | 0 | } |
8743 | | |
8744 | | /// Handle math mode commands |
8745 | 0 | fn handle_math_command(&mut self, expr: &str) -> Result<String> { |
8746 | | // For now, just evaluate normally but could add special math functions |
8747 | 0 | let deadline = Instant::now() + self.config.timeout; |
8748 | 0 | let mut parser = Parser::new(expr); |
8749 | 0 | let ast = parser.parse().context("Failed to parse math expression")?; |
8750 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
8751 | 0 | Ok(format!("= {value}")) |
8752 | 0 | } |
8753 | | |
8754 | | /// Handle debug mode evaluation |
8755 | 0 | fn handle_debug_evaluation(&mut self, input: &str) -> Result<String> { |
8756 | | // Use enhanced debug evaluation per progressive modes specification |
8757 | 0 | self.handle_enhanced_debug_evaluation(input) |
8758 | 0 | } |
8759 | | |
8760 | | /// Enhanced debug mode evaluation with detailed traces |
8761 | 0 | fn handle_enhanced_debug_evaluation(&mut self, input: &str) -> Result<String> { |
8762 | 0 | let start = Instant::now(); |
8763 | | |
8764 | | // Parse timing |
8765 | 0 | let parse_start = Instant::now(); |
8766 | 0 | let mut parser = Parser::new(input); |
8767 | 0 | let ast = parser.parse().context("Failed to parse input")?; |
8768 | 0 | let parse_time = parse_start.elapsed(); |
8769 | | |
8770 | | // Type checking timing (placeholder) |
8771 | 0 | let type_start = Instant::now(); |
8772 | | // Type checking would go here |
8773 | 0 | let type_time = type_start.elapsed(); |
8774 | | |
8775 | | // Evaluation timing |
8776 | 0 | let eval_start = Instant::now(); |
8777 | 0 | let deadline = Instant::now() + self.config.timeout; |
8778 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
8779 | 0 | let eval_time = eval_start.elapsed(); |
8780 | | |
8781 | | // Memory allocation (simplified) |
8782 | 0 | let alloc_bytes = 64; // Placeholder |
8783 | | |
8784 | 0 | let _total_time = start.elapsed(); |
8785 | | |
8786 | | // Format trace according to specification |
8787 | 0 | let trace = format!( |
8788 | 0 | "┌─ Trace ────────┐\n\ |
8789 | 0 | │ parse: {:>5.1}ms │\n\ |
8790 | 0 | │ type: {:>5.1}ms │\n\ |
8791 | 0 | │ eval: {:>5.1}ms │\n\ |
8792 | 0 | │ alloc: {:>5}B │\n\ |
8793 | 0 | └────────────────┘\n\ |
8794 | 0 | {}: {} = {}", |
8795 | 0 | parse_time.as_secs_f64() * 1000.0, |
8796 | 0 | type_time.as_secs_f64() * 1000.0, |
8797 | 0 | eval_time.as_secs_f64() * 1000.0, |
8798 | | alloc_bytes, |
8799 | 0 | input.trim(), |
8800 | 0 | self.infer_type(&value), |
8801 | | value |
8802 | | ); |
8803 | | |
8804 | 0 | Ok(trace) |
8805 | 0 | } |
8806 | | |
8807 | | /// Handle timed evaluation |
8808 | 0 | fn handle_timed_evaluation(&mut self, input: &str) -> Result<String> { |
8809 | 0 | let start = Instant::now(); |
8810 | | |
8811 | | // Parse |
8812 | 0 | let mut parser = Parser::new(input); |
8813 | 0 | let ast = parser.parse().context("Failed to parse input")?; |
8814 | | |
8815 | | // Evaluate |
8816 | 0 | let deadline = Instant::now() + self.config.timeout; |
8817 | 0 | let value = self.evaluate_expr(&ast, deadline, 0)?; |
8818 | | |
8819 | 0 | let elapsed = start.elapsed(); |
8820 | 0 | Ok(format!("{value}\n⏱ Time: {elapsed:?}")) |
8821 | 0 | } |
8822 | | |
8823 | | /// Generate a stack trace from an error |
8824 | 2 | fn generate_stack_trace(&self, error: &anyhow::Error) -> Vec<String> { |
8825 | 2 | let mut stack_trace = Vec::new(); |
8826 | | |
8827 | | // Add the main error |
8828 | 2 | stack_trace.push(format!("Error: {error}")); |
8829 | | |
8830 | | // Add error chain |
8831 | 2 | let mut current = error.source(); |
8832 | 2 | while let Some(err0 ) = current { |
8833 | 0 | stack_trace.push(format!("Caused by: {err}")); |
8834 | 0 | current = err.source(); |
8835 | 0 | } |
8836 | | |
8837 | | // Add current evaluation context if available |
8838 | 2 | if let Some(last_expr1 ) = self.history.last() { |
8839 | 1 | stack_trace.push(format!("Last successful expression: {last_expr}")); |
8840 | 1 | } |
8841 | | |
8842 | 2 | stack_trace |
8843 | 2 | } |
8844 | | |
8845 | | /// Detect progressive mode activation via attributes like #[test] and #[debug] |
8846 | 69 | fn detect_mode_activation(&self, input: &str) -> Option<ReplMode> { |
8847 | 69 | let trimmed = input.trim(); |
8848 | | |
8849 | 69 | if trimmed.starts_with("#[test]") { |
8850 | 0 | Some(ReplMode::Test) |
8851 | 69 | } else if trimmed.starts_with("#[debug]") { |
8852 | 0 | Some(ReplMode::Debug) |
8853 | | } else { |
8854 | 69 | None |
8855 | | } |
8856 | 69 | } |
8857 | | |
8858 | | /// Handle test mode evaluation with assertions and table tests |
8859 | 0 | fn handle_test_evaluation(&mut self, input: &str) -> Result<String> { |
8860 | 0 | let trimmed = input.trim(); |
8861 | | |
8862 | | // Handle assert statements |
8863 | 0 | if let Some(stripped) = trimmed.strip_prefix("assert ") { |
8864 | 0 | return self.handle_assertion(stripped); |
8865 | 0 | } |
8866 | | |
8867 | | // Handle table_test! macro |
8868 | 0 | if trimmed.starts_with("table_test!(") { |
8869 | 0 | return self.handle_table_test(trimmed); |
8870 | 0 | } |
8871 | | |
8872 | | // Regular evaluation with test result formatting |
8873 | 0 | let result = self.eval_internal(input)?; |
8874 | 0 | Ok(format!("✓ {result}")) |
8875 | 0 | } |
8876 | | |
8877 | | /// Handle assertion statements in test mode |
8878 | 0 | fn handle_assertion(&mut self, assertion: &str) -> Result<String> { |
8879 | | // Parse and evaluate the assertion |
8880 | 0 | let mut parser = Parser::new(assertion); |
8881 | 0 | let expr = parser.parse().context("Failed to parse assertion")?; |
8882 | | |
8883 | 0 | let deadline = Instant::now() + self.config.timeout; |
8884 | 0 | let result = self.evaluate_expr(&expr, deadline, 0)?; |
8885 | | |
8886 | 0 | match result { |
8887 | 0 | Value::Bool(true) => Ok("✓ Pass".to_string()), |
8888 | 0 | Value::Bool(false) => Ok("✗ Fail: assertion failed".to_string()), |
8889 | 0 | _ => Ok(format!("✗ Fail: assertion must be boolean, got {result}")), |
8890 | | } |
8891 | 0 | } |
8892 | | |
8893 | | /// Handle table test macro |
8894 | 0 | fn handle_table_test(&mut self, _input: &str) -> Result<String> { |
8895 | | // This is a simplified implementation - in a full version you'd parse the table_test! macro properly |
8896 | | // For now, just indicate successful parsing |
8897 | 0 | Ok("✓ Table test recognized (full implementation pending)".to_string()) |
8898 | 0 | } |
8899 | | |
8900 | | /// Simple type inference for display purposes |
8901 | 0 | fn infer_type(&self, value: &Value) -> &'static str { |
8902 | 0 | match value { |
8903 | 0 | Value::Int(_) => "Int", |
8904 | 0 | Value::Float(_) => "Float", |
8905 | 0 | Value::String(_) => "String", |
8906 | 0 | Value::Bool(_) => "Bool", |
8907 | 0 | Value::Char(_) => "Char", |
8908 | 0 | Value::Unit => "Unit", |
8909 | 0 | Value::List(_) => "List", |
8910 | 0 | Value::Tuple(_) => "Tuple", |
8911 | 0 | Value::Object(_) => "Object", |
8912 | 0 | Value::Function { .. } => "Function", |
8913 | 0 | Value::Lambda { .. } => "Lambda", |
8914 | 0 | Value::DataFrame { .. } => "DataFrame", |
8915 | 0 | Value::HashMap(_) => "HashMap", |
8916 | 0 | Value::HashSet(_) => "HashSet", |
8917 | 0 | Value::Range { .. } => "Range", |
8918 | 0 | Value::EnumVariant { .. } => "EnumVariant", |
8919 | 0 | Value::Nil => "Nil", |
8920 | | } |
8921 | 0 | } |
8922 | | |
8923 | | /// Format size/length information for value (complexity: 8) |
8924 | 0 | fn format_value_size(&self, value: &Value) -> String { |
8925 | 0 | match value { |
8926 | 0 | Value::List(l) => format!("│ Length: {:<20} │\n", l.len()), |
8927 | 0 | Value::String(s) => format!("│ Length: {} chars{:<11} │\n", s.len(), ""), |
8928 | 0 | Value::Object(o) => format!("│ Fields: {:<20} │\n", o.len()), |
8929 | 0 | Value::HashMap(m) => format!("│ Entries: {:<19} │\n", m.len()), |
8930 | 0 | Value::HashSet(s) => format!("│ Size: {:<22} │\n", s.len()), |
8931 | 0 | Value::Tuple(t) => format!("│ Elements: {:<18} │\n", t.len()), |
8932 | 0 | Value::DataFrame { columns, .. } => { |
8933 | 0 | if let Some(first_col) = columns.first() { |
8934 | 0 | let row_count = first_col.values.len(); |
8935 | 0 | format!("│ Columns: {:<19} │\n│ Rows: {row_count:<22} │\n", columns.len()) |
8936 | | } else { |
8937 | 0 | String::new() |
8938 | | } |
8939 | | } |
8940 | | _ => { |
8941 | | // Show value preview for simple types |
8942 | 0 | let preview = format!("{value}"); |
8943 | 0 | if preview.len() <= 24 { |
8944 | 0 | format!("│ Value: {preview:<21} │\n") |
8945 | | } else { |
8946 | 0 | let truncated = &preview[..21]; |
8947 | 0 | format!("│ Value: {truncated}... │\n") |
8948 | | } |
8949 | | } |
8950 | | } |
8951 | 0 | } |
8952 | | |
8953 | | /// Format interactive options for value (complexity: 3) |
8954 | 0 | fn format_value_options(&self, value: &Value) -> String { |
8955 | 0 | let mut output = String::new(); |
8956 | 0 | output.push_str("│ Options: │\n"); |
8957 | | |
8958 | 0 | match value { |
8959 | 0 | Value::List(_) | Value::Object(_) | Value::HashMap(_) => { |
8960 | 0 | output.push_str("│ [Enter] Browse entries │\n"); |
8961 | 0 | output.push_str("│ [S] Statistics │\n"); |
8962 | 0 | } |
8963 | 0 | Value::Function { .. } | Value::Lambda { .. } => { |
8964 | 0 | output.push_str("│ [P] Show parameters │\n"); |
8965 | 0 | output.push_str("│ [B] Show body │\n"); |
8966 | 0 | } |
8967 | 0 | _ => { |
8968 | 0 | output.push_str("│ [V] Show full value │\n"); |
8969 | 0 | output.push_str("│ [T] Type details │\n"); |
8970 | 0 | } |
8971 | | } |
8972 | | |
8973 | 0 | output.push_str("│ [M] Memory layout │\n"); |
8974 | 0 | output |
8975 | 0 | } |
8976 | | |
8977 | | /// Create inspector header (complexity: 2) |
8978 | 0 | fn create_inspector_header(&self, var_name: &str, value: &Value) -> String { |
8979 | 0 | let mut output = String::new(); |
8980 | 0 | output.push_str("┌─ Inspector ────────────────┐\n"); |
8981 | 0 | output.push_str(&format!("│ Variable: {var_name:<17} │\n")); |
8982 | 0 | output.push_str(&format!("│ Type: {:<22} │\n", self.infer_type(value))); |
8983 | 0 | output |
8984 | 0 | } |
8985 | | |
8986 | | /// Inspect a value in detail (for :inspect command) (complexity: 4) |
8987 | 0 | fn inspect_value(&self, var_name: &str) -> String { |
8988 | 0 | if let Some(value) = self.bindings.get(var_name) { |
8989 | 0 | let mut output = String::new(); |
8990 | | |
8991 | | // Header with variable name and type |
8992 | 0 | output.push_str(&self.create_inspector_header(var_name, value)); |
8993 | | |
8994 | | // Size/length information |
8995 | 0 | output.push_str(&self.format_value_size(value)); |
8996 | | |
8997 | | // Memory estimation |
8998 | 0 | let memory_size = self.estimate_memory_size(value); |
8999 | 0 | output.push_str(&format!("│ Memory: ~{:<18} │\n", format!("{memory_size} bytes"))); |
9000 | | |
9001 | | // Separator line |
9002 | 0 | output.push_str("│ │\n"); |
9003 | | |
9004 | | // Interactive options |
9005 | 0 | output.push_str(&self.format_value_options(value)); |
9006 | | |
9007 | | // Footer |
9008 | 0 | output.push_str("└────────────────────────────┘"); |
9009 | | |
9010 | 0 | output |
9011 | | } else { |
9012 | 0 | format!("Variable '{var_name}' not found. Use :env to list all variables.") |
9013 | | } |
9014 | 0 | } |
9015 | | |
9016 | | /// Estimate memory size of a value (simplified) |
9017 | 0 | fn estimate_memory_size(&self, value: &Value) -> usize { |
9018 | 0 | match value { |
9019 | 0 | Value::Int(_) => 8, |
9020 | 0 | Value::Float(_) => 8, |
9021 | 0 | Value::Bool(_) => 1, |
9022 | 0 | Value::Char(_) => 4, |
9023 | 0 | Value::Unit => 0, |
9024 | 0 | Value::String(s) => s.len() + 24, // String overhead + content |
9025 | 0 | Value::List(l) => 24 + l.len() * 8, // Vec overhead + pointers |
9026 | 0 | Value::Tuple(t) => 8 + t.len() * 8, |
9027 | 0 | Value::Object(o) => 24 + o.len() * 32, // HashMap overhead |
9028 | 0 | Value::HashMap(m) => 24 + m.len() * 48, |
9029 | 0 | Value::HashSet(s) => 24 + s.len() * 16, |
9030 | 0 | Value::Function { .. } | Value::Lambda { .. } => 64, // Simplified |
9031 | 0 | Value::DataFrame { columns, .. } => { |
9032 | 0 | 24 + columns.len() * 64 // Simplified estimate |
9033 | | } |
9034 | 0 | Value::Range { .. } => 16, |
9035 | 0 | Value::EnumVariant { .. } => 32, |
9036 | 0 | Value::Nil => 0, |
9037 | | } |
9038 | 0 | } |
9039 | | |
9040 | | /// Run the REPL with session recording enabled |
9041 | | /// |
9042 | | /// This method creates a session recorder that tracks all inputs, outputs, |
9043 | | /// and state changes during the REPL session. The recorded session can be |
9044 | | /// replayed later for testing or educational purposes. |
9045 | | /// |
9046 | | /// # Arguments |
9047 | | /// * `record_file` - Path to save the recorded session |
9048 | | /// |
9049 | | /// # Returns |
9050 | | /// Returns `Ok(())` on successful completion, or an error if recording fails |
9051 | | /// |
9052 | | /// # Errors |
9053 | | /// Returns error if recording initialization fails or I/O operations fail |
9054 | 0 | pub fn run_with_recording(&mut self, record_file: &Path) -> Result<()> { |
9055 | | // Delegate to refactored version with reduced complexity |
9056 | | // Original complexity: 44, New complexity: 15 |
9057 | 0 | self.run_with_recording_refactored(record_file) |
9058 | 0 | } |
9059 | | |
9060 | | // Helper methods for reduced complexity REPL::run |
9061 | | |
9062 | 0 | fn setup_readline_editor(&self) -> Result<rustyline::Editor<RuchyCompleter, DefaultHistory>> { |
9063 | 0 | let config = Config::builder() |
9064 | 0 | .history_ignore_space(true) |
9065 | 0 | .history_ignore_dups(true)? |
9066 | 0 | .completion_type(CompletionType::List) |
9067 | 0 | .edit_mode(EditMode::Emacs) |
9068 | 0 | .build(); |
9069 | | |
9070 | 0 | let mut rl = rustyline::Editor::<RuchyCompleter, DefaultHistory>::with_config(config)?; |
9071 | | |
9072 | 0 | let completer = RuchyCompleter::new(); |
9073 | 0 | rl.set_helper(Some(completer)); |
9074 | | |
9075 | 0 | let history_path = self.temp_dir.join("history.txt"); |
9076 | 0 | let _ = rl.load_history(&history_path); |
9077 | | |
9078 | 0 | Ok(rl) |
9079 | 0 | } |
9080 | | |
9081 | 0 | fn format_prompt(&self, in_multiline: bool) -> String { |
9082 | 0 | if in_multiline { |
9083 | 0 | format!("{} ", " ...".bright_black()) |
9084 | | } else { |
9085 | 0 | format!("{} ", self.get_prompt().bright_green()) |
9086 | | } |
9087 | 0 | } |
9088 | | |
9089 | 0 | fn process_input_line( |
9090 | 0 | &mut self, |
9091 | 0 | line: &str, |
9092 | 0 | rl: &mut rustyline::Editor<RuchyCompleter, DefaultHistory>, |
9093 | 0 | multiline_state: &mut MultilineState |
9094 | 0 | ) -> Result<bool> { |
9095 | | // Skip empty lines unless in multiline mode |
9096 | 0 | if line.trim().is_empty() && !multiline_state.in_multiline { |
9097 | 0 | return Ok(false); |
9098 | 0 | } |
9099 | | |
9100 | | // Handle commands (only when not in multiline mode) |
9101 | 0 | if !multiline_state.in_multiline && line.starts_with(':') { |
9102 | 0 | return self.process_command(line); |
9103 | 0 | } |
9104 | | |
9105 | | // Process regular expression input |
9106 | 0 | self.process_expression_input(line, rl, multiline_state) |
9107 | 0 | } |
9108 | | |
9109 | 0 | fn process_command(&mut self, line: &str) -> Result<bool> { |
9110 | 0 | let (should_quit, output) = self.handle_command_with_output(line)?; |
9111 | 0 | if !output.is_empty() { |
9112 | 0 | println!("{output}"); |
9113 | 0 | } |
9114 | 0 | Ok(should_quit) |
9115 | 0 | } |
9116 | | |
9117 | 0 | fn process_expression_input( |
9118 | 0 | &mut self, |
9119 | 0 | line: &str, |
9120 | 0 | rl: &mut rustyline::Editor<RuchyCompleter, DefaultHistory>, |
9121 | 0 | multiline_state: &mut MultilineState |
9122 | 0 | ) -> Result<bool> { |
9123 | | // Check if this starts a multiline expression |
9124 | 0 | if !multiline_state.in_multiline && Self::needs_continuation(line) { |
9125 | 0 | multiline_state.start_multiline(line); |
9126 | 0 | return Ok(false); |
9127 | 0 | } |
9128 | | |
9129 | 0 | if multiline_state.in_multiline { |
9130 | 0 | self.process_multiline_input(line, rl, multiline_state) |
9131 | | } else { |
9132 | 0 | self.process_single_line_input(line, rl) |
9133 | | } |
9134 | 0 | } |
9135 | | |
9136 | 0 | fn process_multiline_input( |
9137 | 0 | &mut self, |
9138 | 0 | line: &str, |
9139 | 0 | rl: &mut rustyline::Editor<RuchyCompleter, DefaultHistory>, |
9140 | 0 | multiline_state: &mut MultilineState |
9141 | 0 | ) -> Result<bool> { |
9142 | 0 | multiline_state.accumulate_line(line); |
9143 | | |
9144 | 0 | if !Self::needs_continuation(&multiline_state.buffer) { |
9145 | 0 | let _ = rl.add_history_entry(multiline_state.buffer.as_str()); |
9146 | 0 | self.evaluate_and_print(&multiline_state.buffer); |
9147 | 0 | multiline_state.reset(); |
9148 | 0 | } |
9149 | | |
9150 | 0 | Ok(false) |
9151 | 0 | } |
9152 | | |
9153 | 0 | fn process_single_line_input( |
9154 | 0 | &mut self, |
9155 | 0 | line: &str, |
9156 | 0 | rl: &mut rustyline::Editor<RuchyCompleter, DefaultHistory> |
9157 | 0 | ) -> Result<bool> { |
9158 | 0 | let _ = rl.add_history_entry(line); |
9159 | 0 | self.evaluate_and_print(line); |
9160 | 0 | Ok(false) |
9161 | 0 | } |
9162 | | |
9163 | 0 | fn evaluate_and_print(&mut self, expression: &str) { |
9164 | 0 | match self.eval(expression) { |
9165 | 0 | Ok(result) => { |
9166 | 0 | println!("{}", result.bright_white()); |
9167 | 0 | } |
9168 | 0 | Err(e) => { |
9169 | 0 | eprintln!("{}: {}", "Error".bright_red().bold(), e); |
9170 | 0 | } |
9171 | | } |
9172 | 0 | } |
9173 | | } |
9174 | | |
9175 | | // Helper struct for managing multiline state |
9176 | | #[derive(Debug)] |
9177 | | struct MultilineState { |
9178 | | buffer: String, |
9179 | | in_multiline: bool, |
9180 | | } |
9181 | | |
9182 | | impl MultilineState { |
9183 | 0 | fn new() -> Self { |
9184 | 0 | Self { |
9185 | 0 | buffer: String::new(), |
9186 | 0 | in_multiline: false, |
9187 | 0 | } |
9188 | 0 | } |
9189 | | |
9190 | 0 | fn start_multiline(&mut self, line: &str) { |
9191 | 0 | self.buffer = line.to_string(); |
9192 | 0 | self.in_multiline = true; |
9193 | 0 | } |
9194 | | |
9195 | 0 | fn accumulate_line(&mut self, line: &str) { |
9196 | 0 | self.buffer.push('\n'); |
9197 | 0 | self.buffer.push_str(line); |
9198 | 0 | } |
9199 | | |
9200 | 0 | fn reset(&mut self) { |
9201 | 0 | self.buffer.clear(); |
9202 | 0 | self.in_multiline = false; |
9203 | 0 | } |
9204 | | } |