mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-10-31 03:54:25 +00:00 
			
		
		
		
	 a5ec77ac99
			
		
	
	
		a5ec77ac99
		
			
		
	
	
	
	
		
			
			Adding support for (optional) trailing commas like this:
    import {
        Foo,
        Bar,
    } from "foobar.slint";
This way it's more convenient to keep component imports sorted and
leads to smaller diffs when adding more components to the end of the
import statement.
ChangeLog: Allow trailing comma in import statements
Closes #4922
		
	
			
		
			
				
	
	
		
			361 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			361 lines
		
	
	
	
		
			11 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
 | |
| 
 | |
| use super::element::{parse_element, parse_element_content};
 | |
| use super::prelude::*;
 | |
| use super::r#type::{parse_enum_declaration, parse_rustattr, parse_struct_declaration};
 | |
| 
 | |
| #[cfg_attr(test, parser_test)]
 | |
| /// ```test,Document
 | |
| /// component Type { }
 | |
| /// Type := Base { SubElement { } }
 | |
| /// Comp := Base {}  Type := Base {}
 | |
| /// component Q {} Type := Base {} export { Type }
 | |
| /// import { Base } from "somewhere"; Type := Base {}
 | |
| /// struct Foo { foo: foo }
 | |
| /// enum Foo { hello }
 | |
| /// @rust-attr(...) struct X {}
 | |
| /// /* empty */
 | |
| /// ```
 | |
| pub fn parse_document(p: &mut impl Parser) -> bool {
 | |
|     let mut p = p.start_node(SyntaxKind::Document);
 | |
| 
 | |
|     loop {
 | |
|         if p.test(SyntaxKind::Eof) {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         if p.peek().kind() == SyntaxKind::Semicolon {
 | |
|             p.error("Extra semicolon. Remove this semicolon");
 | |
|             p.consume();
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         match p.peek().as_str() {
 | |
|             "export" => {
 | |
|                 if !parse_export(&mut *p, None) {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             "import" => {
 | |
|                 if !parse_import_specifier(&mut *p) {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             "struct" => {
 | |
|                 if !parse_struct_declaration(&mut *p, None) {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             "enum" => {
 | |
|                 if !parse_enum_declaration(&mut *p, None) {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             "@" if p.nth(1).as_str() == "rust-attr" => {
 | |
|                 let checkpoint = p.checkpoint();
 | |
|                 if !parse_rustattr(&mut *p) {
 | |
|                     break;
 | |
|                 }
 | |
|                 let is_export = p.nth(0).as_str() == "export";
 | |
|                 let i = if is_export { 1 } else { 0 };
 | |
|                 if !matches!(p.nth(i).as_str(), "enum" | "struct") {
 | |
|                     p.error("Expected enum or struct after @rust-attr");
 | |
|                     continue;
 | |
|                 }
 | |
|                 let r = if is_export {
 | |
|                     parse_export(&mut *p, Some(checkpoint))
 | |
|                 } else if p.nth(0).as_str() == "struct" {
 | |
|                     parse_struct_declaration(&mut *p, Some(checkpoint))
 | |
|                 } else if p.nth(0).as_str() == "enum" {
 | |
|                     parse_enum_declaration(&mut *p, Some(checkpoint))
 | |
|                 } else {
 | |
|                     false
 | |
|                 };
 | |
|                 if !r {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             _ => {
 | |
|                 if !parse_component(&mut *p) {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     // Always consume the whole document
 | |
|     while !p.test(SyntaxKind::Eof) {
 | |
|         p.consume()
 | |
|     }
 | |
|     false
 | |
| }
 | |
| 
 | |
| #[cfg_attr(test, parser_test)]
 | |
| /// ```test,Component
 | |
| /// Type := Base { }
 | |
| /// Type := Base { prop: value; }
 | |
| /// Type := Base { SubElement { } }
 | |
| /// global Struct := { }
 | |
| /// global Struct { property<int> xx; }
 | |
| /// component C { property<int> xx; }
 | |
| /// component C inherits D { }
 | |
| /// ```
 | |
| pub fn parse_component(p: &mut impl Parser) -> bool {
 | |
|     let simple_component = p.nth(1).kind() == SyntaxKind::ColonEqual;
 | |
|     let is_global = !simple_component && p.peek().as_str() == "global";
 | |
|     let is_new_component = !simple_component && p.peek().as_str() == "component";
 | |
|     if !is_global && !simple_component && !is_new_component {
 | |
|         p.error(
 | |
|             "Parse error: expected a top-level item such as a component, a struct, or a global",
 | |
|         );
 | |
|         return false;
 | |
|     }
 | |
|     let mut p = p.start_node(SyntaxKind::Component);
 | |
|     if is_global || is_new_component {
 | |
|         p.consume();
 | |
|     }
 | |
|     if !p.start_node(SyntaxKind::DeclaredIdentifier).expect(SyntaxKind::Identifier) {
 | |
|         drop(p.start_node(SyntaxKind::Element));
 | |
|         return false;
 | |
|     }
 | |
|     if is_global {
 | |
|         if p.peek().kind() == SyntaxKind::ColonEqual {
 | |
|             p.warning("':=' to declare a global is deprecated. Remove the ':='");
 | |
|             p.consume();
 | |
|         }
 | |
|     } else if !is_new_component {
 | |
|         if p.peek().kind() == SyntaxKind::ColonEqual {
 | |
|             p.warning("':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info");
 | |
|         }
 | |
|         if !p.expect(SyntaxKind::ColonEqual) {
 | |
|             drop(p.start_node(SyntaxKind::Element));
 | |
|             return false;
 | |
|         }
 | |
|     } else if p.peek().as_str() == "inherits" {
 | |
|         p.consume();
 | |
|     } else if p.peek().kind() == SyntaxKind::LBrace {
 | |
|         let mut p = p.start_node(SyntaxKind::Element);
 | |
|         p.consume();
 | |
|         parse_element_content(&mut *p);
 | |
|         return p.expect(SyntaxKind::RBrace);
 | |
|     } else {
 | |
|         p.error("Expected '{' or keyword 'inherits'");
 | |
|         drop(p.start_node(SyntaxKind::Element));
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     if is_global && p.peek().kind() == SyntaxKind::LBrace {
 | |
|         let mut p = p.start_node(SyntaxKind::Element);
 | |
|         p.consume();
 | |
|         parse_element_content(&mut *p);
 | |
|         return p.expect(SyntaxKind::RBrace);
 | |
|     }
 | |
| 
 | |
|     parse_element(&mut *p)
 | |
| }
 | |
| 
 | |
| #[cfg_attr(test, parser_test)]
 | |
| /// ```test,QualifiedName
 | |
| /// Rectangle
 | |
| /// MyModule.Rectangle
 | |
| /// Deeply.Nested.MyModule.Rectangle
 | |
| /// ```
 | |
| pub fn parse_qualified_name(p: &mut impl Parser) -> bool {
 | |
|     let mut p = p.start_node(SyntaxKind::QualifiedName);
 | |
|     if !p.expect(SyntaxKind::Identifier) {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     loop {
 | |
|         if p.nth(0).kind() != SyntaxKind::Dot {
 | |
|             break;
 | |
|         }
 | |
|         p.consume();
 | |
|         p.expect(SyntaxKind::Identifier);
 | |
|     }
 | |
| 
 | |
|     true
 | |
| }
 | |
| 
 | |
| #[cfg_attr(test, parser_test)]
 | |
| /// ```test,ExportsList
 | |
| /// export { Type }
 | |
| /// export { Type, AnotherType, }
 | |
| /// export { Type as Foo, AnotherType }
 | |
| /// export Foo := Item { }
 | |
| /// export struct Foo := { foo: bar }
 | |
| /// export enum Foo { bar }
 | |
| /// export * from "foo";
 | |
| /// export { Abc } from "foo";
 | |
| /// export { Abc, Efg } from "foo";
 | |
| /// ```
 | |
| fn parse_export<P: Parser>(p: &mut P, checkpoint: Option<P::Checkpoint>) -> bool {
 | |
|     debug_assert_eq!(p.peek().as_str(), "export");
 | |
|     let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::ExportsList);
 | |
| 
 | |
|     p.expect(SyntaxKind::Identifier); // "export"
 | |
|     if p.test(SyntaxKind::LBrace) {
 | |
|         loop {
 | |
|             if p.test(SyntaxKind::RBrace) {
 | |
|                 break;
 | |
|             }
 | |
|             parse_export_specifier(&mut *p);
 | |
|             match p.nth(0).kind() {
 | |
|                 SyntaxKind::RBrace => {
 | |
|                     p.consume();
 | |
|                     break;
 | |
|                 }
 | |
|                 SyntaxKind::Eof => {
 | |
|                     p.error("Expected comma");
 | |
|                     return false;
 | |
|                 }
 | |
|                 SyntaxKind::Comma => {
 | |
|                     p.consume();
 | |
|                 }
 | |
|                 _ => {
 | |
|                     p.consume();
 | |
|                     p.error("Expected comma");
 | |
|                     return false;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         if p.peek().as_str() == "from" {
 | |
|             let mut p = p.start_node(SyntaxKind::ExportModule);
 | |
|             p.consume(); // "from"
 | |
|             p.expect(SyntaxKind::StringLiteral);
 | |
|             p.expect(SyntaxKind::Semicolon);
 | |
|         }
 | |
|         true
 | |
|     } else if p.peek().as_str() == "struct" {
 | |
|         parse_struct_declaration(&mut *p, checkpoint)
 | |
|     } else if p.peek().as_str() == "enum" {
 | |
|         parse_enum_declaration(&mut *p, checkpoint)
 | |
|     } else if p.peek().kind == SyntaxKind::Star {
 | |
|         let mut p = p.start_node(SyntaxKind::ExportModule);
 | |
|         p.consume(); // *
 | |
|         if p.peek().as_str() != "from" {
 | |
|             p.error("Expected from keyword for export statement");
 | |
|             return false;
 | |
|         }
 | |
|         p.consume();
 | |
|         let peek = p.peek();
 | |
|         if peek.kind != SyntaxKind::StringLiteral
 | |
|             || !peek.as_str().starts_with('"')
 | |
|             || !peek.as_str().ends_with('"')
 | |
|         {
 | |
|             p.error("Expected plain string literal");
 | |
|             return false;
 | |
|         }
 | |
|         p.consume();
 | |
|         p.expect(SyntaxKind::Semicolon)
 | |
|     } else {
 | |
|         parse_component(&mut *p)
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[cfg_attr(test, parser_test)]
 | |
| /// ```test,ExportSpecifier
 | |
| /// Type
 | |
| /// Type as Something
 | |
| /// ```
 | |
| fn parse_export_specifier(p: &mut impl Parser) -> bool {
 | |
|     let mut p = p.start_node(SyntaxKind::ExportSpecifier);
 | |
|     {
 | |
|         let mut p = p.start_node(SyntaxKind::ExportIdentifier);
 | |
|         if !p.expect(SyntaxKind::Identifier) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
|     if p.peek().as_str() == "as" {
 | |
|         p.consume();
 | |
|         let mut p = p.start_node(SyntaxKind::ExportName);
 | |
|         if !p.expect(SyntaxKind::Identifier) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     true
 | |
| }
 | |
| 
 | |
| #[cfg_attr(test, parser_test)]
 | |
| /// ```test,ImportSpecifier
 | |
| /// import { Type1, Type2 } from "somewhere";
 | |
| /// import "something.ttf";
 | |
| /// ```
 | |
| fn parse_import_specifier(p: &mut impl Parser) -> bool {
 | |
|     debug_assert_eq!(p.peek().as_str(), "import");
 | |
|     let mut p = p.start_node(SyntaxKind::ImportSpecifier);
 | |
|     p.expect(SyntaxKind::Identifier); // "import"
 | |
|     if p.peek().kind != SyntaxKind::StringLiteral {
 | |
|         if !parse_import_identifier_list(&mut *p) {
 | |
|             return false;
 | |
|         }
 | |
|         if p.peek().as_str() != "from" {
 | |
|             p.error("Expected from keyword for import statement");
 | |
|             return false;
 | |
|         }
 | |
|         if !p.expect(SyntaxKind::Identifier) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
|     let peek = p.peek();
 | |
|     if peek.kind != SyntaxKind::StringLiteral
 | |
|         || !peek.as_str().starts_with('"')
 | |
|         || !peek.as_str().ends_with('"')
 | |
|     {
 | |
|         p.error("Expected plain string literal");
 | |
|         return false;
 | |
|     }
 | |
|     p.consume();
 | |
|     p.expect(SyntaxKind::Semicolon)
 | |
| }
 | |
| 
 | |
| #[cfg_attr(test, parser_test)]
 | |
| /// ```test,ImportIdentifierList
 | |
| /// { Type1 }
 | |
| /// { Type2, }
 | |
| /// { Type3, Type4 }
 | |
| /// { Type5, Type6, }
 | |
| /// { Type as Alias1, Type as AnotherAlias1 }
 | |
| /// { Type as Alias2, Type as AnotherAlias2, }
 | |
| /// {}
 | |
| /// ```
 | |
| fn parse_import_identifier_list(p: &mut impl Parser) -> bool {
 | |
|     let mut p = p.start_node(SyntaxKind::ImportIdentifierList);
 | |
|     if !p.expect(SyntaxKind::LBrace) {
 | |
|         return false;
 | |
|     }
 | |
|     loop {
 | |
|         if p.test(SyntaxKind::RBrace) {
 | |
|             return true;
 | |
|         }
 | |
|         parse_import_identifier(&mut *p);
 | |
|         if !p.test(SyntaxKind::Comma) && p.nth(0).kind() != SyntaxKind::RBrace {
 | |
|             p.error("Expected comma or brace");
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[cfg_attr(test, parser_test)]
 | |
| /// ```test,ImportIdentifier
 | |
| /// Type
 | |
| /// Type as Alias1
 | |
| /// ```
 | |
| fn parse_import_identifier(p: &mut impl Parser) -> bool {
 | |
|     let mut p = p.start_node(SyntaxKind::ImportIdentifier);
 | |
|     {
 | |
|         let mut p = p.start_node(SyntaxKind::ExternalName);
 | |
|         if !p.expect(SyntaxKind::Identifier) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
|     if p.nth(0).kind() == SyntaxKind::Identifier && p.peek().as_str() == "as" {
 | |
|         p.consume();
 | |
|         let mut p = p.start_node(SyntaxKind::InternalName);
 | |
|         if !p.expect(SyntaxKind::Identifier) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
|     true
 | |
| }
 |