oak-dhall 0.0.11

Dhall language parser with support for functional programming and type system features.
Documentation
#![doc = include_str!("readme.md")]
/// Token type module for DHall.
pub mod token_type;

use crate::{language::DHallLanguage, lexer::token_type::DHallTokenType};
use oak_core::{
    LexOutput, Lexer, LexerCache, LexerState, OakError,
    lexer::{CommentConfig, StringConfig, WhitespaceConfig},
    source::{Source, TextEdit},
};
use std::sync::LazyLock;

static DHALL_WHITESPACE: LazyLock<WhitespaceConfig> = LazyLock::new(|| WhitespaceConfig { unicode_whitespace: true });
static DHALL_COMMENT: LazyLock<CommentConfig> = LazyLock::new(|| CommentConfig { line_marker: "--", block_start: "{-", block_end: "-}", nested_blocks: true });
static DHALL_STRING: LazyLock<StringConfig> = LazyLock::new(|| StringConfig { quotes: &['"'], escape: Some('\\') });

/// Lexer implementation for DHall.
#[derive(Clone)]
pub struct DHallLexer<'config> {
    config: &'config DHallLanguage,
}

impl<'config> Lexer<DHallLanguage> for DHallLexer<'config> {
    fn lex<'a, S: Source + ?Sized>(&self, source: &'a S, _edits: &[TextEdit], cache: &'a mut impl LexerCache<DHallLanguage>) -> LexOutput<DHallLanguage> {
        let mut state = LexerState::new(source);
        let result = self.run(&mut state);
        state.finish_with_cache(result, cache)
    }
}

impl<'config> DHallLexer<'config> {
    /// Creates a new `DHallLexer`.
    pub fn new(config: &'config DHallLanguage) -> Self {
        Self { config }
    }

    fn run<'a, S: Source + ?Sized>(&self, state: &mut LexerState<'a, S, DHallLanguage>) -> Result<(), OakError> {
        while state.not_at_end() {
            let safe_point = state.get_position();
            if self.skip_whitespace(state) {
                continue;
            };

            if self.skip_comment(state) {
                continue;
            }

            if self.lex_string_literal(state) {
                continue;
            }

            if self.lex_number_literal(state) {
                continue;
            }

            if self.lex_identifier_or_keyword(state) {
                continue;
            }

            if self.lex_operators(state) {
                continue;
            }

            if self.lex_single_char_tokens(state) {
                continue;
            }

            state.advance_if_dead_lock(safe_point)
        }

        Ok(())
    }

    fn skip_whitespace<'a, S: Source + ?Sized>(&self, state: &mut LexerState<'a, S, DHallLanguage>) -> bool {
        DHALL_WHITESPACE.scan(state, DHallTokenType::Whitespace)
    }

    fn skip_comment<'a, S: Source + ?Sized>(&self, state: &mut LexerState<'a, S, DHallLanguage>) -> bool {
        DHALL_COMMENT.scan(state, DHallTokenType::Comment, DHallTokenType::Comment)
    }

    fn lex_string_literal<'a, S: Source + ?Sized>(&self, state: &mut LexerState<'a, S, DHallLanguage>) -> bool {
        DHALL_STRING.scan(state, DHallTokenType::String)
    }

    fn lex_number_literal<'a, S: Source + ?Sized>(&self, state: &mut LexerState<'a, S, DHallLanguage>) -> bool {
        let start = state.get_position();
        let first = match state.peek() {
            Some(c) => c,
            None => return false,
        };

        if !first.is_ascii_digit() {
            return false;
        }

        state.advance(1);
        while let Some(c) = state.peek() {
            if c.is_ascii_digit() { state.advance(1) } else { break }
        }

        state.add_token(DHallTokenType::Number, start, state.get_position());
        true
    }

    fn lex_identifier_or_keyword<'a, S: Source + ?Sized>(&self, state: &mut LexerState<'a, S, DHallLanguage>) -> bool {
        let start = state.get_position();
        let first = match state.peek() {
            Some(c) => c,
            None => return false,
        };

        if !first.is_alphabetic() && first != '_' && first != 'λ' {
            return false;
        }

        state.advance(1);
        while let Some(c) = state.peek() {
            if c.is_alphanumeric() || c == '_' || c == '-' || c == '/' { state.advance(1) } else { break }
        }

        let end = state.get_position();
        let text = state.get_text_in((start..end).into());

        let kind = match text.as_ref() {
            "if" => DHallTokenType::If,
            "then" => DHallTokenType::Then,
            "else" => DHallTokenType::Else,
            "let" => DHallTokenType::Let,
            "in" => DHallTokenType::In,
            "using" => DHallTokenType::Using,
            "as" => DHallTokenType::As,
            "merge" => DHallTokenType::Merge,
            "Some" => DHallTokenType::Some,
            "None" => DHallTokenType::None,
            "with" => DHallTokenType::With,
            "forall" => DHallTokenType::Forall,
            "assert" => DHallTokenType::Assert,
            "Bool" => DHallTokenType::Bool,
            "Natural" => DHallTokenType::Natural,
            "Integer" => DHallTokenType::Integer,
            "Double" => DHallTokenType::Double,
            "Text" => DHallTokenType::Text,
            "List" => DHallTokenType::List,
            "Optional" => DHallTokenType::Optional,
            "True" => DHallTokenType::True,
            "False" => DHallTokenType::False,
            "λ" => DHallTokenType::Lambda,
            _ => DHallTokenType::Identifier,
        };

        state.add_token(kind, start, end);
        true
    }

    fn lex_operators<'a, S: Source + ?Sized>(&self, state: &mut LexerState<'a, S, DHallLanguage>) -> bool {
        let start = state.get_position();
        let text = state.rest();

        let ops = [
            ("->", DHallTokenType::Arrow),
            ("", DHallTokenType::Arrow),
            ("=>", DHallTokenType::FatArrow),
            ("==", DHallTokenType::EqualEqual),
            ("", DHallTokenType::EqualEqual),
            ("!=", DHallTokenType::NotEqual),
            ("&&", DHallTokenType::And),
            ("", DHallTokenType::And),
            ("||", DHallTokenType::Or),
            ("", DHallTokenType::Or),
            ("++", DHallTokenType::Append),
            ("//", DHallTokenType::Combine),
            ("", DHallTokenType::Combine),
            ("/\\", DHallTokenType::CombineTypes),
            ("", DHallTokenType::CombineTypes),
            ("//\\", DHallTokenType::Prefer),
            ("", DHallTokenType::Forall),
            ("λ", DHallTokenType::Lambda),
        ];

        for (op, kind) in ops {
            if text.starts_with(op) {
                state.advance(op.len());
                state.add_token(kind, start, state.get_position());
                return true;
            }
        }

        false
    }

    fn lex_single_char_tokens<'a, S: Source + ?Sized>(&self, state: &mut LexerState<'a, S, DHallLanguage>) -> bool {
        let start = state.get_position();
        let c = match state.peek() {
            Some(c) => c,
            None => return false,
        };

        let kind = match c {
            '(' => DHallTokenType::LeftParen,
            ')' => DHallTokenType::RightParen,
            '[' => DHallTokenType::LeftBracket,
            ']' => DHallTokenType::RightBracket,
            '{' => DHallTokenType::LeftBrace,
            '}' => DHallTokenType::RightBrace,
            '<' => DHallTokenType::Less,
            '>' => DHallTokenType::Greater,
            ',' => DHallTokenType::Comma,
            '.' => DHallTokenType::Dot,
            ':' => DHallTokenType::Colon,
            ';' => DHallTokenType::Semicolon,
            '=' => DHallTokenType::Equal,
            '@' => DHallTokenType::At,
            '#' => DHallTokenType::Hash,
            '?' => DHallTokenType::Question,
            '+' => DHallTokenType::Plus,
            '*' => DHallTokenType::Star,
            '/' => DHallTokenType::Slash,
            '|' => DHallTokenType::Pipe,
            '\\' => DHallTokenType::Lambda,
            _ => return false,
        };

        state.advance(1);
        state.add_token(kind, start, state.get_position());
        true
    }
}