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