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