use retex_arith::{self as arith, UNITY};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Decline;
pub type R<T> = Result<T, Decline>;
const DIMEN_LIMIT: i64 = 0o10000000000;
const INFINITY: i64 = 0o17777777777;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Unit {
Pt,
Ratio(i32, i32),
Sp,
Internal(i32),
}
impl Unit {
pub fn from_name(a: u8, b: u8) -> Option<Unit> {
Some(match (a, b) {
(b'p', b't') => Unit::Pt,
(b'i', b'n') => Unit::Ratio(7227, 100),
(b'p', b'c') => Unit::Ratio(12, 1),
(b'c', b'm') => Unit::Ratio(7227, 254),
(b'm', b'm') => Unit::Ratio(7227, 2540),
(b'b', b'p') => Unit::Ratio(7227, 7200),
(b'd', b'd') => Unit::Ratio(1238, 1157),
(b'c', b'c') => Unit::Ratio(14856, 1157),
(b'n', b'd') => Unit::Ratio(685, 642),
(b'n', b'c') => Unit::Ratio(1370, 107),
(b's', b'p') => Unit::Sp,
_ => return None,
})
}
}
struct Decimal {
negative: bool,
int: Option<i64>,
frac: i32,
}
fn decimal(s: &[u8]) -> R<Decimal> {
let mut i = 0;
let mut negative = false;
while i < s.len() && (s[i] == b'+' || s[i] == b'-') {
if s[i] == b'-' {
negative = !negative;
}
i += 1;
}
if i == s.len() {
return Ok(Decimal {
negative,
int: None,
frac: 0,
});
}
let mut int: i64 = 0;
if s[i] == b'.' {
} else {
let start = i;
while i < s.len() && s[i].is_ascii_digit() {
int = int * 10 + i64::from(s[i] - b'0');
if int > INFINITY {
return Err(Decline); }
i += 1;
}
if i == start {
return Err(Decline); }
}
let mut frac = 0;
if i < s.len() && s[i] == b'.' {
i += 1;
let mut digits: Vec<u8> = Vec::new();
while i < s.len() && s[i].is_ascii_digit() {
if digits.len() < 17 {
digits.push(s[i] - b'0');
}
i += 1;
}
frac = arith::round_decimals(&digits);
}
if i != s.len() {
return Err(Decline); }
Ok(Decimal {
negative,
int: Some(int),
frac,
})
}
fn finish(v: i64, negative: bool) -> R<i32> {
if v.abs() >= DIMEN_LIMIT {
return Err(Decline); }
let v = v as i32;
Ok(if negative { -v } else { v })
}
pub fn scan_unit(s: &[u8], unit: Unit) -> R<i32> {
let d = decimal(s)?;
let Some(int) = d.int else {
if let Unit::Internal(v) = unit {
return finish(i64::from(v), d.negative);
}
return Err(Decline); };
let mut f = i64::from(d.frac);
match unit {
Unit::Pt => {}
Unit::Ratio(num, denom) => {
let r = arith::xn_over_d(int as i32, num, denom).map_err(|_| Decline)?;
let mut cur = i64::from(r.quotient);
f = (i64::from(num) * f + 0o200000 * i64::from(r.remainder)) / i64::from(denom);
cur += f / 0o200000;
f %= 0o200000;
return attach_fraction(cur, f, d.negative);
}
Unit::Sp => return finish(int, d.negative),
Unit::Internal(v) => {
let frac = arith::xn_over_d(v, d.frac, 0o200000)
.map_err(|_| Decline)?
.quotient;
let cur = arith::nx_plus_y(int as i32, v, frac).map_err(|_| Decline)?;
return finish(i64::from(cur), d.negative);
}
}
attach_fraction(int, f, d.negative)
}
fn attach_fraction(int: i64, f: i64, negative: bool) -> R<i32> {
if int >= 0o40000 {
return Err(Decline);
}
finish(int * i64::from(UNITY) + f, negative)
}
pub fn scan_pt(s: &[u8]) -> R<i32> {
scan_unit(s, Unit::Pt)
}
pub fn scan_factor(s: &[u8], v: i32) -> R<i32> {
scan_unit(s, Unit::Internal(v))
}
pub fn print(v: i32) -> Vec<u8> {
let mut s = String::new();
arith::print_scaled(&mut s, v);
s.into_bytes()
}
pub fn returnone(x: i32) -> R<Vec<u8>> {
if i64::from(x).abs() >= DIMEN_LIMIT {
return Err(Decline);
}
Ok(print(x))
}
pub fn scan_int(s: &[u8]) -> R<i64> {
let mut i = 0;
let mut negative = false;
while i < s.len() && (s[i] == b'+' || s[i] == b'-') {
if s[i] == b'-' {
negative = !negative;
}
i += 1;
}
if i == s.len() {
return Err(Decline);
}
let mut v: i64 = 0;
for &c in &s[i..] {
if !c.is_ascii_digit() {
return Err(Decline);
}
v = v * 10 + i64::from(c - b'0');
if v > INFINITY {
return Err(Decline);
}
}
Ok(if negative { -v } else { v })
}
pub fn tex_divide(x: i64, n: i64) -> R<i64> {
if n == 0 {
return Err(Decline);
}
let q = x.abs() / n.abs();
Ok(if (x < 0) != (n < 0) { -q } else { q })
}
pub fn tex_multiply(x: i64, n: i64) -> R<i64> {
let p = x * n;
if p.abs() > INFINITY {
return Err(Decline);
}
Ok(p)
}
pub fn split_point(s: &[u8]) -> (&[u8], &[u8]) {
let p = s.iter().position(|&c| c == b'.').unwrap_or(s.len());
(&s[..p], s.get(p + 1..).unwrap_or(&[]))
}
pub fn int_and_fraction(v: i32) -> R<(i64, i32)> {
let s = print(v);
let (int, frac) = split_point(&s);
let i = scan_int(int)?;
let mut f = vec![b'.'];
f.extend_from_slice(frac);
Ok((i, scan_pt(&f)?))
}
pub fn adv(x: i32, y: i32) -> R<i32> {
let r = i64::from(x) + i64::from(y);
if r.abs() >= DIMEN_LIMIT {
return Err(Decline);
}
Ok(r as i32)
}
pub fn cadv(x: i64, y: i64) -> R<i64> {
let r = x + y;
if r.abs() > INFINITY {
return Err(Decline);
}
Ok(r)
}
pub fn dmul(x: i32, n: i64) -> R<i32> {
let n = i32::try_from(n).map_err(|_| Decline)?;
arith::nx_plus_y(n, x, 0).map_err(|_| Decline)
}
pub fn ddiv(x: i32, n: i64) -> R<i32> {
Ok(tex_divide(i64::from(x), n)? as i32)
}
pub fn int_part(x: i32) -> i64 {
let v = i64::from(x).abs() / i64::from(UNITY);
if x < 0 { -v } else { v }
}
pub fn scan_int_prefix(s: &[u8]) -> R<(i64, usize)> {
let mut i = 0;
let mut negative = false;
while i < s.len() && (s[i] == b'+' || s[i] == b'-') {
if s[i] == b'-' {
negative = !negative;
}
i += 1;
}
let start = i;
let mut v: i64 = 0;
while i < s.len() && s[i].is_ascii_digit() {
v = v * 10 + i64::from(s[i] - b'0');
if v > INFINITY {
return Err(Decline);
}
i += 1;
}
if i == start {
return Err(Decline); }
Ok((if negative { -v } else { v }, i))
}
pub fn add(a: &[u8], b: &[u8]) -> R<Vec<u8>> {
let x = scan_pt(a)?;
let y = scan_pt(b)?;
returnone(x + y)
}
pub fn subtract(a: &[u8], b: &[u8]) -> R<Vec<u8>> {
let x = scan_pt(a)?;
let y = scan_pt(b)?;
returnone(x - y)
}
pub fn neg(a: &[u8]) -> R<Vec<u8>> {
let mut s = vec![b'-'];
s.extend_from_slice(a);
Ok(print(scan_pt(&s)?))
}
pub fn multiply(a: &[u8], b: &[u8]) -> R<Vec<u8>> {
let x = scan_pt(a)?;
let y = scan_pt(b)?;
returnone(scan_factor(&print(y), x)?)
}
pub fn reciprocal(a: &[u8]) -> R<Vec<u8>> {
let x = scan_pt(a)?;
if x == 0 {
return Err(Decline); }
let tmp = print(x);
let (int, frac) = split_point(&tmp);
let mut d: Vec<u8> = frac.to_vec();
d.extend_from_slice(b"0000000");
let d = &d[..5];
let counta = scan_int(d)?;
let int_v = scan_int(int)?;
let x = if counta == 0 {
tex_divide(i64::from(UNITY), int_v)?
} else {
let digits = if int_v > 100 { &d[..3] } else { d };
let mut s = int.to_vec();
s.extend_from_slice(digits);
let counta = scan_int(&s)?;
let countb = tex_divide(1_000_000_000, counta)?;
let counta = tex_divide(countb, 10000)?;
let x = count_pt(counta)?;
let counta = tex_multiply(counta, -10000)?;
let countb = countb + counta;
let mut y = count_pt(countb)?;
if int_v > 100 {
y = tex_divide(i64::from(y), 1_000_000)? as i32;
} else {
for _ in 0..4 {
y = scan_factor(b".1", y)?;
}
}
i64::from(x) + i64::from(y)
};
returnone(x as i32)
}
pub fn count_pt(c: i64) -> R<i32> {
let negative = c < 0;
attach_fraction(c.abs(), 0, negative)
}
fn dimen_by_ten(y: i32) -> R<i32> {
let s = print(y);
let (int, frac) = split_point(&s);
let i = scan_int(int)?;
let q = tex_divide(i, 10)?;
let r = i - q * 10;
let mut t = format!("{q}.{r}").into_bytes();
t.extend_from_slice(frac);
scan_pt(&t)
}
fn advance_last_digit(result: &[u8]) -> R<Vec<u8>> {
let (_, frac) = split_point(result);
let ya = scan_pt(result)?;
let xa: &[u8] = match frac.len() {
0 => b"1",
1 => b".1",
2 => b".01",
3 => b".001",
4 => b".0001",
_ => b".00001",
};
let ya = ya + scan_pt(xa)?;
let s = print(ya);
let (int, frac) = split_point(&s);
let mut out = int.to_vec();
out.push(b'.');
if scan_int(frac)? != 0 {
out.extend_from_slice(frac);
}
Ok(out)
}
fn long_division(mut x: i32, mut y: i32) -> R<Vec<u8>> {
let small = scan_pt(b"0.00002")?;
let mut result = b"0".to_vec();
let mut period = true;
while small < x && small < y {
if y > x {
if period {
result.push(b'.');
period = false;
}
y = dimen_by_ten(y)?;
if y > x {
result.push(b'0');
}
} else {
let mut q = tex_divide(i64::from(x), i64::from(y))?;
let ya = arith::nx_plus_y(q as i32, y, 0).map_err(|_| Decline)?;
x -= ya;
let mut append = true;
if !period && q > 9 {
result = advance_last_digit(&result)?;
q -= 10;
if q == 0 {
append = false;
}
}
if append {
result.extend_from_slice(q.to_string().as_bytes());
}
}
}
Ok(result)
}
pub fn divide(a: &[u8], b: &[u8]) -> R<Vec<u8>> {
let mut x = scan_pt(a)?;
let mut y = scan_pt(b)?;
if y == 0 {
return Err(Decline); }
let (counta, xa) = int_and_fraction(y)?;
if xa == 0 {
return returnone(tex_divide(i64::from(x), counta)? as i32);
}
let mut sign: Vec<u8> = Vec::new();
if x < 0 {
sign.push(b'-');
x = -x;
}
if y < 0 {
sign.push(b'-');
y = -y;
}
if y < UNITY {
let r = reciprocal(&print(y))?;
sign.extend_from_slice(&r);
returnone(scan_factor(&sign, x)?)
} else {
let r = long_division(x, y)?;
sign.extend_from_slice(&r);
returnone(scan_pt(&sign)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: R<Vec<u8>>) -> String {
String::from_utf8(v.unwrap()).unwrap()
}
#[test]
fn scanning() {
assert_eq!(scan_pt(b"1.5"), Ok(98304));
assert_eq!(scan_pt(b"--.5"), Ok(32768));
assert_eq!(scan_pt(b"16384"), Err(Decline));
assert_eq!(scan_pt(b"1.2.3"), Err(Decline));
assert_eq!(scan_pt(b""), Err(Decline));
assert_eq!(scan_pt(b"."), Ok(0));
assert_eq!(scan_factor(b"-", 5), Ok(-5));
assert_eq!(scan_factor(b"", 5), Ok(5));
assert_eq!(scan_unit(b"1", Unit::Ratio(7227, 254)), Ok(1864679));
}
#[test]
fn functions() {
assert_eq!(s(add(b"1", b"2")), "3.0");
assert_eq!(s(multiply(b"1.5", b"2")), "3.0");
assert_eq!(s(divide(b"1", b"3")), "0.33333");
assert_eq!(s(divide(b"10", b"4")), "2.5");
assert_eq!(s(divide(b"1", b"0.5")), "2.0");
assert_eq!(s(neg(b"-2")), "2.0");
assert_eq!(divide(b"1", b"0"), Err(Decline));
}
}