Skip to main content

device_driver_generation/
lib.rs

1#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
2
3use itertools::Itertools;
4
5#[cfg(feature = "dsl")]
6mod dsl_hir;
7mod lir;
8#[cfg(feature = "manifest")]
9mod manifest;
10pub mod mir;
11
12/// Transform the tokens of the DSL lang to the generated device driver (or a compile error).
13///
14/// The `driver_name` arg is used to name the root block of the driver.
15/// It should be given in `PascalCase` form.
16#[cfg(feature = "dsl")]
17pub fn transform_dsl(input: proc_macro2::TokenStream, driver_name: &str) -> String {
18    let mir = match _private_transform_dsl_mir(input) {
19        Ok(mir) => mir,
20        Err(e) => return e.into_compile_error().to_string(),
21    };
22
23    transform_mir(mir, driver_name)
24}
25
26#[doc(hidden)]
27#[cfg(feature = "dsl")]
28pub fn _private_transform_dsl_mir(
29    input: proc_macro2::TokenStream,
30) -> Result<mir::Device, syn::Error> {
31    // Construct the HIR
32    let hir = syn::parse2::<dsl_hir::Device>(input)?;
33
34    // Transform into MIR
35    let mir = dsl_hir::mir_transform::transform(hir)?;
36
37    Ok(mir)
38}
39
40/// Transform the json string to the generated device driver (or a compile error).
41///
42/// The `driver_name` arg is used to name the root block of the driver.
43/// It should be given in `PascalCase` form.
44#[cfg(feature = "json")]
45pub fn transform_json(source: &str, driver_name: &str) -> String {
46    let mir = match _private_transform_json_mir(source) {
47        Ok(mir) => mir,
48        Err(e) => return anyhow_error_to_compile_error(e),
49    };
50
51    transform_mir(mir, driver_name)
52}
53
54#[doc(hidden)]
55#[cfg(feature = "json")]
56pub fn _private_transform_json_mir(source: &str) -> anyhow::Result<mir::Device> {
57    let value = dd_manifest_tree::parse_manifest::<dd_manifest_tree::JsonValue>(source)?;
58    let mir = manifest::transform(value)?;
59
60    Ok(mir)
61}
62
63/// Transform the yaml string to the generated device driver (or a compile error).
64///
65/// The `driver_name` arg is used to name the root block of the driver.
66/// It should be given in `PascalCase` form.
67#[cfg(feature = "yaml")]
68pub fn transform_yaml(source: &str, driver_name: &str) -> String {
69    let mir = match _private_transform_yaml_mir(source) {
70        Ok(mir) => mir,
71        Err(e) => return anyhow_error_to_compile_error(e),
72    };
73
74    transform_mir(mir, driver_name)
75}
76
77#[doc(hidden)]
78#[cfg(feature = "yaml")]
79pub fn _private_transform_yaml_mir(source: &str) -> anyhow::Result<mir::Device> {
80    let value = dd_manifest_tree::parse_manifest::<dd_manifest_tree::YamlValue>(source)?;
81    let mir = manifest::transform(value)?;
82
83    Ok(mir)
84}
85
86/// Transform the toml string to the generated device driver (or a compile error).
87///
88/// The `driver_name` arg is used to name the root block of the driver.
89/// It should be given in `PascalCase` form.
90#[cfg(feature = "toml")]
91pub fn transform_toml(source: &str, driver_name: &str) -> String {
92    let mir = match _private_transform_toml_mir(source) {
93        Ok(mir) => mir,
94        Err(e) => return anyhow_error_to_compile_error(e),
95    };
96
97    transform_mir(mir, driver_name)
98}
99
100#[doc(hidden)]
101#[cfg(feature = "toml")]
102pub fn _private_transform_toml_mir(source: &str) -> anyhow::Result<mir::Device> {
103    let value = dd_manifest_tree::parse_manifest::<dd_manifest_tree::TomlValue>(source)?;
104    let mir = manifest::transform(value)?;
105
106    Ok(mir)
107}
108
109fn transform_mir(mut mir: mir::Device, driver_name: &str) -> String {
110    // Run the MIR passes
111    match mir::passes::run_passes(&mut mir) {
112        Ok(()) => {}
113        Err(e) => return anyhow_error_to_compile_error(e),
114    }
115
116    // Transform into LIR
117    let mut lir = match mir::lir_transform::transform(mir, driver_name) {
118        Ok(lir) => lir,
119        Err(e) => return anyhow_error_to_compile_error(e),
120    };
121
122    // Run the LIR passes
123    match lir::passes::run_passes(&mut lir) {
124        Ok(()) => {}
125        Err(e) => return anyhow_error_to_compile_error(e),
126    };
127
128    // Transform into Rust source token output
129    let output = lir::code_transform::DeviceTemplateRust::new(&lir).to_string();
130
131    match format_code(&output) {
132        Ok(formatted_output) => formatted_output,
133        Err(e) => format!(
134            "{}\n\n{output}",
135            e.to_string().lines().map(|e| format!("// {e}")).join("\n")
136        ),
137    }
138}
139
140fn anyhow_error_to_compile_error(error: anyhow::Error) -> String {
141    syn::Error::new(proc_macro2::Span::call_site(), format!("{error:#}"))
142        .into_compile_error()
143        .to_string()
144}
145
146fn format_code(input: &str) -> Result<String, anyhow::Error> {
147    Ok(prettyplease::unparse(&syn::parse_file(input)?))
148}