use crate::codegen::diagnostic::{
AnyDiagnostic, CyclicImport, DiagnosticExt, DuplicatePolyType, GlobalSpan, ImportFailed,
UndeclaredType,
};
use crate::midl::FileParser;
use crate::parser;
use crate::parser::{ArgAttr, Ctype, Sp};
use codespan::{FileId, Files};
use lalrpop_util::lexer::Token;
use lalrpop_util::ParseError;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::{fs, io};
use uuid::Uuid;
pub mod c;
pub mod diagnostic;
pub mod rust;
#[derive(Clone, Debug)]
struct Import {
c_name: PathBuf,
rust_name: Option<TokenStream>,
file: Option<Rc<File>>,
}
#[derive(Clone, Debug)]
pub struct File {
imports: Vec<Import>,
elems: Vec<Toplevel>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SearchMode {
Pwd,
Include,
Context(GlobalSpan),
}
impl SearchMode {
pub fn context(&self) -> Option<GlobalSpan> {
match self {
SearchMode::Context(id) => Some(*id),
_ => None,
}
}
}
#[derive(Default, Clone, Debug)]
pub struct Context {
files: Files<String>,
by_path: HashMap<PathBuf, FileId>,
file_data: HashMap<FileId, FileData>,
include_paths: Vec<PathBuf>,
}
#[derive(Default, Clone, Debug)]
struct FileData {
ast: Option<Rc<parser::File>>,
logical: Option<Rc<File>>,
}
impl Context {
pub fn new() -> Self {
Default::default()
}
pub fn add_include_paths<I>(&mut self, paths: I)
where
I: IntoIterator,
I::Item: Into<PathBuf>,
{
self.include_paths
.extend(paths.into_iter().map(|el| el.into()))
}
pub fn load_file(&mut self, path: &Path, search_mode: SearchMode) -> FileId {
let paths = match search_mode {
SearchMode::Pwd => vec![path.to_path_buf()],
_ => self
.include_paths
.iter()
.map(|el| el.join(path))
.chain(search_mode.context().iter().map(|el| {
Path::new(self.files.name(el.file))
.parent()
.unwrap()
.join(path)
}))
.collect(),
};
let mut errors = Vec::new();
for path in paths {
let path = match fs::canonicalize(path) {
Ok(path) => path,
Err(e) => {
errors.push(e);
continue;
}
};
if let Some(id) = self.by_path.get(&path) {
return *id;
}
let source = match fs::read_to_string(&path) {
Ok(s) => s,
Err(e) => {
errors.push(e);
continue;
}
};
let id = self.files.add(path.clone(), source);
self.by_path.insert(path, id);
self.file_data.insert(id, Default::default());
return id;
}
match search_mode.context() {
Some(import) => {
ImportFailed::new(path.to_path_buf(), import, errors).report_and_exit(self)
}
None => {
eprintln!("failed to open file: {}", path.display());
std::process::exit(1);
}
}
}
}
#[derive(Debug)]
pub(crate) struct ImportCtx<'a> {
parent: Option<&'a ImportCtx<'a>>,
location: Option<GlobalSpan>,
imported: FileId,
}
impl<'a> ImportCtx<'a> {
fn iter(&'a self) -> ImportCtxIter<'a> {
ImportCtxIter { inner: Some(self) }
}
}
struct ImportCtxIter<'a> {
inner: Option<&'a ImportCtx<'a>>,
}
impl<'a> Iterator for ImportCtxIter<'a> {
type Item = &'a ImportCtx<'a>;
fn next(&mut self) -> Option<Self::Item> {
let r = self.inner?;
self.inner = r.parent;
Some(r)
}
}
impl File {
pub fn load(path: &Path, sm: SearchMode, ctx: &mut Context) -> io::Result<Rc<Self>> {
File::load_recursion_safe(path, sm, ctx, None)
}
fn load_recursion_safe(
path: &Path,
sm: SearchMode,
ctx: &mut Context,
ictx: Option<&ImportCtx>,
) -> io::Result<Rc<Self>> {
let file = ctx.load_file(path, sm);
if let Some(file) = &ctx.file_data.get(&file).unwrap().logical {
return Ok(file.clone());
}
let ictx_out = ImportCtx {
parent: ictx,
location: sm.context(),
imported: file,
};
if ictx_out.iter().skip(1).any(|el| el.imported == file) {
CyclicImport::new(&ictx_out).report_and_exit(ctx);
}
let text = ctx.files.source(file);
let r: Result<parser::File, ParseError<usize, Token, &'static str>> =
FileParser::new().parse(&text);
let f = match r {
Ok(f) => f,
Err(e) => {
AnyDiagnostic::from_parse_error(file, &e).report_and_exit(ctx);
}
};
ctx.file_data.get_mut(&file).unwrap().ast = Some(Rc::new(f));
let f = ctx.file_data.get(&file).unwrap().ast.clone().unwrap();
let mut imports = Vec::new();
let mut elems = Vec::new();
for v in &f.elements {
let (paths, import) = match &v {
parser::Toplevel::Import(paths) => (paths, true),
parser::Toplevel::Include(paths) => (paths, false),
_ => continue,
};
for p in paths {
let import_path = Path::new(p);
let file = match import {
true => Some(File::load_recursion_safe(
&import_path,
SearchMode::Context(GlobalSpan::new(file, p.span())),
ctx,
Some(&ictx_out),
)?),
false => None,
};
let c_name = match import {
true => import_path.with_extension("h"),
false => import_path.to_path_buf(),
};
let rust_name = Ident::new(
import_path.file_stem().unwrap().to_str().unwrap(),
Span::call_site(),
);
let rust_name = quote! { #rust_name };
imports.push(Import {
c_name,
rust_name: Some(rust_name),
file,
});
}
}
for v in &f.elements {
match v {
parser::Toplevel::Import(_) => {}
parser::Toplevel::Include(_) => {}
parser::Toplevel::Interface(itf) => {
let itf = Interface::from_ast(itf, &imports, &elems, file, ctx);
elems.push(Toplevel::Interface(Rc::new(itf)));
}
parser::Toplevel::Typelib(_) => todo!(),
parser::Toplevel::Enum(_) => {}
parser::Toplevel::CppQuote(s) => {
elems.push(Toplevel::CppQuote(s.clone()));
}
}
}
let rc = Rc::new(File { imports, elems });
ctx.file_data.get_mut(&file).unwrap().logical = Some(rc.clone());
Ok(rc)
}
pub fn find_type(&self, name: &str) -> Option<Rc<Interface>> {
find_type_in_parts(&self.imports, &self.elems, name)
}
}
fn find_type_in_parts(imports: &[Import], elems: &[Toplevel], name: &str) -> Option<Rc<Interface>> {
for elem in elems {
let Toplevel::Interface(itf) = elem else {
continue;
};
if &*itf.name == name {
return Some(itf.clone());
}
}
for import in imports {
let Some(import) = &import.file else {
continue;
};
if let Some(itf) = import.find_type(name) {
return Some(itf);
}
}
None
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Toplevel {
CppQuote(String),
Interface(Rc<Interface>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Interface {
name: String,
defn: Option<InterfaceDefn>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InterfaceDefn {
base: Option<Rc<Interface>>,
uuid: Option<Uuid>,
local: bool,
fns: Vec<Function>,
}
impl Interface {
fn from_ast(
itf: &parser::Interface,
imports: &[Import],
tl_elems: &[Toplevel],
file: FileId,
ctx: &Context,
) -> Self {
let defn = match &itf.defn {
None => {
assert!(itf.attrs.is_empty());
None
}
Some(parser::InterfaceDefn { base, elems }) => {
let mut uuid = None;
let mut local = false;
for attr in &itf.attrs {
match attr {
parser::Attr::Local => local = true,
parser::Attr::Uuid(v) => uuid = Some(*v),
_ => {}
}
}
let mut fns = Vec::new();
for f in elems.iter() {
let mut params: Vec<_> = f
.args
.iter()
.map(|p| {
let mut poly_type = None;
for attr in &p.attrs {
match &**attr {
ArgAttr::In => {}
ArgAttr::MaxIs(_) => {}
ArgAttr::Out => {}
ArgAttr::PointerType(_) => {}
ArgAttr::Poly(r) => {
let (poly_idx, _) = f
.args
.iter()
.enumerate()
.find(|(_, arg)| arg.name.as_deref() == Some(r))
.unwrap();
poly_type = Some(Sp::new_from_span(poly_idx, attr.span()));
}
ArgAttr::RetVal => {}
ArgAttr::SizeIs(_) => {}
}
}
FnParam {
name: p.name.clone(),
ty: p.ty.clone(),
poly_type,
poly_value: None,
}
})
.collect();
for idx in 0..params.len() {
if let Some(link_idx) = params[idx].poly_type {
if params[*link_idx].poly_value.is_some() {
let mut p = Vec::new();
for i in 0..params.len() {
if let Some(pt) = params[i].poly_type {
if pt.inner() == link_idx.inner() {
p.push(pt.span());
}
}
}
let target = ¶ms[*link_idx];
let target = target.name.as_ref().map(|v| v.span()).unwrap();
DuplicatePolyType::new(file, p, target).report_and_exit(ctx);
}
params[*link_idx].poly_value = Some(idx);
}
}
fns.push(Function {
name: f.name.clone(),
ret: f.ret.clone(),
params,
});
}
let base = match base {
None => None,
Some(s) => {
let b = find_type_in_parts(&imports, &tl_elems, &s);
match b {
Some(base) => Some(base),
None => UndeclaredType::new(GlobalSpan::new(file, s.span()))
.report_and_exit(ctx),
}
}
};
Some(InterfaceDefn {
base,
uuid,
local,
fns,
})
}
};
Interface {
name: itf.name.clone(),
defn,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Function {
name: String,
ret: Ctype,
params: Vec<FnParam>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FnParam {
name: Option<Sp<String>>,
ty: Sp<Ctype>,
poly_type: Option<Sp<usize>>,
poly_value: Option<usize>,
}
pub fn get_header_name(p: &Path) -> String {
p.file_stem()
.unwrap()
.to_str()
.unwrap()
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() {
c.to_ascii_uppercase()
} else {
'_'
}
})
.collect()
}