From d7e4f1362de33798bd9c32a3b002a29d05fe6524 Mon Sep 17 00:00:00 2001 From: Jos van den Oever Date: Wed, 26 Sep 2018 16:05:37 +0200 Subject: [PATCH] Port rust_qt_binding_generator from C++ to Rust --- .gitignore | 1 + CMakeLists.txt | 13 +- Cargo.toml | 11 + demo/CMakeLists.txt | 4 +- demo/src/Bindings.cpp | 26 +- src/configuration.rs | 487 +++++++++++++ src/cpp.cpp | 1194 -------------------------------- src/cpp.h | 23 - src/cpp.rs | 1518 +++++++++++++++++++++++++++++++++++++++++ src/helper.cpp | 23 - src/helper.h | 66 -- src/lib.rs | 28 + src/main.cpp | 68 -- src/parseJson.cpp | 348 ---------- src/parseJson.h | 23 - src/rust.cpp | 1116 ------------------------------ src/rust.h | 23 - src/rust.rs | 1503 ++++++++++++++++++++++++++++++++++++++++ src/structs.h | 194 ------ src/util.rs | 18 + tests/CMakeLists.txt | 2 +- 21 files changed, 3594 insertions(+), 3095 deletions(-) create mode 100644 Cargo.toml create mode 100644 src/configuration.rs delete mode 100644 src/cpp.cpp delete mode 100644 src/cpp.h create mode 100644 src/cpp.rs delete mode 100644 src/helper.cpp delete mode 100644 src/helper.h create mode 100644 src/lib.rs delete mode 100644 src/main.cpp delete mode 100644 src/parseJson.cpp delete mode 100644 src/parseJson.h delete mode 100644 src/rust.cpp delete mode 100644 src/rust.h create mode 100644 src/rust.rs delete mode 100644 src/structs.h create mode 100644 src/util.rs diff --git a/.gitignore b/.gitignore index b7620bb..ac7386d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ **/Cargo.lock **/target/ +**/*.rs.bk /docker_home/ # tutorial build diff --git a/CMakeLists.txt b/CMakeLists.txt index 57362ae..38a15cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,18 @@ else() set(RUST_BUILD_FLAG --release) endif() -add_subdirectory(src) +set(RustQtBindingGenerator_EXECUTABLE "${CMAKE_CURRENT_SOURCE_DIR}/${RUST_TARGET_DIR}rust_qt_binding_generator") +add_custom_command( + OUTPUT "${RustQtBindingGenerator_EXECUTABLE}" + COMMAND ${Cargo_EXECUTABLE} build ${RUST_BUILD_FLAG} + DEPENDS src/lib.rs + src/configuration.rs + src/cpp.rs + src/util.rs + src/bin/rust_qt_binding_generator.rs + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" +) +add_custom_target(rust_qt_binding_generator DEPENDS "${RustQtBindingGenerator_EXECUTABLE}") add_subdirectory(tests) diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e446338 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rust_qt_binding_generator" +version = "0.1.0" +authors = ["Jos van den Oever "] + +[dependencies] +clap = "2" +regex= "1" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 74184bb..6699f12 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -6,8 +6,8 @@ add_custom_command( "${CMAKE_CURRENT_SOURCE_DIR}/src/Bindings.h" # if the cpp file is marked GENERATED, CMake will not check it for moc # "${CMAKE_CURRENT_SOURCE_DIR}/src/Bindings.cpp" - COMMAND ${CMAKE_BINARY_DIR}/src/rust_qt_binding_generator "${CMAKE_CURRENT_SOURCE_DIR}/bindings.json" - DEPENDS rust_qt_binding_generator bindings.json + COMMAND ${RustQtBindingGenerator_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/bindings.json" + DEPENDS ${RustQtBindingGenerator_EXECUTABLE} bindings.json ) # compile the rust code into a static library diff --git a/demo/src/Bindings.cpp b/demo/src/Bindings.cpp index 460080e..eddf561 100644 --- a/demo/src/Bindings.cpp +++ b/demo/src/Bindings.cpp @@ -3,19 +3,6 @@ namespace { - struct option_quintptr { - public: - quintptr value; - bool some; - operator QVariant() const { - if (some) { - return QVariant::fromValue(value); - } - return QVariant(); - } - }; - static_assert(std::is_pod::value, "option_quintptr must be a POD type."); - struct option_quint64 { public: quint64 value; @@ -29,6 +16,19 @@ namespace { }; static_assert(std::is_pod::value, "option_quint64 must be a POD type."); + struct option_quintptr { + public: + quintptr value; + bool some; + operator QVariant() const { + if (some) { + return QVariant::fromValue(value); + } + return QVariant(); + } + }; + static_assert(std::is_pod::value, "option_quintptr must be a POD type."); + typedef void (*qstring_set)(QString* val, const char* utf8, int nbytes); void set_qstring(QString* val, const char* utf8, int nbytes) { *val = QString::fromUtf8(utf8, nbytes); diff --git a/src/configuration.rs b/src/configuration.rs new file mode 100644 index 0000000..3a31744 --- /dev/null +++ b/src/configuration.rs @@ -0,0 +1,487 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::path::{Path, PathBuf}; +use std::error::Error; +use serde_json; +use std::fs; +use std::rc::Rc; + +mod json { + use std::path::PathBuf; + use std::collections::BTreeMap; + use super::Rust; + + pub fn false_bool() -> bool { + false + } + + fn object() -> super::ObjectType { + super::ObjectType::Object + } + + #[derive(Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Config { + #[serde(rename = "cppFile")] + pub cpp_file: PathBuf, + pub objects: BTreeMap, + pub rust: Rust, + #[serde(default = "false_bool")] + pub overwrite_implementation: bool, + } + + #[derive(Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Object { + #[serde(default)] + pub functions: BTreeMap, + #[serde(rename = "itemProperties", default)] + pub item_properties: BTreeMap, + #[serde(rename = "type", default = "object")] + pub object_type: super::ObjectType, + #[serde(default)] + pub properties: BTreeMap, + } + + #[derive(Deserialize)] + #[serde(deny_unknown_fields)] + pub struct Property { + #[serde(default = "false_bool")] + pub optional: bool, + #[serde(rename = "type")] + pub property_type: String, + #[serde(rename = "rustByFunction", default = "false_bool")] + pub rust_by_function: bool, + #[serde(default = "false_bool")] + pub write: bool, + } +} + +pub struct Config { + pub config_file: PathBuf, + pub cpp_file: PathBuf, + pub objects: BTreeMap>, + pub rust: Rust, + pub overwrite_implementation: bool, +} + +impl Config { + pub fn types(&self) -> BTreeSet { + let mut ops = BTreeSet::new(); + for o in self.objects.values() { + for p in o.properties.values() { + ops.insert(p.type_name().into()); + } + for p in o.item_properties.values() { + ops.insert(p.type_name().into()); + } + for f in o.functions.values() { + ops.insert(f.return_type.name().into()); + for a in &f.arguments { + ops.insert(a.type_name().into()); + } + } + } + ops + } + pub fn optional_types(&self) -> BTreeSet { + let mut ops = BTreeSet::new(); + for o in self.objects.values() { + for p in o.properties.values() { + if p.optional { + ops.insert(p.type_name().into()); + } + } + for p in o.item_properties.values() { + if p.optional { + ops.insert(p.type_name().into()); + } + } + if o.object_type != ObjectType::Object { + ops.insert("quintptr".into()); + } + } + ops + } + pub fn has_list_or_tree(&self) -> bool { + self.objects.values().any(|o| { + o.object_type == ObjectType::List || o.object_type == ObjectType::Tree + }) + } +} + +#[derive(PartialEq)] +pub struct Object { + pub name: String, + pub functions: BTreeMap, + pub item_properties: BTreeMap, + pub object_type: ObjectType, + pub properties: BTreeMap, + pub column_count: usize, +} + +impl Object { + pub fn contains_object(&self) -> bool { + self.properties.values().any(|p| p.is_object()) + } +} + +#[derive(PartialEq)] +pub struct Property { + pub optional: bool, + pub property_type: Type, + pub rust_by_function: bool, + pub write: bool, +} + +impl Property { + pub fn is_object(&self) -> bool { + self.property_type.is_object() + } + pub fn is_complex(&self) -> bool { + self.property_type.is_complex() + } + pub fn type_name(&self) -> &str { + self.property_type.name() + } + pub fn c_get_type(&self) -> String { + let name = self.property_type.name(); + name.to_string() + "*, " + &name.to_lowercase() + "_set" + } +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Rust { + pub dir: PathBuf, + #[serde(rename = "implementationModule")] + pub implementation_module: String, + #[serde(rename = "interfaceModule")] + pub interface_module: String, +} + +#[derive(Deserialize, Clone, Copy, PartialEq)] +pub enum ObjectType { + Object, + List, + Tree, +} + +#[derive(Deserialize, Clone, Copy, PartialEq)] +pub enum SimpleType { + QString, + QByteArray, + #[serde(rename = "bool")] + Bool, + #[serde(rename = "float")] + Float, + #[serde(rename = "double")] + Double, + #[serde(rename = "void")] + Void, + #[serde(rename = "qint8")] + Qint8, + #[serde(rename = "qint16")] + Qint16, + #[serde(rename = "qint32")] + Qint32, + #[serde(rename = "qint64")] + Qint64, + #[serde(rename = "quint8")] + QUint8, + #[serde(rename = "quint16")] + QUint16, + #[serde(rename = "quint32")] + QUint32, + #[serde(rename = "quint64")] + QUint64, +} + +impl SimpleType { + pub fn name(&self) -> &str { + match self { + SimpleType::QString => "QString", + SimpleType::QByteArray => "QByteArray", + SimpleType::Bool => "bool", + SimpleType::Float => "float", + SimpleType::Double => "double", + SimpleType::Void => "void", + SimpleType::Qint8 => "qint8", + SimpleType::Qint16 => "qint16", + SimpleType::Qint32 => "qint32", + SimpleType::Qint64 => "qint64", + SimpleType::QUint8 => "quint8", + SimpleType::QUint16 => "quint16", + SimpleType::QUint32 => "quint32", + SimpleType::QUint64 => "quint64", + } + } + pub fn cpp_set_type(&self) -> &str { + match self { + SimpleType::QString => "const QString&", + SimpleType::QByteArray => "const QByteArray&", + SimpleType::Bool => "bool", + SimpleType::Float => "float", + SimpleType::Double => "double", + SimpleType::Void => "void", + SimpleType::Qint8 => "qint8", + SimpleType::Qint16 => "qint16", + SimpleType::Qint32 => "qint32", + SimpleType::Qint64 => "qint64", + SimpleType::QUint8 => "quint8", + SimpleType::QUint16 => "quint16", + SimpleType::QUint32 => "quint32", + SimpleType::QUint64 => "quint64", + } + } + pub fn c_set_type(&self) -> &str { + match self { + SimpleType::QString => "qstring_t", + SimpleType::QByteArray => "qbytearray_t", + SimpleType::Bool => "bool", + SimpleType::Float => "float", + SimpleType::Double => "double", + SimpleType::Void => "void", + SimpleType::Qint8 => "qint8", + SimpleType::Qint16 => "qint16", + SimpleType::Qint32 => "qint32", + SimpleType::Qint64 => "qint64", + SimpleType::QUint8 => "quint8", + SimpleType::QUint16 => "quint16", + SimpleType::QUint32 => "quint32", + SimpleType::QUint64 => "quint64", + } + } + pub fn rust_type(&self) -> &str { + match self { + SimpleType::QString => "String", + SimpleType::QByteArray => "Vec", + SimpleType::Bool => "bool", + SimpleType::Float => "f32", + SimpleType::Double => "f64", + SimpleType::Void => "()", + SimpleType::Qint8 => "i8", + SimpleType::Qint16 => "i16", + SimpleType::Qint32 => "i32", + SimpleType::Qint64 => "i64", + SimpleType::QUint8 => "u8", + SimpleType::QUint16 => "u16", + SimpleType::QUint32 => "u32", + SimpleType::QUint64 => "u64", + } + } + pub fn rust_type_init(&self) -> &str { + match self { + SimpleType::QString => "String::new()", + SimpleType::QByteArray => "Vec::new()", + SimpleType::Bool => "false", + SimpleType::Float => "0.0", + SimpleType::Double => "0.0", + SimpleType::Void => "()", + SimpleType::Qint8 => "0", + SimpleType::Qint16 => "0", + SimpleType::Qint32 => "0", + SimpleType::Qint64 => "0", + SimpleType::QUint8 => "0", + SimpleType::QUint16 => "0", + SimpleType::QUint32 => "0", + SimpleType::QUint64 => "0", + } + } + pub fn is_complex(self) -> bool { + self == SimpleType::QString || self == SimpleType::QByteArray + } +} + +#[derive(PartialEq)] +pub enum Type { + Simple(SimpleType), + Object(Rc), +} + +impl Type { + pub fn is_object(&self) -> bool { + if let Type::Object(_) = self { + true + } else { + false + } + } + pub fn is_complex(&self) -> bool { + if let Type::Simple(simple) = self { + simple.is_complex() + } else { + false + } + } + pub fn name(&self) -> &str { + match self { + Type::Simple(s) => s.name(), + Type::Object(o) => &o.name, + } + } + pub fn cpp_set_type(&self) -> &str { + match self { + Type::Simple(s) => s.cpp_set_type(), + Type::Object(o) => &o.name, + } + } + pub fn c_set_type(&self) -> &str { + match self { + Type::Simple(s) => s.c_set_type(), + Type::Object(o) => &o.name, + } + } + pub fn rust_type(&self) -> &str { + match self { + Type::Simple(s) => s.rust_type(), + Type::Object(o) => &o.name, + } + } + pub fn rust_type_init(&self) -> &str { + match self { + Type::Simple(s) => s.rust_type_init(), + Type::Object(_) => unimplemented!(), + } + } +} + +#[derive(Deserialize, Clone, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct ItemProperty { + #[serde(rename = "type")] + pub item_property_type: SimpleType, + #[serde(default = "json::false_bool")] + pub optional: bool, + #[serde(default)] + pub roles: Vec>, + #[serde(rename = "rustByValue", default = "json::false_bool")] + pub rust_by_value: bool, + #[serde(default = "json::false_bool")] + pub write: bool, +} + +impl ItemProperty { + pub fn type_name(&self) -> &str { + self.item_property_type.name() + } + pub fn is_complex(&self) -> bool { + self.item_property_type.is_complex() + } + pub fn cpp_set_type(&self) -> String { + let mut t = self.item_property_type.cpp_set_type().to_string(); + if self.optional { + t = "option_".to_string() + &t; + } + t + } + pub fn c_get_type(&self) -> String { + let name = self.item_property_type.name(); + name.to_string() + "*, " + &name.to_lowercase() + "_set" + } + pub fn c_set_type(&self) -> &str { + self.item_property_type.c_set_type() + } +} + +#[derive(Deserialize, Clone, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct Function { + #[serde(rename = "return")] + pub return_type: SimpleType, + #[serde(rename = "mut", default = "json::false_bool")] + pub mutable: bool, + #[serde(default)] + pub arguments: Vec, +} + +impl Function { + pub fn type_name(&self) -> &str { + self.return_type.name() + } +} + +#[derive(Deserialize, Clone, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct Argument { + pub name: String, + #[serde(rename = "type")] + pub argument_type: SimpleType, +} + +impl Argument { + pub fn type_name(&self) -> &str { + self.argument_type.name() + } +} + +fn post_process_property( + a: (&String, &json::Property), + b: &mut BTreeMap>, + c: &BTreeMap, +) -> Result> { + let name = &a.1.property_type; + let t = match serde_json::from_str::(&format!("\"{}\"", name)) { + Err(_) => { + if b.get(name).is_none() { + if let Some(object) = c.get(name) { + post_process_object((name, object), b, c)?; + } else { + return Err(format!("Type {} cannot be found.", name).into()); + } + } + Type::Object(Rc::clone(b.get(name).unwrap())) + } + Ok(simple) => Type::Simple(simple), + }; + Ok(Property { + property_type: t, + optional: a.1.optional, + rust_by_function: a.1.rust_by_function, + write: a.1.write, + }) +} + +fn post_process_object( + a: (&String, &json::Object), + b: &mut BTreeMap>, + c: &BTreeMap, +) -> Result<(), Box> { + let mut properties = BTreeMap::default(); + for p in &a.1.properties { + properties.insert(p.0.clone(), post_process_property(p, b, c)?); + } + let mut column_count = 1; + for ip in &a.1.item_properties { + column_count = column_count.max(ip.1.roles.len()); + } + let object = Rc::new(Object { + name: a.0.clone(), + object_type: a.1.object_type, + functions: a.1.functions.clone(), + item_properties: a.1.item_properties.clone(), + properties, + column_count, + }); + b.insert(a.0.clone(), object); + Ok(()) +} + +fn post_process(config_file: &Path, json: json::Config) -> Result> { + let mut objects = BTreeMap::default(); + for object in &json.objects { + post_process_object(object, &mut objects, &json.objects)?; + } + Ok(Config { + config_file: config_file.into(), + cpp_file: json.cpp_file, + objects, + rust: json.rust, + overwrite_implementation: json.overwrite_implementation, + }) +} + +pub fn parse>(config_file: P) -> Result> { + let contents = fs::read_to_string(config_file.as_ref())?; + let config: json::Config = serde_json::from_str(&contents)?; + post_process(config_file.as_ref(), config) +} diff --git a/src/cpp.cpp b/src/cpp.cpp deleted file mode 100644 index 6800ee4..0000000 --- a/src/cpp.cpp +++ /dev/null @@ -1,1194 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "structs.h" -#include "cpp.h" -#include "helper.h" -#include -#include - -template -QString cppSetType(const T& p) -{ - if (p.optional) { - return "option_" + p.type.cppSetType; - } - return p.type.cppSetType; -} - -template -QString propertyType(const T& p) -{ - return (p.optional && !p.type.isComplex()) ?"QVariant" :p.type.name; -} - -QString upperInitial(const QString& name) { - return name.left(1).toUpper() + name.mid(1); -} - -QString lowerInitial(const QString& name) { - return name.left(1).toLower() + name.mid(1); -} - -QString writeProperty(const QString& name) { - return "WRITE set" + upperInitial(name) + " "; -} - -QString baseType(const Object& o) { - if (o.type != ObjectType::Object) { - return "QAbstractItemModel"; - } - return "QObject"; -} - -QString cGetType(const BindingTypeProperties& type) { - return type.name + "*, " + type.name.toLower() + "_set"; -} - -bool modelIsWritable(const Object& o) { - bool write = false; - for (auto ip: o.itemProperties) { - write |= ip.write; - } - return write; -} - -void writeHeaderItemModel(QTextStream& h, const Object& o) { - h << QString(R"( - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - bool canFetchMore(const QModelIndex &parent) const override; - void fetchMore(const QModelIndex &parent) override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; - int role(const char* name) const; - QHash roleNames() const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; - Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; - Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; -)"); - if (modelIsWritable(o)) { - h << " bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;\n"; - } - for (auto ip: o.itemProperties) { - auto r = propertyType(ip); - auto rw = r; - if (r == "QVariant" || ip.type.isComplex()) { - rw = "const " + r + "&"; - } - if (o.type == ObjectType::List) { - h << QString(" Q_INVOKABLE %2 %1(int row) const;\n").arg(ip.name, r); - if (ip.write) { - h << QString(" Q_INVOKABLE bool set%1(int row, %2 value);\n").arg(upperInitial(ip.name), rw); - } - } else { - h << QString(" Q_INVOKABLE %2 %1(const QModelIndex& index) const;\n").arg(ip.name, r); - if (ip.write) { - h << QString(" Q_INVOKABLE bool set%1(const QModelIndex& index, %2 value);\n").arg(upperInitial(ip.name), rw); - } - } - } - h << R"( -Q_SIGNALS: - // new data is ready to be made available to the model with fetchMore() - void newDataReady(const QModelIndex &parent) const; -private: - QHash, QVariant> m_headerData; - void initHeaderData(); - void updatePersistentIndexes(); -)"; -} - -bool isColumnWrite(const Object& o, int col) { - for (auto ip: o.itemProperties) { - if (ip.write && (col == 0 || (ip.roles.size() > col && ip.roles[col].size() > 0))) { - return true; - } - } - return false; -} - -void writeModelGetterSetter(QTextStream& cpp, const QString& index, - const ItemProperty& ip, const Object& o) { - const QString lcname(snakeCase(o.name)); - QString idx = index; - - // getter - auto r = propertyType(ip); - if (o.type == ObjectType::List) { - idx = ", row"; - cpp << QString("%3 %1::%2(int row) const\n{\n") - .arg(o.name, ip.name, r); - } else { - cpp << QString("%3 %1::%2(const QModelIndex& index) const\n{\n") - .arg(o.name, ip.name, r); - } - if (ip.type.name == "QString") { - cpp << " QString s;\n"; - cpp << QString(" %1_data_%2(m_d%4, &s, set_%3);\n") - .arg(lcname, snakeCase(ip.name), ip.type.name.toLower(), idx); - cpp << " return s;\n"; - } else if (ip.type.name == "QByteArray") { - cpp << " QByteArray b;\n"; - cpp << QString(" %1_data_%2(m_d%4, &b, set_%3);\n") - .arg(lcname, snakeCase(ip.name), ip.type.name.toLower(), idx); - cpp << " return b;\n"; - } else if (ip.optional) { - cpp << " QVariant v;\n"; - cpp << QString(" v = %1_data_%2(m_d%3);\n") - .arg(lcname, snakeCase(ip.name), idx); - cpp << " return v;\n"; - } else { - cpp << QString(" return %1_data_%2(m_d%3);\n") - .arg(lcname, snakeCase(ip.name), idx); - } - cpp << "}\n\n"; - - if (!ip.write) { - return; - } - - // setter - if (r == "QVariant" || ip.type.isComplex()) { - r = "const " + r + "&"; - } - if (o.type == ObjectType::List) { - idx = ", row"; - cpp << QString("bool %1::set%2(int row, %3 value)\n{\n") - .arg(o.name, upperInitial(ip.name), r); - } else { - cpp << QString("bool %1::set%2(const QModelIndex& index, %3 value)\n{\n") - .arg(o.name, upperInitial(ip.name), r); - } - cpp << " bool set = false;\n"; - if (ip.optional) { - QString test = "value.isNull()"; - if (!ip.type.isComplex()) { - test += " || !value.isValid()"; - } - cpp << " if (" << test << ") {\n"; - cpp << QString(" set = %1_set_data_%2_none(m_d%3);") - .arg(lcname, snakeCase(ip.name), idx) << endl; - cpp << " } else {\n"; - } - if (ip.optional && !ip.type.isComplex()) { - cpp << QString(" if (!value.canConvert(qMetaTypeId<%1>())) {\n return false;\n }\n").arg(ip.type.name); - cpp << QString(" set = %1_set_data_%2(m_d%3, value.value<%4>());") - .arg(lcname, snakeCase(ip.name), idx, ip.type.name) << endl; - } else { - QString val = "value"; - if (ip.type.isComplex()) { - if (ip.type.name == "QString") { - val = "value.utf16(), value.length()"; - } else { - val = "value.data(), value.length()"; - } - } - cpp << QString(" set = %1_set_data_%2(m_d%3, %4);") - .arg(lcname, snakeCase(ip.name), idx, val) << endl; - } - if (ip.optional) { - cpp << " }\n"; - } - if (o.type == ObjectType::List) { - cpp << R"( if (set) { - QModelIndex index = createIndex(row, 0, row); - Q_EMIT dataChanged(index, index); - } - return set; -} - -)"; - } else { - cpp << R"( if (set) { - Q_EMIT dataChanged(index, index); - } - return set; -} - -)"; - } -} - -void writeCppModel(QTextStream& cpp, const Object& o) { - const QString lcname(snakeCase(o.name)); - QString indexDecl = ", int"; - QString index = ", index.row()"; - if (o.type == ObjectType::Tree) { - indexDecl = ", quintptr"; - index = ", index.internalId()"; - } - - cpp << "extern \"C\" {\n"; - for (auto ip: o.itemProperties) { - if (ip.type.isComplex()) { - cpp << QString(" void %2_data_%3(const %1::Private*%5, %4);\n") - .arg(o.name, lcname, snakeCase(ip.name), cGetType(ip.type), indexDecl); - } else { - cpp << QString(" %4 %2_data_%3(const %1::Private*%5);\n") - .arg(o.name, lcname, snakeCase(ip.name), cppSetType(ip), indexDecl); - } - if (ip.write) { - if (ip.type.name == "QString") { - cpp << QString(" bool %2_set_data_%3(%1::Private*%4, const ushort* s, int len);") - .arg(o.name, lcname, snakeCase(ip.name), indexDecl) << endl; - } else if (ip.type.name == "QByteArray") { - cpp << QString(" bool %2_set_data_%3(%1::Private*%4, const char* s, int len);") - .arg(o.name, lcname, snakeCase(ip.name), indexDecl) << endl; - } else { - cpp << QString(" bool %2_set_data_%3(%1::Private*%5, %4);") - .arg(o.name, lcname, snakeCase(ip.name), ip.type.cSetType, indexDecl) << endl; - } - if (ip.optional) { - cpp << QString(" bool %2_set_data_%3_none(%1::Private*%4);") - .arg(o.name, lcname, snakeCase(ip.name), indexDecl) << endl; - } - } - } - cpp << QString(" void %2_sort(%1::Private*, unsigned char column, Qt::SortOrder order = Qt::AscendingOrder);\n").arg(o.name, lcname); - if (o.type == ObjectType::List) { - cpp << QString(R"( - int %2_row_count(const %1::Private*); - bool %2_insert_rows(%1::Private*, int, int); - bool %2_remove_rows(%1::Private*, int, int); - bool %2_can_fetch_more(const %1::Private*); - void %2_fetch_more(%1::Private*); -} -int %1::columnCount(const QModelIndex &parent) const -{ - return (parent.isValid()) ? 0 : %3; -} - -bool %1::hasChildren(const QModelIndex &parent) const -{ - return rowCount(parent) > 0; -} - -int %1::rowCount(const QModelIndex &parent) const -{ - return (parent.isValid()) ? 0 : %2_row_count(m_d); -} - -bool %1::insertRows(int row, int count, const QModelIndex &) -{ - return %2_insert_rows(m_d, row, count); -} - -bool %1::removeRows(int row, int count, const QModelIndex &) -{ - return %2_remove_rows(m_d, row, count); -} - -QModelIndex %1::index(int row, int column, const QModelIndex &parent) const -{ - if (!parent.isValid() && row >= 0 && row < rowCount(parent) && column >= 0 && column < %3) { - return createIndex(row, column, (quintptr)row); - } - return QModelIndex(); -} - -QModelIndex %1::parent(const QModelIndex &) const -{ - return QModelIndex(); -} - -bool %1::canFetchMore(const QModelIndex &parent) const -{ - return (parent.isValid()) ? 0 : %2_can_fetch_more(m_d); -} - -void %1::fetchMore(const QModelIndex &parent) -{ - if (!parent.isValid()) { - %2_fetch_more(m_d); - } -} -void %1::updatePersistentIndexes() {} -)").arg(o.name, lcname, QString::number(o.columnCount)); - } else { - cpp << QString(R"( - int %2_row_count(const %1::Private*, option_quintptr); - bool %2_can_fetch_more(const %1::Private*, option_quintptr); - void %2_fetch_more(%1::Private*, option_quintptr); - quintptr %2_index(const %1::Private*, option_quintptr, int); - qmodelindex_t %2_parent(const %1::Private*, quintptr); - int %2_row(const %1::Private*, quintptr); - option_quintptr %2_check_row(const %1::Private*, quintptr, int); -} -int %1::columnCount(const QModelIndex &) const -{ - return %3; -} - -bool %1::hasChildren(const QModelIndex &parent) const -{ - return rowCount(parent) > 0; -} - -int %1::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid() && parent.column() != 0) { - return 0; - } - const option_quintptr rust_parent = { - parent.internalId(), - parent.isValid() - }; - return %2_row_count(m_d, rust_parent); -} - -bool %1::insertRows(int, int, const QModelIndex &) -{ - return false; // not supported yet -} - -bool %1::removeRows(int, int, const QModelIndex &) -{ - return false; // not supported yet -} - -QModelIndex %1::index(int row, int column, const QModelIndex &parent) const -{ - if (row < 0 || column < 0 || column >= %3) { - return QModelIndex(); - } - if (parent.isValid() && parent.column() != 0) { - return QModelIndex(); - } - if (row >= rowCount(parent)) { - return QModelIndex(); - } - const option_quintptr rust_parent = { - parent.internalId(), - parent.isValid() - }; - const quintptr id = %2_index(m_d, rust_parent, row); - return createIndex(row, column, id); -} - -QModelIndex %1::parent(const QModelIndex &index) const -{ - if (!index.isValid()) { - return QModelIndex(); - } - const qmodelindex_t parent = %2_parent(m_d, index.internalId()); - return parent.row >= 0 ?createIndex(parent.row, 0, parent.id) :QModelIndex(); -} - -bool %1::canFetchMore(const QModelIndex &parent) const -{ - if (parent.isValid() && parent.column() != 0) { - return false; - } - const option_quintptr rust_parent = { - parent.internalId(), - parent.isValid() - }; - return %2_can_fetch_more(m_d, rust_parent); -} - -void %1::fetchMore(const QModelIndex &parent) -{ - const option_quintptr rust_parent = { - parent.internalId(), - parent.isValid() - }; - %2_fetch_more(m_d, rust_parent); -} -void %1::updatePersistentIndexes() { - const auto from = persistentIndexList(); - auto to = from; - auto len = to.size(); - for (int i = 0; i < len; ++i) { - auto index = to.at(i); - auto row = %2_check_row(m_d, index.internalId(), index.row()); - if (row.some) { - to[i] = createIndex(row.value, index.column(), index.internalId()); - } else { - to[i] = QModelIndex(); - } - } - changePersistentIndexList(from, to); -} -)").arg(o.name, lcname, QString::number(o.columnCount)); - } - - cpp << QString(R"( -void %1::sort(int column, Qt::SortOrder order) -{ - %2_sort(m_d, column, order); -} -Qt::ItemFlags %1::flags(const QModelIndex &i) const -{ - auto flags = QAbstractItemModel::flags(i); -)").arg(o.name, lcname); - for (int col = 0; col < o.columnCount; ++col) { - if (isColumnWrite(o, col)) { - cpp << " if (i.column() == " << col << ") {\n"; - cpp << " flags |= Qt::ItemIsEditable;\n }\n"; - } - } - cpp << " return flags;\n}\n\n"; - for (auto ip: o.itemProperties) { - writeModelGetterSetter(cpp, index, ip, o); - } - cpp << QString(R"(QVariant %1::data(const QModelIndex &index, int role) const -{ - Q_ASSERT(rowCount(index.parent()) > index.row()); - switch (index.column()) { -)").arg(o.name); - - auto metaRoles = QMetaEnum::fromType(); - for (int col = 0; col < o.columnCount; ++col) { - cpp << QString(" case %1:\n").arg(col); - cpp << QString(" switch (role) {\n"); - for (int i = 0; i < o.itemProperties.size(); ++i) { - auto ip = o.itemProperties[i]; - auto roles = ip.roles.value(col); - if (col > 0 && roles.size() == 0) { - continue; - } - for (auto role: roles) { - cpp << QString(" case Qt::%1:\n").arg(metaRoles.valueToKey(role)); - } - cpp << QString(" case Qt::UserRole + %1:\n").arg(i); - auto ii = (o.type == ObjectType::List) ?".row()" :""; - if (ip.optional && !ip.type.isComplex()) { - cpp << QString(" return %1(index%2);\n").arg(ip.name, ii); - } else if (ip.optional) { - cpp << QString(" return cleanNullQVariant(QVariant::fromValue(%1(index%2)));\n").arg(ip.name, ii); - } else { - cpp << QString(" return QVariant::fromValue(%1(index%2));\n").arg(ip.name, ii); - } - } - cpp << " }\n break;\n"; - } - cpp << " }\n return QVariant();\n}\n\n"; - cpp << "int " << o.name << "::role(const char* name) const {\n"; - cpp << " auto names = roleNames();\n"; - cpp << " auto i = names.constBegin();\n"; - cpp << " while (i != names.constEnd()) {\n"; - cpp << " if (i.value() == name) {\n"; - cpp << " return i.key();\n"; - cpp << " }\n"; - cpp << " ++i;\n"; - cpp << " }\n"; - cpp << " return -1;\n"; - cpp << "}\n"; - cpp << "QHash " << o.name << "::roleNames() const {\n"; - cpp << " QHash names = QAbstractItemModel::roleNames();\n"; - for (int i = 0; i < o.itemProperties.size(); ++i) { - auto ip = o.itemProperties[i]; - cpp << " names.insert(Qt::UserRole + " << i << ", \"" << ip.name << "\");\n"; - } - cpp << " return names;\n"; - cpp << QString(R"(} -QVariant %1::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation != Qt::Horizontal) { - return QVariant(); - } - return m_headerData.value(qMakePair(section, (Qt::ItemDataRole)role), role == Qt::DisplayRole ?QString::number(section + 1) :QVariant()); -} - -bool %1::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) -{ - if (orientation != Qt::Horizontal) { - return false; - } - m_headerData.insert(qMakePair(section, (Qt::ItemDataRole)role), value); - return true; -} - -)").arg(o.name); - if (modelIsWritable(o)) { - cpp << QString("bool %1::setData(const QModelIndex &index, const QVariant &value, int role)\n{\n").arg(o.name); - for (int col = 0; col < o.columnCount; ++col) { - if (!isColumnWrite(o, col)) { - continue; - } - cpp << " if (index.column() == " << col << ") {\n"; - for (int i = 0; i < o.itemProperties.size(); ++i) { - auto ip = o.itemProperties[i]; - if (!ip.write) { - continue; - } - auto roles = ip.roles.value(col); - if (col > 0 && roles.size() == 0) { - continue; - } - cpp << " if ("; - for (auto role: roles) { - cpp << QString("role == Qt::%1 || ").arg(metaRoles.valueToKey(role)); - } - cpp << "role == Qt::UserRole + " << i << ") {\n"; - auto ii = (o.type == ObjectType::List) ?".row()" :""; - if (ip.optional && !ip.type.isComplex()) { - cpp << QString(" return set%1(index%2, value);\n") - .arg(upperInitial(ip.name), ii); - } else { - QString pre = ""; - if (ip.optional) { - pre = "!value.isValid() || value.isNull() ||"; - } - cpp << QString(" if (%2value.canConvert(qMetaTypeId<%1>())) {\n").arg(ip.type.name, pre); - cpp << QString(" return set%1(index%2, value.value<%3>());\n").arg(upperInitial(ip.name), ii, ip.type.name); - cpp << QString(" }\n"); - } - cpp << " }\n"; - } - cpp << " }\n"; - } - cpp << " return false;\n}\n\n"; - } -} - -void writeHeaderObject(QTextStream& h, const Object& o, const Configuration& conf) { - h << QString(R"( -class %1 : public %3 -{ - Q_OBJEC%2 -)").arg(o.name, "T", baseType(o)); - for (auto object: conf.objects) { - if (object.containsObject() && o.name != object.name) { - h << " friend class " << object.name << ";\n"; - } - } - h << R"(public: - class Private; -private: -)"; - for (auto p: o.properties) { - if (p.type.type == BindingType::Object) { - h << " " << p.type.name << "* const m_" << p.name << ";\n"; - } - } - h << R"( Private * m_d; - bool m_ownsPrivate; -)"; - for (auto p: o.properties) { - bool obj = p.type.type == BindingType::Object; - auto t = p.type.name; - if (p.optional && !p.type.isComplex()) { - t = "QVariant"; - } - h << QString(" Q_PROPERTY(%1 %2 READ %2 %3NOTIFY %2Changed FINAL)") - .arg(t + (obj ?"*" :""), - p.name, - p.write ? writeProperty(p.name) :"") - << endl; - } - h << QString(R"( explicit %1(bool owned, QObject *parent); -public: - explicit %1(QObject *parent = nullptr); - ~%1(); -)").arg(o.name); - for (auto p: o.properties) { - if (p.type.type == BindingType::Object) { - h << " const " << p.type.name << "* " << p.name << "() const;" << endl; - h << " " << p.type.name << "* " << p.name << "();" << endl; - } else { - auto t = p.type.name; - auto t2 = p.type.cppSetType; - if (p.optional && !p.type.isComplex()) { - t = "QVariant"; - t2 = "const QVariant&"; - } - h << " " << t << " " << p.name << "() const;" << endl; - if (p.write) { - h << " void set" << upperInitial(p.name) << "(" << t2 << " v);" << endl; - } - } - } - for (auto f: o.functions) { - h << " Q_INVOKABLE " << f.type.name << " " << f.name << "("; - for (auto a = f.args.begin(); a < f.args.end(); a++) { - if (a != f.args.begin()) { - h << ", "; - } - h << QString("%1 %2").arg(a->type.cppSetType, a->name); - } - h << QString(")%1;").arg(f.mut ? "" : " const"); - h << endl; - } - if (baseType(o) == "QAbstractItemModel") { - writeHeaderItemModel(h, o); - } - h << "Q_SIGNALS:" << endl; - for (auto p: o.properties) { - h << " void " << p.name << "Changed();" << endl; - } - h << "};" << endl; -} - -void constructorArgsDecl(QTextStream& cpp, const Object& o, const Configuration& conf) { - cpp << o.name << "*"; - for (auto p: o.properties) { - if (p.type.type == BindingType::Object) { - cpp << QString(", "); - constructorArgsDecl(cpp, conf.findObject(p.type.name), conf); - } else { - cpp << QString(", void (*)(%1*)").arg(o.name); - } - } - if (o.type == ObjectType::List) { - cpp << QString(R"(, - void (*)(const %1*), - void (*)(%1*), - void (*)(%1*), - void (*)(%1*, quintptr, quintptr), - void (*)(%1*), - void (*)(%1*), - void (*)(%1*, int, int), - void (*)(%1*), - void (*)(%1*, int, int, int), - void (*)(%1*), - void (*)(%1*, int, int), - void (*)(%1*))").arg(o.name); - } - if (o.type == ObjectType::Tree) { - cpp << QString(R"(, - void (*)(const %1*, option_quintptr), - void (*)(%1*), - void (*)(%1*), - void (*)(%1*, quintptr, quintptr), - void (*)(%1*), - void (*)(%1*), - void (*)(%1*, option_quintptr, int, int), - void (*)(%1*), - void (*)(%1*, option_quintptr, int, int, option_quintptr, int), - void (*)(%1*), - void (*)(%1*, option_quintptr, int, int), - void (*)(%1*))").arg(o.name); - } -} - -QString changedF(const Object& o, const Property& p) { - return lowerInitial(o.name) + upperInitial(p.name) + "Changed"; -} - -void constructorArgs(QTextStream& cpp, const QString& prefix, const Object& o, const Configuration& conf) { - const QString lcname(snakeCase(o.name)); - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - cpp << ", " << prefix << "m_" << p.name; - constructorArgs(cpp, "m_" + p.name + "->", - conf.findObject(p.type.name), conf); - } else { - cpp << ",\n " << changedF(o, p); - } - } - if (o.type == ObjectType::List) { - cpp << QString(R"(, - [](const %1* o) { - Q_EMIT o->newDataReady(QModelIndex()); - }, - [](%1* o) { - Q_EMIT o->layoutAboutToBeChanged(); - }, - [](%1* o) { - o->updatePersistentIndexes(); - Q_EMIT o->layoutChanged(); - }, - [](%1* o, quintptr first, quintptr last) { - o->dataChanged(o->createIndex(first, 0, first), - o->createIndex(last, %2, last)); - }, - [](%1* o) { - o->beginResetModel(); - }, - [](%1* o) { - o->endResetModel(); - }, - [](%1* o, int first, int last) { - o->beginInsertRows(QModelIndex(), first, last); - }, - [](%1* o) { - o->endInsertRows(); - }, - [](%1* o, int first, int last, int destination) { - o->beginMoveRows(QModelIndex(), first, last, QModelIndex(), destination); - }, - [](%1* o) { - o->endMoveRows(); - }, - [](%1* o, int first, int last) { - o->beginRemoveRows(QModelIndex(), first, last); - }, - [](%1* o) { - o->endRemoveRows(); - } -)").arg(o.name, QString::number(o.columnCount - 1)); - } - if (o.type == ObjectType::Tree) { - cpp << QString(R"(, - [](const %1* o, option_quintptr id) { - if (id.some) { - int row = %2_row(o->m_d, id.value); - Q_EMIT o->newDataReady(o->createIndex(row, 0, id.value)); - } else { - Q_EMIT o->newDataReady(QModelIndex()); - } - }, - [](%1* o) { - Q_EMIT o->layoutAboutToBeChanged(); - }, - [](%1* o) { - o->updatePersistentIndexes(); - Q_EMIT o->layoutChanged(); - }, - [](%1* o, quintptr first, quintptr last) { - quintptr frow = %2_row(o->m_d, first); - quintptr lrow = %2_row(o->m_d, first); - o->dataChanged(o->createIndex(frow, 0, first), - o->createIndex(lrow, %3, last)); - }, - [](%1* o) { - o->beginResetModel(); - }, - [](%1* o) { - o->endResetModel(); - }, - [](%1* o, option_quintptr id, int first, int last) { - if (id.some) { - int row = %2_row(o->m_d, id.value); - o->beginInsertRows(o->createIndex(row, 0, id.value), first, last); - } else { - o->beginInsertRows(QModelIndex(), first, last); - } - }, - [](%1* o) { - o->endInsertRows(); - }, - [](%1* o, option_quintptr sourceParent, int first, int last, option_quintptr destinationParent, int destination) { - QModelIndex s; - if (sourceParent.some) { - int row = %2_row(o->m_d, sourceParent.value); - s = o->createIndex(row, 0, sourceParent.value); - } - QModelIndex d; - if (destinationParent.some) { - int row = %2_row(o->m_d, destinationParent.value); - d = o->createIndex(row, 0, destinationParent.value); - } - o->beginMoveRows(s, first, last, d, destination); - }, - [](%1* o) { - o->endMoveRows(); - }, - [](%1* o, option_quintptr id, int first, int last) { - if (id.some) { - int row = %2_row(o->m_d, id.value); - o->beginRemoveRows(o->createIndex(row, 0, id.value), first, last); - } else { - o->beginRemoveRows(QModelIndex(), first, last); - } - }, - [](%1* o) { - o->endRemoveRows(); - } -)").arg(o.name, lcname, QString::number(o.columnCount - 1)); - } -} - -void writeFunctionCDecl(QTextStream& cpp, const Function& f, const QString& lcname, const Object& o) { - const QString lc(snakeCase(f.name)); - cpp << " "; - if (f.type.isComplex()) { - cpp << "void"; - } else { - cpp << f.type.name; - } - const QString name = QString("%1_%2").arg(lcname, lc); - cpp << QString(" %1(%3%2::Private*").arg(name, o.name, f.mut ? "" : "const "); - // write all the input arguments, for QString and QByteArray, write - // pointers to their content and the length - for (auto a = f.args.begin(); a < f.args.end(); a++) { - if (a->type.name == "QString") { - cpp << ", const ushort*, int"; - } else if (a->type.name == "QByteArray") { - cpp << ", const char*, int"; - } else { - cpp << ", " << a->type.name; - } - } - // If the return type is QString or QByteArray, append a pointer to the - // variable that will be set to the argument list. Also add a setter - // function. - if (f.type.name == "QString") { - cpp << ", QString*, qstring_set"; - } else if (f.type.name == "QByteArray") { - cpp << ", QByteArray*, qbytearray_set"; - } - cpp << ");\n"; -} - -void writeObjectCDecl(QTextStream& cpp, const Object& o, const Configuration& conf) { - const QString lcname(snakeCase(o.name)); - cpp << QString(" %1::Private* %2_new(").arg(o.name, lcname); - constructorArgsDecl(cpp, o, conf); - cpp << ");" << endl; - cpp << QString(" void %2_free(%1::Private*);").arg(o.name, lcname) - << endl; - for (const Property& p: o.properties) { - const QString base = QString("%1_%2").arg(lcname, snakeCase(p.name)); - if (p.type.type == BindingType::Object) { - cpp << QString(" %3::Private* %2_get(const %1::Private*);") - .arg(o.name, base, p.type.name) << endl; - } else if (p.type.isComplex()) { - cpp << QString(" void %2_get(const %1::Private*, %3);") - .arg(o.name, base, cGetType(p.type)) << endl; - } else if (p.optional) { - cpp << QString(" option_%3 %2_get(const %1::Private*);") - .arg(o.name, base, p.type.name) << endl; - } else { - cpp << QString(" %3 %2_get(const %1::Private*);") - .arg(o.name, base, p.type.name) << endl; - } - if (p.write) { - QString t = p.type.cSetType; - if (t == "qstring_t") { - t = "const ushort *str, int len"; - } else if (t == "qbytearray_t") { - t = "const char* bytes, int len"; - } - cpp << QString(" void %2_set(%1::Private*, %3);") - .arg(o.name, base, t) << endl; - if (p.optional) { - cpp << QString(" void %2_set_none(%1::Private*);") - .arg(o.name, base) << endl; - } - } - } - - for (const Function& f: o.functions) { - writeFunctionCDecl(cpp, f, lcname, o); - } -} - -void initializeMembersEmpty(QTextStream& cpp, const Object& o, const Configuration& conf) -{ - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - initializeMembersEmpty(cpp, conf.findObject(p.type.name), conf); - cpp << QString(" %1m_%2(new %3(false, this)),\n") - .arg(p.name, p.type.name); - } - } -} - -void initializeMembersZero(QTextStream& cpp, const Object& o) -{ - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - cpp << QString(" m_%1(new %2(false, this)),\n") - .arg(p.name, p.type.name); - } - } -} - -void initializeMembers(QTextStream& cpp, const QString& prefix, const Object& o, const Configuration& conf) -{ - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - cpp << QString(" %1m_%2->m_d = %3_%4_get(%1m_d);\n") - .arg(prefix, p.name, snakeCase(o.name), snakeCase(p.name)); - initializeMembers(cpp, "m_" + p.name + "->", - conf.findObject(p.type.name), conf); - } - } -} - -void connect(QTextStream& cpp, const QString& d, const Object& o, const Configuration& conf) { - for (auto p: o.properties) { - if (p.type.type == BindingType::Object) { - connect(cpp, d + "->m_" + p.name, conf.findObject(p.type.name), conf); - } - } - if (o.type != ObjectType::Object) { - cpp << QString(R"( connect(%2, &%1::newDataReady, %2, [this](const QModelIndex& i) { - %2->fetchMore(i); - }, Qt::QueuedConnection); -)").arg(o.name, d); - } -} - -void writeCppObject(QTextStream& cpp, const Object& o, const Configuration& conf) { - const QString lcname(snakeCase(o.name)); - cpp << QString("%1::%1(bool /*owned*/, QObject *parent):\n %2(parent),") - .arg(o.name, baseType(o)) << endl; - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - cpp << QString(" m_%1(new %2(false, this)),\n") - .arg(p.name, p.type.name); - } - } - cpp << " m_d(nullptr),\n m_ownsPrivate(false)\n{\n"; - if (o.type != ObjectType::Object) { - cpp << " initHeaderData();\n"; - } - cpp << QString("}\n\n%1::%1(QObject *parent):\n %2(parent),") - .arg(o.name, baseType(o)) << endl; - initializeMembersZero(cpp, o); - cpp << QString(" m_d(%1_new(this").arg(lcname); - constructorArgs(cpp, "", o, conf); - cpp << ")),\n m_ownsPrivate(true)\n{\n"; - initializeMembers(cpp, "", o, conf); - connect(cpp, "this", o, conf); - if (o.type != ObjectType::Object) { - cpp << " initHeaderData();\n"; - } - cpp << QString(R"(} - -%1::~%1() { - if (m_ownsPrivate) { - %2_free(m_d); - } -} -)").arg(o.name, lcname); - - if (o.type != ObjectType::Object) { - cpp << QString("void %1::initHeaderData() {\n").arg(o.name); - - for (int col = 0; col < o.columnCount; ++col) { - for (auto ip: o.itemProperties) { - auto roles = ip.roles.value(col); - if (roles.contains(Qt::DisplayRole)) { - cpp << QString(" m_headerData.insert(qMakePair(%1, Qt::DisplayRole), QVariant(\"%2\"));\n").arg(QString::number(col), ip.name); - } - } - } - cpp << "}\n"; - } - - for (const Property& p: o.properties) { - const QString base = QString("%1_%2").arg(lcname, snakeCase(p.name)); - if (p.type.type == BindingType::Object) { - cpp << QString(R"(const %3* %1::%2() const -{ - return m_%2; -} -%3* %1::%2() -{ - return m_%2; -} -)").arg(o.name, p.name, p.type.name); - } else if (p.type.isComplex()) { - cpp << QString("%3 %1::%2() const\n{\n").arg(o.name, p.name, p.type.name); - cpp << " " << p.type.name << " v;\n"; - cpp << " " << base << "_get(m_d, &v, set_" << p.type.name.toLower() - << ");\n"; - cpp << " return v;\n}\n"; - } else if (p.optional) { - cpp << QString("QVariant %1::%2() const\n{\n").arg(o.name, p.name); - cpp << " QVariant v;\n"; - cpp << QString(" auto r = %1_get(m_d);\n").arg(base); - cpp << " if (r.some) {\n"; - cpp << " v.setValue(r.value);\n"; - cpp << " }\n"; - cpp << " return r;\n"; - cpp << "}\n"; - } else { - cpp << QString("%3 %1::%2() const\n{\n").arg(o.name, p.name, p.type.name); - cpp << QString(" return %1_get(m_d);\n}\n").arg(base); - } - if (p.write) { - auto t = p.type.cppSetType; - if (p.optional && !p.type.isComplex()) { - t = "const QVariant&"; - } - cpp << "void " << o.name << "::set" << upperInitial(p.name) << "(" << t << " v) {" << endl; - if (p.optional) { - if (p.type.isComplex()) { - cpp << " if (v.isNull()) {" << endl; - } else { - cpp << QString(" if (v.isNull() || !v.canConvert<%1>()) {").arg(p.type.name) << endl; - } - cpp << QString(" %1_set_none(m_d);").arg(base) << endl; - cpp << QString(" } else {") << endl; - if (p.type.name == "QString") { - cpp << QString(" %1_set(m_d, reinterpret_cast(v.data()), v.size());").arg(base) << endl; - } else if (p.type.name == "QByteArray") { - cpp << QString(" %1_set(m_d, v.data(), v.size());").arg(base) << endl; - } else if (p.optional) { - cpp << QString(" %1_set(m_d, v.value<%2>());").arg(base, p.type.name) << endl; - } else { - cpp << QString(" %1_set(m_d, v);").arg(base) << endl; - } - cpp << QString(" }") << endl; - } else if (p.type.name == "QString") { - cpp << QString(" %1_set(m_d, reinterpret_cast(v.data()), v.size());").arg(base) << endl; - } else if (p.type.name == "QByteArray") { - cpp << QString(" %1_set(m_d, v.data(), v.size());").arg(base) << endl; - } else { - cpp << QString(" %1_set(m_d, v);").arg(base) << endl; - } - cpp << "}" << endl; - } - } - - for (const Function& f: o.functions) { - const QString base = QString("%1_%2") - .arg(lcname, snakeCase(f.name)); - cpp << QString("%1 %2::%3(").arg(f.type.name, o.name, f.name); - for (auto a = f.args.begin(); a < f.args.end(); a++) { - cpp << QString("%1 %2%3").arg(a->type.cppSetType, a->name, a + 1 < f.args.end() ? ", " : ""); - } - cpp << QString(")%1\n{\n").arg(f.mut ? "" : " const"); - QString argList; - for (auto a = f.args.begin(); a < f.args.end(); a++) { - if (a->type.name == "QString") { - argList.append(QString(", %1.utf16(), %1.size()").arg(a->name)); - } else if (a->type.name == "QByteArray") { - argList.append(QString(", %1.data(), %1.size()").arg(a->name)); - } else { - argList.append(QString(", %1").arg(a->name)); - } - } - if (f.type.name == "QString") { - cpp << QString(" %1 s;").arg(f.type.name) << endl; - cpp << QString(" %1(m_d%2, &s, set_qstring);") - .arg(base, argList) << endl; - cpp << " return s;" << endl; - } else if (f.type.name == "QByteArray") { - cpp << QString(" %1 s;").arg(f.type.name) << endl; - cpp << QString(" %1(m_d%2, &s, set_qbytearray);") - .arg(base, argList) << endl; - cpp << " return s;" << endl; - } else { - cpp << QString(" return %1(m_d%2);") - .arg(base, argList) << endl; - } - cpp << "}" << endl; - } -} - -void writeHeader(const Configuration& conf) { - DifferentFileWriter w(conf.hFile.absoluteFilePath()); - QTextStream h(&w.buffer); - const QString guard(conf.hFile.fileName().replace('.', '_').toUpper()); - h << QString(R"(/* generated by rust_qt_binding_generator */ -#ifndef %1 -#define %1 - -#include -#include - -)").arg(guard); - - for (auto object: conf.objects) { - h << "class " << object.name << ";\n"; - } - for (auto object: conf.objects) { - writeHeaderObject(h, object, conf); - } - - h << QString("#endif // %1\n").arg(guard); -} - -void writeCpp(const Configuration& conf) { - DifferentFileWriter w(conf.cppFile.absoluteFilePath()); - QTextStream cpp(&w.buffer); - cpp << QString(R"(/* generated by rust_qt_binding_generator */ -#include "%1" - -namespace { -)").arg(conf.hFile.fileName()); - for (auto option: conf.optionalTypes()) { - if (option != "QString" && option != "QByteArray") { - cpp << QString(R"( - struct option_%1 { - public: - %1 value; - bool some; - operator QVariant() const { - if (some) { - return QVariant::fromValue(value); - } - return QVariant(); - } - }; - static_assert(std::is_pod::value, "option_%1 must be a POD type."); -)").arg(option); - } - } - if (conf.types().contains("QString")) { - cpp << R"( - typedef void (*qstring_set)(QString* val, const char* utf8, int nbytes); - void set_qstring(QString* val, const char* utf8, int nbytes) { - *val = QString::fromUtf8(utf8, nbytes); - } -)"; - } - if (conf.types().contains("QByteArray")) { - cpp << R"( - typedef void (*qbytearray_set)(QByteArray* val, const char* bytes, int nbytes); - void set_qbytearray(QByteArray* v, const char* bytes, int nbytes) { - if (v->isNull() && nbytes == 0) { - *v = QByteArray(bytes, nbytes); - } else { - v->truncate(0); - v->append(bytes, nbytes); - } - } -)"; - } - if (conf.hasListOrTree()) { - cpp << R"( - struct qmodelindex_t { - int row; - quintptr id; - }; - inline QVariant cleanNullQVariant(const QVariant& v) { - return (v.isNull()) ?QVariant() :v; - } -)"; - } - - for (auto o: conf.objects) { - for (auto p: o.properties) { - if (p.type.type == BindingType::Object) { - continue; - } - cpp << " inline void " << changedF(o, p) << "(" << o.name << "* o)\n"; - cpp << " {\n Q_EMIT o->" << p.name << "Changed();\n }\n"; - } - } - cpp << "}\n"; - - for (auto object: conf.objects) { - if (object.type != ObjectType::Object) { - writeCppModel(cpp, object); - } - - cpp << "extern \"C\" {\n"; - writeObjectCDecl(cpp, object, conf); - cpp << "};\n" << endl; - } - - for (auto object: conf.objects) { - writeCppObject(cpp, object, conf); - } -} diff --git a/src/cpp.h b/src/cpp.h deleted file mode 100644 index 1a27c8d..0000000 --- a/src/cpp.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -struct Configuration; -void writeHeader(const Configuration& conf); -void writeCpp(const Configuration& conf); diff --git a/src/cpp.rs b/src/cpp.rs new file mode 100644 index 0000000..8f82f09 --- /dev/null +++ b/src/cpp.rs @@ -0,0 +1,1518 @@ +use std::io::{Result, Write}; +use configuration::{Config, Function, Object, ObjectType, ItemProperty, Type}; +use util::{snake_case, write_if_different}; + +fn property_type(p: &ItemProperty) -> String { + if p.optional && !p.item_property_type.is_complex() { + "QVariant".into() + } else { + p.type_name().to_string() + } +} + +fn upper_initial(name: &str) -> String { + format!("{}{}", &name[..1].to_uppercase(), &name[1..]) +} + +fn lower_initial(name: &str) -> String { + format!("{}{}", &name[..1].to_lowercase(), &name[1..]) +} + +fn write_property(name: &str) -> String { + format!("WRITE set{} ", upper_initial(name)) +} + +fn base_type(o: &Object) -> &str { + if o.object_type != ObjectType::Object { + return "QAbstractItemModel"; + } + "QObject" +} + +fn model_is_writable(o: &Object) -> bool { + let mut write = false; + for p in o.item_properties.values() { + write |= p.write; + } + write +} + +fn write_header_item_model(h: &mut Vec, o: &Object) -> Result<()> { + writeln!( + h, + " + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; + int role(const char* name) const; + QHash roleNames() const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; + Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;" + ); + if model_is_writable(o) { + writeln!( + h, + " bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;" + ); + } + for (name, ip) in &o.item_properties { + let r = property_type(ip); + let rw = if r == "QVariant" || ip.item_property_type.is_complex() { + format!("const {}&", r) + } else { + r.clone() + }; + if o.object_type == ObjectType::List { + writeln!(h, " Q_INVOKABLE {} {}(int row) const;", r, name); + if ip.write { + writeln!( + h, + " Q_INVOKABLE bool set{}(int row, {} value);", + upper_initial(name), + rw + ); + } + } else { + writeln!( + h, + " Q_INVOKABLE {} {}(const QModelIndex& index) const;", + r, + name + ); + if ip.write { + writeln!( + h, + " Q_INVOKABLE bool set{}(const QModelIndex& index, {} value);", + upper_initial(name), + rw + ); + } + } + } + writeln!( + h, + " +Q_SIGNALS: + // new data is ready to be made available to the model with fetchMore() + void newDataReady(const QModelIndex &parent) const; +private: + QHash, QVariant> m_headerData; + void initHeaderData(); + void updatePersistentIndexes();" + ); + Ok(()) +} + +fn write_header_object(h: &mut Vec, o: &Object, conf: &Config) -> Result<()> { + writeln!( + h, + " +class {} : public {} +{{ + Q_OBJECT", + o.name, + base_type(o) + ); + for object in conf.objects.values() { + if object.contains_object() && o.name != object.name { + writeln!(h, " friend class {};", object.name); + } + } + writeln!( + h, + "public: + class Private; +private:" + ); + for (name, p) in &o.properties { + if p.is_object() { + writeln!(h, " {}* const m_{};", p.type_name(), name); + } + } + writeln!( + h, + " Private * m_d; + bool m_ownsPrivate;" + ); + for (name, p) in &o.properties { + let mut t = if p.optional && !p.is_complex() { + "QVariant" + } else { + p.type_name() + }.to_string(); + if p.is_object() { + t.push_str("*"); + } + writeln!( + h, + " Q_PROPERTY({0} {1} READ {1} {2}NOTIFY {1}Changed FINAL)", + t, + name, + if p.write { + write_property(name) + } else { + String::new() + } + ); + } + writeln!( + h, + " explicit {}(bool owned, QObject *parent); +public: + explicit {0}(QObject *parent = nullptr); + ~{0}();", + o.name + ); + for (name, p) in &o.properties { + if p.is_object() { + writeln!(h, " const {}* {}() const;", p.type_name(), name); + writeln!(h, " {}* {}();", p.type_name(), name); + } else { + let (t, t2) = if p.optional && !p.is_complex() { + ("QVariant", "const QVariant&") + } else { + (p.type_name(), p.property_type.cpp_set_type()) + }; + writeln!(h, " {} {}() const;", t, name); + if p.write { + writeln!(h, " void set{}({} v);", upper_initial(name), t2); + } + } + } + for (name, f) in &o.functions { + write!(h, " Q_INVOKABLE {} {}(", f.return_type.name(), name); + for (i, a) in f.arguments.iter().enumerate() { + if i != 0 { + write!(h, ", "); + } + write!(h, "{} {}", a.argument_type.cpp_set_type(), a.name); + } + writeln!(h, "){};", if f.mutable { "" } else { " const" }); + } + if base_type(o) == "QAbstractItemModel" { + write_header_item_model(h, o)?; + } + writeln!(h, "Q_SIGNALS:"); + for name in o.properties.keys() { + writeln!(h, " void {}Changed();", name); + } + writeln!(h, "}};"); + Ok(()) +} + +fn is_column_write(o: &Object, col: usize) -> bool { + for ip in o.item_properties.values() { + if ip.write && (col == 0 || (ip.roles.len() > col && !ip.roles[col].is_empty())) { + return true; + } + } + false +} + +fn write_function_c_decl( + w: &mut Vec, + (name, f): (&String, &Function), + lcname: &str, + o: &Object, +) -> Result<()> { + let lc = snake_case(name); + write!(w, " "); + if f.return_type.is_complex() { + write!(w, "void"); + } else { + write!(w, "{}", f.type_name()); + } + let name = format!("{}_{}", lcname, lc); + write!( + w, + " {}({}{}::Private*", + name, + if f.mutable { "" } else { "const " }, + o.name + ); + + // write all the input arguments, for QString and QByteArray, write + // pointers to their content and the length + for a in &f.arguments { + if a.type_name() == "QString" { + write!(w, ", const ushort*, int"); + } else if a.type_name() == "QByteArray" { + write!(w, ", const char*, int"); + } else { + write!(w, ", {}", a.type_name()); + } + } + // If the return type is QString or QByteArray, append a pointer to the + // variable that will be set to the argument list. Also add a setter + // function. + if f.return_type.name() == "QString" { + write!(w, ", QString*, qstring_set"); + } else if f.return_type.name() == "QByteArray" { + write!(w, ", QByteArray*, qbytearray_set"); + } + writeln!(w, ");"); + Ok(()) +} + +fn write_object_c_decl(w: &mut Vec, o: &Object, conf: &Config) -> Result<()> { + let lcname = snake_case(&o.name); + write!(w, " {}::Private* {}_new(", o.name, lcname); + constructor_args_decl(w, o, conf)?; + writeln!(w, ");"); + writeln!(w, " void {}_free({}::Private*);", lcname, o.name); + for (name, p) in &o.properties { + let base = format!("{}_{}", lcname, snake_case(name)); + if p.is_object() { + writeln!( + w, + " {}::Private* {}_get(const {}::Private*);", + p.type_name(), + base, + o.name + ); + } else if p.is_complex() { + writeln!( + w, + " void {}_get(const {}::Private*, {});", + base, + o.name, + p.c_get_type() + ); + } else if p.optional { + writeln!( + w, + " option_{} {}_get(const {}::Private*);", + p.type_name(), + base, + o.name + ); + } else { + writeln!( + w, + " {} {}_get(const {}::Private*);", + p.type_name(), + base, + o.name + ); + } + if p.write { + let mut t = p.property_type.c_set_type(); + if t == "qstring_t" { + t = "const ushort *str, int len"; + } else if t == "qbytearray_t" { + t = "const char* bytes, int len"; + } + writeln!(w, " void {}_set({}::Private*, {});", base, o.name, t); + if p.optional { + writeln!(w, " void {}_set_none({}::Private*);", base, o.name); + } + } + } + for f in &o.functions { + write_function_c_decl(w, f, &lcname, o)?; + } + Ok(()) +} + +fn initialize_members_zero(w: &mut Vec, o: &Object) { + for (name, p) in &o.properties { + if p.is_object() { + writeln!(w, " m_{}(new {}(false, this)),", name, p.type_name()); + } + } +} + +fn initialize_members(w: &mut Vec, prefix: &str, o: &Object, conf: &Config) { + for (name, p) in &o.properties { + if let Type::Object(object) = &p.property_type { + writeln!( + w, + " {}m_{}->m_d = {}_{}_get({0}m_d);", + prefix, + name, + snake_case(&o.name), + snake_case(name) + ); + initialize_members(w, &format!("m_{}->", name), object, conf); + } + } +} + +fn connect(w: &mut Vec, d: &str, o: &Object, conf: &Config) { + for (name, p) in &o.properties { + if let Type::Object(object) = &p.property_type { + connect(w, &format!("{}->m_{}", d, name), object, conf); + } + } + if o.object_type != ObjectType::Object { + writeln!( + w, + " connect({}, &{1}::newDataReady, {0}, [this](const QModelIndex& i) {{ + {0}->fetchMore(i); + }}, Qt::QueuedConnection);", + d, + o.name + ); + } +} + +fn write_cpp_object(w: &mut Vec, o: &Object, conf: &Config) -> Result<()> { + let lcname = snake_case(&o.name); + writeln!( + w, + "{}::{0}(bool /*owned*/, QObject *parent): + {}(parent),", + o.name, + base_type(o) + ); + initialize_members_zero(w, o); + writeln!( + w, + " m_d(nullptr), + m_ownsPrivate(false) +{{" + ); + if o.object_type != ObjectType::Object { + writeln!(w, " initHeaderData();"); + } + writeln!( + w, + "}} + +{}::{0}(QObject *parent): + {}(parent),", + o.name, + base_type(o) + ); + initialize_members_zero(w, o); + write!(w, " m_d({}_new(this", lcname); + constructor_args(w, "", o, conf); + writeln!( + w, + ")), + m_ownsPrivate(true) +{{" + ); + initialize_members(w, "", o, conf); + connect(w, "this", o, conf); + if o.object_type != ObjectType::Object { + writeln!(w, " initHeaderData();"); + } + writeln!( + w, + "}} + +{}::~{0}() {{ + if (m_ownsPrivate) {{ + {1}_free(m_d); + }} +}}", + o.name, + lcname + ); + if o.object_type != ObjectType::Object { + writeln!(w, "void {}::initHeaderData() {{", o.name); + for col in 0..o.column_count { + for (name, ip) in &o.item_properties { + let empty = Vec::new(); + let roles = ip.roles.get(col).unwrap_or(&empty); + if roles.contains(&"display".to_string()) { + writeln!( + w, + " m_headerData.insert(qMakePair({}, Qt::DisplayRole), QVariant(\"{}\"));", + col, + name + ); + } + } + } + writeln!(w, "}}"); + } + + for (name, p) in &o.properties { + let base = format!("{}_{}", lcname, snake_case(name)); + if p.is_object() { + writeln!( + w, + "const {}* {}::{}() const +{{ + return m_{2}; +}} +{0}* {1}::{2}() +{{ + return m_{2}; +}}", + p.type_name(), + o.name, + name + ); + } else if p.is_complex() { + writeln!( + w, + "{} {}::{}() const +{{ + {0} v; + {3}_get(m_d, &v, set_{4}); + return v; +}}", + p.type_name(), + o.name, + name, + base, + p.type_name().to_lowercase() + ); + } else if p.optional { + writeln!( + w, + "QVariant {}::{}() const +{{ + QVariant v; + auto r = {2}_get(m_d); + if (r.some) {{ + v.setValue(r.value); + }} + return r; +}}", + o.name, + name, + base + ); + } else { + writeln!( + w, + "{} {}::{}() const +{{ + return {}_get(m_d); +}}", + p.type_name(), + o.name, + name, + base + ); + } + if p.write { + let t = if p.optional && !p.is_complex() { + "const QVariant&" + } else { + p.property_type.cpp_set_type() + }; + writeln!(w, "void {}::set{}({} v) {{", o.name, upper_initial(name), t); + if p.optional { + if p.is_complex() { + writeln!(w, " if (v.isNull()) {{"); + } else { + writeln!( + w, + " if (v.isNull() || !v.canConvert<{}>()) {{", + p.type_name() + ); + } + writeln!(w, " {}_set_none(m_d);", base); + writeln!(w, " }} else {{"); + if p.type_name() == "QString" { + writeln!( + w, + " {}_set(m_d, reinterpret_cast(v.data()), v.size());", + base + ); + } else if p.type_name() == "QByteArray" { + writeln!(w, " {}_set(m_d, v.data(), v.size());", base); + } else if p.optional { + writeln!( + w, + " {}_set(m_d, v.value<{}>());", + base, + p.type_name() + ); + } else { + writeln!(w, " {}_set(m_d, v);", base); + } + writeln!(w, " }}"); + } else if p.type_name() == "QString" { + writeln!( + w, + " {}_set(m_d, reinterpret_cast(v.data()), v.size());", + base + ); + } else if p.type_name() == "QByteArray" { + writeln!(w, " {}_set(m_d, v.data(), v.size());", base); + } else { + writeln!(w, " {}_set(m_d, v);", base); + } + writeln!(w, "}}"); + } + } + + for (name, f) in &o.functions { + let base = format!("{}_{}", lcname, snake_case(name)); + write!(w, "{} {}::{}(", f.type_name(), o.name, name); + for (i, a) in f.arguments.iter().enumerate() { + write!( + w, + "{} {}{}", + a.argument_type.cpp_set_type(), + a.name, + if i + 1 < f.arguments.len() { ", " } else { "" } + ); + } + writeln!(w, "){}\n{{", if f.mutable { "" } else { " const" }); + let mut arg_list = String::new(); + for a in &f.arguments { + if a.type_name() == "QString" { + arg_list.push_str(&format!(", {}.utf16(), {0}.size()", a.name)); + } else if a.type_name() == "QByteArray" { + arg_list.push_str(&format!(", {}.data(), {0}.size()", a.name)); + } else { + arg_list.push_str(&format!(", {}", a.name)); + } + } + if f.return_type.name() == "QString" { + writeln!( + w, + " {} s; + {}(m_d{}, &s, set_qstring); + return s;", + f.type_name(), + base, + arg_list + ); + } else if f.return_type.name() == "QByteArray" { + writeln!( + w, + " {} s; + {}(m_d{}, &s, set_qbytearray); + return s;", + f.type_name(), + base, + arg_list + ); + } else { + writeln!(w, " return {}(m_d{});", base, arg_list); + } + writeln!(w, "}}"); + } + Ok(()) +} + +fn role_name(role: &str) -> String { + match role { + "display" => "DisplayRole".into(), + "decoration" => "DecorationRole".into(), + "edit" => "EditRole".into(), + "toolTip" => "ToolTipRole".into(), + "statustip" => "StatusTipRole".into(), + "whatsthis" => "WhatsThisRole".into(), + _ => panic!("Unknown role {}", role), + } +} + +fn write_model_getter_setter( + w: &mut Vec, + index: &str, + name: &str, + ip: &ItemProperty, + o: &Object, +) -> Result<()> { + let lcname = snake_case(&o.name); + let mut idx = index; + + // getter + let mut r = property_type(ip); + if o.object_type == ObjectType::List { + idx = ", row"; + writeln!(w, "{} {}::{}(int row) const\n{{", r, o.name, name); + } else { + writeln!( + w, + "{} {}::{}(const QModelIndex& index) const\n{{", + r, + o.name, + name + ); + } + if ip.type_name() == "QString" { + writeln!(w, " QString s;"); + writeln!( + w, + " {}_data_{}(m_d{}, &s, set_{});", + lcname, + snake_case(name), + idx, + ip.type_name().to_lowercase() + ); + writeln!(w, " return s;"); + } else if ip.type_name() == "QByteArray" { + writeln!(w, " QByteArray b;"); + writeln!( + w, + " {}_data_{}(m_d{}, &b, set_{});", + lcname, + snake_case(name), + idx, + ip.type_name().to_lowercase() + ); + writeln!(w, " return b;"); + } else if ip.optional { + writeln!(w, " QVariant v;"); + writeln!( + w, + " v = {}_data_{}(m_d{});", + lcname, + snake_case(name), + idx + ); + writeln!(w, " return v;"); + } else { + writeln!( + w, + " return {}_data_{}(m_d{});", + lcname, + snake_case(name), + idx + ); + } + writeln!(w, "}}\n"); + if !ip.write { + return Ok(()); + } + + //setter + if r == "QVariant" || ip.is_complex() { + r = format!("const {}&", r); + } + if o.object_type == ObjectType::List { + idx = ", row"; + writeln!( + w, + "bool {}::set{}(int row, {} value)\n{{", + o.name, + upper_initial(name), + r + ); + } else { + writeln!( + w, + "bool {}::set{}(const QModelIndex& index, {} value)\n{{", + o.name, + upper_initial(name), + r + ); + } + writeln!(w, " bool set = false;"); + if ip.optional { + let mut test = "value.isNull()".to_string(); + if !ip.is_complex() { + test += " || !value.isValid()"; + } + writeln!(w, " if ({}) {{", test); + writeln!( + w, + " set = {}_set_data_{}_none(m_d{});", + lcname, + snake_case(name), + idx + ); + writeln!(w, " }} else {{"); + } + if ip.optional && !ip.is_complex() { + writeln!( + w, + " if (!value.canConvert(qMetaTypeId<{}>())) {{ + return false; + }}", + ip.type_name() + ); + writeln!( + w, + " set = {}_set_data_{}(m_d{}, value.value<{}>());", + lcname, + snake_case(name), + idx, + ip.type_name() + ); + } else { + let mut val = "value"; + if ip.is_complex() { + if ip.type_name() == "QString" { + val = "value.utf16(), value.length()"; + } else { + val = "value.data(), value.length()"; + } + } + writeln!( + w, + " set = {}_set_data_{}(m_d{}, {});", + lcname, + snake_case(name), + idx, + val + ); + } + if ip.optional { + writeln!(w, " }}"); + } + if o.object_type == ObjectType::List { + writeln!( + w, + " if (set) {{ + QModelIndex index = createIndex(row, 0, row); + Q_EMIT dataChanged(index, index); + }} + return set; +}} +" + ); + } else { + writeln!( + w, + " if (set) {{ + Q_EMIT dataChanged(index, index); + }} + return set; +}} +" + ); + } + Ok(()) +} + +fn write_cpp_model(w: &mut Vec, o: &Object) -> Result<()> { + let lcname = snake_case(&o.name); + let (index_decl, index) = if o.object_type == ObjectType::Tree { + (", quintptr", ", index.internalId()") + } else { + (", int", ", index.row()") + }; + writeln!(w, "extern \"C\" {{"); + + for (name, ip) in &o.item_properties { + if ip.is_complex() { + writeln!( + w, + " void {}_data_{}(const {}::Private*{}, {});", + lcname, + snake_case(name), + o.name, + index_decl, + ip.c_get_type() + ); + } else { + writeln!( + w, + " {} {}_data_{}(const {}::Private*{});", + ip.cpp_set_type(), + lcname, + snake_case(name), + o.name, + index_decl + ); + } + if ip.write { + let a = format!(" bool {}_set_data_{}", lcname, snake_case(name)); + let b = format!("({}::Private*{}", o.name, index_decl); + if ip.type_name() == "QString" { + writeln!(w, "{}{}, const ushort* s, int len);", a, b); + } else if ip.type_name() == "QByteArray" { + writeln!(w, "{}{}, const char* s, int len);", a, b); + } else { + writeln!(w, "{}{}, {});", a, b, ip.c_set_type()); + } + if ip.optional { + writeln!(w, "{}_none{});", a, b); + } + } + } + writeln!( + w, + " void {}_sort({}::Private*, unsigned char column, Qt::SortOrder order = Qt::AscendingOrder);", + lcname, + o.name + ); + if o.object_type == ObjectType::List { + writeln!( + w, + " + int {1}_row_count(const {0}::Private*); + bool {1}_insert_rows({0}::Private*, int, int); + bool {1}_remove_rows({0}::Private*, int, int); + bool {1}_can_fetch_more(const {0}::Private*); + void {1}_fetch_more({0}::Private*); +}} +int {0}::columnCount(const QModelIndex &parent) const +{{ + return (parent.isValid()) ? 0 : {2}; +}} + +bool {0}::hasChildren(const QModelIndex &parent) const +{{ + return rowCount(parent) > 0; +}} + +int {0}::rowCount(const QModelIndex &parent) const +{{ + return (parent.isValid()) ? 0 : {1}_row_count(m_d); +}} + +bool {0}::insertRows(int row, int count, const QModelIndex &) +{{ + return {1}_insert_rows(m_d, row, count); +}} + +bool {0}::removeRows(int row, int count, const QModelIndex &) +{{ + return {1}_remove_rows(m_d, row, count); +}} + +QModelIndex {0}::index(int row, int column, const QModelIndex &parent) const +{{ + if (!parent.isValid() && row >= 0 && row < rowCount(parent) && column >= 0 && column < {2}) {{ + return createIndex(row, column, (quintptr)row); + }} + return QModelIndex(); +}} + +QModelIndex {0}::parent(const QModelIndex &) const +{{ + return QModelIndex(); +}} + +bool {0}::canFetchMore(const QModelIndex &parent) const +{{ + return (parent.isValid()) ? 0 : {1}_can_fetch_more(m_d); +}} + +void {0}::fetchMore(const QModelIndex &parent) +{{ + if (!parent.isValid()) {{ + {1}_fetch_more(m_d); + }} +}} +void {0}::updatePersistentIndexes() {{}}", + o.name, + lcname, + o.column_count + ); + } else { + writeln!( + w, + " + int {1}_row_count(const {0}::Private*, option_quintptr); + bool {1}_can_fetch_more(const {0}::Private*, option_quintptr); + void {1}_fetch_more({0}::Private*, option_quintptr); + quintptr {1}_index(const {0}::Private*, option_quintptr, int); + qmodelindex_t {1}_parent(const {0}::Private*, quintptr); + int {1}_row(const {0}::Private*, quintptr); + option_quintptr {1}_check_row(const {0}::Private*, quintptr, int); +}} +int {0}::columnCount(const QModelIndex &) const +{{ + return {2}; +}} + +bool {0}::hasChildren(const QModelIndex &parent) const +{{ + return rowCount(parent) > 0; +}} + +int {0}::rowCount(const QModelIndex &parent) const +{{ + if (parent.isValid() && parent.column() != 0) {{ + return 0; + }} + const option_quintptr rust_parent = {{ + parent.internalId(), + parent.isValid() + }}; + return {1}_row_count(m_d, rust_parent); +}} + +bool {0}::insertRows(int, int, const QModelIndex &) +{{ + return false; // not supported yet +}} + +bool {0}::removeRows(int, int, const QModelIndex &) +{{ + return false; // not supported yet +}} + +QModelIndex {0}::index(int row, int column, const QModelIndex &parent) const +{{ + if (row < 0 || column < 0 || column >= {2}) {{ + return QModelIndex(); + }} + if (parent.isValid() && parent.column() != 0) {{ + return QModelIndex(); + }} + if (row >= rowCount(parent)) {{ + return QModelIndex(); + }} + const option_quintptr rust_parent = {{ + parent.internalId(), + parent.isValid() + }}; + const quintptr id = {1}_index(m_d, rust_parent, row); + return createIndex(row, column, id); +}} + +QModelIndex {0}::parent(const QModelIndex &index) const +{{ + if (!index.isValid()) {{ + return QModelIndex(); + }} + const qmodelindex_t parent = {1}_parent(m_d, index.internalId()); + return parent.row >= 0 ?createIndex(parent.row, 0, parent.id) :QModelIndex(); +}} + +bool {0}::canFetchMore(const QModelIndex &parent) const +{{ + if (parent.isValid() && parent.column() != 0) {{ + return false; + }} + const option_quintptr rust_parent = {{ + parent.internalId(), + parent.isValid() + }}; + return {1}_can_fetch_more(m_d, rust_parent); +}} + +void {0}::fetchMore(const QModelIndex &parent) +{{ + const option_quintptr rust_parent = {{ + parent.internalId(), + parent.isValid() + }}; + {1}_fetch_more(m_d, rust_parent); +}} +void {0}::updatePersistentIndexes() {{ + const auto from = persistentIndexList(); + auto to = from; + auto len = to.size(); + for (int i = 0; i < len; ++i) {{ + auto index = to.at(i); + auto row = {1}_check_row(m_d, index.internalId(), index.row()); + if (row.some) {{ + to[i] = createIndex(row.value, index.column(), index.internalId()); + }} else {{ + to[i] = QModelIndex(); + }} + }} + changePersistentIndexList(from, to); +}}", + o.name, + lcname, + o.column_count + ); + } + writeln!( + w, + " +void {0}::sort(int column, Qt::SortOrder order) +{{ + {1}_sort(m_d, column, order); +}} +Qt::ItemFlags {0}::flags(const QModelIndex &i) const +{{ + auto flags = QAbstractItemModel::flags(i);", + o.name, + lcname + ); + for col in 0..o.column_count { + if is_column_write(o, col) { + writeln!(w, " if (i.column() == {}) {{", col); + writeln!(w, " flags |= Qt::ItemIsEditable;\n }}"); + } + } + writeln!(w, " return flags;\n}}\n"); + for ip in &o.item_properties { + write_model_getter_setter(w, index, ip.0, ip.1, o)?; + } + writeln!( + w, + "QVariant {}::data(const QModelIndex &index, int role) const +{{ + Q_ASSERT(rowCount(index.parent()) > index.row()); + switch (index.column()) {{", + o.name + ); + + for col in 0..o.column_count { + writeln!(w, " case {}:", col); + writeln!(w, " switch (role) {{"); + for (i, (name, ip)) in o.item_properties.iter().enumerate() { + let empty = Vec::new(); + let roles = ip.roles.get(col).unwrap_or(&empty); + if col > 0 && roles.is_empty() { + continue; + } + for role in roles { + writeln!(w, " case Qt::{}:", role_name(role)); + } + writeln!(w, " case Qt::UserRole + {}:", i); + let ii = if o.object_type == ObjectType::List { + ".row()" + } else { + "" + }; + if ip.optional && !ip.is_complex() { + writeln!(w, " return {}(index{});", name, ii); + } else if ip.optional { + writeln!( + w, + " return cleanNullQVariant(QVariant::fromValue({}(index{})));", + name, + ii + ); + } else { + writeln!( + w, + " return QVariant::fromValue({}(index{}));", + name, + ii + ); + } + } + writeln!(w, " }}\n break;"); + } + writeln!( + w, + " }} + return QVariant(); +}} + +int {}::role(const char* name) const {{ + auto names = roleNames(); + auto i = names.constBegin(); + while (i != names.constEnd()) {{ + if (i.value() == name) {{ + return i.key(); + }} + ++i; + }} + return -1; +}} +QHash {0}::roleNames() const {{ + QHash names = QAbstractItemModel::roleNames();", + o.name + ); + for (i, (name, _)) in o.item_properties.iter().enumerate() { + writeln!(w, " names.insert(Qt::UserRole + {}, \"{}\");", i, name); + } + writeln!( + w, + " return names; +}} +QVariant {0}::headerData(int section, Qt::Orientation orientation, int role) const +{{ + if (orientation != Qt::Horizontal) {{ + return QVariant(); + }} + return m_headerData.value(qMakePair(section, (Qt::ItemDataRole)role), role == Qt::DisplayRole ?QString::number(section + 1) :QVariant()); +}} + +bool {0}::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{{ + if (orientation != Qt::Horizontal) {{ + return false; + }} + m_headerData.insert(qMakePair(section, (Qt::ItemDataRole)role), value); + return true; +}} +", + o.name + ); + if model_is_writable(o) { + writeln!( + w, + "bool {}::setData(const QModelIndex &index, const QVariant &value, int role)\n{{", + o.name + ); + for col in 0..o.column_count { + if !is_column_write(o, col) { + continue; + } + writeln!(w, " if (index.column() == {}) {{", col); + for (i, (name, ip)) in o.item_properties.iter().enumerate() { + if !ip.write { + continue; + } + let empty = Vec::new(); + let roles = ip.roles.get(col).unwrap_or(&empty); + if col > 0 && roles.is_empty() { + continue; + } + write!(w, " if ("); + for role in roles { + write!(w, "role == Qt::{} || ", role_name(role)); + } + writeln!(w, "role == Qt::UserRole + {}) {{", i); + let ii = if o.object_type == ObjectType::List { + ".row()" + } else { + "" + }; + if ip.optional && !ip.is_complex() { + writeln!( + w, + " return set{}(index{}, value);", + upper_initial(name), + ii + ); + } else { + let pre = if ip.optional { + "!value.isValid() || value.isNull() ||" + } else { + "" + }; + writeln!( + w, + " if ({}value.canConvert(qMetaTypeId<{}>())) {{", + pre, + ip.type_name() + ); + writeln!( + w, + " return set{}(index{}, value.value<{}>());", + upper_initial(name), + ii, + ip.type_name() + ); + writeln!(w, " }}"); + } + writeln!(w, " }}"); + } + writeln!(w, " }}"); + } + writeln!(w, " return false;\n}}\n"); + } + Ok(()) +} + +fn constructor_args_decl(w: &mut Vec, o: &Object, conf: &Config) -> Result<()> { + write!(w, "{}*", o.name); + for p in o.properties.values() { + if let Type::Object(object) = &p.property_type { + write!(w, ", "); + constructor_args_decl(w, object, conf)?; + } else { + write!(w, ", void (*)({}*)", o.name); + } + } + if o.object_type == ObjectType::List { + write!( + w, + ", + void (*)(const {}*), + void (*)({0}*), + void (*)({0}*), + void (*)({0}*, quintptr, quintptr), + void (*)({0}*), + void (*)({0}*), + void (*)({0}*, int, int), + void (*)({0}*), + void (*)({0}*, int, int, int), + void (*)({0}*), + void (*)({0}*, int, int), + void (*)({0}*)", + o.name + ); + } + if o.object_type == ObjectType::Tree { + write!( + w, + ", + void (*)(const {0}*, option_quintptr), + void (*)({0}*), + void (*)({0}*), + void (*)({0}*, quintptr, quintptr), + void (*)({0}*), + void (*)({0}*), + void (*)({0}*, option_quintptr, int, int), + void (*)({0}*), + void (*)({0}*, option_quintptr, int, int, option_quintptr, int), + void (*)({0}*), + void (*)({0}*, option_quintptr, int, int), + void (*)({0}*)", + o.name + ); + } + Ok(()) +} + +fn changed_f(o: &Object, p_name: &str) -> String { + lower_initial(&o.name) + &upper_initial(p_name) + "Changed" +} + +fn constructor_args(w: &mut Vec, prefix: &str, o: &Object, conf: &Config) { + let lcname = snake_case(&o.name); + for (name, p) in &o.properties { + if let Type::Object(object) = &p.property_type { + write!(w, ", {}m_{}", prefix, name); + constructor_args(w, &format!("m_{}->", name), object, conf); + } else { + write!(w, ",\n {}", changed_f(o, name)); + } + } + if o.object_type == ObjectType::List { + writeln!( + w, + ", + [](const {0}* o) {{ + Q_EMIT o->newDataReady(QModelIndex()); + }}, + []({0}* o) {{ + Q_EMIT o->layoutAboutToBeChanged(); + }}, + []({0}* o) {{ + o->updatePersistentIndexes(); + Q_EMIT o->layoutChanged(); + }}, + []({0}* o, quintptr first, quintptr last) {{ + o->dataChanged(o->createIndex(first, 0, first), + o->createIndex(last, {1}, last)); + }}, + []({0}* o) {{ + o->beginResetModel(); + }}, + []({0}* o) {{ + o->endResetModel(); + }}, + []({0}* o, int first, int last) {{ + o->beginInsertRows(QModelIndex(), first, last); + }}, + []({0}* o) {{ + o->endInsertRows(); + }}, + []({0}* o, int first, int last, int destination) {{ + o->beginMoveRows(QModelIndex(), first, last, QModelIndex(), destination); + }}, + []({0}* o) {{ + o->endMoveRows(); + }}, + []({0}* o, int first, int last) {{ + o->beginRemoveRows(QModelIndex(), first, last); + }}, + []({0}* o) {{ + o->endRemoveRows(); + }}", + o.name, + o.column_count - 1 + ); + } + if o.object_type == ObjectType::Tree { + writeln!( + w, + ", + [](const {0}* o, option_quintptr id) {{ + if (id.some) {{ + int row = {1}_row(o->m_d, id.value); + Q_EMIT o->newDataReady(o->createIndex(row, 0, id.value)); + }} else {{ + Q_EMIT o->newDataReady(QModelIndex()); + }} + }}, + []({0}* o) {{ + Q_EMIT o->layoutAboutToBeChanged(); + }}, + []({0}* o) {{ + o->updatePersistentIndexes(); + Q_EMIT o->layoutChanged(); + }}, + []({0}* o, quintptr first, quintptr last) {{ + quintptr frow = {1}_row(o->m_d, first); + quintptr lrow = {1}_row(o->m_d, first); + o->dataChanged(o->createIndex(frow, 0, first), + o->createIndex(lrow, {2}, last)); + }}, + []({0}* o) {{ + o->beginResetModel(); + }}, + []({0}* o) {{ + o->endResetModel(); + }}, + []({0}* o, option_quintptr id, int first, int last) {{ + if (id.some) {{ + int row = {1}_row(o->m_d, id.value); + o->beginInsertRows(o->createIndex(row, 0, id.value), first, last); + }} else {{ + o->beginInsertRows(QModelIndex(), first, last); + }} + }}, + []({0}* o) {{ + o->endInsertRows(); + }}, + []({0}* o, option_quintptr sourceParent, int first, int last, option_quintptr destinationParent, int destination) {{ + QModelIndex s; + if (sourceParent.some) {{ + int row = {1}_row(o->m_d, sourceParent.value); + s = o->createIndex(row, 0, sourceParent.value); + }} + QModelIndex d; + if (destinationParent.some) {{ + int row = {1}_row(o->m_d, destinationParent.value); + d = o->createIndex(row, 0, destinationParent.value); + }} + o->beginMoveRows(s, first, last, d, destination); + }}, + []({0}* o) {{ + o->endMoveRows(); + }}, + []({0}* o, option_quintptr id, int first, int last) {{ + if (id.some) {{ + int row = {1}_row(o->m_d, id.value); + o->beginRemoveRows(o->createIndex(row, 0, id.value), first, last); + }} else {{ + o->beginRemoveRows(QModelIndex(), first, last); + }} + }}, + []({0}* o) {{ + o->endRemoveRows(); + }}", + o.name, + lcname, + o.column_count - 1 + ); + } +} + +pub fn write_header(conf: &Config) -> Result<()> { + let mut h_file = conf.config_file.parent().unwrap().join(&conf.cpp_file); + h_file.set_extension("h"); + let mut h = Vec::new(); + let guard = h_file + .file_name() + .unwrap() + .to_string_lossy() + .replace(".", "_") + .to_uppercase(); + writeln!( + h, + "/* generated by rust_qt_binding_generator */ +#ifndef {0} +#define {0} + +#include +#include +", + guard + ); + + for name in conf.objects.keys() { + writeln!(h, "class {};", name); + } + for object in conf.objects.values() { + write_header_object(&mut h, object, conf)?; + } + writeln!(h, "#endif // {}", guard); + + write_if_different(h_file, &h)?; + Ok(()) +} + +pub fn write_cpp(conf: &Config) -> Result<()> { + let mut w = Vec::new(); + let mut h_file = conf.config_file.parent().unwrap().join(&conf.cpp_file); + h_file.set_extension("h"); + let file_name = h_file.file_name().unwrap().to_string_lossy(); + writeln!( + w, + "/* generated by rust_qt_binding_generator */ +#include \"{}\" + +namespace {{", + file_name + ); + for option in conf.optional_types() { + if option != "QString" && option != "QByteArray" { + writeln!( + w, + " + struct option_{} {{ + public: + {0} value; + bool some; + operator QVariant() const {{ + if (some) {{ + return QVariant::fromValue(value); + }} + return QVariant(); + }} + }}; + static_assert(std::is_pod::value, \"option_{0} must be a POD type.\");", + option + ); + } + } + if conf.types().contains("QString") { + writeln!( + w, + " + typedef void (*qstring_set)(QString* val, const char* utf8, int nbytes); + void set_qstring(QString* val, const char* utf8, int nbytes) {{ + *val = QString::fromUtf8(utf8, nbytes); + }}" + ); + } + if conf.types().contains("QByteArray") { + writeln!( + w, + " + typedef void (*qbytearray_set)(QByteArray* val, const char* bytes, int nbytes); + void set_qbytearray(QByteArray* v, const char* bytes, int nbytes) {{ + if (v->isNull() && nbytes == 0) {{ + *v = QByteArray(bytes, nbytes); + }} else {{ + v->truncate(0); + v->append(bytes, nbytes); + }} + }}" + ); + } + if conf.has_list_or_tree() { + writeln!( + w, + " + struct qmodelindex_t {{ + int row; + quintptr id; + }}; + inline QVariant cleanNullQVariant(const QVariant& v) {{ + return (v.isNull()) ?QVariant() :v; + }}" + ); + } + for (name, o) in &conf.objects { + for (p_name, p) in &o.properties { + if p.is_object() { + continue; + } + writeln!(w, " inline void {}({}* o)", changed_f(o, p_name), name); + writeln!(w, " {{\n Q_EMIT o->{}Changed();\n }}", p_name); + } + } + writeln!(w, "}}"); + + for o in conf.objects.values() { + if o.object_type != ObjectType::Object { + write_cpp_model(&mut w, o)?; + } + writeln!(w, "extern \"C\" {{"); + write_object_c_decl(&mut w, o, conf)?; + writeln!(w, "}};\n"); + } + + for o in conf.objects.values() { + write_cpp_object(&mut w, o, conf)?; + } + let file = conf.config_file.parent().unwrap().join(&conf.cpp_file); + write_if_different(file, &w) +} diff --git a/src/helper.cpp b/src/helper.cpp deleted file mode 100644 index a194a22..0000000 --- a/src/helper.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -QTextStream err(stderr); diff --git a/src/helper.h b/src/helper.h deleted file mode 100644 index 508c480..0000000 --- a/src/helper.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include - -inline QString snakeCase(const QString& name) { - return name.left(1).toLower() + name.mid(1) - .replace(QRegExp("([A-Z])"), "_\\1").toLower(); -} - -// Only write a file if it is different -class DifferentFileWriter { -public: - const QString path; - QByteArray buffer; - bool overwrite; - DifferentFileWriter(const QString& p, bool o = true) :path(p), overwrite(o) - { - } - ~DifferentFileWriter() { - const QByteArray old = read(); - if (old != buffer && (old.isNull() || overwrite)) { - write(); - } - } - QByteArray read() const { - QByteArray content; - QFile file(path); - if (file.open(QIODevice::ReadOnly)) { - content = file.readAll(); - } - return content; - } - void write() const { - QFile file(path); - if (!file.open(QIODevice::WriteOnly)) { - QTextStream err(stderr); - err << QCoreApplication::translate("main", - "Cannot write %1.\n").arg(file.fileName()); - err.flush(); - exit(1); - } - file.write(buffer); - file.close(); - } -}; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fc0e27d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,28 @@ +extern crate regex; +#[macro_use] +extern crate serde_derive; +extern crate serde_json; + +mod configuration; +mod cpp; +mod rust; +mod util; + +use std::fmt::Display; +use std::path::Path; +use std::error::Error; + +pub fn generate_rust_qt_bindings + Display>( + config_file: P, + overwrite_implementation: bool, +) -> Result<(), Box> { + let mut config = configuration::parse(config_file)?; + if overwrite_implementation { + config.overwrite_implementation = true; + } + cpp::write_header(&config)?; + cpp::write_cpp(&config)?; + rust::write_interface(&config)?; + rust::write_implementation(&config)?; + Ok(()) +} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index fd20700..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "parseJson.h" -#include "cpp.h" -#include "rust.h" -#include "helper.h" -#include - -int main(int argc, char *argv[]) { - QCoreApplication app(argc, argv); - QCoreApplication::setApplicationName(argv[0]); - QCoreApplication::setApplicationVersion("0.1"); - - QCommandLineParser parser; - parser.setApplicationDescription("Generates bindings between Qt and Rust"); - parser.addHelpOption(); - parser.addVersionOption(); - parser.addPositionalArgument("configuration", - QCoreApplication::translate("main", "Configuration file(s)")); - - // A boolean option (--overwrite-implementation) - QCommandLineOption overwriteOption(QStringList() - << "overwrite-implementation", - QCoreApplication::translate("main", - "Overwrite existing implementation.")); - parser.addOption(overwriteOption); - - parser.process(app); - - const QStringList args = parser.positionalArguments(); - if (args.isEmpty()) { - QTextStream err(stderr); - err << QCoreApplication::translate("main", - "Configuration file is missing.\n"); - return 1; - } - - for (auto path: args) { - const QString configurationFile(path); - Configuration configuration = parseConfiguration(configurationFile); - configuration.overwriteImplementation = parser.isSet(overwriteOption); - - writeHeader(configuration); - writeCpp(configuration); - writeRustInterface(configuration); - writeRustImplementation(configuration); - } - - return 0; -} diff --git a/src/parseJson.cpp b/src/parseJson.cpp deleted file mode 100644 index f357cfe..0000000 --- a/src/parseJson.cpp +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "parseJson.h" -#include "helper.h" -#include -#include -#include -#include - -BindingTypeProperties simpleType(BindingType type, const char* name, const char* init) { - return { - .type = type, - .name = name, - .cppSetType = name, - .cSetType = name, - .rustType = name, - .rustTypeInit = init - }; -} - -QList& bindingTypeProperties() { - static QList p; - if (p.empty()) { - QList f; - f.append(simpleType(BindingType::Bool, "bool", "true")); - f.append({ - .type = BindingType::UInt8, - .name = "qint8", - .cppSetType = "qint8", - .cSetType = "qint8", - .rustType = "i8", - .rustTypeInit = "0", - }); - f.append({ - .type = BindingType::UInt8, - .name = "quint8", - .cppSetType = "quint8", - .cSetType = "quint8", - .rustType = "u8", - .rustTypeInit = "0", - }); - f.append({ - .type = BindingType::Int16, - .name = "qint16", - .cppSetType = "qint16", - .cSetType = "qint16", - .rustType = "i16", - .rustTypeInit = "0", - }); - f.append({ - .type = BindingType::UInt16, - .name = "quint16", - .cppSetType = "quint16", - .cSetType = "quint16", - .rustType = "u16", - .rustTypeInit = "0" - }); - f.append({ - .type = BindingType::Int32, - .name = "qint32", - .cppSetType = "qint32", - .cSetType = "qint32", - .rustType = "i32", - .rustTypeInit = "0", - }); - f.append({ - .type = BindingType::UInt32, - .name = "quint32", - .cppSetType = "quint32", - .cSetType = "quint32", - .rustType = "u32", - .rustTypeInit = "0" - }); - f.append({ - .type = BindingType::Int64, - .name = "qint64", - .cppSetType = "qint64", - .cSetType = "qint64", - .rustType = "i64", - .rustTypeInit = "0", - }); - f.append({ - .type = BindingType::UInt64, - .name = "quint64", - .cppSetType = "quint64", - .cSetType = "quint64", - .rustType = "u64", - .rustTypeInit = "0" - }); - f.append({ - .type = BindingType::Float, - .name = "float", - .cppSetType = "float", - .cSetType = "float", - .rustType = "f32", - .rustTypeInit = "0.0" - }); - f.append({ - .type = BindingType::Double, - .name = "double", - .cppSetType = "double", - .cSetType = "double", - .rustType = "f64", - .rustTypeInit = "0.0" - }); - f.append({ - .type = BindingType::QString, - .name = "QString", - .cppSetType = "const QString&", - .cSetType = "qstring_t", - .rustType = "String", - .rustTypeInit = "String::new()" - }); - f.append({ - .type = BindingType::QByteArray, - .name = "QByteArray", - .cppSetType = "const QByteArray&", - .cSetType = "qbytearray_t", - .rustType = "Vec", - .rustTypeInit = "Vec::new()" - }); - f.append({ - .type = BindingType::Void, - .name = "void", - .cppSetType = "void", - .cSetType = "void", - .rustType = "()", - .rustTypeInit = "()", - }); - p = f; - } - return p; -} - -BindingTypeProperties parseBindingType(const QString& value) { - for (auto type: bindingTypeProperties()) { - if (value == type.name) { - return type; - } - } - QTextStream err(stderr); - err << QCoreApplication::translate("main", - "'%1' is not a supported type. Use one of\n").arg(value); - for (auto i: bindingTypeProperties()) { - if (i.name == i.rustType) { - err << " " << i.rustType << "\n"; - } else { - err << " " << i.name << " (" << i.rustType << ")\n"; - } - } - err.flush(); - exit(1); -} - -Property -parseProperty(const QString& name, const QJsonObject& json) { - Property p; - p.name = name; - p.type = parseBindingType(json.value("type").toString()); - p.write = json.value("write").toBool(); - p.optional = json.value("optional").toBool(); - p.rustByValue = json.value("rustByValue").toBool(); - p.rustByFunction = json.value("rustByFunction").toBool(); - return p; -} - -Argument -parseArgument(const QJsonObject& json) { - Argument arg; - arg.name = json.value("name").toString(); - arg.type = parseBindingType(json.value("type").toString()); - QTextStream out(stdout); - out.flush(); - if (arg.type.type == BindingType::Object) { - QTextStream err(stderr); - err << QCoreApplication::translate("main", - "'%1' is not a supported type in argument \"%2\". Only use the basic QT types for now\n").arg(arg.type.name, arg.name); - err.flush(); - exit(1); - } - return arg; -} - -QList -parseArguments(const QJsonArray& json) { - QList args; - for(const auto& a: json) { - args.push_back(parseArgument(a.toObject())); - } - return args; -} - -Function -parseFunction(const QString& name, const QJsonObject& json) { - Function f; - f.name = name; - f.mut = json.value("mut").toBool(); - f.type = parseBindingType(json.value("return").toString()); - if (f.type.type == BindingType::Object) { - QTextStream err(stderr); - err << QCoreApplication::translate("main", - "'%1' is not a supported return type in function \"%2\". Only use the basic QT types for now\n").arg(f.type.name, f.name); - err.flush(); - exit(1); - } - f.args = parseArguments(json.value("arguments").toArray()); - return f; -} - -Qt::ItemDataRole parseItemDataRole(const QString& s) { - const QString name = s.left(1).toUpper() + s.mid(1) + "Role"; - int v = QMetaEnum::fromType() - .keyToValue(name.toUtf8()); - if (v >= 0) { - return (Qt::ItemDataRole)v; - } - QTextStream err(stderr); - err << QCoreApplication::translate("main", - "%1 is not a valid role name.\n").arg(s); - err.flush(); - exit(1); -} - -ItemProperty -parseItemProperty(const QString& name, const QJsonObject& json) { - ItemProperty ip; - ip.name = name; - ip.type = parseBindingType(json.value("type").toString()); - ip.write = json.value("write").toBool(); - ip.optional = json.value("optional").toBool(); - ip.rustByValue = json.value("rustByValue").toBool(); - QJsonArray roles = json.value("roles").toArray(); - for (auto r: roles) { - QList l; - for (auto v: r.toArray()) { - l.append(parseItemDataRole(v.toString())); - } - ip.roles.append(l); - } - return ip; -} - -Object -parseObject(const QString& name, const QJsonObject& json) { - Object o; - o.name = name; - QString type = json.value("type").toString(); - if (type == "List") { - o.type = ObjectType::List; - } else if (type == "Tree") { - o.type = ObjectType::Tree; - } else { - o.type = ObjectType::Object; - } - const QJsonObject& properties = json.value("properties").toObject(); - for (const QString& key: properties.keys()) { - o.properties.append(parseProperty(key, properties[key].toObject())); - } - const QJsonObject& functions = json.value("functions").toObject(); - for (const QString& key: functions.keys()) { - o.functions.append(parseFunction(key, functions[key].toObject())); - } - QTextStream err(stderr); - const QJsonObject& itemProperties = json.value("itemProperties").toObject(); - if (o.type != ObjectType::Object && itemProperties.size() == 0) { - err << QCoreApplication::translate("main", - "No item properties are defined for %1.\n").arg(o.name); - err.flush(); - exit(1); - } else if (o.type == ObjectType::Object && itemProperties.size() > 0) { - err << QCoreApplication::translate("main", - "%1 is an Object and should not have itemProperties.").arg(o.name); - err.flush(); - exit(1); - } - o.columnCount = 0; - for (const QString& key: itemProperties.keys()) { - ItemProperty p = parseItemProperty(key, itemProperties[key].toObject()); - o.columnCount = qMax(o.columnCount, p.roles.size()); - o.itemProperties.append(p); - } - o.columnCount = qMax(1, o.columnCount); - return o; -} - -Configuration -parseConfiguration(const QString& path) { - QFile configurationFile(path); - const QDir base = QFileInfo(configurationFile).dir(); - QTextStream err(stderr); - if (!configurationFile.open(QIODevice::ReadOnly)) { - err << QCoreApplication::translate("main", - "Cannot read %1.\n").arg(configurationFile.fileName()); - err.flush(); - exit(1); - } - const QByteArray data(configurationFile.readAll()); - QJsonParseError error; - const QJsonDocument doc(QJsonDocument::fromJson(data, &error)); - if (error.error != QJsonParseError::NoError) { - err << error.errorString(); - err.flush(); - exit(1); - } - const QJsonObject o = doc.object(); - Configuration c; - c.cppFile = QFileInfo(base, o.value("cppFile").toString()); - QDir(c.cppFile.dir()).mkpath("."); - c.hFile = QFileInfo(c.cppFile.dir(), c.cppFile.completeBaseName() + ".h"); - const QJsonObject& object = o.value("objects").toObject(); - for (const QString& key: object.keys()) { - bindingTypeProperties().append({ - .type = BindingType::Object, - .name = key, - .cppSetType = key, - .cSetType = key, - .rustType = key, - .rustTypeInit = "", - }); - } - for (const QString& key: object.keys()) { - Object o = parseObject(key, object[key].toObject()); - c.objects.append(o); - } - const QJsonObject rust = o.value("rust").toObject(); - c.rustdir = QDir(base.filePath(rust.value("dir").toString())); - c.interfaceModule = rust.value("interfaceModule").toString(); - c.implementationModule = rust.value("implementationModule").toString(); - return c; -} diff --git a/src/parseJson.h b/src/parseJson.h deleted file mode 100644 index 42e4d0c..0000000 --- a/src/parseJson.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "structs.h" - -Configuration parseConfiguration(const QString& path); diff --git a/src/rust.cpp b/src/rust.cpp deleted file mode 100644 index 0ba12f7..0000000 --- a/src/rust.cpp +++ /dev/null @@ -1,1116 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "structs.h" -#include "helper.h" - -template -QString rustType(const T& p) -{ - if (p.optional) { - return "Option<" + p.type.rustType + ">"; - } - return p.type.rustType; -} - -template -QString rustReturnType(const T& p) -{ - QString type = p.type.rustType; - if (type == "String" && !p.rustByValue) { - type = "str"; - } - if (type == "Vec" && !p.rustByValue) { - type = "[u8]"; - } - if (p.type.isComplex() && !p.rustByValue) { - type = "&" + type; - } - if (p.optional) { - return "Option<" + type + ">"; - } - return type; -} - -template -QString rustCType(const T& p) -{ - if (p.optional) { - return "COption<" + p.type.rustType + ">"; - } - return p.type.rustType; -} - -template -QString rustTypeInit(const T& p) -{ - if (p.optional) { - return "None"; - } - return p.type.rustTypeInit; -} - -void rConstructorArgsDecl(QTextStream& r, const QString& name, const Object& o, const Configuration& conf) { - r << QString(" %2: *mut %1QObject").arg(o.name, snakeCase(name)); - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - r << QString(",\n"); - rConstructorArgsDecl(r, p.name, conf.findObject(p.type.name), conf); - } else { - r << QString(",\n %3_%2_changed: fn(*const %1QObject)") - .arg(o.name, snakeCase(p.name), snakeCase(name)); - } - } - if (o.type == ObjectType::List) { - r << QString(",\n %2_new_data_ready: fn(*const %1QObject)") - .arg(o.name, snakeCase(name)); - } else if (o.type == ObjectType::Tree) { - r << QString(",\n %2_new_data_ready: fn(*const %1QObject, index: COption)") - .arg(o.name, snakeCase(name)); - } - if (o.type != ObjectType::Object) { - QString indexDecl; - if (o.type == ObjectType::Tree) { - indexDecl = " index: COption,"; - } - QString destDecl; - if (o.type == ObjectType::Tree) { - destDecl = " index: COption,"; - } - r << QString(R"(, - %3_layout_about_to_be_changed: fn(*const %1QObject), - %3_layout_changed: fn(*const %1QObject), - %3_data_changed: fn(*const %1QObject, usize, usize), - %3_begin_reset_model: fn(*const %1QObject), - %3_end_reset_model: fn(*const %1QObject), - %3_begin_insert_rows: fn(*const %1QObject,%2 usize, usize), - %3_end_insert_rows: fn(*const %1QObject), - %3_begin_move_rows: fn(*const %1QObject,%2 usize, usize,%4 usize), - %3_end_move_rows: fn(*const %1QObject), - %3_begin_remove_rows: fn(*const %1QObject,%2 usize, usize), - %3_end_remove_rows: fn(*const %1QObject))").arg(o.name, indexDecl, - snakeCase(name), destDecl); - } -} - -void rConstructorArgs(QTextStream& r, const QString& name, const Object& o, const Configuration& conf) { - const QString lcname(snakeCase(o.name)); - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - rConstructorArgs(r, p.name, conf.findObject(p.type.name), conf); - } - } - r << QString(R"( let %2_emit = %1Emitter { - qobject: Arc::new(Mutex::new(%2)), -)").arg(o.name, snakeCase(name)); - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) continue; - r << QString(" %1_changed: %2_%1_changed,\n").arg(snakeCase(p.name), snakeCase(name)); - } - if (o.type != ObjectType::Object) { - r << QString(" new_data_ready: %1_new_data_ready,\n") - .arg(snakeCase(name)); - } - QString model = ""; - if (o.type != ObjectType::Object) { - const QString type = o.type == ObjectType::List ? "List" : "Tree"; - model = ", model"; - r << QString(R"( }; - let model = %1%2 { - qobject: %3, - layout_about_to_be_changed: %4_layout_about_to_be_changed, - layout_changed: %4_layout_changed, - data_changed: %4_data_changed, - begin_reset_model: %4_begin_reset_model, - end_reset_model: %4_end_reset_model, - begin_insert_rows: %4_begin_insert_rows, - end_insert_rows: %4_end_insert_rows, - begin_move_rows: %4_begin_move_rows, - end_move_rows: %4_end_move_rows, - begin_remove_rows: %4_begin_remove_rows, - end_remove_rows: %4_end_remove_rows, -)").arg(o.name, type, snakeCase(name), snakeCase(name)); - } - r << QString(" };\n let d_%3 = %1::new(%3_emit%2") - .arg(o.name, model, snakeCase(name)); - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - r << ",\n d_" << snakeCase(p.name); - } - } - r << ");\n"; -} - -void writeFunction(QTextStream& r, const Function& f, const QString& lcname, const Object& o) { - const QString lc(snakeCase(f.name)); - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %1_%2(ptr: *%3 %4)").arg(lcname, lc, f.mut ? "mut" : "const", o.name); - // write all the input arguments, for QString and QByteArray, write - // pointers to their content and the length which is int in Qt - for (auto a = f.args.begin(); a < f.args.end(); a++) { - r << ", "; - if (a->type.name == "QString") { - r << QString("%1_str: *const c_ushort, %1_len: c_int").arg(a->name); - } else if (a->type.name == "QByteArray") { - r << QString("%1_str: *const c_char, %1_len: c_int").arg(a->name); - } else { - r << a->name << ": " << a->type.rustType; - } - } - // If the return type is QString or QByteArray, append a pointer to the - // variable that will be set to the argument list. Also add a setter - // function. - if (f.type.isComplex()) { - r << QString(", d: *mut %1, set: fn(*mut %1, str: *const c_char, len: c_int)) {\n").arg(f.type.name); - } else { - r << ") -> " << f.type.rustType << " {\n"; - } - for (auto a = f.args.begin(); a < f.args.end(); a++) { - if (a->type.name == "QString") { - r << " let mut " << a->name << " = String::new();\n"; - r << QString(" set_string_from_utf16(&mut %1, %1_str, %1_len);\n").arg(a->name); - } else if (a->type.name == "QByteArray") { - r << QString(" let %1 = { slice::from_raw_parts(%1_str as *const u8, to_usize(%1_len)) };\n").arg(a->name); - } - } - if (f.mut) { - r << " let o = &mut *ptr;\n"; - } else { - r << " let o = &*ptr;\n"; - } - r << " let r = o." << lc << "("; - for (auto a = f.args.begin(); a < f.args.end(); a++) { - if (a != f.args.begin()) { - r << ", "; - } - r << a->name; - } - r << ");\n"; - if (f.type.isComplex()) { - r << " let s: *const c_char = r.as_ptr() as (*const c_char);\n"; - r << " set(d, s, r.len() as i32);\n"; - } else { - r << " r\n"; - } - r << "}\n"; -} - -void writeRustInterfaceObject(QTextStream& r, const Object& o, const Configuration& conf) { - const QString lcname(snakeCase(o.name)); - r << QString(R"( -pub struct %1QObject {} - -#[derive(Clone)] -pub struct %1Emitter { - qobject: Arc>, -)").arg(o.name); - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - continue; - } - r << QString(" %2_changed: fn(*const %1QObject),\n") - .arg(o.name, snakeCase(p.name)); - } - if (o.type == ObjectType::List) { - r << QString(" new_data_ready: fn(*const %1QObject),\n") - .arg(o.name); - } else if (o.type == ObjectType::Tree) { - r << QString(" new_data_ready: fn(*const %1QObject, index: COption),\n") - .arg(o.name); - } - r << QString(R"(} - -unsafe impl Send for %1Emitter {} - -impl %1Emitter { - fn clear(&self) { - *self.qobject.lock().unwrap() = null(); - } -)").arg(o.name); - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - continue; - } - r << QString(R"( pub fn %1_changed(&self) { - let ptr = *self.qobject.lock().unwrap(); - if !ptr.is_null() { - (self.%1_changed)(ptr); - } - } -)").arg(snakeCase(p.name)); - } - if (o.type == ObjectType::List) { - r << R"( pub fn new_data_ready(&self) { - let ptr = *self.qobject.lock().unwrap(); - if !ptr.is_null() { - (self.new_data_ready)(ptr); - } - } -)"; - } else if (o.type == ObjectType::Tree) { - r << R"( pub fn new_data_ready(&self, item: Option) { - let ptr = *self.qobject.lock().unwrap(); - if !ptr.is_null() { - (self.new_data_ready)(ptr, item.into()); - } - } -)"; - } - - QString modelStruct = ""; - if (o.type != ObjectType::Object) { - QString type = o.type == ObjectType::List ? "List" : "Tree"; - modelStruct = ", model: " + o.name + type; - QString index; - QString indexDecl; - QString indexCDecl; - QString dest; - QString destDecl; - QString destCDecl; - if (o.type == ObjectType::Tree) { - indexDecl = " index: Option,"; - indexCDecl = " index: COption,"; - index = " index.into(),"; - destDecl = " dest: Option,"; - destCDecl = " dest: COption,"; - dest = " dest.into(),"; - } - r << QString(R"(} - -#[derive(Clone)] -pub struct %1%2 { - qobject: *const %1QObject, - layout_about_to_be_changed: fn(*const %1QObject), - layout_changed: fn(*const %1QObject), - data_changed: fn(*const %1QObject, usize, usize), - begin_reset_model: fn(*const %1QObject), - end_reset_model: fn(*const %1QObject), - begin_insert_rows: fn(*const %1QObject,%5 usize, usize), - end_insert_rows: fn(*const %1QObject), - begin_move_rows: fn(*const %1QObject,%5 usize, usize,%8 usize), - end_move_rows: fn(*const %1QObject), - begin_remove_rows: fn(*const %1QObject,%5 usize, usize), - end_remove_rows: fn(*const %1QObject), -} - -impl %1%2 { - pub fn layout_about_to_be_changed(&self) { - (self.layout_about_to_be_changed)(self.qobject); - } - pub fn layout_changed(&self) { - (self.layout_changed)(self.qobject); - } - pub fn data_changed(&self, first: usize, last: usize) { - (self.data_changed)(self.qobject, first, last); - } - pub fn begin_reset_model(&self) { - (self.begin_reset_model)(self.qobject); - } - pub fn end_reset_model(&self) { - (self.end_reset_model)(self.qobject); - } - pub fn begin_insert_rows(&self,%3 first: usize, last: usize) { - (self.begin_insert_rows)(self.qobject,%4 first, last); - } - pub fn end_insert_rows(&self) { - (self.end_insert_rows)(self.qobject); - } - pub fn begin_move_rows(&self,%3 first: usize, last: usize,%6 destination: usize) { - (self.begin_move_rows)(self.qobject,%4 first, last,%7 destination); - } - pub fn end_move_rows(&self) { - (self.end_move_rows)(self.qobject); - } - pub fn begin_remove_rows(&self,%3 first: usize, last: usize) { - (self.begin_remove_rows)(self.qobject,%4 first, last); - } - pub fn end_remove_rows(&self) { - (self.end_remove_rows)(self.qobject); - } -)").arg(o.name, type, indexDecl, index, indexCDecl, destDecl, dest, destCDecl); - } - - r << QString(R"(} - -pub trait %1Trait { - fn new(emit: %1Emitter%2)").arg(o.name, modelStruct); - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - r << ",\n " << snakeCase(p.name) << ": " << p.type.name; - } - } - r << QString(R"() -> Self; - fn emit(&self) -> &%1Emitter; -)").arg(o.name); - for (const Property& p: o.properties) { - const QString lc(snakeCase(p.name)); - if (p.type.type == BindingType::Object) { - r << QString(" fn %1(&self) -> &%2;\n").arg(lc, rustType(p)); - r << QString(" fn %1_mut(&mut self) -> &mut %2;\n").arg(lc, rustType(p)); - } else { - if (p.rustByFunction) { - r << QString(" fn %1(&self, getter: F) where F: FnOnce(%2);").arg(lc, rustReturnType(p)); - } else { - r << QString(" fn %1(&self) -> %2;\n").arg(lc, rustReturnType(p)); - } - if (p.write) { - if (p.type.name == "QByteArray") { - if (p.optional) { - r << QString(" fn set_%1(&mut self, value: Option<&[u8]>);\n").arg(lc); - } else { - r << QString(" fn set_%1(&mut self, value: &[u8]);\n").arg(lc); - } - } else { - r << QString(" fn set_%1(&mut self, value: %2);\n").arg(lc, rustType(p)); - } - } - } - } - for (const Function& f: o.functions) { - const QString lc(snakeCase(f.name)); - QString argList; - if (f.args.size() > 0) { - for (auto a = f.args.begin(); a < f.args.end(); a++) { - auto t = a->type.name == "QByteArray" ?"&[u8]" :a->type.rustType; - argList.append(QString(", %1: %2").arg(a->name, t)); - } - } - r << QString(" fn %1(&%2self%4) -> %3;\n") - .arg(lc, f.mut ? "mut " : "", f.type.rustType, argList); - } - if (o.type == ObjectType::List) { - r << R"( fn row_count(&self) -> usize; - fn insert_rows(&mut self, _row: usize, _count: usize) -> bool { false } - fn remove_rows(&mut self, _row: usize, _count: usize) -> bool { false } - fn can_fetch_more(&self) -> bool { - false - } - fn fetch_more(&mut self) {} - fn sort(&mut self, u8, SortOrder) {} -)"; - } else if (o.type == ObjectType::Tree) { - r << R"( fn row_count(&self, Option) -> usize; - fn can_fetch_more(&self, Option) -> bool { - false - } - fn fetch_more(&mut self, Option) {} - fn sort(&mut self, u8, SortOrder) {} - fn check_row(&self, index: usize, row: usize) -> Option; - fn index(&self, item: Option, row: usize) -> usize; - fn parent(&self, index: usize) -> Option; - fn row(&self, index: usize) -> usize; -)"; - } - if (o.type != ObjectType::Object) { - for (auto ip: o.itemProperties) { - r << QString(" fn %1(&self, index: usize) -> %2;\n") - .arg(snakeCase(ip.name), rustReturnType(ip)); - if (ip.write) { - if (ip.type.name == "QByteArray") { - if (ip.optional) { - r << QString(" fn set_%1(&mut self, index: usize, Option<&[u8]>) -> bool;\n") - .arg(snakeCase(ip.name)); - } else { - r << QString(" fn set_%1(&mut self, index: usize, &[u8]) -> bool;\n") - .arg(snakeCase(ip.name)); - } - } else { - r << QString(" fn set_%1(&mut self, index: usize, %2) -> bool;\n") - .arg(snakeCase(ip.name), rustType(ip)); - } - } - } - } - - r << QString(R"(} - -#[no_mangle] -pub extern "C" fn %1_new( -)").arg(lcname); - rConstructorArgsDecl(r, lcname, o, conf); - r << QString(",\n) -> *mut %1 {\n").arg(o.name); - rConstructorArgs(r, lcname, o, conf); - r << QString(R"( Box::into_raw(Box::new(d_%2)) -} - -#[no_mangle] -pub unsafe extern "C" fn %2_free(ptr: *mut %1) { - Box::from_raw(ptr).emit().clear(); -} -)").arg(o.name, lcname); - for (const Property& p: o.properties) { - const QString base = QString("%1_%2").arg(lcname, snakeCase(p.name)); - QString ret = ") -> " + rustType(p); - if (p.type.type == BindingType::Object) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_get(ptr: *mut %1) -> *mut %4 { - (&mut *ptr).%3_mut() -} -)").arg(o.name, base, snakeCase(p.name), rustType(p)); - - } else if (p.type.isComplex() && !p.optional) { - if (p.rustByFunction) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_get( - ptr: *const %1, - p: *mut %4, - set: fn(*mut %4, *const c_char, c_int), -) { - let o = &*ptr; - o.%3(|v| { - let s: *const c_char = v.as_ptr() as (*const c_char); - set(p, s, to_c_int(v.len())); - }); -} -)").arg(o.name, base, snakeCase(p.name), p.type.name); - } else { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_get( - ptr: *const %1, - p: *mut %4, - set: fn(*mut %4, *const c_char, c_int), -) { - let o = &*ptr; - let v = o.%3(); - let s: *const c_char = v.as_ptr() as (*const c_char); - set(p, s, to_c_int(v.len())); -} -)").arg(o.name, base, snakeCase(p.name), p.type.name); - } - if (p.write && p.type.name == "QString") { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: *const c_ushort, len: c_int) { - let o = &mut *ptr; - let mut s = String::new(); - set_string_from_utf16(&mut s, v, len); - o.set_%3(s); -} -)").arg(o.name, base, snakeCase(p.name)); - } else if (p.write) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: *const c_char, len: c_int) { - let o = &mut *ptr; - let v = slice::from_raw_parts(v as *const u8, to_usize(len)); - o.set_%3(v); -} -)").arg(o.name, base, snakeCase(p.name)); - } - } else if (p.type.isComplex()) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_get( - ptr: *const %1, - p: *mut %4, - set: fn(*mut %4, *const c_char, c_int), -) { - let o = &*ptr; - let v = o.%3(); - if let Some(v) = v { - let s: *const c_char = v.as_ptr() as (*const c_char); - set(p, s, to_c_int(v.len())); - } -} -)").arg(o.name, base, snakeCase(p.name), p.type.name); - if (p.write && p.type.name == "QString") { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: *const c_ushort, len: c_int) { - let o = &mut *ptr; - let mut s = String::new(); - set_string_from_utf16(&mut s, v, len); - o.set_%3(Some(s)); -} -)").arg(o.name, base, snakeCase(p.name)); - } else if (p.write) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: *const c_char, len: c_int) { - let o = &mut *ptr; - let v = slice::from_raw_parts(v as *const u8, to_usize(len)); - o.set_%3(Some(v.into())); -} -)").arg(o.name, base, snakeCase(p.name)); - } - } else if (p.optional) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_get(ptr: *const %1) -> COption<%4> { - match (&*ptr).%3() { - Some(value) => COption { data: value, some: true }, - None => COption { data: %4::default(), some: false} - } -} -)").arg(o.name, base, snakeCase(p.name), p.type.rustType); - if (p.write) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: %4) { - (&mut *ptr).set_%3(Some(v)); -} -)").arg(o.name, base, snakeCase(p.name), p.type.rustType); - } - } else { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_get(ptr: *const %1) -> %4 { - (&*ptr).%3() -} -)").arg(o.name, base, snakeCase(p.name), rustType(p)); - if (p.write) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: %4) { - (&mut *ptr).set_%3(v); -} -)").arg(o.name, base, snakeCase(p.name), rustType(p)); - } - } - if (p.write && p.optional) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set_none(ptr: *mut %1) { - let o = &mut *ptr; - o.set_%3(None); -} -)").arg(o.name, base, snakeCase(p.name)); - } - } - for (const Function& f: o.functions) { - writeFunction(r, f, lcname, o); - } - if (o.type == ObjectType::List) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_row_count(ptr: *const %1) -> c_int { - to_c_int((&*ptr).row_count()) -} -#[no_mangle] -pub unsafe extern "C" fn %2_insert_rows(ptr: *mut %1, row: c_int, count: c_int) -> bool { - (&mut *ptr).insert_rows(to_usize(row), to_usize(count)) -} -#[no_mangle] -pub unsafe extern "C" fn %2_remove_rows(ptr: *mut %1, row: c_int, count: c_int) -> bool { - (&mut *ptr).remove_rows(to_usize(row), to_usize(count)) -} -#[no_mangle] -pub unsafe extern "C" fn %2_can_fetch_more(ptr: *const %1) -> bool { - (&*ptr).can_fetch_more() -} -#[no_mangle] -pub unsafe extern "C" fn %2_fetch_more(ptr: *mut %1) { - (&mut *ptr).fetch_more() -} -#[no_mangle] -pub unsafe extern "C" fn %2_sort( - ptr: *mut %1, - column: u8, - order: SortOrder, -) { - (&mut *ptr).sort(column, order) -} -)").arg(o.name, lcname); - } else if (o.type == ObjectType::Tree) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_row_count( - ptr: *const %1, - index: COption, -) -> c_int { - to_c_int((&*ptr).row_count(index.into())) -} -#[no_mangle] -pub unsafe extern "C" fn %2_can_fetch_more( - ptr: *const %1, - index: COption, -) -> bool { - (&*ptr).can_fetch_more(index.into()) -} -#[no_mangle] -pub unsafe extern "C" fn %2_fetch_more(ptr: *mut %1, index: COption) { - (&mut *ptr).fetch_more(index.into()) -} -#[no_mangle] -pub unsafe extern "C" fn %2_sort( - ptr: *mut %1, - column: u8, - order: SortOrder -) { - (&mut *ptr).sort(column, order) -} -#[no_mangle] -pub unsafe extern "C" fn %2_check_row( - ptr: *const %1, - index: usize, - row: c_int, -) -> COption { - (&*ptr).check_row(index.into(), to_usize(row)).into() -} -#[no_mangle] -pub unsafe extern "C" fn %2_index( - ptr: *const %1, - index: COption, - row: c_int, -) -> usize { - (&*ptr).index(index.into(), to_usize(row)) -} -#[no_mangle] -pub unsafe extern "C" fn %2_parent(ptr: *const %1, index: usize) -> QModelIndex { - if let Some(parent) = (&*ptr).parent(index) { - QModelIndex { - row: to_c_int((&*ptr).row(parent)), - internal_id: parent, - } - } else { - QModelIndex { - row: -1, - internal_id: 0, - } - } -} -#[no_mangle] -pub unsafe extern "C" fn %2_row(ptr: *const %1, index: usize) -> c_int { - to_c_int((&*ptr).row(index)) -} -)").arg(o.name, lcname); - } - if (o.type != ObjectType::Object) { - QString indexDecl = ", row: c_int"; - QString index = "to_usize(row)"; - if (o.type == ObjectType::Tree) { - indexDecl = ", index: usize"; - index = "index"; - } - for (auto ip: o.itemProperties) { - if (ip.type.isComplex() && !ip.optional) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_data_%3( - ptr: *const %1%4, - d: *mut %6, - set: fn(*mut %6, *const c_char, len: c_int), -) { - let o = &*ptr; - let data = o.%3(%5); - let s: *const c_char = data.as_ptr() as (*const c_char); - set(d, s, to_c_int(data.len())); -} -)").arg(o.name, lcname, snakeCase(ip.name), indexDecl, index, ip.type.name); - } else if (ip.type.isComplex()) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_data_%3( - ptr: *const %1%4, - d: *mut %6, - set: fn(*mut %6, *const c_char, len: c_int), -) { - let o = &*ptr; - let data = o.%3(%5); - if let Some(data) = data { - let s: *const c_char = data.as_ptr() as (*const c_char); - set(d, s, to_c_int(data.len())); - } -} -)").arg(o.name, lcname, snakeCase(ip.name), indexDecl, index, ip.type.name); - } else { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_data_%3(ptr: *const %1%5) -> %4 { - let o = &*ptr; - o.%3(%6).into() -} -)").arg(o.name, lcname, snakeCase(ip.name), rustCType(ip), indexDecl, index); - } - if (ip.write) { - QString val = "v"; - if (ip.optional) { - val = "Some(" + val + ")"; - } - if (ip.type.name == "QString") { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set_data_%3( - ptr: *mut %1%4, - s: *const c_ushort, len: c_int, -) -> bool { - let o = &mut *ptr; - let mut v = String::new(); - set_string_from_utf16(&mut v, s, len); - o.set_%3(%5, %6) -} -)").arg(o.name, lcname, snakeCase(ip.name), indexDecl, index, val); - } else if (ip.type.name == "QByteArray") { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set_data_%3( - ptr: *mut %1%4, - s: *const c_char, len: c_int, -) -> bool { - let o = &mut *ptr; - let slice = ::std::slice::from_raw_parts(s as *const u8, to_usize(len)); - o.set_%3(%5, %6) -} -)").arg(o.name, lcname, snakeCase(ip.name), indexDecl, index, ip.optional ?"Some(slice)" :"slice"); - } else { - const QString type = ip.type.rustType; - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set_data_%3( - ptr: *mut %1%4, - v: %6, -) -> bool { - (&mut *ptr).set_%3(%5, %7) -} -)").arg(o.name, lcname, snakeCase(ip.name), indexDecl, index, type, val); - } - } - if (ip.write && ip.optional) { - r << QString(R"( -#[no_mangle] -pub unsafe extern "C" fn %2_set_data_%3_none(ptr: *mut %1%4) -> bool { - (&mut *ptr).set_%3(%5, None) -} -)").arg(o.name, lcname, snakeCase(ip.name), indexDecl, index); - } - } - } -} - -QString rustFile(const QDir rustdir, const QString& module) { - QDir src(rustdir.absoluteFilePath("src")); - QString modulePath = src.absoluteFilePath(module + "/mod.rs"); - if (QFile::exists(modulePath)) { - return modulePath; - } - return src.absoluteFilePath(module + ".rs"); -} - -void writeRustTypes(const Configuration& conf, QTextStream& r) { - bool hasOption = false; - bool hasString = false; - bool hasByteArray = false; - bool hasListOrTree = false; - - for (auto o: conf.objects) { - hasListOrTree |= o.type != ObjectType::Object; - for (auto p: o.properties) { - hasOption |= p.optional; - hasString |= p.type.type == BindingType::QString; - hasByteArray |= p.type.type == BindingType::QByteArray; - } - for (auto p: o.itemProperties) { - hasOption |= p.optional; - hasString |= p.type.type == BindingType::QString; - hasByteArray |= p.type.type == BindingType::QByteArray; - } - for (auto f: o.functions) { - hasString |= f.type.type == BindingType::QString; - hasByteArray |= f.type.type == BindingType::QByteArray; - for (auto a: f.args) { - hasString |= a.type.type == BindingType::QString; - hasByteArray |= a.type.type == BindingType::QByteArray; - } - } - } - - if (hasOption || hasListOrTree) { - r << R"( - -#[repr(C)] -pub struct COption { - data: T, - some: bool, -} - -impl COption { - #![allow(dead_code)] - fn into(self) -> Option { - if self.some { - Some(self.data) - } else { - None - } - } -} - -impl From> for COption -where - T: Default, -{ - fn from(t: Option) -> COption { - if let Some(v) = t { - COption { - data: v, - some: true, - } - } else { - COption { - data: T::default(), - some: false, - } - } - } -} -)"; - } - if (hasString) { - r << R"( - -pub enum QString {} - -fn set_string_from_utf16(s: &mut String, str: *const c_ushort, len: c_int) { - let utf16 = unsafe { slice::from_raw_parts(str, to_usize(len)) }; - let characters = decode_utf16(utf16.iter().cloned()) - .map(|r| r.unwrap()); - s.clear(); - s.extend(characters); -} - -)"; - } - if (hasByteArray) { - r << R"( - -pub enum QByteArray {} -)"; - } - if (hasListOrTree) { - r << R"( - -#[repr(C)] -#[derive(PartialEq, Eq, Debug)] -pub enum SortOrder { - Ascending = 0, - Descending = 1, -} - -#[repr(C)] -pub struct QModelIndex { - row: c_int, - internal_id: usize, -} -)"; - } - - if (hasString || hasByteArray || hasListOrTree) { - r << R"( - -fn to_usize(n: c_int) -> usize { - if n < 0 { - panic!("Cannot cast {} to usize", n); - } - n as usize -} - -)"; - } - - if (hasString || hasByteArray || hasListOrTree) { - r << R"( -fn to_c_int(n: usize) -> c_int { - if n > c_int::max_value() as usize { - panic!("Cannot cast {} to c_int", n); - } - n as c_int -} - -)"; - } -} - -void writeRustInterface(const Configuration& conf) { - DifferentFileWriter w(rustFile(conf.rustdir, conf.interfaceModule)); - QTextStream r(&w.buffer); - r << QString(R"(/* generated by rust_qt_binding_generator */ -#![allow(unknown_lints)] -#![allow(mutex_atomic, needless_pass_by_value)] -use libc::{c_char, c_ushort, c_int}; -use std::slice; -use std::char::decode_utf16; - -use std::sync::{Arc, Mutex}; -use std::ptr::null; - -use %1::*; -)").arg(conf.implementationModule); - - writeRustTypes(conf, r); - - for (auto object: conf.objects) { - writeRustInterfaceObject(r, object, conf); - } -} - -void writeRustImplementationObject(QTextStream& r, const Object& o) { - const QString lcname(snakeCase(o.name)); - if (o.type != ObjectType::Object) { - r << "#[derive(Default, Clone)]\n"; - r << QString("struct %1Item {\n").arg(o.name); - for (auto ip: o.itemProperties) { - const QString lc(snakeCase(ip.name)); - r << QString(" %1: %2,\n").arg(lc, ip.type.rustType); - } - r << "}\n\n"; - } - QString modelStruct = ""; - r << QString("pub struct %1 {\n emit: %1Emitter,\n").arg((o.name)); - if (o.type == ObjectType::List) { - modelStruct = ", model: " + o.name + "List"; - r << QString(" model: %1List,\n").arg(o.name); - } else if (o.type == ObjectType::Tree) { - modelStruct = ", model: " + o.name + "Tree"; - r << QString(" model: %1Tree,\n").arg(o.name); - } - for (const Property& p: o.properties) { - const QString lc(snakeCase(p.name)); - r << QString(" %1: %2,\n").arg(lc, rustType(p)); - } - if (o.type != ObjectType::Object) { - r << QString(" list: Vec<%1Item>,\n").arg(o.name); - } - r << "}\n\n"; - for (const Property& p: o.properties) { - if (p.type.type == BindingType::Object) { - modelStruct += ", " + p.name + ": " + p.type.name; - } - } - r << QString(R"(impl %1Trait for %1 { - fn new(emit: %1Emitter%2) -> %1 { - %1 { - emit: emit, -)").arg(o.name, modelStruct); - if (o.type != ObjectType::Object) { - r << QString(" model: model,\n"); - } - for (const Property& p: o.properties) { - const QString lc(snakeCase(p.name)); - if (p.type.type == BindingType::Object) { - r << QString(" %1: %1,\n").arg(lc); - } else { - r << QString(" %1: %2,\n").arg(lc, rustTypeInit(p)); - } - } - r << QString(R"( } - } - fn emit(&self) -> &%1Emitter { - &self.emit - } -)").arg(o.name); - for (const Property& p: o.properties) { - const QString lc(snakeCase(p.name)); - if (p.type.type == BindingType::Object) { - r << QString(R"( fn %1(&self) -> &%2 { - &self.%1 - } - fn %1_mut(&mut self) -> &mut %2 { - &mut self.%1 - } -)").arg(lc, rustReturnType(p)); - } else { - r << QString(" fn %1(&self) -> %2 {\n").arg(lc, rustReturnType(p)); - if (p.type.isComplex()) { - if (p.optional) { -/* - if (rustType(p) == "Option") { - r << QString(" self.%1.as_ref().map(|p|p.as_str())\n").arg(lc); - } else { - } -*/ - r << QString(" self.%1.as_ref().map(|p|&p[..])\n").arg(lc); - } else { - r << QString(" &self.%1\n").arg(lc); - } - } else { - r << QString(" self.%1\n").arg(lc); - } - r << " }\n"; - if (p.write) { - r << QString(R"( fn set_%1(&mut self, value: %2) { - self.%1 = value; - self.emit.%1_changed(); - } -)").arg(lc, rustType(p)); - } - } - } - if (o.type == ObjectType::List) { - r << " fn row_count(&self) -> usize {\n self.list.len()\n }\n"; - } else if (o.type == ObjectType::Tree) { - r << R"( fn row_count(&self, item: Option) -> usize { - self.list.len() - } - fn index(&self, item: Option, row: usize) -> usize { - 0 - } - fn parent(&self, index: usize) -> Option { - None - } - fn row(&self, index: usize) -> usize { - item - } -)"; - } - if (o.type != ObjectType::Object) { - QString index; - if (o.type == ObjectType::Tree) { - index = ", index: usize"; - } - for (auto ip: o.itemProperties) { - const QString lc(snakeCase(ip.name)); - r << QString(" fn %1(&self, index: usize) -> %2 {\n") - .arg(lc, rustReturnType(ip)); - if (ip.type.isComplex()) { - r << " &self.list[index]." << lc << "\n"; - } else { - r << " self.list[index]." << lc << "\n"; - } - r << " }\n"; - if (ip.write) { - r << QString(" fn set_%1(&mut self, index: usize, v: %2) -> bool {\n") - .arg(snakeCase(ip.name), rustType(ip)); - r << " self.list[index]." << lc << " = v;\n"; - r << " true\n"; - r << " }\n"; - } - } - } - r << "}\n\n"; -} - -void writeRustImplementation(const Configuration& conf) { - DifferentFileWriter w(rustFile(conf.rustdir, conf.implementationModule), - conf.overwriteImplementation); - QTextStream r(&w.buffer); - r << QString(R"(#![allow(unused_imports)] -#![allow(unused_variables)] -#![allow(dead_code)] -use %1::*; - -)").arg(conf.interfaceModule); - - for (auto object: conf.objects) { - writeRustImplementationObject(r, object); - } -} diff --git a/src/rust.h b/src/rust.h deleted file mode 100644 index 779f379..0000000 --- a/src/rust.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -class Configuration; -void writeRustInterface(const Configuration& conf); -void writeRustImplementation(const Configuration& conf); diff --git a/src/rust.rs b/src/rust.rs new file mode 100644 index 0000000..fa7a2ac --- /dev/null +++ b/src/rust.rs @@ -0,0 +1,1503 @@ +use configuration::{Config, Function, ItemProperty, Object, ObjectType, Property, SimpleType, Type}; +use std::io::{Result, Write}; +use util::{snake_case, write_if_different}; + +fn rust_type(p: &Property) -> String { + if p.optional { + return format!("Option<{}>", p.property_type.rust_type()); + } + p.property_type.rust_type().to_string() +} + +fn rust_type_(p: &ItemProperty) -> String { + if p.optional { + return format!("Option<{}>", p.item_property_type.rust_type()); + } + p.item_property_type.rust_type().to_string() +} + +fn rust_return_type(p: &Property) -> String { + let mut type_: String = p.property_type.rust_type().to_string(); + if type_ == "String" { + type_ = "str".to_string(); + } + if type_ == "Vec" { + type_ = "[u8]".to_string(); + } + if p.property_type.is_complex() { + type_ = "&".to_string() + &type_; + } + if p.optional { + return "Option<".to_string() + &type_ + ">"; + } + type_ +} + +fn rust_return_type_(p: &ItemProperty) -> String { + let mut type_: String = p.item_property_type.rust_type().to_string(); + if type_ == "String" && !p.rust_by_value { + type_ = "str".to_string(); + } + if type_ == "Vec" && !p.rust_by_value { + type_ = "[u8]".to_string(); + } + if p.item_property_type.is_complex() && !p.rust_by_value { + type_ = "&".to_string() + &type_; + } + if p.optional { + return "Option<".to_string() + &type_ + ">"; + } + type_ +} + +fn rust_c_type(p: &ItemProperty) -> String { + if p.optional { + return format!("COption<{}>", p.item_property_type.rust_type()); + } + p.item_property_type.rust_type().to_string() +} + +fn rust_type_init(p: &Property) -> &str { + if p.optional { + return "None"; + } + p.property_type.rust_type_init() +} + +fn r_constructor_args_decl(r: &mut Vec, name: &str, o: &Object, conf: &Config) { + write!(r, " {}: *mut {}QObject", snake_case(name), o.name); + for (p_name, p) in &o.properties { + if let Type::Object(object) = &p.property_type { + writeln!(r, ","); + r_constructor_args_decl(r, p_name, object, conf); + } else { + write!( + r, + ",\n {}_{}_changed: fn(*const {}QObject)", + snake_case(name), + snake_case(p_name), + o.name + ); + } + } + if o.object_type == ObjectType::List { + write!( + r, + ",\n {}_new_data_ready: fn(*const {}QObject)", + snake_case(name), + o.name + ); + } else if o.object_type == ObjectType::Tree { + write!( + r, + ",\n {}_new_data_ready: fn(*const {}QObject, index: COption)", + snake_case(name), + o.name + ); + } + if o.object_type != ObjectType::Object { + let index_decl = if o.object_type == ObjectType::Tree { + " index: COption," + } else { + "" + }; + let dest_decl = if o.object_type == ObjectType::Tree { + " index: COption," + } else { + "" + }; + write!( + r, + ", + {2}_layout_about_to_be_changed: fn(*const {0}QObject), + {2}_layout_changed: fn(*const {0}QObject), + {2}_data_changed: fn(*const {0}QObject, usize, usize), + {2}_begin_reset_model: fn(*const {0}QObject), + {2}_end_reset_model: fn(*const {0}QObject), + {2}_begin_insert_rows: fn(*const {0}QObject,{1} usize, usize), + {2}_end_insert_rows: fn(*const {0}QObject), + {2}_begin_move_rows: fn(*const {0}QObject,{1} usize, usize,{3} usize), + {2}_end_move_rows: fn(*const {0}QObject), + {2}_begin_remove_rows: fn(*const {0}QObject,{1} usize, usize), + {2}_end_remove_rows: fn(*const {0}QObject)", + o.name, + index_decl, + snake_case(name), + dest_decl + ); + } +} + +fn r_constructor_args(r: &mut Vec, name: &str, o: &Object, conf: &Config) { + for (name, p) in &o.properties { + if let Type::Object(object) = &p.property_type { + r_constructor_args(r, name, object, conf); + } + } + writeln!( + r, + " let {}_emit = {}Emitter {{ + qobject: Arc::new(Mutex::new({0})),", + snake_case(name), + o.name + ); + for (p_name, p) in &o.properties { + if p.is_object() { + continue; + } + writeln!( + r, + " {}_changed: {}_{0}_changed,", + snake_case(p_name), + snake_case(name) + ); + } + if o.object_type != ObjectType::Object { + writeln!( + r, + " new_data_ready: {}_new_data_ready,", + snake_case(name) + ); + } + let mut model = String::new(); + if o.object_type != ObjectType::Object { + let type_ = if o.object_type == ObjectType::List { + "List" + } else { + "Tree" + }; + model.push_str(", model"); + writeln!( + r, + " }}; + let model = {}{} {{ + qobject: {}, + layout_about_to_be_changed: {2}_layout_about_to_be_changed, + layout_changed: {2}_layout_changed, + data_changed: {2}_data_changed, + begin_reset_model: {2}_begin_reset_model, + end_reset_model: {2}_end_reset_model, + begin_insert_rows: {2}_begin_insert_rows, + end_insert_rows: {2}_end_insert_rows, + begin_move_rows: {2}_begin_move_rows, + end_move_rows: {2}_end_move_rows, + begin_remove_rows: {2}_begin_remove_rows, + end_remove_rows: {2}_end_remove_rows,", + o.name, + type_, + snake_case(name) + ); + } + write!( + r, + " }};\n let d_{} = {}::new({0}_emit{}", + snake_case(name), + o.name, + model + ); + for (name, p) in &o.properties { + if p.is_object() { + write!(r, ",\n d_{}", snake_case(name)); + } + } + writeln!(r, ");"); +} + +fn write_function(r: &mut Vec, (name, f): (&String, &Function), lcname: &str, o: &Object) { + let lc = snake_case(name); + write!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_{}(ptr: *{} {}", + lcname, + lc, + if f.mutable { "mut" } else { "const" }, + o.name + ); + // write all the input arguments, for QString and QByteArray, write + // pointers to their content and the length which is int in Qt + for a in &f.arguments { + write!(r, ", "); + if a.argument_type.name() == "QString" { + write!(r, "{}_str: *const c_ushort, {0}_len: c_int", a.name); + } else if a.argument_type.name() == "QByteArray" { + write!(r, "{}_str: *const c_char, {0}_len: c_int", a.name); + } else { + write!(r, "{}: {}", a.name, a.argument_type.rust_type()); + } + } + // If the return type is QString or QByteArray, append a pointer to the + // variable that will be set to the argument list. Also add a setter + // function. + if f.return_type.is_complex() { + writeln!( + r, + ", d: *mut {}, set: fn(*mut {0}, str: *const c_char, len: c_int)) {{", + f.return_type.name() + ); + } else { + writeln!(r, ") -> {} {{", f.return_type.rust_type()); + } + for a in &f.arguments { + if a.argument_type.name() == "QString" { + writeln!( + r, + " let mut {} = String::new(); + set_string_from_utf16(&mut {0}, {0}_str, {0}_len);", + a.name + ); + } else if a.argument_type.name() == "QByteArray" { + writeln!( + r, + " let {} = {{ slice::from_raw_parts({0}_str as *const u8, to_usize({0}_len)) }};", + a.name + ); + } + } + if f.mutable { + writeln!(r, " let o = &mut *ptr;"); + } else { + writeln!(r, " let o = &*ptr;"); + } + write!(r, " let r = o.{}(", lc); + for (i, a) in f.arguments.iter().enumerate() { + if i > 0 { + write!(r, ", "); + } + write!(r, "{}", a.name); + } + writeln!(r, ");"); + if f.return_type.is_complex() { + writeln!( + r, + " let s: *const c_char = r.as_ptr() as (*const c_char); + set(d, s, r.len() as i32);" + ); + } else { + writeln!(r, " r"); + } + writeln!(r, "}}"); +} + +fn write_rust_interface_object(r: &mut Vec, o: &Object, conf: &Config) { + let lcname = snake_case(&o.name); + writeln!( + r, + " +pub struct {}QObject {{}} + +#[derive(Clone)] +pub struct {0}Emitter {{ + qobject: Arc>,", + o.name + ); + for (name, p) in &o.properties { + if p.is_object() { + continue; + } + writeln!( + r, + " {}_changed: fn(*const {}QObject),", + snake_case(name), + o.name + ); + } + if o.object_type == ObjectType::List { + writeln!(r, " new_data_ready: fn(*const {}QObject),", o.name); + } else if o.object_type == ObjectType::Tree { + writeln!( + r, + " new_data_ready: fn(*const {}QObject, index: COption),", + o.name + ); + } + writeln!( + r, + "}} + +unsafe impl Send for {}Emitter {{}} + +impl {0}Emitter {{ + fn clear(&self) {{ + *self.qobject.lock().unwrap() = null(); + }}", + o.name + ); + + for (name, p) in &o.properties { + if p.is_object() { + continue; + } + writeln!( + r, + " pub fn {}_changed(&self) {{ + let ptr = *self.qobject.lock().unwrap(); + if !ptr.is_null() {{ + (self.{0}_changed)(ptr); + }} + }}", + snake_case(name) + ); + } + + if o.object_type == ObjectType::List { + writeln!( + r, + " pub fn new_data_ready(&self) {{ + let ptr = *self.qobject.lock().unwrap(); + if !ptr.is_null() {{ + (self.new_data_ready)(ptr); + }} + }}" + ); + } else if o.object_type == ObjectType::Tree { + writeln!( + r, + " pub fn new_data_ready(&self, item: Option) {{ + let ptr = *self.qobject.lock().unwrap(); + if !ptr.is_null() {{ + (self.new_data_ready)(ptr, item.into()); + }} + }}" + ); + } + + let mut model_struct = String::new(); + if o.object_type != ObjectType::Object { + let type_ = if o.object_type == ObjectType::List { + "List" + } else { + "Tree" + }; + model_struct = format!(", model: {}{}", o.name, type_); + let mut index = ""; + let mut index_decl = ""; + let mut index_c_decl = ""; + let mut dest = ""; + let mut dest_decl = ""; + let mut dest_c_decl = ""; + if o.object_type == ObjectType::Tree { + index_decl = " index: Option,"; + index_c_decl = " index: COption,"; + index = " index.into(),"; + dest_decl = " dest: Option,"; + dest_c_decl = " dest: COption,"; + dest = " dest.into(),"; + } + writeln!( + r, + "}} + +#[derive(Clone)] +pub struct {0}{1} {{ + qobject: *const {0}QObject, + layout_about_to_be_changed: fn(*const {0}QObject), + layout_changed: fn(*const {0}QObject), + data_changed: fn(*const {0}QObject, usize, usize), + begin_reset_model: fn(*const {0}QObject), + end_reset_model: fn(*const {0}QObject), + begin_insert_rows: fn(*const {0}QObject,{4} usize, usize), + end_insert_rows: fn(*const {0}QObject), + begin_move_rows: fn(*const {0}QObject,{4} usize, usize,{7} usize), + end_move_rows: fn(*const {0}QObject), + begin_remove_rows: fn(*const {0}QObject,{4} usize, usize), + end_remove_rows: fn(*const {0}QObject), +}} + +impl {0}{1} {{ + pub fn layout_about_to_be_changed(&self) {{ + (self.layout_about_to_be_changed)(self.qobject); + }} + pub fn layout_changed(&self) {{ + (self.layout_changed)(self.qobject); + }} + pub fn data_changed(&self, first: usize, last: usize) {{ + (self.data_changed)(self.qobject, first, last); + }} + pub fn begin_reset_model(&self) {{ + (self.begin_reset_model)(self.qobject); + }} + pub fn end_reset_model(&self) {{ + (self.end_reset_model)(self.qobject); + }} + pub fn begin_insert_rows(&self,{2} first: usize, last: usize) {{ + (self.begin_insert_rows)(self.qobject,{3} first, last); + }} + pub fn end_insert_rows(&self) {{ + (self.end_insert_rows)(self.qobject); + }} + pub fn begin_move_rows(&self,{2} first: usize, last: usize,{5} destination: usize) {{ + (self.begin_move_rows)(self.qobject,{3} first, last,{6} destination); + }} + pub fn end_move_rows(&self) {{ + (self.end_move_rows)(self.qobject); + }} + pub fn begin_remove_rows(&self,{2} first: usize, last: usize) {{ + (self.begin_remove_rows)(self.qobject,{3} first, last); + }} + pub fn end_remove_rows(&self) {{ + (self.end_remove_rows)(self.qobject); + }}", + o.name, + type_, + index_decl, + index, + index_c_decl, + dest_decl, + dest, + dest_c_decl + ); + } + + write!( + r, + "}} + +pub trait {}Trait {{ + fn new(emit: {0}Emitter{}", + o.name, + model_struct + ); + for (name, p) in &o.properties { + if p.is_object() { + write!(r, ",\n {}: {}", snake_case(name), p.type_name()); + } + } + writeln!( + r, + ") -> Self; + fn emit(&self) -> &{}Emitter;", + o.name + ); + for (name, p) in &o.properties { + let lc = snake_case(name).to_lowercase(); + if p.is_object() { + writeln!(r, " fn {}(&self) -> &{};", lc, rust_type(p)); + writeln!(r, " fn {}_mut(&mut self) -> &mut {};", lc, rust_type(p)); + } else { + if p.rust_by_function { + write!( + r, + " fn {}(&self, getter: F) where F: FnOnce({});", + lc, + rust_return_type(p) + ); + } else { + writeln!(r, " fn {}(&self) -> {};", lc, rust_return_type(p)); + } + if p.write { + if p.type_name() == "QByteArray" { + if p.optional { + writeln!(r, " fn set_{}(&mut self, value: Option<&[u8]>);", lc); + } else { + writeln!(r, " fn set_{}(&mut self, value: &[u8]);", lc); + } + } else { + writeln!(r, " fn set_{}(&mut self, value: {});", lc, rust_type(p)); + } + } + } + } + for (name, f) in &o.functions { + let lc = snake_case(name); + let mut arg_list = String::new(); + if !f.arguments.is_empty() { + for a in &f.arguments { + let t = if a.argument_type.name() == "QByteArray" { + "&[u8]" + } else { + a.argument_type.rust_type() + }; + arg_list.push_str(&format!(", {}: {}", a.name, t)); + } + } + writeln!( + r, + " fn {}(&{}self{}) -> {};", + lc, + if f.mutable { "mut " } else { "" }, + arg_list, + f.return_type.rust_type() + ); + } + if o.object_type == ObjectType::List { + writeln!( + r, + " fn row_count(&self) -> usize; + fn insert_rows(&mut self, _row: usize, _count: usize) -> bool {{ false }} + fn remove_rows(&mut self, _row: usize, _count: usize) -> bool {{ false }} + fn can_fetch_more(&self) -> bool {{ + false + }} + fn fetch_more(&mut self) {{}} + fn sort(&mut self, u8, SortOrder) {{}}" + ); + } else if o.object_type == ObjectType::Tree { + writeln!( + r, + " fn row_count(&self, Option) -> usize; + fn can_fetch_more(&self, Option) -> bool {{ + false + }} + fn fetch_more(&mut self, Option) {{}} + fn sort(&mut self, u8, SortOrder) {{}} + fn check_row(&self, index: usize, row: usize) -> Option; + fn index(&self, item: Option, row: usize) -> usize; + fn parent(&self, index: usize) -> Option; + fn row(&self, index: usize) -> usize;" + ); + } + if o.object_type != ObjectType::Object { + for (name, ip) in &o.item_properties { + let name = snake_case(name); + writeln!( + r, + " fn {}(&self, index: usize) -> {};", + name, + rust_return_type_(ip) + ); + if ip.write { + if ip.item_property_type.name() == "QByteArray" { + if ip.optional { + writeln!( + r, + " fn set_{}(&mut self, index: usize, Option<&[u8]>) -> bool;", + name + ); + } else { + writeln!( + r, + " fn set_{}(&mut self, index: usize, &[u8]) -> bool;", + name + ); + } + } else { + writeln!( + r, + " fn set_{}(&mut self, index: usize, {}) -> bool;", + name, + rust_type_(ip) + ); + } + } + } + } + writeln!( + r, + "}} + +#[no_mangle] +pub extern \"C\" fn {}_new(", + lcname + ); + r_constructor_args_decl(r, &lcname, o, conf); + writeln!(r, ",\n) -> *mut {} {{", o.name); + r_constructor_args(r, &lcname, o, conf); + writeln!( + r, + " Box::into_raw(Box::new(d_{})) +}} + +#[no_mangle] +pub unsafe extern \"C\" fn {0}_free(ptr: *mut {}) {{ + Box::from_raw(ptr).emit().clear(); +}}", + lcname, + o.name + ); + + for (name, p) in &o.properties { + let base = format!("{}_{}", lcname, snake_case(name)); + if p.is_object() { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_get(ptr: *mut {}) -> *mut {} {{ + (&mut *ptr).{}_mut() +}}", + base, + o.name, + rust_type(p), + snake_case(name) + ); + } else if p.is_complex() && !p.optional { + if p.rust_by_function { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_get( + ptr: *const {}, + p: *mut {}, + set: fn(*mut {2}, *const c_char, c_int), +) {{ + let o = &*ptr; + o.{}(|v| {{ + let s: *const c_char = v.as_ptr() as (*const c_char); + set(p, s, to_c_int(v.len())); + }}); +}}", + base, + o.name, + p.type_name(), + snake_case(name) + ); + } else { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_get( + ptr: *const {}, + p: *mut {}, + set: fn(*mut {2}, *const c_char, c_int), +) {{ + let o = &*ptr; + let v = o.{}(); + let s: *const c_char = v.as_ptr() as (*const c_char); + set(p, s, to_c_int(v.len())); +}}", + base, + o.name, + p.type_name(), + snake_case(name) + ); + } + if p.write && p.type_name() == "QString" { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set(ptr: *mut {}, v: *const c_ushort, len: c_int) {{ + let o = &mut *ptr; + let mut s = String::new(); + set_string_from_utf16(&mut s, v, len); + o.set_{}(s); +}}", + base, + o.name, + snake_case(name) + ); + } else if p.write { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set(ptr: *mut {}, v: *const c_char, len: c_int) {{ + let o = &mut *ptr; + let v = slice::from_raw_parts(v as *const u8, to_usize(len)); + o.set_{}(v); +}}", + base, + o.name, + snake_case(name) + ); + } + } else if p.is_complex() { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_get( + ptr: *const {}, + p: *mut {}, + set: fn(*mut {2}, *const c_char, c_int), +) {{ + let o = &*ptr; + let v = o.{}(); + if let Some(v) = v {{ + let s: *const c_char = v.as_ptr() as (*const c_char); + set(p, s, to_c_int(v.len())); + }} +}}", + base, + o.name, + p.type_name(), + snake_case(name) + ); + if p.write && p.type_name() == "QString" { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set(ptr: *mut {}, v: *const c_ushort, len: c_int) {{ + let o = &mut *ptr; + let mut s = String::new(); + set_string_from_utf16(&mut s, v, len); + o.set_{}(Some(s)); +}}", + base, + o.name, + snake_case(name) + ); + } else if p.write { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set(ptr: *mut {}, v: *const c_char, len: c_int) {{ + let o = &mut *ptr; + let v = slice::from_raw_parts(v as *const u8, to_usize(len)); + o.set_{}(Some(v.into())); +}}", + base, + o.name, + snake_case(name) + ); + } + } else if p.optional { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_get(ptr: *const {}) -> COption<{}> {{ + match (&*ptr).{}() {{ + Some(value) => COption {{ data: value, some: true }}, + None => COption {{ data: {2}::default(), some: false}} + }} +}}", + base, + o.name, + p.property_type.rust_type(), + snake_case(name) + ); + if p.write { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set(ptr: *mut {}, v: {}) {{ + (&mut *ptr).set_{}(Some(v)); +}}", + base, + o.name, + p.property_type.rust_type(), + snake_case(name) + ); + } + } else { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_get(ptr: *const {}) -> {} {{ + (&*ptr).{}() +}}", + base, + o.name, + rust_type(p), + snake_case(name) + ); + if p.write { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set(ptr: *mut {}, v: {}) {{ + (&mut *ptr).set_{}(v); +}}", + base, + o.name, + rust_type(p), + snake_case(name) + ); + } + } + if p.write && p.optional { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set_none(ptr: *mut {}) {{ + let o = &mut *ptr; + o.set_{}(None); +}}", + base, + o.name, + snake_case(name) + ); + } + } + for f in &o.functions { + write_function(r, f, &lcname, o); + } + if o.object_type == ObjectType::List { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {1}_row_count(ptr: *const {0}) -> c_int {{ + to_c_int((&*ptr).row_count()) +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_insert_rows(ptr: *mut {0}, row: c_int, count: c_int) -> bool {{ + (&mut *ptr).insert_rows(to_usize(row), to_usize(count)) +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_remove_rows(ptr: *mut {0}, row: c_int, count: c_int) -> bool {{ + (&mut *ptr).remove_rows(to_usize(row), to_usize(count)) +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_can_fetch_more(ptr: *const {0}) -> bool {{ + (&*ptr).can_fetch_more() +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_fetch_more(ptr: *mut {0}) {{ + (&mut *ptr).fetch_more() +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_sort( + ptr: *mut {0}, + column: u8, + order: SortOrder, +) {{ + (&mut *ptr).sort(column, order) +}}", + o.name, + lcname + ); + } else if o.object_type == ObjectType::Tree { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {1}_row_count( + ptr: *const {0}, + index: COption, +) -> c_int {{ + to_c_int((&*ptr).row_count(index.into())) +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_can_fetch_more( + ptr: *const {0}, + index: COption, +) -> bool {{ + (&*ptr).can_fetch_more(index.into()) +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_fetch_more(ptr: *mut {0}, index: COption) {{ + (&mut *ptr).fetch_more(index.into()) +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_sort( + ptr: *mut {0}, + column: u8, + order: SortOrder +) {{ + (&mut *ptr).sort(column, order) +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_check_row( + ptr: *const {0}, + index: usize, + row: c_int, +) -> COption {{ + (&*ptr).check_row(index.into(), to_usize(row)).into() +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_index( + ptr: *const {0}, + index: COption, + row: c_int, +) -> usize {{ + (&*ptr).index(index.into(), to_usize(row)) +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_parent(ptr: *const {0}, index: usize) -> QModelIndex {{ + if let Some(parent) = (&*ptr).parent(index) {{ + QModelIndex {{ + row: to_c_int((&*ptr).row(parent)), + internal_id: parent, + }} + }} else {{ + QModelIndex {{ + row: -1, + internal_id: 0, + }} + }} +}} +#[no_mangle] +pub unsafe extern \"C\" fn {1}_row(ptr: *const {0}, index: usize) -> c_int {{ + to_c_int((&*ptr).row(index)) +}}", + o.name, + lcname + ); + } + if o.object_type != ObjectType::Object { + let (index_decl, index) = if o.object_type == ObjectType::Tree { + (", index: usize", "index") + } else { + (", row: c_int", "to_usize(row)") + }; + for (name, ip) in &o.item_properties { + if ip.is_complex() && !ip.optional { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_data_{}( + ptr: *const {}{}, + d: *mut {}, + set: fn(*mut {4}, *const c_char, len: c_int), +) {{ + let o = &*ptr; + let data = o.{1}({}); + let s: *const c_char = data.as_ptr() as (*const c_char); + set(d, s, to_c_int(data.len())); +}}", + lcname, + snake_case(name), + o.name, + index_decl, + ip.type_name(), + index + ); + } else if ip.is_complex() { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_data_{}( + ptr: *const {}{}, + d: *mut {}, + set: fn(*mut {4}, *const c_char, len: c_int), +) {{ + let o = &*ptr; + let data = o.{1}({}); + if let Some(data) = data {{ + let s: *const c_char = data.as_ptr() as (*const c_char); + set(d, s, to_c_int(data.len())); + }} +}}", + lcname, + snake_case(name), + o.name, + index_decl, + ip.type_name(), + index + ); + } else { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_data_{}(ptr: *const {}{}) -> {} {{ + let o = &*ptr; + o.{1}({}).into() +}}", + lcname, + snake_case(name), + o.name, + index_decl, + rust_c_type(ip), + index + ); + } + if ip.write { + let val = if ip.optional { "Some(v)" } else { "v" }; + if ip.type_name() == "QString" { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set_data_{}( + ptr: *mut {}{}, + s: *const c_ushort, len: c_int, +) -> bool {{ + let o = &mut *ptr; + let mut v = String::new(); + set_string_from_utf16(&mut v, s, len); + o.set_{1}({}, {}) +}}", + lcname, + snake_case(name), + o.name, + index_decl, + index, + val + ); + } else if ip.type_name() == "QByteArray" { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set_data_{}( + ptr: *mut {}{}, + s: *const c_char, len: c_int, +) -> bool {{ + let o = &mut *ptr; + let slice = ::std::slice::from_raw_parts(s as *const u8, to_usize(len)); + o.set_{1}({}, {}) +}}", + lcname, + snake_case(name), + o.name, + index_decl, + index, + if ip.optional { "Some(slice)" } else { "slice" } + ); + } else { + let type_ = ip.item_property_type.rust_type(); + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set_data_{}( + ptr: *mut {}{}, + v: {}, +) -> bool {{ + (&mut *ptr).set_{1}({}, {}) +}}", + lcname, + snake_case(name), + o.name, + index_decl, + type_, + index, + val + ); + } + } + if ip.write && ip.optional { + writeln!( + r, + " +#[no_mangle] +pub unsafe extern \"C\" fn {}_set_data_{}_none(ptr: *mut {}{}) -> bool {{ + (&mut *ptr).set_{1}({}, None) +}}", + lcname, + snake_case(name), + o.name, + index_decl, + index + ); + } + } + } +} + +fn write_rust_types(conf: &Config, r: &mut Vec) { + let mut has_option = false; + let mut has_string = false; + let mut has_byte_array = false; + let mut has_list_or_tree = false; + + for o in conf.objects.values() { + has_list_or_tree |= o.object_type != ObjectType::Object; + for p in o.properties.values() { + has_option |= p.optional; + has_string |= p.property_type == Type::Simple(SimpleType::QString); + has_byte_array |= p.property_type == Type::Simple(SimpleType::QByteArray); + } + for p in o.item_properties.values() { + has_option |= p.optional; + has_string |= p.item_property_type == SimpleType::QString; + has_byte_array |= p.item_property_type == SimpleType::QByteArray; + } + for f in o.functions.values() { + has_string |= f.return_type == SimpleType::QString; + has_byte_array |= f.return_type == SimpleType::QByteArray; + for a in &f.arguments { + has_string |= a.argument_type == SimpleType::QString; + has_byte_array |= a.argument_type == SimpleType::QByteArray; + } + } + } + + if has_option || has_list_or_tree { + writeln!( + r, + " + +#[repr(C)] +pub struct COption {{ + data: T, + some: bool, +}} + +impl COption {{ + #![allow(dead_code)] + fn into(self) -> Option {{ + if self.some {{ + Some(self.data) + }} else {{ + None + }} + }} +}} + +impl From> for COption +where + T: Default, +{{ + fn from(t: Option) -> COption {{ + if let Some(v) = t {{ + COption {{ + data: v, + some: true, + }} + }} else {{ + COption {{ + data: T::default(), + some: false, + }} + }} + }} +}}" + ); + } + if has_string { + writeln!( + r, + " + +pub enum QString {{}} + +fn set_string_from_utf16(s: &mut String, str: *const c_ushort, len: c_int) {{ + let utf16 = unsafe {{ slice::from_raw_parts(str, to_usize(len)) }}; + let characters = decode_utf16(utf16.iter().cloned()) + .map(|r| r.unwrap()); + s.clear(); + s.extend(characters); +}} +" + ); + } + if has_byte_array { + writeln!( + r, + " + +pub enum QByteArray {{}}" + ); + } + if has_list_or_tree { + writeln!( + r, + " + +#[repr(C)] +#[derive(PartialEq, Eq, Debug)] +pub enum SortOrder {{ + Ascending = 0, + Descending = 1, +}} + +#[repr(C)] +pub struct QModelIndex {{ + row: c_int, + internal_id: usize, +}}" + ); + } + + if has_string || has_byte_array || has_list_or_tree { + writeln!( + r, + " + +fn to_usize(n: c_int) -> usize {{ + if n < 0 {{ + panic!(\"Cannot cast {{}} to usize\", n); + }} + n as usize +}} +" + ); + } + + if has_string || has_byte_array || has_list_or_tree { + writeln!( + r, + " +fn to_c_int(n: usize) -> c_int {{ + if n > c_int::max_value() as usize {{ + panic!(\"Cannot cast {{}} to c_int\", n); + }} + n as c_int +}} +" + ); + } +} + +pub fn write_interface(conf: &Config) -> Result<()> { + let mut r = Vec::new(); + writeln!( + r, + "/* generated by rust_qt_binding_generator */ +#![allow(unknown_lints)] +#![allow(mutex_atomic, needless_pass_by_value)] +use libc::{{c_char, c_ushort, c_int}}; +use std::slice; +use std::char::decode_utf16; + +use std::sync::{{Arc, Mutex}}; +use std::ptr::null; + +use {}::*;", + conf.rust.implementation_module + ); + + write_rust_types(conf, &mut r); + + for object in conf.objects.values() { + write_rust_interface_object(&mut r, object, conf); + } + let mut file = conf.config_file + .parent() + .unwrap() + .join(&conf.rust.dir) + .join("src") + .join(&conf.rust.interface_module); + file.set_extension("rs"); + write_if_different(file, &r) +} + +fn write_rust_implementation_object(r: &mut Vec, o: &Object) { + if o.object_type != ObjectType::Object { + writeln!(r, "#[derive(Default, Clone)]"); + writeln!(r, "struct {}Item {{", o.name); + for (name, ip) in &o.item_properties { + let lc = snake_case(name); + if ip.optional { + writeln!( + r, + " {}: Option<{}>,", + lc, + ip.item_property_type.rust_type() + ); + } else { + writeln!(r, " {}: {},", lc, ip.item_property_type.rust_type()); + } + } + writeln!(r, "}}\n"); + } + let mut model_struct = String::new(); + writeln!(r, "pub struct {} {{\n emit: {0}Emitter,", o.name); + if o.object_type == ObjectType::List { + model_struct = format!(", model: {}List", o.name); + writeln!(r, " model: {}List,", o.name); + } else if o.object_type == ObjectType::Tree { + model_struct = format!(", model: {}Tree", o.name); + writeln!(r, " model: {}Tree,", o.name); + } + for (name, p) in &o.properties { + let lc = snake_case(name); + writeln!(r, " {}: {},", lc, rust_type(p)); + } + if o.object_type != ObjectType::Object { + writeln!(r, " list: Vec<{}Item>,", o.name); + } + writeln!(r, "}}\n"); + for (name, p) in &o.properties { + if p.is_object() { + model_struct += &format!(", {}: {}", name, p.type_name()); + } + } + writeln!( + r, + "impl {}Trait for {0} {{ + fn new(emit: {0}Emitter{}) -> {0} {{ + {0} {{ + emit,", + o.name, + model_struct + ); + if o.object_type != ObjectType::Object { + writeln!(r, " model,"); + writeln!(r, " list: Vec::new(),"); + } + for (name, p) in &o.properties { + let lc = snake_case(name); + if p.is_object() { + writeln!(r, " {},", lc); + } else { + writeln!(r, " {}: {},", lc, rust_type_init(p)); + } + } + writeln!( + r, + " }} + }} + fn emit(&self) -> &{}Emitter {{ + &self.emit + }}", + o.name + ); + for (name, p) in &o.properties { + let lc = snake_case(name); + if p.is_object() { + writeln!( + r, + " fn {}(&self) -> &{} {{ + &self.{0} + }} + fn {0}_mut(&mut self) -> &mut {1} {{ + &mut self.{0} + }}", + lc, + rust_return_type(p) + ); + } else if p.rust_by_function { + writeln!( + r, + " fn {}(&self, getter: F) + where + F: FnOnce({}), + {{ + getter(&self.{0}) + }}", + lc, + rust_return_type(p) + ); + } else { + writeln!(r, " fn {}(&self) -> {} {{", lc, rust_return_type(p)); + if p.is_complex() { + if p.optional { + writeln!(r, " self.{}.as_ref().map(|p| &p[..])", lc); + } else { + writeln!(r, " &self.{}", lc); + } + } else { + writeln!(r, " self.{}", lc); + } + writeln!(r, " }}"); + } + if !p.is_object() && p.write { + let bytearray = p.property_type == Type::Simple(SimpleType::QByteArray); + let (t, v) = if bytearray && p.optional { + ("Option<&[u8]>".to_string(), ".map(|v| v.to_vec())") + } else if bytearray { + ("&[u8]".to_string(), ".to_vec()") + } else { + (rust_type(p), "") + }; + writeln!( + r, + " fn set_{}(&mut self, value: {}) {{ + self.{0} = value{}; + self.emit.{0}_changed(); + }}", + lc, + t, + v + ); + } + } + if o.object_type == ObjectType::List { + writeln!( + r, + " fn row_count(&self) -> usize {{\n self.list.len()\n }}" + ); + } else if o.object_type == ObjectType::Tree { + writeln!( + r, + " fn row_count(&self, item: Option) -> usize {{ + self.list.len() + }} + fn index(&self, item: Option, row: usize) -> usize {{ + 0 + }} + fn parent(&self, index: usize) -> Option {{ + None + }} + fn row(&self, index: usize) -> usize {{ + index + }} + fn check_row(&self, index: usize, _row: usize) -> Option {{ + if index < self.list.len() {{ + Some(index) + }} else {{ + None + }} + }}" + ); + } + if o.object_type != ObjectType::Object { + for (name, ip) in &o.item_properties { + let lc = snake_case(name); + writeln!( + r, + " fn {}(&self, index: usize) -> {} {{", + lc, + rust_return_type_(ip) + ); + if ip.is_complex() && ip.optional { + writeln!( + r, + " self.list[index].{}.as_ref().map(|v| &v[..])", + lc + ); + } else if ip.is_complex() { + writeln!(r, " &self.list[index].{}", lc); + } else { + writeln!(r, " self.list[index].{}", lc); + } + writeln!(r, " }}"); + let bytearray = ip.item_property_type == SimpleType::QByteArray; + if ip.write && bytearray && ip.optional { + writeln!( + r, + " fn set_{}(&mut self, index: usize, v: Option<&[u8]>) -> bool {{ + self.list[index].{0} = v.map(|v| v.to_vec()); + true + }}", + lc + ); + } else if ip.write && bytearray { + writeln!( + r, + " fn set_{}(&mut self, index: usize, v: &[u8]) -> bool {{ + self.list[index].{0} = v.to_vec(); + true + }}", + lc + ); + } else if ip.write { + writeln!( + r, + " fn set_{}(&mut self, index: usize, v: {}) -> bool {{ + self.list[index].{0} = v; + true + }}", + lc, + rust_type_(ip) + ); + } + } + } + writeln!(r, "}}"); +} + +pub fn write_implementation(conf: &Config) -> Result<()> { + let mut file = conf.config_file + .parent() + .unwrap() + .join(&conf.rust.dir) + .join("src") + .join(&conf.rust.implementation_module); + file.set_extension("rs"); + if !conf.overwrite_implementation && file.exists() { + return Ok(()); + } + let mut r = Vec::new(); + writeln!( + r, + "#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(dead_code)] +use {}::*; +", + conf.rust.interface_module + ); + + for object in conf.objects.values() { + write_rust_implementation_object(&mut r, object); + } + write_if_different(file, &r) +} diff --git a/src/structs.h b/src/structs.h deleted file mode 100644 index 862451d..0000000 --- a/src/structs.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2017 Jos van den Oever - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include - -enum class ObjectType { - Object, - List, - Tree -}; - -enum class BindingType { - Bool, - Int8, - UInt8, - Int16, - UInt16, - Int32, - UInt32, - Int64, - UInt64, - Float, - Double, - QString, - QByteArray, - Object, - Void, -}; - -struct BindingTypeProperties { - BindingType type; - QString name; - QString cppSetType; - QString cSetType; - QString rustType; - QString rustTypeInit; - bool isComplex() const { - return name.startsWith("Q"); - } - bool operator==(const BindingTypeProperties& other) { - return type == other.type - && name == other.name - && cppSetType == other.cppSetType - && cSetType == other.cSetType - && rustType == other.rustType - && rustTypeInit == other.rustTypeInit; - } -}; - -struct Property { - QString name; - BindingTypeProperties type; - bool write; - bool optional; - bool rustByValue; - bool rustByFunction; -}; - -struct Argument { - QString name; - BindingTypeProperties type; -}; - -struct Function { - QString name; - BindingTypeProperties type; - QList args; - bool mut; -}; - -struct ItemProperty { - QString name; - BindingTypeProperties type; - bool write; - bool optional; - bool rustByValue; - QList> roles; -}; - -struct Object { - QString name; - ObjectType type; - QList properties; - QList itemProperties; - QList functions; - int columnCount; - bool containsObject() { - for (auto p: properties) { - if (p.type.type == BindingType::Object) { - return true; - } - } - return false; - } -}; - -struct Configuration { - QFileInfo hFile; - QFileInfo cppFile; - QDir rustdir; - QString interfaceModule; - QString implementationModule; - QList objects; - bool overwriteImplementation; - const Object& findObject(const QString& name) const { - for (auto& o: objects) { - if (o.name == name) { - return o; - } - } - QTextStream err(stderr); - err << QCoreApplication::translate("main", - "Cannot find type %1.\n").arg(name); - err.flush(); - exit(1); - } - QList types() const { - QList ops; - for (auto o: objects) { - for (auto ip: o.properties) { - if (!ops.contains(ip.type.name)) { - ops.append(ip.type.name); - } - } - for (auto ip: o.itemProperties) { - if (!ops.contains(ip.type.name)) { - ops.append(ip.type.name); - } - } - for (auto f: o.functions) { - if (!ops.contains(f.type.name)) { - ops.append(f.type.name); - } - for (auto a: f.args) { - if (!ops.contains(a.type.name)) { - ops.append(a.type.name); - } - } - } - } - return ops; - } - QList optionalTypes() const { - QList ops; - for (auto o: objects) { - for (auto ip: o.properties) { - if (ip.optional && !ops.contains(ip.type.name)) { - ops.append(ip.type.name); - } - } - for (auto ip: o.itemProperties) { - if (ip.optional && !ops.contains(ip.type.name)) { - ops.append(ip.type.name); - } - } - if (o.type != ObjectType::Object && !ops.contains("quintptr")) { - ops.append("quintptr"); - } - } - return ops; - } - bool hasListOrTree() const { - for (auto o: objects) { - if (o.type == ObjectType::List || o.type == ObjectType::Tree) { - return true; - } - } - return false; - } -}; - diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..c2a16a7 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,18 @@ +use std::fs; +use std::path::Path; +use std::io::Result; +use regex::Regex; + +pub fn write_if_different>(path: P, contents: &[u8]) -> Result<()> { + let old_contents = fs::read(&path).ok(); + if old_contents.map(|c| c == contents).unwrap_or(false) { + Ok(()) + } else { + fs::write(path, contents) + } +} + +pub fn snake_case(name: &str) -> String { + let re = Regex::new("([A-Z])").unwrap(); + (name[..1].to_string() + &re.replace_all(&name[1..], "_$1")).to_lowercase() +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e692777..b34b41d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ -SET(GENERATOR "${CMAKE_BINARY_DIR}/src/rust_qt_binding_generator") +set(GENERATOR "${RustQtBindingGenerator_EXECUTABLE}") include_directories("${CMAKE_CURRENT_BINARY_DIR}") add_custom_target("clean-rust")