Skip to main content

run/engine/
c.rs

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