Skip to content

Commit

Permalink
cxx-qt-gen: add C++ generator and writer of extern "C++Qt"
Browse files Browse the repository at this point in the history
Related to #577
  • Loading branch information
ahayzen-kdab authored and Be-ing committed Aug 15, 2023
1 parent d8a8e31 commit 288c079
Show file tree
Hide file tree
Showing 9 changed files with 429 additions and 0 deletions.
105 changes: 105 additions & 0 deletions crates/cxx-qt-gen/src/generator/cpp/externcxxqt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{
generator::cpp::signal::generate_cpp_free_signal,
parser::{externcxxqt::ParsedExternCxxQt, mappings::ParsedCxxMappings},
CppFragment,
};
use syn::Result;

#[derive(Default)]
pub struct GeneratedCppExternCxxQtBlocks {
/// List of methods
pub method: CppFragment,
/// Namespace of the method block
pub namespace: String,
}

pub fn generate(
blocks: &[ParsedExternCxxQt],
cxx_mappings: &ParsedCxxMappings,
) -> Result<Vec<GeneratedCppExternCxxQtBlocks>> {
let mut out = vec![];

for block in blocks {
for signal in &block.signals {
// Build a namespace that includes any namespace for the T
let ident_namespace = if let Some(namespace) = cxx_mappings
.namespaces
.get(&signal.qobject_ident.to_string())
{
format!("::{namespace}")
} else {
"".to_owned()
};

out.push(GeneratedCppExternCxxQtBlocks {
method: generate_cpp_free_signal(signal, cxx_mappings)?,
namespace: format!("rust::cxxqtgen1::externcxxqt{ident_namespace}"),
});
}
}

Ok(out)
}

#[cfg(test)]
mod tests {
use syn::parse_quote;

use super::*;

#[test]
fn test_generate_cpp_extern_qt() {
let blocks = vec![ParsedExternCxxQt::parse(parse_quote! {
unsafe extern "C++Qt" {
type MyObject;

#[qsignal]
fn signal1(self: Pin<&mut ObjRust>);

#[qsignal]
fn signal2(self: Pin<&mut ObjRust>);
}
})
.unwrap()];
let generated = generate(&blocks, &ParsedCxxMappings::default()).unwrap();
assert_eq!(generated.len(), 2);

assert_eq!(generated[0].namespace, "rust::cxxqtgen1::externcxxqt");
assert_eq!(generated[1].namespace, "rust::cxxqtgen1::externcxxqt");
}

#[test]
fn test_generate_cpp_extern_qt_mapping() {
let blocks = vec![ParsedExternCxxQt::parse(parse_quote! {
unsafe extern "C++Qt" {
#[cxx_name = "ObjCpp"]
#[namespace = "mynamespace"]
type ObjRust;

#[qsignal]
fn signal(self: Pin<&mut ObjRust>);
}
})
.unwrap()];
let mut cxx_mappings = ParsedCxxMappings::default();
cxx_mappings
.cxx_names
.insert("ObjRust".to_owned(), "ObjCpp".to_owned());
cxx_mappings
.namespaces
.insert("ObjRust".to_owned(), "mynamespace".to_owned());

let generated = generate(&blocks, &cxx_mappings).unwrap();
assert_eq!(generated.len(), 1);

assert_eq!(
generated[0].namespace,
"rust::cxxqtgen1::externcxxqt::mynamespace"
);
}
}
8 changes: 8 additions & 0 deletions crates/cxx-qt-gen/src/generator/cpp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

mod constructor;
pub mod cxxqttype;
pub mod externcxxqt;
pub mod fragment;
pub mod inherit;
pub mod locking;
Expand All @@ -15,6 +16,7 @@ pub mod signal;
pub mod threading;

use crate::parser::Parser;
use externcxxqt::GeneratedCppExternCxxQtBlocks;
use qobject::GeneratedCppQObject;
use syn::Result;

Expand All @@ -26,6 +28,8 @@ pub struct GeneratedCppBlocks {
pub namespace: String,
/// Generated QObjects
pub qobjects: Vec<GeneratedCppQObject>,
/// Generated extern C++Qt blocks
pub extern_cxx_qt: Vec<GeneratedCppExternCxxQtBlocks>,
}

impl GeneratedCppBlocks {
Expand All @@ -39,6 +43,10 @@ impl GeneratedCppBlocks {
.values()
.map(|qobject| GeneratedCppQObject::from(qobject, &parser.cxx_qt_data.cxx_mappings))
.collect::<Result<Vec<GeneratedCppQObject>>>()?,
extern_cxx_qt: externcxxqt::generate(
&parser.cxx_qt_data.extern_cxxqt_blocks,
&parser.cxx_qt_data.cxx_mappings,
)?,
})
}
}
Expand Down
169 changes: 169 additions & 0 deletions crates/cxx-qt-gen/src/generator/cpp/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,60 @@ fn parameter_types_and_values(
})
}

/// Generate C++ blocks for a free signal on an existing QObject (not generated by CXX-Qt), eg QPushButton::clicked
pub fn generate_cpp_free_signal(
signal: &ParsedSignal,
cxx_mappings: &ParsedCxxMappings,
) -> Result<CppFragment> {
// Prepare the idents we need
let qobject_ident = signal.qobject_ident.to_string();
let qobject_ident_namespaced = cxx_mappings.cxx(&qobject_ident);
let idents = QSignalName::from(signal);
let signal_ident = idents.name.cpp.to_string();
// TODO: in the future we might improve the naming of the methods
// to avoid collisions (maybe use a separator similar to how CXX uses $?)
let connect_ident = idents.connect_name.cpp.to_string();

// Retrieve the parameters for the signal
let parameters = parameter_types_and_values(
&signal.parameters,
cxx_mappings,
SelfValue {
ident: "self",
ty: &qobject_ident_namespaced,
},
)?;
let parameters_types_closure = parameters.types_closure;
let parameters_types_signal = parameters.types_signal;
let parameters_values_closure = parameters.values_closure;

Ok(CppFragment::Pair {
header: formatdoc!(
r#"
::QMetaObject::Connection
{qobject_ident}_{connect_ident}({qobject_ident_namespaced}& self, ::rust::Fn<void({parameters_types_closure})> func, ::Qt::ConnectionType type);
"#,
),
source: formatdoc! {
r#"
::QMetaObject::Connection
{qobject_ident}_{connect_ident}({qobject_ident_namespaced}& self, ::rust::Fn<void({parameters_types_closure})> func, ::Qt::ConnectionType type)
{{
return ::QObject::connect(
&self,
&{qobject_ident_namespaced}::{signal_ident},
&self,
[&, func = ::std::move(func)]({parameters_types_signal}) {{
const ::rust::cxxqtlib1::MaybeLockGuard<{qobject_ident_namespaced}> guard(self);
func({parameters_values_closure});
}},
type);
}}
"#,
},
})
}

pub fn generate_cpp_signals(
signals: &Vec<ParsedSignal>,
qobject_idents: &QObjectName,
Expand Down Expand Up @@ -312,4 +366,119 @@ mod tests {
"#}
);
}

#[test]
fn test_generate_cpp_signal_free() {
let signal = ParsedSignal {
method: parse_quote! {
fn signal_rust_name(self: Pin<&mut ObjRust>);
},
qobject_ident: format_ident!("ObjRust"),
mutable: true,
parameters: vec![],
ident: CombinedIdent {
cpp: format_ident!("signalRustName"),
rust: format_ident!("signal_rust_name"),
},
safe: true,
inherit: false,
};

let generated = generate_cpp_free_signal(&signal, &ParsedCxxMappings::default()).unwrap();

let (header, source) = if let CppFragment::Pair { header, source } = &generated {
(header, source)
} else {
panic!("Expected Pair")
};

assert_str_eq!(
header,
indoc! {
r#"
::QMetaObject::Connection
ObjRust_signalRustNameConnect(ObjRust& self, ::rust::Fn<void(ObjRust&)> func, ::Qt::ConnectionType type);
"#}
);
assert_str_eq!(
source,
indoc! {r#"
::QMetaObject::Connection
ObjRust_signalRustNameConnect(ObjRust& self, ::rust::Fn<void(ObjRust&)> func, ::Qt::ConnectionType type)
{
return ::QObject::connect(
&self,
&ObjRust::signalRustName,
&self,
[&, func = ::std::move(func)]() {
const ::rust::cxxqtlib1::MaybeLockGuard<ObjRust> guard(self);
func(self);
},
type);
}
"#}
);
}

#[test]
fn test_generate_cpp_signal_free_mapped() {
let signal = ParsedSignal {
method: parse_quote! {
#[cxx_name = "signalCxxName"]
fn signal_rust_name(self: Pin<&mut ObjRust>);
},
qobject_ident: format_ident!("ObjRust"),
mutable: true,
parameters: vec![],
ident: CombinedIdent {
cpp: format_ident!("signalCxxName"),
rust: format_ident!("signal_rust_name"),
},
safe: true,
inherit: false,
};

let mut cxx_mappings = ParsedCxxMappings::default();
cxx_mappings
.cxx_names
.insert("ObjRust".to_owned(), "ObjCpp".to_owned());
cxx_mappings
.namespaces
.insert("ObjRust".to_owned(), "mynamespace".to_owned());

let generated = generate_cpp_free_signal(&signal, &cxx_mappings).unwrap();

let (header, source) = if let CppFragment::Pair { header, source } = &generated {
(header, source)
} else {
panic!("Expected Pair")
};

assert_str_eq!(
header,
indoc! {
r#"
::QMetaObject::Connection
ObjRust_signalCxxNameConnect(::mynamespace::ObjCpp& self, ::rust::Fn<void(::mynamespace::ObjCpp&)> func, ::Qt::ConnectionType type);
"#}
);
assert_str_eq!(
source,
indoc! {r#"
::QMetaObject::Connection
ObjRust_signalCxxNameConnect(::mynamespace::ObjCpp& self, ::rust::Fn<void(::mynamespace::ObjCpp&)> func, ::Qt::ConnectionType type)
{
return ::QObject::connect(
&self,
&::mynamespace::ObjCpp::signalCxxName,
&self,
[&, func = ::std::move(func)]() {
const ::rust::cxxqtlib1::MaybeLockGuard<::mynamespace::ObjCpp> guard(self);
func(self);
},
type);
}
"#}
);
}
}
16 changes: 16 additions & 0 deletions crates/cxx-qt-gen/src/writer/cpp/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,27 @@ pub fn write_cpp_header(generated: &GeneratedCppBlocks) -> String {
{forward_declare}
#include "cxx-qt-gen/{cxx_file_stem}.cxx.h"
{extern_cxx_qt}
{qobjects}
"#,
cxx_file_stem = generated.cxx_file_stem,
forward_declare = forward_declare(generated).join("\n"),
qobjects = qobjects_header(generated).join("\n"),
extern_cxx_qt = {
let mut out = vec![];
for block in &generated.extern_cxx_qt {
if let Some(method) = pair_as_header(&block.method) {
let (namespace_start, namespace_end) = namespace_start_and_end(&block.namespace);
out.push(formatdoc! { r#"
{namespace_start}
{method}
{namespace_end}
"#,
});
}
}
out.join("\n")
},
includes = generated.qobjects.iter()
.fold(BTreeSet::<&String>::default(), |mut acc, qobject| {
acc.extend(qobject.blocks.includes.iter());
Expand Down
Loading

0 comments on commit 288c079

Please sign in to comment.