mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-06 19:58:53 +00:00

The generated file offers the following two pieces of functionality: - Convenient front-end to slint.load_file() by loading the .slint file (replaces use of auto-loader) - More importantly: Type information for exported properties, callbacks, functions, and globals
446 lines
15 KiB
Rust
446 lines
15 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
/*! module for the C++ code generator
|
|
*/
|
|
|
|
// cSpell:ignore cmath constexpr cstdlib decltype intptr itertools nullptr prepended struc subcomponent uintptr vals
|
|
|
|
use std::collections::HashSet;
|
|
use std::sync::OnceLock;
|
|
|
|
use smol_str::{format_smolstr, SmolStr, StrExt};
|
|
|
|
// Check if word is one of Python keywords
|
|
// (https://docs.python.org/3/reference/lexical_analysis.html#keywords)
|
|
fn is_python_keyword(word: &str) -> bool {
|
|
static PYTHON_KEYWORDS: OnceLock<HashSet<&'static str>> = OnceLock::new();
|
|
let keywords = PYTHON_KEYWORDS.get_or_init(|| {
|
|
let keywords: HashSet<&str> = HashSet::from([
|
|
"False", "await", "else", "import", "pass", "None", "break", "except", "in", "raise",
|
|
"True", "class", "finally", "is", "return", "and", "continue", "for", "lambda", "try",
|
|
"as", "def", "from", "nonlocal", "while", "assert", "del", "global", "not", "with",
|
|
"async", "elif", "if", "or", "yield",
|
|
]);
|
|
keywords
|
|
});
|
|
keywords.contains(word)
|
|
}
|
|
|
|
fn ident(ident: &str) -> SmolStr {
|
|
let mut new_ident = SmolStr::from(ident);
|
|
if ident.contains('-') {
|
|
new_ident = ident.replace_smolstr("-", "_");
|
|
}
|
|
if is_python_keyword(new_ident.as_str()) {
|
|
new_ident = format_smolstr!("{}_", new_ident);
|
|
}
|
|
new_ident
|
|
}
|
|
|
|
/// This module contains some data structures that helps represent a Python file.
|
|
/// It is then rendered into an actual Python code using the Display trait
|
|
mod python_ast {
|
|
|
|
use std::fmt::{Display, Error, Formatter};
|
|
|
|
use smol_str::SmolStr;
|
|
|
|
///A full Python file
|
|
#[derive(Default, Debug)]
|
|
pub struct File {
|
|
pub imports: Vec<SmolStr>,
|
|
pub declarations: Vec<Declaration>,
|
|
pub trailing_code: Vec<SmolStr>,
|
|
}
|
|
|
|
impl Display for File {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
|
writeln!(f, "# This file is auto-generated\n")?;
|
|
for import in &self.imports {
|
|
writeln!(f, "import {}", import)?;
|
|
}
|
|
writeln!(f, "")?;
|
|
for decl in &self.declarations {
|
|
writeln!(f, "{}", decl)?;
|
|
}
|
|
for code in &self.trailing_code {
|
|
writeln!(f, "{}", code)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, derive_more::Display)]
|
|
pub enum Declaration {
|
|
Class(Class),
|
|
Variable(Variable),
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct Class {
|
|
pub name: SmolStr,
|
|
pub super_class: Option<SmolStr>,
|
|
pub fields: Vec<Field>,
|
|
pub function_declarations: Vec<FunctionDeclaration>,
|
|
}
|
|
|
|
impl Display for Class {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
if let Some(super_class) = self.super_class.as_ref() {
|
|
writeln!(f, "class {}({}):", self.name, super_class)?;
|
|
} else {
|
|
writeln!(f, "class {}:", self.name)?;
|
|
}
|
|
if self.fields.is_empty() && self.function_declarations.is_empty() {
|
|
writeln!(f, " pass")?;
|
|
return Ok(());
|
|
}
|
|
|
|
for field in &self.fields {
|
|
writeln!(f, " {}", field)?;
|
|
}
|
|
|
|
if !self.fields.is_empty() {
|
|
writeln!(f, "")?;
|
|
}
|
|
|
|
for fundecl in &self.function_declarations {
|
|
writeln!(f, " {}", fundecl)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Variable {
|
|
pub name: SmolStr,
|
|
pub value: SmolStr,
|
|
}
|
|
|
|
impl Display for Variable {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
writeln!(f, "{} = {}", self.name, self.value)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PyType {
|
|
pub name: SmolStr,
|
|
pub optional: bool,
|
|
}
|
|
|
|
impl Display for PyType {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
if self.optional {
|
|
write!(f, "typing.Optional[{}]", self.name)
|
|
} else {
|
|
write!(f, "{}", self.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Field {
|
|
pub name: SmolStr,
|
|
pub ty: Option<PyType>,
|
|
pub default_value: Option<SmolStr>,
|
|
}
|
|
|
|
impl Display for Field {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.name)?;
|
|
if let Some(ty) = &self.ty {
|
|
write!(f, ": {}", ty)?;
|
|
}
|
|
if let Some(default_value) = &self.default_value {
|
|
write!(f, " = {}", default_value)?
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct FunctionDeclaration {
|
|
pub name: SmolStr,
|
|
pub positional_parameters: Vec<SmolStr>,
|
|
pub keyword_parameters: Vec<Field>,
|
|
pub return_type: Option<PyType>,
|
|
}
|
|
|
|
impl Display for FunctionDeclaration {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "def {}(self", self.name)?;
|
|
|
|
if !self.positional_parameters.is_empty() {
|
|
write!(f, ", {}", self.positional_parameters.join(","))?;
|
|
}
|
|
|
|
if !self.keyword_parameters.is_empty() {
|
|
write!(f, ", *")?;
|
|
write!(
|
|
f,
|
|
", {}",
|
|
self.keyword_parameters
|
|
.iter()
|
|
.map(ToString::to_string)
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)?;
|
|
}
|
|
writeln!(
|
|
f,
|
|
") -> {}: ...",
|
|
self.return_type.as_ref().map_or(std::borrow::Cow::Borrowed("None"), |ty| {
|
|
std::borrow::Cow::Owned(ty.to_string())
|
|
})
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
use crate::langtype::Type;
|
|
|
|
use crate::llr;
|
|
use crate::object_tree::Document;
|
|
use crate::CompilerConfiguration;
|
|
use itertools::{Either, Itertools};
|
|
use python_ast::*;
|
|
|
|
/// Returns the text of the C++ code produced by the given root component
|
|
pub fn generate(
|
|
doc: &Document,
|
|
compiler_config: &CompilerConfiguration,
|
|
destination_path: Option<&std::path::Path>,
|
|
) -> std::io::Result<impl std::fmt::Display> {
|
|
let mut file = File { ..Default::default() };
|
|
file.imports.push(SmolStr::new_static("slint"));
|
|
file.imports.push(SmolStr::new_static("typing"));
|
|
|
|
let mut need_enums_import = false;
|
|
|
|
for ty in &doc.used_types.borrow().structs_and_enums {
|
|
match ty {
|
|
Type::Struct(s) => {
|
|
if let Some(name) = &s.name {
|
|
let fields = s
|
|
.fields
|
|
.iter()
|
|
.map(|(name, ty)| Field {
|
|
name: ident(name),
|
|
ty: Some(PyType { name: python_type_name(ty), optional: false }),
|
|
default_value: None,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let ctor = FunctionDeclaration {
|
|
name: SmolStr::new_static("__init__"),
|
|
positional_parameters: Vec::default(),
|
|
keyword_parameters: fields
|
|
.iter()
|
|
.map(|field| {
|
|
let mut kw_field = field.clone();
|
|
kw_field.ty.as_mut().unwrap().optional = true;
|
|
kw_field.default_value = Some(SmolStr::new_static("None"));
|
|
kw_field
|
|
})
|
|
.collect(),
|
|
return_type: None,
|
|
};
|
|
|
|
let struct_class = Class {
|
|
name: name.clone(),
|
|
fields,
|
|
function_declarations: vec![ctor],
|
|
..Default::default()
|
|
};
|
|
file.declarations.push(Declaration::Class(struct_class));
|
|
}
|
|
}
|
|
Type::Enumeration(en) => {
|
|
need_enums_import = true;
|
|
file.declarations.push(Declaration::Class(Class {
|
|
name: en.name.clone(),
|
|
super_class: Some(SmolStr::new_static("enum.StrEnum")),
|
|
fields: en
|
|
.values
|
|
.iter()
|
|
.map(|val| Field {
|
|
name: ident(&val),
|
|
ty: None,
|
|
default_value: Some(format_smolstr!("\"{}\"", val)),
|
|
})
|
|
.collect(),
|
|
function_declarations: vec![],
|
|
}))
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
if need_enums_import {
|
|
file.imports.push(SmolStr::new_static("enum"));
|
|
}
|
|
|
|
let llr = llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config)?;
|
|
|
|
let globals = llr.globals.iter().filter(|glob| glob.exported && glob.must_generate());
|
|
|
|
for global in globals.clone() {
|
|
generate_global(global, &mut file);
|
|
}
|
|
|
|
for public_component in &llr.public_components {
|
|
generate_public_component(&public_component, globals.clone(), &mut file);
|
|
}
|
|
|
|
file.declarations.extend(generate_named_exports(&doc.exports));
|
|
|
|
let main_file = std::path::absolute(
|
|
doc.node
|
|
.as_ref()
|
|
.ok_or_else(|| std::io::Error::other("Cannot determine path of the main file"))?
|
|
.source_file
|
|
.path(),
|
|
)
|
|
.unwrap();
|
|
|
|
let destination_path = destination_path.and_then(|maybe_relative_destination_path| {
|
|
std::path::absolute(maybe_relative_destination_path)
|
|
.ok()
|
|
.and_then(|p| p.parent().map(std::path::PathBuf::from))
|
|
});
|
|
|
|
let relative_path_from_destination_to_main_file =
|
|
destination_path.and_then(|destination_path| {
|
|
pathdiff::diff_paths(main_file.parent().unwrap(), destination_path)
|
|
});
|
|
|
|
if let Some(relative_path_from_destination_to_main_file) =
|
|
relative_path_from_destination_to_main_file
|
|
{
|
|
file.imports.push(SmolStr::new_static("os"));
|
|
file.trailing_code.push(format_smolstr!(
|
|
"globals().update(vars(slint.load_file(os.path.join(os.path.dirname(__file__), '{}'))))",
|
|
relative_path_from_destination_to_main_file.join(main_file.file_name().unwrap()).to_string_lossy()
|
|
));
|
|
}
|
|
|
|
Ok(file)
|
|
}
|
|
|
|
fn generate_global(global: &llr::GlobalComponent, file: &mut File) {
|
|
let global_name = ident(&global.name);
|
|
|
|
let mut class = Class { name: global_name.clone(), ..Default::default() };
|
|
|
|
class.fields = generate_fields_for_public_properties(&global.public_properties).collect();
|
|
|
|
file.declarations.push(Declaration::Class(class));
|
|
|
|
file.declarations.extend(global.aliases.iter().map(|exported_name| {
|
|
Declaration::Variable(Variable { name: ident(&exported_name), value: global_name.clone() })
|
|
}))
|
|
}
|
|
|
|
fn generate_public_component<'a>(
|
|
component: &'a llr::PublicComponent,
|
|
globals: impl Iterator<Item = &'a llr::GlobalComponent>,
|
|
file: &mut File,
|
|
) {
|
|
let mut class = Class {
|
|
name: ident(&component.name),
|
|
super_class: Some(SmolStr::new_static("slint.Component")),
|
|
..Default::default()
|
|
};
|
|
|
|
class.fields = generate_fields_for_public_properties(&component.public_properties)
|
|
.chain(globals.map(|glob| {
|
|
let glob_name = ident(&glob.name);
|
|
Field {
|
|
name: glob_name.clone(),
|
|
ty: Some(PyType { name: glob_name, optional: false }),
|
|
default_value: None,
|
|
}
|
|
}))
|
|
.collect();
|
|
|
|
file.declarations.push(Declaration::Class(class));
|
|
}
|
|
|
|
fn generate_fields_for_public_properties(
|
|
public_properties: &llr::PublicProperties,
|
|
) -> impl Iterator<Item = Field> + '_ {
|
|
public_properties.iter().map(|property| Field {
|
|
name: ident(&property.name),
|
|
ty: Some(PyType { name: python_type_name(&property.ty), optional: false }),
|
|
default_value: None,
|
|
})
|
|
}
|
|
|
|
pub fn generate_named_exports(
|
|
exports: &crate::object_tree::Exports,
|
|
) -> impl Iterator<Item = Declaration> + '_ {
|
|
exports
|
|
.iter()
|
|
.filter_map(|export| match &export.1 {
|
|
Either::Left(component) if !component.is_global() => {
|
|
Some((&export.0.name, &component.id))
|
|
}
|
|
Either::Right(ty) => match &ty {
|
|
Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
|
|
Some((&export.0.name, s.name.as_ref().unwrap()))
|
|
}
|
|
Type::Enumeration(en) => Some((&export.0.name, &en.name)),
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
})
|
|
.filter(|(export_name, type_name)| export_name != type_name)
|
|
.map(|(export_name, type_name)| {
|
|
let type_id = ident(type_name);
|
|
let export_id = ident(export_name);
|
|
Declaration::Variable(Variable { name: export_id, value: type_id })
|
|
})
|
|
}
|
|
|
|
fn python_type_name(ty: &Type) -> SmolStr {
|
|
match ty {
|
|
Type::Invalid => panic!("Invalid type encountered in llr output"),
|
|
Type::Void => SmolStr::new_static("None"),
|
|
Type::String => SmolStr::new_static("str"),
|
|
Type::Color => SmolStr::new_static("slint.Color"),
|
|
Type::Float32
|
|
| Type::Int32
|
|
| Type::Duration
|
|
| Type::Angle
|
|
| Type::PhysicalLength
|
|
| Type::LogicalLength
|
|
| Type::Percent
|
|
| Type::UnitProduct(_) => SmolStr::new_static("float"),
|
|
Type::Image => SmolStr::new_static("slint.Image"),
|
|
Type::Bool => SmolStr::new_static("bool"),
|
|
Type::Brush => SmolStr::new_static("Brush"),
|
|
Type::Array(elem_type) => format_smolstr!("slint.Model[{}]", python_type_name(elem_type)),
|
|
Type::Struct(s) => match (&s.name, &s.node) {
|
|
(Some(name), Some(_)) => ident(name),
|
|
(Some(name), None) => todo!(),
|
|
_ => {
|
|
let tuple_types =
|
|
s.fields.values().map(|ty| python_type_name(ty)).collect::<Vec<_>>();
|
|
format_smolstr!("typing.Tuple[{}]", tuple_types.join(", "))
|
|
}
|
|
},
|
|
Type::Enumeration(enumeration) => ident(&enumeration.name),
|
|
Type::Callback(function) | Type::Function(function) => {
|
|
format_smolstr!(
|
|
"typing.Callable[[{}], {}]",
|
|
function.args.iter().map(|arg_ty| python_type_name(arg_ty)).join(", "),
|
|
python_type_name(&function.return_type)
|
|
)
|
|
}
|
|
ty @ _ => unimplemented!("implemented type conversion {:#?}", ty),
|
|
}
|
|
}
|