1use std::fs;
2use std::path::{Path, PathBuf};
3use std::process::{Command, Stdio};
4use std::time::{Duration, Instant};
5
6use anyhow::{Context, Result};
7use tempfile::{Builder, TempDir};
8
9use super::{
10 ExecutionOutcome, ExecutionPayload, LanguageEngine, LanguageSession, cache_lookup, cache_store,
11 compiler_command, hash_source, perf_record, run_version_command, try_cached_execution,
12};
13
14pub struct CppEngine {
15 compiler: Option<PathBuf>,
16}
17
18impl Default for CppEngine {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl CppEngine {
25 pub fn new() -> Self {
26 Self {
27 compiler: resolve_cpp_compiler(),
28 }
29 }
30
31 fn ensure_compiler(&self) -> Result<&Path> {
32 self.compiler.as_deref().ok_or_else(|| {
33 anyhow::anyhow!(
34 "C++ support requires a C++ compiler such as `c++`, `clang++`, or `g++`. Install one and ensure it is on your PATH."
35 )
36 })
37 }
38
39 fn write_source(&self, code: &str, dir: &Path) -> Result<PathBuf> {
40 let source_path = dir.join("main.cpp");
41 std::fs::write(&source_path, code).with_context(|| {
42 format!(
43 "failed to write temporary C++ source to {}",
44 source_path.display()
45 )
46 })?;
47 Ok(source_path)
48 }
49
50 fn copy_source(&self, original: &Path, dir: &Path) -> Result<PathBuf> {
51 let target = dir.join("main.cpp");
52 std::fs::copy(original, &target).with_context(|| {
53 format!(
54 "failed to copy C++ source from {} to {}",
55 original.display(),
56 target.display()
57 )
58 })?;
59 Ok(target)
60 }
61
62 fn compile(&self, source: &Path, output: &Path) -> Result<std::process::Output> {
63 let compiler = self.ensure_compiler()?;
64 let mut cmd = compiler_command(compiler);
65 cmd.arg(source)
66 .arg("-std=c++17")
67 .arg("-O0")
68 .arg("-w")
69 .arg("-o")
70 .arg(output)
71 .stdout(Stdio::piped())
72 .stderr(Stdio::piped());
73 if let Some(pch_header) = ensure_global_cpp_pch(compiler) {
74 cmd.arg("-include").arg(pch_header);
75 }
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_cpp_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 fn execute_file_incremental(&self, source: &Path, args: &[String]) -> Result<ExecutionOutcome> {
107 let start = Instant::now();
108 let source_text = fs::read_to_string(source).unwrap_or_default();
109 let source_hash = hash_source(&source_text);
110
111 let compiler = self.ensure_compiler()?;
112 let source_key = source
113 .canonicalize()
114 .unwrap_or_else(|_| source.to_path_buf());
115 let workspace = std::env::temp_dir().join(format!(
116 "run-cpp-inc-{:016x}",
117 hash_source(&source_key.to_string_lossy())
118 ));
119 fs::create_dir_all(&workspace).with_context(|| {
120 format!(
121 "failed to create C++ incremental workspace {}",
122 workspace.display()
123 )
124 })?;
125 let obj = workspace.join("main.o");
126 let dep = workspace.join("main.d");
127 let bin = workspace.join("run_cpp_incremental_binary");
128
129 let needs_compile = cpp_needs_recompile(source, &obj, &dep);
130 if !needs_compile && bin.exists() {
131 perf_record("cpp", "file.workspace_hit");
132 cache_store("cpp-file", source_hash, &bin);
133 let run_output = self.run_binary(&bin, args)?;
134 return Ok(ExecutionOutcome {
135 language: self.id().to_string(),
136 exit_code: run_output.status.code(),
137 stdout: String::from_utf8_lossy(&run_output.stdout).into_owned(),
138 stderr: String::from_utf8_lossy(&run_output.stderr).into_owned(),
139 duration: start.elapsed(),
140 });
141 }
142
143 if let Some(cached_bin) = cache_lookup("cpp-file", source_hash) {
144 perf_record("cpp", "file.cache_hit");
145 let _ = fs::copy(&cached_bin, &bin);
146 let run_output = self.run_binary(&bin, args)?;
147 return Ok(ExecutionOutcome {
148 language: self.id().to_string(),
149 exit_code: run_output.status.code(),
150 stdout: String::from_utf8_lossy(&run_output.stdout).into_owned(),
151 stderr: String::from_utf8_lossy(&run_output.stderr).into_owned(),
152 duration: start.elapsed(),
153 });
154 }
155 perf_record("cpp", "file.cache_miss");
156
157 if needs_compile {
158 perf_record("cpp", "file.compile");
159 let mut compile = compiler_command(compiler);
160 compile
161 .arg(source)
162 .arg("-std=c++17")
163 .arg("-O0")
164 .arg("-w")
165 .arg("-c")
166 .arg("-MMD")
167 .arg("-MF")
168 .arg(&dep)
169 .arg("-o")
170 .arg(&obj)
171 .stdout(Stdio::piped())
172 .stderr(Stdio::piped());
173 let compile_out = compile.output().with_context(|| {
174 format!(
175 "failed to invoke {} for incremental C++ compile",
176 compiler.display()
177 )
178 })?;
179 if !compile_out.status.success() {
180 perf_record("cpp", "file.compile_fail");
181 return Ok(ExecutionOutcome {
182 language: self.id().to_string(),
183 exit_code: compile_out.status.code(),
184 stdout: String::from_utf8_lossy(&compile_out.stdout).into_owned(),
185 stderr: String::from_utf8_lossy(&compile_out.stderr).into_owned(),
186 duration: start.elapsed(),
187 });
188 }
189
190 let mut link = compiler_command(compiler);
191 perf_record("cpp", "file.link");
192 link.arg(&obj)
193 .arg("-o")
194 .arg(&bin)
195 .stdout(Stdio::piped())
196 .stderr(Stdio::piped());
197 let link_out = link.output().with_context(|| {
198 format!(
199 "failed to invoke {} for incremental C++ link",
200 compiler.display()
201 )
202 })?;
203 if !link_out.status.success() {
204 perf_record("cpp", "file.link_fail");
205 return Ok(ExecutionOutcome {
206 language: self.id().to_string(),
207 exit_code: link_out.status.code(),
208 stdout: String::from_utf8_lossy(&link_out.stdout).into_owned(),
209 stderr: String::from_utf8_lossy(&link_out.stderr).into_owned(),
210 duration: start.elapsed(),
211 });
212 }
213 cache_store("cpp-file", source_hash, &bin);
214 } else {
215 perf_record("cpp", "file.rehydrate_cache");
217 cache_store("cpp-file", source_hash, &bin);
218 }
219
220 let run_output = self.run_binary(&bin, args)?;
221 Ok(ExecutionOutcome {
222 language: self.id().to_string(),
223 exit_code: run_output.status.code(),
224 stdout: String::from_utf8_lossy(&run_output.stdout).into_owned(),
225 stderr: String::from_utf8_lossy(&run_output.stderr).into_owned(),
226 duration: start.elapsed(),
227 })
228 }
229}
230
231impl LanguageEngine for CppEngine {
232 fn id(&self) -> &'static str {
233 "cpp"
234 }
235
236 fn display_name(&self) -> &'static str {
237 "C++"
238 }
239
240 fn aliases(&self) -> &[&'static str] {
241 &["c++"]
242 }
243
244 fn supports_sessions(&self) -> bool {
245 self.compiler.is_some()
246 }
247
248 fn validate(&self) -> Result<()> {
249 let compiler = self.ensure_compiler()?;
250 let mut cmd = Command::new(compiler);
251 cmd.arg("--version")
252 .stdout(Stdio::null())
253 .stderr(Stdio::null());
254 cmd.status()
255 .with_context(|| format!("failed to invoke {}", compiler.display()))?
256 .success()
257 .then_some(())
258 .ok_or_else(|| anyhow::anyhow!("{} is not executable", compiler.display()))
259 }
260
261 fn toolchain_version(&self) -> Result<Option<String>> {
262 let compiler = self.ensure_compiler()?;
263 let mut cmd = Command::new(compiler);
264 cmd.arg("--version");
265 let context = format!("{}", compiler.display());
266 run_version_command(cmd, &context)
267 }
268
269 fn execute(&self, payload: &ExecutionPayload) -> Result<ExecutionOutcome> {
270 let args = payload.args();
271 if let ExecutionPayload::File { path, .. } = payload {
272 return self.execute_file_incremental(path, args);
273 }
274
275 if let Some(code) = match payload {
277 ExecutionPayload::Inline { code, .. } | ExecutionPayload::Stdin { code, .. } => {
278 Some(code.as_str())
279 }
280 _ => None,
281 } {
282 let src_hash = hash_source(code);
283 if let Some(output) = try_cached_execution("cpp", src_hash) {
284 perf_record("cpp", "inline.cache_hit");
285 let start = Instant::now();
286 return Ok(ExecutionOutcome {
287 language: self.id().to_string(),
288 exit_code: output.status.code(),
289 stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
290 stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
291 duration: start.elapsed(),
292 });
293 }
294 perf_record("cpp", "inline.cache_miss");
295 }
296
297 let temp_dir = Builder::new()
298 .prefix("run-cpp")
299 .tempdir()
300 .context("failed to create temporary directory for cpp build")?;
301 let dir_path = temp_dir.path();
302
303 let (source_path, cache_key) = match payload {
304 ExecutionPayload::Inline { code, .. } | ExecutionPayload::Stdin { code, .. } => {
305 let h = hash_source(code);
306 (self.write_source(code, dir_path)?, Some(h))
307 }
308 ExecutionPayload::File { path, .. } => (self.copy_source(path, dir_path)?, None),
309 };
310
311 let binary_path = Self::binary_path(dir_path);
312 let start = Instant::now();
313
314 let compile_output = self.compile(&source_path, &binary_path)?;
315 if !compile_output.status.success() {
316 return Ok(ExecutionOutcome {
317 language: self.id().to_string(),
318 exit_code: compile_output.status.code(),
319 stdout: String::from_utf8_lossy(&compile_output.stdout).into_owned(),
320 stderr: String::from_utf8_lossy(&compile_output.stderr).into_owned(),
321 duration: start.elapsed(),
322 });
323 }
324
325 if let Some(h) = cache_key {
326 cache_store("cpp", h, &binary_path);
327 }
328
329 let run_output = self.run_binary(&binary_path, args)?;
330 Ok(ExecutionOutcome {
331 language: self.id().to_string(),
332 exit_code: run_output.status.code(),
333 stdout: String::from_utf8_lossy(&run_output.stdout).into_owned(),
334 stderr: String::from_utf8_lossy(&run_output.stderr).into_owned(),
335 duration: start.elapsed(),
336 })
337 }
338
339 fn start_session(&self) -> Result<Box<dyn LanguageSession>> {
340 let compiler = self.ensure_compiler().map(Path::to_path_buf)?;
341
342 let temp_dir = Builder::new()
343 .prefix("run-cpp-repl")
344 .tempdir()
345 .context("failed to create temporary directory for cpp repl")?;
346 let dir_path = temp_dir.path();
347 let source_path = dir_path.join("main.cpp");
348 let binary_path = Self::binary_path(dir_path);
349
350 Ok(Box::new(CppSession {
351 compiler,
352 _temp_dir: temp_dir,
353 source_path,
354 binary_path,
355 definitions: Vec::new(),
356 statements: Vec::new(),
357 previous_stdout: String::new(),
358 previous_stderr: String::new(),
359 }))
360 }
361}
362
363fn resolve_cpp_compiler() -> Option<PathBuf> {
364 ["c++", "clang++", "g++"]
365 .into_iter()
366 .find_map(|candidate| which::which(candidate).ok())
367}
368
369const SESSION_PREAMBLE: &str = concat!(
370 "#include <iostream>\n",
371 "#include <iomanip>\n",
372 "#include <string>\n",
373 "#include <vector>\n",
374 "#include <map>\n",
375 "#include <set>\n",
376 "#include <unordered_map>\n",
377 "#include <unordered_set>\n",
378 "#include <deque>\n",
379 "#include <list>\n",
380 "#include <queue>\n",
381 "#include <stack>\n",
382 "#include <memory>\n",
383 "#include <functional>\n",
384 "#include <algorithm>\n",
385 "#include <numeric>\n",
386 "#include <cmath>\n\n",
387 "using namespace std;\n\n",
388);
389
390struct CppSession {
391 compiler: PathBuf,
392 _temp_dir: TempDir,
393 source_path: PathBuf,
394 binary_path: PathBuf,
395 definitions: Vec<String>,
396 statements: Vec<String>,
397 previous_stdout: String,
398 previous_stderr: String,
399}
400
401impl CppSession {
402 fn render_prelude(&self) -> String {
403 let mut source = String::from(SESSION_PREAMBLE);
404 for def in &self.definitions {
405 source.push_str(def);
406 if !def.ends_with('\n') {
407 source.push('\n');
408 }
409 source.push('\n');
410 }
411 source
412 }
413
414 fn render_source(&self) -> String {
415 let mut source = self.render_prelude();
416 source.push_str("int main()\n{\n ios::sync_with_stdio(false);\n cin.tie(nullptr);\n cout.setf(std::ios::boolalpha);\n");
417 for stmt in &self.statements {
418 for line in stmt.lines() {
419 source.push_str(" ");
420 source.push_str(line);
421 source.push('\n');
422 }
423 if !stmt.ends_with('\n') {
424 source.push('\n');
425 }
426 }
427 source.push_str(" return 0;\n}\n");
428 source
429 }
430
431 fn write_source(&self, contents: &str) -> Result<()> {
432 fs::write(&self.source_path, contents).with_context(|| {
433 format!(
434 "failed to write generated C++ REPL source to {}",
435 self.source_path.display()
436 )
437 })
438 }
439
440 fn compile_and_run(&mut self) -> Result<(std::process::Output, Duration)> {
441 let start = Instant::now();
442 let source = self.render_source();
443 self.write_source(&source)?;
444 let compile_output =
445 invoke_cpp_compiler(&self.compiler, &self.source_path, &self.binary_path)?;
446 if !compile_output.status.success() {
447 let duration = start.elapsed();
448 return Ok((compile_output, duration));
449 }
450 let execution_output = run_cpp_binary(&self.binary_path)?;
451 let duration = start.elapsed();
452 Ok((execution_output, duration))
453 }
454
455 fn run_standalone_program(&mut self, code: &str) -> Result<ExecutionOutcome> {
456 let start = Instant::now();
457 let mut source = self.render_prelude();
458 if !source.ends_with('\n') {
459 source.push('\n');
460 }
461 source.push_str(code);
462 if !code.ends_with('\n') {
463 source.push('\n');
464 }
465
466 let standalone_path = self
467 .source_path
468 .parent()
469 .unwrap_or_else(|| Path::new("."))
470 .join("standalone.cpp");
471 fs::write(&standalone_path, &source)
472 .with_context(|| "failed to write standalone C++ source".to_string())?;
473
474 let compile_output =
475 invoke_cpp_compiler(&self.compiler, &standalone_path, &self.binary_path)?;
476 if !compile_output.status.success() {
477 return Ok(ExecutionOutcome {
478 language: "cpp".to_string(),
479 exit_code: compile_output.status.code(),
480 stdout: normalize_output(&compile_output.stdout),
481 stderr: normalize_output(&compile_output.stderr),
482 duration: start.elapsed(),
483 });
484 }
485
486 let run_output = run_cpp_binary(&self.binary_path)?;
487 Ok(ExecutionOutcome {
488 language: "cpp".to_string(),
489 exit_code: run_output.status.code(),
490 stdout: normalize_output(&run_output.stdout),
491 stderr: normalize_output(&run_output.stderr),
492 duration: start.elapsed(),
493 })
494 }
495
496 fn reset_state(&mut self) -> Result<()> {
497 self.definitions.clear();
498 self.statements.clear();
499 self.previous_stdout.clear();
500 self.previous_stderr.clear();
501 let source = self.render_source();
502 self.write_source(&source)
503 }
504
505 fn diff_outputs(
506 &mut self,
507 output: &std::process::Output,
508 duration: Duration,
509 ) -> ExecutionOutcome {
510 let stdout_full = normalize_output(&output.stdout);
511 let stderr_full = normalize_output(&output.stderr);
512
513 let stdout_delta = diff_output(&self.previous_stdout, &stdout_full);
514 let stderr_delta = diff_output(&self.previous_stderr, &stderr_full);
515
516 if output.status.success() {
517 self.previous_stdout = stdout_full;
518 self.previous_stderr = stderr_full;
519 }
520
521 ExecutionOutcome {
522 language: "cpp".to_string(),
523 exit_code: output.status.code(),
524 stdout: stdout_delta,
525 stderr: stderr_delta,
526 duration,
527 }
528 }
529
530 fn add_definition(&mut self, snippet: String) {
531 self.definitions.push(snippet);
532 }
533
534 fn add_statement(&mut self, snippet: String) {
535 self.statements.push(snippet);
536 }
537
538 fn remove_last_definition(&mut self) {
539 let _ = self.definitions.pop();
540 }
541
542 fn remove_last_statement(&mut self) {
543 let _ = self.statements.pop();
544 }
545}
546
547impl LanguageSession for CppSession {
548 fn language_id(&self) -> &str {
549 "cpp"
550 }
551
552 fn eval(&mut self, code: &str) -> Result<ExecutionOutcome> {
553 let trimmed = code.trim();
554 if trimmed.is_empty() {
555 return Ok(ExecutionOutcome {
556 language: self.language_id().to_string(),
557 exit_code: None,
558 stdout: String::new(),
559 stderr: String::new(),
560 duration: Instant::now().elapsed(),
561 });
562 }
563
564 if trimmed.eq_ignore_ascii_case(":reset") {
565 self.reset_state()?;
566 return Ok(ExecutionOutcome {
567 language: self.language_id().to_string(),
568 exit_code: None,
569 stdout: String::new(),
570 stderr: String::new(),
571 duration: Duration::default(),
572 });
573 }
574
575 if trimmed.eq_ignore_ascii_case(":help") {
576 return Ok(ExecutionOutcome {
577 language: self.language_id().to_string(),
578 exit_code: None,
579 stdout:
580 "C++ commands:\n :reset - clear session state\n :help - show this message\n"
581 .to_string(),
582 stderr: String::new(),
583 duration: Duration::default(),
584 });
585 }
586
587 if contains_main_definition(code) {
588 return self.run_standalone_program(code);
589 }
590
591 let classification = classify_snippet(trimmed);
592 match classification {
593 SnippetKind::Definition => {
594 self.add_definition(code.to_string());
595 let (output, duration) = self.compile_and_run()?;
596 if !output.status.success() {
597 self.remove_last_definition();
598 }
599 Ok(self.diff_outputs(&output, duration))
600 }
601 SnippetKind::Expression => {
602 let wrapped = wrap_cpp_expression(trimmed);
603 self.add_statement(wrapped);
604 let (output, duration) = self.compile_and_run()?;
605 if !output.status.success() {
606 self.remove_last_statement();
607 return Ok(self.diff_outputs(&output, duration));
608 }
609 Ok(self.diff_outputs(&output, duration))
610 }
611 SnippetKind::Statement => {
612 let stmt = ensure_trailing_newline(code);
613 self.add_statement(stmt);
614 let (output, duration) = self.compile_and_run()?;
615 if !output.status.success() {
616 self.remove_last_statement();
617 }
618 Ok(self.diff_outputs(&output, duration))
619 }
620 }
621 }
622
623 fn shutdown(&mut self) -> Result<()> {
624 Ok(())
625 }
626}
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
629enum SnippetKind {
630 Definition,
631 Statement,
632 Expression,
633}
634
635fn classify_snippet(code: &str) -> SnippetKind {
636 let trimmed = code.trim();
637 if trimmed.starts_with("#include")
638 || trimmed.starts_with("using ")
639 || trimmed.starts_with("namespace ")
640 || trimmed.starts_with("class ")
641 || trimmed.starts_with("struct ")
642 || trimmed.starts_with("enum ")
643 || trimmed.starts_with("template ")
644 || trimmed.ends_with("};")
645 {
646 return SnippetKind::Definition;
647 }
648
649 if trimmed.contains('{') && trimmed.contains('}') && trimmed.contains('(') {
650 const CONTROL_KEYWORDS: [&str; 8] =
651 ["if", "for", "while", "switch", "do", "else", "try", "catch"];
652 let first = trimmed.split_whitespace().next().unwrap_or("");
653 if !CONTROL_KEYWORDS.iter().any(|kw| {
654 first == *kw
655 || trimmed.starts_with(&format!("{} ", kw))
656 || trimmed.starts_with(&format!("{}(", kw))
657 }) {
658 return SnippetKind::Definition;
659 }
660 }
661
662 if is_cpp_expression(trimmed) {
663 return SnippetKind::Expression;
664 }
665
666 SnippetKind::Statement
667}
668
669fn is_cpp_expression(code: &str) -> bool {
670 if code.contains('\n') {
671 return false;
672 }
673 if code.ends_with(';') {
674 return false;
675 }
676 if code.starts_with("return ") {
677 return false;
678 }
679 if code.starts_with("if ")
680 || code.starts_with("for ")
681 || code.starts_with("while ")
682 || code.starts_with("switch ")
683 || code.starts_with("do ")
684 || code.starts_with("auto ")
685 {
686 return false;
687 }
688 if code.starts_with("std::") && code.contains('(') {
689 return false;
690 }
691 if code.starts_with("cout") || code.starts_with("cin") {
692 return false;
693 }
694 if code.starts_with('"') && code.ends_with('"') {
695 return true;
696 }
697 if code.parse::<f64>().is_ok() {
698 return true;
699 }
700 if code == "true" || code == "false" {
701 return true;
702 }
703 if code.contains("==") || code.contains("!=") || code.contains("<=") || code.contains(">=") {
704 return true;
705 }
706 if code.chars().any(|c| "+-*/%<>^|&".contains(c)) {
707 return true;
708 }
709 if code
710 .chars()
711 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
712 {
713 return true;
714 }
715 false
716}
717
718fn contains_main_definition(code: &str) -> bool {
719 let bytes = code.as_bytes();
720 let len = bytes.len();
721 let mut i = 0;
722 let mut in_line_comment = false;
723 let mut in_block_comment = false;
724 let mut in_string = false;
725 let mut string_delim = b'"';
726 let mut in_char = false;
727
728 while i < len {
729 let b = bytes[i];
730
731 if in_line_comment {
732 if b == b'\n' {
733 in_line_comment = false;
734 }
735 i += 1;
736 continue;
737 }
738
739 if in_block_comment {
740 if b == b'*' && i + 1 < len && bytes[i + 1] == b'/' {
741 in_block_comment = false;
742 i += 2;
743 continue;
744 }
745 i += 1;
746 continue;
747 }
748
749 if in_string {
750 if b == b'\\' {
751 i = (i + 2).min(len);
752 continue;
753 }
754 if b == string_delim {
755 in_string = false;
756 }
757 i += 1;
758 continue;
759 }
760
761 if in_char {
762 if b == b'\\' {
763 i = (i + 2).min(len);
764 continue;
765 }
766 if b == b'\'' {
767 in_char = false;
768 }
769 i += 1;
770 continue;
771 }
772
773 match b {
774 b'/' if i + 1 < len && bytes[i + 1] == b'/' => {
775 in_line_comment = true;
776 i += 2;
777 continue;
778 }
779 b'/' if i + 1 < len && bytes[i + 1] == b'*' => {
780 in_block_comment = true;
781 i += 2;
782 continue;
783 }
784 b'"' | b'\'' => {
785 if b == b'"' {
786 in_string = true;
787 string_delim = b;
788 } else {
789 in_char = true;
790 }
791 i += 1;
792 continue;
793 }
794 b'm' if i + 4 <= len && &bytes[i..i + 4] == b"main" => {
795 if i > 0 {
796 let prev = bytes[i - 1];
797 if prev.is_ascii_alphanumeric() || prev == b'_' {
798 i += 1;
799 continue;
800 }
801 }
802
803 let after_name = i + 4;
804 if after_name < len {
805 let next = bytes[after_name];
806 if next.is_ascii_alphanumeric() || next == b'_' {
807 i += 1;
808 continue;
809 }
810 }
811
812 let mut j = after_name;
813 while j < len && bytes[j].is_ascii_whitespace() {
814 j += 1;
815 }
816 if j >= len || bytes[j] != b'(' {
817 i += 1;
818 continue;
819 }
820
821 let mut depth = 1usize;
822 let mut k = j + 1;
823 let mut inner_line_comment = false;
824 let mut inner_block_comment = false;
825 let mut inner_string = false;
826 let mut inner_char = false;
827
828 while k < len {
829 let ch = bytes[k];
830
831 if inner_line_comment {
832 if ch == b'\n' {
833 inner_line_comment = false;
834 }
835 k += 1;
836 continue;
837 }
838
839 if inner_block_comment {
840 if ch == b'*' && k + 1 < len && bytes[k + 1] == b'/' {
841 inner_block_comment = false;
842 k += 2;
843 continue;
844 }
845 k += 1;
846 continue;
847 }
848
849 if inner_string {
850 if ch == b'\\' {
851 k = (k + 2).min(len);
852 continue;
853 }
854 if ch == b'"' {
855 inner_string = false;
856 }
857 k += 1;
858 continue;
859 }
860
861 if inner_char {
862 if ch == b'\\' {
863 k = (k + 2).min(len);
864 continue;
865 }
866 if ch == b'\'' {
867 inner_char = false;
868 }
869 k += 1;
870 continue;
871 }
872
873 match ch {
874 b'/' if k + 1 < len && bytes[k + 1] == b'/' => {
875 inner_line_comment = true;
876 k += 2;
877 continue;
878 }
879 b'/' if k + 1 < len && bytes[k + 1] == b'*' => {
880 inner_block_comment = true;
881 k += 2;
882 continue;
883 }
884 b'"' => {
885 inner_string = true;
886 k += 1;
887 continue;
888 }
889 b'\'' => {
890 inner_char = true;
891 k += 1;
892 continue;
893 }
894 b'(' => {
895 depth += 1;
896 }
897 b')' => {
898 depth -= 1;
899 k += 1;
900 if depth == 0 {
901 break;
902 } else {
903 continue;
904 }
905 }
906 _ => {}
907 }
908
909 k += 1;
910 }
911
912 if depth != 0 {
913 i += 1;
914 continue;
915 }
916
917 let mut after = k;
918 loop {
919 while after < len && bytes[after].is_ascii_whitespace() {
920 after += 1;
921 }
922 if after + 1 < len && bytes[after] == b'/' && bytes[after + 1] == b'/' {
923 after += 2;
924 while after < len && bytes[after] != b'\n' {
925 after += 1;
926 }
927 continue;
928 }
929 if after + 1 < len && bytes[after] == b'/' && bytes[after + 1] == b'*' {
930 after += 2;
931 while after + 1 < len {
932 if bytes[after] == b'*' && bytes[after + 1] == b'/' {
933 after += 2;
934 break;
935 }
936 after += 1;
937 }
938 continue;
939 }
940 break;
941 }
942
943 while after < len {
944 match bytes[after] {
945 b'{' => return true,
946 b';' => break,
947 b'/' if after + 1 < len && bytes[after + 1] == b'/' => {
948 after += 2;
949 while after < len && bytes[after] != b'\n' {
950 after += 1;
951 }
952 }
953 b'/' if after + 1 < len && bytes[after + 1] == b'*' => {
954 after += 2;
955 while after + 1 < len {
956 if bytes[after] == b'*' && bytes[after + 1] == b'/' {
957 after += 2;
958 break;
959 }
960 after += 1;
961 }
962 }
963 b'"' => {
964 after += 1;
965 while after < len {
966 if bytes[after] == b'"' {
967 after += 1;
968 break;
969 }
970 if bytes[after] == b'\\' {
971 after = (after + 2).min(len);
972 } else {
973 after += 1;
974 }
975 }
976 }
977 b'\'' => {
978 after += 1;
979 while after < len {
980 if bytes[after] == b'\'' {
981 after += 1;
982 break;
983 }
984 if bytes[after] == b'\\' {
985 after = (after + 2).min(len);
986 } else {
987 after += 1;
988 }
989 }
990 }
991 b'-' if after + 1 < len && bytes[after + 1] == b'>' => {
992 after += 2;
993 }
994 b'(' => {
995 let mut depth = 1usize;
996 after += 1;
997 while after < len && depth > 0 {
998 match bytes[after] {
999 b'(' => depth += 1,
1000 b')' => depth -= 1,
1001 b'"' => {
1002 after += 1;
1003 while after < len {
1004 if bytes[after] == b'"' {
1005 after += 1;
1006 break;
1007 }
1008 if bytes[after] == b'\\' {
1009 after = (after + 2).min(len);
1010 } else {
1011 after += 1;
1012 }
1013 }
1014 continue;
1015 }
1016 b'\'' => {
1017 after += 1;
1018 while after < len {
1019 if bytes[after] == b'\'' {
1020 after += 1;
1021 break;
1022 }
1023 if bytes[after] == b'\\' {
1024 after = (after + 2).min(len);
1025 } else {
1026 after += 1;
1027 }
1028 }
1029 continue;
1030 }
1031 _ => {}
1032 }
1033 after += 1;
1034 }
1035 }
1036 _ => {
1037 after += 1;
1038 }
1039 }
1040 }
1041 }
1042 _ => {}
1043 }
1044
1045 i += 1;
1046 }
1047
1048 false
1049}
1050
1051fn wrap_cpp_expression(code: &str) -> String {
1052 format!("std::cout << ({code}) << std::endl;\n")
1053}
1054
1055fn ensure_trailing_newline(code: &str) -> String {
1056 let mut owned = code.to_string();
1057 if !owned.ends_with('\n') {
1058 owned.push('\n');
1059 }
1060 owned
1061}
1062
1063fn diff_output(previous: &str, current: &str) -> String {
1064 if let Some(stripped) = current.strip_prefix(previous) {
1065 stripped.to_string()
1066 } else {
1067 current.to_string()
1068 }
1069}
1070
1071fn normalize_output(bytes: &[u8]) -> String {
1072 String::from_utf8_lossy(bytes)
1073 .replace("\r\n", "\n")
1074 .replace('\r', "")
1075}
1076
1077fn invoke_cpp_compiler(
1078 compiler: &Path,
1079 source: &Path,
1080 output: &Path,
1081) -> Result<std::process::Output> {
1082 let mut cmd = compiler_command(compiler);
1083 cmd.arg(source)
1084 .arg("-std=c++17")
1085 .arg("-O0")
1086 .arg("-w")
1087 .arg("-o")
1088 .arg(output)
1089 .stdout(Stdio::piped())
1090 .stderr(Stdio::piped());
1091 if let Some(pch_header) = ensure_global_cpp_pch(compiler) {
1092 cmd.arg("-include").arg(pch_header);
1093 }
1094 cmd.output().with_context(|| {
1095 format!(
1096 "failed to invoke {} to compile {}",
1097 compiler.display(),
1098 source.display()
1099 )
1100 })
1101}
1102
1103fn run_cpp_binary(binary: &Path) -> Result<std::process::Output> {
1104 let mut cmd = Command::new(binary);
1105 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
1106 cmd.output()
1107 .with_context(|| format!("failed to execute compiled binary {}", binary.display()))
1108}
1109
1110fn ensure_global_cpp_pch(compiler: &Path) -> Option<PathBuf> {
1111 let cache_dir = std::env::temp_dir().join("run-compile-cache");
1112 std::fs::create_dir_all(&cache_dir).ok()?;
1113 let header = cache_dir.join("run_cpp_pch.hpp");
1114 let gch = cache_dir.join("run_cpp_pch.hpp.gch");
1115 if !header.exists() {
1116 let contents = concat!(
1117 "#include <iostream>\n",
1118 "#include <vector>\n",
1119 "#include <string>\n",
1120 "#include <map>\n",
1121 "#include <unordered_map>\n",
1122 "#include <algorithm>\n",
1123 "#include <utility>\n"
1124 );
1125 std::fs::write(&header, contents).ok()?;
1126 }
1127 let needs_build = if !gch.exists() {
1128 true
1129 } else {
1130 let h = header.metadata().ok()?.modified().ok()?;
1131 let g = gch.metadata().ok()?.modified().ok()?;
1132 h > g
1133 };
1134 if needs_build {
1135 let mut pch_cmd = compiler_command(compiler);
1136 let out = pch_cmd
1137 .arg("-std=c++17")
1138 .arg("-x")
1139 .arg("c++-header")
1140 .arg(&header)
1141 .arg("-o")
1142 .arg(&gch)
1143 .stdout(Stdio::piped())
1144 .stderr(Stdio::piped())
1145 .output()
1146 .ok()?;
1147 if !out.status.success() {
1148 return None;
1149 }
1150 }
1151 Some(header)
1152}
1153
1154fn cpp_needs_recompile(source: &Path, object: &Path, depfile: &Path) -> bool {
1155 if !object.exists() {
1156 return true;
1157 }
1158 let obj_time = match object.metadata().and_then(|m| m.modified()) {
1159 Ok(t) => t,
1160 Err(_) => return true,
1161 };
1162 let src_time = match source.metadata().and_then(|m| m.modified()) {
1163 Ok(t) => t,
1164 Err(_) => return true,
1165 };
1166 if src_time > obj_time {
1167 return true;
1168 }
1169 if !depfile.exists() {
1170 return true;
1171 }
1172 let dep_text = match fs::read_to_string(depfile) {
1173 Ok(t) => t.replace("\\\n", " "),
1174 Err(_) => return true,
1175 };
1176 for token in dep_text.split_whitespace().skip(1) {
1177 let path = token.trim_end_matches(':');
1178 if path.is_empty() {
1179 continue;
1180 }
1181 let p = Path::new(path);
1182 if let Ok(t) = p.metadata().and_then(|m| m.modified())
1183 && t > obj_time
1184 {
1185 return true;
1186 }
1187 }
1188 false
1189}