mago-syntax 1.0.0-alpha.5

A correct, fast, and memory-efficient PHP syntax implementation, including Lexer, Parser, AST, and utilities for Mago.
Documentation
use mago_database::file::HasFileId;

use crate::T;
use crate::ast::ast::*;
use crate::ast::sequence::TokenSeparatedSequence;
use crate::error::ParseError;
use crate::parser::internal::identifier::parse_identifier;
use crate::parser::internal::identifier::parse_local_identifier;
use crate::parser::internal::terminator::parse_terminator;
use crate::parser::internal::token_stream::TokenStream;
use crate::parser::internal::utils;

pub fn parse_use(stream: &mut TokenStream<'_, '_>) -> Result<Use, ParseError> {
    Ok(Use {
        r#use: utils::expect_keyword(stream, T!["use"])?,
        items: parse_use_items(stream)?,
        terminator: parse_terminator(stream)?,
    })
}

pub fn parse_use_items(stream: &mut TokenStream<'_, '_>) -> Result<UseItems, ParseError> {
    let next = utils::peek(stream)?.kind;

    Ok(match next {
        T!["const" | "function"] => match utils::maybe_peek_nth(stream, 2)?.map(|token| token.kind) {
            Some(T!["\\"]) => UseItems::TypedList(parse_typed_use_item_list(stream)?),
            _ => UseItems::TypedSequence(parse_typed_use_item_sequence(stream)?),
        },
        _ => match utils::maybe_peek_nth(stream, 1)?.map(|token| token.kind) {
            Some(T!["\\"]) => UseItems::MixedList(parse_mixed_use_item_list(stream)?),
            _ => UseItems::Sequence(parse_use_item_sequence(stream)?),
        },
    })
}

pub fn parse_use_item_sequence(stream: &mut TokenStream<'_, '_>) -> Result<UseItemSequence, ParseError> {
    let start = utils::peek(stream)?.span.start;

    let mut items = Vec::new();
    let mut commas = Vec::new();
    loop {
        items.push(parse_use_item(stream)?);

        match utils::maybe_expect(stream, T![","])? {
            Some(comma) => {
                commas.push(comma);
            }
            None => break,
        }
    }

    Ok(UseItemSequence { file_id: stream.file_id(), start, items: TokenSeparatedSequence::new(items, commas) })
}

pub fn parse_typed_use_item_sequence(stream: &mut TokenStream<'_, '_>) -> Result<TypedUseItemSequence, ParseError> {
    let r#type = parse_use_type(stream)?;
    let mut items = Vec::new();
    let mut commas = Vec::new();
    loop {
        items.push(parse_use_item(stream)?);

        match utils::maybe_expect(stream, T![","])? {
            Some(comma) => {
                commas.push(comma);
            }
            None => break,
        }
    }

    Ok(TypedUseItemSequence { r#type, items: TokenSeparatedSequence::new(items, commas) })
}

pub fn parse_typed_use_item_list(stream: &mut TokenStream<'_, '_>) -> Result<TypedUseItemList, ParseError> {
    let r#type = parse_use_type(stream)?;
    let namespace = parse_identifier(stream)?;
    let namespace_separator = utils::expect_span(stream, T!["\\"])?;
    let left_brace = utils::expect_span(stream, T!["{"])?;
    let mut items = Vec::new();
    let mut commas = Vec::new();
    loop {
        let next = utils::peek(stream)?;
        if matches!(next.kind, T!["}"]) {
            break;
        }

        items.push(parse_use_item(stream)?);

        match utils::maybe_expect(stream, T![","])? {
            Some(comma) => {
                commas.push(comma);
            }
            None => break,
        }
    }
    let right_brace = utils::expect_span(stream, T!["}"])?;

    Ok(TypedUseItemList {
        r#type,
        namespace,
        namespace_separator,
        left_brace,
        items: TokenSeparatedSequence::new(items, commas),
        right_brace,
    })
}

pub fn parse_mixed_use_item_list(stream: &mut TokenStream<'_, '_>) -> Result<MixedUseItemList, ParseError> {
    let namespace = parse_identifier(stream)?;
    let namespace_separator = utils::expect_span(stream, T!["\\"])?;
    let left_brace = utils::expect_span(stream, T!["{"])?;
    let mut items = Vec::new();
    let mut commas = Vec::new();
    loop {
        let next = utils::peek(stream)?;
        if matches!(next.kind, T!["}"]) {
            break;
        }

        items.push(parse_maybe_typed_use_item(stream)?);

        match utils::maybe_expect(stream, T![","])? {
            Some(comma) => {
                commas.push(comma);
            }
            None => break,
        }
    }
    let right_brace = utils::expect_span(stream, T!["}"])?;

    Ok(MixedUseItemList {
        namespace,
        namespace_separator,
        left_brace,
        items: TokenSeparatedSequence::new(items, commas),
        right_brace,
    })
}

pub fn parse_maybe_typed_use_item(stream: &mut TokenStream<'_, '_>) -> Result<MaybeTypedUseItem, ParseError> {
    Ok(MaybeTypedUseItem { r#type: parse_optional_use_type(stream)?, item: parse_use_item(stream)? })
}

pub fn parse_optional_use_type(stream: &mut TokenStream<'_, '_>) -> Result<Option<UseType>, ParseError> {
    Ok(match utils::maybe_peek(stream)?.map(|t| t.kind) {
        Some(T!["function"]) => Some(UseType::Function(utils::expect_any_keyword(stream)?)),
        Some(T!["const"]) => Some(UseType::Const(utils::expect_any_keyword(stream)?)),
        _ => None,
    })
}

pub fn parse_use_type(stream: &mut TokenStream<'_, '_>) -> Result<UseType, ParseError> {
    let next = utils::peek(stream)?;

    Ok(match next.kind {
        T!["function"] => UseType::Function(utils::expect_any_keyword(stream)?),
        T!["const"] => UseType::Const(utils::expect_any_keyword(stream)?),
        _ => return Err(utils::unexpected(stream, Some(next), T!["function", "const"])),
    })
}

pub fn parse_use_item(stream: &mut TokenStream<'_, '_>) -> Result<UseItem, ParseError> {
    Ok(UseItem { name: parse_identifier(stream)?, alias: parse_optional_use_item_alias(stream)? })
}

pub fn parse_optional_use_item_alias(stream: &mut TokenStream<'_, '_>) -> Result<Option<UseItemAlias>, ParseError> {
    Ok(match utils::maybe_peek(stream)?.map(|t| t.kind) {
        Some(T!["as"]) => Some(parse_use_item_alias(stream)?),
        _ => None,
    })
}

pub fn parse_use_item_alias(stream: &mut TokenStream<'_, '_>) -> Result<UseItemAlias, ParseError> {
    let r#as = utils::expect_keyword(stream, T!["as"])?;
    let identifier = parse_local_identifier(stream)?;

    Ok(UseItemAlias { r#as, identifier })
}