///|
/// A validated `Plural-Forms` rule.
pub struct PluralRule {
nplurals : Int
expression : String
} derive(Debug, Eq)
///|
/// The conventional English rule: singular only when `n == 1`.
pub fn PluralRule::english() -> PluralRule {
{ nplurals: 2, expression: "n != 1" }
}
///|
fn parse_positive_decimal(
text : String,
field : String,
) -> Int raise GettextError {
let trimmed = text.trim().to_owned()
if trimmed == "" {
raise Validation(message="\{field} cannot be empty")
}
let mut value = 0
for c in trimmed {
guard c is ('0'..='9') else {
raise Validation(message="\{field} must be a positive decimal integer")
}
let digit = c.to_int() - '0'.to_int()
if value > 214748364 || (value == 214748364 && digit > 7) {
raise Validation(message="\{field} exceeds MoonBit Int range")
}
value = value * 10 + digit
}
if value <= 0 {
raise Validation(message="\{field} must be greater than zero")
}
value
}
///|
fn plural_forms_payload(input : String) -> String {
match input.find("Plural-Forms:") {
Some(start) => {
let after = input[start + "Plural-Forms:".length():].to_owned()
match after.find("\n") {
Some(end) => after[:end].trim_end(chars="\r").to_owned()
None => after
}
}
None => input
}
}
///|
/// Parse a `Plural-Forms` value or a complete gettext metadata header.
///
/// Both `nplurals` and `plural` assignments are required. Assignment order and
/// surrounding whitespace are ignored.
pub fn parse_plural_forms(input : String) -> PluralRule raise GettextError {
let payload = plural_forms_payload(input)
let mut nplurals : Int? = None
let mut expression : String? = None
for raw_assignment in payload.split(";") {
let assignment = raw_assignment.trim().to_owned()
if assignment == "" {
continue
}
match assignment.split_once("=") {
Some((raw_key, raw_value)) => {
let key = raw_key.trim().to_owned()
let value = raw_value.trim().to_owned()
if key == "nplurals" {
nplurals = Some(parse_positive_decimal(value, "nplurals"))
} else if key == "plural" {
expression = Some(value)
}
}
None => ()
}
}
guard nplurals is Some(count) else {
raise Validation(message="Plural-Forms is missing nplurals")
}
guard expression is Some(code) else {
raise Validation(message="Plural-Forms is missing plural expression")
}
ignore(parse_plural_ast(code))
{ nplurals: count, expression: code }
}
///|
/// Select a translation index and ensure the expression stays inside the
/// declared `[0, nplurals)` range.
pub fn PluralRule::select(self : PluralRule, n : Int) -> Int raise GettextError {
let index = evaluate_plural_expression(self.expression, n)
if index < 0 || index >= self.nplurals {
raise Validation(
message="plural expression returned \{index}, outside 0..<\{self.nplurals}",
)
}
index
}