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