// Copyright (c) ZeroC, Inc.

use crate::parsers::common::SourceBlock;
use crate::parsers::preprocessor::tokens::*;
use crate::parsers::preprocessor::grammar::*;
use crate::parsers::preprocessor::parser::Preprocessor;
use crate::slice_file::Span;
use std::convert::identity;

// Specify the signature of the parser's entry function.
grammar<'input, 'a>(preprocessor: &mut Preprocessor<'a>);

extern {
    // Specify the types that the parser should use for location tracking and error emission.
    type Location = crate::slice_file::Location;
    type Error = crate::parsers::preprocessor::tokens::Error;

    // Link the names of terminal tokens with their actual token types. Ex: `identifier => TokenKind::Identifier`
    // says that wherever we use `identifier` in the grammar, it actually represents a `TokenKind::Identifier`.
    // Identifiers must match the names we use in the grammar rules, and values must match enumerators in `tokens.rs`.
    enum TokenKind<'input> {
        source_block => TokenKind::SourceBlock(<SourceBlock<'input>>),
        identifier => TokenKind::Identifier(<&'input str>),

        // Directive keywords
        define_keyword => TokenKind::DefineKeyword,
        undefine_keyword => TokenKind::UndefineKeyword,
        if_keyword => TokenKind::IfKeyword,
        elif_keyword => TokenKind::ElifKeyword,
        else_keyword => TokenKind::ElseKeyword,
        endif_keyword => TokenKind::EndifKeyword,

        directive_end => TokenKind::DirectiveEnd,

        // Operators
        "!" => TokenKind::Not,
        "&&" => TokenKind::And,
        "||"  => TokenKind::Or,

        // Brackets
        "(" => TokenKind::LeftParenthesis,
        ")" => TokenKind::RightParenthesis,
    }
}

// Grammar Rules

pub SliceFile: std::vec::IntoIter<SourceBlock<'input>> = {
    BlockContent => {
        let mut source_blocks = Vec::new();
        process_nodes(<>, &mut source_blocks, preprocessor);
        source_blocks.into_iter()
    }
}

BlockContent: Vec<Node<'input>> = {
    Node* => {
        <>.into_iter().filter_map(identity).collect()
    }
}

Node: Option<Node<'input>> = {
    source_block => Some(Node::SourceBlock(<>)),
    DefineDirective => Some(Node::DefineDirective(<>)),
    UndefineDirective => Some(Node::UndefineDirective(<>)),
    Conditional => Some(Node::Conditional(<>)),

    // If there was a syntax error in a preprocessor directive, we recover from it here
    // by matching any syntax error, followed by a `DirectiveEnd` (newline or EOF).
    <!> directive_end => {
        recover_from_error(preprocessor, <>);
        None
    },
}

DefineDirective: &'input str = define_keyword <identifier> directive_end;

UndefineDirective: &'input str = undefine_keyword <identifier> directive_end;

IfDirective = if_keyword <Expression> directive_end;

ElifDirective = elif_keyword <Expression> directive_end;

ElseDirective: () = {
    else_keyword directive_end => (),
}

EndifDirective: () = {
    endif_keyword directive_end => (),
}

 Conditional: Conditional<'input> = {
    <if_section: (IfDirective BlockContent)> <elif_sections: (ElifDirective BlockContent)*> <else_section: (ElseDirective <BlockContent>)?> EndifDirective => {
        Conditional { if_section, elif_sections, else_section }
    },
}

Expression: Expression<'input> = {
    <term: Term> => Expression::Term(term),
    "!" <term: Term> => Expression::Not(term),
    <expr: Expression> "&&" <term: Term> => Expression::And(Box::new(expr), term),
    <expr: Expression> "||" <term: Term> => Expression::Or(Box::new(expr), term),
}

Term: Term<'input> = {
    identifier => Term::Symbol(<>),
    "(" <Expression> ")" => Term::Expression(Box::new(<>)),
}
