#![doc = include_str!("readme.md")]
/// J element type definitions
pub mod element_type;

pub use element_type::JElementType;

use crate::{language::JLanguage, lexer::JTokenType};
use oak_core::{
    OakError, TextEdit,
    parser::{ParseCache, ParseOutput, Parser, ParserState},
    source::Source,
};

pub(crate) type State<'a, S> = ParserState<'a, JLanguage, S>;

/// J language parser
pub struct JParser<'config> {
    pub(crate) config: &'config JLanguage,
}

impl<'config> JParser<'config> {
    /// Creates a new J parser
    pub fn new(config: &'config JLanguage) -> Self {
        Self { config }
    }

    /// Parses a sentence
    fn parse_sentence<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> bool {
        let cp = state.checkpoint();

        // Simple assignment or expression parsing
        let mut matched;
        if state.at(JTokenType::Identifier) && (state.peek_kind_at(1) == Some(JTokenType::IsGlobal) || state.peek_kind_at(1) == Some(JTokenType::IsLocal)) {
            matched = self.parse_assignment(state);
        }
        else {
            matched = self.parse_expression(state);
        }

        if matched {
            state.finish_at(cp, JElementType::Sentence);
        }
        matched
    }

    /// Parses an assignment
    fn parse_assignment<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> bool {
        let cp = state.checkpoint();
        state.eat(JTokenType::Identifier);
        if state.at(JTokenType::IsGlobal) {
            state.eat(JTokenType::IsGlobal);
        }
        else {
            state.eat(JTokenType::IsLocal);
        }
        self.parse_expression(state);
        state.finish_at(cp, JElementType::Assignment);
        true
    }

    /// Parses an expression
    fn parse_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> bool {
        let cp = state.checkpoint();
        let mut matched = false;

        while state.not_at_end() && !state.at(JTokenType::Newline) && !state.at(JTokenType::Eof) {
            if state.at(JTokenType::Identifier) {
                state.eat(JTokenType::Identifier);
                matched = true;
            }
            else if state.at(JTokenType::NumberLiteral) {
                state.eat(JTokenType::NumberLiteral);
                matched = true;
            }
            else if state.at(JTokenType::StringLiteral) {
                state.eat(JTokenType::StringLiteral);
                matched = true;
            }
            else if state.at(JTokenType::LeftParen) {
                let group_cp = state.checkpoint();
                state.eat(JTokenType::LeftParen);
                self.parse_expression(state);
                let _ = state.expect(JTokenType::RightParen);
                state.finish_at(group_cp, JElementType::Group);
                matched = true;
            }
            else {
                // Possibly an operator
                state.advance();
                matched = true;
            }
        }

        if matched {
            state.finish_at(cp, JElementType::Expression);
        }
        matched
    }
}

impl<'config> Parser<JLanguage> for JParser<'config> {
    fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<JLanguage>) -> ParseOutput<'a, JLanguage> {
        let lexer = crate::lexer::JLexer::new(self.config);
        oak_core::parser::parse_with_lexer(&lexer, text, edits, cache, |state| {
            let root_cp = state.checkpoint();
            let unit_cp = state.checkpoint();

            while state.not_at_end() {
                if !self.parse_sentence(state) {
                    state.advance();
                }
            }

            state.finish_at(unit_cp, JElementType::CompilationUnit);
            Ok(state.finish_at(root_cp, JElementType::Root))
        })
    }
}
