use crate::error::{HedlError, HedlResult};
#[derive(Debug, Clone, Copy)]
pub struct CountParsingConfig {
pub allow_zero: bool,
pub check_leading_zeros: bool,
pub check_trailing_content: bool,
pub use_rightmost_paren: bool,
}
impl CountParsingConfig {
pub const LIST_HINT: Self = Self {
allow_zero: false,
check_leading_zeros: false,
check_trailing_content: false,
use_rightmost_paren: false,
};
pub const STRUCT_HINT: Self = Self {
allow_zero: true,
check_leading_zeros: true,
check_trailing_content: true,
use_rightmost_paren: true,
};
}
pub fn parse_parenthesized_count(
input: &str,
config: CountParsingConfig,
line_num: usize,
) -> HedlResult<(String, Option<usize>)> {
let paren_start = if config.use_rightmost_paren {
input.rfind('(')
} else {
input.find('(')
};
let Some(paren_start) = paren_start else {
return Ok((input.to_string(), None));
};
let name_part = input[..paren_start].trim();
let after_paren = &input[paren_start + 1..];
let paren_end = after_paren
.find(')')
.ok_or_else(|| HedlError::syntax("unclosed count hint parenthesis", line_num))?;
let count_str = after_paren[..paren_end].trim();
if config.check_trailing_content {
let trailing = after_paren[paren_end + 1..].trim();
if !trailing.is_empty() {
return Err(HedlError::syntax(
format!("unexpected content after count: {}", trailing),
line_num,
));
}
}
let count_val: usize = count_str
.parse()
.map_err(|_| HedlError::syntax(format!("invalid count value: {}", count_str), line_num))?;
if config.check_leading_zeros && count_str.len() > 1 && count_str.starts_with('0') {
return Err(HedlError::syntax(
"leading zeros not allowed in count",
line_num,
));
}
if !config.allow_zero && count_val == 0 {
return Err(HedlError::syntax(
"count hint must be greater than zero",
line_num,
));
}
Ok((name_part.to_string(), Some(count_val)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_parenthesis_returns_input() {
let (name, count) =
parse_parenthesized_count("simple", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "simple");
assert_eq!(count, None);
}
#[test]
fn test_simple_count_hint() {
let (name, count) =
parse_parenthesized_count("name(5)", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "name");
assert_eq!(count, Some(5));
}
#[test]
fn test_count_with_spaces() {
let (name, count) =
parse_parenthesized_count("name (10)", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "name");
assert_eq!(count, Some(10));
}
#[test]
fn test_count_with_internal_spaces() {
let (name, count) =
parse_parenthesized_count("name( 10 )", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "name");
assert_eq!(count, Some(10));
}
#[test]
fn test_struct_hint_allows_zero() {
let (name, count) =
parse_parenthesized_count("Empty(0)", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "Empty");
assert_eq!(count, Some(0));
}
#[test]
fn test_list_hint_rejects_zero() {
let result = parse_parenthesized_count("teams(0)", CountParsingConfig::LIST_HINT, 1);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("greater than zero"));
}
#[test]
fn test_struct_hint_rejects_leading_zeros() {
let result = parse_parenthesized_count("Company(01)", CountParsingConfig::STRUCT_HINT, 1);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("leading zeros"));
}
#[test]
fn test_list_hint_allows_leading_zeros() {
let (name, count) =
parse_parenthesized_count("teams(01)", CountParsingConfig::LIST_HINT, 1).unwrap();
assert_eq!(name, "teams");
assert_eq!(count, Some(1));
}
#[test]
fn test_struct_hint_rejects_trailing_content() {
let result =
parse_parenthesized_count("Company(10) extra", CountParsingConfig::STRUCT_HINT, 1);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("unexpected content"));
}
#[test]
fn test_list_hint_ignores_trailing_content() {
let (name, count) =
parse_parenthesized_count("teams(5) extra", CountParsingConfig::LIST_HINT, 1).unwrap();
assert_eq!(name, "teams");
assert_eq!(count, Some(5));
}
#[test]
fn test_struct_hint_uses_rightmost_paren() {
let (name, count) =
parse_parenthesized_count("Func(x)(10)", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "Func(x)");
assert_eq!(count, Some(10));
}
#[test]
fn test_list_hint_uses_first_paren() {
let (name, count) =
parse_parenthesized_count("func(5)extra(10)", CountParsingConfig::LIST_HINT, 1)
.unwrap();
assert_eq!(name, "func");
assert_eq!(count, Some(5));
}
#[test]
fn test_unclosed_parenthesis_error() {
let result = parse_parenthesized_count("name(10", CountParsingConfig::STRUCT_HINT, 1);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("unclosed"));
}
#[test]
fn test_invalid_number_error() {
let result = parse_parenthesized_count("name(abc)", CountParsingConfig::STRUCT_HINT, 1);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("invalid count"));
}
#[test]
fn test_empty_parentheses_error() {
let result = parse_parenthesized_count("name()", CountParsingConfig::STRUCT_HINT, 1);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("invalid count"));
}
#[test]
fn test_negative_number_error() {
let result = parse_parenthesized_count("name(-5)", CountParsingConfig::STRUCT_HINT, 1);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("invalid count"));
}
#[test]
fn test_overflow_number_error() {
let overflow = format!("name({}0)", usize::MAX);
let result = parse_parenthesized_count(&overflow, CountParsingConfig::STRUCT_HINT, 1);
assert!(result.is_err());
}
#[test]
fn test_large_valid_count() {
let (name, count) =
parse_parenthesized_count("name(999999)", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "name");
assert_eq!(count, Some(999999));
}
#[test]
fn test_single_digit_count() {
let (name, count) =
parse_parenthesized_count("x(1)", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "x");
assert_eq!(count, Some(1));
}
#[test]
fn test_empty_name_with_count() {
let (name, count) =
parse_parenthesized_count("(5)", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "");
assert_eq!(count, Some(5));
}
#[test]
fn test_whitespace_only_name() {
let (name, count) =
parse_parenthesized_count(" (10)", CountParsingConfig::STRUCT_HINT, 1).unwrap();
assert_eq!(name, "");
assert_eq!(count, Some(10));
}
#[test]
fn test_error_includes_line_number() {
let result = parse_parenthesized_count("name(abc)", CountParsingConfig::STRUCT_HINT, 42);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.line, 42);
}
}