1use std::sync::Arc;
6use std::sync::atomic::{AtomicU32, Ordering};
7
8use num_bigint::{BigInt, Sign};
9use sim_kernel::{
10 Args, Callable, Class, ClassId, ClassRef, Cx, DefaultFactory, Error, Expr, Factory,
11 NumberLiteral, NumberValue, Object, ObjectEncode, ObjectEncoding, ReadConstructor,
12 ReadConstructorRef, Result, ShapeRef, Symbol, TableRef, Value,
13};
14
15use crate::implementation::number_domain;
16use sim_lib_numbers_core::domains;
17
18use super::domain::{rational_value_class_symbol, value_shape_symbol};
19use super::integer::{
20 compact_canonical, is_integer_domain, parse_integer_literal, parse_integer_value,
21};
22
23#[derive(Clone)]
26pub struct Rational {
27 pub num: Value,
29 pub den: Value,
31}
32
33impl Rational {
34 pub(crate) fn new(num: Value, den: Value) -> Self {
35 Self { num, den }
36 }
37}
38
39impl Object for Rational {
40 fn display(&self, cx: &mut Cx) -> Result<String> {
41 if let Some(canonical) = compact_canonical(cx, &self.num, &self.den)? {
42 return Ok(canonical);
43 }
44 Ok(format!(
45 "#<rational {}/{}>",
46 self.num.object().display(cx)?,
47 self.den.object().display(cx)?
48 ))
49 }
50
51 fn as_any(&self) -> &dyn std::any::Any {
52 self
53 }
54}
55
56impl sim_kernel::ObjectCompat for Rational {
57 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
58 if let Some(value) = cx
59 .registry()
60 .class_by_symbol(&rational_value_class_symbol())
61 {
62 return Ok(value.clone());
63 }
64 DefaultFactory.class_stub(
65 sim_kernel::CORE_NUMBER_CLASS_ID,
66 Symbol::qualified("core", "Number"),
67 )
68 }
69 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
70 if let Some(canonical) = compact_canonical(cx, &self.num, &self.den)? {
71 return Ok(Expr::Number(NumberLiteral {
72 domain: number_domain(),
73 canonical,
74 }));
75 }
76 Ok(Expr::Extension {
77 tag: rational_value_class_symbol(),
78 payload: Box::new(Expr::Vector(vec![
79 self.num.object().as_expr(cx)?,
80 self.den.object().as_expr(cx)?,
81 ])),
82 })
83 }
84 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
85 cx.factory().table(vec![
86 (
87 Symbol::new("kind"),
88 cx.factory().string("rational".to_owned())?,
89 ),
90 (Symbol::new("num"), self.num.clone()),
91 (Symbol::new("den"), self.den.clone()),
92 ])
93 }
94 fn as_number_value(&self) -> Option<&dyn NumberValue> {
95 Some(self)
96 }
97 fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
98 Some(self)
99 }
100}
101
102impl NumberValue for Rational {
103 fn number_domain(&self, _cx: &mut Cx) -> Result<Symbol> {
104 Ok(number_domain())
105 }
106
107 fn number_literal(&self, cx: &mut Cx) -> Result<Option<NumberLiteral>> {
108 Ok(
109 compact_canonical(cx, &self.num, &self.den)?.map(|canonical| NumberLiteral {
110 domain: number_domain(),
111 canonical,
112 }),
113 )
114 }
115}
116
117impl ObjectEncode for Rational {
118 fn object_encoding(&self, cx: &mut Cx) -> Result<ObjectEncoding> {
119 Ok(ObjectEncoding::Constructor {
120 class: rational_value_class_symbol(),
121 args: vec![
122 self.num.object().as_expr(cx)?,
123 self.den.object().as_expr(cx)?,
124 ],
125 })
126 }
127}
128
129impl sim_citizen::Citizen for Rational {
130 fn citizen_symbol() -> Symbol {
131 rational_value_class_symbol()
132 }
133
134 fn citizen_version() -> u32 {
135 0
136 }
137
138 fn citizen_arity() -> usize {
139 2
140 }
141
142 fn citizen_fields() -> &'static [&'static str] {
143 &["num", "den"]
144 }
145}
146
147pub(crate) struct RationalValueClass {
148 id: AtomicU32,
149}
150
151impl RationalValueClass {
152 pub(crate) fn new() -> Self {
153 Self {
154 id: AtomicU32::new(0),
155 }
156 }
157
158 pub(crate) fn set_id(&self, id: ClassId) {
159 self.id.store(id.0, Ordering::Relaxed);
160 }
161}
162
163impl Object for RationalValueClass {
164 fn display(&self, _cx: &mut Cx) -> Result<String> {
165 Ok(format!("#<class {}>", rational_value_class_symbol()))
166 }
167
168 fn as_any(&self) -> &dyn std::any::Any {
169 self
170 }
171}
172
173impl sim_kernel::ObjectCompat for RationalValueClass {
174 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
175 if let Some(value) = cx
176 .registry()
177 .class_by_symbol(&Symbol::qualified("core", "Class"))
178 {
179 return Ok(value.clone());
180 }
181 DefaultFactory.class_stub(
182 sim_kernel::CORE_CLASS_CLASS_ID,
183 Symbol::qualified("core", "Class"),
184 )
185 }
186 fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
187 Ok(Expr::Symbol(rational_value_class_symbol()))
188 }
189 fn as_callable(&self) -> Option<&dyn Callable> {
190 Some(self)
191 }
192 fn as_class(&self) -> Option<&dyn Class> {
193 Some(self)
194 }
195 fn as_read_constructor(&self) -> Option<&dyn ReadConstructor> {
196 Some(self)
197 }
198}
199
200impl Callable for RationalValueClass {
201 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
202 let values = args.into_vec();
203 let [num, den] = values.as_slice() else {
204 return Err(Error::Eval(format!(
205 "class {} expects exactly two arguments",
206 rational_value_class_symbol()
207 )));
208 };
209 make_rational(cx, num.clone(), den.clone())
210 }
211}
212
213impl Class for RationalValueClass {
214 fn id(&self) -> ClassId {
215 ClassId(self.id.load(Ordering::Relaxed))
216 }
217
218 fn symbol(&self) -> Symbol {
219 rational_value_class_symbol()
220 }
221
222 fn constructor_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
223 cx.factory().nil()
224 }
225
226 fn instance_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
227 Ok(cx
228 .registry()
229 .shape_by_symbol(&value_shape_symbol())
230 .cloned()
231 .unwrap_or(cx.factory().symbol(value_shape_symbol())?))
232 }
233
234 fn read_constructor(&self, cx: &mut Cx) -> Result<Option<ReadConstructorRef>> {
235 Ok(cx
236 .registry()
237 .class_by_symbol(&rational_value_class_symbol())
238 .cloned())
239 }
240
241 fn members(&self, cx: &mut Cx) -> Result<TableRef> {
242 cx.factory().table(Vec::new())
243 }
244}
245
246impl ReadConstructor for RationalValueClass {
247 fn symbol(&self) -> Symbol {
248 rational_value_class_symbol()
249 }
250
251 fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
252 cx.factory().nil()
253 }
254
255 fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
256 self.call(cx, Args::new(args))
257 }
258}
259
260pub(crate) fn make_rational(cx: &mut Cx, num: Value, den: Value) -> Result<Value> {
261 build_rational(cx, num, den, false)
262}
263
264pub(crate) fn make_reduced_rational(cx: &mut Cx, num: Value, den: Value) -> Result<Value> {
265 build_rational(cx, num, den, true)
266}
267
268fn build_rational(cx: &mut Cx, num: Value, den: Value, cross_reduce: bool) -> Result<Value> {
269 let (num, den) = normalize_integer_parts(cx, num, den, cross_reduce)?;
270 if let Some(canonical) = compact_canonical(cx, &num, &den)? {
271 return cx.factory().number_literal(number_domain(), canonical);
272 }
273 cx.factory().opaque(Arc::new(Rational::new(num, den)))
274}
275
276pub(crate) fn expect_rational_parts(cx: &mut Cx, value: Value, side: &str) -> Result<Rational> {
277 let Some(number) = cx.number_value_ref(value.clone())? else {
278 return Err(Error::Eval(format!(
279 "{side} operand expected number domain {}, found non-number",
280 number_domain()
281 )));
282 };
283 if number.domain != number_domain() {
284 return Err(Error::Eval(format!(
285 "{side} operand expected number domain {}, found {}",
286 number_domain(),
287 number.domain
288 )));
289 }
290 if let Some(rational) = value.object().downcast_ref::<Rational>() {
291 return Ok(rational.clone());
292 }
293 let literal = number.literal.ok_or_else(|| {
294 Error::Eval(format!(
295 "{side} operand in {} does not have a canonical rational form",
296 number_domain()
297 ))
298 })?;
299 let (num_text, den_text) = literal.canonical.split_once('/').ok_or_else(|| {
300 Error::Eval(format!(
301 "{side} operand was not a valid rational literal: {}",
302 literal.canonical
303 ))
304 })?;
305 Ok(Rational::new(
306 parse_integer_value(cx, num_text)?,
307 parse_integer_value(cx, den_text)?,
308 ))
309}
310
311fn normalize_integer_parts(
312 cx: &mut Cx,
313 num: Value,
314 den: Value,
315 cross_reduce: bool,
316) -> Result<(Value, Value)> {
317 require_integer_value(cx, &num, "numerator")?;
318 require_integer_value(cx, &den, "denominator")?;
319 let Some(den_literal) = cx
320 .number_value_ref(den.clone())?
321 .and_then(|number| number.literal)
322 else {
323 return Err(Error::Eval(
324 "denominator integer value does not have a canonical literal form".to_owned(),
325 ));
326 };
327 let den_big = parse_integer_literal(&den_literal)?;
328 if den_big == BigInt::from(0_u8) {
329 return Err(Error::Eval(
330 "rational denominator must not be zero".to_owned(),
331 ));
332 }
333
334 let (mut num, mut den) = if den_big.sign() == Sign::Minus {
335 (
336 negate_integer_value(cx, num.clone())?,
337 negate_integer_value(cx, den.clone())?,
338 )
339 } else {
340 (num, den)
341 };
342
343 if let Some((common_num, common_den)) =
344 coerce_for_reduction(cx, num.clone(), den.clone(), cross_reduce)?
345 {
346 let gcd = gcd_integer_values(cx, common_num.clone(), common_den.clone())?;
347 if let Some(gcd_literal) = cx
348 .number_value_ref(gcd.clone())?
349 .and_then(|value| value.literal)
350 && parse_integer_literal(&gcd_literal)? != BigInt::from(1_u8)
351 {
352 num = exact_divide_integer_value(cx, common_num, gcd.clone())?;
353 den = exact_divide_integer_value(cx, common_den, gcd)?;
354 return Ok((num, den));
355 }
356 num = common_num;
357 den = common_den;
358 }
359
360 Ok((num, den))
361}
362
363fn require_integer_value(cx: &mut Cx, value: &Value, side: &str) -> Result<()> {
364 let Some(number) = cx.number_value_ref(value.clone())? else {
365 return Err(Error::Eval(format!(
366 "{side} expected integer number value, found non-number"
367 )));
368 };
369 if !is_integer_domain(&number.domain) {
370 return Err(Error::Eval(format!(
371 "{side} expected integer number domain, found {}",
372 number.domain
373 )));
374 }
375 Ok(())
376}
377
378fn negate_integer_value(cx: &mut Cx, value: Value) -> Result<Value> {
379 cx.apply_value_number_unary_op(&Symbol::qualified("math", "neg"), value)
380}
381
382fn coerce_for_reduction(
383 cx: &mut Cx,
384 num: Value,
385 den: Value,
386 cross_reduce: bool,
387) -> Result<Option<(Value, Value)>> {
388 let num_ref = cx.number_value_ref(num.clone())?.ok_or_else(|| {
389 Error::Eval("rational numerator lost numeric identity during normalization".to_owned())
390 })?;
391 let den_ref = cx.number_value_ref(den.clone())?.ok_or_else(|| {
392 Error::Eval("rational denominator lost numeric identity during normalization".to_owned())
393 })?;
394 if num_ref.domain == den_ref.domain {
395 return Ok(Some((num, den)));
396 }
397 if !cross_reduce
398 || cx
399 .registry()
400 .number_domain_by_symbol(&domains::bigint())
401 .is_none()
402 {
403 return Ok(None);
404 }
405 let Some(num_literal) = num_ref.literal else {
406 return Ok(None);
407 };
408 let Some(den_literal) = den_ref.literal else {
409 return Ok(None);
410 };
411 let num_big = parse_integer_literal(&num_literal)?;
412 let den_big = parse_integer_literal(&den_literal)?;
413 Ok(Some((
414 cx.factory()
415 .number_literal(domains::bigint(), num_big.to_string())?,
416 cx.factory()
417 .number_literal(domains::bigint(), den_big.to_string())?,
418 )))
419}
420
421fn gcd_integer_values(cx: &mut Cx, left: Value, right: Value) -> Result<Value> {
422 let rem = Symbol::qualified("math", "rem");
423 let mut left = absolute_integer_value(cx, left)?;
424 let mut right = absolute_integer_value(cx, right)?;
425 loop {
426 let Some(right_literal) = cx
427 .number_value_ref(right.clone())?
428 .and_then(|value| value.literal)
429 else {
430 return Ok(left);
431 };
432 if parse_integer_literal(&right_literal)? == BigInt::from(0_u8) {
433 return Ok(left);
434 }
435 let next = cx.apply_value_number_binary_op(&rem, left, right.clone())?;
436 left = right;
437 right = next;
438 }
439}
440
441fn absolute_integer_value(cx: &mut Cx, value: Value) -> Result<Value> {
442 let Some(literal) = cx
443 .number_value_ref(value.clone())?
444 .and_then(|value| value.literal)
445 else {
446 return Ok(value);
447 };
448 let big = parse_integer_literal(&literal)?;
449 if big.sign() == Sign::Minus {
450 negate_integer_value(cx, value)
451 } else {
452 Ok(value)
453 }
454}
455
456fn exact_divide_integer_value(cx: &mut Cx, left: Value, right: Value) -> Result<Value> {
457 let left_ref = cx.number_value_ref(left)?.ok_or_else(|| {
458 Error::Eval("exact integer division expected a numeric left operand".to_owned())
459 })?;
460 let right_ref = cx.number_value_ref(right)?.ok_or_else(|| {
461 Error::Eval("exact integer division expected a numeric right operand".to_owned())
462 })?;
463 if left_ref.domain != right_ref.domain {
464 return Err(Error::Eval(format!(
465 "exact integer division requires a shared domain, found {} and {}",
466 left_ref.domain, right_ref.domain
467 )));
468 }
469 let left_literal = left_ref.literal.ok_or_else(|| {
470 Error::Eval("exact integer division requires a canonical left integer literal".to_owned())
471 })?;
472 let right_literal = right_ref.literal.ok_or_else(|| {
473 Error::Eval("exact integer division requires a canonical right integer literal".to_owned())
474 })?;
475 let left_big = parse_integer_literal(&left_literal)?;
476 let right_big = parse_integer_literal(&right_literal)?;
477 if right_big == BigInt::from(0_u8) {
478 return Err(Error::Eval(
479 "exact integer division encountered a zero divisor".to_owned(),
480 ));
481 }
482 if (&left_big % &right_big) != BigInt::from(0_u8) {
483 return Err(Error::Eval(format!(
484 "exact integer division found a non-divisible pair {}/{}",
485 left_big, right_big
486 )));
487 }
488 cx.factory()
489 .number_literal(left_ref.domain, (left_big / right_big).to_string())
490}