///|
/// Decimal text editing. Precision limits fractional digits; it never rounds.
/// Uses '.' as radix and optional ',' grouping. At most 256 raw characters.
pub fn decimal(
precision? : Int = 2,
signed? : Bool = false,
grouped? : Bool = true,
) -> Result[Mask, InputError] {
if precision < 0 || precision > 18 {
return Err(InvalidConfig("precision must be between 0 and 18"))
}
Ok({ kind: DecimalKind(precision, signed, grouped), })
}
///|
fn decimal_chars(
text : String,
grouped : Bool,
) -> Result[Array[Char], InputError] {
if text.length() > 512 {
return Err(TooLong(512))
}
let chars : Array[Char] = []
for c in text {
if c == ',' && grouped {
continue
}
if !c.is_ascii_digit() && c != '.' && c != '-' {
return Err(InvalidCharacter(chars.length(), c.to_string()))
}
chars.push(c)
}
Ok(chars)
}
///|
fn render_decimal(
raw : Array[Char],
precision : Int,
signed : Bool,
grouped : Bool,
) -> Result[Render, InputError] {
if raw.length() > 256 {
return Err(TooLong(256))
}
let mut dot = raw.length()
let sign = if raw.length() > 0 && raw[0] == '-' { 1 } else { 0 }
if sign == 1 && !signed {
return Err(InvalidCharacter(0, "-"))
}
for i, c in raw {
if c == '.' {
if dot != raw.length() || precision == 0 {
return Err(InvalidCharacter(i, "."))
}
dot = i
} else if c == '-' && i == 0 {
()
} else if !c.is_ascii_digit() {
return Err(InvalidCharacter(i, c.to_string()))
}
}
if dot < raw.length() && raw.length() - dot - 1 > precision {
return Err(InvalidValue("fraction exceeds configured precision"))
}
let starts : Array[Int] = []
let ends : Array[Int] = []
let mut display = ""
for i, c in raw {
if grouped && i > sign && i < dot && (dot - i) % 3 == 0 {
display += ","
}
starts.push(display.length())
display += c.to_string()
ends.push(display.length())
}
Ok({ raw, display, starts, ends, })
}
///|
fn parse_decimal(
text : String,
precision : Int,
signed : Bool,
grouped : Bool,
) -> Result[Render, InputError] {
let chars = match decimal_chars(text, grouped) {
Ok(chars) => chars
Err(e) => return Err(e)
}
match render_decimal(chars, precision, signed, grouped) {
Err(e) => Err(e)
Ok(render) => {
if text.contains_char(',') && render.display != text {
return Err(InvalidValue("malformed digit grouping"))
}
Ok(render)
}
}
}
///|
fn decimal_status(raw : Array[Char]) -> Status {
if raw.is_empty() {
return Empty
}
if raw[raw.length() - 1] == '.' || raw[raw.length() - 1] == '-' {
return Incomplete
}
Complete
}
///|
fn decimal_value(raw : Array[Char]) -> String {
let negative = raw[0] == '-'
let mut i = if negative { 1 } else { 0 }
while i + 1 < raw.length() && raw[i] == '0' && raw[i + 1].is_ascii_digit() {
i += 1
}
let mut value = if negative { "-" } else { "" }
if raw[i] == '.' {
value += "0"
}
while i < raw.length() {
value += raw[i].to_string()
i += 1
}
value
}