gluon_parser 0.6.1

The parser for the gluon programming language
Documentation
use std::str::FromStr;

use itertools::Itertools;

use base::ast::{Alternative, Array, AstType, Comment, Expr, ExprField, Lambda, Literal, Pattern,
    PatternField, SpannedExpr, SpannedIdent, TypeBinding, TypedIdent, ValueBinding};
use base::kind::{ArcKind, Kind};
use base::pos::{self, BytePos, Spanned};
use base::types::{AliasData, ArcType, BuiltinType, Field, Generic, Type, TypeCache};

use ::new_ident;
use token::Token;

use {Error, ErrorEnv, FieldExpr, FieldPattern, MutIdentEnv};

grammar<'input, 'env, Id>(src: &'input str, type_cache: &TypeCache<Id, ArcType<Id>>, env: MutIdentEnv<'env, Id>, errors: ErrorEnv<'env, 'input>)
    where Id: Clone;

extern {
    type Location = BytePos;
    type Error = Spanned<Error, BytePos>;

    enum Token<'input> {
        "shebang line" => Token::ShebangLine(<&'input str>),
        "identifier" => Token::Identifier(<&'input str>),
        "operator" => Token::Operator(<&'input str>),
        "string literal" => Token::StringLiteral(<String>),
        "char literal" => Token::CharLiteral(<char>),
        "int literal" => Token::IntLiteral(<i64>),
        "byte literal" => Token::ByteLiteral(<u8>),
        "float literal" => Token::FloatLiteral(<f64>),
        "documentation comment" => Token::DocComment(<Comment>),

        "and" => Token::And,
        "else" => Token::Else,
        "if" => Token::If,
        "in" => Token::In,
        "let" => Token::Let,
        "match" => Token::Match,
        "then" => Token::Then,
        "type" => Token::Type,
        "with" => Token::With,

        ":" => Token::Colon,
        "," => Token::Comma,
        "." => Token::Dot,
        "=" => Token::Equals,
        "\\" => Token::Lambda,
        "|" => Token::Pipe,
        "->" => Token::RArrow,

        "{" => Token::LBrace,
        "[" => Token::LBracket,
        "(" => Token::LParen,

        "}" => Token::RBrace,
        "]" => Token::RBracket,
        ")" => Token::RParen,

        "block open" => Token::OpenBlock,
        "block close" => Token::CloseBlock,
        "block separator" => Token::Semi,
    }
}

// Utils

Comma<Rule>: Vec<Rule> =
    <rules: (<Rule> ",")*> <last: Rule?> => {
        let mut rules = rules;
        rules.extend(last);
        rules
    };

Sp<Rule>: Spanned<Rule, BytePos> =
    <l: @L> <rule: Rule> <r: @R> =>
        pos::spanned2(l, r, rule);

IdentStr: &'input str = {
    "identifier" => <>,
    "(" <"operator"> ")" => <>,
};

SkipExtraTokens: () = {
    => (),
    <!> => errors.push(<>.error),
};

Ident: Id =
    IdentStr => env.from_str(<>);

SpannedIdent: SpannedIdent<Id> =
    Sp<Ident> => pos::spanned(<>.span, new_ident(type_cache, <>.value));

Operator: TypedIdent<Id> =
    "operator" => new_ident(type_cache, env.from_str(<>));

DocComment: Comment =
    "documentation comment"+ => {
        let typ = <>.last().unwrap().typ;
        Comment {
            typ: typ,
            content: <>.into_iter().map(|comment| comment.content).join("\n")
        }
    };

// Kinds

AtomicKind: ArcKind = {
    <l: @L> <id: "identifier"> <r: @R> =>? {
        use lalrpop_util::ParseError;

        match id {
            "_" => Ok(Kind::hole()),
            "Type" => Ok(Kind::typ()),
            "Row" => Ok(Kind::row()),
            id => Err(ParseError::User {
                error: pos::spanned2(
                    l.into(),
                    r.into(),
                    Error::UnexpectedToken(
                        "identifier".to_string(),
                        ["_", "Row", "Type"].iter().map(|s| s.to_string()).collect())),
            }),
        }
    },

    "(" <kind: Kind> ")" => kind,
};

Kind: ArcKind = {
    AtomicKind,

    <lhs: AtomicKind> "->" <rhs: Kind> =>
        Kind::function(lhs, rhs),
};

// Types

TypeParam: Generic<Id> = {
    <id : Ident> =>
        Generic::new(id, Kind::hole()),

    "(" <id: Ident> ":" <kind: Kind> ")" =>
        Generic::new(id, kind),
};

RecordField: Field<Id, AstType<Id>> =
    <comment: DocComment?> <id: Ident> ":" <typ: Sp<Type_>> =>
        Field::new(
            id,
            AstType::with_comment(comment, typ),
        );

VariantField: (Id, Vec<AstType<Id>>) =
    "|" <Ident> <AtomicType*> => (<>);

TypeBinding: TypeBinding<Id> = {
    <id: Sp<Ident>> <params: TypeParam*> "=" <row: Sp<VariantField+>> => {
        let typ_args = params.iter().cloned().map(Type::generic).collect();
        let typ: AstType<Id> = Type::app(Type::ident(id.value.clone()), typ_args);

        let row_span = row.span;
        let row = row.value.into_iter()
            .map(|(id, params)| Field::new(id, Type::function(params, typ.clone())))
            .collect();
        
        TypeBinding {
            comment: None,
            name: id.clone(),
            alias: pos::spanned(
                row_span,
                AliasData::new(
                    id.value.clone(),
                    params,
                    AstType::from(pos::spanned(
                        row_span,
                        Type::Variant(Type::extend_row(vec![], row, Type::empty_row()))
                    )),
                )
            ),
            finalized_alias: None,
        }
    },

    <id: Sp<Ident>> <params: TypeParam*> "=" <body: Sp<Type>> => {
        TypeBinding {
            comment: None,
            name: id.clone(),
            alias: pos::spanned(body.span, AliasData::new(id.value.clone(), params, body.value)),
            finalized_alias: None,
        }
    },
};

AtomicType_: Type<Id, AstType<Id>> = {
    "(" "->" ")" =>
        Type::Builtin(BuiltinType::Function),

    <ids: (<IdentStr> ".")*> <last: IdentStr> => {
        if ids.is_empty() {
            if last == "_" {
                Type::Hole
            } else {
                match BuiltinType::from_str(last) {
                    Ok(ty) => Type::Builtin(ty),
                    Err(_) if last.starts_with(char::is_uppercase) => {
                        Type::Ident(env.from_str(last))
                    }
                    Err(_) => {
                        Type::Generic(Generic::new(env.from_str(last), Kind::hole()))
                    }
                }
            }
        } else {
            let project_id: String = ids
                .iter()
                .cloned()
                .chain(Some(last))
                .intersperse(".")
                .collect();
            Type::Ident(env.from_str(&project_id))
        }
    },

    "(" <elems: Comma<Type>> ")" =>
        match elems.len() {
            // Parenthesized type
            1 => elems.into_iter().next().unwrap().into_inner(),
            _ => Type::tuple_(env, elems),
        },

    "{" <row: Comma<RecordField>> "}" =>
        Type::Record(Type::extend_row(
            vec![],
            row,
            Type::empty_row(),
        )),
};

AtomicType : AstType<Id> = {
    <typ: Sp<AtomicType_>> => AstType::from(typ),
};

AppType_ = {
    AtomicType_,

    <ty: AtomicType> <args: AtomicType+> =>
        Type::App(ty, args.into_iter().collect()),
};

AppType : AstType<Id> = {
    <typ: Sp<AppType_>> => AstType::from(typ),
};

Type_ = {
    AppType_,

    <lhs: AppType> <f: Sp<"->">> <rhs: Type> =>
        Type::App(
            AstType::from(pos::spanned(f.span, Type::Builtin(BuiltinType::Function))),
            collect![lhs, rhs]
        )
};

Type : AstType<Id> = {
    <typ: Sp<Type_>> => AstType::from(typ),
};

// Patterns

FieldPattern : FieldPattern<Id> = {
    <id: Sp<Ident>> "=" <body: Sp<Pattern>> =>
        FieldPattern::Value(id, Some(body)),

    <Sp<IdentStr>> => {
        let id = pos::spanned(<>.span, env.from_str(<>.value));
        if <>.value.starts_with(char::is_uppercase) {
            FieldPattern::Type(id, None)
        } else {
            FieldPattern::Value(id, None)
        }
    },
};

AtomicPattern: Pattern<Id> = {
    <id: Ident> =>
        if env.string(&id).starts_with(char::is_uppercase) {
            Pattern::Constructor(new_ident(type_cache, id), Vec::new())
        } else {
            Pattern::Ident(new_ident(type_cache, id))
        },

    "(" <elems: Comma<Sp<Pattern>>> ")" =>
        match elems.len() {
            // Parenthesized pattern
            1 => elems.into_iter().next().unwrap().value,
            _ => Pattern::Tuple { typ: Type::hole(), elems: elems },
        },

    "{" <fields: Comma<FieldPattern>> "}" => {
        let mut types = Vec::new();
        let mut values = Vec::new();

        for field in fields {
            match field {
                FieldPattern::Type(id, typ) => types.push(PatternField {
                    name: id,
                    value: typ
                }),
                FieldPattern::Value(id, field) => values.push(PatternField {
                    name: id,
                    value: field
                }),
            }
        }

        Pattern::Record {
            typ: Type::hole(),
            types: types,
            fields: values,
        }
    },
};

NoErrorPattern = {
    AtomicPattern,

    <id: Ident> <args: Sp<AtomicPattern>+> => {
        let id = new_ident(type_cache, id);

        Pattern::Constructor(id, args)
    },
};

Pattern = {
    NoErrorPattern,
    <!> => {
        errors.push(<>.error);
        Pattern::Error
    },
};

// Expressions

Literal: Literal = {
    "string literal" => Literal::String(<>),
    "char literal" => Literal::Char(<>),
    "int literal" => Literal::Int(<>),
    "byte literal" => Literal::Byte(<>),
    "float literal" => Literal::Float(<>),
};

Alternative: Alternative<Id> = {
    "|" <pat: Sp<Pattern>> "->" <expr: Sp<BlockExpr>> => {
        Alternative {
            pattern: pat,
            expr: super::shrink_hidden_spans(expr),
        }
    },
    "|" <pat: Sp<NoErrorPattern>> <end: @L> <err: !> => {
        errors.push(err.error);
        let span = pos::Span::new(pat.span.end, end);
        Alternative {
            pattern: pat,
            expr: pos::spanned(span, Expr::Error),
        }
    },
    "|" <start: @R> <end: @L> <err: !> => {
        errors.push(err.error);
        let span = pos::Span::new(start, end);
        Alternative {
            pattern: pos::spanned(span, Pattern::Error),
            expr: pos::spanned(span, Expr::Error),
        }
    },
};

FieldExpr: FieldExpr<Id> = {
    <comment: DocComment?> <id: Sp<Ident>> "=" <body: SpExpr> => {
        FieldExpr::Value(comment, id, Some(body))
    },

    <comment: DocComment?> <id_str: Sp<IdentStr>> => {
        let id = pos::spanned(id_str.span, env.from_str(id_str.value));
        if id_str.value.starts_with(char::is_uppercase) {
            FieldExpr::Type(comment, id, None)
        } else {
            FieldExpr::Value(comment, id, None)
        }
    },
};

ValueBinding: ValueBinding<Id> = {
    <comment: DocComment?> <name: Sp<AtomicPattern>> <typ: (":" <Type>)?> "=" <body: SpExpr> =>
        ValueBinding {
            comment: comment,
            name: name,
            typ: typ,
            resolved_type: type_cache.hole(),
            args: vec![],
            expr: body,
        },

    <comment: DocComment?> <name: Sp<Ident>> <args: SpannedIdent+> <typ: (":" <Type>)?> "=" <body: SpExpr> =>
        ValueBinding {
            comment,
            name: name.map(|name| new_ident(type_cache, name)).map(Pattern::Ident),
            typ: typ,
            resolved_type: type_cache.hole(),
            args,
            expr: body,
        },
};

AtomicExpr: Expr<Id> = {
    <id: Ident> =>
        Expr::Ident(new_ident(type_cache, id)),

    <lit: Literal> =>
        Expr::Literal(lit),

    // TODO: Getters
    // "(" "." <id: Ident> ")" =>
    //     Expr::Getter(id),

    <expr: SpAtomicExpr> "." <id: Ident> =>
        Expr::Projection(Box::new(expr), id, Type::hole()),

    <expr: SpAtomicExpr> "." <err: !> => {
        errors.push(err.error);
        Expr::Projection(Box::new(expr), env.from_str(""), Type::hole())
    },

    "(" <elems: Comma<SpExpr>> ")" =>
        Expr::Tuple { typ: Type::hole(), elems: elems },

    "[" <elems: Comma<SpExpr>> "]" => Expr::Array(Array {
            typ: Type::hole(),
            exprs: elems,
        }),

    "{" <fields: Comma<FieldExpr>> "}" => {
        let mut types = Vec::new();
        let mut values = Vec::new();

        for field in fields {
            match field {
                FieldExpr::Type(comment, id, typ) => types.push(ExprField {
                    comment: comment,
                    name: id,
                    value: typ
                }),
                FieldExpr::Value(comment, id, expr) => values.push(ExprField {
                    comment: comment,
                    name: id,
                    value: expr
                }),
            }
        }

        Expr::Record {
            typ: Type::hole(),
            types: types,
            exprs: values,
        }
    },
};

SpAtomicExpr: SpannedExpr<Id> = {
    <Sp<AtomicExpr>> => super::shrink_hidden_spans(<>)
};

AppExpr = {
    AtomicExpr,

    <expr: SpAtomicExpr> <args: SpAtomicExpr+> =>
        Expr::App(Box::new(expr), args),
};

InfixExpr = {
    AppExpr,

    "\\" <args: SpannedIdent+> "->" <body: SpExpr> =>
        Expr::Lambda(Lambda {
            id: new_ident(type_cache, env.from_str("")),
            args,
            body: Box::new(body),
        }),

    <lhs: Sp<AppExpr>> <op: Sp<Operator>> <rhs: Sp<InfixExpr>> =>
        Expr::Infix(Box::new(lhs), op, Box::new(super::shrink_hidden_spans(rhs))),
};

AndValueBinding: ValueBinding<Id> =
    <comment: DocComment?> "and" <binding: ValueBinding> => {
        let mut binding = binding;
        binding.comment = comment;
        binding
    };

AndTypeBinding: TypeBinding<Id> =
    <comment: DocComment?> "and" <binding: TypeBinding> => {
        let mut binding = binding;
        binding.comment = comment;
        binding
    };

Expr: Expr<Id> = {
    InfixExpr,

    "if" <pred: SpExpr> "then" <if_true: SpExpr> "else" <if_false: SpExpr> =>
        Expr::IfElse(Box::new(pred), Box::new(if_true), Box::new(if_false)),

    "match" <input: SpExpr> "with" <arms: Alternative+> =>
        Expr::Match(Box::new(input), arms),

    <comment: DocComment?> "let" <first: ValueBinding> <bindings: AndValueBinding*> SkipExtraTokens "in" <body: SpExpr> => {
        let mut first = first;
        let mut bindings = bindings;
        first.comment = comment;
        bindings.insert(0, first);

        Expr::LetBindings(bindings, Box::new(body))
    },

    <comment: DocComment?> "type" <first: TypeBinding> <bindings: AndTypeBinding*> SkipExtraTokens "in" <body: SpExpr> => {
        let mut first = first;
        let mut bindings = bindings;
        first.comment = comment;
        bindings.insert(0, first);

        Expr::TypeBindings(bindings, Box::new(body))
    },

    BlockExpr,

    ! => {
        errors.push(<>.error);
        Expr::Error
    }
};

BlockExpr: Expr<Id> = {
    "block open" <exprs: (<SpExpr> "block separator")*> <last: SpExpr> "block close" => {
        let mut exprs = exprs;
        exprs.push(last);
        Expr::Block(exprs)
    },
};

SpExpr: SpannedExpr<Id> = {
    <expr: Sp<Expr>> => super::shrink_hidden_spans(expr),
};

pub TopExpr: SpannedExpr<Id> = {
    "shebang line"? <expr: SpExpr> => expr,
};

pub LetOrExpr: Result<SpannedExpr<Id>, ValueBinding<Id>> = {
    <TopExpr> => Ok(<>),
    "block open" "let" <ValueBinding> SkipExtraTokens => Err(<>)
};