nuidl_lib/codegen/
mod.rs

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        // TODO: return an error here
127        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                // no attrs in forward decl
331                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                                // TODO handle array
381                            }
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 = &params[*link_idx];
399                                // unwrap can never fail, since we identify the parameter by name
400                                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}