mod line;
mod location;
mod rule_line;

#[cfg(test)]
mod tests;

use crate::error::ParseError;
use crate::program::RuleSet;
use crate::source::ProgramSource;

use line::RawSourceLine;
use location::{parse_allocation_error, source_line_number};

/// Parses source bytes into a typed program.
///
/// # Errors
///
/// Returns `ParseError` if source location conversion, line compaction, rule
/// parsing, or parsed-rule storage fails.
pub(crate) fn parse_rules_impl(source: ProgramSource<'_>) -> Result<RuleSet, ParseError> {
    let mut rule_set = RuleSet::new();

    for (zero_based_line, raw_line) in source.as_bytes().split(|&byte| byte == b'\n').enumerate() {
        let line_number = source_line_number(zero_based_line)?;
        let compact_code = RawSourceLine::new(line_number, raw_line)
            .into_code_line()?
            .into_compact_line()?;

        let Some(non_empty_code) = compact_code.into_non_empty() else {
            continue;
        };

        let parsed_rule = non_empty_code.into_rule_syntax()?.parse()?;

        rule_set
            .push_parsed_rule(parsed_rule)
            .map_err(|error| parse_allocation_error(line_number, error))?;
    }

    Ok(rule_set)
}
