Skip to main content

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