1#![doc = include_str!("../README.md")]
2
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6use machine_check_common::iir::description::IMachine;
7use machine_check_common::iir::path::{IIdent, ISpan};
8use machine_check_common::iir::property::IProperty;
9use machine_check_common::iir::ty::IElementaryType;
10use machine_check_common::Signedness;
11use mck::concr::FullMachine;
12use proc_macro2::{Ident, Span};
13use quote::{quote, ToTokens};
14use support::error_list::ErrorList;
15use syn::punctuated::Punctuated;
16use syn::spanned::Spanned;
17use syn::visit_mut::{self, VisitMut};
18use syn::{parse_quote, Attribute, Expr, Item, ItemFn, ItemMod, Meta, MetaList, PathSegment};
19use syn_path::path;
20use wir::IntoSyn;
21
22use crate::util::{create_item_mod, path_matches_global_names};
23use crate::wir::{WBasicType, WIdent, WSpan};
24
25mod abstr;
26mod concr;
27mod into_iir;
28mod into_wir;
29mod support;
30mod util;
31mod wir;
32
33pub use support::machine_error::{Error, ErrorType};
34
35pub type Errors = ErrorList<Error>;
36
37#[derive(Clone)]
38struct Description {
39 pub items: Vec<Item>,
40}
41
42pub fn process_file(mut file: syn::File) -> Result<syn::File, Errors> {
43 process_items(&mut file.items)?;
44 Ok(file)
45}
46
47pub fn process_module(mut module: ItemMod) -> Result<ItemMod, Errors> {
48 let Some((_, items)) = &mut module.content else {
49 return Err(Errors::single(Error::new(
50 ErrorType::ModuleWithoutContent,
51 WSpan::from_syn(&module),
52 )));
53 };
54 process_items(items)?;
55 Ok(module)
56}
57
58pub fn inherent_property() -> IProperty {
59 let mut global_basic_types = HashMap::new();
60 global_basic_types.insert(
61 WIdent::new(String::from("__panic"), Span::call_site()),
62 WBasicType::Bitvector(Signedness::None, 32),
63 );
64
65 let expr = parse_quote!(AG![__panic == 0]);
66
67 let (property, _panic_messages) =
68 into_wir::create_property_description(expr, &global_basic_types)
69 .expect("Inherent property should be created");
70
71 property
74 .into_iir()
75 .expect("Inherent property should not produce an error")
76}
77
78pub fn process_property<M: FullMachine>(
79 machine: &IMachine,
80 property: &str,
81) -> Result<IProperty, Errors> {
82 let expr: Expr = syn::parse_str(property).map_err(|err| {
83 Errors::single(Error::new(
84 ErrorType::ExpressionParseError(err.to_string()),
85 WSpan::from_span(err.span()),
86 ))
87 })?;
88
89 let mut global_ident_types = machine.state().fields.clone();
90
91 global_ident_types.insert(
92 IIdent::new(String::from("__panic"), ISpan::Unspecified),
93 IElementaryType::Bitvector(32),
94 );
95
96 let mut global_basic_types = HashMap::new();
97
98 for (global_ident, elementary_type) in &global_ident_types {
100 let ty = match elementary_type {
101 IElementaryType::Bitvector(width) => WBasicType::Bitvector(Signedness::None, *width),
102 IElementaryType::Array(type_array) => WBasicType::BitvectorArray(type_array.clone()),
103 IElementaryType::Boolean => WBasicType::Boolean,
104 IElementaryType::Struct(_struct_id) => {
105 todo!("Support nested structs")
106 }
107 };
108 global_basic_types.insert(
109 WIdent::new(global_ident.name().to_string(), Span::call_site()),
110 ty,
111 );
112 }
113
114 let (property, _panic_messages) =
116 into_wir::create_property_description(expr, &global_basic_types)?;
117
118 let property = property.into_iir();
119
120 Ok(property?)
121}
122
123pub fn default_main() -> Item {
124 let main_fn: ItemFn = parse_quote!(
125 fn main() {
126 ::machine_check_exec::run::<refin::Input, refin::State, refin::Machine>()
127 }
128 );
129 Item::Fn(main_fn)
130}
131
132fn out_dir() -> Option<PathBuf> {
133 let mut args = std::env::args();
134
135 let mut out_dir = None;
136 while let Some(arg) = args.next() {
137 if arg == "--out-dir" {
138 out_dir = args.next();
139 }
140 }
141 let out_dir = out_dir?;
142 let mut out_dir = PathBuf::from(out_dir);
144 while !out_dir.ends_with("target") {
145 if !out_dir.pop() {
146 return None;
147 }
148 }
149
150 Some(out_dir)
151}
152
153#[allow(dead_code)]
154fn unparse(items: Vec<Item>) -> String {
155 prettyplease::unparse(&syn::File {
156 shebang: None,
157 attrs: vec![],
158 items,
159 })
160}
161
162fn process_items(items: &mut Vec<Item>) -> Result<(), Errors> {
163 let out_dir: Option<PathBuf> = if cfg!(feature = "write_machine") {
164 out_dir()
165 } else {
166 None
167 };
168
169 let (description, panic_messages) = into_wir::create_description(items.clone())?;
170
171 if let Some(out_dir) = &out_dir {
172 std::fs::write(
173 out_dir.join("description.rs"),
174 unparse(wir::IntoSyn::into_syn(description.clone()).items),
175 )
176 .expect("SSA machine file should be writable");
177 }
178
179 let iir = description.clone().into_iir()?;
180
181 let (abstract_description, misc_abstract_items) =
182 abstr::create_abstract_description(description);
183
184 if let Some(out_dir) = &out_dir {
185 std::fs::write(
186 out_dir.join("description_abstr.rs"),
187 unparse(wir::IntoSyn::into_syn(abstract_description.clone()).items),
188 )
189 .expect("Abstract machine file should be writable");
190 }
191
192 let mut abstract_description = Description {
193 items: abstract_description.into_syn().items,
194 };
195
196 abstract_description.items.extend(misc_abstract_items);
197
198 support::strip_machine::strip_machine(&mut abstract_description)?;
199
200 concr::process_items(items, &panic_messages, iir)?;
201
202 let abstract_module = create_machine_module("__mck_mod_abstr", abstract_description);
203 items.push(abstract_module);
204
205 redirect_mck(items)?;
206
207 if let Some(out_dir) = &out_dir {
208 std::fs::write(out_dir.join("description_full.rs"), unparse(items.clone()))
209 .expect("Full machine file should be writable");
210 }
211
212 Ok(())
213}
214
215fn redirect_mck(items: &mut [Item]) -> Result<(), Error> {
216 let mut redirect_visitor = RedirectVisitor;
217 for item in items.iter_mut() {
218 redirect_visitor.visit_item_mut(item);
219 }
220
221 Ok(())
222}
223
224struct RedirectVisitor;
225
226impl VisitMut for RedirectVisitor {
227 fn visit_path_mut(&mut self, path: &mut syn::Path) {
228 if path.leading_colon.is_some() {
229 let first_segment = path
230 .segments
231 .first()
232 .expect("Path should have first segment");
233 if first_segment.ident == "mck" {
234 let span = first_segment.span();
235 path.segments.insert(
237 0,
238 PathSegment {
239 ident: Ident::new("machine_check", span),
240 arguments: syn::PathArguments::None,
241 },
242 );
243 }
244 }
245 visit_mut::visit_path_mut(self, path);
246 }
247
248 fn visit_macro_mut(&mut self, mac: &mut syn::Macro) {
249 if path_matches_global_names(&mac.path, &["std", "vec"]) {
251 let mut parsed = mac
252 .parse_body_with(Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated)
253 .expect("Macro ::std::vec! should be parseable");
254 for expr in &mut parsed {
255 visit_mut::visit_expr_mut(self, expr);
256 }
257 mac.tokens = parsed.into_token_stream();
258 }
259 visit_mut::visit_macro_mut(self, mac);
260 }
261}
262
263fn create_machine_module(name: &str, machine: Description) -> Item {
264 let mut module = create_item_mod(
265 syn::Visibility::Public(Default::default()),
266 Ident::new(name, Span::call_site()),
267 machine.items,
268 );
269 module.attrs.push(Attribute {
272 pound_token: Default::default(),
273 style: syn::AttrStyle::Outer,
274 bracket_token: Default::default(),
275 meta: Meta::List(MetaList {
276 path: path!(allow),
277 delimiter: syn::MacroDelimiter::Paren(Default::default()),
278 tokens: quote!(
279 non_snake_case, clippy::all, ),
282 }),
283 });
284
285 Item::Mod(module)
286}