kodept-parse 0.3.0

Simple compiler with dependent types support in mind
Documentation
use crate::common::{EagerTokensProducer, TokenProducer};
use crate::lexer::Token;
use crate::lexer::{BitOperator, ComparisonOperator, LogicOperator, MathOperator};
use crate::lexer::{Identifier, Ignore, Keyword, Literal, Operator, Symbol};
use crate::token_match::TokenMatch;
use cfg_if::cfg_if;
use derive_more::Constructor;
use kodept_core::code_point::CodePoint;
use kodept_core::structure::span::Span;
use peg::error::ParseError;
use peg::str::LineCol;

peg::parser! {grammar grammar() for str {
    rule newline() = "\n" / "\r\n" / "\r"

    rule comment() -> Ignore<'input> =
        i:$("//" (!newline() [_])* &newline()?) { Ignore::Comment(i) }
    rule multiline_comment() -> Ignore<'input> =
        i:$( "/*" (!"*/" [_])* "*/") { Ignore::MultilineComment(i) }
    rule whitespace() -> Ignore<'input> =
        ("\t" / " ") { Ignore::Whitespace }
    rule ignore() -> Ignore<'input> = quiet!{i:(
        comment()                                  /
        multiline_comment()                        /
        whitespace()+       { Ignore::Whitespace } /
        newline()           { Ignore::Newline }
    ) { i }}

    rule keyword() -> Keyword =
        "fun"       { Keyword::Fun }       /
        "val"       { Keyword::Val }       /
        "var"       { Keyword::Var }       /
        "match"     { Keyword::Match }     /
        "while"     { Keyword::While }     /
        "module"    { Keyword::Module }    /
        "extend"    { Keyword::Extend }    /
        "return"    { Keyword::Return }    /
        "\\"        { Keyword::Lambda }    /
        "if"        { Keyword::If }        /
        "elif"      { Keyword::Elif }      /
        "else"      { Keyword::Else }      /
        "abstract"  { Keyword::Abstract }  /
        "trait"     { Keyword::Trait }     /
        "struct"    { Keyword::Struct }    /
        "class"     { Keyword::Class }     /
        "enum"      { Keyword::Enum }      /
        "foreign"   { Keyword::Foreign }   /
        "type"      { Keyword::TypeAlias } /
        "with"      { Keyword::With }

    rule symbol() -> Symbol =
        ","  { Symbol::Comma }       /
        ";"  { Symbol::Semicolon }   /
        "{"  { Symbol::LBrace }      /
        "}"  { Symbol::RBrace }      /
        "["  { Symbol::LBracket }    /
        "]"  { Symbol::RBracket }    /
        "("  { Symbol::LParen }      /
        ")"  { Symbol::RParen }      /
        "_"  { Symbol::TypeGap }     /
        "::" { Symbol::DoubleColon } /
        ":"  { Symbol::Colon }

    rule type_() -> Identifier<'input> = i:$(
        "_"*
        (quiet!{[cl if cl.is_uppercase()]} / expected!("uppercase letter"))
        ("_" / (quiet!{[c if c.is_alphanumeric()]} / expected!("letter")))*
    ) { Identifier::Type(i) }

    rule reference() -> Identifier<'input> = i:$(
        "_"*
        (quiet!{[cl if cl.is_lowercase()]} / expected!("lowercase letter"))
        ("_" / (quiet!{[c if c.is_alphanumeric()]} / expected!("letter")))*
    ) { Identifier::Identifier(i) }

    rule identifier() -> Identifier<'input> = reference() / type_()

    rule number<T>(prefix_lower: char, prefix_upper: char, digits: rule<T>) -> &'input str = i:$(
        "0" [c if c == prefix_lower || c == prefix_upper] (
            ([^'0' | '_'] (digits() {  } / "_")+) /
            digits() {  }
        )
    ) { i }

    rule bin_lit() -> Literal<'input> =
        i:number('b', 'B', <['0'..='1']>) { Literal::Binary(i) }
    rule oct_lit() -> Literal<'input> =
        i:number('c', 'C', <['0'..='7']>) { Literal::Octal(i) }
    rule hex_lit() -> Literal<'input> =
        i:number('x', 'X', <['0'..='9' | 'a'..='f' | 'A'..='F']>) { Literal::Hex(i) }

    rule sign() = ['+' | '-']
    rule floating_lit() =
        ['0'..='9']+ ("." ['0'..='9']*)? / "." ['0'..='9']+
    rule e_notation() =
        ['e' | 'E'] sign()? ['0'..='9']+

    rule literal() -> Literal<'input> =
        bin_lit()                                                                        /
        oct_lit()                                                                        /
        hex_lit()                                                                        /
        i:$(sign()? whitespace()* floating_lit() e_notation()?) { Literal::Floating(i) } /
        "'" i:$(!"'" [_]) "'"                                   { Literal::Char(i) }     /
        "\"" i:$((!"\"" [_])*) "\""                             { Literal::String(i) }

    rule operator() -> Operator =
        "."   { Operator::Dot }                                            /
        "=>"  { Operator::Flow }                                           /
        "+"   { Operator::Math(MathOperator::Plus) }                       /
        "-"   { Operator::Math(MathOperator::Sub) }                        /
        "**"  { Operator::Math(MathOperator::Pow) }                        /
        "*"   { Operator::Math(MathOperator::Times) }                      /
        "/"   { Operator::Math(MathOperator::Div) }                        /
        "%"   { Operator::Math(MathOperator::Mod) }                        /
        "<=>" { Operator::Comparison(ComparisonOperator::Spaceship) }      /
        "=="  { Operator::Comparison(ComparisonOperator::Equiv) }          /
        "="   { Operator::Comparison(ComparisonOperator::Equals) }         /
        "!="  { Operator::Comparison(ComparisonOperator::NotEquiv) }       /
        ">="  { Operator::Comparison(ComparisonOperator::GreaterEquals) }  /
        ">"   { Operator::Comparison(ComparisonOperator::Greater) }        /
        "<="  { Operator::Comparison(ComparisonOperator::LessEquals) }     /
        "<"   { Operator::Comparison(ComparisonOperator::Less) }           /
        "||"  { Operator::Logic(LogicOperator::OrLogic) }                  /
        "&&"  { Operator::Logic(LogicOperator::AndLogic) }                 /
        "!"   { Operator::Logic(LogicOperator::NotLogic) }                 /
        "|"   { Operator::Bit(BitOperator::OrBit) }                        /
        "&"   { Operator::Bit(BitOperator::AndBit) }                       /
        "^"   { Operator::Bit(BitOperator::XorBit) }                       /
        "~"   { Operator::Bit(BitOperator::NotBit) }

    rule token_() -> Token<'input> =
        i:ignore()     { Token::Ignore(i) }     /
        i:keyword()    { Token::Keyword(i) }    /
        i:symbol()     { Token::Symbol(i) }     /
        i:identifier() { Token::Identifier(i) } /
        i:literal()    { Token::Literal(i) }    /
        i:operator()   { Token::Operator(i) }

    rule token_match() -> TokenMatch<'input> =
        start:position!() t:token_() end:position!() {
            let length = end - start;
            TokenMatch::new(t, Span::new(CodePoint::new(length as u32, start as u32)))
        }

    rule traced<T>(e: rule<T>) -> T =
        &(input:$([_]*) {
            #[cfg(feature = "trace")]
            println!("[PEG_INPUT_START]\n{}\n[PEG_TRACE_START]", input);
        })
        e:e()? {?
            #[cfg(feature = "trace")]
            println!("[PEG_TRACE_STOP]");
            e.ok_or("")
        }

    rule tokens_() -> Vec<TokenMatch<'input>> = i:token_match()* ![_] { i }

    pub rule tokens() -> Vec<TokenMatch<'input>> = traced(<tokens_()>)
    #[no_eof]
    pub rule token() -> TokenMatch<'input> = traced(<token_match()>)
}}

#[derive(Constructor, Debug, Copy, Clone)]
pub struct Lexer<const TRACE: bool>;

#[allow(dead_code)]
enum GagContainer {
    Empty,
    #[cfg(feature = "trace")]
    Full(gag::Gag),
}

impl GagContainer {
    #[must_use]
    fn enable<const E: bool>() -> Self {
        cfg_if! {
            if #[cfg(feature = "trace")] {
                if !E {
                    Self::Full(gag::Gag::stdout().expect("Cannot suppress stdout"))
                } else {
                    Self::Empty
                }
            } else {
                Self::Empty
            }
        }
    }
}

impl<const TRACE: bool> TokenProducer for Lexer<TRACE> {
    type Error<'t> = ParseError<LineCol>;

    fn parse_token<'t>(
        &self,
        whole_input: &'t str,
        position: usize,
    ) -> Result<TokenMatch<'t>, Self::Error<'t>> {
        let input = &whole_input[position..];
        let _gag = GagContainer::enable::<TRACE>();
        grammar::token(input)
    }
}

impl<const TRACE: bool> EagerTokensProducer for Lexer<TRACE> {
    type Error<'t> = ParseError<LineCol>;

    fn parse_tokens<'t>(&self, input: &'t str) -> Result<Vec<TokenMatch<'t>>, Self::Error<'t>> {
        let _gag = GagContainer::enable::<TRACE>();
        grammar::tokens(input)
    }
}