machine_check_machine/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::path::PathBuf;
4
5use proc_macro2::{Ident, Span};
6use quote::quote;
7use support::error_list::ErrorList;
8use syn::spanned::Spanned;
9use syn::visit_mut::{self, VisitMut};
10use syn::{parse_quote, Attribute, Item, ItemFn, ItemMod, Meta, MetaList, PathSegment};
11use syn_path::path;
12use wir::IntoSyn;
13
14use crate::util::create_item_mod;
15use crate::wir::WSpan;
16
17mod abstr;
18mod concr;
19mod description;
20mod refin;
21mod support;
22mod util;
23mod wir;
24
25pub use support::machine_error::{Error, ErrorType};
26
27pub type Errors = ErrorList<Error>;
28
29#[derive(Clone)]
30struct Description {
31    pub items: Vec<Item>,
32}
33
34pub fn process_file(mut file: syn::File) -> Result<syn::File, Errors> {
35    process_items(&mut file.items)?;
36    Ok(file)
37}
38
39pub fn process_module(mut module: ItemMod) -> Result<ItemMod, Errors> {
40    let Some((_, items)) = &mut module.content else {
41        return Err(Errors::single(Error::new(
42            ErrorType::ModuleWithoutContent,
43            WSpan::from_syn(&module),
44        )));
45    };
46    process_items(items)?;
47    Ok(module)
48}
49
50pub fn default_main() -> Item {
51    let main_fn: ItemFn = parse_quote!(
52        fn main() {
53            ::machine_check_exec::run::<refin::Input, refin::State, refin::Machine>()
54        }
55    );
56    Item::Fn(main_fn)
57}
58
59fn out_dir() -> Option<PathBuf> {
60    let mut args = std::env::args();
61
62    let mut out_dir = None;
63    while let Some(arg) = args.next() {
64        if arg == "--out-dir" {
65            out_dir = args.next();
66        }
67    }
68    let out_dir = out_dir?;
69    // find target directory
70    let mut out_dir = PathBuf::from(out_dir);
71    while !out_dir.ends_with("target") {
72        if !out_dir.pop() {
73            return None;
74        }
75    }
76
77    Some(out_dir)
78}
79
80#[allow(dead_code)]
81fn unparse(items: Vec<Item>) -> String {
82    prettyplease::unparse(&syn::File {
83        shebang: None,
84        attrs: vec![],
85        items,
86    })
87}
88
89fn process_items(items: &mut Vec<Item>) -> Result<(), Errors> {
90    let out_dir: Option<PathBuf> = if cfg!(feature = "write_machine") {
91        out_dir()
92    } else {
93        None
94    };
95
96    let (description, panic_messages) = description::create_description(items.clone())?;
97
98    if let Some(out_dir) = &out_dir {
99        std::fs::write(
100            out_dir.join("description.rs"),
101            unparse(wir::IntoSyn::into_syn(description.clone()).items),
102        )
103        .expect("SSA machine file should be writable");
104    }
105
106    let (abstract_description, misc_abstract_items) =
107        abstr::create_abstract_description(description);
108
109    if let Some(out_dir) = &out_dir {
110        std::fs::write(
111            out_dir.join("description_abstr.rs"),
112            unparse(wir::IntoSyn::into_syn(abstract_description.clone()).items),
113        )
114        .expect("Abstract machine file should be writable");
115    }
116
117    let (refinement_description, misc_refinement_items) =
118        refin::create_refinement_description(&abstract_description);
119
120    let mut refinement_description = Description {
121        items: refinement_description.into_syn().items,
122    };
123
124    refinement_description.items.extend(misc_refinement_items);
125
126    // create new module at the end of the file that will contain the refinement
127    let refinement_module = create_machine_module("__mck_mod_refin", refinement_description);
128
129    let mut abstract_description = Description {
130        items: abstract_description.into_syn().items,
131    };
132
133    abstract_description.items.extend(misc_abstract_items);
134    abstract_description.items.push(refinement_module);
135
136    support::strip_machine::strip_machine(&mut abstract_description)?;
137
138    concr::process_items(items, &panic_messages)?;
139
140    let abstract_module = create_machine_module("__mck_mod_abstr", abstract_description);
141    items.push(abstract_module);
142
143    redirect_mck(items)?;
144
145    if let Some(out_dir) = &out_dir {
146        std::fs::write(out_dir.join("description_full.rs"), unparse(items.clone()))
147            .expect("Full machine file should be writable");
148    }
149
150    Ok(())
151}
152
153fn redirect_mck(items: &mut [Item]) -> Result<(), Error> {
154    let mut redirect_visitor = RedirectVisitor;
155    for item in items.iter_mut() {
156        redirect_visitor.visit_item_mut(item);
157    }
158
159    Ok(())
160}
161
162struct RedirectVisitor;
163
164impl VisitMut for RedirectVisitor {
165    fn visit_path_mut(&mut self, path: &mut syn::Path) {
166        if path.leading_colon.is_some() {
167            let first_segment = path
168                .segments
169                .first()
170                .expect("Path should have first segment");
171            if first_segment.ident == "mck" {
172                let span = first_segment.span();
173                // add machine_check before it
174                path.segments.insert(
175                    0,
176                    PathSegment {
177                        ident: Ident::new("machine_check", span),
178                        arguments: syn::PathArguments::None,
179                    },
180                );
181            }
182        }
183        visit_mut::visit_path_mut(self, path);
184    }
185}
186
187fn create_machine_module(name: &str, machine: Description) -> Item {
188    let mut module = create_item_mod(
189        syn::Visibility::Public(Default::default()),
190        Ident::new(name, Span::call_site()),
191        machine.items,
192    );
193    // Turn off some warnings due to the form of the rewritten modules.
194    // Note that they can still fire for the original code.
195    module.attrs.push(Attribute {
196        pound_token: Default::default(),
197        style: syn::AttrStyle::Outer,
198        bracket_token: Default::default(),
199        meta: Meta::List(MetaList {
200            path: path!(allow),
201            delimiter: syn::MacroDelimiter::Paren(Default::default()),
202            tokens: quote!(
203                non_snake_case, // _a can become __mck__a and violate snake-casing
204                clippy::all,    // turn off clippy altogether to speed up
205            ),
206        }),
207    });
208
209    Item::Mod(module)
210}