/src/compiler.rs

https://github.com/hatoo/Accepted · Rust · 264 lines · 226 code · 35 blank · 3 comment · 20 complexity · c0e04451d1ff6791b31e4d6f49bd35f9 MD5 · raw file

  1. use std::io;
  2. use std::io::BufRead;
  3. use std::path::PathBuf;
  4. use std::process;
  5. use regex;
  6. use crate::config::types::CompilerConfig;
  7. use crate::config::types::CompilerType;
  8. use crate::core::Cursor;
  9. use crate::core::Id;
  10. use crate::job_queue::JobQueue;
  11. use crate::rustc;
  12. use std::ffi::OsString;
  13. use std::ops::RangeInclusive;
  14. use async_trait::async_trait;
  15. use futures::prelude::*;
  16. pub struct CompilerOutput {
  17. pub message: String,
  18. pub line: usize,
  19. pub level: String,
  20. pub span: RangeInclusive<Cursor>,
  21. }
  22. pub struct Compiler<'a> {
  23. config: &'a CompilerConfig,
  24. worker: Box<dyn CompilerWorker>,
  25. }
  26. impl<'a> Compiler<'a> {
  27. pub fn new(config: &'a CompilerConfig) -> Self {
  28. let worker: Box<dyn CompilerWorker> = match config.output_type {
  29. None => Box::new(Unknown::default()),
  30. Some(CompilerType::Gcc) => Box::new(Cpp::default()),
  31. Some(CompilerType::Rustc) => Box::new(Rust::default()),
  32. };
  33. Self { config, worker }
  34. }
  35. pub fn compile(&self, path: PathBuf, compile_id: CompileId) {
  36. crate::env::set_env(&path);
  37. if let Some((head, tail)) = self.config.command.split_first() {
  38. if let Ok(head) = shellexpand::full(head) {
  39. let mut command = process::Command::new(OsString::from(head.as_ref()));
  40. let mut args = tail.to_vec();
  41. if compile_id.is_optimize {
  42. args.extend_from_slice(self.config.optimize_option.as_slice());
  43. }
  44. if let Ok(args) = args
  45. .iter()
  46. .map(shellexpand::full)
  47. .collect::<Result<Vec<_>, _>>()
  48. {
  49. command.args(args.into_iter().map(|s| OsString::from(s.as_ref())));
  50. self.worker.compile(command, compile_id);
  51. }
  52. }
  53. }
  54. }
  55. pub fn try_recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  56. self.worker.try_recv_compile_result()
  57. }
  58. // Block
  59. pub async fn recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  60. self.worker.recv_compile_result().await
  61. }
  62. pub fn is_compiling(&self) -> bool {
  63. self.worker.is_compiling()
  64. }
  65. }
  66. #[derive(PartialEq, Eq, Debug, Clone, Copy, Default)]
  67. pub struct CompileId {
  68. pub id: Id,
  69. pub is_optimize: bool,
  70. }
  71. #[derive(Default)]
  72. pub struct CompileResult {
  73. pub success: bool,
  74. pub messages: Vec<CompilerOutput>,
  75. }
  76. #[async_trait]
  77. trait CompilerWorker: Sync + Send + 'static {
  78. // Must be async
  79. fn compile(&self, _command: process::Command, _compile_id: CompileId) {}
  80. // Do not Block
  81. fn try_recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  82. None
  83. }
  84. async fn recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  85. None
  86. }
  87. fn is_compiling(&self) -> bool {
  88. false
  89. }
  90. }
  91. pub struct Cpp {
  92. job_queue: JobQueue<(process::Command, CompileId), (CompileId, CompileResult)>,
  93. }
  94. pub struct Rust {
  95. job_queue: JobQueue<(process::Command, CompileId), (CompileId, CompileResult)>,
  96. }
  97. pub struct Unknown {
  98. job_queue: JobQueue<(process::Command, CompileId), (CompileId, CompileResult)>,
  99. }
  100. impl Default for Unknown {
  101. fn default() -> Self {
  102. let job_queue = JobQueue::new(|(cmd, req): (process::Command, CompileId)| {
  103. async move {
  104. let mut cmd = tokio::process::Command::from(cmd);
  105. let messages = Vec::new();
  106. let mut success = false;
  107. if let Ok(cmd) = cmd.stderr(process::Stdio::piped()).output().await {
  108. success = cmd.status.success();
  109. }
  110. (req, CompileResult { messages, success })
  111. }
  112. .boxed()
  113. });
  114. Self { job_queue }
  115. }
  116. }
  117. impl Default for Rust {
  118. fn default() -> Self {
  119. let job_queue = JobQueue::new(|(rustc, req): (process::Command, CompileId)| {
  120. async move {
  121. let mut rustc = tokio::process::Command::from(rustc);
  122. let mut messages = Vec::new();
  123. let mut success = false;
  124. if let Ok(rustc) = rustc.stderr(process::Stdio::piped()).output().await {
  125. success = rustc.status.success();
  126. let buf = rustc.stderr;
  127. let mut reader = io::Cursor::new(buf);
  128. let mut line = String::new();
  129. while {
  130. line.clear();
  131. reader.read_line(&mut line).is_ok() && !line.is_empty()
  132. } {
  133. if let Some(rustc_output) = rustc::parse_rustc_json(&line) {
  134. messages.push(rustc_output);
  135. }
  136. }
  137. }
  138. (req, CompileResult { messages, success })
  139. }
  140. .boxed()
  141. });
  142. Self { job_queue }
  143. }
  144. }
  145. impl Default for Cpp {
  146. fn default() -> Self {
  147. let job_queue = JobQueue::new(|(clang, req): (process::Command, CompileId)| {
  148. async move {
  149. let mut clang = tokio::process::Command::from(clang);
  150. let mut messages = Vec::new();
  151. let mut success = false;
  152. if let Ok(clang) = clang.stderr(process::Stdio::piped()).output().await {
  153. success = clang.status.success();
  154. let buf = clang.stderr;
  155. let mut reader = io::Cursor::new(buf);
  156. let mut line = String::new();
  157. let re = regex::Regex::new(
  158. r"^[^:]*:(?P<line>\d*):(?P<col>\d*): (?P<level>[^:]*): (?P<msg>.*)",
  159. )
  160. .unwrap();
  161. while {
  162. line.clear();
  163. reader.read_line(&mut line).is_ok() && !line.is_empty()
  164. } {
  165. if let Some(caps) = re.captures(&line) {
  166. let line = caps["line"].parse::<usize>().unwrap() - 1;
  167. let col = caps["col"].parse::<usize>().unwrap() - 1;
  168. let out = CompilerOutput {
  169. message: caps["msg"].into(),
  170. line,
  171. level: caps["level"].into(),
  172. span: Cursor { row: line, col }..=Cursor { row: line, col },
  173. };
  174. messages.push(out);
  175. }
  176. }
  177. }
  178. (req, CompileResult { success, messages })
  179. }
  180. .boxed()
  181. });
  182. Self { job_queue }
  183. }
  184. }
  185. #[async_trait]
  186. impl CompilerWorker for Unknown {
  187. fn compile(&self, command: process::Command, compile_id: CompileId) {
  188. self.job_queue.send((command, compile_id)).unwrap();
  189. }
  190. fn try_recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  191. self.job_queue.rx().try_recv().ok()
  192. }
  193. async fn recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  194. self.job_queue.rx().recv().await
  195. }
  196. fn is_compiling(&self) -> bool {
  197. self.job_queue.is_running()
  198. }
  199. }
  200. #[async_trait]
  201. impl CompilerWorker for Cpp {
  202. fn compile(&self, command: process::Command, compile_id: CompileId) {
  203. self.job_queue.send((command, compile_id)).unwrap();
  204. }
  205. fn try_recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  206. self.job_queue.rx().try_recv().ok()
  207. }
  208. async fn recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  209. self.job_queue.rx().recv().await
  210. }
  211. fn is_compiling(&self) -> bool {
  212. self.job_queue.is_running()
  213. }
  214. }
  215. #[async_trait]
  216. impl CompilerWorker for Rust {
  217. fn compile(&self, command: process::Command, compile_id: CompileId) {
  218. self.job_queue.send((command, compile_id)).unwrap();
  219. }
  220. fn try_recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  221. self.job_queue.rx().try_recv().ok()
  222. }
  223. async fn recv_compile_result(&mut self) -> Option<(CompileId, CompileResult)> {
  224. self.job_queue.rx().recv().await
  225. }
  226. fn is_compiling(&self) -> bool {
  227. self.job_queue.is_running()
  228. }
  229. }