use crate::errors::{error, nil, New};
use crate::types::{float64, int, int64, string};
#[allow(non_snake_case)]
pub fn ErrSyntax() -> error { New("invalid syntax") }
#[allow(non_snake_case)]
pub fn ErrRange() -> error { New("value out of range") }
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NumError {
pub Func: string,
pub Num: string,
pub Err: error,
}
impl NumError {
pub fn new(func: &str, num: &str, err: error) -> NumError {
NumError { Func: func.into(), Num: num.into(), Err: err }
}
#[allow(non_snake_case)]
pub fn Error(&self) -> string {
format!("strconv.{}: parsing {:?}: {}", self.Func, self.Num, self.Err).into()
}
#[allow(non_snake_case)]
pub fn Unwrap(&self) -> error { self.Err.clone() }
}
impl std::fmt::Display for NumError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.Error())
}
}
fn num_error(func: &str, s: &str, sentinel: error) -> error {
New(&format!("strconv.{}: parsing {:?}: {}", func, s, sentinel))
}
fn syntax_err(fn_name: &str, s: &str) -> error { num_error(fn_name, s, ErrSyntax()) }
#[allow(dead_code)] fn range_err(fn_name: &str, s: &str) -> error { num_error(fn_name, s, ErrRange()) }
pub const IntSize: int = 64;
pub fn Atoi(s: impl AsRef<str>) -> (int, error) {
let s = s.as_ref();
let (n, err) = ParseInt(s, 10, 0);
if err != nil {
let msg = format!("{}", err).replace("strconv.ParseInt", "strconv.Atoi");
return (n, New(&msg));
}
(n, nil)
}
pub fn Itoa(n: int) -> string {
n.to_string().into()
}
#[allow(non_snake_case)]
pub fn ParseUint(s: impl AsRef<str>, base: int, bit_size: int) -> (u64, error) {
let s_raw = s.as_ref();
let s0 = s_raw;
const FN: &str = "ParseUint";
if s_raw.is_empty() {
return (0, num_error(FN, s0, ErrSyntax()));
}
let base0 = base == 0;
let (mut base_u, body) = match base {
b if (2..=36).contains(&b) => (b as u32, s_raw),
0 => {
let bs = s_raw.as_bytes();
if bs[0] == b'0' {
if s_raw.len() >= 3 {
let c = bs[1].to_ascii_lowercase();
if c == b'b' { (2, &s_raw[2..]) }
else if c == b'o' { (8, &s_raw[2..]) }
else if c == b'x' { (16, &s_raw[2..]) }
else { (8, &s_raw[1..]) }
} else {
(8, &s_raw[1..])
}
} else {
(10, s_raw)
}
}
_ => return (0, New(&format!("strconv.{}: parsing {:?}: invalid base {}", FN, s0, base))),
};
let _ = &mut base_u;
let bit_size_effective: u32 = if bit_size == 0 { IntSize as u32 }
else if bit_size < 0 || bit_size > 64 {
return (0, New(&format!("strconv.{}: parsing {:?}: invalid bit size {}", FN, s0, bit_size)));
}
else { bit_size as u32 };
let max_val: u64 = if bit_size_effective == 64 { u64::MAX }
else { (1u64 << bit_size_effective) - 1 };
let cutoff: u64 = u64::MAX / base_u as u64 + 1;
let mut n: u64 = 0;
let mut underscores = false;
for c in body.as_bytes() {
let d: u8;
let c = *c;
if c == b'_' && base0 {
underscores = true;
continue;
} else if (b'0'..=b'9').contains(&c) {
d = c - b'0';
} else if (b'a'..=b'z').contains(&c.to_ascii_lowercase()) {
d = c.to_ascii_lowercase() - b'a' + 10;
} else {
return (0, num_error(FN, s0, ErrSyntax()));
}
if d as u32 >= base_u {
return (0, num_error(FN, s0, ErrSyntax()));
}
if n >= cutoff {
return (max_val, num_error(FN, s0, ErrRange()));
}
n = n.wrapping_mul(base_u as u64);
let (n1, wrapped) = n.overflowing_add(d as u64);
if wrapped || n1 > max_val {
return (max_val, num_error(FN, s0, ErrRange()));
}
n = n1;
}
if underscores && !underscore_ok(s0) {
return (0, num_error(FN, s0, ErrSyntax()));
}
(n, nil)
}
pub fn ParseInt(s: impl AsRef<str>, base: int, bit_size: int) -> (int64, error) {
let s_raw = s.as_ref();
let s0 = s_raw;
const FN: &str = "ParseInt";
if s_raw.is_empty() {
return (0, num_error(FN, s0, ErrSyntax()));
}
let bs = s_raw.as_bytes();
let (neg, body) = match bs[0] {
b'+' => (false, &s_raw[1..]),
b'-' => (true, &s_raw[1..]),
_ => (false, s_raw),
};
let (un, err) = ParseUint(body, base, bit_size);
if err != nil && !format!("{}", err).contains("value out of range") {
let msg = format!("strconv.ParseInt: parsing {:?}: invalid syntax", s0);
if format!("{}", err).contains("invalid base") || format!("{}", err).contains("invalid bit size") {
let rebuilt = format!("{}", err).replace("strconv.ParseUint", "strconv.ParseInt")
.replacen(&format!("{:?}", body), &format!("{:?}", s0), 1);
return (0, New(&rebuilt));
}
return (0, New(&msg));
}
let bit_size_eff = if bit_size == 0 { IntSize as u32 } else { bit_size as u32 };
let cutoff: u64 = 1u64 << (bit_size_eff - 1);
let sat_max: i64 = (cutoff - 1) as i64;
let sat_min: i64 = if bit_size_eff == 64 { i64::MIN } else { -((cutoff as i64)) };
if !neg && un >= cutoff {
return (sat_max, num_error(FN, s0, ErrRange()));
}
if neg && un > cutoff {
return (sat_min, num_error(FN, s0, ErrRange()));
}
if err != nil {
let msg = format!("strconv.ParseInt: parsing {:?}: value out of range", s0);
let sat = if neg { sat_min } else { sat_max };
return (sat, New(&msg));
}
let n: i64 = if neg {
if un == cutoff { sat_min } else { -(un as i64) }
} else {
un as i64
};
(n, nil)
}
fn underscore_ok(s: &str) -> bool {
let mut saw: char = '^';
let bs = s.as_bytes();
let mut i = 0usize;
if !bs.is_empty() && (bs[0] == b'+' || bs[0] == b'-') {
let rest = &s[1..];
return underscore_ok(rest);
}
let mut hex = false;
if bs.len() >= 2 && bs[0] == b'0' {
let c1 = bs[1].to_ascii_lowercase();
if c1 == b'b' || c1 == b'o' || c1 == b'x' {
i = 2;
saw = '0';
hex = c1 == b'x';
}
}
while i < bs.len() {
let c = bs[i];
if (b'0'..=b'9').contains(&c) || (hex && (b'a'..=b'f').contains(&c.to_ascii_lowercase())) {
saw = '0';
i += 1;
continue;
}
if c == b'_' {
if saw != '0' { return false; }
saw = '_';
i += 1;
continue;
}
if saw == '_' { return false; }
saw = '!';
i += 1;
}
saw != '_'
}
pub fn ParseFloat(s: impl AsRef<str>, _bit_size: int) -> (float64, error) {
let s = s.as_ref();
match s.parse::<f64>() {
Ok(n) => (n, nil),
Err(_) => (0.0, syntax_err("ParseFloat", s)),
}
}
pub fn ParseBool(s: impl AsRef<str>) -> (bool, error) {
let s = s.as_ref();
match s {
"1" | "t" | "T" | "TRUE" | "true" | "True" => (true, nil),
"0" | "f" | "F" | "FALSE" | "false" | "False" => (false, nil),
_ => (false, syntax_err("ParseBool", s)),
}
}
pub fn FormatInt(n: int64, base: int) -> string {
if !(2..=36).contains(&base) {
panic!("strconv: illegal number base {}", base);
}
let (neg, mut nn) = if n < 0 {
(true, (n as i128).unsigned_abs())
} else {
(false, n as u128)
};
if nn == 0 {
return "0".into();
}
let mut s = String::new();
let base_u = base as u128;
while nn > 0 {
let d = (nn % base_u) as u32;
s.insert(0, std::char::from_digit(d, base as u32).unwrap_or('?'));
nn /= base_u;
}
if neg {
s.insert(0, '-');
}
s.into()
}
pub fn FormatUint(n: u64, base: int) -> string {
if !(2..=36).contains(&base) {
panic!("strconv: illegal number base {}", base);
}
if n == 0 {
return "0".into();
}
let mut s = String::new();
let mut nn = n as u128;
let base_u = base as u128;
while nn > 0 {
let d = (nn % base_u) as u32;
s.insert(0, std::char::from_digit(d, base as u32).unwrap_or('?'));
nn /= base_u;
}
s.into()
}
#[allow(non_snake_case)]
pub fn AppendInt(mut dst: Vec<crate::types::byte>, n: int64, base: int) -> Vec<crate::types::byte> {
dst.extend_from_slice(FormatInt(n, base).as_bytes());
dst
}
#[allow(non_snake_case)]
pub fn AppendUint(mut dst: Vec<crate::types::byte>, n: u64, base: int) -> Vec<crate::types::byte> {
dst.extend_from_slice(FormatUint(n, base).as_bytes());
dst
}
#[allow(non_snake_case)]
pub fn FormatFloat(f: float64, fmt: u8, prec: int, bit_size: int) -> string {
let v: f64 = if bit_size == 32 { f as f32 as f64 } else { f };
if v.is_nan() { return "NaN".into(); }
if v.is_infinite() { return if v < 0.0 { "-Inf".into() } else { "+Inf".into() }; }
match fmt {
b'e' | b'E' => format_e(v, prec, fmt == b'E', bit_size),
b'f' => format_f(v, prec, bit_size),
b'g' | b'G' => format_g(v, prec, fmt == b'G', bit_size),
b'b' => format_b(v, bit_size),
b'x' | b'X' => format_x(v, prec, fmt == b'X', bit_size),
_ => format!("%{}", fmt as char).into(),
}
}
#[allow(non_snake_case)]
pub fn AppendFloat(mut dst: Vec<crate::types::byte>, f: float64, fmtc: u8, prec: int, bit_size: int) -> Vec<crate::types::byte> {
dst.extend_from_slice(FormatFloat(f, fmtc, prec, bit_size).as_bytes());
dst
}
fn shortest_f64_str(v: f64, bit_size: int) -> string {
let s = if bit_size == 32 {
format!("{}", v as f32)
} else {
format!("{}", v)
};
s.into()
}
fn format_e(v: f64, prec: int, upper: bool, bit_size: int) -> string {
if prec < 0 {
let base = shortest_f64_str(v, bit_size);
return canonical_exponent(&base, 0, true, upper);
}
let p = prec as usize;
if v == 0.0 {
let tail: String = std::iter::repeat('0').take(p).collect();
let sep = if p > 0 { "." } else { "" };
let marker = if upper { 'E' } else { 'e' };
let sign = if v.is_sign_negative() { "-" } else { "" };
return format!("{}0{}{}{}+00", sign, sep, tail, marker).into();
}
let neg = v.is_sign_negative();
let abs = v.abs();
let exp10 = abs.log10().floor() as i32;
let mut mantissa = abs / 10f64.powi(exp10);
let scale = 10f64.powi(p as i32);
mantissa = (mantissa * scale + 0.5).floor() / scale;
let (mantissa, exp10) = if mantissa >= 10.0 { (mantissa / 10.0, exp10 + 1) } else { (mantissa, exp10) };
let marker = if upper { 'E' } else { 'e' };
let sign_m = if neg { "-" } else { "" };
let e_sign = if exp10 >= 0 { '+' } else { '-' };
let padded = format!("{:02}", exp10.abs());
format!("{}{:.*}{}{}{}", sign_m, p, mantissa, marker, e_sign, padded).into()
}
fn format_f(v: f64, prec: int, _bit_size: int) -> string {
if prec < 0 {
let base = shortest_f64_str(v, _bit_size);
if base.contains('e') || base.contains('E') {
return format!("{:.*}", 20, v).trim_end_matches('0').trim_end_matches('.').into();
}
return base;
}
format!("{:.*}", prec as usize, v).into()
}
fn format_g(v: f64, prec: int, upper: bool, bit_size: int) -> string {
if prec < 0 {
if v == 0.0 { return "0".into(); }
let abs = v.abs();
let expn = abs.log10().floor() as i32;
let shortest = shortest_f64_str(v, bit_size);
if expn < -4 || expn >= 6 {
if shortest.contains('e') || shortest.contains('E') {
return rust_to_go_shortest(&shortest, upper);
}
return canonical_exponent(&shortest, 0, true, upper);
} else {
if shortest.contains('e') || shortest.contains('E') {
let n_digits = (expn + 1).max(1) as usize;
return format!("{:.*}", n_digits.saturating_sub((expn + 1) as usize), v).into();
}
return shortest;
}
}
let p = if prec == 0 { 1 } else { prec as i32 };
if v == 0.0 {
return "0".into();
}
let abs = v.abs();
let expn = abs.log10().floor() as i32;
if expn < -4 || expn >= p {
let e_prec = (p - 1).max(0);
let raw = format_e(v, e_prec as i64, upper, 64);
trim_g_trailing_zeros(&raw, upper)
} else {
let f_prec = (p - 1 - expn).max(0) as usize;
let scale = 10f64.powi(f_prec as i32);
let rounded = if v.is_sign_negative() {
((v * scale) - 0.5).ceil() / scale
} else {
((v * scale) + 0.5).floor() / scale
};
let raw = format!("{:.*}", f_prec, rounded);
trim_g_trailing_zeros(&raw, upper)
}
}
fn format_b(v: f64, _bit_size: int) -> string {
let bits = v.to_bits();
let sign = bits >> 63;
let mut exp = ((bits >> 52) & 0x7FF) as i32;
let mut mant = bits & 0xF_FFFF_FFFF_FFFF;
if exp == 0 {
if mant == 0 { return if sign == 1 { "-0p-1074".into() } else { "0p-1074".into() }; }
exp = 1;
} else {
mant |= 1u64 << 52;
}
let adjusted_exp = exp - 1023 - 52;
let sign_s = if sign == 1 { "-" } else { "" };
if adjusted_exp >= 0 {
format!("{}{}p+{}", sign_s, mant, adjusted_exp).into()
} else {
format!("{}{}p-{}", sign_s, mant, -adjusted_exp).into()
}
}
fn format_x(v: f64, prec: int, upper: bool, _bit_size: int) -> string {
if v == 0.0 {
let p = if prec < 0 { 0 } else { prec as usize };
let zeros: String = std::iter::repeat('0').take(p).collect();
let dot = if p > 0 { "." } else { "" };
let sign_s = if v.is_sign_negative() { "-" } else { "" };
let prefix = if upper { "0X0" } else { "0x0" };
let p_ex = if upper { "P+00" } else { "p+00" };
return format!("{}{}{}{}{}", sign_s, prefix, dot, zeros, p_ex).into();
}
let bits = v.to_bits();
let sign = bits >> 63;
let mut exp = ((bits >> 52) & 0x7FF) as i32;
let mut mant = bits & 0xF_FFFF_FFFF_FFFF;
if exp == 0 {
exp = 1;
} else {
mant |= 1u64 << 52;
}
let e = exp - 1023;
let mant_hex = if prec < 0 {
let frac = mant & 0xF_FFFF_FFFF_FFFF;
let mut h = format!("{:013x}", frac);
while h.ends_with('0') { h.pop(); }
if h.is_empty() { String::new() } else { format!(".{}", h) }
} else if prec == 0 {
String::new()
} else {
let shift = 52i32 - 4 * (prec as i32);
let frac_full = mant & 0xF_FFFF_FFFF_FFFF;
let frac = if shift >= 0 {
frac_full >> shift
} else {
frac_full << (-shift)
};
format!(".{:0width$x}", frac, width = prec as usize)
};
let sign_s = if sign == 1 { "-" } else { "" };
let prefix = if upper { "0X1" } else { "0x1" };
let mant_hex = if upper { mant_hex.to_uppercase() } else { mant_hex };
let p_letter = if upper { 'P' } else { 'p' };
let e_sign = if e >= 0 { '+' } else { '-' };
format!("{}{}{}{}{}{:02}", sign_s, prefix, mant_hex, p_letter, e_sign, e.abs()).into()
}
fn go_exponent(s: &str, upper: bool) -> string {
let marker = if upper { 'E' } else { 'e' };
if let Some(pos) = s.find(marker) {
let (mant, exp) = s.split_at(pos);
let mut exp = &exp[1..];
let (sign, digits) = if let Some(rest) = exp.strip_prefix('-') {
("-", rest)
} else if let Some(rest) = exp.strip_prefix('+') {
("+", rest)
} else {
("+", exp)
};
let _ = &mut exp;
let padded = if digits.len() < 2 { format!("0{}", digits) } else { digits.into() };
return format!("{}{}{}{}", mant, marker, sign, padded).into();
}
s.into()
}
fn canonical_exponent(s: &str, _prec: usize, _use_e: bool, upper: bool) -> string {
let marker = if upper { 'E' } else { 'e' };
let lower = if upper { s.to_uppercase() } else { s.into() };
if lower.contains(marker) || lower.contains(if upper { 'e' } else { 'E' }) {
return go_exponent(&lower.replace(if upper { 'e' } else { 'E' }, &marker.to_string()), upper);
}
let (mantissa, neg) = if let Some(rest) = lower.strip_prefix('-') { (rest.into(), true) } else { (lower.clone(), false) };
let (int_part, frac_part) = match mantissa.split_once('.') {
Some((i, f)) => (i.into(), f.into()),
None => (mantissa.clone(), String::new()),
};
let all_digits = format!("{}{}", int_part, frac_part);
let first_nonzero = all_digits.find(|c: char| c != '0');
let exp_val: i32;
let digits_trimmed: String;
match first_nonzero {
None => return lower.into(), Some(idx) => {
exp_val = int_part.len() as i32 - 1 - idx as i32;
digits_trimmed = all_digits[idx..].trim_end_matches('0').into();
}
}
let (lead, tail): (std::string::String, std::string::String) =
if digits_trimmed.is_empty() { ("0".into(), std::string::String::new()) }
else { (digits_trimmed[..1].into(), digits_trimmed[1..].into()) };
let sign = if exp_val >= 0 { '+' } else { '-' };
let sign_prefix = if neg { "-" } else { "" };
if tail.is_empty() {
format!("{}{}{}{}{:02}", sign_prefix, lead, marker, sign, exp_val.abs()).into()
} else {
format!("{}{}.{}{}{}{:02}", sign_prefix, lead, tail, marker, sign, exp_val.abs()).into()
}
}
fn rust_to_go_shortest(s: &str, upper: bool) -> string {
if s.contains('e') || s.contains('E') {
let normalized = if upper { s.to_uppercase() } else { s.into() };
return go_exponent(&normalized, upper);
}
s.into()
}
fn trim_g_trailing_zeros(s: &str, upper: bool) -> string {
let marker = if upper { 'E' } else { 'e' };
if let Some(pos) = s.find(marker) {
let mant = &s[..pos];
let exp = &s[pos..];
let trimmed = trim_g_mantissa(mant);
return format!("{}{}", trimmed, exp).into();
}
trim_g_mantissa(s)
}
fn trim_g_mantissa(m: &str) -> string {
if !m.contains('.') { return m.into(); }
let t = m.trim_end_matches('0');
let t = t.trim_end_matches('.');
t.into()
}
pub fn FormatBool(b: bool) -> string {
if b { "true".into() } else { "false".into() }
}
#[allow(non_snake_case)]
pub fn AppendBool(mut dst: Vec<crate::types::byte>, b: bool) -> Vec<crate::types::byte> {
dst.extend_from_slice(if b { b"true" } else { b"false" });
dst
}
pub fn Quote(s: impl AsRef<str>) -> string {
quote_with(s.as_ref(), '"', false, false)
}
#[allow(non_snake_case)]
pub fn QuoteToASCII(s: impl AsRef<str>) -> string {
quote_with(s.as_ref(), '"', true, false)
}
#[allow(non_snake_case)]
pub fn QuoteToGraphic(s: impl AsRef<str>) -> string {
quote_with(s.as_ref(), '"', false, true)
}
#[allow(non_snake_case)]
pub fn AppendQuote(mut dst: Vec<crate::types::byte>, s: impl AsRef<str>) -> Vec<crate::types::byte> {
dst.extend_from_slice(Quote(s).as_bytes());
dst
}
#[allow(non_snake_case)]
pub fn AppendQuoteToASCII(mut dst: Vec<crate::types::byte>, s: impl AsRef<str>) -> Vec<crate::types::byte> {
dst.extend_from_slice(QuoteToASCII(s).as_bytes());
dst
}
#[allow(non_snake_case)]
pub fn AppendQuoteToGraphic(mut dst: Vec<crate::types::byte>, s: impl AsRef<str>) -> Vec<crate::types::byte> {
dst.extend_from_slice(QuoteToGraphic(s).as_bytes());
dst
}
#[allow(non_snake_case)]
pub fn QuoteRune(r: crate::types::rune) -> string {
quote_rune_with(r, false, false)
}
#[allow(non_snake_case)]
pub fn QuoteRuneToASCII(r: crate::types::rune) -> string {
quote_rune_with(r, true, false)
}
#[allow(non_snake_case)]
pub fn QuoteRuneToGraphic(r: crate::types::rune) -> string {
quote_rune_with(r, false, true)
}
#[allow(non_snake_case)]
pub fn AppendQuoteRune(mut dst: Vec<crate::types::byte>, r: crate::types::rune) -> Vec<crate::types::byte> {
dst.extend_from_slice(QuoteRune(r).as_bytes());
dst
}
#[allow(non_snake_case)]
pub fn AppendQuoteRuneToASCII(mut dst: Vec<crate::types::byte>, r: crate::types::rune) -> Vec<crate::types::byte> {
dst.extend_from_slice(QuoteRuneToASCII(r).as_bytes());
dst
}
#[allow(non_snake_case)]
pub fn AppendQuoteRuneToGraphic(mut dst: Vec<crate::types::byte>, r: crate::types::rune) -> Vec<crate::types::byte> {
dst.extend_from_slice(QuoteRuneToGraphic(r).as_bytes());
dst
}
#[allow(non_snake_case)]
pub fn IsPrint(r: crate::types::rune) -> bool {
if r < 0 || r > 0x0010_FFFF { return false; }
let u = r as u32;
if (0xD800..=0xDFFF).contains(&u) { return false; }
if (0xFDD0..=0xFDEF).contains(&u) { return false; }
if (u & 0xFFFE) == 0xFFFE { return false; }
if (0xE000..=0xF8FF).contains(&u) { return false; }
if (0xF0000..=0xFFFFD).contains(&u) { return false; }
if (0x100000..=0x10FFFD).contains(&u) { return false; }
crate::unicode::IsPrint(r)
}
#[allow(non_snake_case)]
pub fn IsGraphic(r: crate::types::rune) -> bool {
if IsPrint(r) { return true; }
matches!(r,
0x00A0 | 0x1680 | 0x2000..=0x200A | 0x202F | 0x205F | 0x3000
)
}
#[allow(non_snake_case)]
pub fn CanBackquote(s: impl AsRef<str>) -> bool {
let s = s.as_ref();
for r in s.chars() {
let code = r as u32;
if code < 0x20 && code != b'\t' as u32 { return false; }
if code == 0x7F { return false; }
if r == '`' { return false; }
if r == '\u{FFFD}' {
return false;
}
}
true
}
fn quote_with(s: &str, quote: char, ascii: bool, graphic: bool) -> string {
let mut out = String::with_capacity(s.len() + 2);
out.push(quote);
for r in s.chars() {
append_escaped_rune(&mut out, r, quote, ascii, graphic);
}
out.push(quote);
out.into()
}
fn quote_rune_with(r: crate::types::rune, ascii: bool, graphic: bool) -> string {
let mut out = String::with_capacity(10);
out.push('\'');
let r_eff: crate::types::rune = if r < 0 || r > 0x0010_FFFF || (0xD800..=0xDFFF).contains(&r) {
0xFFFD
} else {
r
};
append_escaped_rune(&mut out, std::char::from_u32(r_eff as u32).unwrap_or('\u{FFFD}'), '\'', ascii, graphic);
out.push('\'');
out.into()
}
fn append_escaped_rune(out: &mut String, r: char, quote: char, ascii: bool, graphic: bool) {
if r == quote || r == '\\' {
out.push('\\');
out.push(r);
return;
}
if ascii {
let code = r as u32;
if code < 0x80 {
if is_ascii_printable(r) {
out.push(r);
return;
}
}
append_escape_non_ascii(out, r);
return;
}
let code = r as u32;
if code < 0x80 && is_ascii_printable(r) {
out.push(r);
return;
}
let keep_as_is = if graphic { IsGraphic(code as i32) } else { IsPrint(code as i32) };
if keep_as_is {
out.push(r);
return;
}
append_escape_non_ascii(out, r);
}
fn is_ascii_printable(r: char) -> bool {
let c = r as u32;
c >= 0x20 && c < 0x7F
}
fn append_escape_non_ascii(out: &mut String, r: char) {
match r {
'\x07' => { out.push_str("\\a"); return; }
'\x08' => { out.push_str("\\b"); return; }
'\x0c' => { out.push_str("\\f"); return; }
'\n' => { out.push_str("\\n"); return; }
'\r' => { out.push_str("\\r"); return; }
'\t' => { out.push_str("\\t"); return; }
'\x0b' => { out.push_str("\\v"); return; }
_ => {}
}
let code = r as u32;
if code < 0x80 {
out.push_str(&format!("\\x{:02x}", code));
} else if code < 0x10000 {
out.push_str(&format!("\\u{:04x}", code));
} else {
out.push_str(&format!("\\U{:08x}", code));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn atoi_round_trip() {
let (n, err) = Atoi("42");
assert!(err == nil);
assert_eq!(n, 42);
assert_eq!(Itoa(42), "42");
let (n, err) = Atoi("-7");
assert!(err == nil);
assert_eq!(n, -7);
}
#[test]
fn atoi_invalid() {
let (n, err) = Atoi("abc");
assert_eq!(n, 0);
assert!(err != nil);
assert!(format!("{}", err).contains("invalid syntax"));
}
#[test]
fn parse_int_base_and_bitsize() {
assert_eq!(ParseInt("ff", 16, 64).0, 255);
assert_eq!(ParseInt("0xff", 0, 64).0, 255);
assert_eq!(ParseInt("0b1010", 0, 64).0, 10);
assert_eq!(ParseInt("-100", 10, 64).0, -100);
let (_, err) = ParseInt("200", 10, 8);
assert!(err != nil);
assert!(format!("{}", err).contains("out of range"));
}
#[test]
fn parse_float_basic() {
let (f, err) = ParseFloat("3.14", 64);
assert!(err == nil);
assert!((f - 3.14).abs() < 1e-9);
}
#[test]
fn parse_bool_variants() {
assert_eq!(ParseBool("true").0, true);
assert_eq!(ParseBool("T").0, true);
assert_eq!(ParseBool("1").0, true);
assert_eq!(ParseBool("FALSE").0, false);
assert_eq!(ParseBool("0").0, false);
let (_, err) = ParseBool("maybe");
assert!(err != nil);
}
#[test]
fn format_int_bases() {
assert_eq!(FormatInt(255, 10), "255");
assert_eq!(FormatInt(255, 16), "ff");
assert_eq!(FormatInt(255, 2), "11111111");
assert_eq!(FormatInt(-10, 10), "-10");
}
#[test]
fn format_bool() {
assert_eq!(FormatBool(true), "true");
assert_eq!(FormatBool(false), "false");
}
#[test]
fn quote_basic() {
assert_eq!(Quote("hi"), "\"hi\"");
}
}