i_slint_compiler/parser/
document.rs

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
4use super::element::{parse_element, parse_element_content};
5use super::prelude::*;
6use super::r#type::{parse_enum_declaration, parse_rustattr, parse_struct_declaration};
7
8#[cfg_attr(test, parser_test)]
9/// ```test,Document
10/// component Type { }
11/// Type := Base { SubElement { } }
12/// Comp := Base {}  Type := Base {}
13/// component Q {} Type := Base {} export { Type }
14/// import { Base } from "somewhere"; Type := Base {}
15/// struct Foo { foo: foo }
16/// enum Foo { hello }
17/// @rust-attr(...) struct X {}
18/// /* empty */
19/// ```
20pub fn parse_document(p: &mut impl Parser) -> bool {
21    let mut p = p.start_node(SyntaxKind::Document);
22
23    loop {
24        if p.test(SyntaxKind::Eof) {
25            return true;
26        }
27
28        if p.peek().kind() == SyntaxKind::Semicolon {
29            p.error("Extra semicolon. Remove this semicolon");
30            p.consume();
31            continue;
32        }
33
34        match p.peek().as_str() {
35            "export" => {
36                if !parse_export(&mut *p, None) {
37                    break;
38                }
39            }
40            "import" => {
41                if !parse_import_specifier(&mut *p) {
42                    break;
43                }
44            }
45            "struct" => {
46                if !parse_struct_declaration(&mut *p, None) {
47                    break;
48                }
49            }
50            "enum" => {
51                if !parse_enum_declaration(&mut *p, None) {
52                    break;
53                }
54            }
55            "@" if p.nth(1).as_str() == "rust-attr" => {
56                let checkpoint = p.checkpoint();
57                if !parse_rustattr(&mut *p) {
58                    break;
59                }
60                let is_export = p.nth(0).as_str() == "export";
61                let i = if is_export { 1 } else { 0 };
62                if !matches!(p.nth(i).as_str(), "enum" | "struct") {
63                    p.error("Expected enum or struct after @rust-attr");
64                    continue;
65                }
66                let r = if is_export {
67                    parse_export(&mut *p, Some(checkpoint))
68                } else if p.nth(0).as_str() == "struct" {
69                    parse_struct_declaration(&mut *p, Some(checkpoint))
70                } else if p.nth(0).as_str() == "enum" {
71                    parse_enum_declaration(&mut *p, Some(checkpoint))
72                } else {
73                    false
74                };
75                if !r {
76                    break;
77                }
78            }
79            _ => {
80                if !parse_component(&mut *p) {
81                    break;
82                }
83            }
84        }
85    }
86    // Always consume the whole document
87    while !p.test(SyntaxKind::Eof) {
88        p.consume()
89    }
90    false
91}
92
93#[cfg_attr(test, parser_test)]
94/// ```test,Component
95/// Type := Base { }
96/// Type := Base { prop: value; }
97/// Type := Base { SubElement { } }
98/// global Struct := { }
99/// global Struct { property<int> xx; }
100/// component C { property<int> xx; }
101/// component C inherits D { }
102/// ```
103pub fn parse_component(p: &mut impl Parser) -> bool {
104    let simple_component = p.nth(1).kind() == SyntaxKind::ColonEqual;
105    let is_global = !simple_component && p.peek().as_str() == "global";
106    let is_new_component = !simple_component && p.peek().as_str() == "component";
107    if !is_global && !simple_component && !is_new_component {
108        p.error(
109            "Parse error: expected a top-level item such as a component, a struct, or a global",
110        );
111        return false;
112    }
113    let mut p = p.start_node(SyntaxKind::Component);
114    if is_global || is_new_component {
115        p.consume();
116    }
117    if !p.start_node(SyntaxKind::DeclaredIdentifier).expect(SyntaxKind::Identifier) {
118        drop(p.start_node(SyntaxKind::Element));
119        return false;
120    }
121    if is_global {
122        if p.peek().kind() == SyntaxKind::ColonEqual {
123            p.warning("':=' to declare a global is deprecated. Remove the ':='");
124            p.consume();
125        }
126    } else if !is_new_component {
127        if p.peek().kind() == SyntaxKind::ColonEqual {
128            p.warning("':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info");
129        }
130        if !p.expect(SyntaxKind::ColonEqual) {
131            drop(p.start_node(SyntaxKind::Element));
132            return false;
133        }
134    } else if p.peek().as_str() == "inherits" {
135        p.consume();
136    } else if p.peek().kind() == SyntaxKind::LBrace {
137        let mut p = p.start_node(SyntaxKind::Element);
138        p.consume();
139        parse_element_content(&mut *p);
140        return p.expect(SyntaxKind::RBrace);
141    } else {
142        p.error("Expected '{' or keyword 'inherits'");
143        drop(p.start_node(SyntaxKind::Element));
144        return false;
145    }
146
147    if is_global && p.peek().kind() == SyntaxKind::LBrace {
148        let mut p = p.start_node(SyntaxKind::Element);
149        p.consume();
150        parse_element_content(&mut *p);
151        return p.expect(SyntaxKind::RBrace);
152    }
153
154    parse_element(&mut *p)
155}
156
157#[cfg_attr(test, parser_test)]
158/// ```test,QualifiedName
159/// Rectangle
160/// MyModule.Rectangle
161/// Deeply.Nested.MyModule.Rectangle
162/// ```
163pub fn parse_qualified_name(p: &mut impl Parser) -> bool {
164    let mut p = p.start_node(SyntaxKind::QualifiedName);
165    if !p.expect(SyntaxKind::Identifier) {
166        return false;
167    }
168
169    loop {
170        if p.nth(0).kind() != SyntaxKind::Dot {
171            break;
172        }
173        p.consume();
174        p.expect(SyntaxKind::Identifier);
175    }
176
177    true
178}
179
180#[cfg_attr(test, parser_test)]
181/// ```test,ExportsList
182/// export { Type }
183/// export { Type, AnotherType, }
184/// export { Type as Foo, AnotherType }
185/// export Foo := Item { }
186/// export struct Foo := { foo: bar }
187/// export enum Foo { bar }
188/// export * from "foo";
189/// export { Abc } from "foo";
190/// export { Abc, Efg } from "foo";
191/// ```
192fn parse_export<P: Parser>(p: &mut P, checkpoint: Option<P::Checkpoint>) -> bool {
193    debug_assert_eq!(p.peek().as_str(), "export");
194    let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::ExportsList);
195
196    p.expect(SyntaxKind::Identifier); // "export"
197    if p.test(SyntaxKind::LBrace) {
198        loop {
199            if p.test(SyntaxKind::RBrace) {
200                break;
201            }
202            parse_export_specifier(&mut *p);
203            match p.nth(0).kind() {
204                SyntaxKind::RBrace => {
205                    p.consume();
206                    break;
207                }
208                SyntaxKind::Eof => {
209                    p.error("Expected comma");
210                    return false;
211                }
212                SyntaxKind::Comma => {
213                    p.consume();
214                }
215                _ => {
216                    p.consume();
217                    p.error("Expected comma");
218                    return false;
219                }
220            }
221        }
222        if p.peek().as_str() == "from" {
223            let mut p = p.start_node(SyntaxKind::ExportModule);
224            p.consume(); // "from"
225            p.expect(SyntaxKind::StringLiteral);
226            p.expect(SyntaxKind::Semicolon);
227        }
228        true
229    } else if p.peek().as_str() == "struct" {
230        parse_struct_declaration(&mut *p, checkpoint)
231    } else if p.peek().as_str() == "enum" {
232        parse_enum_declaration(&mut *p, checkpoint)
233    } else if p.peek().kind == SyntaxKind::Star {
234        let mut p = p.start_node(SyntaxKind::ExportModule);
235        p.consume(); // *
236        if p.peek().as_str() != "from" {
237            p.error("Expected from keyword for export statement");
238            return false;
239        }
240        p.consume();
241        let peek = p.peek();
242        if peek.kind != SyntaxKind::StringLiteral
243            || !peek.as_str().starts_with('"')
244            || !peek.as_str().ends_with('"')
245        {
246            p.error("Expected plain string literal");
247            return false;
248        }
249        p.consume();
250        p.expect(SyntaxKind::Semicolon)
251    } else {
252        parse_component(&mut *p)
253    }
254}
255
256#[cfg_attr(test, parser_test)]
257/// ```test,ExportSpecifier
258/// Type
259/// Type as Something
260/// ```
261fn parse_export_specifier(p: &mut impl Parser) -> bool {
262    let mut p = p.start_node(SyntaxKind::ExportSpecifier);
263    {
264        let mut p = p.start_node(SyntaxKind::ExportIdentifier);
265        if !p.expect(SyntaxKind::Identifier) {
266            return false;
267        }
268    }
269    if p.peek().as_str() == "as" {
270        p.consume();
271        let mut p = p.start_node(SyntaxKind::ExportName);
272        if !p.expect(SyntaxKind::Identifier) {
273            return false;
274        }
275    }
276
277    true
278}
279
280#[cfg_attr(test, parser_test)]
281/// ```test,ImportSpecifier
282/// import { Type1, Type2 } from "somewhere";
283/// import "something.ttf";
284/// ```
285fn parse_import_specifier(p: &mut impl Parser) -> bool {
286    debug_assert_eq!(p.peek().as_str(), "import");
287    let mut p = p.start_node(SyntaxKind::ImportSpecifier);
288    p.expect(SyntaxKind::Identifier); // "import"
289    if p.peek().kind != SyntaxKind::StringLiteral {
290        if !parse_import_identifier_list(&mut *p) {
291            return false;
292        }
293        if p.peek().as_str() != "from" {
294            p.error("Expected from keyword for import statement");
295            return false;
296        }
297        if !p.expect(SyntaxKind::Identifier) {
298            return false;
299        }
300    }
301    let peek = p.peek();
302    if peek.kind != SyntaxKind::StringLiteral
303        || !peek.as_str().starts_with('"')
304        || !peek.as_str().ends_with('"')
305    {
306        p.error("Expected plain string literal");
307        return false;
308    }
309    p.consume();
310    p.expect(SyntaxKind::Semicolon)
311}
312
313#[cfg_attr(test, parser_test)]
314/// ```test,ImportIdentifierList
315/// { Type1 }
316/// { Type2, }
317/// { Type3, Type4 }
318/// { Type5, Type6, }
319/// { Type as Alias1, Type as AnotherAlias1 }
320/// { Type as Alias2, Type as AnotherAlias2, }
321/// {}
322/// ```
323fn parse_import_identifier_list(p: &mut impl Parser) -> bool {
324    let mut p = p.start_node(SyntaxKind::ImportIdentifierList);
325    if !p.expect(SyntaxKind::LBrace) {
326        return false;
327    }
328    loop {
329        if p.test(SyntaxKind::RBrace) {
330            return true;
331        }
332        parse_import_identifier(&mut *p);
333        if !p.test(SyntaxKind::Comma) && p.nth(0).kind() != SyntaxKind::RBrace {
334            p.error("Expected comma or brace");
335            return false;
336        }
337    }
338}
339
340#[cfg_attr(test, parser_test)]
341/// ```test,ImportIdentifier
342/// Type
343/// Type as Alias1
344/// ```
345fn parse_import_identifier(p: &mut impl Parser) -> bool {
346    let mut p = p.start_node(SyntaxKind::ImportIdentifier);
347    {
348        let mut p = p.start_node(SyntaxKind::ExternalName);
349        if !p.expect(SyntaxKind::Identifier) {
350            return false;
351        }
352    }
353    if p.nth(0).kind() == SyntaxKind::Identifier && p.peek().as_str() == "as" {
354        p.consume();
355        let mut p = p.start_node(SyntaxKind::InternalName);
356        if !p.expect(SyntaxKind::Identifier) {
357            return false;
358        }
359    }
360    true
361}