///|
/// Strip the `_` separators. They are notation, not value.
fn strip_underscores(s : String) -> String {
if !s.contains_char('_') {
return s
}
let buf = StringBuilder()
for c in s {
if c != '_' {
buf.write_char(c)
}
}
buf.to_string()
}
///|
/// An integer lexeme, with optional sign and separators, as an exact integer.
fn parse_integer(lexeme : String) -> @bigint.BigInt {
let s = strip_underscores(lexeme)
let (neg, body) = split_sign(s)
let v = @bigint.BigInt::from_string(body)
if neg {
-v
} else {
v
}
}
///|
fn split_sign(s : String) -> (Bool, String) {
if s.length() == 0 {
return (false, s)
}
match s.get_char(0) {
Some('+') => (false, s.clamped_view(start=1).to_owned())
Some('-') => (true, s.clamped_view(start=1).to_owned())
_ => (false, s)
}
}
///|
/// A number lexeme as the value it denotes.
///
/// Exactness is preserved the way Racket preserves it: an integer, in any
/// radix, is exact; anything with a `.` or an exponent is a flonum. Turning
/// `123456789012345678901234567890` into a `Double` would silently lose digits
/// the reference keeps.
fn parse_number(lexeme : String) -> @sexp.Datum {
let s = strip_underscores(lexeme)
let (neg, body) = split_sign(s)
let radix = if body.has_prefix("0x") {
16
} else if body.has_prefix("0o") {
8
} else if body.has_prefix("0b") {
2
} else {
10
}
if radix != 10 {
let digits = body.clamped_view(start=2).to_owned()
let v = @bigint.BigInt::from_string(digits, radix~)
return Int_(if neg { -v } else { v })
}
if body.contains_char('.') ||
body.contains_char('e') ||
body.contains_char('E') {
let d = parse_double(body)
return Flo(if neg { -d } else { d })
}
let v = @bigint.BigInt::from_string(body)
Int_(if neg { -v } else { v })
}
///|
/// A decimal-integer lexeme read as a flonum — the `1.` case, where the
/// trailing dot has already been decided but not yet appended.
fn parse_decimal_as_double(lexeme : String) -> Double {
let s = strip_underscores(lexeme)
let (neg, body) = split_sign(s)
let d = parse_double(body)
if neg {
-d
} else {
d
}
}
///|
/// Read an unsigned decimal, possibly with a fraction and an exponent.
///
/// Hand-rolled rather than `@strconv.parse_double`, because the lexemes that
/// reach here include forms that a general parser may reject or accept
/// differently — a bare `1.`, a `.5`, an `E` exponent — and the answer has to
/// be the one Racket's reader gives. Falls back on the standard parser for the
/// shapes it handles identically.
fn parse_double(body : String) -> Double {
// Normalise `1.` to `1.0` and `.5` to `0.5`; both are what Racket reads them
// as, and both are shapes a strict parser may refuse.
let buf = StringBuilder()
if body.has_prefix(".") {
buf.write_char('0')
}
for i in 0.. {
buf.write_char(ch)
if ch == '.' && (i + 1 == body.length() || is_exponent_at(body, i + 1)) {
buf.write_char('0')
}
}
None => ()
}
}
let text = buf.to_string()
// Unreachable for a lexeme the scanner accepted; answering with a NaN rather
// than raising keeps a malformed literal from taking down a parse that would
// otherwise report it properly.
@string.parse_double(text) catch {
_ => @double.not_a_number
}
}
///|
fn is_exponent_at(s : String, i : Int) -> Bool {
match s.get_char(i) {
Some('e') | Some('E') => true
_ => false
}
}