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