Skip to main content

ab_riscv_macros/
build.rs

1mod enum_definition;
2mod enum_impl;
3mod execution_impl;
4mod state;
5
6use crate::build::enum_definition::{
7    collect_enum_definitions_from_dependencies, process_enum_definition,
8    process_pending_enum_definitions,
9};
10use crate::build::enum_impl::{
11    collect_enum_impls_from_dependencies, process_enum_impl, process_pending_enum_impls,
12};
13use crate::build::execution_impl::{
14    collect_enum_execution_impls_from_dependencies, process_execution_impl,
15    process_pending_enum_execution_impls,
16};
17use crate::build::state::State;
18use ab_riscv_macros_common::code_utils::pre_process_rust_code;
19use anyhow::Context;
20use quote::ToTokens;
21use std::path::{Path, PathBuf};
22use std::rc::Rc;
23use std::{env, fs, io, iter};
24use syn::Item;
25
26/// Processes all instruction macros in the crate when called from `build.rs`
27pub fn process_instruction_macros() -> anyhow::Result<()> {
28    let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").context(
29        "Failed to retrieve `CARGO_MANIFEST_DIR` environment variable, make sure to call \
30        `process_instruction_macros` from `build.rs`",
31    )?;
32    let out_dir = env::var_os("OUT_DIR").context(
33        "Failed to retrieve `OUT_DIR` environment variable, make sure to call \
34        `process_instruction_macros` from `build.rs`",
35    )?;
36    let out_dir = Path::new(&out_dir);
37
38    let mut state = State::new();
39
40    for maybe_enum_definition in collect_enum_definitions_from_dependencies() {
41        let (item_enum, dependencies, source) = maybe_enum_definition?;
42
43        state.insert_known_enum_definition(item_enum, dependencies, source)?;
44    }
45    for maybe_enum_impl in collect_enum_impls_from_dependencies() {
46        let (item_impl, source) = maybe_enum_impl?;
47        state.insert_known_enum_impl(item_impl, source)?;
48    }
49    for maybe_enum_execution_impl in collect_enum_execution_impls_from_dependencies() {
50        let (item_impl, source) = maybe_enum_execution_impl?;
51        state.insert_known_enum_execution_impl(item_impl, source)?;
52    }
53
54    for maybe_rust_file in rust_files_in(Path::new(&manifest_dir).join("src")) {
55        let rust_file = maybe_rust_file.context("Failed to collect Rust files")?;
56        let rust_file = Rc::<Path>::from(rust_file.into_boxed_path());
57        process_rust_file(rust_file.clone(), out_dir, &mut state)
58            .with_context(|| format!("Failed to process Rust file `{}`", rust_file.display()))?;
59    }
60
61    process_pending_enum_definitions(out_dir, &mut state)?;
62    process_pending_enum_impls(out_dir, &mut state)?;
63    process_pending_enum_execution_impls(out_dir, &mut state)
64}
65
66fn rust_files_in(dir: PathBuf) -> Box<dyn Iterator<Item = io::Result<PathBuf>>> {
67    fn walk(dir: PathBuf) -> Box<dyn Iterator<Item = io::Result<PathBuf>>> {
68        let read_dir = match fs::read_dir(dir) {
69            Ok(iter) => iter,
70            Err(error) => {
71                return Box::new(iter::once(Err(error))) as Box<_>;
72            }
73        };
74
75        Box::new(read_dir.flat_map(move |entry_res| {
76            let entry = match entry_res {
77                Ok(entry) => entry,
78                Err(error) => {
79                    return Box::new(iter::once(Err(error))) as Box<_>;
80                }
81            };
82
83            let path = entry.path();
84
85            if path.is_dir() {
86                walk(path)
87            } else if path
88                .extension()
89                .and_then(|ext| ext.to_str())
90                .is_some_and(|ext| ext == "rs")
91            {
92                Box::new(iter::once(Ok(path))) as Box<_>
93            } else {
94                Box::new(iter::empty::<io::Result<PathBuf>>()) as Box<_>
95            }
96        }))
97    }
98
99    walk(dir)
100}
101
102fn process_rust_file(source: Rc<Path>, out_dir: &Path, state: &mut State) -> anyhow::Result<()> {
103    let mut file_contents = fs::read_to_string(&source).context("Failed to read Rust file")?;
104    if !file_contents.contains("#[instruction") {
105        // Quickly skip files without instruction macro calls. This helps to ignore the files that
106        // may use Rust nightly syntax features not supported by `syn`, which is limited to stable
107        // Rust.
108        return Ok(());
109    }
110
111    pre_process_rust_code(&mut file_contents);
112
113    let file = syn::parse_file(&file_contents).context("Failed to parse Rust file")?;
114
115    for item in file.items {
116        match item {
117            Item::Enum(item_enum) => {
118                let enum_name = item_enum.ident.clone();
119                process_enum_definition(item_enum, out_dir, state).with_context(|| {
120                    format!(
121                        "Failed to process enum `{enum_name}` in file `{}`",
122                        source.display()
123                    )
124                })?;
125            }
126            Item::Impl(item_impl) => {
127                let trait_name = item_impl.trait_.as_ref().map(|(_, path, _)| {
128                    path.segments
129                        .last()
130                        .expect("Path is never empty; qed")
131                        .ident
132                        .clone()
133                });
134                let type_name = item_impl.self_ty.clone();
135                if let Some(result) = process_enum_impl(item_impl.clone(), out_dir, state) {
136                    result.with_context(|| {
137                        format!(
138                            "Failed to process impl block (`{:?}` for `{}`) in file `{}`",
139                            trait_name.to_token_stream(),
140                            type_name.to_token_stream(),
141                            source.display()
142                        )
143                    })?;
144                } else if let Some(result) = process_execution_impl(item_impl, out_dir, state) {
145                    result.with_context(|| {
146                        format!(
147                            "Failed to process impl block (`{:?}` for `{}`) in file `{}`",
148                            trait_name.to_token_stream(),
149                            type_name.to_token_stream(),
150                            source.display()
151                        )
152                    })?;
153                    continue;
154                }
155            }
156            _ => {
157                // Ignore
158            }
159        }
160    }
161
162    Ok(())
163}