1use crate::error::JudgeCoreError;
2use crate::utils::get_pathbuf_str;
3use anyhow::anyhow;
4use serde_derive::Serialize;
5use std::fmt;
6use std::path::PathBuf;
7use std::{process::Command, str::FromStr};
8
9const TEMPLATE_ARG_SRC_PATH: &str = "{src_path}";
10const TEMPLATE_ARG_TARGET_PATH: &str = "{target_path}";
11
12const RUST_COMPILE_COMMAND_TEMPLATE: &str = "rustc {src_path} -o {target_path}";
13const CPP_COMPILE_COMMAND_TEMPLATE: &str = "g++ {src_path} -o {target_path}";
14const PYTHON_COMPILE_COMMAND_TEMPLATE: &str = "cp {src_path} {target_path}";
15
16#[derive(Clone)]
17struct CommandBuilder {
18 command_template: String,
19 template_args: Vec<String>,
20}
21
22impl CommandBuilder {
23 pub fn new(command_template: String, template_args: Vec<String>) -> Self {
24 Self {
25 command_template,
26 template_args,
27 }
28 }
29
30 pub fn get_command(&self, args: Vec<String>) -> String {
32 let mut command = self.command_template.to_string();
33 for (i, arg) in self.template_args.iter().enumerate() {
34 command = command.replace(arg, &args[i]);
35 }
36 command
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Copy, Serialize)]
41pub enum Language {
42 Rust,
43 Cpp,
44 Python,
45 }
47
48impl fmt::Display for Language {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::Rust => write!(f, "rust"),
52 Self::Cpp => write!(f, "cpp"),
53 Self::Python => write!(f, "python"),
54 }
56 }
57}
58
59impl FromStr for Language {
60 type Err = anyhow::Error;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 match s {
64 "rust" => Ok(Self::Rust),
65 "cpp" => Ok(Self::Cpp),
66 "python" => Ok(Self::Python),
67 _ => Err(anyhow::anyhow!("Compiler not found: {}", s)),
68 }
69 }
70}
71
72#[derive(Clone)]
73pub struct Compiler {
74 language: Language,
75 command_builder: CommandBuilder,
76 compiler_args: Vec<String>,
77}
78
79impl Compiler {
80 pub fn new(language: Language, compiler_args: Vec<String>) -> Self {
81 let command_template = match language {
82 Language::Rust => RUST_COMPILE_COMMAND_TEMPLATE,
83 Language::Cpp => CPP_COMPILE_COMMAND_TEMPLATE,
84 Language::Python => PYTHON_COMPILE_COMMAND_TEMPLATE,
85 }
87 .to_owned();
88 let template_args = match language {
89 Language::Rust | Language::Cpp | Language::Python => vec![
90 TEMPLATE_ARG_SRC_PATH.to_owned(),
91 TEMPLATE_ARG_TARGET_PATH.to_owned(),
92 ],
93 };
95 let command_builder = CommandBuilder::new(command_template, template_args);
96 Self {
97 language,
98 command_builder,
99 compiler_args,
100 }
101 }
102
103 pub fn compile(
104 &self,
105 src_path: &PathBuf,
106 target_path: &PathBuf,
107 ) -> Result<String, JudgeCoreError> {
108 if !PathBuf::from(src_path).exists() {
109 return Err(JudgeCoreError::AnyhowError(anyhow!(
110 "Source file not found: {:?}",
111 src_path
112 )));
113 }
114
115 let src_path_string = get_pathbuf_str(src_path)?;
116 let target_path_string = get_pathbuf_str(target_path)?;
117
118 log::info!(
119 "Compiling language={} src={} target={}",
120 self.language,
121 src_path_string,
122 target_path_string
123 );
124
125 if PathBuf::from(target_path).exists() {
126 std::fs::remove_file(target_path)?;
127 }
128
129 let output = Command::new("sh")
130 .arg("-c")
131 .arg(
132 &self
133 .command_builder
134 .get_command(vec![src_path_string, target_path_string]),
135 )
136 .args(self.compiler_args.iter())
137 .output()?;
138 if output.status.success() {
139 let compile_output = String::from_utf8_lossy(&output.stdout).to_string();
140 log::info!("Compile output: {}", compile_output);
141 Ok(compile_output)
142 } else {
143 let error_output = String::from_utf8_lossy(&output.stderr).to_string();
144 log::error!("Compile error: {}", error_output);
145 Err(JudgeCoreError::AnyhowError(anyhow!(error_output)))
146 }
147 }
148}
149
150#[cfg(test)]
151pub mod compiler {
152 use std::path::PathBuf;
153
154 use super::{Compiler, Language};
155
156 fn init() {
157 let _ = env_logger::builder().is_test(true).try_init();
158 }
159
160 #[test]
161 fn test_compile_cpp() {
162 init();
163 let compiler = Compiler::new(Language::Cpp, vec!["-std=c++17".to_string()]);
164 match compiler.compile(
165 &PathBuf::from("../test-collection/src/programs/infinite_loop.cpp"),
166 &PathBuf::from("../tmp/infinite_loop_test"),
167 ) {
168 Ok(out) => {
169 log::info!("{}", out);
170 }
171 Err(e) => panic!("{:?}", e),
172 }
173 }
174
175 #[test]
176 fn test_compile_py() {
177 init();
178 let compiler = Compiler::new(Language::Python, vec![]);
179 match compiler.compile(
180 &PathBuf::from("../test-collection/src/programs/read_and_write.py"),
181 &PathBuf::from("../tmp/read_and_write"),
182 ) {
183 Ok(out) => {
184 log::info!("{}", out);
185 }
186 Err(e) => panic!("{:?}", e),
187 }
188 }
189}