noml 0.9.0

High-performance dynamic configuration language with format preservation, environment variables, native types, string interpolation, and TOML compatibility. Blazing-fast parsing at 25µs with zero-copy architecture.
Documentation
//! # NOML Parser Module
//!
//! High-performance parser implementation for NOML (Nested Object Markup Language).
//! Features a hand-written recursive descent parser optimized for speed and accuracy,
//! with complete source fidelity preservation for advanced tooling support.
//!
//! ## Architecture
//!
//! The parser consists of three main components:
//!
//! - **[`lexer`]** - Zero-copy tokenizer that produces a stream of tokens
//! - **[`grammar`]** - Recursive descent parser that builds the AST
//! - **[`ast`]** - Abstract syntax tree with full source information
//!
//! ## Key Features
//!
//! - **Zero-copy lexing** - Tokens reference the original source string
//! - **Source preservation** - Comments, whitespace, and formatting retained
//! - **Precise error reporting** - Line/column information for all errors
//! - **Full AST** - Complete representation of the parsed document
//! - **Thread-safe** - All components implement `Send + Sync`
//!
//! ## Performance
//!
//! The parser is optimized for high-throughput scenarios:
//!
//! - Small configs: ~19μs parse time
//! - Medium configs: ~200μs parse time  
//! - Large configs: ~1.7ms parse time
//!
//! ## Example Usage
//!
//! ```rust
//! use noml::parser::{parse, Lexer, NomlParser};
//!
//! // High-level parsing
//! let document = parse(r#"
//!     name = "my-app"
//!     
//!     [server]
//!     port = 8080
//!     host = "localhost"
//! "#)?;
//!
//! // Low-level lexing
//! let mut lexer = Lexer::new("key = 'value'");
//! let tokens = lexer.tokenize()?;
//!
//! // Custom parsing
//! let mut parser = NomlParser::new(tokens, "key = 'value'");
//! let document = parser.parse()?;
//!
//! # Ok::<(), noml::error::NomlError>(())
//! ```

pub mod ast;
pub mod grammar;
pub mod lexer;

// Re-export main parsing functions and types
pub use ast::{AstNode, AstValue, Comment, Comments, Document, Key, Span, TableEntry};
pub use grammar::{parse_file, parse_string, NomlParser};
pub use lexer::{Lexer, Token, TokenKind};

use crate::error::Result;

/// Parse NOML source code from a string
///
/// This is the main entry point for parsing NOML text. It provides
/// comprehensive error reporting and preserves all source information.
///
/// # Example
/// ```rust
/// use noml::parser::parse;
///
/// let source = r#"
/// [server]
/// host = "localhost"  
/// port = 8080
/// "#;
///
/// let document = parse(source)?;
/// # Ok::<(), noml::error::NomlError>(())
/// ```
pub fn parse(source: &str) -> Result<Document> {
    parse_string(source, None)
}

/// Parse NOML from a file path
///
/// Reads the file and parses its contents, providing the file path
/// for better error reporting.
///
/// # Example
/// ```rust
/// use noml::parser::parse_from_file;
///
/// // This would work if config.noml exists:
/// // let document = parse_from_file("config.noml")?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn parse_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Document> {
    parse_file(path.as_ref())
}

/// Validate NOML syntax without building a full AST
///
/// This is faster than full parsing when you only need to check
/// if the syntax is valid.
///
/// # Example
/// ```rust
/// use noml::parser::validate;
///
/// let is_valid = validate(r#"
/// [server]
/// host = "localhost"
/// "#).is_ok();
///
/// assert!(is_valid);
/// ```
pub fn validate(source: &str) -> Result<()> {
    parse(source).map(|_| ())
}

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

    #[test]
    fn basic_parsing() {
        let source = r#"
        # Simple config
        name = "test"
        version = 1.0
        debug = true
        
        [database]
        url = "sqlite://test.db"
        "#;

        let doc = parse(source).expect("Should parse successfully");
        let value = doc.to_value().expect("Should convert to value");

        // Check some basic values
        assert_eq!(value.get("name").unwrap().as_string().unwrap(), "test");
        assert_eq!(value.get("version").unwrap().as_float().unwrap(), 1.0);
        assert!(value.get("debug").unwrap().as_bool().unwrap());
        assert_eq!(
            value.get("database.url").unwrap().as_string().unwrap(),
            "sqlite://test.db"
        );
    }

    #[test]
    fn validation_success() {
        let valid_noml = r#"
        [server]
        host = "localhost"
        port = 8080
        "#;

        assert!(validate(valid_noml).is_ok());
    }

    #[test]
    fn validation_failure() {
        let invalid_noml = r#"
        [server
        host = "localhost"
        "#;

        assert!(validate(invalid_noml).is_err());
    }

    #[test]
    fn comment_preservation() {
        let source = r#"
        # Main configuration
        name = "test" # App name
        
        # Database section
        [database]
        url = "sqlite://test.db"
        "#;

        let doc = parse(source).expect("Should parse successfully");
        let comments = doc.all_comments();

        // Should have preserved comments
        assert!(!comments.is_empty());
        assert!(comments
            .iter()
            .any(|c| c.text.contains("Main configuration")));
        assert!(comments.iter().any(|c| c.text.contains("App name")));
        assert!(comments.iter().any(|c| c.text.contains("Database section")));
    }

    #[test]
    fn nested_key_paths() {
        let source = r#"
        [server.database]
        url = "postgres://localhost/test"
        
        [server.cache.redis]
        host = "localhost"
        port = 6379
        "#;

        let doc = parse(source).expect("Should parse successfully");
        let value = doc.to_value().expect("Should convert to value");

        assert_eq!(
            value
                .get("server.database.url")
                .unwrap()
                .as_string()
                .unwrap(),
            "postgres://localhost/test"
        );
        assert_eq!(
            value
                .get("server.cache.redis.host")
                .unwrap()
                .as_string()
                .unwrap(),
            "localhost"
        );
        assert_eq!(
            value
                .get("server.cache.redis.port")
                .unwrap()
                .as_integer()
                .unwrap(),
            6379
        );
    }

    #[test]
    fn array_operations() {
        let source = r#"
        ports = [8080, 8081, 8082]
        
        servers = [
            "server1.example.com",
            "server2.example.com",
        ]
        "#;

        let doc = parse(source).expect("Should parse successfully");
        let value = doc.to_value().expect("Should convert to value");

        let ports = value.get("ports").unwrap().as_array().unwrap();
        assert_eq!(ports.len(), 3);
        assert_eq!(ports[0].as_integer().unwrap(), 8080);

        let servers = value.get("servers").unwrap().as_array().unwrap();
        assert_eq!(servers.len(), 2);
        assert_eq!(servers[0].as_string().unwrap(), "server1.example.com");
    }
}