mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-29 13:24:48 +00:00
250 lines
8.9 KiB
Rust
250 lines
8.9 KiB
Rust
/*!
|
|
This module contains the intermediate representation of the code in the form of an object tree
|
|
*/
|
|
|
|
use crate::diagnostics::Diagnostics;
|
|
use crate::expressions::Expression;
|
|
use crate::parser::{Spanned, SyntaxKind, SyntaxNode, SyntaxNodeEx};
|
|
use crate::typeregister::{Type, TypeRegister};
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::rc::Rc;
|
|
/// The full document (a complete file)
|
|
#[derive(Default, Debug)]
|
|
pub struct Document {
|
|
// node: SyntaxNode,
|
|
pub inner_components: Vec<Rc<Component>>,
|
|
pub root_component: Rc<Component>,
|
|
}
|
|
|
|
impl Document {
|
|
pub fn from_node(node: SyntaxNode, diag: &mut Diagnostics, tr: &mut TypeRegister) -> Self {
|
|
debug_assert_eq!(node.kind(), SyntaxKind::Document);
|
|
|
|
let inner_components = node
|
|
.children()
|
|
.filter(|n| n.kind() == SyntaxKind::Component)
|
|
.map(|n| {
|
|
let compo = Rc::new(Component::from_node(n, diag, tr));
|
|
tr.add(compo.clone());
|
|
compo
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
Document {
|
|
// FIXME: one should use the `component` hint instead of always returning the last
|
|
root_component: inner_components.last().cloned().unwrap_or_default(),
|
|
|
|
inner_components,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A component is a type in the language which can be instantiated
|
|
#[derive(Default, Debug)]
|
|
pub struct Component {
|
|
// node: SyntaxNode,
|
|
pub id: String,
|
|
pub root_element: Rc<RefCell<Element>>,
|
|
}
|
|
|
|
impl Component {
|
|
pub fn from_node(node: SyntaxNode, diag: &mut Diagnostics, tr: &TypeRegister) -> Self {
|
|
debug_assert_eq!(node.kind(), SyntaxKind::Component);
|
|
Component {
|
|
id: node.child_text(SyntaxKind::Identifier).unwrap_or_default(),
|
|
root_element: Rc::new(RefCell::new(
|
|
node.child_node(SyntaxKind::Element).map_or_else(Default::default, |n| {
|
|
Element::from_node(n, "root".into(), diag, tr)
|
|
}),
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An Element is an instentation of a Component
|
|
#[derive(Default, Debug)]
|
|
pub struct Element {
|
|
/* node: SyntaxNode, */
|
|
/// The id as named in the original .60 file
|
|
pub id: String,
|
|
pub lowered_id: String,
|
|
pub base: QualifiedTypeName,
|
|
pub base_type: crate::typeregister::Type,
|
|
/// Currently contains also the signals. FIXME: should that be changed?
|
|
pub bindings: HashMap<String, Expression>,
|
|
pub children: Vec<Rc<RefCell<Element>>>,
|
|
|
|
/// This should probably be in the Component instead
|
|
pub signals_declaration: Vec<String>,
|
|
pub property_declarations: Vec<PropertyDeclaration>,
|
|
}
|
|
|
|
impl Element {
|
|
pub fn from_node(
|
|
node: SyntaxNode,
|
|
id: String,
|
|
diag: &mut Diagnostics,
|
|
tr: &TypeRegister,
|
|
) -> Self {
|
|
debug_assert_eq!(node.kind(), SyntaxKind::Element);
|
|
let mut r = Element {
|
|
id,
|
|
base: QualifiedTypeName::from_node(
|
|
node.children()
|
|
.filter(|n| n.kind() == SyntaxKind::QualifiedTypeName)
|
|
.nth(0)
|
|
.unwrap(),
|
|
),
|
|
..Default::default()
|
|
};
|
|
r.base_type = tr.lookup(&r.base.to_string());
|
|
if !r.base_type.is_object_type() {
|
|
diag.push_error(format!("Unkown type {}", r.base), node.span());
|
|
return r;
|
|
}
|
|
for b in node.children().filter(|n| n.kind() == SyntaxKind::Binding) {
|
|
let name_token = match b.child_token(SyntaxKind::Identifier) {
|
|
Some(x) => x,
|
|
None => continue,
|
|
};
|
|
let name = name_token.text().to_string();
|
|
let prop_type = r.base_type.lookup_property(&name);
|
|
if !prop_type.is_property_type() {
|
|
diag.push_error(
|
|
match prop_type {
|
|
Type::Invalid => format!("Unkown property {} in {}", name, r.base),
|
|
Type::Signal => format!("'{}' is a signal. use `=>` to connect", name),
|
|
_ => format!("Cannot assing to {} in {}", name, r.base),
|
|
},
|
|
crate::diagnostics::Span::new(name_token.text_range().start().into()),
|
|
);
|
|
}
|
|
if let Some(csn) = b.child_node(SyntaxKind::CodeStatement) {
|
|
if r.bindings.insert(name, Expression::Uncompiled(csn)).is_some() {
|
|
diag.push_error(
|
|
"Duplicated property".into(),
|
|
crate::diagnostics::Span::new(name_token.text_range().start().into()),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
for con_node in node.children().filter(|n| n.kind() == SyntaxKind::SignalConnection) {
|
|
let name_token = match con_node.child_token(SyntaxKind::Identifier) {
|
|
Some(x) => x,
|
|
None => continue,
|
|
};
|
|
let name = name_token.text().to_string();
|
|
let prop_type = r.base_type.lookup_property(&name);
|
|
if !matches!(prop_type, Type::Signal) {
|
|
diag.push_error(
|
|
format!("'{}' is not a signal in {}", name, r.base),
|
|
crate::diagnostics::Span::new(name_token.text_range().start().into()),
|
|
);
|
|
}
|
|
if let Some(csn) = con_node.child_node(SyntaxKind::CodeBlock) {
|
|
if r.bindings.insert(name, Expression::Uncompiled(csn)).is_some() {
|
|
diag.push_error(
|
|
"Duplicated signal".into(),
|
|
crate::diagnostics::Span::new(name_token.text_range().start().into()),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
for sig_decl in node.children().filter(|n| n.kind() == SyntaxKind::SignalDeclaration) {
|
|
// We need to go reverse to skip the "signal" token
|
|
let name_token = match sig_decl
|
|
.children_with_tokens()
|
|
.filter(|n| n.kind() == SyntaxKind::Identifier)
|
|
.last()
|
|
{
|
|
Some(x) => x.into_token().unwrap(),
|
|
None => continue,
|
|
};
|
|
let name = name_token.text().to_string();
|
|
r.signals_declaration.push(name);
|
|
}
|
|
|
|
for prop_decl in node.children().filter(|n| n.kind() == SyntaxKind::PropertyDeclaration) {
|
|
r.property_declarations.push(PropertyDeclaration::from_node(prop_decl, tr, diag));
|
|
}
|
|
|
|
for se in node.children() {
|
|
if se.kind() == SyntaxKind::SubElement {
|
|
let id = se.child_text(SyntaxKind::Identifier).unwrap_or_default();
|
|
if let Some(element_node) = se.child_node(SyntaxKind::Element) {
|
|
r.children.push(Rc::new(RefCell::new(Element::from_node(
|
|
element_node,
|
|
id,
|
|
diag,
|
|
tr,
|
|
))));
|
|
} else {
|
|
assert!(diag.has_error());
|
|
}
|
|
} else if se.kind() == SyntaxKind::RepeatedElement {
|
|
diag.push_error("TODO: for not implemented".to_owned(), se.span())
|
|
}
|
|
}
|
|
r
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
pub struct PropertyDeclaration {
|
|
qualified_type: QualifiedTypeName,
|
|
property_type: Type,
|
|
name: String,
|
|
}
|
|
|
|
impl PropertyDeclaration {
|
|
pub fn from_node(node: SyntaxNode, tr: &TypeRegister, diag: &mut Diagnostics) -> Self {
|
|
debug_assert_eq!(node.kind(), SyntaxKind::PropertyDeclaration);
|
|
let mut d = Self::default();
|
|
|
|
let qualified_type_node =
|
|
node.children().filter(|n| n.kind() == SyntaxKind::QualifiedTypeName).nth(0).unwrap();
|
|
let type_span = qualified_type_node.span();
|
|
d.qualified_type = QualifiedTypeName::from_node(qualified_type_node);
|
|
|
|
d.property_type = tr.lookup_qualified(&d.qualified_type.members);
|
|
|
|
match d.property_type {
|
|
Type::Invalid => {
|
|
diag.push_error(
|
|
format!("unknown property type '{}'", d.qualified_type.to_string()),
|
|
type_span,
|
|
);
|
|
}
|
|
_ => (),
|
|
};
|
|
|
|
d.name = node.child_text(SyntaxKind::Identifier).unwrap();
|
|
|
|
d
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
pub struct QualifiedTypeName {
|
|
members: Vec<String>,
|
|
}
|
|
|
|
impl QualifiedTypeName {
|
|
pub fn from_node(node: SyntaxNode) -> Self {
|
|
debug_assert_eq!(node.kind(), SyntaxKind::QualifiedTypeName);
|
|
let members = node
|
|
.children_with_tokens()
|
|
.filter(|n| n.kind() == SyntaxKind::Identifier)
|
|
.filter_map(|x| x.as_token().map(|x| x.text().to_string()))
|
|
.collect();
|
|
Self { members }
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for QualifiedTypeName {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.members.join("."))
|
|
}
|
|
}
|