device_driver_core/
lib.rs1#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
2
3use std::fmt::Write;
4
5use clap::Parser;
6use device_driver_diagnostics::{Diagnostics, DynError, ResultExt};
7use itertools::Itertools;
8
9pub use device_driver_codegen::{RustCodegenOptions, Target as CodegenTarget};
10pub use device_driver_diagnostics::Metadata;
11pub use device_driver_mir::MirOptions;
12
13use crate::timings::Timings;
14
15mod timings;
16
17#[derive(Parser, Debug, Clone)]
18#[command(no_binary_name = true, bin_name = "")]
19pub struct CompileOptions {
20 #[command(flatten)]
21 pub general_options: GeneralOptions,
22 #[command(flatten)]
23 pub mir_options: MirOptions,
24 #[command(subcommand)]
25 pub target: CodegenTarget,
26}
27
28#[derive(Parser, Debug, Clone, Default)]
29#[command(no_binary_name = true)]
30pub struct GeneralOptions {
31 #[arg(long = "unstable-ui-test-mode", global = true)]
33 pub ui_test_mode: bool,
34 #[arg(long, global = true, require_equals = true, default_value = "off")]
37 pub timings: TimingsMode,
38}
39
40#[derive(clap::ValueEnum, Debug, Clone, Copy, Default)]
41pub enum TimingsMode {
42 #[default]
43 Off,
44 Show,
45 Verbose,
46}
47
48pub fn compile(source: &str, options: CompileOptions) -> Result<(String, Diagnostics), DynError> {
49 let mut timings = Timings::new(options.general_options.timings);
50 let mut diagnostics = Diagnostics::new();
51
52 let tokens = {
53 let _t = timings.start_lexer();
54 device_driver_lexer::lex(source)
55 };
56 let ast = {
57 let _t = timings.start_parser();
58 device_driver_parser::parse(&tokens, &mut diagnostics)
59 };
60 let (mir, mir_timings) = {
61 let _t = timings.start_mir();
62 device_driver_mir::lower_ast(ast, &options.mir_options, &mut diagnostics)
63 .with_message(|| "could not lower AST to MIR")?
64 };
65 timings.set_mir_timings(mir_timings);
66 let lir = {
67 let _t = timings.start_lir();
68 device_driver_lir::lower_mir(mir).with_message(|| "could not lower MIR to LIR")?
69 };
70 let mut code = {
71 let _t = timings.start_codegen();
72 device_driver_codegen::codegen(&options.target, &lir, source)
73 };
74
75 if !matches!(options.general_options.timings, TimingsMode::Off) {
76 diagnostics.add(timings);
77 }
78
79 if diagnostics.has_error() {
80 let _ = write!(code, "\n{}\n", options.target.create_error_message());
81 }
82
83 let formatted_code = match format_code(&code) {
85 Ok(formatted_code) => formatted_code,
86 Err(e) => format!(
87 "{}\n\n{code}",
88 e.to_string().lines().map(|e| format!("// {e}")).join("\n")
89 ),
90 };
91
92 let preamble = options.target.to_comments(&format!(
93 "This code was generated using device-driver `{}` ({}),
94a tool distributed under {} by {}
95This version was built for {} using {}
96
97For more information about device-driver, visit the website: {}",
98 if options.general_options.ui_test_mode {
99 "xx.xx.xx"
100 } else {
101 env!("CARGO_PKG_VERSION")
102 },
103 if options.general_options.ui_test_mode {
104 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
105 } else {
106 env!("BUILDRS_GIT_SHA")
107 },
108 env!("CARGO_PKG_LICENSE"),
109 env!("CARGO_PKG_AUTHORS"),
110 if options.general_options.ui_test_mode {
111 "xxxx-xxxx-xxxx"
112 } else {
113 env!("BUILDRS_TARGET")
114 },
115 if options.general_options.ui_test_mode {
116 "rustc 1.xx.x (xxxxxxxxx xxxx-xx-xx)"
117 } else {
118 env!("BUILDRS_RUSTC")
119 },
120 env!("CARGO_PKG_HOMEPAGE"),
121 ));
122
123 let formatted_code = preamble + "\n\n" + &formatted_code;
124
125 Ok((formatted_code, diagnostics))
126}
127
128#[cfg(feature = "gen-docs")]
129pub fn gen_docs(output_path: &std::path::Path) -> Result<(), DynError> {
130 std::fs::create_dir_all(output_path).with_message(|| {
131 format!(
132 "creating folder for gen-docs output at {}",
133 output_path.display()
134 )
135 })?;
136
137 let parser_folder = output_path.join("parser");
138 if !parser_folder.exists() {
139 std::fs::create_dir(&parser_folder).with_message(|| {
140 format!(
141 "creating folder for gen-docs parser output at {}",
142 parser_folder.display()
143 )
144 })?;
145 }
146 device_driver_parser::gen_docs::gen_docs(&parser_folder)
147 .with_message(|| "gen-docs for parser")?;
148
149 let mir_shapes_folder = output_path.join("mir-shapes");
150 if !mir_shapes_folder.exists() {
151 std::fs::create_dir(&mir_shapes_folder).with_message(|| {
152 format!(
153 "creating folder for gen-docs mir-shapes output at {}",
154 mir_shapes_folder.display()
155 )
156 })?;
157 }
158 device_driver_mir::gen_docs(&mir_shapes_folder).with_message(|| "gen-docs for mir shapes")?;
159
160 Ok(())
161}
162
163#[cfg(not(feature = "prettyplease"))]
164fn format_code(input: &str) -> Result<String, DynError> {
165 use std::io::{Read, Write};
166 use std::process::Stdio;
167
168 use device_driver_diagnostics::ResultExt;
169
170 let mut cmd = std::process::Command::new("rustfmt");
171
172 cmd.args(["--edition", "2024"])
173 .args(["--config", "newline_style=native"])
174 .args(["--color", "never"])
175 .stdin(Stdio::piped())
176 .stdout(Stdio::piped())
177 .stderr(Stdio::piped());
178
179 let mut child = cmd
180 .spawn()
181 .with_message(|| "error while spawning rustfmt")?;
182 let mut child_stdin = child.stdin.take().unwrap();
183 let mut child_stdout = child.stdout.take().unwrap();
184
185 let output = std::thread::scope(|s| {
189 s.spawn(|| {
190 child_stdin
191 .write_all(input.as_bytes())
192 .with_message(|| "couldn't write input to rustfmt")?;
193 child_stdin
194 .flush()
195 .with_message(|| "couldn't flush input to rustfmt")?;
196 drop(child_stdin);
197 Result::<(), DynError>::Ok(())
198 });
199 let handle: std::thread::ScopedJoinHandle<'_, Result<Vec<u8>, DynError>> = s.spawn(|| {
200 let mut output = Vec::new();
201 child_stdout.read_to_end(&mut output).into_dyn_result()?;
202 Ok(output)
203 });
204
205 handle.join()
206 });
207
208 let status = child.wait().into_dyn_result()?;
209 if !status.success() {
210 return Err(DynError::new(format!(
211 "rustfmt exited unsuccessfully ({status}):\n{}",
212 child
213 .stderr
214 .map(|mut stderr| {
215 let mut err = String::new();
216 stderr.read_to_string(&mut err).unwrap();
217 err
218 })
219 .unwrap_or_default()
220 )));
221 }
222
223 let output = match output {
224 Ok(output) => output,
225 Err(e) => std::panic::resume_unwind(e),
226 };
227
228 String::from_utf8(output?).into_dyn_result()
229}
230
231#[cfg(feature = "prettyplease")]
232fn format_code(input: &str) -> Result<String, syn::Error> {
233 Ok(prettyplease::unparse(&syn::parse_file(input)?))
234}