1use std::collections::BTreeSet;
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::process::{Command, Stdio};
5use std::time::Instant;
6
7use anyhow::{Context, Result};
8use tempfile::{Builder, TempDir};
9
10use super::{
11 ExecutionOutcome, ExecutionPayload, LanguageEngine, LanguageSession, cache_store, hash_source,
12 run_version_command, try_cached_execution,
13};
14
15pub struct CEngine {
16 compiler: Option<PathBuf>,
17}
18
19impl Default for CEngine {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl CEngine {
26 pub fn new() -> Self {
27 Self {
28 compiler: resolve_c_compiler(),
29 }
30 }
31
32 fn ensure_compiler(&self) -> Result<&Path> {
33 self.compiler.as_deref().ok_or_else(|| {
34 anyhow::anyhow!(
35 "C support requires a C compiler such as `cc`, `clang`, or `gcc`. Install one and ensure it is on your PATH."
36 )
37 })
38 }
39
40 fn write_source(&self, code: &str, dir: &Path) -> Result<PathBuf> {
41 let source_path = dir.join("main.c");
42 let prepared = prepare_inline_source(code);
43 std::fs::write(&source_path, prepared).with_context(|| {
44 format!(
45 "failed to write temporary C source to {}",
46 source_path.display()
47 )
48 })?;
49 Ok(source_path)
50 }
51
52 fn copy_source(&self, original: &Path, dir: &Path) -> Result<PathBuf> {
53 let target = dir.join("main.c");
54 std::fs::copy(original, &target).with_context(|| {
55 format!(
56 "failed to copy C source from {} to {}",
57 original.display(),
58 target.display()
59 )
60 })?;
61 Ok(target)
62 }
63
64 fn compile(&self, source: &Path, output: &Path) -> Result<std::process::Output> {
65 let compiler = self.ensure_compiler()?;
66 let mut cmd = Command::new(compiler);
67 cmd.arg(source)
68 .arg("-std=c11")
69 .arg("-O0")
70 .arg("-Wall")
71 .arg("-Wextra")
72 .arg("-o")
73 .arg(output)
74 .stdout(Stdio::piped())
75 .stderr(Stdio::piped());
76 cmd.output().with_context(|| {
77 format!(
78 "failed to invoke {} to compile {}",
79 compiler.display(),
80 source.display()
81 )
82 })
83 }
84
85 fn run_binary(&self, binary: &Path, args: &[String]) -> Result<std::process::Output> {
86 let mut cmd = Command::new(binary);
87 cmd.args(args).stdout(Stdio::piped()).stderr(Stdio::piped());
88 cmd.stdin(Stdio::inherit());
89 cmd.output()
90 .with_context(|| format!("failed to execute compiled binary {}", binary.display()))
91 }
92
93 fn binary_path(dir: &Path) -> PathBuf {
94 let mut path = dir.join("run_c_binary");
95 let suffix = std::env::consts::EXE_SUFFIX;
96 if !suffix.is_empty() {
97 if let Some(stripped) = suffix.strip_prefix('.') {
98 path.set_extension(stripped);
99 } else {
100 path = PathBuf::from(format!("{}{}", path.display(), suffix));
101 }
102 }
103 path
104 }
105}
106
107impl LanguageEngine for CEngine {
108 fn id(&self) -> &'static str {
109 "c"
110 }
111
112 fn display_name(&self) -> &'static str {
113 "C"
114 }
115
116 fn aliases(&self) -> &[&'static str] {
117 &["ansi-c"]
118 }
119
120 fn supports_sessions(&self) -> bool {
121 true
122 }
123
124 fn validate(&self) -> Result<()> {
125 let compiler = self.ensure_compiler()?;
126 let mut cmd = Command::new(compiler);
127 cmd.arg("--version")
128 .stdout(Stdio::null())
129 .stderr(Stdio::null());
130 cmd.status()
131 .with_context(|| format!("failed to invoke {}", compiler.display()))?
132 .success()
133 .then_some(())
134 .ok_or_else(|| anyhow::anyhow!("{} is not executable", compiler.display()))
135 }
136
137 fn toolchain_version(&self) -> Result<Option<String>> {
138 let compiler = self.ensure_compiler()?;
139 let mut cmd = Command::new(compiler);
140 cmd.arg("--version");
141 let context = format!("{}", compiler.display());
142 run_version_command(cmd, &context)
143 }
144
145 fn execute(&self, payload: &ExecutionPayload) -> Result<ExecutionOutcome> {
146 let args = payload.args();
148
149 if let Some(code) = match payload {
150 ExecutionPayload::Inline { code, .. } | ExecutionPayload::Stdin { code, .. } => {
151 Some(code.as_str())
152 }
153 _ => None,
154 } {
155 let prepared = prepare_inline_source(code);
156 let src_hash = hash_source(&prepared);
157 if let Some(output) = try_cached_execution(src_hash) {
158 let start = Instant::now();
159 return Ok(ExecutionOutcome {
160 language: self.id().to_string(),
161 exit_code: output.status.code(),
162 stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
163 stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
164 duration: start.elapsed(),
165 });
166 }
167 }
168
169 let temp_dir = Builder::new()
170 .prefix("run-c")
171 .tempdir()
172 .context("failed to create temporary directory for c build")?;
173 let dir_path = temp_dir.path();
174
175 let (source_path, cache_key) = match payload {
176 ExecutionPayload::Inline { code, .. } | ExecutionPayload::Stdin { code, .. } => {
177 let prepared = prepare_inline_source(code);
178 let h = hash_source(&prepared);
179 (self.write_source(code, dir_path)?, Some(h))
180 }
181 ExecutionPayload::File { path, .. } => (self.copy_source(path, dir_path)?, None),
182 };
183
184 let binary_path = Self::binary_path(dir_path);
185 let start = Instant::now();
186
187 let compile_output = self.compile(&source_path, &binary_path)?;
188 if !compile_output.status.success() {
189 return Ok(ExecutionOutcome {
190 language: self.id().to_string(),
191 exit_code: compile_output.status.code(),
192 stdout: String::from_utf8_lossy(&compile_output.stdout).into_owned(),
193 stderr: String::from_utf8_lossy(&compile_output.stderr).into_owned(),
194 duration: start.elapsed(),
195 });
196 }
197
198 if let Some(h) = cache_key {
199 cache_store(h, &binary_path);
200 }
201
202 let run_output = self.run_binary(&binary_path, args)?;
203 Ok(ExecutionOutcome {
204 language: self.id().to_string(),
205 exit_code: run_output.status.code(),
206 stdout: String::from_utf8_lossy(&run_output.stdout).into_owned(),
207 stderr: String::from_utf8_lossy(&run_output.stderr).into_owned(),
208 duration: start.elapsed(),
209 })
210 }
211
212 fn start_session(&self) -> Result<Box<dyn LanguageSession>> {
213 let compiler = self.ensure_compiler()?.to_path_buf();
214 let session = CSession::new(compiler)?;
215 Ok(Box::new(session))
216 }
217}
218
219const SESSION_MAIN_FILE: &str = "main.c";
220
221const PRINT_HELPERS: &str = concat!(
222 "static void __print_int(long long value) { printf(\"%lld\\n\", value); }\n",
223 "static void __print_uint(unsigned long long value) { printf(\"%llu\\n\", value); }\n",
224 "static void __print_double(double value) { printf(\"%0.17g\\n\", value); }\n",
225 "static void __print_cstr(const char *value) { if (!value) { printf(\"(null)\\n\"); } else { printf(\"%s\\n\", value); } }\n",
226 "static void __print_char(int value) { printf(\"%d\\n\", value); }\n",
227 "static void __print_pointer(const void *value) { printf(\"%p\\n\", value); }\n",
228 "#define __print(value) _Generic((value), \\\n",
229 " char: __print_char, \\\n",
230 " signed char: __print_int, \\\n",
231 " short: __print_int, \\\n",
232 " int: __print_int, \\\n",
233 " long: __print_int, \\\n",
234 " long long: __print_int, \\\n",
235 " unsigned char: __print_uint, \\\n",
236 " unsigned short: __print_uint, \\\n",
237 " unsigned int: __print_uint, \\\n",
238 " unsigned long: __print_uint, \\\n",
239 " unsigned long long: __print_uint, \\\n",
240 " float: __print_double, \\\n",
241 " double: __print_double, \\\n",
242 " long double: __print_double, \\\n",
243 " char *: __print_cstr, \\\n",
244 " const char *: __print_cstr, \\\n",
245 " default: __print_pointer \\\n",
246 ")(value)\n\n",
247);
248
249struct CSession {
250 compiler: PathBuf,
251 workspace: TempDir,
252 includes: BTreeSet<String>,
253 items: Vec<String>,
254 statements: Vec<String>,
255 last_stdout: String,
256 last_stderr: String,
257}
258
259enum CSnippetKind {
260 Include(Option<String>),
261 Item,
262 Statement,
263}
264
265impl CSession {
266 fn new(compiler: PathBuf) -> Result<Self> {
267 let workspace = TempDir::new().context("failed to create C session workspace")?;
268 let session = Self {
269 compiler,
270 workspace,
271 includes: Self::default_includes(),
272 items: Vec::new(),
273 statements: Vec::new(),
274 last_stdout: String::new(),
275 last_stderr: String::new(),
276 };
277 session.persist_source()?;
278 Ok(session)
279 }
280
281 fn default_includes() -> BTreeSet<String> {
282 let mut includes = BTreeSet::new();
283 includes.insert("#include <stdio.h>".to_string());
284 includes.insert("#include <inttypes.h>".to_string());
285 includes
286 }
287
288 fn language_id(&self) -> &str {
289 "c"
290 }
291
292 fn source_path(&self) -> PathBuf {
293 self.workspace.path().join(SESSION_MAIN_FILE)
294 }
295
296 fn binary_path(&self) -> PathBuf {
297 CEngine::binary_path(self.workspace.path())
298 }
299
300 fn persist_source(&self) -> Result<()> {
301 let source = self.render_source();
302 fs::write(self.source_path(), source)
303 .with_context(|| "failed to write C session source".to_string())
304 }
305
306 fn render_source(&self) -> String {
307 let mut source = String::new();
308
309 for include in &self.includes {
310 source.push_str(include);
311 if !include.ends_with('\n') {
312 source.push('\n');
313 }
314 }
315
316 source.push('\n');
317 source.push_str(PRINT_HELPERS);
318
319 for item in &self.items {
320 source.push_str(item);
321 if !item.ends_with('\n') {
322 source.push('\n');
323 }
324 source.push('\n');
325 }
326
327 source.push_str("int main(void) {\n");
328 if self.statements.is_empty() {
329 source.push_str(" // session body\n");
330 } else {
331 for snippet in &self.statements {
332 for line in snippet.lines() {
333 source.push_str(" ");
334 source.push_str(line);
335 source.push('\n');
336 }
337 }
338 }
339 source.push_str(" return 0;\n}\n");
340
341 source
342 }
343
344 fn compile(&self) -> Result<std::process::Output> {
345 let source = self.source_path();
346 let binary = self.binary_path();
347 self.compile_with_paths(&source, &binary)
348 }
349
350 fn compile_with_paths(&self, source: &Path, binary: &Path) -> Result<std::process::Output> {
351 let mut cmd = Command::new(&self.compiler);
352 cmd.arg(source)
353 .arg("-std=c11")
354 .arg("-O0")
355 .arg("-Wall")
356 .arg("-Wextra")
357 .arg("-o")
358 .arg(binary)
359 .stdout(Stdio::piped())
360 .stderr(Stdio::piped());
361 cmd.output().with_context(|| {
362 format!(
363 "failed to invoke {} to compile {}",
364 self.compiler.display(),
365 source.display()
366 )
367 })
368 }
369
370 fn run_binary(&self) -> Result<std::process::Output> {
371 let binary = self.binary_path();
372 self.run_binary_path(&binary)
373 }
374
375 fn run_binary_path(&self, binary: &Path) -> Result<std::process::Output> {
376 let mut cmd = Command::new(binary);
377 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
378 cmd.output()
379 .with_context(|| format!("failed to execute compiled binary {}", binary.display()))
380 }
381
382 fn run_standalone_program(&self, code: &str) -> Result<ExecutionOutcome> {
383 let start = Instant::now();
384 let source_path = self.workspace.path().join("standalone.c");
385 let binary_path = self.workspace.path().join("standalone_c_binary");
386
387 let mut source = String::new();
388
389 for include in &self.includes {
390 source.push_str(include);
391 if !include.ends_with('\n') {
392 source.push('\n');
393 }
394 }
395
396 source.push('\n');
397 source.push_str(PRINT_HELPERS);
398
399 for item in &self.items {
400 source.push_str(item);
401 if !item.ends_with('\n') {
402 source.push('\n');
403 }
404 source.push('\n');
405 }
406
407 source.push_str(code);
408 if !code.ends_with('\n') {
409 source.push('\n');
410 }
411
412 fs::write(&source_path, source)
413 .with_context(|| "failed to write C standalone source".to_string())?;
414
415 let compile_output = self.compile_with_paths(&source_path, &binary_path)?;
416 if !compile_output.status.success() {
417 return Ok(ExecutionOutcome {
418 language: self.language_id().to_string(),
419 exit_code: compile_output.status.code(),
420 stdout: Self::normalize_output(&compile_output.stdout),
421 stderr: Self::normalize_output(&compile_output.stderr),
422 duration: start.elapsed(),
423 });
424 }
425
426 let run_output = self.run_binary_path(&binary_path)?;
427 Ok(ExecutionOutcome {
428 language: self.language_id().to_string(),
429 exit_code: run_output.status.code(),
430 stdout: Self::normalize_output(&run_output.stdout),
431 stderr: Self::normalize_output(&run_output.stderr),
432 duration: start.elapsed(),
433 })
434 }
435
436 fn add_include(&mut self, line: &str) -> CSnippetKind {
437 let added = self.includes.insert(line.to_string());
438 if added {
439 CSnippetKind::Include(Some(line.to_string()))
440 } else {
441 CSnippetKind::Include(None)
442 }
443 }
444
445 fn add_item(&mut self, code: &str) -> CSnippetKind {
446 let mut snippet = code.to_string();
447 if !snippet.ends_with('\n') {
448 snippet.push('\n');
449 }
450 self.items.push(snippet);
451 CSnippetKind::Item
452 }
453
454 fn add_statement(&mut self, code: &str) -> CSnippetKind {
455 let mut snippet = code.to_string();
456 if !snippet.ends_with('\n') {
457 snippet.push('\n');
458 }
459 self.statements.push(snippet);
460 CSnippetKind::Statement
461 }
462
463 fn add_expression(&mut self, code: &str) -> CSnippetKind {
464 let wrapped = wrap_expression(code);
465 self.statements.push(wrapped);
466 CSnippetKind::Statement
467 }
468
469 fn reset_state(&mut self) -> Result<()> {
470 self.includes = Self::default_includes();
471 self.items.clear();
472 self.statements.clear();
473 self.last_stdout.clear();
474 self.last_stderr.clear();
475 self.persist_source()
476 }
477
478 fn rollback(&mut self, kind: CSnippetKind) -> Result<()> {
479 match kind {
480 CSnippetKind::Include(Some(line)) => {
481 self.includes.remove(&line);
482 }
483 CSnippetKind::Include(None) => {}
484 CSnippetKind::Item => {
485 self.items.pop();
486 }
487 CSnippetKind::Statement => {
488 self.statements.pop();
489 }
490 }
491 self.persist_source()
492 }
493
494 fn normalize_output(bytes: &[u8]) -> String {
495 String::from_utf8_lossy(bytes)
496 .replace("\r\n", "\n")
497 .replace('\r', "")
498 }
499
500 fn diff_outputs(previous: &str, current: &str) -> String {
501 if let Some(suffix) = current.strip_prefix(previous) {
502 suffix.to_string()
503 } else {
504 current.to_string()
505 }
506 }
507
508 fn run_insertion(&mut self, kind: CSnippetKind) -> Result<(ExecutionOutcome, bool)> {
509 if matches!(kind, CSnippetKind::Include(None)) {
510 return Ok((
511 ExecutionOutcome {
512 language: self.language_id().to_string(),
513 exit_code: None,
514 stdout: String::new(),
515 stderr: String::new(),
516 duration: Default::default(),
517 },
518 true,
519 ));
520 }
521
522 self.persist_source()?;
523 let start = Instant::now();
524 let compile_output = self.compile()?;
525
526 if !compile_output.status.success() {
527 let duration = start.elapsed();
528 self.rollback(kind)?;
529 let outcome = ExecutionOutcome {
530 language: self.language_id().to_string(),
531 exit_code: compile_output.status.code(),
532 stdout: Self::normalize_output(&compile_output.stdout),
533 stderr: Self::normalize_output(&compile_output.stderr),
534 duration,
535 };
536 return Ok((outcome, false));
537 }
538
539 let run_output = self.run_binary()?;
540 let duration = start.elapsed();
541 let stdout_full = Self::normalize_output(&run_output.stdout);
542 let stderr_full = Self::normalize_output(&run_output.stderr);
543
544 let stdout = Self::diff_outputs(&self.last_stdout, &stdout_full);
545 let stderr = Self::diff_outputs(&self.last_stderr, &stderr_full);
546
547 if run_output.status.success() {
548 self.last_stdout = stdout_full;
549 self.last_stderr = stderr_full;
550 let outcome = ExecutionOutcome {
551 language: self.language_id().to_string(),
552 exit_code: run_output.status.code(),
553 stdout,
554 stderr,
555 duration,
556 };
557 return Ok((outcome, true));
558 }
559
560 self.rollback(kind)?;
561 let outcome = ExecutionOutcome {
562 language: self.language_id().to_string(),
563 exit_code: run_output.status.code(),
564 stdout,
565 stderr,
566 duration,
567 };
568 Ok((outcome, false))
569 }
570
571 fn run_include(&mut self, line: &str) -> Result<(ExecutionOutcome, bool)> {
572 let kind = self.add_include(line);
573 self.run_insertion(kind)
574 }
575
576 fn run_item(&mut self, code: &str) -> Result<(ExecutionOutcome, bool)> {
577 let kind = self.add_item(code);
578 self.run_insertion(kind)
579 }
580
581 fn run_statement(&mut self, code: &str) -> Result<(ExecutionOutcome, bool)> {
582 let kind = self.add_statement(code);
583 self.run_insertion(kind)
584 }
585
586 fn run_expression(&mut self, code: &str) -> Result<(ExecutionOutcome, bool)> {
587 let kind = self.add_expression(code);
588 self.run_insertion(kind)
589 }
590}
591
592impl LanguageSession for CSession {
593 fn language_id(&self) -> &str {
594 CSession::language_id(self)
595 }
596
597 fn eval(&mut self, code: &str) -> Result<ExecutionOutcome> {
598 let trimmed = code.trim();
599 if trimmed.is_empty() {
600 return Ok(ExecutionOutcome {
601 language: self.language_id().to_string(),
602 exit_code: None,
603 stdout: String::new(),
604 stderr: String::new(),
605 duration: Instant::now().elapsed(),
606 });
607 }
608
609 if trimmed.eq_ignore_ascii_case(":reset") {
610 self.reset_state()?;
611 return Ok(ExecutionOutcome {
612 language: self.language_id().to_string(),
613 exit_code: None,
614 stdout: String::new(),
615 stderr: String::new(),
616 duration: Default::default(),
617 });
618 }
619
620 if trimmed.eq_ignore_ascii_case(":help") {
621 return Ok(ExecutionOutcome {
622 language: self.language_id().to_string(),
623 exit_code: None,
624 stdout:
625 "C commands:\n :reset - clear session state\n :help - show this message\n"
626 .to_string(),
627 stderr: String::new(),
628 duration: Default::default(),
629 });
630 }
631
632 if contains_main_definition(code) {
633 return self.run_standalone_program(code);
634 }
635
636 if let Some(include) = parse_include(trimmed) {
637 let (outcome, _) = self.run_include(&include)?;
638 return Ok(outcome);
639 }
640
641 if is_item_snippet(trimmed) {
642 let (outcome, _) = self.run_item(code)?;
643 return Ok(outcome);
644 }
645
646 if should_treat_as_expression(trimmed) {
647 let (outcome, success) = self.run_expression(trimmed)?;
648 if success {
649 return Ok(outcome);
650 }
651 }
652
653 let (outcome, _) = self.run_statement(code)?;
654 Ok(outcome)
655 }
656
657 fn shutdown(&mut self) -> Result<()> {
658 Ok(())
659 }
660}
661
662fn contains_main_definition(code: &str) -> bool {
663 let bytes = code.as_bytes();
664 let len = bytes.len();
665 let mut i = 0;
666 let mut in_line_comment = false;
667 let mut in_block_comment = false;
668 let mut in_string = false;
669 let mut string_delim = b'"';
670 let mut in_char = false;
671
672 while i < len {
673 let b = bytes[i];
674
675 if in_line_comment {
676 if b == b'\n' {
677 in_line_comment = false;
678 }
679 i += 1;
680 continue;
681 }
682
683 if in_block_comment {
684 if b == b'*' && i + 1 < len && bytes[i + 1] == b'/' {
685 in_block_comment = false;
686 i += 2;
687 continue;
688 }
689 i += 1;
690 continue;
691 }
692
693 if in_string {
694 if b == b'\\' {
695 i = (i + 2).min(len);
696 continue;
697 }
698 if b == string_delim {
699 in_string = false;
700 }
701 i += 1;
702 continue;
703 }
704
705 if in_char {
706 if b == b'\\' {
707 i = (i + 2).min(len);
708 continue;
709 }
710 if b == b'\'' {
711 in_char = false;
712 }
713 i += 1;
714 continue;
715 }
716
717 match b {
718 b'/' if i + 1 < len && bytes[i + 1] == b'/' => {
719 in_line_comment = true;
720 i += 2;
721 continue;
722 }
723 b'/' if i + 1 < len && bytes[i + 1] == b'*' => {
724 in_block_comment = true;
725 i += 2;
726 continue;
727 }
728 b'"' | b'\'' => {
729 if b == b'"' {
730 in_string = true;
731 string_delim = b;
732 } else {
733 in_char = true;
734 }
735 i += 1;
736 continue;
737 }
738 b'm' if i + 4 <= len && &bytes[i..i + 4] == b"main" => {
739 if i > 0 {
740 let prev = bytes[i - 1];
741 if prev.is_ascii_alphanumeric() || prev == b'_' {
742 i += 1;
743 continue;
744 }
745 }
746
747 let after_name = i + 4;
748 if after_name < len {
749 let next = bytes[after_name];
750 if next.is_ascii_alphanumeric() || next == b'_' {
751 i += 1;
752 continue;
753 }
754 }
755
756 let mut j = after_name;
757 while j < len && bytes[j].is_ascii_whitespace() {
758 j += 1;
759 }
760 if j >= len || bytes[j] != b'(' {
761 i += 1;
762 continue;
763 }
764
765 let mut depth = 1usize;
766 let mut k = j + 1;
767 let mut inner_line_comment = false;
768 let mut inner_block_comment = false;
769 let mut inner_string = false;
770 let mut inner_char = false;
771
772 while k < len {
773 let ch = bytes[k];
774
775 if inner_line_comment {
776 if ch == b'\n' {
777 inner_line_comment = false;
778 }
779 k += 1;
780 continue;
781 }
782
783 if inner_block_comment {
784 if ch == b'*' && k + 1 < len && bytes[k + 1] == b'/' {
785 inner_block_comment = false;
786 k += 2;
787 continue;
788 }
789 k += 1;
790 continue;
791 }
792
793 if inner_string {
794 if ch == b'\\' {
795 k = (k + 2).min(len);
796 continue;
797 }
798 if ch == b'"' {
799 inner_string = false;
800 }
801 k += 1;
802 continue;
803 }
804
805 if inner_char {
806 if ch == b'\\' {
807 k = (k + 2).min(len);
808 continue;
809 }
810 if ch == b'\'' {
811 inner_char = false;
812 }
813 k += 1;
814 continue;
815 }
816
817 match ch {
818 b'/' if k + 1 < len && bytes[k + 1] == b'/' => {
819 inner_line_comment = true;
820 k += 2;
821 continue;
822 }
823 b'/' if k + 1 < len && bytes[k + 1] == b'*' => {
824 inner_block_comment = true;
825 k += 2;
826 continue;
827 }
828 b'"' => {
829 inner_string = true;
830 k += 1;
831 continue;
832 }
833 b'\'' => {
834 inner_char = true;
835 k += 1;
836 continue;
837 }
838 b'(' => {
839 depth += 1;
840 }
841 b')' => {
842 depth -= 1;
843 k += 1;
844 if depth == 0 {
845 break;
846 } else {
847 continue;
848 }
849 }
850 _ => {}
851 }
852
853 k += 1;
854 }
855
856 if depth != 0 {
857 i += 1;
858 continue;
859 }
860
861 let mut after = k;
862 loop {
863 while after < len && bytes[after].is_ascii_whitespace() {
864 after += 1;
865 }
866 if after + 1 < len && bytes[after] == b'/' && bytes[after + 1] == b'/' {
867 after += 2;
868 while after < len && bytes[after] != b'\n' {
869 after += 1;
870 }
871 continue;
872 }
873 if after + 1 < len && bytes[after] == b'/' && bytes[after + 1] == b'*' {
874 after += 2;
875 while after + 1 < len {
876 if bytes[after] == b'*' && bytes[after + 1] == b'/' {
877 after += 2;
878 break;
879 }
880 after += 1;
881 }
882 continue;
883 }
884 break;
885 }
886
887 if after < len && bytes[after] == b'{' {
888 return true;
889 }
890 }
891 _ => {}
892 }
893
894 i += 1;
895 }
896
897 false
898}
899
900fn parse_include(code: &str) -> Option<String> {
901 let trimmed = code.trim_start();
902 if !trimmed.starts_with("#include") {
903 return None;
904 }
905 let line = trimmed.lines().next()?.trim().to_string();
906 if line.is_empty() { None } else { Some(line) }
907}
908
909fn is_item_snippet(code: &str) -> bool {
910 let trimmed = code.trim_start();
911 if trimmed.starts_with("#include") {
912 return false;
913 }
914
915 if trimmed.starts_with('#') {
916 return true;
917 }
918
919 const KEYWORDS: [&str; 8] = [
920 "typedef", "struct", "union", "enum", "extern", "static", "const", "volatile",
921 ];
922 if KEYWORDS.iter().any(|kw| trimmed.starts_with(kw)) {
923 return true;
924 }
925
926 if let Some(open_brace) = trimmed.find('{') {
927 let before_brace = trimmed[..open_brace].trim();
928 if before_brace.ends_with(';') {
929 return false;
930 }
931
932 const CONTROL_KEYWORDS: [&str; 5] = ["if", "for", "while", "switch", "do"];
933 if CONTROL_KEYWORDS
934 .iter()
935 .any(|kw| before_brace.starts_with(kw))
936 {
937 return false;
938 }
939
940 if before_brace.contains('(') {
941 return true;
942 }
943 }
944
945 if trimmed.ends_with(';') {
946 if trimmed.contains('(') && trimmed.contains(')') {
947 let before_paren = trimmed.split('(').next().unwrap_or_default();
948 if before_paren.split_whitespace().count() >= 2 {
949 return true;
950 }
951 }
952
953 let first_token = trimmed.split_whitespace().next().unwrap_or_default();
954 const TYPE_PREFIXES: [&str; 12] = [
955 "auto", "register", "signed", "unsigned", "short", "long", "int", "char", "float",
956 "double", "_Bool", "void",
957 ];
958 if TYPE_PREFIXES.contains(&first_token) {
959 return true;
960 }
961 }
962
963 false
964}
965
966fn should_treat_as_expression(code: &str) -> bool {
967 let trimmed = code.trim();
968 if trimmed.is_empty() {
969 return false;
970 }
971 if trimmed.contains('\n') {
972 return false;
973 }
974 if trimmed.ends_with(';') {
975 return false;
976 }
977 const DISALLOWED_PREFIXES: [&str; 21] = [
978 "#", "typedef", "struct", "union", "enum", "extern", "static", "const", "volatile", "void",
979 "auto", "signed", "register", "unsigned", "short", "long", "int", "char", "float",
980 "double", "_Bool",
981 ];
982 if DISALLOWED_PREFIXES.iter().any(|kw| trimmed.starts_with(kw)) {
983 return false;
984 }
985 true
986}
987
988fn wrap_expression(code: &str) -> String {
989 format!("__print({});\n", code)
990}
991
992fn prepare_inline_source(code: &str) -> String {
993 if !needs_wrapper(code) {
994 return code.to_string();
995 }
996
997 if code.trim().is_empty() {
998 return "#include <stdio.h>\n\nint main(void)\n{\n return 0;\n}\n".to_string();
999 }
1000
1001 let body = indent_snippet(code);
1002 format!("#include <stdio.h>\n\nint main(void)\n{{\n{body} return 0;\n}}\n",)
1003}
1004
1005fn needs_wrapper(code: &str) -> bool {
1006 let trimmed = code.trim();
1007 if trimmed.is_empty() {
1008 return true;
1009 }
1010
1011 !(trimmed.contains("#include") || trimmed.contains("main("))
1012}
1013
1014fn indent_snippet(snippet: &str) -> String {
1015 let mut result = String::new();
1016 for line in snippet.lines() {
1017 if line.trim().is_empty() {
1018 result.push('\n');
1019 } else {
1020 result.push_str(" ");
1021 result.push_str(line);
1022 result.push('\n');
1023 }
1024 }
1025
1026 result
1027}
1028
1029fn resolve_c_compiler() -> Option<PathBuf> {
1030 ["cc", "clang", "gcc"]
1031 .into_iter()
1032 .find_map(|candidate| which::which(candidate).ok())
1033}