///|
priv enum Number {
Small(Int)
Whole(@bigint.BigInt)
Real(Double)
} derive(Debug)
///|
fn whole(text : String) -> @bigint.BigInt raise TclError {
if small_decimal(text) is Some(value) {
return @bigint.BigInt::from_int(value)
}
whole_trimmed(text.trim().to_owned())
}
///|
fn whole_trimmed(text : String) -> @bigint.BigInt raise TclError {
if text.length() > 5000 {
raise Invalid("integer size limit")
}
let negative = text.has_prefix("-")
let s = if negative || text.has_prefix("+") {
text[1:].to_owned()
} else {
text
}
let (digits, base) = if s.has_prefix("0x") || s.has_prefix("0X") {
(s[2:].to_owned(), 16)
} else if s.has_prefix("0b") || s.has_prefix("0B") {
(s[2:].to_owned(), 2)
} else if s.has_prefix("0o") || s.has_prefix("0O") {
(s[2:].to_owned(), 8)
} else if s.length() > 1 && s.has_prefix("0") {
(s, 8)
} else {
(s, 10)
}
if digits.has_prefix("-") || digits.has_prefix("+") {
raise Invalid("expected integer: " + text)
}
let value = @strconv.parse_bigint(digits, base~) catch {
_ => raise Invalid("expected integer: " + text)
}
if value.bit_length() > 16384 {
raise Invalid("integer bit limit")
}
if negative {
-value
} else {
value
}
}
///|
fn number(text : String) -> Number? {
if small_decimal(text) is Some(value) {
return Some(Small(value))
}
let text = text.trim().to_owned()
// Skip the failing bigint probe for decimal floating-point operands and
// obvious nonnumbers. This is only a conservative dispatch check; whole()
// still validates syntax and enforces the exact integer resource limits.
if could_be_whole(text) {
if (Some(whole_trimmed(text)) catch { _ => None }) is Some(value) {
return Some(Whole(value))
}
}
let lower = text.to_lower()
if lower == "inf" ||
lower == "+inf" ||
lower == "infinity" ||
lower == "+infinity" {
return Some(Real(1.0 / 0.0))
}
if lower == "-inf" || lower == "-infinity" {
return Some(Real(-1.0 / 0.0))
}
if lower == "nan" {
return Some(Real(0.0 / 0.0))
}
if !text.contains(".") && !lower.contains("e") {
return None
}
Some(Real(@strconv.parse_double(text))) catch {
_ => None
}
}
///|
fn could_be_whole(text : String) -> Bool {
let n = text.length()
if n == 0 {
return false
}
let initial = text.at(0).to_int()
let start = if initial == 43 || initial == 45 { 1 } else { 0 }
if start >= n {
return false
}
let first = text.at(start).to_int()
if first < 48 || first > 57 {
return false
}
if first == 48 && start + 1 < n {
let marker = text.at(start + 1).to_int() | 32
if marker == 120 || marker == 98 || marker == 111 {
// Hexadecimal e/E is an integer digit, not an exponent marker.
return true
}
}
for i in (start + 1).. 57 {
return false
}
}
true
}
///|
fn Number::double(self : Number) -> Double {
match self {
Small(x) => x.to_double()
Real(x) => x
Whole(x) =>
@strconv.parse_double(x.to_string()) catch {
_ => if x < 0N { -1.0 / 0.0 } else { 1.0 / 0.0 }
}
}
}
///|
fn Number::text(self : Number) -> String raise TclError {
match self {
Small(x) => x.to_string()
Whole(value) => {
if value.bit_length() > 16384 {
raise Invalid("integer bit limit")
}
value.to_string()
}
Real(value) => {
if value.is_nan() {
raise Signal(
completion_error(
"domain error: argument not in valid range",
errorcode="ARITH DOMAIN {domain error: argument not in valid range}",
),
)
}
if value.is_inf() {
return if value < 0.0 { "-Inf" } else { "Inf" }
}
float_text(value)
}
}
}
///|
fn boolean(text : String) -> Bool raise TclError {
if number(text) is Some(n) {
let value = n.double()
if value.is_nan() {
raise Invalid("expected boolean")
}
return value != 0.0
}
let text = text.to_lower()
if text.is_empty() {
raise Invalid("expected boolean")
}
if ["true", "yes", "on"].iter().any(s => s.has_prefix(text)) && text != "o" {
return true
}
if ["false", "no", "off"].iter().any(s => s.has_prefix(text)) && text != "o" {
return false
}
raise Invalid("expected boolean")
}
///|
fn boolean_text(value : Bool) -> String {
if value {
"1"
} else {
"0"
}
}
///|
fn numeric_binary(
operator : String,
left : TclValue,
right : TclValue,
) -> TclValue raise TclError {
if operator == "eq" || operator == "ne" {
return text_value(
boolean_text((left.text == right.text) == (operator == "eq")),
)
}
if operator == "in" || operator == "ni" {
return text_value(
boolean_text(
right.as_list().iter().any(v => v.text == left.text) ==
(operator == "in"),
),
)
}
let a = left.as_number()
let b = right.as_number()
if (a, b) is (Some(Small(x)), Some(Small(y))) {
if small_operation(operator, x, y) is Some(result) {
return text_value(result)
}
}
let a = a.map(Number::promote)
let b = b.map(Number::promote)
if ["==", "!=", "<", ">", "<=", ">="].contains(operator) {
let comparison = match (a, b) {
(Some(Whole(a)), Some(Whole(b))) => a.compare(b)
(Some(Whole(a)), Some(Real(b))) => {
if b.is_nan() {
return text_value(boolean_text(operator == "!="))
}
integer_double_compare(a, b)
}
(Some(Real(a)), Some(Whole(b))) => {
if a.is_nan() {
return text_value(boolean_text(operator == "!="))
}
-integer_double_compare(b, a)
}
(Some(a), Some(b)) => {
let x = a.double()
let y = b.double()
if x.is_nan() || y.is_nan() {
return text_value(boolean_text(operator == "!="))
}
if x < y {
-1
} else if x > y {
1
} else {
0
}
}
_ => tcl_string_compare(left.text, right.text)
}
return text_value(
boolean_text(
match operator {
"==" => comparison == 0
"!=" => comparison != 0
"<" => comparison < 0
">" => comparison > 0
"<=" => comparison <= 0
_ => comparison >= 0
},
),
)
}
guard a is Some(a) && b is Some(b) else {
raise Invalid("expected numeric operand")
}
if (a, b) is (Whole(x), Whole(y)) {
let value = match operator {
"+" => x + y
"-" => x - y
"*" => {
if x.bit_length() + y.bit_length() > 16384 {
raise Invalid("integer bit limit")
}
x * y
}
"%" => {
if y == 0N {
raise Signal(
completion_error(
"divide by zero",
errorcode="ARITH DIVZERO {divide by zero}",
),
)
}
let r = x % y
if r != 0N && (r < 0N) != (y < 0N) {
r + y
} else {
r
}
}
"**" =>
if y < 0N {
if x == 0N {
raise Signal(
completion_error(
"exponentiation of zero by negative power",
errorcode="ARITH DOMAIN {exponentiation of zero by negative power}",
),
)
}
if x == 1N {
1N
} else if x == -1N {
if y % 2N == 0N {
1N
} else {
-1N
}
} else {
0N
}
} else {
if y > 16384N ||
(
x.bit_length() > 1 &&
y > @bigint.BigInt::from_int(16384 / x.bit_length())
) {
raise Invalid("exponent limit")
}
x.pow(y)
}
"<<" | ">>" => {
if y < 0N || y > 16384N {
raise Invalid("shift limit")
}
if operator == "<<" {
if x.bit_length() + y.to_int() > 16384 {
raise Invalid("integer bit limit")
}
x << y.to_int()
} else {
x >> y.to_int()
}
}
"&" => x & y
"|" => x | y
"^" => x ^ y
_ => raise Invalid("unsupported integer operator")
}
return number_value(Whole(value))
}
if ["%", "<<", ">>", "&", "|", "^"].contains(operator) {
raise Invalid("integer operand required")
}
let x = a.double()
let y = b.double()
let result = match operator {
"+" => x + y
"-" => x - y
"*" => x * y
"/" => x / y
"**" => @math.pow(x, y)
_ => raise Invalid("unsupported arithmetic operator")
}
number_value(Real(result))
}
///|
fn integer_double_compare(a : @bigint.BigInt, b : Double) -> Int raise TclError {
if b.is_inf() {
return if b < 0.0 { 1 } else { -1 }
}
let integral = double_integer(b)
let order = a.compare(integral)
if order != 0 {
return order
}
if b == b.floor() {
0
} else if b < 0.0 {
1
} else {
-1
}
}
///|
fn float_text(value : Double) -> String raise TclError {
if value == 0.0 {
return if value.reinterpret_as_uint64() >> 63 == 1UL {
"-0.0"
} else {
"0.0"
}
}
let negative = value < 0.0
let raw = tcl_shortest_power(value.abs())
let parts = raw.split("e").to_array()
let mantissa = parts[0].to_owned()
let decimal = mantissa.split(".").to_array()
let mut exponent = if parts.length() == 2 {
@strconv.parse_int(parts[1].to_owned()) catch {
_ => raise Invalid("invalid decimal exponent")
}
} else {
0
}
exponent += decimal[0].length() - 1
let mut digits = mantissa.replace_all(old=".", new="")
while digits.has_prefix("0") {
digits = digits[1:].to_owned()
exponent -= 1
}
while digits.length() > 1 && digits.has_suffix("0") {
digits = digits[:digits.length() - 1].to_owned()
}
let result = if exponent < -4 || exponent >= 17 {
digits[:1].to_owned() +
(if digits.length() > 1 { "." + digits[1:].to_owned() } else { "" }) +
"e" +
(if exponent >= 0 { "+" } else { "" }) +
exponent.to_string()
} else if exponent < 0 {
"0." + String::from_array(Array::make(-exponent - 1, '0')) + digits
} else if exponent + 1 >= digits.length() {
digits +
String::from_array(Array::make(exponent + 1 - digits.length(), '0')) +
".0"
} else {
digits[:exponent + 1].to_owned() + "." + digits[exponent + 1:].to_owned()
}
(if negative { "-" } else { "" }) + result
}
///|
fn numeric_divide(left : TclValue, right : TclValue) -> TclValue raise TclError {
let a = left.as_number()
let b = right.as_number()
if (a, b) is (Some(Small(x)), Some(Small(y))) {
return text_value(small_operation("/", x, y).unwrap())
}
match (a.map(Number::promote), b.map(Number::promote)) {
(Some(Whole(x)), Some(Whole(y))) => {
if y == 0N {
raise Signal(
completion_error(
"divide by zero",
errorcode="ARITH DIVZERO {divide by zero}",
),
)
}
let q = x / y
let r = x % y
number_value(
Whole(if r != 0N && (x < 0N) != (y < 0N) { q - 1N } else { q }),
)
}
_ => numeric_binary("/", left, right)
}
}
///|
// A conservative decimal fast path. Leading-zero, base-prefixed, whitespace,
// and larger operands continue through the full Tcl integer parser.
fn small_decimal(text : String) -> Int? {
let length = text.length()
if length == 0 || length > 10 {
return None
}
let initial = text.get(0).unwrap().to_int()
let negative = initial == 45
let start = if negative || initial == 43 { 1 } else { 0 }
if length == start || length - start > 9 {
return None
}
if length - start > 1 && text.get(start).unwrap().to_int() == 48 {
return None
}
let mut value = 0
for i in start.. 9 {
return None
}
value = value * 10 + digit
}
Some(if negative { -value } else { value })
}
///|
fn Number::promote(self : Number) -> Number {
match self {
Small(x) => Whole(@bigint.BigInt::from_int(x))
other => other
}
}
///|
fn small_operation(
operator : String,
x : Int,
y : Int,
) -> String? raise TclError {
// Nine decimal digits fit exactly in Int64 even after multiplication.
let a = x.to_int64()
let b = y.to_int64()
let result = match operator {
"+" => (a + b).to_string()
"-" => (a - b).to_string()
"*" => (a * b).to_string()
"/" | "%" => {
if y == 0 {
raise Signal(
completion_error(
"divide by zero",
errorcode="ARITH DIVZERO {divide by zero}",
),
)
}
let q = a / b
let r = a % b
if operator == "/" {
(if r != 0L && (x < 0) != (y < 0) { q - 1L } else { q }).to_string()
} else {
(if r != 0L && (r < 0L) != (y < 0) { r + b } else { r }).to_string()
}
}
"&" => (x & y).to_string()
"|" => (x | y).to_string()
"^" => (x ^ y).to_string()
"==" => boolean_text(x == y)
"!=" => boolean_text(x != y)
"<" => boolean_text(x < y)
">" => boolean_text(x > y)
"<=" => boolean_text(x <= y)
">=" => boolean_text(x >= y)
_ => return None
}
Some(result)
}
///|
fn integer_add(left : String, right : String) -> String raise TclError {
if (small_decimal(left), small_decimal(right)) is (Some(x), Some(y)) {
return (x.to_int64() + y.to_int64()).to_string()
}
Whole(whole(left) + whole(right)).text()
}