///|
typealias @string.View as str
///|
pub enum Align {
Left
Right
Center
} derive(Show, Eq)
///|
pub enum Grouping {
Comma
Underscore
Default
} derive(Show)
///|
impl Default for Grouping with default() {
return Grouping::Default
}
///|
pub enum SpecType {
Binary
Digit
ExponentLower
ExponentUpper
Float
Octal
HexLower
HexUpper
Percent
Default
} derive(Show)
///|
impl Default for SpecType with default() {
return SpecType::Default
}
///|
pub struct Options {
fill : Char?
align : Align
sharp : Bool
zero : Bool
} derive(Show)
///|
impl Default for Options with default() {
return { fill: None, align: Align::Left, sharp: false, zero: false }
}
///|
pub struct FormatSpec {
options : Options
width : Int?
grouping : Grouping
precision : Int?
typ : SpecType
} derive(Show)
///|
impl Default for FormatSpec with default() {
return {
options: Options::default(),
width: None,
grouping: Grouping::Default,
precision: None,
typ: SpecType::Default,
}
}
///|
priv enum FormatPart {
LocField(loc~ : Int, spec~ : FormatSpec)
Text(text~ : String)
} derive(Show)
///|
fn match_align_char(c : Char) -> Align {
match c {
'<' => Align::Left
'>' => Align::Right
'^' => Align::Center
_ => Align::Left
}
}
///|
fn is_align_char(c : Char) -> Bool {
return ['<', '>', '^'].contains(c)
}
///|
fn parse_fill_align(input : str) -> (Char?, Align, str) {
match input {
[any, align_c, .. input] if is_align_char(align_c) =>
(Some(any), match_align_char(align_c), input)
[align_c, .. chars] if is_align_char(align_c) =>
(None, match_align_char(align_c), chars)
[..] => (None, Left, input)
}
}
///|
fn parse_options(input : str) -> (Options, str) {
let (fill, align, input) = parse_fill_align(input)
match input {
['#', .. chars] => ({ fill, align, sharp: true, zero: true }, chars)
['0', .. chars] => ({ fill, align, sharp: false, zero: true }, chars)
[..] => ({ fill, align, sharp: false, zero: false }, input)
}
}
///|
fn parse_digit(input : str) -> (Int?, str) {
let mut result = 0
let mut idx = 0
for c in input {
if not(c.is_digit(10)) {
break
}
result = result * 10 + (Char::to_int(c) - '0')
idx += 1
}
if idx == 0 {
(None, input)
} else {
(Some(result), input.charcodes(start=idx))
}
}
///|
fn parse_grouping(input : str) -> (Grouping?, str) {
match input {
[',', .. chars] => return (Some(Grouping::Comma), chars)
['_', .. chars] => return (Some(Grouping::Underscore), chars)
[..] => return (None, input)
}
}
///|
fn parse_precision(input : str) -> (Int?, str) {
match input {
['.', .. input] => {
let (prec, rest) = parse_digit(input)
(prec, rest)
}
_ => (None, input)
}
}
///|
fn parse_type(input : str) -> (SpecType, str) {
match input {
['b', .. input] => (Binary, input)
['d', .. input] => (Digit, input)
['e', .. input] => (ExponentLower, input)
['E', .. input] => (ExponentUpper, input)
['f', .. input] => (Float, input)
['o', .. input] => (Octal, input)
['x', .. input] => (HexLower, input)
['X', .. input] => (HexUpper, input)
['%', .. input] => (Percent, input)
_ => (Default, input)
}
}
///|
fn parse_spec(input : str) -> (FormatSpec, str) {
// 1. options
let (options, rest1) = parse_options(input)
// 2. width
let (width, rest2) = parse_digit(rest1)
// 3. grouping
let (grouping, rest3) = parse_grouping(rest2)
// 4. precision
let (precision, rest4) = parse_precision(rest3)
// 5. type
let (typ, rest5) = parse_type(rest4)
let spec = { options, width, grouping: grouping.or_default(), precision, typ }
(spec, rest5)
}
///|
fn parse_format_part(input : str, last~ : Int) -> (FormatPart, str) {
let (loc, input) = parse_digit(input)
let loc = loc.or(last)
let default = LocField(loc~, spec=FormatSpec::default())
match input {
[':', .. input] => {
let (spec, input) = parse_spec(input)
(LocField(loc~, spec~), input)
}
[..] => (default, input)
}
}
///|
fn parse_text(input : str) -> (String, str) {
let mut idx = 0
let buf = StringBuilder::new()
for char in input {
if char == '{' || char == '}' {
break
}
buf.write_char(char)
idx += 1
}
(buf.to_string(), input.charcodes(start=idx))
}
///|
fn parse_braces(input : str) -> (Char?, str) {
if input.has_prefix("{{") {
(Some('{'), input.charcodes(start=2))
} else if input.has_prefix("}}") {
(Some('}'), input.charcodes(start=2))
} else {
(None, input)
}
}
///|
fn parse_format_string(input : String) -> Array[FormatPart] {
let parts = Array::new()
let mut last = 0
let mut chars = input[:]
let buf = StringBuilder::new()
while not(chars.is_empty()) {
let (text, new_chars) = parse_text(chars)
buf.write_string(text)
let (c, new_chars) = parse_braces(new_chars)
chars = new_chars
if c is Some(v) {
buf.write_char(v)
}
if not(buf.is_empty()) {
parts.push(Text(text=buf.to_string()))
buf.reset()
}
match new_chars {
['{', .. new_chars] => {
let (spec, new_chars) = parse_format_part(new_chars, last~)
chars = new_chars.charcodes(start=1) // Skip the '}'
last += 1
parts.push(spec)
}
['}', ..new_chars] => {
if new_chars.has_prefix("}") {
// This is a literal '}' in the format string
buf.write_char('}')
parts.push(Text(text=buf.to_string()))
chars = new_chars.charcodes(start=1) // Skip the '}'
} else {
// If we reach here, it means we have an unmatched '}'
abort("Unmatched '}' in format string")
}
}
[..] => continue
}
}
parts
}
///|
test "parse_spec_precision" {
let input = ".2f"
let (spec, _) = match parse_spec(input[:]) {
result => result
}
inspect(
spec,
content="{options: {fill: None, align: Left, sharp: false, zero: false}, width: None, grouping: Default, precision: Some(2), typ: Float}",
)
}
///|
test "parse_spec_width" {
let input = "10d"
let (spec, _) = match parse_spec(input[:]) {
result => result
}
inspect(
spec,
content="{options: {fill: None, align: Left, sharp: false, zero: false}, width: Some(10), grouping: Default, precision: None, typ: Digit}",
)
}
///|
test "parse_format_part" {
let input = "3:10d"
let (part, rest) = match parse_format_part(input[:], last=0) {
result => result
}
inspect(
part,
content="LocField(loc=3, spec={options: {fill: None, align: Left, sharp: false, zero: false}, width: Some(10), grouping: Default, precision: None, typ: Digit})",
)
inspect(rest, content="")
}
///|
test "parse_fmt_string" {
let input = "{:10d} {0:10d} {1:10d} {{}}"
let result = match parse_format_string(input) {
parts => parts
}
inspect(
result,
content=
#|[LocField(loc=0, spec={options: {fill: None, align: Left, sharp: false, zero: false}, width: Some(10), grouping: Default, precision: None, typ: Digit}), Text(text=" "), LocField(loc=0, spec={options: {fill: None, align: Left, sharp: false, zero: false}, width: Some(10), grouping: Default, precision: None, typ: Digit}), Text(text=" "), LocField(loc=1, spec={options: {fill: None, align: Left, sharp: false, zero: false}, width: Some(10), grouping: Default, precision: None, typ: Digit}), Text(text=" {"), Text(text="}")]
,
)
}
///|
test "parse_pure_braces" {
let input = "{{}}"
let result = match parse_format_string(input) {
parts => parts
}
inspect(
result,
content=
#|[Text(text="{"), Text(text="}")]
,
)
}