| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | /*! |
| 5 | Parse the contents of builtins.slint and fill the builtin type registry |
| 6 | */ |
| 7 | |
| 8 | use smol_str::{SmolStr, ToSmolStr}; |
| 9 | use std::cell::RefCell; |
| 10 | use std::collections::HashMap; |
| 11 | use std::rc::Rc; |
| 12 | |
| 13 | use crate::expression_tree::Expression; |
| 14 | use crate::langtype::{ |
| 15 | BuiltinElement, BuiltinPropertyDefault, BuiltinPropertyInfo, DefaultSizeBinding, ElementType, |
| 16 | Function, NativeClass, Type, |
| 17 | }; |
| 18 | use crate::object_tree::{self, *}; |
| 19 | use crate::parser::{identifier_text, syntax_nodes, SyntaxKind, SyntaxNode}; |
| 20 | use crate::typeregister::TypeRegister; |
| 21 | |
| 22 | /// Parse the contents of builtins.slint and fill the builtin type registry |
| 23 | /// `register` is the register to fill with the builtin types. |
| 24 | /// At this point, it really should already contain the basic Types (string, int, ...) |
| 25 | pub(crate) fn load_builtins(register: &mut TypeRegister) { |
| 26 | let mut diag = crate::diagnostics::BuildDiagnostics::default(); |
| 27 | let node = crate::parser::parse(include_str!("builtins.slint" ).into(), None, &mut diag); |
| 28 | if !diag.is_empty() { |
| 29 | let vec = diag.to_string_vec(); |
| 30 | #[cfg (feature = "display-diagnostics" )] |
| 31 | diag.print(); |
| 32 | panic!("Error parsing the builtin elements: {vec:?}" ); |
| 33 | } |
| 34 | |
| 35 | assert_eq!(node.kind(), crate::parser::SyntaxKind::Document); |
| 36 | let doc: syntax_nodes::Document = node.into(); |
| 37 | |
| 38 | let mut natives = HashMap::<SmolStr, Rc<BuiltinElement>>::new(); |
| 39 | |
| 40 | let exports = doc |
| 41 | .ExportsList() |
| 42 | .flat_map(|e| { |
| 43 | e.Component() |
| 44 | .map(|x| { |
| 45 | let x = identifier_text(&x.DeclaredIdentifier()).unwrap(); |
| 46 | (x.clone(), x) |
| 47 | }) |
| 48 | .into_iter() |
| 49 | .chain(e.ExportSpecifier().map(|e| { |
| 50 | ( |
| 51 | identifier_text(&e.ExportIdentifier()).unwrap(), |
| 52 | identifier_text(&e.ExportName().unwrap()).unwrap(), |
| 53 | ) |
| 54 | })) |
| 55 | }) |
| 56 | .collect::<HashMap<_, _>>(); |
| 57 | |
| 58 | for c in doc.Component().chain(doc.ExportsList().filter_map(|e| e.Component())) { |
| 59 | let id = identifier_text(&c.DeclaredIdentifier()).unwrap(); |
| 60 | let e = c.Element(); |
| 61 | let diag = RefCell::new(&mut diag); |
| 62 | let mut n = NativeClass::new_with_properties( |
| 63 | &id, |
| 64 | e.PropertyDeclaration() |
| 65 | .filter(|p| p.TwoWayBinding().is_none()) // aliases are handled further down |
| 66 | .map(|p| { |
| 67 | let prop_name = identifier_text(&p.DeclaredIdentifier()).unwrap(); |
| 68 | |
| 69 | let mut info = BuiltinPropertyInfo::new(object_tree::type_from_node( |
| 70 | p.Type().unwrap(), |
| 71 | *diag.borrow_mut(), |
| 72 | register, |
| 73 | )); |
| 74 | |
| 75 | info.property_visibility = PropertyVisibility::Private; |
| 76 | |
| 77 | for token in p.children_with_tokens() { |
| 78 | if token.kind() != SyntaxKind::Identifier { |
| 79 | continue; |
| 80 | } |
| 81 | match (token.as_token().unwrap().text(), info.property_visibility) { |
| 82 | ("in" , PropertyVisibility::Private) => { |
| 83 | info.property_visibility = PropertyVisibility::Input |
| 84 | } |
| 85 | ("out" , PropertyVisibility::Private) => { |
| 86 | info.property_visibility = PropertyVisibility::Output |
| 87 | } |
| 88 | ("in-out" , PropertyVisibility::Private) => { |
| 89 | info.property_visibility = PropertyVisibility::InOut |
| 90 | } |
| 91 | ("property" , _) => (), |
| 92 | _ => unreachable!("invalid property keyword when parsing builtin file for property {id}:: {prop_name}" ), |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | if let Some(e) = p.BindingExpression() { |
| 97 | let ty = info.ty.clone(); |
| 98 | info.default_value = BuiltinPropertyDefault::Expr(compiled(e, register, ty)); |
| 99 | } |
| 100 | |
| 101 | (prop_name, info) |
| 102 | }) |
| 103 | .chain(e.CallbackDeclaration().map(|s| { |
| 104 | ( |
| 105 | identifier_text(&s.DeclaredIdentifier()).unwrap(), |
| 106 | BuiltinPropertyInfo::new(Type::Callback(Rc::new(Function{ |
| 107 | args: s |
| 108 | .CallbackDeclarationParameter() |
| 109 | .map(|a| { |
| 110 | object_tree::type_from_node(a.Type(), *diag.borrow_mut(), register) |
| 111 | }) |
| 112 | .collect(), |
| 113 | return_type: s.ReturnType().map(|a| { |
| 114 | object_tree::type_from_node( |
| 115 | a.Type(), |
| 116 | *diag.borrow_mut(), |
| 117 | register, |
| 118 | ) |
| 119 | }).unwrap_or(Type::Void), |
| 120 | arg_names: s |
| 121 | .CallbackDeclarationParameter() |
| 122 | .map(|a| a.DeclaredIdentifier().and_then(|x| identifier_text(&x)).unwrap_or_default()) |
| 123 | .collect() |
| 124 | }))), |
| 125 | ) |
| 126 | })) |
| 127 | ); |
| 128 | n.deprecated_aliases = e |
| 129 | .PropertyDeclaration() |
| 130 | .flat_map(|p| { |
| 131 | if let Some(twb) = p.TwoWayBinding() { |
| 132 | let alias_name = identifier_text(&p.DeclaredIdentifier()).unwrap(); |
| 133 | let alias_target = identifier_text(&twb.Expression().QualifiedName().expect( |
| 134 | "internal error: built-in aliases can only be declared within the type" , |
| 135 | )) |
| 136 | .unwrap(); |
| 137 | Some((alias_name, alias_target)) |
| 138 | } else { |
| 139 | None |
| 140 | } |
| 141 | }) |
| 142 | .collect(); |
| 143 | n.cpp_type = parse_annotation("cpp_type" , &e).map(|x| x.unwrap()); |
| 144 | n.rust_type_constructor = parse_annotation("rust_type_constructor" , &e).map(|x| x.unwrap()); |
| 145 | enum Base { |
| 146 | None, |
| 147 | Global, |
| 148 | NativeParent(Rc<BuiltinElement>), |
| 149 | } |
| 150 | let base = if c.child_text(SyntaxKind::Identifier).is_some_and(|t| t == "global" ) { |
| 151 | Base::Global |
| 152 | } else if let Some(base) = e.QualifiedName() { |
| 153 | let base = QualifiedTypeName::from_node(base).to_smolstr(); |
| 154 | let base = natives.get(&base).unwrap().clone(); |
| 155 | // because they are not taken from if we inherit from it |
| 156 | assert!( |
| 157 | base.additional_accepted_child_types.is_empty() && !base.additional_accept_self |
| 158 | ); |
| 159 | n.parent = Some(base.native_class.clone()); |
| 160 | Base::NativeParent(base) |
| 161 | } else { |
| 162 | Base::None |
| 163 | }; |
| 164 | |
| 165 | n.properties.extend(e.Function().map(|f| { |
| 166 | let name = identifier_text(&f.DeclaredIdentifier()).unwrap(); |
| 167 | let return_type = f.ReturnType().map_or(Type::Void, |p| { |
| 168 | object_tree::type_from_node(p.Type(), *diag.borrow_mut(), register) |
| 169 | }); |
| 170 | ( |
| 171 | name, |
| 172 | BuiltinPropertyInfo::new(Type::Function( |
| 173 | Function { return_type, args: vec![], arg_names: vec![] }.into(), |
| 174 | )), |
| 175 | ) |
| 176 | })); |
| 177 | |
| 178 | let mut builtin = BuiltinElement::new(Rc::new(n)); |
| 179 | builtin.is_global = matches!(base, Base::Global); |
| 180 | let properties = &mut builtin.properties; |
| 181 | if let Base::NativeParent(parent) = &base { |
| 182 | properties.extend(parent.properties.iter().map(|(k, v)| (k.clone(), v.clone()))); |
| 183 | } |
| 184 | properties |
| 185 | .extend(builtin.native_class.properties.iter().map(|(k, v)| (k.clone(), v.clone()))); |
| 186 | |
| 187 | builtin.disallow_global_types_as_child_elements = |
| 188 | parse_annotation("disallow_global_types_as_child_elements" , &e).is_some(); |
| 189 | builtin.is_non_item_type = parse_annotation("is_non_item_type" , &e).is_some(); |
| 190 | builtin.is_internal = parse_annotation("is_internal" , &e).is_some(); |
| 191 | builtin.accepts_focus = parse_annotation("accepts_focus" , &e).is_some(); |
| 192 | builtin.default_size_binding = parse_annotation("default_size_binding" , &e) |
| 193 | .map(|size_type| match size_type.as_deref() { |
| 194 | Some("expands_to_parent_geometry" ) => DefaultSizeBinding::ExpandsToParentGeometry, |
| 195 | Some("implicit_size" ) => DefaultSizeBinding::ImplicitSize, |
| 196 | other => panic!("invalid default size binding {other:?}" ), |
| 197 | }) |
| 198 | .unwrap_or(DefaultSizeBinding::None); |
| 199 | builtin.additional_accepted_child_types = e |
| 200 | .SubElement() |
| 201 | .filter_map(|s| { |
| 202 | let a = identifier_text(&s.Element().QualifiedName().unwrap()).unwrap(); |
| 203 | if a == builtin.native_class.class_name { |
| 204 | builtin.additional_accept_self = true; |
| 205 | None |
| 206 | } else { |
| 207 | let t = natives[&a].clone(); |
| 208 | Some((a, t)) |
| 209 | } |
| 210 | }) |
| 211 | .collect(); |
| 212 | if let Some(builtin_name) = exports.get(&id) { |
| 213 | if !matches!(&base, Base::Global) { |
| 214 | builtin.name.clone_from(builtin_name); |
| 215 | register.add_builtin(Rc::new(builtin)); |
| 216 | } else { |
| 217 | let glob = Rc::new(Component { |
| 218 | id: builtin_name.clone(), |
| 219 | root_element: Rc::new(RefCell::new(Element { |
| 220 | base_type: ElementType::Builtin(Rc::new(builtin)), |
| 221 | ..Default::default() |
| 222 | })), |
| 223 | ..Default::default() |
| 224 | }); |
| 225 | glob.root_element.borrow_mut().enclosing_component = Rc::downgrade(&glob); |
| 226 | register.add(glob); |
| 227 | } |
| 228 | } else { |
| 229 | natives.insert(id, Rc::new(builtin)); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | register.property_animation_type = |
| 234 | ElementType::Builtin(natives.remove("PropertyAnimation" ).unwrap()); |
| 235 | |
| 236 | register.empty_type = ElementType::Builtin(natives.remove("Empty" ).unwrap()); |
| 237 | |
| 238 | if !diag.is_empty() { |
| 239 | let vec = diag.to_string_vec(); |
| 240 | #[cfg (feature = "display-diagnostics" )] |
| 241 | diag.print(); |
| 242 | panic!("Error loading the builtin elements: {vec:?}" ); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | /// Compile an expression, knowing that the expression is basic (does not have lookup to other things) |
| 247 | fn compiled( |
| 248 | node: syntax_nodes::BindingExpression, |
| 249 | type_register: &TypeRegister, |
| 250 | ty: Type, |
| 251 | ) -> Expression { |
| 252 | let mut diag: BuildDiagnostics = crate::diagnostics::BuildDiagnostics::default(); |
| 253 | let e: Expression = Expression::from_binding_expression_node( |
| 254 | node.clone().into(), |
| 255 | &mut crate::lookup::LookupCtx::empty_context(type_register, &mut diag), |
| 256 | ) |
| 257 | .maybe_convert_to(target_type:ty, &node, &mut diag); |
| 258 | if diag.has_errors() { |
| 259 | let vec: Vec = diag.to_string_vec(); |
| 260 | #[cfg (feature = "display-diagnostics" )] |
| 261 | diag.print(); |
| 262 | panic!("Error parsing the builtin elements: {vec:?}" ); |
| 263 | } |
| 264 | e |
| 265 | } |
| 266 | |
| 267 | /// Find out if there are comments that starts with `//-key` and returns `None` |
| 268 | /// if no annotation with this key is found, or `Some(None)` if it is found without a value |
| 269 | /// or `Some(Some(value))` if there is a `//-key:value` match |
| 270 | fn parse_annotation(key: &str, node: &SyntaxNode) -> Option<Option<SmolStr>> { |
| 271 | for x: NodeOrToken in node.children_with_tokens() { |
| 272 | if x.kind() == SyntaxKind::Comment { |
| 273 | if let Some(comment: &str) = xOption<&str> |
| 274 | .as_token() |
| 275 | .unwrap() |
| 276 | .text() |
| 277 | .strip_prefix("//-" ) |
| 278 | .and_then(|x: &str| x.trim_end().strip_prefix(key)) |
| 279 | { |
| 280 | if comment.is_empty() { |
| 281 | return Some(None); |
| 282 | } |
| 283 | if let Some(comment: &str) = comment.strip_prefix(':' ) { |
| 284 | return Some(Some(comment.into())); |
| 285 | } |
| 286 | } |
| 287 | } |
| 288 | } |
| 289 | None |
| 290 | } |
| 291 | |