mirror of
https://github.com/slint-ui/slint.git
synced 2025-07-12 15:45:24 +00:00

Add life-time annotations as recommended by the compiler to clarify the flow of life times of arguments to the output.
1078 lines
38 KiB
Rust
1078 lines
38 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
|
|
|
|
/*! The Slint Language Parser
|
|
|
|
This module is responsible to parse a string onto a syntax tree.
|
|
|
|
The core of it is the `DefaultParser` class that holds a list of token and
|
|
generates a `rowan::GreenNode`
|
|
|
|
This module has different sub modules with the actual parser functions
|
|
|
|
*/
|
|
|
|
use crate::diagnostics::{BuildDiagnostics, SourceFile, Spanned};
|
|
use smol_str::SmolStr;
|
|
use std::fmt::Display;
|
|
|
|
mod document;
|
|
mod element;
|
|
mod expressions;
|
|
mod statements;
|
|
mod r#type;
|
|
|
|
/// Each parser submodule would simply do `use super::prelude::*` to import typically used items
|
|
mod prelude {
|
|
#[cfg(test)]
|
|
pub use super::DefaultParser;
|
|
#[cfg(test)]
|
|
pub use super::{syntax_nodes, SyntaxNode, SyntaxNodeVerify};
|
|
pub use super::{Parser, SyntaxKind};
|
|
#[cfg(test)]
|
|
pub use i_slint_parser_test_macro::parser_test;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub trait SyntaxNodeVerify {
|
|
/// The SyntaxKind corresponding to this type
|
|
const KIND: SyntaxKind;
|
|
/// Asserts that the node is of the given SyntaxKind and that it has the expected children
|
|
/// Panic if this is not the case
|
|
fn verify(node: SyntaxNode) {
|
|
assert_eq!(node.kind(), Self::KIND)
|
|
}
|
|
}
|
|
|
|
pub use rowan::{TextRange, TextSize};
|
|
|
|
/// Check that a node has the assumed children
|
|
#[cfg(test)]
|
|
macro_rules! verify_node {
|
|
// Some combination of children
|
|
($node:ident, [ $($t1:tt $($t2:ident)?),* ]) => {
|
|
// Check that every children is there
|
|
$(verify_node!(@check_has_children $node, $t1 $($t2)* );)*
|
|
|
|
// check that there are not too many nodes
|
|
for c in $node.children() {
|
|
assert!(
|
|
false $(|| c.kind() == verify_node!(@extract_kind $t1 $($t2)*))*,
|
|
"Node is none of [{}]\n{:?}", stringify!($($t1 $($t2)*),*) ,c);
|
|
}
|
|
|
|
// recurse
|
|
$(
|
|
for _c in $node.children().filter(|n| n.kind() == verify_node!(@extract_kind $t1 $($t2)*)) {
|
|
<verify_node!(@extract_type $t1 $($t2)*)>::verify(_c)
|
|
}
|
|
)*
|
|
};
|
|
|
|
// Any number of this kind.
|
|
(@check_has_children $node:ident, * $kind:ident) => {};
|
|
// 1 or 0
|
|
(@check_has_children $node:ident, ? $kind:ident) => {
|
|
let count = $node.children_with_tokens().filter(|n| n.kind() == SyntaxKind::$kind).count();
|
|
assert!(count <= 1, "Expecting one or zero sub-node of type {}, found {}\n{:?}", stringify!($kind), count, $node);
|
|
};
|
|
// Exactly one
|
|
(@check_has_children $node:ident, $kind:ident) => {
|
|
let count = $node.children_with_tokens().filter(|n| n.kind() == SyntaxKind::$kind).count();
|
|
assert_eq!(count, 1, "Expecting exactly one sub-node of type {}\n{:?}", stringify!($kind), $node);
|
|
};
|
|
// Exact number
|
|
(@check_has_children $node:ident, $count:literal $kind:ident) => {
|
|
let count = $node.children_with_tokens().filter(|n| n.kind() == SyntaxKind::$kind).count();
|
|
assert_eq!(count, $count, "Expecting {} sub-node of type {}, found {}\n{:?}", $count, stringify!($kind), count, $node);
|
|
};
|
|
|
|
(@extract_kind * $kind:ident) => {SyntaxKind::$kind};
|
|
(@extract_kind ? $kind:ident) => {SyntaxKind::$kind};
|
|
(@extract_kind $count:literal $kind:ident) => {SyntaxKind::$kind};
|
|
(@extract_kind $kind:ident) => {SyntaxKind::$kind};
|
|
|
|
(@extract_type * $kind:ident) => {$crate::parser::syntax_nodes::$kind};
|
|
(@extract_type ? $kind:ident) => {$crate::parser::syntax_nodes::$kind};
|
|
(@extract_type $count:literal $kind:ident) => {$crate::parser::syntax_nodes::$kind};
|
|
(@extract_type $kind:ident) => {$crate::parser::syntax_nodes::$kind};
|
|
}
|
|
|
|
macro_rules! node_accessors {
|
|
// Some combination of children
|
|
([ $($t1:tt $($t2:ident)?),* ]) => {
|
|
$(node_accessors!{@ $t1 $($t2)*} )*
|
|
};
|
|
|
|
(@ * $kind:ident) => {
|
|
#[allow(non_snake_case)]
|
|
pub fn $kind(&self) -> impl Iterator<Item = $kind> {
|
|
self.0.children().filter(|n| n.kind() == SyntaxKind::$kind).map(Into::into)
|
|
}
|
|
};
|
|
(@ ? $kind:ident) => {
|
|
#[allow(non_snake_case)]
|
|
pub fn $kind(&self) -> Option<$kind> {
|
|
self.0.child_node(SyntaxKind::$kind).map(Into::into)
|
|
}
|
|
};
|
|
(@ 2 $kind:ident) => {
|
|
#[allow(non_snake_case)]
|
|
#[track_caller]
|
|
pub fn $kind(&self) -> ($kind, $kind) {
|
|
let mut it = self.0.children().filter(|n| n.kind() == SyntaxKind::$kind);
|
|
let a = it.next().expect(stringify!(Missing first $kind));
|
|
let b = it.next().expect(stringify!(Missing second $kind));
|
|
debug_assert!(it.next().is_none(), stringify!(More $kind than expected));
|
|
(a.into(), b.into())
|
|
}
|
|
};
|
|
(@ 3 $kind:ident) => {
|
|
#[allow(non_snake_case)]
|
|
#[track_caller]
|
|
pub fn $kind(&self) -> ($kind, $kind, $kind) {
|
|
let mut it = self.0.children().filter(|n| n.kind() == SyntaxKind::$kind);
|
|
let a = it.next().expect(stringify!(Missing first $kind));
|
|
let b = it.next().expect(stringify!(Missing second $kind));
|
|
let c = it.next().expect(stringify!(Missing third $kind));
|
|
debug_assert!(it.next().is_none(), stringify!(More $kind than expected));
|
|
(a.into(), b.into(), c.into())
|
|
}
|
|
};
|
|
(@ $kind:ident) => {
|
|
#[allow(non_snake_case)]
|
|
#[track_caller]
|
|
pub fn $kind(&self) -> $kind {
|
|
self.0.child_node(SyntaxKind::$kind).expect(stringify!(Missing $kind)).into()
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
/// This macro is invoked once, to declare all the token and syntax kind.
|
|
/// The purpose of this macro is to declare the token with its regexp at the same place,
|
|
/// and the nodes with their contents.
|
|
///
|
|
/// This is split into two group: first the tokens, then the nodes.
|
|
///
|
|
/// # Tokens
|
|
///
|
|
/// Given as `$token:ident -> $rule:expr`. The rule parameter can be either a string literal or
|
|
/// a lexer function. The order of tokens is important because the rules will be run in that order
|
|
/// and the first one matching will be chosen.
|
|
///
|
|
/// # Nodes
|
|
///
|
|
/// Given as `$(#[$attr:meta])* $nodekind:ident -> [$($children:tt),*] `.
|
|
/// Where `children` is a list of sub-nodes (not including tokens).
|
|
/// This will allow to self-document and create the structure from the [`syntax_nodes`] module.
|
|
/// The children can be prefixed with the following symbol:
|
|
///
|
|
/// - nothing: The node occurs once and exactly once, the generated accessor returns the node itself
|
|
/// - `+`: the node occurs one or several times, the generated accessor returns an `Iterator`
|
|
/// - `*`: the node occurs zero or several times, the generated accessor returns an `Iterator`
|
|
/// - `?`: the node occurs once or zero times, the generated accessor returns an `Option`
|
|
/// - `2` or `3`: the node occurs exactly two or three times, the generated accessor returns a tuple
|
|
///
|
|
/// Note: the parser must generate the right amount of sub nodes, even if there is a parse error.
|
|
///
|
|
/// ## The [`syntax_nodes`] module
|
|
///
|
|
/// Creates one struct for every node with the given accessor.
|
|
/// The struct can be converted from and to the node.
|
|
macro_rules! declare_syntax {
|
|
({
|
|
$($token:ident -> $rule:expr ,)*
|
|
}
|
|
{
|
|
$( $(#[$attr:meta])* $nodekind:ident -> $children:tt ,)*
|
|
})
|
|
=> {
|
|
#[repr(u16)]
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Hash, Ord, PartialOrd)]
|
|
pub enum SyntaxKind {
|
|
Error,
|
|
Eof,
|
|
|
|
// Tokens:
|
|
$(
|
|
/// Token
|
|
$token,
|
|
)*
|
|
|
|
// Nodes:
|
|
$(
|
|
$(#[$attr])*
|
|
$nodekind,
|
|
)*
|
|
}
|
|
|
|
impl Display for SyntaxKind {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
$(Self::$token => {
|
|
if let Some(character) = <dyn std::any::Any>::downcast_ref::<&str>(& $rule) {
|
|
return write!(f, "'{}'", character)
|
|
}
|
|
})*
|
|
_ => ()
|
|
}
|
|
write!(f, "{:?}", self)
|
|
}
|
|
}
|
|
|
|
|
|
/// Returns a pair of the matched token type at the beginning of `text`, and its size
|
|
pub fn lex_next_token(text : &str, state: &mut crate::lexer::LexState) -> Option<(usize, SyntaxKind)> {
|
|
use crate::lexer::LexingRule;
|
|
$(
|
|
let len = ($rule).lex(text, state);
|
|
if len > 0 {
|
|
return Some((len, SyntaxKind::$token));
|
|
}
|
|
)*
|
|
None
|
|
}
|
|
|
|
pub mod syntax_nodes {
|
|
use super::*;
|
|
$(
|
|
#[derive(Debug, Clone, derive_more::Deref, derive_more::Into)]
|
|
pub struct $nodekind(SyntaxNode);
|
|
#[cfg(test)]
|
|
impl SyntaxNodeVerify for $nodekind {
|
|
const KIND: SyntaxKind = SyntaxKind::$nodekind;
|
|
#[track_caller]
|
|
fn verify(node: SyntaxNode) {
|
|
assert_eq!(node.kind(), Self::KIND);
|
|
verify_node!(node, $children);
|
|
}
|
|
}
|
|
impl $nodekind {
|
|
node_accessors!{$children}
|
|
|
|
/// Create a new node from a SyntaxNode, if the SyntaxNode is of the correct kind
|
|
pub fn new(node: SyntaxNode) -> Option<Self> {
|
|
(node.kind() == SyntaxKind::$nodekind).then(|| Self(node))
|
|
}
|
|
}
|
|
|
|
impl From<SyntaxNode> for $nodekind {
|
|
#[track_caller]
|
|
fn from(node: SyntaxNode) -> Self {
|
|
assert_eq!(node.kind(), SyntaxKind::$nodekind);
|
|
Self(node)
|
|
}
|
|
}
|
|
|
|
impl Spanned for $nodekind {
|
|
fn span(&self) -> crate::diagnostics::Span {
|
|
self.0.span()
|
|
}
|
|
|
|
fn source_file(&self) -> Option<&SourceFile> {
|
|
self.0.source_file()
|
|
}
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
}
|
|
declare_syntax! {
|
|
// Tokens.
|
|
// WARNING: when changing this, do not forget to update the tokenizer in the slint-rs-macro crate!
|
|
// The order of token is important because the rules will be run in that order
|
|
// and the first one matching will be chosen.
|
|
{
|
|
Whitespace -> &crate::lexer::lex_whitespace,
|
|
Comment -> &crate::lexer::lex_comment,
|
|
StringLiteral -> &crate::lexer::lex_string,
|
|
NumberLiteral -> &crate::lexer::lex_number,
|
|
ColorLiteral -> &crate::lexer::lex_color,
|
|
Identifier -> &crate::lexer::lex_identifier,
|
|
DoubleArrow -> "<=>",
|
|
PlusEqual -> "+=",
|
|
MinusEqual -> "-=",
|
|
StarEqual -> "*=",
|
|
DivEqual -> "/=",
|
|
LessEqual -> "<=",
|
|
GreaterEqual -> ">=",
|
|
EqualEqual -> "==",
|
|
NotEqual -> "!=",
|
|
ColonEqual -> ":=",
|
|
FatArrow -> "=>",
|
|
Arrow -> "->",
|
|
OrOr -> "||",
|
|
AndAnd -> "&&",
|
|
LBrace -> "{",
|
|
RBrace -> "}",
|
|
LParent -> "(",
|
|
RParent -> ")",
|
|
LAngle -> "<",
|
|
RAngle -> ">",
|
|
LBracket -> "[",
|
|
RBracket -> "]",
|
|
Plus -> "+",
|
|
Minus -> "-",
|
|
Star -> "*",
|
|
Div -> "/",
|
|
Equal -> "=",
|
|
Colon -> ":",
|
|
Comma -> ",",
|
|
Semicolon -> ";",
|
|
Bang -> "!",
|
|
Dot -> ".",
|
|
Question -> "?",
|
|
Dollar -> "$",
|
|
At -> "@",
|
|
Pipe -> "|",
|
|
Percent -> "%",
|
|
}
|
|
// syntax kind
|
|
{
|
|
Document -> [ *Component, *ExportsList, *ImportSpecifier, *StructDeclaration, *EnumDeclaration ],
|
|
/// `DeclaredIdentifier := Element { ... }`
|
|
Component -> [ DeclaredIdentifier, Element ],
|
|
/// `id := Element { ... }`
|
|
SubElement -> [ Element ],
|
|
Element -> [ ?QualifiedName, *PropertyDeclaration, *Binding, *CallbackConnection,
|
|
*CallbackDeclaration, *ConditionalElement, *Function, *SubElement,
|
|
*RepeatedElement, *PropertyAnimation, *PropertyChangedCallback,
|
|
*TwoWayBinding, *States, *Transitions, ?ChildrenPlaceholder ],
|
|
RepeatedElement -> [ ?DeclaredIdentifier, ?RepeatedIndex, Expression , SubElement],
|
|
RepeatedIndex -> [],
|
|
ConditionalElement -> [ Expression , SubElement],
|
|
CallbackDeclaration -> [ DeclaredIdentifier, *CallbackDeclarationParameter, ?ReturnType, ?TwoWayBinding ],
|
|
// `foo: type` or just `type`
|
|
CallbackDeclarationParameter -> [ ?DeclaredIdentifier, Type],
|
|
Function -> [DeclaredIdentifier, *ArgumentDeclaration, ?ReturnType, CodeBlock ],
|
|
ArgumentDeclaration -> [DeclaredIdentifier, Type],
|
|
/// `-> type` (but without the ->)
|
|
ReturnType -> [Type],
|
|
CallbackConnection -> [ *DeclaredIdentifier, CodeBlock ],
|
|
/// Declaration of a property.
|
|
PropertyDeclaration-> [ ?Type , DeclaredIdentifier, ?BindingExpression, ?TwoWayBinding ],
|
|
/// QualifiedName are the properties name
|
|
PropertyAnimation-> [ *QualifiedName, *Binding ],
|
|
/// `changed xxx => {...}` where `xxx` is the DeclaredIdentifier
|
|
PropertyChangedCallback-> [ DeclaredIdentifier, CodeBlock ],
|
|
/// wraps Identifiers, like `Rectangle` or `SomeModule.SomeType`
|
|
QualifiedName-> [],
|
|
/// Wraps single identifier (to disambiguate when there are other identifier in the production)
|
|
DeclaredIdentifier -> [],
|
|
ChildrenPlaceholder -> [],
|
|
Binding-> [ BindingExpression ],
|
|
/// `xxx <=> something`
|
|
TwoWayBinding -> [ Expression ],
|
|
/// the right-hand-side of a binding
|
|
// Fixme: the test should be a or
|
|
BindingExpression-> [ ?CodeBlock, ?Expression ],
|
|
CodeBlock-> [ *Expression, *ReturnStatement ],
|
|
ReturnStatement -> [ ?Expression ],
|
|
// FIXME: the test should test that as alternative rather than several of them (but it can also be a literal)
|
|
Expression-> [ ?Expression, ?FunctionCallExpression, ?IndexExpression, ?SelfAssignment,
|
|
?ConditionalExpression, ?QualifiedName, ?BinaryExpression, ?Array, ?ObjectLiteral,
|
|
?UnaryOpExpression, ?CodeBlock, ?StringTemplate, ?AtImageUrl, ?AtGradient, ?AtTr,
|
|
?MemberAccess ],
|
|
/// Concatenate the Expressions to make a string (usually expended from a template string)
|
|
StringTemplate -> [*Expression],
|
|
/// `@image-url("foo.png")`
|
|
AtImageUrl -> [],
|
|
/// `@linear-gradient(...)` or `@radial-gradient(...)`
|
|
AtGradient -> [*Expression],
|
|
/// `@tr("foo", ...)` // the string is a StringLiteral
|
|
AtTr -> [?TrContext, ?TrPlural, *Expression],
|
|
/// `"foo" =>` in a `AtTr` node
|
|
TrContext -> [],
|
|
/// `| "foo" % n` in a `AtTr` node
|
|
TrPlural -> [Expression],
|
|
/// expression()
|
|
FunctionCallExpression -> [*Expression],
|
|
/// `expression[index]`
|
|
IndexExpression -> [2 Expression],
|
|
/// `expression += expression`
|
|
SelfAssignment -> [2 Expression],
|
|
/// `condition ? first : second`
|
|
ConditionalExpression -> [3 Expression],
|
|
/// `expr + expr`
|
|
BinaryExpression -> [2 Expression],
|
|
/// `- expr`
|
|
UnaryOpExpression -> [Expression],
|
|
/// `(foo).bar`, where `foo` is the base expression, and `bar` is a Identifier.
|
|
MemberAccess -> [Expression],
|
|
/// `[ ... ]`
|
|
Array -> [ *Expression ],
|
|
/// `{ foo: bar }`
|
|
ObjectLiteral -> [ *ObjectMember ],
|
|
/// `foo: bar` inside an ObjectLiteral
|
|
ObjectMember -> [ Expression ],
|
|
/// `states: [...]`
|
|
States -> [*State],
|
|
/// The DeclaredIdentifier is the state name. The Expression, if any, is the condition.
|
|
State -> [DeclaredIdentifier, ?Expression, *StatePropertyChange, *Transition],
|
|
/// binding within a state
|
|
StatePropertyChange -> [ QualifiedName, BindingExpression ],
|
|
/// `transitions: [...]`
|
|
Transitions -> [*Transition],
|
|
/// There is an identifier "in", "out", "in-out", the DeclaredIdentifier is the state name
|
|
Transition -> [?DeclaredIdentifier, *PropertyAnimation],
|
|
/// Export a set of declared components by name
|
|
ExportsList -> [ *ExportSpecifier, ?Component, *StructDeclaration, ?ExportModule, *EnumDeclaration ],
|
|
/// Declare the first identifier to be exported, either under its name or instead
|
|
/// under the name of the second identifier.
|
|
ExportSpecifier -> [ ExportIdentifier, ?ExportName ],
|
|
ExportIdentifier -> [],
|
|
ExportName -> [],
|
|
/// `export ... from "foo"`. The import uri is stored as string literal.
|
|
ExportModule -> [],
|
|
/// import { foo, bar, baz } from "blah"; The import uri is stored as string literal.
|
|
ImportSpecifier -> [ ?ImportIdentifierList ],
|
|
ImportIdentifierList -> [ *ImportIdentifier ],
|
|
/// { foo as bar } or just { foo }
|
|
ImportIdentifier -> [ ExternalName, ?InternalName ],
|
|
ExternalName -> [],
|
|
InternalName -> [],
|
|
/// The representation of a type
|
|
Type -> [ ?QualifiedName, ?ObjectType, ?ArrayType ],
|
|
/// `{foo: string, bar: string} `
|
|
ObjectType ->[ *ObjectTypeMember ],
|
|
/// `foo: type` inside an ObjectType
|
|
ObjectTypeMember -> [ Type ],
|
|
/// `[ type ]`
|
|
ArrayType -> [ Type ],
|
|
/// `struct Foo { ... }`
|
|
StructDeclaration -> [DeclaredIdentifier, ObjectType, ?AtRustAttr],
|
|
/// `enum Foo { bli, bla, blu }`
|
|
EnumDeclaration -> [DeclaredIdentifier, *EnumValue, ?AtRustAttr],
|
|
/// The value is a Identifier
|
|
EnumValue -> [],
|
|
/// `@rust-attr(...)`
|
|
AtRustAttr -> [],
|
|
}
|
|
}
|
|
|
|
impl From<SyntaxKind> for rowan::SyntaxKind {
|
|
fn from(v: SyntaxKind) -> Self {
|
|
rowan::SyntaxKind(v.into())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Token {
|
|
pub kind: SyntaxKind,
|
|
pub text: SmolStr,
|
|
pub offset: usize,
|
|
#[cfg(feature = "proc_macro_span")]
|
|
pub span: Option<proc_macro::Span>,
|
|
}
|
|
|
|
impl Default for Token {
|
|
fn default() -> Self {
|
|
Token {
|
|
kind: SyntaxKind::Eof,
|
|
text: Default::default(),
|
|
offset: 0,
|
|
#[cfg(feature = "proc_macro_span")]
|
|
span: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Token {
|
|
pub fn as_str(&self) -> &str {
|
|
self.text.as_str()
|
|
}
|
|
|
|
pub fn kind(&self) -> SyntaxKind {
|
|
self.kind
|
|
}
|
|
}
|
|
|
|
mod parser_trait {
|
|
//! module allowing to keep implementation details of the node private
|
|
use super::*;
|
|
|
|
pub trait Parser: Sized {
|
|
type Checkpoint: Clone;
|
|
|
|
/// Enter a new node. The node is going to be finished when
|
|
/// The return value of this function is dropped
|
|
///
|
|
/// (do not re-implement this function, re-implement
|
|
/// start_node_impl and finish_node_impl)
|
|
#[must_use = "The node will be finished when it is dropped"]
|
|
fn start_node(&mut self, kind: SyntaxKind) -> Node<'_, Self> {
|
|
self.start_node_impl(kind, None, NodeToken(()));
|
|
Node(self)
|
|
}
|
|
#[must_use = "use start_node_at to use this checkpoint"]
|
|
fn checkpoint(&mut self) -> Self::Checkpoint;
|
|
#[must_use = "The node will be finished when it is dropped"]
|
|
fn start_node_at(
|
|
&mut self,
|
|
checkpoint: impl Into<Option<Self::Checkpoint>>,
|
|
kind: SyntaxKind,
|
|
) -> Node<'_, Self> {
|
|
self.start_node_impl(kind, checkpoint.into(), NodeToken(()));
|
|
Node(self)
|
|
}
|
|
|
|
/// Can only be called by Node::drop
|
|
fn finish_node_impl(&mut self, token: NodeToken);
|
|
/// Can only be called by Self::start_node
|
|
fn start_node_impl(
|
|
&mut self,
|
|
kind: SyntaxKind,
|
|
checkpoint: Option<Self::Checkpoint>,
|
|
token: NodeToken,
|
|
);
|
|
|
|
/// Same as nth(0)
|
|
fn peek(&mut self) -> Token {
|
|
self.nth(0)
|
|
}
|
|
/// Peek the `n`th token, not including whitespace and comments
|
|
fn nth(&mut self, n: usize) -> Token;
|
|
fn consume(&mut self);
|
|
fn error(&mut self, e: impl Into<String>);
|
|
fn warning(&mut self, e: impl Into<String>);
|
|
|
|
/// Consume the token if it has the right kind, otherwise report a syntax error.
|
|
/// Returns true if the token was consumed.
|
|
fn expect(&mut self, kind: SyntaxKind) -> bool {
|
|
if !self.test(kind) {
|
|
self.error(format!("Syntax error: expected {kind}"));
|
|
return false;
|
|
}
|
|
true
|
|
}
|
|
|
|
/// If the token if of this type, consume it and return true, otherwise return false
|
|
fn test(&mut self, kind: SyntaxKind) -> bool {
|
|
if self.nth(0).kind() != kind {
|
|
return false;
|
|
}
|
|
self.consume();
|
|
true
|
|
}
|
|
|
|
/// consume everything until reaching a token of this kind
|
|
fn until(&mut self, kind: SyntaxKind) {
|
|
let mut parens = 0;
|
|
let mut braces = 0;
|
|
let mut brackets = 0;
|
|
loop {
|
|
match self.nth(0).kind() {
|
|
k if k == kind && parens == 0 && braces == 0 && brackets == 0 => break,
|
|
SyntaxKind::Eof => break,
|
|
SyntaxKind::LParent => parens += 1,
|
|
SyntaxKind::LBrace => braces += 1,
|
|
SyntaxKind::LBracket => brackets += 1,
|
|
SyntaxKind::RParent if parens == 0 => break,
|
|
SyntaxKind::RParent => parens -= 1,
|
|
SyntaxKind::RBrace if braces == 0 => break,
|
|
SyntaxKind::RBrace => braces -= 1,
|
|
SyntaxKind::RBracket if brackets == 0 => break,
|
|
SyntaxKind::RBracket => brackets -= 1,
|
|
_ => {}
|
|
};
|
|
self.consume();
|
|
}
|
|
self.expect(kind);
|
|
}
|
|
}
|
|
|
|
/// A token to proof that start_node_impl and finish_node_impl are only
|
|
/// called from the Node implementation
|
|
///
|
|
/// Since the constructor is private, it cannot be produced by anything else.
|
|
pub struct NodeToken(());
|
|
/// The return value of `DefaultParser::start_node`. This borrows the parser
|
|
/// and finishes the node on Drop
|
|
#[derive(derive_more::DerefMut)]
|
|
pub struct Node<'a, P: Parser>(&'a mut P);
|
|
impl<P: Parser> Drop for Node<'_, P> {
|
|
fn drop(&mut self) {
|
|
self.0.finish_node_impl(NodeToken(()));
|
|
}
|
|
}
|
|
impl<P: Parser> core::ops::Deref for Node<'_, P> {
|
|
type Target = P;
|
|
fn deref(&self) -> &Self::Target {
|
|
self.0
|
|
}
|
|
}
|
|
}
|
|
#[doc(inline)]
|
|
pub use parser_trait::*;
|
|
|
|
pub struct DefaultParser<'a> {
|
|
builder: rowan::GreenNodeBuilder<'static>,
|
|
tokens: Vec<Token>,
|
|
cursor: usize,
|
|
diags: &'a mut BuildDiagnostics,
|
|
source_file: SourceFile,
|
|
}
|
|
|
|
impl<'a> DefaultParser<'a> {
|
|
fn from_tokens(tokens: Vec<Token>, diags: &'a mut BuildDiagnostics) -> Self {
|
|
Self {
|
|
builder: Default::default(),
|
|
tokens,
|
|
cursor: 0,
|
|
diags,
|
|
source_file: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Constructor that create a parser from the source code
|
|
pub fn new(source: &str, diags: &'a mut BuildDiagnostics) -> Self {
|
|
Self::from_tokens(crate::lexer::lex(source), diags)
|
|
}
|
|
|
|
fn current_token(&self) -> Token {
|
|
self.tokens.get(self.cursor).cloned().unwrap_or_default()
|
|
}
|
|
|
|
/// Consume all the whitespace
|
|
pub fn consume_ws(&mut self) {
|
|
while matches!(self.current_token().kind, SyntaxKind::Whitespace | SyntaxKind::Comment) {
|
|
self.consume()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Parser for DefaultParser<'_> {
|
|
fn start_node_impl(
|
|
&mut self,
|
|
kind: SyntaxKind,
|
|
checkpoint: Option<Self::Checkpoint>,
|
|
_: NodeToken,
|
|
) {
|
|
if kind != SyntaxKind::Document {
|
|
self.consume_ws();
|
|
}
|
|
match checkpoint {
|
|
None => self.builder.start_node(kind.into()),
|
|
Some(cp) => self.builder.start_node_at(cp, kind.into()),
|
|
}
|
|
}
|
|
|
|
fn finish_node_impl(&mut self, _: NodeToken) {
|
|
self.builder.finish_node();
|
|
}
|
|
|
|
/// Peek the `n`th token, not including whitespace and comments
|
|
fn nth(&mut self, mut n: usize) -> Token {
|
|
self.consume_ws();
|
|
let mut c = self.cursor;
|
|
while n > 0 {
|
|
n -= 1;
|
|
c += 1;
|
|
while c < self.tokens.len()
|
|
&& matches!(self.tokens[c].kind, SyntaxKind::Whitespace | SyntaxKind::Comment)
|
|
{
|
|
c += 1;
|
|
}
|
|
}
|
|
self.tokens.get(c).cloned().unwrap_or_default()
|
|
}
|
|
|
|
/// Consume the current token
|
|
fn consume(&mut self) {
|
|
let t = self.current_token();
|
|
self.builder.token(t.kind.into(), t.text.as_str());
|
|
if t.kind != SyntaxKind::Eof {
|
|
self.cursor += 1;
|
|
}
|
|
}
|
|
|
|
/// Reports an error at the current token location
|
|
fn error(&mut self, e: impl Into<String>) {
|
|
let current_token = self.current_token();
|
|
#[allow(unused_mut)]
|
|
let mut span = crate::diagnostics::Span::new(current_token.offset);
|
|
#[cfg(feature = "proc_macro_span")]
|
|
{
|
|
span.span = current_token.span;
|
|
}
|
|
|
|
self.diags.push_error_with_span(
|
|
e.into(),
|
|
crate::diagnostics::SourceLocation {
|
|
source_file: Some(self.source_file.clone()),
|
|
span,
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Reports an error at the current token location
|
|
fn warning(&mut self, e: impl Into<String>) {
|
|
let current_token = self.current_token();
|
|
#[allow(unused_mut)]
|
|
let mut span = crate::diagnostics::Span::new(current_token.offset);
|
|
#[cfg(feature = "proc_macro_span")]
|
|
{
|
|
span.span = current_token.span;
|
|
}
|
|
|
|
self.diags.push_warning_with_span(
|
|
e.into(),
|
|
crate::diagnostics::SourceLocation {
|
|
source_file: Some(self.source_file.clone()),
|
|
span,
|
|
},
|
|
);
|
|
}
|
|
|
|
type Checkpoint = rowan::Checkpoint;
|
|
fn checkpoint(&mut self) -> Self::Checkpoint {
|
|
self.builder.checkpoint()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
|
|
pub enum Language {}
|
|
impl rowan::Language for Language {
|
|
type Kind = SyntaxKind;
|
|
fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
|
|
SyntaxKind::try_from(raw.0).unwrap()
|
|
}
|
|
fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
|
|
kind.into()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, derive_more::Deref)]
|
|
pub struct SyntaxNode {
|
|
#[deref]
|
|
pub node: rowan::SyntaxNode<Language>,
|
|
pub source_file: SourceFile,
|
|
}
|
|
|
|
#[derive(Debug, Clone, derive_more::Deref)]
|
|
pub struct SyntaxToken {
|
|
#[deref]
|
|
pub token: rowan::SyntaxToken<Language>,
|
|
pub source_file: SourceFile,
|
|
}
|
|
|
|
impl SyntaxToken {
|
|
pub fn parent(&self) -> SyntaxNode {
|
|
SyntaxNode { node: self.token.parent().unwrap(), source_file: self.source_file.clone() }
|
|
}
|
|
pub fn parent_ancestors(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
|
|
self.token
|
|
.parent_ancestors()
|
|
.map(|node| SyntaxNode { node, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn next_token(&self) -> Option<SyntaxToken> {
|
|
// Due to a bug (as of rowan 0.15.3), rowan::SyntaxToken::next_token doesn't work if a
|
|
// sibling don't have tokens.
|
|
// For example, if we have an expression like `if (true) {}` the
|
|
// ConditionalExpression has an empty Expression/CodeBlock for the else part,
|
|
// and next_token doesn't go into that.
|
|
// So re-implement
|
|
|
|
let token = self
|
|
.token
|
|
.next_sibling_or_token()
|
|
.and_then(|e| match e {
|
|
rowan::NodeOrToken::Node(n) => n.first_token(),
|
|
rowan::NodeOrToken::Token(t) => Some(t),
|
|
})
|
|
.or_else(|| {
|
|
self.token.parent_ancestors().find_map(|it| it.next_sibling_or_token()).and_then(
|
|
|e| match e {
|
|
rowan::NodeOrToken::Node(n) => n.first_token(),
|
|
rowan::NodeOrToken::Token(t) => Some(t),
|
|
},
|
|
)
|
|
})?;
|
|
Some(SyntaxToken { token, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn prev_token(&self) -> Option<SyntaxToken> {
|
|
let token = self.token.prev_token()?;
|
|
Some(SyntaxToken { token, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn text(&self) -> &str {
|
|
self.token.text()
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for SyntaxToken {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.token.fmt(f)
|
|
}
|
|
}
|
|
|
|
impl SyntaxNode {
|
|
pub fn child_node(&self, kind: SyntaxKind) -> Option<SyntaxNode> {
|
|
self.node
|
|
.children()
|
|
.find(|n| n.kind() == kind)
|
|
.map(|node| SyntaxNode { node, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn child_token(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
|
|
self.node
|
|
.children_with_tokens()
|
|
.find(|n| n.kind() == kind)
|
|
.and_then(|x| x.into_token())
|
|
.map(|token| SyntaxToken { token, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn child_text(&self, kind: SyntaxKind) -> Option<SmolStr> {
|
|
self.node
|
|
.children_with_tokens()
|
|
.find(|n| n.kind() == kind)
|
|
.and_then(|x| x.as_token().map(|x| x.text().into()))
|
|
}
|
|
pub fn descendants(&self) -> impl Iterator<Item = SyntaxNode> {
|
|
let source_file = self.source_file.clone();
|
|
self.node
|
|
.descendants()
|
|
.map(move |node| SyntaxNode { node, source_file: source_file.clone() })
|
|
}
|
|
pub fn kind(&self) -> SyntaxKind {
|
|
self.node.kind()
|
|
}
|
|
pub fn children(&self) -> impl Iterator<Item = SyntaxNode> {
|
|
let source_file = self.source_file.clone();
|
|
self.node.children().map(move |node| SyntaxNode { node, source_file: source_file.clone() })
|
|
}
|
|
pub fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken> {
|
|
let source_file = self.source_file.clone();
|
|
self.node.children_with_tokens().map(move |token| match token {
|
|
rowan::NodeOrToken::Node(node) => {
|
|
SyntaxNode { node, source_file: source_file.clone() }.into()
|
|
}
|
|
rowan::NodeOrToken::Token(token) => {
|
|
SyntaxToken { token, source_file: source_file.clone() }.into()
|
|
}
|
|
})
|
|
}
|
|
pub fn text(&self) -> rowan::SyntaxText {
|
|
self.node.text()
|
|
}
|
|
pub fn parent(&self) -> Option<SyntaxNode> {
|
|
self.node.parent().map(|node| SyntaxNode { node, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn first_token(&self) -> Option<SyntaxToken> {
|
|
self.node
|
|
.first_token()
|
|
.map(|token| SyntaxToken { token, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn last_token(&self) -> Option<SyntaxToken> {
|
|
self.node
|
|
.last_token()
|
|
.map(|token| SyntaxToken { token, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn token_at_offset(&self, offset: TextSize) -> rowan::TokenAtOffset<SyntaxToken> {
|
|
self.node
|
|
.token_at_offset(offset)
|
|
.map(|token| SyntaxToken { token, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn first_child(&self) -> Option<SyntaxNode> {
|
|
self.node
|
|
.first_child()
|
|
.map(|node| SyntaxNode { node, source_file: self.source_file.clone() })
|
|
}
|
|
pub fn first_child_or_token(&self) -> Option<NodeOrToken> {
|
|
self.node.first_child_or_token().map(|n_o_t| match n_o_t {
|
|
rowan::NodeOrToken::Node(node) => {
|
|
NodeOrToken::Node(SyntaxNode { node, source_file: self.source_file.clone() })
|
|
}
|
|
rowan::NodeOrToken::Token(token) => {
|
|
NodeOrToken::Token(SyntaxToken { token, source_file: self.source_file.clone() })
|
|
}
|
|
})
|
|
}
|
|
pub fn next_sibling(&self) -> Option<SyntaxNode> {
|
|
self.node
|
|
.next_sibling()
|
|
.map(|node| SyntaxNode { node, source_file: self.source_file.clone() })
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, derive_more::From)]
|
|
pub enum NodeOrToken {
|
|
Node(SyntaxNode),
|
|
Token(SyntaxToken),
|
|
}
|
|
|
|
impl NodeOrToken {
|
|
pub fn kind(&self) -> SyntaxKind {
|
|
match self {
|
|
NodeOrToken::Node(n) => n.kind(),
|
|
NodeOrToken::Token(t) => t.kind(),
|
|
}
|
|
}
|
|
|
|
pub fn as_node(&self) -> Option<&SyntaxNode> {
|
|
match self {
|
|
NodeOrToken::Node(n) => Some(n),
|
|
NodeOrToken::Token(_) => None,
|
|
}
|
|
}
|
|
|
|
pub fn as_token(&self) -> Option<&SyntaxToken> {
|
|
match self {
|
|
NodeOrToken::Node(_) => None,
|
|
NodeOrToken::Token(t) => Some(t),
|
|
}
|
|
}
|
|
|
|
pub fn into_token(self) -> Option<SyntaxToken> {
|
|
match self {
|
|
NodeOrToken::Token(t) => Some(t),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn into_node(self) -> Option<SyntaxNode> {
|
|
match self {
|
|
NodeOrToken::Node(n) => Some(n),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn text_range(&self) -> TextRange {
|
|
match self {
|
|
NodeOrToken::Node(n) => n.text_range(),
|
|
NodeOrToken::Token(t) => t.text_range(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Spanned for SyntaxNode {
|
|
fn span(&self) -> crate::diagnostics::Span {
|
|
crate::diagnostics::Span::new(self.node.text_range().start().into())
|
|
}
|
|
|
|
fn source_file(&self) -> Option<&SourceFile> {
|
|
Some(&self.source_file)
|
|
}
|
|
}
|
|
|
|
impl Spanned for Option<SyntaxNode> {
|
|
fn span(&self) -> crate::diagnostics::Span {
|
|
self.as_ref().map(|n| n.span()).unwrap_or_default()
|
|
}
|
|
|
|
fn source_file(&self) -> Option<&SourceFile> {
|
|
self.as_ref().and_then(|n| n.source_file())
|
|
}
|
|
}
|
|
|
|
impl Spanned for SyntaxToken {
|
|
fn span(&self) -> crate::diagnostics::Span {
|
|
crate::diagnostics::Span::new(self.token.text_range().start().into())
|
|
}
|
|
|
|
fn source_file(&self) -> Option<&SourceFile> {
|
|
Some(&self.source_file)
|
|
}
|
|
}
|
|
|
|
impl Spanned for NodeOrToken {
|
|
fn span(&self) -> crate::diagnostics::Span {
|
|
match self {
|
|
NodeOrToken::Node(n) => n.span(),
|
|
NodeOrToken::Token(t) => t.span(),
|
|
}
|
|
}
|
|
|
|
fn source_file(&self) -> Option<&SourceFile> {
|
|
match self {
|
|
NodeOrToken::Node(n) => n.source_file(),
|
|
NodeOrToken::Token(t) => t.source_file(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Spanned for Option<NodeOrToken> {
|
|
fn span(&self) -> crate::diagnostics::Span {
|
|
self.as_ref().map(|t| t.span()).unwrap_or_default()
|
|
}
|
|
fn source_file(&self) -> Option<&SourceFile> {
|
|
self.as_ref().and_then(|t| t.source_file())
|
|
}
|
|
}
|
|
|
|
impl Spanned for Option<SyntaxToken> {
|
|
fn span(&self) -> crate::diagnostics::Span {
|
|
self.as_ref().map(|t| t.span()).unwrap_or_default()
|
|
}
|
|
fn source_file(&self) -> Option<&SourceFile> {
|
|
self.as_ref().and_then(|t| t.source_file())
|
|
}
|
|
}
|
|
|
|
/// return the normalized identifier string of the first SyntaxKind::Identifier in this node
|
|
pub fn identifier_text(node: &SyntaxNode) -> Option<SmolStr> {
|
|
node.child_text(SyntaxKind::Identifier).map(|x| normalize_identifier(&x))
|
|
}
|
|
|
|
pub fn normalize_identifier(ident: &str) -> SmolStr {
|
|
let mut builder = smol_str::SmolStrBuilder::default();
|
|
for (pos, c) in ident.chars().enumerate() {
|
|
match (pos, c) {
|
|
(0, '-') | (0, '_') => builder.push('_'),
|
|
(_, '_') => builder.push('-'),
|
|
(_, c) => builder.push(c),
|
|
}
|
|
}
|
|
builder.finish()
|
|
}
|
|
|
|
#[test]
|
|
fn test_normalize_identifier() {
|
|
assert_eq!(normalize_identifier("true"), SmolStr::new("true"));
|
|
assert_eq!(normalize_identifier("foo_bar"), SmolStr::new("foo-bar"));
|
|
assert_eq!(normalize_identifier("-foo_bar"), SmolStr::new("_foo-bar"));
|
|
assert_eq!(normalize_identifier("-foo-bar"), SmolStr::new("_foo-bar"));
|
|
assert_eq!(normalize_identifier("foo_bar_"), SmolStr::new("foo-bar-"));
|
|
assert_eq!(normalize_identifier("foo_bar-"), SmolStr::new("foo-bar-"));
|
|
assert_eq!(normalize_identifier("_foo_bar_"), SmolStr::new("_foo-bar-"));
|
|
assert_eq!(normalize_identifier("__1"), SmolStr::new("_-1"));
|
|
assert_eq!(normalize_identifier("--1"), SmolStr::new("_-1"));
|
|
assert_eq!(normalize_identifier("--1--"), SmolStr::new("_-1--"));
|
|
}
|
|
|
|
// Actual parser
|
|
pub fn parse(
|
|
source: String,
|
|
path: Option<&std::path::Path>,
|
|
build_diagnostics: &mut BuildDiagnostics,
|
|
) -> SyntaxNode {
|
|
let mut p = DefaultParser::new(&source, build_diagnostics);
|
|
p.source_file = std::rc::Rc::new(crate::diagnostics::SourceFileInner::new(
|
|
path.map(crate::pathutils::clean_path).unwrap_or_default(),
|
|
source,
|
|
));
|
|
document::parse_document(&mut p);
|
|
SyntaxNode {
|
|
node: rowan::SyntaxNode::new_root(p.builder.finish()),
|
|
source_file: p.source_file.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn parse_file<P: AsRef<std::path::Path>>(
|
|
path: P,
|
|
build_diagnostics: &mut BuildDiagnostics,
|
|
) -> Option<SyntaxNode> {
|
|
let path = crate::pathutils::clean_path(path.as_ref());
|
|
let source = crate::diagnostics::load_from_path(&path)
|
|
.map_err(|d| build_diagnostics.push_internal_error(d))
|
|
.ok()?;
|
|
Some(parse(source, Some(path.as_ref()), build_diagnostics))
|
|
}
|
|
|
|
pub fn parse_tokens(
|
|
tokens: Vec<Token>,
|
|
source_file: SourceFile,
|
|
diags: &mut BuildDiagnostics,
|
|
) -> SyntaxNode {
|
|
let mut p = DefaultParser::from_tokens(tokens, diags);
|
|
document::parse_document(&mut p);
|
|
SyntaxNode { node: rowan::SyntaxNode::new_root(p.builder.finish()), source_file }
|
|
}
|