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