molecule_codegen/
compiler.rs

1use std::{env, ffi, fs, io, io::Write as _, path};
2
3#[cfg(feature = "compiler-plugin")]
4use std::process;
5
6use crate::{generator, parser};
7
8#[cfg(feature = "compiler-plugin")]
9use crate::ir;
10
11pub struct Compiler {
12    target: Option<generator::Target>,
13    input: Option<Input>,
14    output: Option<Output>,
15}
16
17pub(crate) enum Input {
18    SchemaFile(path::PathBuf),
19    #[cfg(feature = "compiler-plugin")]
20    Intermediate(ir::Format, Vec<u8>),
21}
22
23pub(crate) enum Output {
24    Directory(path::PathBuf),
25    Stdout,
26    #[cfg(feature = "compiler-plugin")]
27    PluginProcess(process::Child),
28}
29
30impl Default for Compiler {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl Compiler {
37    pub fn new() -> Self {
38        Self {
39            target: None,
40            input: None,
41            output: Some(Output::Stdout),
42        }
43    }
44
45    pub fn generate_code(&mut self, lang: generator::Language) -> &mut Self {
46        self.target.replace(generator::Target::Language(lang));
47        self
48    }
49
50    #[cfg(feature = "compiler-plugin")]
51    pub fn generate_intermediate(&mut self, format: ir::Format) -> &mut Self {
52        self.target.replace(generator::Target::Intermediate(format));
53        self
54    }
55
56    pub fn input_schema_file<P: AsRef<path::Path>>(&mut self, path: P) -> &mut Self {
57        self.input
58            .replace(Input::SchemaFile(path.as_ref().to_path_buf()));
59        self
60    }
61
62    #[cfg(feature = "compiler-plugin")]
63    pub fn input_intermediate(&mut self, format: ir::Format, data: Vec<u8>) -> &mut Self {
64        self.input.replace(Input::Intermediate(format, data));
65        self
66    }
67
68    pub fn output_dir_set_default(&mut self) -> &mut Self {
69        let out_dir = path::PathBuf::from(&env::var("OUT_DIR").unwrap_or_else(|_| ".".to_string()));
70        self.output_dir(out_dir)
71    }
72
73    pub fn output_dir<P: AsRef<path::Path>>(&mut self, path: P) -> &mut Self {
74        self.output
75            .replace(Output::Directory(path.as_ref().to_path_buf()));
76        self
77    }
78
79    #[cfg(feature = "compiler-plugin")]
80    pub fn output_plugin_process(&mut self, child: process::Child) -> &mut Self {
81        self.output.replace(Output::PluginProcess(child));
82        self
83    }
84
85    pub fn run(&mut self) -> Result<(), String> {
86        let Self {
87            target,
88            ref input,
89            #[cfg(not(feature = "compiler-plugin"))]
90            ref output,
91            #[cfg(feature = "compiler-plugin")]
92            ref mut output,
93        } = self;
94        let target = target.ok_or("target is not set: generate code or intermediate data")?;
95        let input = input
96            .as_ref()
97            .ok_or("input is not set: schema file or intermediate data")?;
98
99        #[cfg(not(feature = "compiler-plugin"))]
100        let output = output.as_ref().ok_or("output is not set")?;
101        #[cfg(feature = "compiler-plugin")]
102        let output = output.as_mut().ok_or("output is not set")?;
103
104        #[cfg(not(feature = "compiler-plugin"))]
105        let mut file_name = Default::default();
106        #[cfg(feature = "compiler-plugin")]
107        let mut file_name = None;
108
109        let ast = match input {
110            Input::SchemaFile(ref file_path) => {
111                file_path
112                    .as_path()
113                    .file_name()
114                    .and_then(ffi::OsStr::to_str)
115                    .clone_into(&mut file_name);
116                parser::Parser::parse(file_path)
117            }
118            #[cfg(feature = "compiler-plugin")]
119            Input::Intermediate(format, ref data) => format.recover(data)?,
120        };
121        let generator = generator::Generator::new(ast);
122
123        let mut output_data = Vec::<u8>::new();
124        generator
125            .generate(target, &mut output_data)
126            .map_err(|err| format!("failed to write data by generator: {}", err))?;
127
128        match output {
129            Output::Directory(ref out_dir) => {
130                let file_name = file_name.unwrap();
131                let mut out_file = out_dir.to_owned();
132                out_file.push(file_name);
133                out_file.set_extension(target.extension());
134                let mut file_out = fs::OpenOptions::new()
135                    .create(true)
136                    .write(true)
137                    .truncate(true)
138                    .open(&out_file)
139                    .unwrap();
140                file_out.write_all(&output_data).unwrap();
141                file_out.flush().unwrap();
142            }
143            Output::Stdout => {
144                let stdout = io::stdout();
145                let mut stdout_handle = stdout.lock();
146                stdout_handle.write_all(&output_data).unwrap();
147                stdout_handle.flush().unwrap();
148            }
149            #[cfg(feature = "compiler-plugin")]
150            Output::PluginProcess(ref mut process) => {
151                {
152                    let child_stdin = process.stdin.as_mut().unwrap();
153                    child_stdin.write_all(&output_data).unwrap();
154                    child_stdin.flush().unwrap();
155                }
156                if let Ok(status) = process.wait() {
157                    if !status.success() {
158                        process::exit(1)
159                    }
160                } else {
161                    eprintln!("Error: failed to execute the plugin");
162                    process::exit(1)
163                }
164            }
165        }
166
167        Ok(())
168    }
169}