//! Weedle - A WebIDL Parser
//!
//! Parses valid WebIDL definitions & produces a data structure starting from
//! [`Definitions`](struct.Definitions.html).
//!
//! ### Example
//!
//! ```
//! extern crate weedle;
//!
//! let parsed = weedle::parse("
//!     interface Window {
//!         readonly attribute Storage sessionStorage;
//!     };
//! ").unwrap();
//! println!("{:?}", parsed);
//! ```
//!
//! Note:
//! This parser follows the grammar given at [WebIDL](https://heycam.github.io/webidl).
//!
//! If any flaws found when parsing string with a valid grammar, create an issue.

use self::argument::ArgumentList;
use self::attribute::ExtendedAttributeList;
use self::common::{Braced, Identifier, Parenthesized, PunctuatedNonEmpty};
use self::dictionary::DictionaryMembers;
use self::interface::{Inheritance, InterfaceMembers};
use self::literal::StringLit;
use self::mixin::MixinMembers;
use self::namespace::NamespaceMembers;
use self::types::{AttributedType, ReturnType};
pub use nom::{error::Error, Err, IResult};

#[macro_use]
mod macros;
#[macro_use]
mod whitespace;
#[macro_use]
pub mod term;
pub mod argument;
pub mod attribute;
pub mod common;
pub mod dictionary;
pub mod interface;
pub mod literal;
pub mod mixin;
pub mod namespace;
pub mod types;

/// A convenient parse function
///
/// ### Example
///
/// ```
/// extern crate weedle;
///
/// let parsed = weedle::parse("
///     interface Window {
///         readonly attribute Storage sessionStorage;
///     };
/// ").unwrap();
///
/// println!("{:?}", parsed);
/// ```
pub fn parse(raw: &str) -> Result<Definitions<'_>, Err<Error<&str>>> {
    let (remaining, parsed) = Definitions::parse(raw)?;
    assert!(
        remaining.is_empty(),
        "There is redundant raw data after parsing"
    );
    Ok(parsed)
}

pub trait Parse<'a>: Sized {
    fn parse(input: &'a str) -> IResult<&'a str, Self>;
}

/// Parses WebIDL definitions. It is the root struct for a complete WebIDL definition.
///
/// ### Example
/// ```
/// use weedle::{Definitions, Parse};
///
/// let (_, parsed) = Definitions::parse("
///     interface Window {
///         readonly attribute Storage sessionStorage;
///     };
/// ").unwrap();
///
/// println!("{:?}", parsed);
/// ```
///
/// It is recommended to use [`parse`](fn.parse.html) instead.
pub type Definitions<'a> = Vec<Definition<'a>>;

ast_types! {
    /// Parses a definition
    enum Definition<'a> {
        /// Parses `[attributes]? callback identifier = type ( (arg1, arg2, ..., argN)? );`
        Callback(struct CallbackDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            callback: term!(callback),
            identifier: Identifier<'a>,
            assign: term!(=),
            return_type: ReturnType<'a>,
            arguments: Parenthesized<ArgumentList<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? callback interface identifier ( : inheritance )? { members };`
        CallbackInterface(struct CallbackInterfaceDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            callback: term!(callback),
            interface: term!(interface),
            identifier: Identifier<'a>,
            inheritance: Option<Inheritance<'a>>,
            members: Braced<InterfaceMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? interface identifier ( : inheritance )? { members };`
        Interface(struct InterfaceDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            interface: term!(interface),
            identifier: Identifier<'a>,
            inheritance: Option<Inheritance<'a>>,
            members: Braced<InterfaceMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? interface mixin identifier { members };`
        InterfaceMixin(struct InterfaceMixinDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            interface: term!(interface),
            mixin: term!(mixin),
            identifier: Identifier<'a>,
            members: Braced<MixinMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? namespace identifier { members };`
        Namespace(struct NamespaceDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            namespace: term!(namespace),
            identifier: Identifier<'a>,
            members: Braced<NamespaceMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? dictionary identifier ( : inheritance )? { members };`
        Dictionary(struct DictionaryDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            dictionary: term!(dictionary),
            identifier: Identifier<'a>,
            inheritance: Option<Inheritance<'a>>,
            members: Braced<DictionaryMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? partial interface identifier { members };`
        PartialInterface(struct PartialInterfaceDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            partial: term!(partial),
            interface: term!(interface),
            identifier: Identifier<'a>,
            members: Braced<InterfaceMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? partial interface mixin identifier { members };`
        PartialInterfaceMixin(struct PartialInterfaceMixinDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            partial: term!(partial),
            interface: term!(interface),
            mixin: term!(mixin),
            identifier: Identifier<'a>,
            members: Braced<MixinMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? partial dictionary identifier { members };`
        PartialDictionary(struct PartialDictionaryDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            partial: term!(partial),
            dictionary: term!(dictionary),
            identifier: Identifier<'a>,
            members: Braced<DictionaryMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? partial namespace identifier { members };`
        PartialNamespace(struct PartialNamespaceDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            partial: term!(partial),
            namespace: term!(namespace),
            identifier: Identifier<'a>,
            members: Braced<NamespaceMembers<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? enum identifier { values };`
        Enum(struct EnumDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            enum_: term!(enum),
            identifier: Identifier<'a>,
            values: Braced<EnumValueList<'a>>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? typedef attributedtype identifier;`
        Typedef(struct TypedefDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            typedef: term!(typedef),
            type_: AttributedType<'a>,
            identifier: Identifier<'a>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? identifier includes identifier;`
        IncludesStatement(struct IncludesStatementDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            lhs_identifier: Identifier<'a>,
            includes: term!(includes),
            rhs_identifier: Identifier<'a>,
            semi_colon: term!(;),
        }),
        /// Parses `[attributes]? identifier implements identifier;`
        Implements(struct ImplementsDefinition<'a> {
            attributes: Option<ExtendedAttributeList<'a>>,
            lhs_identifier: Identifier<'a>,
            includes: term!(implements),
            rhs_identifier: Identifier<'a>,
            semi_colon: term!(;),
        }),
    }
}

/// Parses a non-empty enum value list
pub type EnumValueList<'a> = PunctuatedNonEmpty<StringLit<'a>, term!(,)>;

#[cfg(test)]
mod test {
    use super::*;

    test!(should_parse_includes_statement { "first includes second;" =>
        "";
        IncludesStatementDefinition;
        attributes.is_none();
        lhs_identifier.0 == "first";
        rhs_identifier.0 == "second";
    });

    test!(should_parse_typedef { "typedef short Short;" =>
        "";
        TypedefDefinition;
        attributes.is_none();
        identifier.0 == "Short";
    });

    test!(should_parse_enum { r#"enum name { "first", "second" }; "# =>
        "";
        EnumDefinition;
        attributes.is_none();
        identifier.0 == "name";
        values.body.list.len() == 2;
    });

    test!(should_parse_dictionary { "dictionary A { long c; long g; };" =>
        "";
        DictionaryDefinition;
        attributes.is_none();
        identifier.0 == "A";
        inheritance.is_none();
        members.body.len() == 2;
    });

    test!(should_parse_dictionary_inherited { "dictionary C : B { long e; long f; };" =>
        "";
        DictionaryDefinition;
        attributes.is_none();
        identifier.0 == "C";
        inheritance.is_some();
        members.body.len() == 2;
    });

    test!(should_parse_partial_namespace { "
        partial namespace VectorUtils {
            readonly attribute Vector unit;
            double dotProduct(Vector x, Vector y);
            Vector crossProduct(Vector x, Vector y);
        };
    " =>
        "";
        PartialNamespaceDefinition;
        attributes.is_none();
        identifier.0 == "VectorUtils";
        members.body.len() == 3;
    });

    test!(should_parse_partial_dictionary { "partial dictionary C { long e; long f; };" =>
        "";
        PartialDictionaryDefinition;
        attributes.is_none();
        identifier.0 == "C";
        members.body.len() == 2;
    });

    test!(should_parse_partial_interface_mixin { "
        partial interface mixin WindowSessionStorage {
          readonly attribute Storage sessionStorage;
        };
    " =>
        "";
        PartialInterfaceMixinDefinition;
        attributes.is_none();
        identifier.0 == "WindowSessionStorage";
        members.body.len() == 1;
    });

    test!(should_parse_partial_interface { "
        partial interface Window {
          readonly attribute Storage sessionStorage;
        };
    " =>
        "";
        PartialInterfaceDefinition;
        attributes.is_none();
        identifier.0 == "Window";
        members.body.len() == 1;
    });

    test!(should_parse_namespace { "
        namespace VectorUtils {
          readonly attribute Vector unit;
          double dotProduct(Vector x, Vector y);
          Vector crossProduct(Vector x, Vector y);
        };
    " =>
        "";
        NamespaceDefinition;
        attributes.is_none();
        identifier.0 == "VectorUtils";
        members.body.len() == 3;
    });

    test!(should_parse_interface_mixin { "
        interface mixin WindowSessionStorage {
          readonly attribute Storage sessionStorage;
        };
    " =>
        "";
        InterfaceMixinDefinition;
        attributes.is_none();
        identifier.0 == "WindowSessionStorage";
        members.body.len() == 1;
    });

    test!(should_parse_interface { "
        interface Window {
          readonly attribute Storage sessionStorage;
        };
    " =>
        "";
        InterfaceDefinition;
        attributes.is_none();
        identifier.0 == "Window";
        members.body.len() == 1;
    });

    test!(should_parse_callback_interface {"
        callback interface Options {
          attribute DOMString? option1;
          attribute DOMString? option2;
          attribute long? option3;
        };
    " =>
        "";
        CallbackInterfaceDefinition;
        attributes.is_none();
        identifier.0 == "Options";
        members.body.len() == 3;
    });

    test!(should_parse_callback { "callback AsyncOperationCallback = undefined (DOMString status);" =>
        "";
        CallbackDefinition;
        attributes.is_none();
        identifier.0 == "AsyncOperationCallback";
        arguments.body.list.len() == 1;
    });

    test!(should_parse_with_line_comments { "
        // This is a comment
        callback AsyncOperationCallback = undefined (DOMString status);
    " =>
        "";
        CallbackDefinition;
    });

    test!(should_parse_with_block_comments { "
        /* This is a comment */
        callback AsyncOperationCallback = undefined (DOMString status);
    " =>
        "";
        CallbackDefinition;
    });

    test!(should_parse_with_multiple_comments { "
        // This is a comment
        // This is a comment
        // This is a comment

        // This is a comment
        callback AsyncOperationCallback = undefined (DOMString status);
    " =>
        "";
        CallbackDefinition;
    });
}
