judge_core/
compiler.rs

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    // TODO: check if args match template_args
31    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    // add other supported languages here
46}
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            // add other supported languages here
55        }
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            // TODO: add other supported language
86        }
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            // TODO: add other supported language
94        };
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}