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::rules::NoRuleMatch;
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;
12
13use crate::util::create_item_mod;
14
15mod abstr;
16mod concr;
17mod refin;
18mod ssa;
19mod support;
20mod util;
21
22#[derive(Clone)]
23pub struct MachineDescription {
24    pub items: Vec<Item>,
25    pub panic_messages: Vec<String>,
26}
27
28pub fn process_file(mut file: syn::File) -> Result<syn::File, MachineError> {
29    process_items(&mut file.items)?;
30    Ok(file)
31}
32
33pub fn process_module(mut module: ItemMod) -> Result<ItemMod, MachineError> {
34    let Some((_, items)) = &mut module.content else {
35        return Err(MachineError::new(
36            ErrorType::ModuleWithoutContent,
37            module.span(),
38        ));
39    };
40    process_items(items)?;
41    Ok(module)
42}
43
44pub fn default_main() -> Item {
45    let main_fn: ItemFn = parse_quote!(
46        fn main() {
47            ::machine_check_exec::run::<refin::Input, refin::State, refin::Machine>()
48        }
49    );
50    Item::Fn(main_fn)
51}
52
53#[allow(dead_code)]
54fn out_dir() -> Option<PathBuf> {
55    // TODO: disable creation of temporary files unless specifically requested
56    let mut args = std::env::args();
57
58    let mut out_dir = None;
59    while let Some(arg) = args.next() {
60        if arg == "--out-dir" {
61            out_dir = args.next();
62        }
63    }
64    let out_dir = out_dir?;
65    // find target directory
66    let mut out_dir = PathBuf::from(out_dir);
67    while !out_dir.ends_with("target") {
68        if !out_dir.pop() {
69            return None;
70        }
71    }
72
73    Some(out_dir)
74}
75
76#[allow(dead_code)]
77fn unparse(machine: &MachineDescription) -> String {
78    prettyplease::unparse(&syn::File {
79        shebang: None,
80        attrs: vec![],
81        items: machine.items.clone(),
82    })
83}
84
85fn process_items(items: &mut Vec<Item>) -> Result<(), MachineError> {
86    //println!("Machine-check-machine starting processing");
87
88    //let out_dir = out_dir();
89
90    let ssa_machine = ssa::create_ssa_machine(items.clone())?;
91    /*if let Some(out_dir) = &out_dir {
92        std::fs::write(out_dir.join("machine_ssa.rs"), unparse(&ssa_machine))
93            .expect("SSA machine file should be writable");
94    }*/
95
96    let mut abstract_machine = abstr::create_abstract_machine(&ssa_machine)?;
97
98    /*if let Some(out_dir) = &out_dir {
99        std::fs::write(out_dir.join("machine_abstr.rs"), unparse(&abstract_machine))
100            .expect("Abstract machine file should be writable");
101    }*/
102
103    let refinement_machine = refin::create_refinement_machine(&abstract_machine)?;
104
105    // create new module at the end of the file that will contain the refinement
106    let refinement_module = create_machine_module("__mck_mod_refin", refinement_machine);
107    abstract_machine.items.push(refinement_module);
108
109    support::strip_machine::strip_machine(&mut abstract_machine)?;
110
111    concr::process_items(items, &ssa_machine.panic_messages)?;
112
113    let abstract_module = create_machine_module("__mck_mod_abstr", abstract_machine);
114    items.push(abstract_module);
115
116    redirect_mck(items)?;
117
118    /*if let Some(out_dir) = &out_dir {
119        std::fs::write(
120            out_dir.join("machine_full.rs"),
121            unparse(&MachineDescription {
122                items: items.clone(),
123                panic_messages: ssa_machine.panic_messages.clone(),
124            }),
125        )
126        .expect("Full machine file should be writable");
127    }*/
128
129    //println!("Machine-check-machine ending processing");
130
131    Ok(())
132}
133
134fn redirect_mck(items: &mut [Item]) -> Result<(), MachineError> {
135    let mut redirect_visitor = RedirectVisitor;
136    for item in items.iter_mut() {
137        redirect_visitor.visit_item_mut(item);
138    }
139
140    Ok(())
141}
142
143struct RedirectVisitor;
144
145impl VisitMut for RedirectVisitor {
146    fn visit_path_mut(&mut self, path: &mut syn::Path) {
147        if path.leading_colon.is_some() {
148            let first_segment = path
149                .segments
150                .first()
151                .expect("Path should have first segment");
152            if first_segment.ident == "mck" {
153                let span = first_segment.span();
154                // add machine_check before it
155                path.segments.insert(
156                    0,
157                    PathSegment {
158                        ident: Ident::new("machine_check", span),
159                        arguments: syn::PathArguments::None,
160                    },
161                );
162            }
163        }
164        visit_mut::visit_path_mut(self, path);
165    }
166}
167
168fn create_machine_module(name: &str, machine: MachineDescription) -> Item {
169    let mut module = create_item_mod(
170        syn::Visibility::Public(Default::default()),
171        Ident::new(name, Span::call_site()),
172        machine.items,
173    );
174    // Turn off some warnings due to the form of the rewritten modules.
175    // Note that they can still fire for the original code.
176    module.attrs.push(Attribute {
177        pound_token: Default::default(),
178        style: syn::AttrStyle::Outer,
179        bracket_token: Default::default(),
180        meta: Meta::List(MetaList {
181            path: path!(allow),
182            delimiter: syn::MacroDelimiter::Paren(Default::default()),
183            tokens: quote!(
184                non_snake_case, // _a can become __mck__a and violate snake-casing
185                clippy::all,    // turn off clippy altogether to speed up
186            ),
187        }),
188    });
189
190    Item::Mod(module)
191}
192
193#[derive(thiserror::Error, Debug, Clone)]
194#[error("{0}")]
195pub enum BackwardErrorType {
196    #[error("Unable to convert")]
197    NoRuleMatch,
198    #[error("Identifier type discovery failed")]
199    IdentTypeDiscovery,
200    #[error("Unsupported construct: {0}")]
201    UnsupportedConstruct(String),
202}
203
204#[derive(thiserror::Error, Debug, Clone)]
205#[error("{ty}")]
206pub struct BackwardError {
207    pub ty: BackwardErrorType,
208    pub span: Span,
209}
210
211impl From<NoRuleMatch> for BackwardError {
212    fn from(error: NoRuleMatch) -> Self {
213        BackwardError::new(BackwardErrorType::NoRuleMatch, error.0)
214    }
215}
216
217impl BackwardError {
218    fn new(ty: BackwardErrorType, span: Span) -> Self {
219        Self { ty, span }
220    }
221}
222
223#[derive(thiserror::Error, Debug, Clone)]
224pub enum ErrorType {
225    #[error("machine-check: Cannot parse module without content")]
226    ModuleWithoutContent,
227
228    #[error("machine-check error: Unknown macro")]
229    UnknownMacro,
230    #[error("{0}")]
231    MacroError(String),
232    #[error("{0}")]
233    MacroParseError(syn::Error),
234    #[error("machine-check: {0}")]
235    UnsupportedConstruct(String),
236    #[error("machine-check: {0}")]
237    IllegalConstruct(String),
238    #[error("machine-check: Could not infer variable type")]
239    InferenceFailure,
240    #[error("machine-check (concrete conversion): {0}")]
241    ConcreteConversionError(String),
242    #[error("machine-check (forward conversion): {0}")]
243    ForwardConversionError(String),
244    #[error("machine-check (backward conversion): {0}")]
245    BackwardConversionError(String),
246
247    #[error("machine-check internal error (SSA translation): {0}")]
248    SsaInternal(String),
249    #[error("machine-check internal error (forward translation): {0}")]
250    ForwardInternal(String),
251    #[error("machine-check internal error (backward translation): {0}")]
252    BackwardInternal(String),
253    #[error("machine-check internal error (rules): {0}")]
254    RulesInternal(String),
255}
256
257impl From<BackwardError> for MachineError {
258    fn from(error: BackwardError) -> Self {
259        MachineError {
260            ty: ErrorType::BackwardConversionError(format!("{}", error)),
261            span: error.span,
262        }
263    }
264}
265
266#[derive(thiserror::Error, Debug, Clone)]
267#[error("{span:?}: {ty}")]
268pub struct MachineError {
269    pub ty: ErrorType,
270    pub span: Span,
271}
272
273impl MachineError {
274    fn new(ty: ErrorType, span: Span) -> Self {
275        Self { ty, span }
276    }
277}