Port rust_qt_binding_generator from C++ to Rust

master
Jos van den Oever 2018-09-26 16:05:37 +02:00
parent 0294ed948b
commit d7e4f1362d
21 changed files with 3594 additions and 3095 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
**/Cargo.lock
**/target/
**/*.rs.bk
/docker_home/
# tutorial build

View File

@ -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)

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "rust_qt_binding_generator"
version = "0.1.0"
authors = ["Jos van den Oever <jos@vandenoever.info>"]
[dependencies]
clap = "2"
regex= "1"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"

View File

@ -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

View File

@ -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<option_quintptr>::value, "option_quintptr must be a POD type.");
struct option_quint64 {
public:
quint64 value;
@ -29,6 +16,19 @@ namespace {
};
static_assert(std::is_pod<option_quint64>::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<option_quintptr>::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);

487
src/configuration.rs Normal file
View File

@ -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<String, Object>,
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<String, super::Function>,
#[serde(rename = "itemProperties", default)]
pub item_properties: BTreeMap<String, super::ItemProperty>,
#[serde(rename = "type", default = "object")]
pub object_type: super::ObjectType,
#[serde(default)]
pub properties: BTreeMap<String, Property>,
}
#[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<String, Rc<Object>>,
pub rust: Rust,
pub overwrite_implementation: bool,
}
impl Config {
pub fn types(&self) -> BTreeSet<String> {
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<String> {
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<String, Function>,
pub item_properties: BTreeMap<String, ItemProperty>,
pub object_type: ObjectType,
pub properties: BTreeMap<String, Property>,
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<u8>",
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<Object>),
}
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<Vec<String>>,
#[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<Argument>,
}
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<String, Rc<Object>>,
c: &BTreeMap<String, json::Object>,
) -> Result<Property, Box<Error>> {
let name = &a.1.property_type;
let t = match serde_json::from_str::<SimpleType>(&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<String, Rc<Object>>,
c: &BTreeMap<String, json::Object>,
) -> Result<(), Box<Error>> {
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<Config, Box<Error>> {
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<P: AsRef<Path>>(config_file: P) -> Result<Config, Box<Error>> {
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)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
/*
* Copyright 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <http://www.gnu.org/licenses/>.
*/
struct Configuration;
void writeHeader(const Configuration& conf);
void writeCpp(const Configuration& conf);

1518
src/cpp.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
/*
* Copyright 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QTextStream>
QTextStream err(stderr);

View File

@ -1,66 +0,0 @@
/*
* Copyright 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QRegExp>
#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
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();
}
};

28
src/lib.rs Normal file
View File

@ -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<P: AsRef<Path> + Display>(
config_file: P,
overwrite_implementation: bool,
) -> Result<(), Box<Error>> {
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(())
}

View File

@ -1,68 +0,0 @@
/*
* Copyright 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "parseJson.h"
#include "cpp.h"
#include "rust.h"
#include "helper.h"
#include <QCommandLineParser>
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;
}

View File

@ -1,348 +0,0 @@
/*
* Copyright 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "parseJson.h"
#include "helper.h"
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonArray>
#include <QMetaEnum>
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>& bindingTypeProperties() {
static QList<BindingTypeProperties> p;
if (p.empty()) {
QList<BindingTypeProperties> 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<u8>",
.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<Argument>
parseArguments(const QJsonArray& json) {
QList<Argument> 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<Qt::ItemDataRole>()
.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<Qt::ItemDataRole> 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;
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "structs.h"
Configuration parseConfiguration(const QString& path);

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
/*
* Copyright 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class Configuration;
void writeRustInterface(const Configuration& conf);
void writeRustImplementation(const Configuration& conf);

1503
src/rust.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,194 +0,0 @@
/*
* Copyright 2017 Jos van den Oever <jos@vandenoever.info>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QString>
#include <QByteArray>
#include <QList>
#include <QFileInfo>
#include <QDir>
#include <QTextStream>
#include <QCoreApplication>
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<Argument> args;
bool mut;
};
struct ItemProperty {
QString name;
BindingTypeProperties type;
bool write;
bool optional;
bool rustByValue;
QList<QList<Qt::ItemDataRole>> roles;
};
struct Object {
QString name;
ObjectType type;
QList<Property> properties;
QList<ItemProperty> itemProperties;
QList<Function> 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<Object> 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<QString> types() const {
QList<QString> 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<QString> optionalTypes() const {
QList<QString> 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;
}
};

18
src/util.rs Normal file
View File

@ -0,0 +1,18 @@
use std::fs;
use std::path::Path;
use std::io::Result;
use regex::Regex;
pub fn write_if_different<P: AsRef<Path>>(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()
}

View File

@ -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")