1use std::sync::Arc;
6
7use sim_kernel::{
8 AbiVersion, ClassId, DefaultFactory, Dependency, Export, Expr, Factory, Lib, LibManifest,
9 LibTarget, Linker, NumberDomain, NumberLiteral, Object, Result, Symbol, Value,
10 ValuePromotionRule, Version,
11};
12use sim_lib_numbers_core::{
13 DomainNumberValueShape, NumberDomainTableSpec, NumberLiteralClass, NumberLiteralShape,
14 ScalarBinaryOp, ScalarOps, ScalarReductionOp, ScalarUnaryOp, class_surface_or_symbol, domains,
15 install_scalar_ops, number_domain_table, shape_surface_or_symbol,
16};
17use sim_shape::shape_value;
18
19use super::integer::compact_canonical;
20use super::ops::{
21 RationalRuleFn, ValueRuleFn, promote_f64_literal_to_rational, promote_f64_value_to_rational,
22 promote_integer_literal_to_rational, promote_integer_value_to_rational,
23 promote_rational_literal_to_f64, promote_rational_value_to_f64, rational_add_rule,
24 rational_add_value_rule, rational_div_rule, rational_div_value_rule, rational_mul_rule,
25 rational_mul_value_rule, rational_neg_rule, rational_neg_value_rule, rational_pow_rule,
26 rational_pow_value_rule, rational_product_rule, rational_product_value_rule, rational_sub_rule,
27 rational_sub_value_rule, rational_sum_rule, rational_sum_value_rule,
28};
29use super::value::{Rational, RationalValueClass};
30
31pub fn number_domain() -> Symbol {
34 domains::rational()
35}
36
37pub fn literal_class_symbol() -> Symbol {
40 domains::literal_class("rational")
41}
42
43pub fn literal_instance_shape_symbol() -> Symbol {
46 Symbol::qualified(literal_class_symbol().to_string(), "instance-shape")
47}
48
49pub fn rational_value_class_symbol() -> Symbol {
52 domains::rational_value_class()
53}
54
55pub(crate) fn value_shape_symbol() -> Symbol {
56 sim_lib_numbers_core::value_shape_symbol(&number_domain())
57}
58
59pub fn f64_domain() -> Symbol {
62 domains::f64()
63}
64
65pub fn add_symbol() -> Symbol {
67 Symbol::qualified("math", "add")
68}
69
70pub fn sub_symbol() -> Symbol {
72 Symbol::qualified("math", "sub")
73}
74
75pub fn mul_symbol() -> Symbol {
77 Symbol::qualified("math", "mul")
78}
79
80pub fn div_symbol() -> Symbol {
82 Symbol::qualified("math", "div")
83}
84
85pub fn pow_symbol() -> Symbol {
87 Symbol::qualified("math", "pow")
88}
89
90#[sim_citizen_derive::non_citizen(
91 reason = "numbers/rational number-domain marker; reconstruct by loading the rational number lib",
92 kind = "marker"
93)]
94pub struct RationalNumberDomain;
97
98impl NumberDomain for RationalNumberDomain {
99 fn symbol(&self) -> Symbol {
100 number_domain()
101 }
102
103 fn parse_literal(&self, cx: &mut sim_kernel::Cx, text: &str) -> Result<Option<Value>> {
104 let Some((numerator, denominator)) = super::ops::parse_rational_parts(text) else {
105 return Ok(None);
106 };
107 cx.factory()
108 .number_literal(number_domain(), format!("{numerator}/{denominator}"))
109 .map(Some)
110 }
111
112 fn encode_literal(
113 &self,
114 cx: &mut sim_kernel::Cx,
115 value: Value,
116 ) -> Result<Option<NumberLiteral>> {
117 match value.object().as_expr(cx)? {
118 Expr::Number(number) if number.domain == number_domain() => Ok(Some(number)),
119 _ => Ok(value
120 .object()
121 .downcast_ref::<Rational>()
122 .cloned()
123 .map(|rational| compact_canonical(cx, &rational.num, &rational.den))
124 .transpose()?
125 .flatten()
126 .map(|canonical| NumberLiteral {
127 domain: number_domain(),
128 canonical,
129 })),
130 }
131 }
132}
133
134impl Object for RationalNumberDomain {
135 fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
136 Ok("#<number-domain numbers/rational>".to_owned())
137 }
138
139 fn as_any(&self) -> &dyn std::any::Any {
140 self
141 }
142}
143
144impl sim_kernel::ObjectCompat for RationalNumberDomain {
145 fn class(&self, cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
146 sim_lib_numbers_core::number_domain_class_stub(cx)
147 }
148 fn as_expr(&self, _cx: &mut sim_kernel::Cx) -> Result<Expr> {
149 Ok(Expr::Symbol(number_domain()))
150 }
151 fn as_table(&self, cx: &mut sim_kernel::Cx) -> Result<Value> {
152 let literal_class = class_surface_or_symbol(cx, literal_class_symbol())?;
153 let instance_shape = shape_surface_or_symbol(cx, literal_instance_shape_symbol())?;
154 let value_shape = shape_surface_or_symbol(cx, value_shape_symbol())?;
155 number_domain_table(
156 cx,
157 NumberDomainTableSpec::new(
158 number_domain(),
159 "rational",
160 "numerator/denominator",
161 0,
162 literal_class,
163 instance_shape,
164 value_shape,
165 ),
166 )
167 }
168 fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
169 Some(self)
170 }
171}
172
173pub struct RationalNumbersLib;
197
198impl RationalNumbersLib {
199 pub fn new() -> Self {
201 Self
202 }
203}
204
205impl Default for RationalNumbersLib {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211impl Lib for RationalNumbersLib {
212 fn manifest(&self) -> LibManifest {
213 LibManifest {
214 id: number_domain(),
215 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
216 abi: AbiVersion { major: 0, minor: 1 },
217 target: LibTarget::HostRegistered,
218 requires: Vec::<Dependency>::new(),
219 capabilities: Vec::new(),
220 exports: vec![
221 Export::NumberDomain {
222 symbol: number_domain(),
223 number_domain_id: None,
224 },
225 Export::Class {
226 symbol: literal_class_symbol(),
227 class_id: None,
228 },
229 Export::Shape {
230 symbol: literal_instance_shape_symbol(),
231 shape_id: None,
232 },
233 Export::Shape {
234 symbol: value_shape_symbol(),
235 shape_id: None,
236 },
237 Export::Class {
238 symbol: rational_value_class_symbol(),
239 class_id: None,
240 },
241 ],
242 }
243 }
244
245 fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
246 let instance_shape = Arc::new(NumberLiteralShape::new(
247 number_domain(),
248 "RationalLiteral",
249 [
250 "number literal in the numbers/rational domain",
251 "matches Expr::Number where domain == numbers/rational",
252 ],
253 ));
254 let literal_class = Arc::new(NumberLiteralClass::new(
255 literal_class_symbol(),
256 number_domain(),
257 "rational",
258 "numerator/denominator",
259 literal_instance_shape_symbol(),
260 instance_shape.clone(),
261 ));
262 let value_shape = Arc::new(DomainNumberValueShape::new(
263 number_domain(),
264 "RationalValue",
265 [
266 "number value in the numbers/rational domain",
267 "accepts any NumberValue where domain == numbers/rational",
268 ],
269 ));
270
271 linker.number_domain_value(
272 number_domain(),
273 DefaultFactory
274 .opaque(Arc::new(RationalNumberDomain))
275 .expect("number domain should be boxable"),
276 )?;
277 let literal_class_id = linker.class_value(
278 literal_class_symbol(),
279 DefaultFactory
280 .opaque(literal_class.clone())
281 .expect("number literal class should be boxable"),
282 )?;
283 literal_class.set_id(literal_class_id);
284 register_rational_value_class(linker)?;
285
286 linker.shape_value(
287 literal_instance_shape_symbol(),
288 shape_value(literal_instance_shape_symbol(), instance_shape),
289 )?;
290 linker.shape_value(
291 value_shape_symbol(),
292 shape_value(value_shape_symbol(), value_shape),
293 )?;
294
295 register_promotions(linker);
296 let binary = [
297 (
298 add_symbol(),
299 rational_add_rule as RationalRuleFn,
300 rational_add_value_rule as ValueRuleFn,
301 ),
302 (sub_symbol(), rational_sub_rule, rational_sub_value_rule),
303 (mul_symbol(), rational_mul_rule, rational_mul_value_rule),
304 (div_symbol(), rational_div_rule, rational_div_value_rule),
305 (pow_symbol(), rational_pow_rule, rational_pow_value_rule),
306 ]
307 .into_iter()
308 .map(|(operator, literal_apply, value_apply)| ScalarBinaryOp {
309 operator,
310 literal_cost: 0,
311 literal_apply,
312 value_cost: 1,
313 value_apply,
314 })
315 .collect();
316 let ops = ScalarOps {
317 domain: number_domain(),
318 binary,
319 unary: vec![ScalarUnaryOp {
320 operator: Symbol::qualified("math", "neg"),
321 literal_cost: 0,
322 literal_apply: rational_neg_rule,
323 value_cost: 1,
324 value_apply: rational_neg_value_rule,
325 }],
326 reduction: vec![
327 ScalarReductionOp {
328 operator: Symbol::qualified("math", "sum"),
329 literal_cost: 0,
330 literal_apply: rational_sum_rule,
331 value_cost: 1,
332 value_apply: rational_sum_value_rule,
333 },
334 ScalarReductionOp {
335 operator: Symbol::qualified("math", "product"),
336 literal_cost: 0,
337 literal_apply: rational_product_rule,
338 value_cost: 1,
339 value_apply: rational_product_value_rule,
340 },
341 ],
342 };
343 install_scalar_ops(linker, &ops);
344 Ok(())
345 }
346}
347
348fn register_rational_value_class(linker: &mut Linker<'_>) -> Result<ClassId> {
349 let rational_value_class = Arc::new(RationalValueClass::new());
350 let rational_class_id = linker.class_value(
351 rational_value_class_symbol(),
352 DefaultFactory
353 .opaque(rational_value_class.clone())
354 .expect("rational value class should be boxable"),
355 )?;
356 rational_value_class.set_id(rational_class_id);
357 Ok(rational_class_id)
358}
359
360fn install_rational_value_citizen(linker: &mut Linker<'_>) -> Result<()> {
361 register_rational_value_class(linker).map(|_| ())
362}
363
364fn conformance_rational_value_citizen(cx: &mut sim_kernel::Cx) -> Result<()> {
365 let num = cx
366 .factory()
367 .number_literal(domains::i64(), "3".to_owned())?;
368 let den = cx
369 .factory()
370 .number_literal(domains::bigint(), "5".to_owned())?;
371 let value = super::value::make_rational(cx, num, den)?;
372 sim_citizen::check_value_fixture(cx, value)
373}
374
375sim_citizen::inventory::submit! {
376 sim_citizen::CitizenInfo {
377 symbol: "numbers/Rational",
378 version: 0,
379 crate_name: env!("CARGO_PKG_NAME"),
380 arity: 2,
381 install: install_rational_value_citizen,
382 conformance: conformance_rational_value_citizen,
383 }
384}
385
386fn register_promotions(linker: &mut Linker<'_>) {
387 for domain in integer_domains() {
388 linker.promotion_rule(sim_kernel::PromotionRule {
389 from_domain: domain.clone(),
390 to_domain: number_domain(),
391 cost: 1,
392 convert: promote_integer_literal_to_rational,
393 });
394 linker.value_promotion_rule(ValuePromotionRule {
395 from_domain: domain,
396 to_domain: number_domain(),
397 cost: 1,
398 convert: promote_integer_value_to_rational,
399 });
400 }
401 linker.promotion_rule(sim_kernel::PromotionRule {
402 from_domain: f64_domain(),
403 to_domain: number_domain(),
404 cost: 1,
405 convert: promote_f64_literal_to_rational,
406 });
407 linker.value_promotion_rule(ValuePromotionRule {
408 from_domain: f64_domain(),
409 to_domain: number_domain(),
410 cost: 1,
411 convert: promote_f64_value_to_rational,
412 });
413 linker.promotion_rule(sim_kernel::PromotionRule {
414 from_domain: number_domain(),
415 to_domain: f64_domain(),
416 cost: 50,
417 convert: promote_rational_literal_to_f64,
418 });
419 linker.value_promotion_rule(ValuePromotionRule {
420 from_domain: number_domain(),
421 to_domain: f64_domain(),
422 cost: 50,
423 convert: promote_rational_value_to_f64,
424 });
425}
426
427fn integer_domains() -> Vec<Symbol> {
428 domains::integer_domains()
429}