///|
fn integer_token(text : String) -> Bool {
let characters = text.iter().collect()
let start = if characters.length() > 0 &&
(characters[0] == '-' || characters[0] == '+') {
1
} else {
0
}
if start == characters.length() {
return false
}
for i = start; i < characters.length(); i = i + 1 {
if characters[i] < '0' || characters[i] > '9' {
return false
}
}
true
}
///|
fn bigint(text : String) -> @bigint.BigInt {
@bigint.BigInt::from_string(
if text.has_prefix("+") {
text[1:].to_owned()
} else {
text
},
)
}
///|
pub fn rational(
numerator : @bigint.BigInt,
denominator : @bigint.BigInt,
) -> Number raise SchemeError {
if denominator.is_zero() {
raise Invalid("Division by zero")
}
let mut a = if numerator < @bigint.BigInt::from_int(0) {
-numerator
} else {
numerator
}
let mut b = if denominator < @bigint.BigInt::from_int(0) {
-denominator
} else {
denominator
}
while !b.is_zero() {
let remainder = a % b
a = b
b = remainder
}
let divisor = if denominator < @bigint.BigInt::from_int(0) { -a } else { a }
let n = numerator / divisor
let d = denominator / divisor
if d == @bigint.BigInt::from_int(1) {
Integer(n)
} else {
Rational(n, d)
}
}
///|
pub fn parse_number(text : String) -> Number? raise SchemeError {
if integer_token(text) {
return Some(Integer(bigint(text)))
}
let parts = text.split("/").collect()
if parts.length() == 2 &&
integer_token(parts[0].to_owned()) &&
integer_token(parts[1].to_owned()) {
return Some(
rational(bigint(parts[0].to_owned()), bigint(parts[1].to_owned())),
)
}
if text.contains(".") || text.contains("e") || text.contains("E") {
let text = if text.has_prefix("+") { text[1:].to_owned() } else { text }
let parsed = @json.parse(text) catch { _ => return None }
if parsed is Number(value, ..) {
if value.is_inf() || value.is_nan() {
raise Invalid("Nonfinite number")
}
return Some(Real(value))
}
}
None
}
///|
pub fn Number::to_text(self : Number) -> String {
match self {
Integer(value) => value.to_string()
Rational(n, d) => n.to_string() + "/" + d.to_string()
Real(value) => {
// JavaScript's numeric printer drops the sign of zero.
if value.reinterpret_as_uint64() == 0x8000000000000000UL {
return "-0.0"
}
let text = value.to_string()
if text.contains(".") || text.contains("e") || text.contains("E") {
text
} else {
text + ".0"
}
}
}
}
///|
fn Number::real(self : Number) -> Double raise SchemeError {
match self {
Real(value) => value
Integer(value) => {
let json = @json.parse(value.to_string()) catch {
_ => raise Invalid("Invalid number")
}
match json {
Number(number, ..) if !number.is_inf() => number
_ => raise Invalid("Inexact conversion overflow")
}
}
Rational(n, d) => Number::Integer(n).real() / Number::Integer(d).real()
}
}
///|
fn Number::fraction(
self : Number,
) -> (@bigint.BigInt, @bigint.BigInt) raise SchemeError {
match self {
Integer(value) => (value, @bigint.BigInt::from_int(1))
Rational(n, d) => (n, d)
Real(_) => raise Invalid("Expected exact number")
}
}
///|
fn arithmetic(
op : String,
left : Number,
right : Number,
) -> Number raise SchemeError {
if left is Real(_) || right is Real(_) {
let a = left.real()
let b = right.real()
if op == "/" && b == 0 {
raise Invalid("Division by zero")
}
let value = match op {
"+" => a + b
"-" => a - b
"*" => a * b
_ => a / b
}
if value.is_inf() || value.is_nan() {
raise Invalid("Nonfinite arithmetic result")
}
Real(value)
} else {
let (a, b) = left.fraction()
let (c, d) = right.fraction()
match op {
"+" => rational(a * d + c * b, b * d)
"-" => rational(a * d - c * b, b * d)
"*" => rational(a * c, b * d)
_ => rational(a * d, b * c)
}
}
}
///|
fn compare_numbers(left : Number, right : Number) -> Int32 raise SchemeError {
if left is Real(_) || right is Real(_) {
left.real().compare(right.real())
} else {
let (a, b) = left.fraction()
let (c, d) = right.fraction()
(a * d).compare(c * b)
}
}
///|
fn number(value : Value) -> Number raise SchemeError {
match value {
Number(n) => n
_ => raise Invalid("Expected number")
}
}