luau-syntax 0.732.0

Luau lexer, parser, AST, CST, and source utilities
Documentation
use super::*;

impl<'source, 'ast, 'name, 'names> Parser<'source, 'ast, 'name, 'names>
where
    'name: 'ast,
{
    pub(in crate::parser) fn parse_do(
        &mut self,
        location: Location,
    ) -> Result<ParsedStatement<'ast>> {
        let begin = self.current_token_location().begin;
        self.advance();
        let stats_start = self.current_position();
        let mut body = self.parse_block_until(&["end"], "do", location)?;
        let end_location = self.current_token_location();
        let has_end = self.expect_match_end_with_lookahead("do", location);
        let end = if has_end {
            end_location.begin
        } else {
            Position::missing()
        };
        body.has_end = has_end;
        body.base.location.begin = begin;
        if has_end {
            body.base.location.end = end_location.end;
        }

        let body = self.arena.alloc_block(body);
        Ok(self.parsed_statement_from_node(
            body.as_statement(),
            self.cst_node(|| CstNode::StatDo(CstStatDo { stats_start, end })),
        ))
    }

    pub(in crate::parser) fn parse_for(
        &mut self,
        location: Location,
    ) -> Result<ParsedStatement<'ast>> {
        self.advance();

        let binding = self.parse_binding(false)?;

        if self.current.kind() != TokenKind::Equal {
            return self.parse_generic_for(location, binding);
        }

        self.parse_numeric_for(binding, location)
    }

    pub(in crate::parser) fn expect_match_end_with_lookahead(
        &mut self,
        opener: &'static str,
        opener_location: Location,
    ) -> bool {
        self.expect_match_keyword_with_lookahead(R::End, "end", opener, opener_location)
    }

    pub(in crate::parser) fn expect_match_keyword_with_lookahead(
        &mut self,
        expected: R,
        expected_name: &'static str,
        opener: &'static str,
        opener_location: Location,
    ) -> bool {
        if self.current.kind() == TokenKind::Reserved(expected) {
            let current_location = self.current_token_location();
            if current_location.begin.line != opener_location.begin.line
                && current_location.begin.column != opener_location.begin.column
                && self
                    .contexts
                    .end_mismatch_suspect
                    .is_none_or(|suspect| suspect.line < opener_location.begin.line as usize + 1)
            {
                self.contexts.end_mismatch_suspect = Some(BlockContext {
                    opener,
                    line: opener_location.begin.line as usize + 1,
                    column: opener_location.begin.column as usize + 1,
                });
            }
            self.advance();
            return true;
        }

        let error = self.matched_block_token_error(expected_name, opener, opener_location);
        self.report_parse_error(error);
        if self.peek().kind() == TokenKind::Reserved(expected) {
            self.advance();
            self.advance();
            true
        } else {
            false
        }
    }

    pub(in crate::parser) fn expect_match_token(
        &mut self,
        expected: Token<'source, 'ast>,
        expected_name: &'static str,
        opener: &'static str,
        opener_location: Location,
        search_for_missing: bool,
    ) -> bool {
        let expected_kind = expected.kind();
        if self.current.kind() == expected_kind {
            self.advance();
            return true;
        }

        let error = self.matched_token_error(expected_name, opener, opener_location);
        self.report_parse_error(error);
        if search_for_missing {
            self.recover_match_on_line(expected)
        } else if self.peek().kind() == expected_kind {
            self.advance();
            self.advance();
            true
        } else {
            false
        }
    }

    pub(in crate::parser) fn parse_numeric_for(
        &mut self,
        binding: Binding<'ast>,
        location: Location,
    ) -> Result<ParsedStatement<'ast>> {
        let equals = self.current_position();
        self.advance();
        let start = self.parse_expression()?;
        let end_comma = if self.expect_and_consume(Token::Comma, "index range") {
            self.previous_token_location().begin
        } else {
            Position::missing()
        };
        let limit = self.parse_expression()?;
        let step = if self.current.kind() == TokenKind::Comma {
            let step_comma = self.current_position();
            self.advance();
            Some((self.parse_expression()?, step_comma))
        } else {
            None
        };
        let do_location = self.current_location();
        let has_do = self.expect_and_consume_keyword(R::Do, "for loop");
        self.push_local_scope();
        self.current_function_mut().loop_depth += 1;
        let local = self.declare_local(
            binding.name,
            binding.name_location,
            binding.annotation,
            binding.is_const,
            false,
        );
        let body = self.parse_block_until_no_scope(&["end"], "do", do_location);
        self.current_function_mut().loop_depth -= 1;
        self.pop_local_scope();
        let mut body = body?;
        let end = self.current_token_location();
        body.has_end = self.expect_match_end_with_lookahead("do", do_location);
        let location = Location::new(location.begin, end.end);
        let body = self.arena.alloc_block(body);

        let statement = self.arena.alloc_statement_node(StatementNumericFor::new(
            location,
            false,
            local,
            start,
            limit,
            step.map(|(expression, _)| expression),
            body,
            has_do,
            do_location,
        ));
        Ok(self.parsed_statement_from_node(
            statement,
            self.cst_node(|| {
                CstNode::StatFor(CstStatFor {
                    annotation_colon: binding.colon_position,
                    equals,
                    end_comma,
                    step_comma: step.as_ref().map(|(_, comma)| *comma),
                })
            }),
        ))
    }

    pub(in crate::parser) fn parse_generic_for(
        &mut self,
        location: Location,
        first_binding: Binding<'ast>,
    ) -> Result<ParsedStatement<'ast>> {
        let mut bindings = self.temp_bindings();
        bindings.push_back(first_binding);
        let mut variable_commas = self.temp_positions();

        if self.current.kind() == TokenKind::Comma {
            if self.cst.enabled() {
                variable_commas.push_back(self.current_position());
            }
            self.advance();
            self.parse_binding_list_into(&mut bindings, false, &mut variable_commas, false)?;
        }

        let in_location = self.current_location();
        let has_in = self.expect_and_consume_keyword(R::In, "for loop");
        let mut values = self.temp_expressions();
        let mut value_commas = self.temp_positions();
        self.parse_expression_list_into(&mut values, &mut value_commas)?;
        let do_location = self.current_location();
        let has_do = self.expect_and_consume_keyword(R::Do, "for loop");
        self.push_local_scope();
        self.current_function_mut().loop_depth += 1;
        let mut locals = self.temp_locals();
        for binding in bindings.iter() {
            locals.push_back(self.declare_local(
                binding.name,
                binding.name_location,
                binding.annotation,
                binding.is_const,
                false,
            ));
        }
        let body = self.parse_block_until_no_scope(&["end"], "do", do_location);
        self.current_function_mut().loop_depth -= 1;
        self.pop_local_scope();
        let mut body = body?;
        let end = self.current_token_location();
        body.has_end = self.expect_match_end_with_lookahead("do", do_location);
        let location = Location::new(location.begin, end.end);
        let body = self.arena.alloc_block(body);

        let statement = self.arena.alloc_statement_node(StatementGenericFor::new(
            location,
            false,
            self.arena.alloc_slice_copy(locals.as_slice()),
            self.arena.alloc_slice_copy(values.as_slice()),
            body,
            has_in,
            in_location,
            has_do,
            do_location,
        ));
        Ok(self.parsed_statement_from_node(statement, {
            self.cst_node(|| {
                let mut variable_annotation_colons = variable_commas.nested();
                for binding in bindings.iter() {
                    variable_annotation_colons.push_back(binding.colon_position);
                }
                CstNode::StatForIn(CstStatForIn {
                    variable_annotation_colons: self
                        .arena
                        .alloc_slice_copy(variable_annotation_colons.as_slice()),
                    variable_commas: self.arena.alloc_slice_copy(variable_commas.as_slice()),
                    value_commas: self.arena.alloc_slice_copy(value_commas.as_slice()),
                })
            })
        }))
    }

    pub(in crate::parser) fn parse_if(&mut self, location: Location) -> Result<Statement<'ast>> {
        self.advance();
        let condition = self.parse_expression()?;
        let then_location = self.current_location();
        let has_then = self.expect_and_consume_keyword(R::Then, "if statement");

        let mut then_body =
            self.parse_block_until(&["elseif", "else", "end"], "then", then_location)?;
        let mut else_body: Option<Statement<'ast>> = None;
        let mut else_location = None;
        let end;

        if self.current.kind() == TokenKind::Reserved(R::Elseif) {
            then_body.has_end = true;
            let old_recursion_counter = self.enter_recursion("elseif")?;
            let elseif_location = self.current_location();
            let elseif = self.parse_if(elseif_location)?;
            self.contexts.recursion_counter = old_recursion_counter;
            end = elseif.location().end;
            else_location = Some(elseif_location);
            else_body = Some(elseif);
        } else {
            let mut match_location = then_location;
            let mut match_opener = "then";
            if self.current.kind() == TokenKind::Reserved(R::Else) {
                then_body.has_end = true;
                let current_else_location = self.current_location();
                else_location = Some(current_else_location);
                match_location = current_else_location;
                match_opener = "else";
                self.advance();
                let mut body = self.parse_block_until(&["end"], "else", current_else_location)?;
                body.base.location.begin = current_else_location.end;
                end = self.current_token_location().end;
                let has_end = self.expect_match_end_with_lookahead(match_opener, match_location);
                body.has_end = has_end;
                let body = self.arena.alloc_block(body);
                else_body = Some(body.as_statement());
            } else {
                end = self.current_token_location().end;
                let has_end = self.expect_match_end_with_lookahead(match_opener, match_location);
                then_body.has_end = has_end;
            }
        }

        let then_body = self.arena.alloc_block(then_body);
        let location = Location::new(location.begin, end);

        Ok(self.arena.alloc_statement_node(StatementIf::new(
            location,
            false,
            condition,
            then_body,
            else_body,
            has_then.then_some(then_location),
            else_location,
        )))
    }

    pub(in crate::parser) fn parse_repeat(
        &mut self,
        location: Location,
    ) -> Result<ParsedStatement<'ast>> {
        self.advance();
        let locals_begin = self.locals.stack.len();
        let body = match self.parse_loop_block_until_no_scope(&["until"], "repeat", location) {
            Ok(body) => body,
            Err(error) => {
                self.restore_locals(locals_begin);
                return Err(error);
            }
        };
        let has_until =
            self.expect_match_keyword_with_lookahead(R::Until, "until", "repeat", location);
        let until = if has_until {
            self.previous_token_location().begin
        } else {
            Position::missing()
        };
        let condition = match self.parse_expression() {
            Ok(condition) => condition,
            Err(error) => {
                self.restore_locals(locals_begin);
                return Err(error);
            }
        };
        self.restore_locals(locals_begin);
        let location = Location::new(location.begin, condition.location.end);
        let mut body = body;
        body.has_end = has_until;
        let body = self.arena.alloc_block(body);

        Ok(self.parsed_statement_from_node(
            self.arena
                .alloc_statement_node(StatementRepeat::new(location, false, body, condition)),
            self.cst_node(|| CstNode::StatRepeat(CstStatRepeat { until })),
        ))
    }

    pub(in crate::parser) fn parse_while(
        &mut self,
        location: Location,
    ) -> Result<ParsedStatement<'ast>> {
        self.advance();
        let condition = self.parse_expression()?;
        let do_location = self.current_location();
        let has_do = self.expect_and_consume_keyword(R::Do, "while loop");
        let mut body = self.parse_loop_block_until(&["end"], "do", do_location)?;
        let end = self.current_token_location();
        body.has_end = self.expect_match_end_with_lookahead("do", do_location);
        let location = Location::new(location.begin, end.end);
        let body = self.arena.alloc_block(body);

        Ok(self.parsed_statement_from_node(
            self.arena.alloc_statement_node(StatementWhile::new(
                location,
                false,
                condition,
                body,
                has_do,
                do_location,
            )),
            None,
        ))
    }
}