aiken-lang 1.0.13-alpha

The Aiken compiler
Documentation
use chumsky::prelude::*;

use crate::{
    ast,
    expr::UntypedExpr,
    parser::{
        error::{self, ParseError},
        token::Token,
    },
};

pub fn parser(
    r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
    choice((
        select! {Token::Name { name } => name}
            .map_with_span(|module, span: ast::Span| (module, span))
            .then_ignore(just(Token::Dot))
            .or_not()
            .then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span)))
            .then(
                choice((
                    select! {Token::Name {name} => name}
                        .then_ignore(just(Token::Colon))
                        .then(choice((
                            r.clone(),
                            select! {Token::DiscardName {name} => name }.validate(
                                |_name, span, emit| {
                                    emit(ParseError::expected_input_found(
                                        span,
                                        None,
                                        Some(error::Pattern::Discard),
                                    ));

                                    UntypedExpr::Var {
                                        location: span,
                                        name: ast::CAPTURE_VARIABLE.to_string(),
                                    }
                                },
                            ),
                        )))
                        .map_with_span(|(label, value), span| ast::CallArg {
                            location: span,
                            value,
                            label: Some(label),
                        }),
                    choice((
                        select! {Token::Name {name} => name}.map_with_span(|name, span| {
                            (
                                UntypedExpr::Var {
                                    name: name.clone(),
                                    location: span,
                                },
                                name,
                            )
                        }),
                        select! {Token::DiscardName {name} => name }.validate(
                            |name, span, emit| {
                                emit(ParseError::expected_input_found(
                                    span,
                                    None,
                                    Some(error::Pattern::Discard),
                                ));

                                (
                                    UntypedExpr::Var {
                                        location: span,
                                        name: ast::CAPTURE_VARIABLE.to_string(),
                                    },
                                    name,
                                )
                            },
                        ),
                    ))
                    .map(|(value, name)| ast::CallArg {
                        location: value.location(),
                        value,
                        label: Some(name),
                    }),
                ))
                .separated_by(just(Token::Comma))
                .allow_trailing()
                .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
            ),
        select! {Token::Name { name } => name}
            .map_with_span(|module, span| (module, span))
            .then_ignore(just(Token::Dot))
            .or_not()
            .then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span)))
            .then(
                select! {Token::Name {name} => name}
                    .ignored()
                    .then_ignore(just(Token::Colon))
                    .validate(|_label, span, emit| {
                        emit(ParseError::expected_input_found(
                            span,
                            None,
                            Some(error::Pattern::Label),
                        ))
                    })
                    .or_not()
                    .then(choice((
                        r.clone(),
                        select! {Token::DiscardName {name} => name }.validate(
                            |_name, span, emit| {
                                emit(ParseError::expected_input_found(
                                    span,
                                    None,
                                    Some(error::Pattern::Discard),
                                ));

                                UntypedExpr::Var {
                                    location: span,
                                    name: ast::CAPTURE_VARIABLE.to_string(),
                                }
                            },
                        ),
                    )))
                    .map(|(_label, value)| ast::CallArg {
                        location: value.location(),
                        value,
                        label: None,
                    })
                    .separated_by(just(Token::Comma))
                    .allow_trailing()
                    .delimited_by(just(Token::LeftParen), just(Token::RightParen)),
            ),
    ))
    .map_with_span(|((module, (name, n_span)), arguments), span| {
        let fun = if let Some((module, m_span)) = module {
            UntypedExpr::FieldAccess {
                location: m_span.union(n_span),
                label: name,
                container: Box::new(UntypedExpr::Var {
                    location: m_span,
                    name: module,
                }),
            }
        } else {
            UntypedExpr::Var {
                location: n_span,
                name,
            }
        };

        UntypedExpr::Call {
            arguments,
            fun: Box::new(fun),
            location: span,
        }
    })
}

#[cfg(test)]
mod tests {
    use crate::assert_expr;

    #[test]
    fn record_create_labeled() {
        assert_expr!(r#"User { name: "Aiken", age, thing: 2 }"#);
    }

    #[test]
    fn record_create_labeled_with_field_access() {
        assert_expr!(r#"some_module.User { name: "Aiken", age, thing: 2 }"#);
    }

    #[test]
    fn record_create_unlabeled() {
        assert_expr!(r#"some_module.Thing(1, a)"#);
    }
}