use super::document::parse_qualified_name;
use super::expressions::parse_expression;
use super::prelude::*;
use super::statements::parse_statement;
use super::r#type::parse_type;
#[cfg_attr(test, parser_test)]
pub fn parse_element(p: &mut impl Parser) -> bool {
let mut p = p.start_node(SyntaxKind::Element);
if !parse_qualified_name(&mut *p) {
return if p.test(SyntaxKind::LBrace) {
parse_element_content(&mut *p);
p.expect(SyntaxKind::RBrace)
} else {
false
};
}
if !p.expect(SyntaxKind::LBrace) {
return false;
}
parse_element_content(&mut *p);
p.expect(SyntaxKind::RBrace)
}
#[cfg_attr(test, parser_test)]
pub fn parse_element_content(p: &mut impl Parser) {
let mut had_parse_error = false;
loop {
match p.nth(0).kind() {
SyntaxKind::RBrace => return,
SyntaxKind::Eof => return,
SyntaxKind::Identifier => match p.nth(1).kind() {
SyntaxKind::Identifier | SyntaxKind::Semicolon if p.peek().as_str() == "slot" => {
parse_slot_declaration(&mut *p)
}
SyntaxKind::Colon => parse_property_binding(&mut *p),
SyntaxKind::DoubleLess => {
had_parse_error |= !parse_slot_assignment_or_forwarding(&mut *p)
}
SyntaxKind::ColonEqual | SyntaxKind::LBrace => {
had_parse_error |= !parse_sub_element(&mut *p);
}
SyntaxKind::FatArrow | SyntaxKind::LParent
if !["if", "match"].contains(&p.peek().as_str()) =>
{
parse_callback_connection(&mut *p)
}
SyntaxKind::DoubleArrow => parse_two_way_binding(&mut *p),
SyntaxKind::Identifier if p.peek().as_str() == "for" => {
parse_repeated_element(&mut *p);
}
SyntaxKind::Identifier
if p.peek().as_str() == "callback"
|| (p.peek().as_str() == "pure" && p.nth(1).as_str() == "callback") =>
{
parse_callback_declaration(&mut *p, None);
}
SyntaxKind::Identifier
if p.peek().as_str() == "function"
|| (matches!(p.peek().as_str(), "public" | "pure" | "protected")
&& p.nth(1).as_str() == "function")
|| (matches!(p.nth(1).as_str(), "public" | "pure" | "protected")
&& p.nth(2).as_str() == "function") =>
{
parse_function(&mut *p, None);
}
SyntaxKind::Identifier | SyntaxKind::Star if p.peek().as_str() == "animate" => {
parse_property_animation(&mut *p);
}
SyntaxKind::Identifier if p.peek().as_str() == "changed" => {
parse_changed_callback(&mut *p);
}
SyntaxKind::LAngle | SyntaxKind::Identifier if p.peek().as_str() == "property" => {
parse_property_declaration(&mut *p, None);
}
SyntaxKind::Identifier
if p.nth(1).as_str() == "property"
&& matches!(
p.peek().as_str(),
"in" | "out" | "in_out" | "in-out" | "private"
) =>
{
parse_property_declaration(&mut *p, None);
}
_ if p.peek().as_str() == "if" => {
parse_if_element(&mut *p);
}
SyntaxKind::Identifier | SyntaxKind::LParent if p.peek().as_str() == "match" => {
let mut i = 2;
loop {
match p.nth(i).kind() {
SyntaxKind::FatArrow => {
parse_callback_connection(&mut *p);
break;
}
SyntaxKind::LBrace => {
parse_match_element(&mut *p);
break;
}
SyntaxKind::Eof => {
if !had_parse_error {
p.error("Error: Expected '{'");
had_parse_error = true;
}
break;
}
_ => i += 1,
}
}
}
SyntaxKind::LBracket if p.peek().as_str() == "states" => {
parse_states(&mut *p);
}
SyntaxKind::LBracket if p.peek().as_str() == "transitions" => {
parse_transitions(&mut *p);
}
SyntaxKind::Identifier if p.peek().as_str() == "implement" => {
parse_implement_statement(&mut *p);
}
_ => {
if p.peek().as_str() == "changed" {
parse_changed_callback(&mut *p);
} else {
p.consume();
if !had_parse_error {
p.error("Parse error");
had_parse_error = true;
}
}
}
},
SyntaxKind::At => {
if matches!(p.nth(1).as_str(), "deprecated" | "shadowable") {
let checkpoint = p.checkpoint();
let attribute = parse_member_attributes(&mut *p);
let mut i = 0;
while matches!(
p.nth(i).as_str(),
"in" | "out"
| "in-out"
| "in_out"
| "private"
| "public"
| "protected"
| "pure"
) {
i += 1;
}
match p.nth(i).as_str() {
"callback" => parse_callback_declaration(&mut *p, Some(checkpoint)),
"function" => parse_function(&mut *p, Some(checkpoint)),
"property" => parse_property_declaration(&mut *p, Some(checkpoint)),
_ => {
(0..i).for_each(|_| p.consume());
if let Some(attribute) = attribute {
p.error(format!(
"@{attribute} can only be applied to a member declaration"
));
}
}
}
continue;
}
let checkpoint = p.checkpoint();
p.consume();
if p.peek().as_str() == "children" {
let mut p =
p.start_node_at(checkpoint.clone(), SyntaxKind::ChildrenPlaceholder);
p.consume()
} else {
p.test(SyntaxKind::Identifier);
p.error("Parse error: Expected @children")
}
}
_ => {
if !had_parse_error {
p.error("Parse error");
had_parse_error = true;
}
p.consume();
}
}
}
}
#[cfg_attr(test, parser_test)]
fn parse_sub_element(p: &mut impl Parser) -> bool {
let mut p = p.start_node(SyntaxKind::SubElement);
if p.nth(1).kind() == SyntaxKind::ColonEqual {
p.expect(SyntaxKind::Identifier);
p.expect(SyntaxKind::ColonEqual);
}
parse_element(&mut *p)
}
#[cfg_attr(test, parser_test)]
fn parse_slot_declaration(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "slot");
let mut p = p.start_node(SyntaxKind::SlotDeclaration);
p.expect(SyntaxKind::Identifier); {
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
p.expect(SyntaxKind::Semicolon);
}
#[cfg_attr(test, parser_test)]
fn parse_slot_assignment(p: &mut impl Parser) -> bool {
let mut p = p.start_node(SyntaxKind::SlotAssignment);
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
p.expect(SyntaxKind::DoubleLess);
if !parse_sub_element(&mut *p) {
p.error("Expected element after '<<'");
return false;
}
true
}
#[cfg_attr(test, parser_test)]
fn parse_slot_forwarding(p: &mut impl Parser) -> bool {
let mut p = p.start_node(SyntaxKind::SlotForwarding);
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
p.expect(SyntaxKind::DoubleLess);
if !parse_expression(&mut *p) {
return false;
}
p.expect(SyntaxKind::Semicolon)
}
fn parse_slot_assignment_or_forwarding(p: &mut impl Parser) -> bool {
debug_assert_eq!(p.nth(1).kind(), SyntaxKind::DoubleLess);
let rhs_first = p.nth(2).kind();
let rhs_second = p.nth(3).kind();
let is_slot_assignment = rhs_first == SyntaxKind::Identifier
&& matches!(rhs_second, SyntaxKind::LBrace | SyntaxKind::ColonEqual);
if is_slot_assignment { parse_slot_assignment(p) } else { parse_slot_forwarding(p) }
}
#[cfg_attr(test, parser_test)]
fn parse_repeated_element(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "for");
let mut p = p.start_node(SyntaxKind::RepeatedElement);
p.expect(SyntaxKind::Identifier); if p.nth(0).kind() == SyntaxKind::Identifier {
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
if p.nth(0).kind() == SyntaxKind::LBracket {
let mut p = p.start_node(SyntaxKind::RepeatedIndex);
p.expect(SyntaxKind::LBracket);
p.expect(SyntaxKind::Identifier);
p.expect(SyntaxKind::RBracket);
}
if p.peek().as_str() != "in" {
p.error("Invalid 'for' syntax: there should be a 'in' token");
drop(p.start_node(SyntaxKind::Expression));
drop(p.start_node(SyntaxKind::SubElement).start_node(SyntaxKind::Element));
return;
}
p.consume(); parse_expression(&mut *p);
p.expect(SyntaxKind::Colon);
parse_sub_element(&mut *p);
}
#[cfg_attr(test, parser_test)]
fn parse_if_element(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "if");
let mut p = p.start_node(SyntaxKind::ConditionalElement);
p.expect(SyntaxKind::Identifier); parse_expression(&mut *p);
if !p.expect(SyntaxKind::Colon) {
drop(p.start_node(SyntaxKind::SubElement).start_node(SyntaxKind::Element));
return;
}
parse_sub_element(&mut *p);
}
#[cfg_attr(test, parser_test)]
fn parse_match_element(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "match");
let mut p = p.start_node(SyntaxKind::MatchElement);
p.expect(SyntaxKind::Identifier); parse_expression(&mut *p);
if !p.test(SyntaxKind::LBrace) {
p.error("Expected '{' to start cases");
}
while ![SyntaxKind::RBrace, SyntaxKind::Star, SyntaxKind::Eof].contains(&p.peek().kind()) {
parse_match_case(&mut *p);
}
if p.peek().kind() == SyntaxKind::Star {
parse_wildcard_case(&mut *p);
let mut reported = false;
while p.peek().kind() == SyntaxKind::Star
|| (![SyntaxKind::RBrace, SyntaxKind::Eof].contains(&p.peek().kind())
&& p.nth(1).kind() == SyntaxKind::Colon)
{
if !reported {
p.error("Cases after the '*' case are never hit");
reported = true;
}
if p.peek().kind() == SyntaxKind::Star {
parse_wildcard_case(&mut *p);
} else {
parse_match_case(&mut *p);
}
}
}
p.expect(SyntaxKind::RBrace);
}
#[cfg_attr(test, parser_test)]
fn parse_match_case(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::MatchCase);
parse_expression(&mut *p);
parse_case_inner(&mut *p, "match case");
}
#[cfg_attr(test, parser_test)]
fn parse_wildcard_case(p: &mut impl Parser) {
debug_assert_eq!(p.peek().kind(), SyntaxKind::Star);
let mut p = p.start_node(SyntaxKind::WildcardMatchCase);
p.expect(SyntaxKind::Star);
parse_case_inner(&mut *p, "'*'");
}
fn parse_case_inner(p: &mut impl Parser, after: &str) {
if !p.test(SyntaxKind::Colon) {
p.error(format!("Expected ':' after {after}"));
if p.peek().kind() != SyntaxKind::Identifier {
p.consume();
}
}
if p.peek().kind() == SyntaxKind::LBrace {
p.expect(SyntaxKind::LBrace);
if p.peek().kind() == SyntaxKind::Identifier {
p.error("Remove '{ }' around case element");
parse_sub_element(&mut *p);
}
p.expect(SyntaxKind::RBrace);
return;
}
parse_sub_element(&mut *p);
}
#[cfg_attr(test, parser_test)]
fn parse_property_binding(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::Binding);
p.consume();
p.expect(SyntaxKind::Colon);
parse_binding_expression(&mut *p);
}
#[cfg_attr(test, parser_test)]
fn parse_binding_expression(p: &mut impl Parser) -> bool {
let mut p = p.start_node(SyntaxKind::BindingExpression);
if p.nth(0).kind() == SyntaxKind::LBrace && p.nth(2).kind() != SyntaxKind::Colon {
parse_code_block(&mut *p);
p.test(SyntaxKind::Semicolon);
true
} else if parse_expression(&mut *p) {
p.expect(SyntaxKind::Semicolon)
} else {
p.test(SyntaxKind::Semicolon);
false
}
}
#[cfg_attr(test, parser_test)]
pub fn parse_code_block(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::CodeBlock);
p.expect(SyntaxKind::LBrace);
while p.nth(0).kind() != SyntaxKind::RBrace {
if !parse_statement(&mut *p) {
break;
}
}
p.expect(SyntaxKind::RBrace);
}
#[cfg_attr(test, parser_test)]
fn parse_callback_connection(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::CallbackConnection);
p.consume(); if p.test(SyntaxKind::LParent) {
while p.peek().kind() != SyntaxKind::RParent {
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
if !p.test(SyntaxKind::Comma) {
break;
}
}
p.expect(SyntaxKind::RParent);
}
p.expect(SyntaxKind::FatArrow);
if p.nth(0).kind() == SyntaxKind::LBrace && p.nth(2).kind() != SyntaxKind::Colon {
parse_code_block(&mut *p);
p.test(SyntaxKind::Semicolon);
} else if parse_expression(&mut *p) {
p.expect(SyntaxKind::Semicolon);
} else {
p.test(SyntaxKind::Semicolon);
}
}
#[cfg_attr(test, parser_test)]
fn parse_two_way_binding(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::TwoWayBinding);
p.consume(); p.expect(SyntaxKind::DoubleArrow);
parse_expression(&mut *p);
p.expect(SyntaxKind::Semicolon);
}
#[cfg_attr(test, parser_test)]
fn parse_implement_statement(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "implement");
let mut p = p.start_node(SyntaxKind::ImplementStatement);
p.expect(SyntaxKind::Identifier); parse_qualified_name(&mut *p);
p.expect(SyntaxKind::DoubleArrow);
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
p.expect(SyntaxKind::Semicolon);
}
#[cfg_attr(test, parser_test)]
fn parse_callback_declaration<P: Parser>(p: &mut P, checkpoint: Option<P::Checkpoint>) {
let checkpoint = checkpoint.unwrap_or_else(|| p.checkpoint());
let mut p = p.start_node_at(checkpoint, SyntaxKind::CallbackDeclaration);
if p.peek().as_str() == "pure" {
p.consume();
}
debug_assert_eq!(p.peek().as_str(), "callback");
p.expect(SyntaxKind::Identifier); {
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
if p.test(SyntaxKind::LParent) {
while p.peek().kind() != SyntaxKind::RParent {
{
let mut p = p.start_node(SyntaxKind::CallbackDeclarationParameter);
if p.peek().kind() == SyntaxKind::Identifier && p.nth(1).kind() == SyntaxKind::Colon
{
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
p.expect(SyntaxKind::Colon);
}
parse_type(&mut *p);
}
if !p.test(SyntaxKind::Comma) {
break;
}
}
p.expect(SyntaxKind::RParent);
if p.test(SyntaxKind::Arrow) {
let mut p = p.start_node(SyntaxKind::ReturnType);
parse_type(&mut *p);
}
if p.peek().kind() == SyntaxKind::DoubleArrow {
p.error("When declaring a callback alias, one must omit parentheses. e.g. 'callback foo <=> other.bar;'");
}
} else if p.test(SyntaxKind::Arrow) {
p.error("Callback with return value must be declared with parentheses e.g. 'callback foo() -> int;'");
parse_type(&mut *p);
}
if p.peek().kind() == SyntaxKind::DoubleArrow {
let mut p = p.start_node(SyntaxKind::TwoWayBinding);
p.expect(SyntaxKind::DoubleArrow);
parse_expression(&mut *p);
}
p.expect(SyntaxKind::Semicolon);
}
#[cfg_attr(test, parser_test)]
fn parse_member_attributes(p: &mut impl Parser) -> Option<&'static str> {
let mut seen: Vec<&'static str> = Vec::new();
while p.nth(0).kind() == SyntaxKind::At
&& matches!(p.nth(1).as_str(), "deprecated" | "shadowable")
{
let is_deprecated = p.nth(1).as_str() == "deprecated";
let (name, kind) = if is_deprecated {
("deprecated", SyntaxKind::PropertyDeprecation)
} else {
("shadowable", SyntaxKind::ShadowableAttribute)
};
let duplicated = seen.contains(&name);
seen.push(name);
let mut p = p.start_node(kind);
p.consume(); p.consume(); if duplicated {
p.error(format!("Duplicated @{name} attribute"));
}
if is_deprecated && p.test(SyntaxKind::LParent) {
let peek = p.peek();
if peek.kind() != SyntaxKind::StringLiteral
|| !peek.as_str().starts_with('"')
|| !peek.as_str().ends_with('"')
{
p.error("@deprecated message must be a plain string literal, without any '\\{}' expressions");
p.until(SyntaxKind::RParent);
} else {
p.expect(SyntaxKind::StringLiteral);
p.expect(SyntaxKind::RParent);
}
}
}
seen.first().copied()
}
#[cfg_attr(test, parser_test)]
fn parse_property_declaration<P: Parser>(p: &mut P, checkpoint: Option<P::Checkpoint>) {
let checkpoint = checkpoint.unwrap_or_else(|| p.checkpoint());
while matches!(p.peek().as_str(), "in" | "out" | "in-out" | "in_out" | "private") {
p.consume();
}
if p.peek().as_str() != "property" {
p.error("Expected 'property' keyword");
return;
}
let mut p = p.start_node_at(checkpoint, SyntaxKind::PropertyDeclaration);
p.consume();
if p.test(SyntaxKind::LAngle) {
parse_type(&mut *p);
p.expect(SyntaxKind::RAngle);
} else if p.nth(0).kind() == SyntaxKind::Identifier
&& p.nth(1).kind() != SyntaxKind::DoubleArrow
{
p.error("Missing type. The syntax to declare a property is `property <type> name;`. Only two way bindings can omit the type");
}
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
match p.nth(0).kind() {
SyntaxKind::Colon => {
p.consume();
parse_binding_expression(&mut *p);
}
SyntaxKind::DoubleArrow => {
let mut p = p.start_node(SyntaxKind::TwoWayBinding);
p.consume();
parse_expression(&mut *p);
p.expect(SyntaxKind::Semicolon);
}
_ => {
p.expect(SyntaxKind::Semicolon);
}
}
}
#[cfg_attr(test, parser_test)]
fn parse_property_animation(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "animate");
let mut p = p.start_node(SyntaxKind::PropertyAnimation);
p.expect(SyntaxKind::Identifier); if p.nth(0).kind() == SyntaxKind::Star {
p.consume();
} else {
parse_qualified_name(&mut *p);
while p.nth(0).kind() == SyntaxKind::Comma {
p.consume();
parse_qualified_name(&mut *p);
}
};
p.expect(SyntaxKind::LBrace);
loop {
match p.nth(0).kind() {
SyntaxKind::RBrace => {
p.consume();
return;
}
SyntaxKind::Eof => return,
SyntaxKind::Identifier => match p.nth(1).kind() {
SyntaxKind::Colon => parse_property_binding(&mut *p),
_ => {
p.consume();
p.error("Only bindings are allowed in animations");
}
},
_ => {
p.consume();
p.error("Only bindings are allowed in animations");
}
}
}
}
#[cfg_attr(test, parser_test)]
fn parse_changed_callback(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "changed");
let mut p = p.start_node(SyntaxKind::PropertyChangedCallback);
p.expect(SyntaxKind::Identifier); {
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
p.expect(SyntaxKind::FatArrow);
if p.nth(0).kind() == SyntaxKind::LBrace && p.nth(2).kind() != SyntaxKind::Colon {
parse_code_block(&mut *p);
p.test(SyntaxKind::Semicolon);
} else if parse_expression(&mut *p) {
p.expect(SyntaxKind::Semicolon);
} else {
p.test(SyntaxKind::Semicolon);
}
}
#[cfg_attr(test, parser_test)]
fn parse_states(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "states");
let mut p = p.start_node(SyntaxKind::States);
p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LBracket);
while parse_state(&mut *p) {}
p.expect(SyntaxKind::RBracket);
}
#[cfg_attr(test, parser_test)]
fn parse_state(p: &mut impl Parser) -> bool {
if p.nth(0).kind() != SyntaxKind::Identifier {
return false;
}
let mut p = p.start_node(SyntaxKind::State);
{
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
if p.peek().as_str() == "when" {
p.consume();
parse_expression(&mut *p);
}
p.expect(SyntaxKind::Colon);
if !p.expect(SyntaxKind::LBrace) {
return false;
}
loop {
match p.nth(0).kind() {
SyntaxKind::RBrace => {
p.consume();
return true;
}
SyntaxKind::Eof => return false,
_ => {
if p.nth(1).kind() == SyntaxKind::LBrace
&& matches!(p.peek().as_str(), "in" | "out" | "in-out" | "in_out")
{
let mut p = p.start_node(SyntaxKind::Transition);
p.consume(); p.expect(SyntaxKind::LBrace);
if !parse_transition_inner(&mut *p) {
return false;
}
continue;
};
let checkpoint = p.checkpoint();
if !parse_qualified_name(&mut *p)
|| !p.expect(SyntaxKind::Colon)
|| !parse_binding_expression(&mut *p)
{
p.test(SyntaxKind::RBrace);
return false;
}
let _ = p.start_node_at(checkpoint, SyntaxKind::StatePropertyChange);
}
}
}
}
#[cfg_attr(test, parser_test)]
fn parse_transitions(p: &mut impl Parser) {
debug_assert_eq!(p.peek().as_str(), "transitions");
let mut p = p.start_node(SyntaxKind::Transitions);
p.expect(SyntaxKind::Identifier); p.expect(SyntaxKind::LBracket);
while p.nth(0).kind() != SyntaxKind::RBracket && parse_transition(&mut *p) {}
p.expect(SyntaxKind::RBracket);
}
#[cfg_attr(test, parser_test)]
fn parse_transition(p: &mut impl Parser) -> bool {
if !matches!(p.peek().as_str(), "in" | "out" | "in-out" | "in_out") {
p.error("Expected 'in', 'out', or 'in-out' to declare a transition");
return false;
}
let mut p = p.start_node(SyntaxKind::Transition);
p.consume(); {
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
p.expect(SyntaxKind::Colon);
if !p.expect(SyntaxKind::LBrace) {
return false;
}
parse_transition_inner(&mut *p)
}
#[cfg_attr(test, parser_test)]
fn parse_transition_inner(p: &mut impl Parser) -> bool {
loop {
match p.nth(0).kind() {
SyntaxKind::RBrace => {
p.consume();
return true;
}
SyntaxKind::Eof => return false,
SyntaxKind::Identifier if p.peek().as_str() == "animate" => {
parse_property_animation(&mut *p);
}
_ => {
p.consume();
p.error("Expected 'animate'");
}
}
}
}
#[cfg_attr(test, parser_test)]
fn parse_function<P: Parser>(p: &mut P, checkpoint: Option<P::Checkpoint>) {
let checkpoint = checkpoint.unwrap_or_else(|| p.checkpoint());
let mut p = p.start_node_at(checkpoint, SyntaxKind::Function);
if matches!(p.peek().as_str(), "public" | "protected") {
p.consume();
if p.peek().as_str() == "pure" {
p.consume()
}
} else if p.peek().as_str() == "pure" {
p.consume();
if matches!(p.peek().as_str(), "public" | "protected") {
p.consume()
}
}
if p.peek().as_str() != "function" {
p.error("Unexpected identifier");
p.consume();
while p.peek().kind == SyntaxKind::Identifier && p.peek().as_str() != "function" {
p.consume();
}
}
debug_assert_eq!(p.peek().as_str(), "function");
p.expect(SyntaxKind::Identifier); {
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
if p.expect(SyntaxKind::LParent) {
while p.peek().kind() != SyntaxKind::RParent {
let mut p_arg = p.start_node(SyntaxKind::ArgumentDeclaration);
{
let mut p = p_arg.start_node(SyntaxKind::DeclaredIdentifier);
p.expect(SyntaxKind::Identifier);
}
p_arg.expect(SyntaxKind::Colon);
parse_type(&mut *p_arg);
drop(p_arg);
if !p.test(SyntaxKind::Comma) {
break;
}
}
p.expect(SyntaxKind::RParent);
if p.test(SyntaxKind::Arrow) {
let mut p = p.start_node(SyntaxKind::ReturnType);
parse_type(&mut *p);
}
}
if p.peek().kind() == SyntaxKind::LBrace {
parse_code_block(&mut *p);
} else if !p.test(SyntaxKind::Semicolon) {
p.error("Expected function body or semicolon");
}
}