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_block_until(
        &mut self,
        terminators: &'static [&'static str],
        opener: &'static str,
        location: Location,
    ) -> Result<BlockNode<'ast>> {
        self.parse_block_until_from(terminators, opener, location, location.end)
    }

    pub(in crate::parser) fn parse_block_until_from(
        &mut self,
        terminators: &'static [&'static str],
        opener: &'static str,
        location: Location,
        block_begin: Position,
    ) -> Result<BlockNode<'ast>> {
        self.contexts.blocks.push(BlockContext {
            opener,
            line: location.begin.line as usize + 1,
            column: location.begin.column as usize + 1,
        });
        self.push_local_scope();
        let mut statements = self.temp_statements();
        let result = self.parse_block_until_inner_into(terminators, &mut statements);
        if result.is_ok() {
            self.update_end_mismatch_suspect();
        }
        self.pop_local_scope();
        self.contexts.blocks.pop();
        result.map(|()| {
            let has_end = terminators
                .iter()
                .any(|terminator| self.current.is_keyword(terminator));
            BlockNode::new(
                self.arena.alloc_slice_copy(statements.as_slice()),
                has_end,
                Location::new(block_begin, self.current_token_location().begin),
            )
        })
    }

    pub(in crate::parser) fn parse_loop_block_until(
        &mut self,
        terminators: &'static [&'static str],
        opener: &'static str,
        location: Location,
    ) -> Result<BlockNode<'ast>> {
        self.current_function_mut().loop_depth += 1;
        let result = self.parse_block_until(terminators, opener, location);
        self.current_function_mut().loop_depth -= 1;
        result
    }

    pub(in crate::parser) fn parse_block_until_no_scope(
        &mut self,
        terminators: &'static [&'static str],
        opener: &'static str,
        location: Location,
    ) -> Result<BlockNode<'ast>> {
        self.contexts.blocks.push(BlockContext {
            opener,
            line: location.begin.line as usize + 1,
            column: location.begin.column as usize + 1,
        });
        let mut statements = self.temp_statements();
        let result = self.parse_block_until_inner_into(terminators, &mut statements);
        if result.is_ok() {
            self.update_end_mismatch_suspect();
        }
        self.contexts.blocks.pop();
        result.map(|()| {
            let has_end = terminators
                .iter()
                .any(|terminator| self.current.is_keyword(terminator));
            BlockNode::new(
                self.arena.alloc_slice_copy(statements.as_slice()),
                has_end,
                Location::new(location.end, self.current_token_location().begin),
            )
        })
    }

    pub(in crate::parser) fn parse_loop_block_until_no_scope(
        &mut self,
        terminators: &'static [&'static str],
        opener: &'static str,
        location: Location,
    ) -> Result<BlockNode<'ast>> {
        self.current_function_mut().loop_depth += 1;
        let result = self.parse_block_until_no_scope(terminators, opener, location);
        self.current_function_mut().loop_depth -= 1;
        result
    }

    pub(in crate::parser) fn parse_block_until_inner_into(
        &mut self,
        terminators: &'static [&'static str],
        statements: &mut TempVector<Statement<'ast>>,
    ) -> Result<()> {
        while !self.diagnostics.error_limit_reached {
            match self.current {
                Token::Eof => return Ok(()),
                token
                    if terminators
                        .iter()
                        .any(|terminator| token.is_keyword(terminator)) =>
                {
                    return Ok(());
                }
                _ => {
                    let old_recursion_counter = self.enter_recursion("block")?;
                    let parsed = match self.parse_statement() {
                        Ok(parsed) => parsed,
                        Err(error) => {
                            self.contexts.recursion_counter = old_recursion_counter;
                            return Err(error);
                        }
                    };
                    self.contexts.recursion_counter = old_recursion_counter;
                    let statement = parsed.statement;
                    let terminal = statement.is_terminal();
                    if self.current.kind() == TokenKind::Semicolon {
                        statement.set_semicolon(self.current_token_location());
                        self.advance();
                    }
                    statements.push_back(statement);
                    if terminal {
                        return Ok(());
                    }
                }
            }
        }

        Ok(())
    }

    pub(in crate::parser) fn parse_return_values(
        &mut self,
    ) -> Result<(Vec<Expression<'ast>>, Vec<Position>)> {
        if matches!(
            self.current,
            Token::Reserved(R::Elseif | R::Else | R::End | R::Until)
                | Token::Semicolon
                | Token::Eof
        ) {
            return Ok((Vec::new(), Vec::new()));
        }

        self.parse_expression_list_with_commas()
    }

    pub(in crate::parser) fn parse_expression_list_into(
        &mut self,
        expressions: &mut TempVector<Expression<'ast>>,
        comma_positions: &mut TempVector<Position>,
    ) -> Result<()> {
        expressions.push_back(self.parse_expression()?);

        while self.current.kind() == TokenKind::Comma {
            if self.cst.enabled() {
                comma_positions.push_back(self.current_position());
            }
            self.advance();
            if self.current.kind() == TokenKind::RightParen {
                self.report_parse_error(
                    self.located("Expected expression after ',' but got ')' instead"),
                );
                break;
            }
            expressions.push_back(self.parse_expression()?);
        }

        Ok(())
    }

    pub(in crate::parser) fn parse_expression_list_with_commas(
        &mut self,
    ) -> Result<(Vec<Expression<'ast>>, Vec<Position>)> {
        let mut expressions = self.temp_expressions();
        let mut comma_positions = self.temp_positions();
        self.parse_expression_list_into(&mut expressions, &mut comma_positions)?;
        Ok((
            expressions.as_slice().to_vec(),
            comma_positions.as_slice().to_vec(),
        ))
    }

    pub(in crate::parser) fn check_assignable_expression(
        &mut self,
        expression: Expression<'ast>,
    ) -> Expression<'ast> {
        match expression.kind() {
            ExpressionKind::Local { local, .. } => {
                if local.is_const {
                    self.error_lvalue_expression(expression)
                } else {
                    expression
                }
            }
            ExpressionKind::Global(name) => {
                if self.locals.classes_within_module.contains(&name) {
                    self.error_lvalue_expression(expression)
                } else {
                    expression
                }
            }
            ExpressionKind::IndexExpr { .. } | ExpressionKind::IndexName { .. } => expression,
            _ => self.error_lvalue_expression(expression),
        }
    }

    pub(in crate::parser) fn error_lvalue_expression(
        &mut self,
        expression: Expression<'ast>,
    ) -> Expression<'ast> {
        let message = match expression.kind() {
            ExpressionKind::Local { local, .. }
                if local.is_const && flags::LuauExportValueSyntax.get() =>
            {
                self.name_message(
                    b"Variable '",
                    local.name,
                    b"' is constant and may not be reassigned",
                )
            }
            ExpressionKind::Global(name)
                if flags::DebugLuauUserDefinedClasses.get()
                    && self.locals.classes_within_module.contains(&name) =>
            {
                let class = self
                    .locals
                    .classes_within_module
                    .get(&name)
                    .copied()
                    .flatten()
                    .expect("class map value must be present");
                self.name_message(
                    b"'",
                    class.name.name,
                    format!(
                        "' refers to a class and cannot be used as a variable name (defined on line {})",
                        class.location().begin.line + 1
                    )
                    .as_bytes(),
                )
            }
            _ => ParseMessage::from("Assigned expression must be a variable or a field"),
        };
        let message_index = self.report_parse_error(ParseError::new(expression.location, message));
        self.alloc_expression(ExpressionInit::new(
            expression.location,
            ExpressionKind::Error {
                expressions: self.arena.alloc_slice_copy(&[expression]),
                message_index,
            },
        ))
    }
}