1use std::collections::HashMap;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub enum Language {
14 Rust,
15 Python,
16 JavaScript,
17 TypeScript,
18 Go,
19 Bash,
20 Unknown(String),
21}
22
23impl std::fmt::Display for Language {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 Language::Rust => write!(f, "rust"),
27 Language::Python => write!(f, "python"),
28 Language::JavaScript => write!(f, "javascript"),
29 Language::TypeScript => write!(f, "typescript"),
30 Language::Go => write!(f, "go"),
31 Language::Bash => write!(f, "bash"),
32 Language::Unknown(s) => write!(f, "{}", s),
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
42pub struct CodeBlock {
43 pub language: Language,
44 pub code: String,
45 pub start_line: usize,
46 pub end_line: usize,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum ExecutionMode {
55 Simulate,
56 Extract,
57 Validate,
58}
59
60#[derive(Debug, Clone)]
65pub struct SimulationResult {
66 pub block: CodeBlock,
67 pub output: String,
68 pub errors: Vec<String>,
69 pub warnings: Vec<String>,
70 pub execution_ms: u64,
71}
72
73pub struct CodeExtractor;
78
79impl CodeExtractor {
80 pub fn extract(text: &str) -> Vec<CodeBlock> {
82 let mut blocks = Vec::new();
83 let lines: Vec<&str> = text.lines().collect();
84 let mut i = 0;
85
86 while i < lines.len() {
87 let line = lines[i].trim();
88 if line.starts_with("```") {
89 let tag = line.trim_start_matches('`').trim();
90 let language = Self::detect_language(tag);
91 let start_line = i + 1; i += 1;
93
94 let mut code_lines = Vec::new();
95 while i < lines.len() {
96 let inner = lines[i];
97 if inner.trim() == "```" {
98 break;
99 }
100 code_lines.push(inner);
101 i += 1;
102 }
103 let end_line = i; blocks.push(CodeBlock {
106 language,
107 code: code_lines.join("\n"),
108 start_line,
109 end_line,
110 });
111 }
112 i += 1;
113 }
114
115 blocks
116 }
117
118 pub fn detect_language(tag: &str) -> Language {
120 match tag.to_lowercase().trim() {
121 "rust" | "rs" => Language::Rust,
122 "python" | "py" => Language::Python,
123 "javascript" | "js" => Language::JavaScript,
124 "typescript" | "ts" => Language::TypeScript,
125 "go" | "golang" => Language::Go,
126 "bash" | "sh" | "shell" => Language::Bash,
127 other => Language::Unknown(other.to_string()),
128 }
129 }
130}
131
132pub struct CodeSimulator {
137 pub mode: ExecutionMode,
138}
139
140impl CodeSimulator {
141 pub fn new(mode: ExecutionMode) -> Self {
142 Self { mode }
143 }
144
145 pub fn simulate(&self, block: &CodeBlock) -> SimulationResult {
147 let start = std::time::Instant::now();
148 let mut output = String::new();
149 let mut errors = Vec::new();
150 let warnings = Vec::new();
151
152 match &block.language {
153 Language::Rust => {
154 if block.code.contains("panic!") {
155 errors.push("thread 'main' panicked".to_string());
156 } else if block.code.contains("println!") {
157 output = Self::extract_rust_println(&block.code);
158 } else {
159 output = "Execution simulated".to_string();
160 }
161 }
162 Language::Python => {
163 if block.code.contains("raise") {
164 errors.push("Traceback (most recent call last): RuntimeError".to_string());
165 } else if block.code.contains("print(") {
166 output = Self::extract_python_print(&block.code);
167 } else {
168 output = "Execution simulated".to_string();
169 }
170 }
171 Language::Bash => {
172 if block.code.contains("echo ") {
173 output = Self::extract_bash_echo(&block.code);
174 } else if block.code.contains("ls") {
175 output = "[file list simulation]".to_string();
176 } else {
177 output = "Execution simulated".to_string();
178 }
179 }
180 _ => {
181 output = "Execution simulated".to_string();
182 }
183 }
184
185 SimulationResult {
186 block: block.clone(),
187 output,
188 errors,
189 warnings,
190 execution_ms: start.elapsed().as_millis() as u64,
191 }
192 }
193
194 fn extract_rust_println(code: &str) -> String {
195 let mut results = Vec::new();
197 for line in code.lines() {
198 let trimmed = line.trim();
199 if let Some(start) = trimmed.find("println!(") {
200 let after = &trimmed[start + 9..];
201 if let Some(content) = Self::extract_string_arg(after) {
203 results.push(content);
205 }
206 }
207 }
208 results.join("\n")
209 }
210
211 fn extract_python_print(code: &str) -> String {
212 let mut results = Vec::new();
213 for line in code.lines() {
214 let trimmed = line.trim();
215 if let Some(start) = trimmed.find("print(") {
216 let after = &trimmed[start + 6..];
217 if let Some(content) = Self::extract_string_arg(after) {
218 results.push(content);
219 }
220 }
221 }
222 results.join("\n")
223 }
224
225 fn extract_bash_echo(code: &str) -> String {
226 let mut results = Vec::new();
227 for line in code.lines() {
228 let trimmed = line.trim();
229 if trimmed.starts_with("echo ") {
230 let arg = trimmed[5..].trim().trim_matches('"').trim_matches('\'');
231 results.push(arg.to_string());
232 }
233 }
234 results.join("\n")
235 }
236
237 fn extract_string_arg(s: &str) -> Option<String> {
238 let s = s.trim();
240 if s.starts_with('"') {
241 let inner = &s[1..];
242 if let Some(end) = inner.find('"') {
243 return Some(inner[..end].to_string());
244 }
245 } else if s.starts_with('\'') {
246 let inner = &s[1..];
247 if let Some(end) = inner.find('\'') {
248 return Some(inner[..end].to_string());
249 }
250 }
251 None
252 }
253
254 pub fn validate_syntax(block: &CodeBlock) -> Vec<String> {
256 let mut errors = Vec::new();
257 let code = &block.code;
258
259 let open_braces = code.chars().filter(|&c| c == '{').count();
261 let close_braces = code.chars().filter(|&c| c == '}').count();
262 if open_braces != close_braces {
263 errors.push(format!(
264 "Unbalanced braces: {} opening, {} closing",
265 open_braces, close_braces
266 ));
267 }
268
269 let open_brackets = code.chars().filter(|&c| c == '[').count();
271 let close_brackets = code.chars().filter(|&c| c == ']').count();
272 if open_brackets != close_brackets {
273 errors.push(format!(
274 "Unbalanced brackets: {} opening, {} closing",
275 open_brackets, close_brackets
276 ));
277 }
278
279 let open_parens = code.chars().filter(|&c| c == '(').count();
281 let close_parens = code.chars().filter(|&c| c == ')').count();
282 if open_parens != close_parens {
283 errors.push(format!(
284 "Unbalanced parentheses: {} opening, {} closing",
285 open_parens, close_parens
286 ));
287 }
288
289 let quote_count = code.chars().filter(|&c| c == '"').count();
292 if quote_count % 2 != 0 {
293 errors.push("Possible unclosed string: odd number of double-quote characters".to_string());
294 }
295
296 errors
297 }
298}
299
300#[derive(Debug, Clone)]
305pub struct CodeReport {
306 pub total_blocks: usize,
307 pub by_language: HashMap<String, usize>,
308 pub total_lines: usize,
309 pub syntax_errors: usize,
310}
311
312pub fn analyze_blocks(blocks: &[CodeBlock]) -> CodeReport {
314 let mut by_language: HashMap<String, usize> = HashMap::new();
315 let mut total_lines = 0usize;
316 let mut syntax_errors = 0usize;
317
318 for block in blocks {
319 let lang_key = block.language.to_string();
320 *by_language.entry(lang_key).or_insert(0) += 1;
321
322 let lines = block.code.lines().count();
323 total_lines += lines;
324
325 let errs = CodeSimulator::validate_syntax(block);
326 syntax_errors += errs.len();
327 }
328
329 CodeReport {
330 total_blocks: blocks.len(),
331 by_language,
332 total_lines,
333 syntax_errors,
334 }
335}
336
337#[cfg(test)]
342mod tests {
343 use super::*;
344
345 const SAMPLE_MD: &str = r#"
346Some text here.
347
348```rust
349fn main() {
350 println!("Hello, world!");
351}
352```
353
354Another paragraph.
355
356```python
357def greet():
358 print("Hello from Python")
359```
360
361```bash
362echo "Hello from bash"
363ls
364```
365"#;
366
367 #[test]
368 fn test_extract_finds_code_blocks() {
369 let blocks = CodeExtractor::extract(SAMPLE_MD);
370 assert_eq!(blocks.len(), 3);
371 }
372
373 #[test]
374 fn test_language_detection() {
375 assert_eq!(CodeExtractor::detect_language("rust"), Language::Rust);
376 assert_eq!(CodeExtractor::detect_language("py"), Language::Python);
377 assert_eq!(CodeExtractor::detect_language("js"), Language::JavaScript);
378 assert_eq!(CodeExtractor::detect_language("ts"), Language::TypeScript);
379 assert_eq!(CodeExtractor::detect_language("go"), Language::Go);
380 assert_eq!(CodeExtractor::detect_language("sh"), Language::Bash);
381 assert_eq!(
382 CodeExtractor::detect_language("haskell"),
383 Language::Unknown("haskell".to_string())
384 );
385 }
386
387 #[test]
388 fn test_simulate_rust_println() {
389 let block = CodeBlock {
390 language: Language::Rust,
391 code: r#"fn main() { println!("Hello, world!"); }"#.to_string(),
392 start_line: 1,
393 end_line: 1,
394 };
395 let sim = CodeSimulator::new(ExecutionMode::Simulate);
396 let result = sim.simulate(&block);
397 assert!(result.output.contains("Hello, world!"));
398 assert!(result.errors.is_empty());
399 }
400
401 #[test]
402 fn test_simulate_python_print() {
403 let block = CodeBlock {
404 language: Language::Python,
405 code: "print(\"Hello from Python\")".to_string(),
406 start_line: 1,
407 end_line: 1,
408 };
409 let sim = CodeSimulator::new(ExecutionMode::Simulate);
410 let result = sim.simulate(&block);
411 assert!(result.output.contains("Hello from Python"));
412 }
413
414 #[test]
415 fn test_simulate_bash_echo() {
416 let block = CodeBlock {
417 language: Language::Bash,
418 code: "echo \"Hello from bash\"".to_string(),
419 start_line: 1,
420 end_line: 1,
421 };
422 let sim = CodeSimulator::new(ExecutionMode::Simulate);
423 let result = sim.simulate(&block);
424 assert!(result.output.contains("Hello from bash"));
425 }
426
427 #[test]
428 fn test_validate_catches_unbalanced_braces() {
429 let block = CodeBlock {
430 language: Language::Rust,
431 code: "fn main() { let x = 1;".to_string(),
432 start_line: 1,
433 end_line: 1,
434 };
435 let errors = CodeSimulator::validate_syntax(&block);
436 assert!(!errors.is_empty());
437 assert!(errors[0].contains("brace"));
438 }
439
440 #[test]
441 fn test_validate_balanced_code() {
442 let block = CodeBlock {
443 language: Language::Rust,
444 code: "fn main() { let x = 1; }".to_string(),
445 start_line: 1,
446 end_line: 1,
447 };
448 let errors = CodeSimulator::validate_syntax(&block);
449 assert!(errors.is_empty());
450 }
451
452 #[test]
453 fn test_analyze_blocks() {
454 let blocks = CodeExtractor::extract(SAMPLE_MD);
455 let report = analyze_blocks(&blocks);
456 assert_eq!(report.total_blocks, 3);
457 assert!(report.by_language.contains_key("rust"));
458 assert!(report.by_language.contains_key("python"));
459 assert!(report.by_language.contains_key("bash"));
460 }
461
462 #[test]
463 fn test_rust_panic_produces_error() {
464 let block = CodeBlock {
465 language: Language::Rust,
466 code: r#"fn main() { panic!("something went wrong"); }"#.to_string(),
467 start_line: 1,
468 end_line: 1,
469 };
470 let sim = CodeSimulator::new(ExecutionMode::Simulate);
471 let result = sim.simulate(&block);
472 assert!(!result.errors.is_empty());
473 }
474}