2
1
Fork 0

typegen: add bare minimum parsing of qml files

This commit is contained in:
outfoxxed 2024-09-15 02:20:35 -07:00
parent 7590fd3246
commit 34cfae3023
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
3 changed files with 170 additions and 14 deletions

View file

@ -35,18 +35,44 @@ fn main() -> anyhow::Result<()> {
}) })
.collect::<Result<HashMap<_, _>, _>>()?; .collect::<Result<HashMap<_, _>, _>>()?;
let parser = parse::Parser::new(); let qml_texts = module
.header
.qml_files
.iter()
.map(|file| {
let text = std::fs::read_to_string(dir.join(file)).with_context(|| {
format!(
"failed to read module qml file `{file}` at {:?}",
dir.join(file)
)
})?;
Ok::<_, anyhow::Error>((file, text))
})
.collect::<Result<HashMap<_, _>, _>>()?;
let header_parser = parse::CppParser::new();
let qml_parser = parse::QmlParser::new();
let mut ctx = parse::ParseContext::new(&module.header.name); let mut ctx = parse::ParseContext::new(&module.header.name);
texts texts
.iter() .iter()
.map(|(header, text)| { .map(|(header, text)| {
parser header_parser
.parse(&text, &mut ctx) .parse(&text, &mut ctx)
.with_context(|| format!("while parsing module header `{header}`")) .with_context(|| format!("while parsing module header `{header}`"))
}) })
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
qml_texts
.iter()
.map(|(file, text)| {
qml_parser
.parse(&file, &text, &mut ctx)
.with_context(|| format!("while parsing module qml file `{file}`"))
})
.collect::<Result<_, _>>()?;
let typespec = ctx.gen_typespec(&module.header.name); let typespec = ctx.gen_typespec(&module.header.name);
let text = serde_json::to_string_pretty(&typespec).unwrap(); let text = serde_json::to_string_pretty(&typespec).unwrap();

View file

@ -1,4 +1,4 @@
use std::borrow::Cow; use std::{borrow::Cow, collections::HashMap};
use anyhow::{anyhow, bail, Context}; use anyhow::{anyhow, bail, Context};
use fancy_regex::Regex; use fancy_regex::Regex;
@ -10,7 +10,10 @@ use crate::typespec;
pub struct ModuleInfoHeader { pub struct ModuleInfoHeader {
pub name: String, pub name: String,
pub description: String, pub description: String,
#[serde(default)]
pub headers: Vec<String>, pub headers: Vec<String>,
#[serde(default)]
pub qml_files: Vec<String>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -50,7 +53,7 @@ pub struct ClassInfo<'a> {
pub type_: ClassType, pub type_: ClassType,
pub name: &'a str, pub name: &'a str,
pub qml_name: Option<&'a str>, pub qml_name: Option<&'a str>,
pub superclass: Option<&'a str>, pub superclass: Option<Cow<'a, str>>,
pub singleton: bool, pub singleton: bool,
pub uncreatable: bool, pub uncreatable: bool,
pub comment: Option<Comment<'a>>, pub comment: Option<Comment<'a>>,
@ -66,9 +69,9 @@ pub enum ClassType {
Gadget, Gadget,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct Property<'a> { pub struct Property<'a> {
pub type_: &'a str, pub type_: Cow<'a, str>,
pub name: &'a str, pub name: &'a str,
pub comment: Option<Comment<'a>>, pub comment: Option<Comment<'a>>,
pub readable: bool, pub readable: bool,
@ -112,7 +115,7 @@ pub struct Variant<'a> {
pub comment: Option<Comment<'a>>, pub comment: Option<Comment<'a>>,
} }
pub struct Parser { pub struct CppParser {
pub class_regex: Regex, pub class_regex: Regex,
pub macro_regex: Regex, pub macro_regex: Regex,
pub property_regex: Regex, pub property_regex: Regex,
@ -126,6 +129,14 @@ pub struct Parser {
pub enum_variant_regex: Regex, pub enum_variant_regex: Regex,
} }
pub struct QmlParser {
pub class_regex: Regex,
pub alias_regex: Regex,
pub safe_body_regex: Regex,
pub property_regex: Regex,
pub function_regex: Regex,
}
#[derive(Debug)] #[derive(Debug)]
pub struct ParseContext<'a> { pub struct ParseContext<'a> {
pub module: &'a str, pub module: &'a str,
@ -143,7 +154,7 @@ impl<'a> ParseContext<'a> {
} }
} }
impl Parser { impl CppParser {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
class_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*class\s+(?<name>\w+)(?:\s*:\s*public\s+((?<super>\w+)(<.+>)?)(\s*,(\s*\w+)*)*)?\s*\{(?<body>[\s\S]*?)(?!};\s*Q_ENUM)};"#).unwrap(), class_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*class\s+(?<name>\w+)(?:\s*:\s*public\s+((?<super>\w+)(<.+>)?)(\s*,(\s*\w+)*)*)?\s*\{(?<body>[\s\S]*?)(?!};\s*Q_ENUM)};"#).unwrap(),
@ -235,7 +246,7 @@ impl Parser {
} }
properties.push(Property { properties.push(Property {
type_: prop.name("type").unwrap().as_str(), type_: Cow::Borrowed(prop.name("type").unwrap().as_str()),
name: prop.name("name").unwrap().as_str(), name: prop.name("name").unwrap().as_str(),
comment: comment.map(|v| Comment::new(v, ctx.module)), comment: comment.map(|v| Comment::new(v, ctx.module)),
readable: read || member, readable: read || member,
@ -364,7 +375,7 @@ impl Parser {
type_, type_,
name, name,
qml_name, qml_name,
superclass, superclass: superclass.map(|s| Cow::Borrowed(s)),
singleton, singleton,
uncreatable, uncreatable,
comment: comment.map(|v| Comment::new(v, ctx.module)), comment: comment.map(|v| Comment::new(v, ctx.module)),
@ -461,6 +472,102 @@ impl Parser {
} }
} }
// todo: use an actual parser (never)
impl QmlParser {
pub fn new() -> Self {
Self {
class_regex: Regex::new(r#"((?<aliases>(\s*\/\/\/ alias.+)+)[\s\S]*?)?(?<comment>(\s*\/\/\/.*\n)+)?\s*(?<super>[A-Z]\w*)\s+{(?<body>[\s\S]*)}"#).unwrap(),
alias_regex: Regex::new(r#"alias\s+(?<alias>\w+)\s+(?<definition>[\w.]+)"#).unwrap(),
safe_body_regex: Regex::new(r#"((?<safebody1>^[\s\S]*?)(\n\s+[A-Z]\w* {[\s\S]*)|(?<safebody2>^[\s\S]*))"#).unwrap(),
// note: can pick up function bodies
property_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*(?<default>default\s+)?(?<required>required\s+)?(?<readonly>readonly\s+)?property\s+(\/\*(?<typeoverride>\w+)\*\/)?(?<type>\w+)\s+(?<name>\w+)\s*(:\s*(?<definition>(.*{\n[\s\S]*?\/\/ END-DEF|.*?(?!{)\n)))?"#).unwrap(),
// note: can pick up prop bodies
function_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*function\s+(?<name>\w+)\s*\((?<params>.*)\)\s*:\s*(?<return>\w+)\s*{"#).unwrap(),
}
}
pub fn parse<'a>(
&self,
filename: &'a str,
text: &'a str,
ctx: &mut ParseContext<'a>,
) -> anyhow::Result<()> {
for class in self.class_regex.captures_iter(text) {
let class = class?;
let mut aliases = HashMap::new();
if let Some(aliases_str) = class.name("aliases").map(|m| m.as_str()) {
for alias in self.alias_regex.captures_iter(aliases_str) {
let alias = alias?;
aliases.insert(
alias.name("alias").unwrap().as_str(),
alias.name("definition").unwrap().as_str(),
);
}
}
let alias_lookup = |name: &'a str| *aliases.get(name).unwrap_or(&name);
let comment = class.name("comment").map(|m| m.as_str());
let superclass = class.name("super").unwrap().as_str();
let body = {
let body = class.name("body").unwrap().as_str();
let sbc = self
.safe_body_regex
.captures(body)?
.ok_or_else(|| anyhow!("unable to capture safebody"))?;
sbc.name("safebody1")
.or_else(|| sbc.name("safebody2"))
.unwrap()
.as_str()
};
let mut properties = Vec::new();
for prop in self.property_regex.captures_iter(body) {
let prop = prop?;
let type_ = prop
.name("typeoverride")
.or_else(|| prop.name("type"))
.unwrap()
.as_str();
properties.push(Property {
type_: Cow::Owned(format!("QML:{}", alias_lookup(type_))),
name: prop.name("name").unwrap().as_str(),
comment: prop
.name("comment")
.map(|m| Comment::new(m.as_str(), ctx.module)),
readable: true,
writable: prop.name("readonly").is_none(),
default: prop.name("default").is_some(),
});
}
let name = filename.split_once('.').unwrap().0;
ctx.classes.push(ClassInfo {
type_: ClassType::Object,
name,
qml_name: Some(name),
superclass: Some(Cow::Owned(format!("QML:{}", superclass))),
singleton: false,
uncreatable: false,
comment: comment.map(|v| Comment::new(v, ctx.module)),
properties,
invokables: Vec::new(),
signals: Vec::new(),
enums: Vec::new(),
});
}
Ok(())
}
}
impl ParseContext<'_> { impl ParseContext<'_> {
pub fn gen_typespec(&self, module: &str) -> typespec::TypeSpec { pub fn gen_typespec(&self, module: &str) -> typespec::TypeSpec {
typespec::TypeSpec { typespec::TypeSpec {
@ -506,10 +613,10 @@ impl ParseContext<'_> {
description, description,
details, details,
// filters gadgets // filters gadgets
superclass: class.superclass?.to_string(), superclass: class.superclass.clone()?.to_string(),
singleton: class.singleton, singleton: class.singleton,
uncreatable: class.uncreatable, uncreatable: class.uncreatable,
properties: class.properties.iter().map(|p| (*p).into()).collect(), properties: class.properties.iter().map(|p| p.clone().into()).collect(),
functions: class.invokables.iter().map(|f| f.as_typespec()).collect(), functions: class.invokables.iter().map(|f| f.as_typespec()).collect(),
signals: class.signals.iter().map(|s| s.as_typespec()).collect(), signals: class.signals.iter().map(|s| s.as_typespec()).collect(),
enums: class enums: class
@ -543,7 +650,7 @@ impl ParseContext<'_> {
.filter_map(|class| match class.type_ { .filter_map(|class| match class.type_ {
ClassType::Gadget => Some(typespec::Gadget { ClassType::Gadget => Some(typespec::Gadget {
cname: class.name.to_string(), cname: class.name.to_string(),
properties: class.properties.iter().map(|p| (*p).into()).collect(), properties: class.properties.iter().map(|p| p.clone().into()).collect(),
}), }),
_ => None, _ => None,
}) })

View file

@ -16,7 +16,17 @@ pub fn resolve_types(
.iter() .iter()
.filter(|type_| type_.module.as_ref().map(|v| v as &str) == Some(module)); .filter(|type_| type_.module.as_ref().map(|v| v as &str) == Some(module));
let findqmltype = |cname: &str| typespec.typemap.iter().find(|type_| type_.cname == cname); let findqmltype = |name: &str| {
if name.starts_with("QML:") {
println!("QML? {name}");
typespec
.typemap
.iter()
.find(|type_| type_.name == name[4..])
} else {
typespec.typemap.iter().find(|type_| type_.cname == name)
}
};
for mapping in types { for mapping in types {
let Some(class) = typespec let Some(class) = typespec
@ -57,6 +67,19 @@ pub fn resolve_types(
}; };
fn qmlparamtype(ctype: &str, typespec: &TypeSpec) -> outform::Type { fn qmlparamtype(ctype: &str, typespec: &TypeSpec) -> outform::Type {
if ctype.starts_with("QML:") {
return match typespec
.typemap
.iter()
.find(|type_| type_.name == ctype[4..])
{
Some(t) => {
outform::Type::resolve(t.module.as_ref().map(|v| v as &str), &t.name)
},
None => outform::Type::unknown(),
}
}
let (mut ctype, of) = match ctype.split_once('<') { let (mut ctype, of) = match ctype.split_once('<') {
None => (ctype, None), None => (ctype, None),
Some((ctype, mut remaining)) => { Some((ctype, mut remaining)) => {