// import_data.mbt — data import helpers (v0.42.1).
//
// Provides string → numeric parsers and CSV loaders. Complements
// `image.mbt` (BMP/QOI/TGA/PNG/GIF/JPEG/ICO/TIFF via
// riantr/moonbit_image) by handling the structured-data formats.
//
// API:
// parse_int(s) → Int (returns 0 on parse failure)
// parse_float(s) → Float (returns 0.0F on parse failure;
// supports leading sign + decimal +
// optional 'e'/'E' exponent)
// parse_csv_row(line) → Array[Float] (single CSV row)
// parse_csv(text) → Array[Array[Float]] (multi-row CSV)
// parse_int_list(text) → Array[Int] (single line, comma-separated)
//
// CSV format: rows separated by '\n' (also handles '\r\n'), columns
// by ','. Whitespace within fields is stripped. No header support —
// caller strips headers if needed.
///|
/// Parse a decimal string to Int. Returns 0 on parse failure.
pub fn parse_int(s : String) -> Int {
let n = s.length()
if n == 0 {
return 0
}
let mut i = 0
let mut sign = 1
if s[0] == '-' {
sign = -1
i = 1
} else if s[0] == '+' {
i = 1
}
let mut acc : Int = 0
let mut found_digit = false
while i < n {
let c = s[i]
if c >= '0' && c <= '9' {
acc = acc * 10 + (c.to_int() - '0'.to_int())
found_digit = true
i = i + 1
} else {
break
}
}
if !found_digit {
return 0
}
sign * acc
}
///|
/// Parse a decimal string to Float. Handles leading sign, integer part,
/// decimal part, and optional 'e'/'E' exponent. Returns 0.0F on
/// parse failure or empty input.
pub fn parse_float(s : String) -> Float {
let n = s.length()
if n == 0 {
return 0.0F
}
let mut i = 0
let mut sign : Float = 1.0F
if s[0] == '-' {
sign = -1.0F
i = 1
} else if s[0] == '+' {
i = 1
}
let mut acc : Float = 0.0F
let mut found_digit = false
// Integer part.
while i < n {
let c = s[i]
if c >= '0' && c <= '9' {
acc = acc * 10.0F + Float::from_int(c.to_int() - '0'.to_int())
found_digit = true
i = i + 1
} else {
break
}
}
// Decimal part.
if i < n && s[i] == '.' {
i = i + 1
let mut frac : Float = 0.1F
while i < n {
let c = s[i]
if c >= '0' && c <= '9' {
acc = acc + Float::from_int(c.to_int() - '0'.to_int()) * frac
frac = frac * 0.1F
found_digit = true
i = i + 1
} else {
break
}
}
}
if !found_digit {
return 0.0F
}
// Exponent.
if i < n && (s[i] == 'e' || s[i] == 'E') {
i = i + 1
let mut exp_sign : Int = 1
if i < n && s[i] == '-' {
exp_sign = -1
i = i + 1
} else if i < n && s[i] == '+' {
i = i + 1
}
let mut exp : Int = 0
while i < n {
let c = s[i]
if c >= '0' && c <= '9' {
exp = exp * 10 + (c.to_int() - '0'.to_int())
i = i + 1
} else {
break
}
}
let factor : Float = if exp_sign == 1 {
pow10_int(exp)
} else {
1.0F / pow10_int(exp)
}
acc = acc * factor
}
sign * acc
}
///|
/// Helper: 10^n as Float. Only handles small n.
fn pow10_int(n : Int) -> Float {
let mut result = 1.0F
let mut k = 0
while k < n {
result = result * 10.0F
k = k + 1
}
result
}
///|
/// Parse a single CSV row into Array[Float]. Splits on ',', trims
/// whitespace, parses each field via `parse_float`.
pub fn parse_csv_row(line : String) -> Array[Float] {
let fields = split_csv_line(line)
let out : Array[Float] = Array::make(fields.length(), 0.0F)
for i in 0.. Array[String] {
let out : Array[String] = []
let buf = StringBuilder::new()
let n = line.length()
let mut i = 0
while i < n {
let c = line[i]
if c == ',' {
out.push(buf.to_string())
buf.reset()
} else if c != ' ' && c != '\t' {
// Skip whitespace; only append non-whitespace.
buf.write_char(c.unsafe_to_char())
}
i = i + 1
}
out.push(buf.to_string())
out
}
///|
/// Parse multi-line CSV text into 2D Float array. Splits on '\n',
/// then each line via `parse_csv_row`. Handles '\r\n' (Windows).
pub fn parse_csv(text : String) -> Array[Array[Float]] {
let out : Array[Array[Float]] = []
let n = text.length()
let buf = StringBuilder::new()
let mut i = 0
while i < n {
let c = text[i]
if c == '\n' {
let line = buf.to_string()
buf.reset()
if line.length() > 0 {
out.push(parse_csv_row(line))
}
} else if c != '\r' {
buf.write_char(c.unsafe_to_char())
}
i = i + 1
}
// Trailing line (no newline at end).
let trailing = buf.to_string()
if trailing.length() > 0 {
out.push(parse_csv_row(trailing))
}
out
}
///|
/// Parse a comma-separated Int list (single line).
pub fn parse_int_list(text : String) -> Array[Int] {
let fields = split_csv_line(text)
let out : Array[Int] = Array::make(fields.length(), 0)
for i in 0..