luau-syntax 0.732.0

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

mod class;
mod control_flow;
mod functions;
mod locals;

impl<'source, 'ast, 'name, 'names> Parser<'source, 'ast, 'name, 'names>
where
    'name: 'ast,
{
    pub(in crate::parser) fn parse_statement(&mut self) -> Result<ParsedStatement<'ast>> {
        let location = self.current_location();

        match self.current {
            Token::Attribute(_) | Token::AttributeOpen => self.parse_attribute_statement(location),
            Token::Reserved(R::Break) => {
                self.advance();
                if !self.in_loop() {
                    let message_index =
                        self.report_parse_error(self.loop_control_error(location, "break"));
                    let statement = self.arena.alloc_statement_node(StatementUnit::new(
                        StatementTag::Break,
                        location,
                        false,
                    ));
                    let error = self.arena.alloc_statement_node(StatementError::new(
                        location,
                        false,
                        self.empty_expression_slice(),
                        self.arena.alloc_slice_copy(&[statement]),
                        message_index,
                    ));
                    return Ok(self.parsed_statement_from_node(error, None));
                }
                let statement = self.arena.alloc_statement_node(StatementUnit::new(
                    StatementTag::Break,
                    location,
                    false,
                ));
                Ok(self.parsed_statement_from_node(statement, None))
            }
            Token::Reserved(R::Do) => self.parse_do(location),
            Token::Reserved(R::For) => self.parse_for(location),
            Token::Reserved(R::Function) => {
                self.parse_function_declaration(location, ParsedAttributes::default())
            }
            Token::Reserved(R::If) => self
                .parse_if(location)
                .map(|statement| self.parsed_statement_from_node(statement, None)),
            Token::Reserved(R::Local) => self.parse_local(location, ParsedAttributes::default()),
            Token::Reserved(R::Repeat) => self.parse_repeat(location),
            Token::Reserved(R::Return) => {
                self.advance();
                let (expressions, comma_positions) = self.parse_return_values()?;
                let end = expressions
                    .last()
                    .map(|expression| expression.location.end)
                    .unwrap_or(location.end);
                let statement = self.arena.alloc_statement_node(StatementReturn::new(
                    Location::new(location.begin, end),
                    false,
                    self.arena.alloc_slice_copy(&expressions),
                ));
                let statement = self.parsed_statement_from_node(
                    statement,
                    if self.cst.enabled() {
                        Some(CstNode::StatReturn(CstStatReturn { comma_positions }))
                    } else {
                        None
                    },
                );

                if flags::LuauExportValueSyntax.get() && self.contexts.functions.is_empty() {
                    if !self.declared_export_bindings.is_empty() {
                        self.report_parse_error(self.error_at(
                            statement.statement.location(),
                            "Exporting values is not compatible with top-level return (export/return conflict)",
                        ));
                    }

                    self.has_module_return = true;
                }

                Ok(statement)
            }
            Token::Reserved(R::While) => self.parse_while(location),
            Token::Ident(_) => {
                let expression = self.parse_primary_expression_statement()?;
                self.parse_expression_statement_tail(expression)
            }
            Token::LeftParen => {
                let expression = self.parse_primary_expression_statement()?;
                self.parse_expression_statement_tail(expression)
            }
            _ => {
                let expression = self.parse_primary_expression_statement()?;
                let parsed = self.parse_expression_statement_tail(expression)?;
                if self.current_location() == location {
                    self.advance();
                }
                Ok(parsed)
            }
        }
    }

    pub(in crate::parser) fn parse_attribute_statement(
        &mut self,
        location: Location,
    ) -> Result<ParsedStatement<'ast>> {
        let attributes = match self.parse_attributes() {
            Ok(attributes) => attributes,
            Err(error) => {
                let error_location = error.location;
                return Ok(self.report_statement_error(error_location, error));
            }
        };

        match self.current {
            Token::Reserved(R::Function) => self.parse_function_declaration(location, attributes),
            Token::Reserved(R::Local) => self.parse_local(location, attributes),
            Token::Ident(name) if name == "const" => self.parse_const(location, attributes),
            Token::Ident(name) if name == "export" && flags::LuauExportValueSyntax.get() => {
                let keyword = self.current_token_location();
                self.advance();
                self.parse_export_value_after_keyword(location, keyword, attributes)
            }
            Token::Ident(name) if name == "declare" && self.options.allow_declaration_syntax() => {
                let expression = self.parse_primary_expression_statement()?;
                self.parse_declaration_after_keyword(expression.location, attributes.attributes)
                    .map(|statement| self.parsed_statement_from_node(statement, None))
            }
            _ => {
                let error_location = self.current_location();
                let error = self.attribute_statement_error();
                Ok(self.report_statement_error(error_location, error))
            }
        }
    }

    pub(in crate::parser) fn parse_expression_statement_tail(
        &mut self,
        expression: Expression<'ast>,
    ) -> Result<ParsedStatement<'ast>> {
        if expression.is_call_statement() {
            let statement = self.arena.alloc_statement_node(StatementExpression::new(
                expression.location,
                false,
                expression,
            ));
            return Ok(self.parsed_statement_from_node(statement, None));
        }

        if matches!(self.current, Token::Comma | Token::Equal) {
            return self.parse_assignment(expression);
        }

        if let Some(op) = self.current.compound_assignment_op() {
            let var = self.check_assignable_expression(expression);
            let op_position = self.current_position();

            self.advance();
            let value = self.parse_expression()?;
            let location = Location::new(expression.location.begin, value.location.end);
            let cst = if self.cst.enabled() {
                Some(CstNode::StatCompoundAssign(CstStatCompoundAssign {
                    op: op_position,
                }))
            } else {
                None
            };
            let statement = self
                .arena
                .alloc_statement_node(StatementCompoundAssign::new(
                    location, false, var, op, value,
                ));
            return Ok(self.parsed_statement_from_node(statement, cst));
        }

        if let Some(identifier) = self.statement_identifier(expression) {
            return self.parse_contextual_statement(identifier, expression);
        }

        let (location, expression, message_index) = if let ExpressionKind::Error {
            expressions,
            message_index,
        } = expression.kind()
        {
            let expression = self.arena.alloc_expression_error_direct(
                expression.location,
                expressions,
                message_index,
            );
            (expression.location, expression, message_index)
        } else {
            let expression_location = expression.location;
            let message_index = self.report_parse_error(ParseError::new(
                expression_location,
                "Incomplete statement: expected assignment or a function call",
            ));
            (expression_location, expression, message_index)
        };
        let expressions = self.arena.alloc_slice_copy(&[expression]);
        let statements = self.empty_statement_slice();
        let statement = self.arena.alloc_statement_node(StatementError::new(
            location,
            false,
            expressions,
            statements,
            message_index,
        ));
        Ok(self.parsed_statement_from_node(statement, None))
    }

    pub(in crate::parser) fn statement_identifier(
        &self,
        expression: Expression<'ast>,
    ) -> Option<AstName<'ast>> {
        match expression.kind() {
            ExpressionKind::Global(name) => Some(name),
            ExpressionKind::Local { local, .. } => Some(local.name),
            _ => None,
        }
    }

    pub(in crate::parser) fn parse_contextual_statement(
        &mut self,
        identifier: AstName<'ast>,
        expression: Expression<'ast>,
    ) -> Result<ParsedStatement<'ast>> {
        if identifier == "type" {
            return self.parse_type_alias_after_keyword(
                expression.location,
                false,
                expression.location.begin,
            );
        }

        if identifier == "export" && matches!(self.current, Token::Ident(name) if name == "type") {
            let type_keyword = self.current_position();
            self.advance();
            return self.parse_type_alias_after_keyword(expression.location, true, type_keyword);
        }

        if identifier == "export"
            && flags::LuauExportValueSyntax.get()
            && (matches!(self.current, Token::Reserved(R::Local | R::Function))
                || matches!(self.current, Token::Ident(name) if name == "const")
                || (flags::DebugLuauUserDefinedClasses.get()
                    && matches!(self.current, Token::Ident(name) if name == "class")))
        {
            return self.parse_export_value_after_keyword(
                expression.location,
                expression.location,
                ParsedAttributes::default(),
            );
        }

        if identifier == "continue" {
            return Ok(self.parse_continue_statement(expression.location));
        }

        if identifier == "const" {
            return self.parse_local_after_keyword(
                expression.location,
                ParsedAttributes::default(),
                true,
                false,
                expression.location,
            );
        }

        if flags::DebugLuauUserDefinedClasses.get() && identifier == "class" {
            return self
                .parse_class_statement_after_keyword(expression.location, false)
                .map(|parsed| self.parsed_statement_from_node(parsed.statement, None));
        }

        if self.options.allow_declaration_syntax() && identifier == "declare" {
            return self
                .parse_declaration_after_keyword(expression.location, Vec::new())
                .map(|statement| self.parsed_statement_from_node(statement, None));
        }

        let expression_location = expression.location;
        let message_index = self.report_parse_error(ParseError::new(
            expression_location,
            "Incomplete statement: expected assignment or a function call",
        ));
        Ok(self.parsed_statement_from_node(
            self.arena.alloc_statement_node(StatementError::new(
                expression_location,
                false,
                self.arena.alloc_slice_copy(&[expression]),
                self.empty_statement_slice(),
                message_index,
            )),
            None,
        ))
    }

    pub(in crate::parser) fn parse_continue_statement(
        &mut self,
        location: Location,
    ) -> ParsedStatement<'ast> {
        if !self.in_loop() {
            let message_index =
                self.report_parse_error(self.loop_control_error(location, "continue"));
            let statement = self.arena.alloc_statement_node(StatementUnit::new(
                StatementTag::Continue,
                location,
                false,
            ));
            let error = self.arena.alloc_statement_node(StatementError::new(
                location,
                false,
                self.empty_expression_slice(),
                self.arena.alloc_slice_copy(&[statement]),
                message_index,
            ));
            return self.parsed_statement_from_node(error, None);
        }

        let statement = self.arena.alloc_statement_node(StatementUnit::new(
            StatementTag::Continue,
            location,
            false,
        ));
        self.parsed_statement_from_node(statement, None)
    }

    pub(in crate::parser) fn report_statement_error(
        &mut self,
        location: Location,
        error: ParseError,
    ) -> ParsedStatement<'ast> {
        let message_index = self.report_parse_error(error);
        let statement = self.error_statement(location, message_index);
        self.parsed_statement_from_node(statement, None)
    }

    pub(in crate::parser) fn parse_assignment(
        &mut self,
        first: Expression<'ast>,
    ) -> Result<ParsedStatement<'ast>> {
        let mut vars = self.temp_expressions();
        vars.push_back(self.check_assignable_expression(first));
        let mut variable_commas = self.temp_positions();

        while self.current.kind() == TokenKind::Comma {
            if self.cst.enabled() {
                variable_commas.push_back(self.current_position());
            }
            self.advance();

            let target = self.parse_primary_expression_statement()?;
            vars.push_back(self.check_assignable_expression(target));
        }

        let equals = if self.expect_and_consume(Token::Equal, "assignment") {
            self.previous_token_location().begin
        } else {
            Position::missing()
        };
        let mut values = self.temp_expressions_aux();
        let mut value_commas = self.temp_positions();
        self.parse_expression_list_into(&mut values, &mut value_commas)?;
        let end = values
            .as_slice()
            .last()
            .map(|value| value.location.end)
            .unwrap_or(first.location.end);
        let location = Location::new(first.location.begin, end);
        let statement = self.arena.alloc_statement_node(StatementAssign::new(
            location,
            false,
            self.arena.alloc_slice_copy(vars.as_slice()),
            self.arena.alloc_slice_copy(values.as_slice()),
        ));
        Ok(self.parsed_statement_from_node(
            statement,
            if self.cst.enabled() {
                Some(CstNode::StatAssign(CstStatAssign {
                    variable_commas: self.arena.alloc_slice_copy(variable_commas.as_slice()),
                    equals,
                    value_commas: self.arena.alloc_slice_copy(value_commas.as_slice()),
                }))
            } else {
                None
            },
        ))
    }
}