1#![forbid(unsafe_code)]
2
3use std::sync::Arc;
8
9use sim_kernel::{
10 AbiVersion, DefaultFactory, Dependency, Export, Expr, Factory, Lib, LibManifest, LibTarget,
11 Linker, NumberDomain, NumberLiteral, Object, PromotionRule, Result, Symbol, Value,
12 ValuePromotionRule, Version,
13};
14use sim_lib_numbers_core::{
15 DomainNumberValueShape, NumberDomainTableSpec, ScalarBinaryOp, ScalarOps, domains,
16 install_scalar_ops, number_domain_table,
17};
18use sim_shape::shape_value;
19
20use crate::literal::{
21 NumberLiteralClass, NumberLiteralShape, class_surface_or_symbol, shape_surface_or_symbol,
22 value_instance_shape_symbol,
23};
24
25pub fn number_domain() -> Symbol {
28 domains::bool()
29}
30
31fn literal_class_symbol() -> Symbol {
32 domains::literal_class("bool")
33}
34
35pub(crate) fn literal_instance_shape_symbol() -> Symbol {
36 Symbol::qualified(literal_class_symbol().to_string(), "instance-shape")
37}
38
39fn value_shape_symbol() -> Symbol {
40 value_instance_shape_symbol()
41}
42
43fn u8_domain() -> Symbol {
44 domains::u8()
45}
46
47fn i64_domain() -> Symbol {
48 domains::i64()
49}
50
51fn f64_domain() -> Symbol {
52 domains::f64()
53}
54
55fn add_symbol() -> Symbol {
56 Symbol::qualified("math", "add")
57}
58
59fn sub_symbol() -> Symbol {
60 Symbol::qualified("math", "sub")
61}
62
63fn mul_symbol() -> Symbol {
64 Symbol::qualified("math", "mul")
65}
66
67#[sim_citizen_derive::non_citizen(
68 reason = "numbers/bool number-domain marker; reconstruct by loading the bool number lib",
69 kind = "marker",
70 descriptor = "numbers/bool"
71)]
72pub struct BoolNumberDomain;
76
77impl NumberDomain for BoolNumberDomain {
78 fn symbol(&self) -> Symbol {
79 number_domain()
80 }
81
82 fn parse_priority(&self) -> i32 {
83 -10
84 }
85
86 fn parse_literal(&self, cx: &mut sim_kernel::Cx, text: &str) -> Result<Option<Value>> {
87 match text {
88 "true" => cx
89 .factory()
90 .number_literal(number_domain(), "true".to_owned())
91 .map(Some),
92 "false" => cx
93 .factory()
94 .number_literal(number_domain(), "false".to_owned())
95 .map(Some),
96 _ => Ok(None),
97 }
98 }
99
100 fn encode_literal(
101 &self,
102 cx: &mut sim_kernel::Cx,
103 value: Value,
104 ) -> Result<Option<NumberLiteral>> {
105 match value.object().as_expr(cx)? {
106 Expr::Number(number) if number.domain == number_domain() => Ok(Some(number)),
107 Expr::Bool(value) => Ok(Some(NumberLiteral {
108 domain: number_domain(),
109 canonical: if value { "true" } else { "false" }.to_owned(),
110 })),
111 _ => Ok(None),
112 }
113 }
114
115 fn promotions(&self) -> Vec<PromotionRule> {
116 vec![
117 PromotionRule {
118 from_domain: number_domain(),
119 to_domain: u8_domain(),
120 cost: 1,
121 convert: promote_bool_to_u8,
122 },
123 PromotionRule {
124 from_domain: number_domain(),
125 to_domain: i64_domain(),
126 cost: 2,
127 convert: promote_bool_to_i64,
128 },
129 PromotionRule {
130 from_domain: number_domain(),
131 to_domain: f64_domain(),
132 cost: 4,
133 convert: promote_bool_to_f64,
134 },
135 ]
136 }
137}
138
139impl Object for BoolNumberDomain {
140 fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
141 Ok("#<number-domain numbers/bool>".to_owned())
142 }
143
144 fn as_any(&self) -> &dyn std::any::Any {
145 self
146 }
147}
148
149impl sim_kernel::ObjectCompat for BoolNumberDomain {
150 fn class(&self, cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
151 sim_lib_numbers_core::number_domain_class_stub(cx)
152 }
153 fn as_expr(&self, _cx: &mut sim_kernel::Cx) -> Result<Expr> {
154 Ok(Expr::Symbol(number_domain()))
155 }
156 fn as_table(&self, cx: &mut sim_kernel::Cx) -> Result<Value> {
157 let literal_class = class_surface_or_symbol(cx, literal_class_symbol())?;
158 let instance_shape = shape_surface_or_symbol(cx, literal_instance_shape_symbol())?;
159 let value_shape = shape_surface_or_symbol(cx, value_shape_symbol())?;
160 number_domain_table(
161 cx,
162 NumberDomainTableSpec::new(
163 number_domain(),
164 "boolean",
165 "true|false",
166 -10,
167 literal_class,
168 instance_shape,
169 value_shape,
170 ),
171 )
172 }
173 fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
174 Some(self)
175 }
176}
177
178pub struct BoolNumbersLib;
196
197impl BoolNumbersLib {
198 pub fn new() -> Self {
200 Self
201 }
202}
203
204impl Default for BoolNumbersLib {
205 fn default() -> Self {
206 Self::new()
207 }
208}
209
210impl Lib for BoolNumbersLib {
211 fn manifest(&self) -> LibManifest {
212 LibManifest {
213 id: number_domain(),
214 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
215 abi: AbiVersion { major: 0, minor: 1 },
216 target: LibTarget::HostRegistered,
217 requires: Vec::<Dependency>::new(),
218 capabilities: Vec::new(),
219 exports: vec![
220 Export::NumberDomain {
221 symbol: number_domain(),
222 number_domain_id: None,
223 },
224 Export::Class {
225 symbol: literal_class_symbol(),
226 class_id: None,
227 },
228 Export::Shape {
229 symbol: literal_instance_shape_symbol(),
230 shape_id: None,
231 },
232 Export::Shape {
233 symbol: value_shape_symbol(),
234 shape_id: None,
235 },
236 ],
237 }
238 }
239
240 fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
241 let instance_shape = Arc::new(NumberLiteralShape::new(
242 number_domain(),
243 "BoolLiteral",
244 [
245 "number literal in the numbers/bool domain",
246 "matches Expr::Bool or Expr::Number where domain == numbers/bool",
247 ],
248 ));
249 let literal_class = Arc::new(NumberLiteralClass::new(
250 literal_class_symbol(),
251 number_domain(),
252 "boolean",
253 "true|false",
254 instance_shape.clone(),
255 ));
256 let value_shape = Arc::new(DomainNumberValueShape::new(
257 number_domain(),
258 "BoolValue",
259 [
260 "number value in the numbers/bool domain",
261 "accepts any NumberValue where domain == numbers/bool",
262 ],
263 ));
264 linker.number_domain_value(
265 number_domain(),
266 DefaultFactory
267 .opaque(Arc::new(BoolNumberDomain))
268 .expect("number domain should be boxable"),
269 )?;
270 let class_id = linker.class_value(
271 literal_class_symbol(),
272 DefaultFactory
273 .opaque(literal_class.clone())
274 .expect("number literal class should be boxable"),
275 )?;
276 literal_class.set_id(class_id);
277 linker.shape_value(
278 literal_instance_shape_symbol(),
279 shape_value(literal_instance_shape_symbol(), instance_shape),
280 )?;
281 linker.shape_value(
282 value_shape_symbol(),
283 shape_value(value_shape_symbol(), value_shape),
284 )?;
285 for rule in BoolNumberDomain.promotions() {
286 linker.promotion_rule(rule.clone());
287 linker.value_promotion_rule(bool_value_promotion_rule(&rule));
288 }
289 let binary = [
290 (
291 add_symbol(),
292 bool_add_rule as BoolRuleFn,
293 bool_add_value_rule as ValueRuleFn,
294 ),
295 (sub_symbol(), bool_sub_rule, bool_sub_value_rule),
296 (mul_symbol(), bool_mul_rule, bool_mul_value_rule),
297 ]
298 .into_iter()
299 .map(|(operator, literal_apply, value_apply)| ScalarBinaryOp {
300 operator,
301 literal_cost: 0,
302 literal_apply,
303 value_cost: 1,
304 value_apply,
305 })
306 .collect();
307 let ops = ScalarOps {
308 domain: number_domain(),
309 binary,
310 unary: Vec::new(),
311 reduction: Vec::new(),
312 };
313 install_scalar_ops(linker, &ops);
314 Ok(())
315 }
316}
317
318type BoolRuleFn = fn(&mut sim_kernel::Cx, NumberLiteral, NumberLiteral) -> Result<Value>;
319type ValueRuleFn = fn(&mut sim_kernel::Cx, Value, Value) -> Result<Value>;
320
321fn bool_add_rule(
322 cx: &mut sim_kernel::Cx,
323 left: NumberLiteral,
324 right: NumberLiteral,
325) -> Result<Value> {
326 let out = parse_bool_literal(left, "left")? || parse_bool_literal(right, "right")?;
327 cx.factory().bool(out)
328}
329
330fn bool_sub_rule(
331 cx: &mut sim_kernel::Cx,
332 left: NumberLiteral,
333 right: NumberLiteral,
334) -> Result<Value> {
335 let out = parse_bool_literal(left, "left")? ^ parse_bool_literal(right, "right")?;
336 cx.factory().bool(out)
337}
338
339fn bool_mul_rule(
340 cx: &mut sim_kernel::Cx,
341 left: NumberLiteral,
342 right: NumberLiteral,
343) -> Result<Value> {
344 let out = parse_bool_literal(left, "left")? && parse_bool_literal(right, "right")?;
345 cx.factory().bool(out)
346}
347
348fn bool_add_value_rule(cx: &mut sim_kernel::Cx, left: Value, right: Value) -> Result<Value> {
349 let left = expect_bool_literal(cx, left, "left")?;
350 let right = expect_bool_literal(cx, right, "right")?;
351 bool_add_rule(cx, left, right)
352}
353
354fn bool_sub_value_rule(cx: &mut sim_kernel::Cx, left: Value, right: Value) -> Result<Value> {
355 let left = expect_bool_literal(cx, left, "left")?;
356 let right = expect_bool_literal(cx, right, "right")?;
357 bool_sub_rule(cx, left, right)
358}
359
360fn bool_mul_value_rule(cx: &mut sim_kernel::Cx, left: Value, right: Value) -> Result<Value> {
361 let left = expect_bool_literal(cx, left, "left")?;
362 let right = expect_bool_literal(cx, right, "right")?;
363 bool_mul_rule(cx, left, right)
364}
365
366fn parse_bool_literal(number: NumberLiteral, side: &str) -> Result<bool> {
367 if number.domain != number_domain() {
368 return Err(sim_kernel::Error::Eval(format!(
369 "{side} operand expected number domain {}, found {}",
370 number_domain(),
371 number.domain
372 )));
373 }
374 match number.canonical.as_str() {
375 "true" => Ok(true),
376 "false" => Ok(false),
377 other => Err(sim_kernel::Error::Eval(format!(
378 "{side} operand was not a valid bool literal: {}",
379 other
380 ))),
381 }
382}
383
384fn expect_bool_literal(cx: &mut sim_kernel::Cx, value: Value, side: &str) -> Result<NumberLiteral> {
385 let Some(number) = cx.number_value_ref(value)? else {
386 return Err(sim_kernel::Error::Eval(format!(
387 "{side} operand expected number domain {}, found non-number",
388 number_domain()
389 )));
390 };
391 if number.domain != number_domain() {
392 return Err(sim_kernel::Error::Eval(format!(
393 "{side} operand expected number domain {}, found {}",
394 number_domain(),
395 number.domain
396 )));
397 }
398 match number.literal {
399 Some(literal) => Ok(literal),
400 None => Err(sim_kernel::Error::Eval(format!(
401 "{side} operand in {} does not have a canonical literal form",
402 number_domain()
403 ))),
404 }
405}
406
407fn bool_value_promotion_rule(rule: &PromotionRule) -> ValuePromotionRule {
408 let convert = if rule.to_domain == u8_domain() {
409 promote_bool_value_to_u8
410 } else if rule.to_domain == i64_domain() {
411 promote_bool_value_to_i64
412 } else {
413 promote_bool_value_to_f64
414 };
415 ValuePromotionRule {
416 from_domain: rule.from_domain.clone(),
417 to_domain: rule.to_domain.clone(),
418 cost: rule.cost,
419 convert,
420 }
421}
422
423fn promote_bool_value_to_u8(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
424 let literal = expect_bool_literal(cx, value, "operand")?;
425 let promoted = promote_bool_to_u8(cx, literal)?;
426 cx.factory()
427 .number_literal(promoted.domain, promoted.canonical)
428}
429
430fn promote_bool_value_to_i64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
431 let literal = expect_bool_literal(cx, value, "operand")?;
432 let promoted = promote_bool_to_i64(cx, literal)?;
433 cx.factory()
434 .number_literal(promoted.domain, promoted.canonical)
435}
436
437fn promote_bool_value_to_f64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
438 let literal = expect_bool_literal(cx, value, "operand")?;
439 let promoted = promote_bool_to_f64(cx, literal)?;
440 cx.factory()
441 .number_literal(promoted.domain, promoted.canonical)
442}
443
444fn promote_bool_to_u8(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
445 Ok(NumberLiteral {
446 domain: u8_domain(),
447 canonical: if parse_bool_literal(number, "operand")? {
448 "1"
449 } else {
450 "0"
451 }
452 .to_owned(),
453 })
454}
455
456fn promote_bool_to_i64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
457 Ok(NumberLiteral {
458 domain: i64_domain(),
459 canonical: if parse_bool_literal(number, "operand")? {
460 "1"
461 } else {
462 "0"
463 }
464 .to_owned(),
465 })
466}
467
468fn promote_bool_to_f64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
469 Ok(NumberLiteral {
470 domain: f64_domain(),
471 canonical: if parse_bool_literal(number, "operand")? {
472 "1"
473 } else {
474 "0"
475 }
476 .to_owned(),
477 })
478}