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    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        // Try cache for inline/stdin payloads
147        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}