1use crate::codegen::diagnostic::{
2 AnyDiagnostic, CyclicImport, DiagnosticExt, DuplicatePolyType, GlobalSpan, ImportFailed,
3 UndeclaredType,
4};
5use crate::midl::FileParser;
6use crate::parser;
7use crate::parser::{ArgAttr, Ctype, Sp};
8use codespan::{FileId, Files};
9use lalrpop_util::lexer::Token;
10use lalrpop_util::ParseError;
11use proc_macro2::{Ident, Span, TokenStream};
12use quote::quote;
13use std::collections::HashMap;
14use std::path::{Path, PathBuf};
15use std::rc::Rc;
16use std::{fs, io};
17use uuid::Uuid;
18
19pub mod c;
20pub mod diagnostic;
21pub mod rust;
22
23#[derive(Clone, Debug)]
24struct Import {
25 c_name: PathBuf,
26 rust_name: Option<TokenStream>,
27 file: Option<Rc<File>>,
28}
29
30#[derive(Clone, Debug)]
31pub struct File {
32 imports: Vec<Import>,
33 elems: Vec<Toplevel>,
34}
35
36#[derive(Debug, Clone, Copy, Eq, PartialEq)]
37pub enum SearchMode {
38 Pwd,
39 Include,
40 Context(GlobalSpan),
41}
42
43impl SearchMode {
44 pub fn context(&self) -> Option<GlobalSpan> {
45 match self {
46 SearchMode::Context(id) => Some(*id),
47 _ => None,
48 }
49 }
50}
51
52#[derive(Default, Clone, Debug)]
53pub struct Context {
54 files: Files<String>,
55 by_path: HashMap<PathBuf, FileId>,
56 file_data: HashMap<FileId, FileData>,
57 include_paths: Vec<PathBuf>,
58}
59
60#[derive(Default, Clone, Debug)]
61struct FileData {
62 ast: Option<Rc<parser::File>>,
63 logical: Option<Rc<File>>,
64}
65
66impl Context {
67 pub fn new() -> Self {
68 Default::default()
69 }
70
71 pub fn add_include_paths<I>(&mut self, paths: I)
72 where
73 I: IntoIterator,
74 I::Item: Into<PathBuf>,
75 {
76 self.include_paths
77 .extend(paths.into_iter().map(|el| el.into()))
78 }
79
80 pub fn load_file(&mut self, path: &Path, search_mode: SearchMode) -> FileId {
81 let paths = match search_mode {
82 SearchMode::Pwd => vec![path.to_path_buf()],
83 _ => self
84 .include_paths
85 .iter()
86 .map(|el| el.join(path))
87 .chain(search_mode.context().iter().map(|el| {
88 Path::new(self.files.name(el.file))
89 .parent()
90 .unwrap()
91 .join(path)
92 }))
93 .collect(),
94 };
95
96 let mut errors = Vec::new();
97
98 for path in paths {
99 let path = match fs::canonicalize(path) {
100 Ok(path) => path,
101 Err(e) => {
102 errors.push(e);
103 continue;
104 }
105 };
106
107 if let Some(id) = self.by_path.get(&path) {
108 return *id;
109 }
110
111 let source = match fs::read_to_string(&path) {
112 Ok(s) => s,
113 Err(e) => {
114 errors.push(e);
115 continue;
116 }
117 };
118
119 let id = self.files.add(path.clone(), source);
120
121 self.by_path.insert(path, id);
122 self.file_data.insert(id, Default::default());
123 return id;
124 }
125
126 match search_mode.context() {
128 Some(import) => {
129 ImportFailed::new(path.to_path_buf(), import, errors).report_and_exit(self)
130 }
131 None => {
132 eprintln!("failed to open file: {}", path.display());
133 std::process::exit(1);
134 }
135 }
136 }
137}
138
139#[derive(Debug)]
140pub(crate) struct ImportCtx<'a> {
141 parent: Option<&'a ImportCtx<'a>>,
142 location: Option<GlobalSpan>,
143 imported: FileId,
144}
145
146impl<'a> ImportCtx<'a> {
147 fn iter(&'a self) -> ImportCtxIter<'a> {
148 ImportCtxIter { inner: Some(self) }
149 }
150}
151
152struct ImportCtxIter<'a> {
153 inner: Option<&'a ImportCtx<'a>>,
154}
155
156impl<'a> Iterator for ImportCtxIter<'a> {
157 type Item = &'a ImportCtx<'a>;
158
159 fn next(&mut self) -> Option<Self::Item> {
160 let r = self.inner?;
161 self.inner = r.parent;
162 Some(r)
163 }
164}
165
166impl File {
167 pub fn load(path: &Path, sm: SearchMode, ctx: &mut Context) -> io::Result<Rc<Self>> {
168 File::load_recursion_safe(path, sm, ctx, None)
169 }
170
171 fn load_recursion_safe(
172 path: &Path,
173 sm: SearchMode,
174 ctx: &mut Context,
175 ictx: Option<&ImportCtx>,
176 ) -> io::Result<Rc<Self>> {
177 let file = ctx.load_file(path, sm);
178
179 if let Some(file) = &ctx.file_data.get(&file).unwrap().logical {
180 return Ok(file.clone());
181 }
182
183 let ictx_out = ImportCtx {
184 parent: ictx,
185 location: sm.context(),
186 imported: file,
187 };
188
189 if ictx_out.iter().skip(1).any(|el| el.imported == file) {
190 CyclicImport::new(&ictx_out).report_and_exit(ctx);
191 }
192
193 let text = ctx.files.source(file);
194
195 let r: Result<parser::File, ParseError<usize, Token, &'static str>> =
196 FileParser::new().parse(&text);
197 let f = match r {
198 Ok(f) => f,
199 Err(e) => {
200 AnyDiagnostic::from_parse_error(file, &e).report_and_exit(ctx);
201 }
202 };
203
204 ctx.file_data.get_mut(&file).unwrap().ast = Some(Rc::new(f));
205 let f = ctx.file_data.get(&file).unwrap().ast.clone().unwrap();
206
207 let mut imports = Vec::new();
208 let mut elems = Vec::new();
209
210 for v in &f.elements {
211 let (paths, import) = match &v {
212 parser::Toplevel::Import(paths) => (paths, true),
213 parser::Toplevel::Include(paths) => (paths, false),
214 _ => continue,
215 };
216
217 for p in paths {
218 let import_path = Path::new(p);
219
220 let file = match import {
221 true => Some(File::load_recursion_safe(
222 &import_path,
223 SearchMode::Context(GlobalSpan::new(file, p.span())),
224 ctx,
225 Some(&ictx_out),
226 )?),
227 false => None,
228 };
229
230 let c_name = match import {
231 true => import_path.with_extension("h"),
232 false => import_path.to_path_buf(),
233 };
234
235 let rust_name = Ident::new(
236 import_path.file_stem().unwrap().to_str().unwrap(),
237 Span::call_site(),
238 );
239 let rust_name = quote! { #rust_name };
240
241 imports.push(Import {
242 c_name,
243 rust_name: Some(rust_name),
244 file,
245 });
246 }
247 }
248
249 for v in &f.elements {
250 match v {
251 parser::Toplevel::Import(_) => {}
252 parser::Toplevel::Include(_) => {}
253 parser::Toplevel::Interface(itf) => {
254 let itf = Interface::from_ast(itf, &imports, &elems, file, ctx);
255 elems.push(Toplevel::Interface(Rc::new(itf)));
256 }
257 parser::Toplevel::Typelib(_) => todo!(),
258 parser::Toplevel::Enum(_) => {}
259 parser::Toplevel::CppQuote(s) => {
260 elems.push(Toplevel::CppQuote(s.clone()));
261 }
262 }
263 }
264
265 let rc = Rc::new(File { imports, elems });
266 ctx.file_data.get_mut(&file).unwrap().logical = Some(rc.clone());
267
268 Ok(rc)
269 }
270
271 pub fn find_type(&self, name: &str) -> Option<Rc<Interface>> {
272 find_type_in_parts(&self.imports, &self.elems, name)
273 }
274}
275
276fn find_type_in_parts(imports: &[Import], elems: &[Toplevel], name: &str) -> Option<Rc<Interface>> {
277 for elem in elems {
278 let Toplevel::Interface(itf) = elem else {
279 continue;
280 };
281
282 if &*itf.name == name {
283 return Some(itf.clone());
284 }
285 }
286
287 for import in imports {
288 let Some(import) = &import.file else {
289 continue;
290 };
291
292 if let Some(itf) = import.find_type(name) {
293 return Some(itf);
294 }
295 }
296
297 None
298}
299
300#[derive(Clone, Debug, Eq, PartialEq)]
301pub enum Toplevel {
302 CppQuote(String),
303 Interface(Rc<Interface>),
304}
305
306#[derive(Clone, Debug, Eq, PartialEq)]
307pub struct Interface {
308 name: String,
309 defn: Option<InterfaceDefn>,
310}
311
312#[derive(Clone, Debug, Eq, PartialEq)]
313pub struct InterfaceDefn {
314 base: Option<Rc<Interface>>,
315 uuid: Option<Uuid>,
316 local: bool,
317 fns: Vec<Function>,
318}
319
320impl Interface {
321 fn from_ast(
322 itf: &parser::Interface,
323 imports: &[Import],
324 tl_elems: &[Toplevel],
325 file: FileId,
326 ctx: &Context,
327 ) -> Self {
328 let defn = match &itf.defn {
329 None => {
330 assert!(itf.attrs.is_empty());
332 None
333 }
334 Some(parser::InterfaceDefn { base, elems }) => {
335 let mut uuid = None;
336 let mut local = false;
337
338 for attr in &itf.attrs {
339 match attr {
340 parser::Attr::Local => local = true,
341 parser::Attr::Uuid(v) => uuid = Some(*v),
342 _ => {}
343 }
344 }
345
346 let mut fns = Vec::new();
347
348 for f in elems.iter() {
349 let mut params: Vec<_> = f
350 .args
351 .iter()
352 .map(|p| {
353 let mut poly_type = None;
354
355 for attr in &p.attrs {
356 match &**attr {
357 ArgAttr::In => {}
358 ArgAttr::MaxIs(_) => {}
359 ArgAttr::Out => {}
360 ArgAttr::PointerType(_) => {}
361 ArgAttr::Poly(r) => {
362 let (poly_idx, _) = f
363 .args
364 .iter()
365 .enumerate()
366 .find(|(_, arg)| arg.name.as_deref() == Some(r))
367 .unwrap();
368 poly_type = Some(Sp::new_from_span(poly_idx, attr.span()));
369 }
370 ArgAttr::RetVal => {}
371 ArgAttr::SizeIs(_) => {}
372 }
373 }
374
375 FnParam {
376 name: p.name.clone(),
377 ty: p.ty.clone(),
378 poly_type,
379 poly_value: None,
380 }
382 })
383 .collect();
384
385 for idx in 0..params.len() {
386 if let Some(link_idx) = params[idx].poly_type {
387 if params[*link_idx].poly_value.is_some() {
388 let mut p = Vec::new();
389
390 for i in 0..params.len() {
391 if let Some(pt) = params[i].poly_type {
392 if pt.inner() == link_idx.inner() {
393 p.push(pt.span());
394 }
395 }
396 }
397
398 let target = ¶ms[*link_idx];
399 let target = target.name.as_ref().map(|v| v.span()).unwrap();
401 DuplicatePolyType::new(file, p, target).report_and_exit(ctx);
402 }
403 params[*link_idx].poly_value = Some(idx);
404 }
405 }
406
407 fns.push(Function {
408 name: f.name.clone(),
409 ret: f.ret.clone(),
410 params,
411 });
412 }
413
414 let base = match base {
415 None => None,
416 Some(s) => {
417 let b = find_type_in_parts(&imports, &tl_elems, &s);
418
419 match b {
420 Some(base) => Some(base),
421 None => UndeclaredType::new(GlobalSpan::new(file, s.span()))
422 .report_and_exit(ctx),
423 }
424 }
425 };
426
427 Some(InterfaceDefn {
428 base,
429 uuid,
430 local,
431 fns,
432 })
433 }
434 };
435
436 Interface {
437 name: itf.name.clone(),
438 defn,
439 }
440 }
441}
442
443#[derive(Clone, Debug, Eq, PartialEq)]
444pub struct Function {
445 name: String,
446 ret: Ctype,
447 params: Vec<FnParam>,
448}
449
450#[derive(Clone, Debug, Eq, PartialEq)]
451pub struct FnParam {
452 name: Option<Sp<String>>,
453 ty: Sp<Ctype>,
454 poly_type: Option<Sp<usize>>,
455 poly_value: Option<usize>,
456}
457
458pub fn get_header_name(p: &Path) -> String {
459 p.file_stem()
460 .unwrap()
461 .to_str()
462 .unwrap()
463 .chars()
464 .map(|c| {
465 if c.is_ascii_alphanumeric() {
466 c.to_ascii_uppercase()
467 } else {
468 '_'
469 }
470 })
471 .collect()
472}