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, domains, number_domain_table,
16};
17use sim_shape::shape_value;
18
19use crate::literal::{
20 NumberLiteralClass, NumberLiteralShape, class_surface_or_symbol, shape_surface_or_symbol,
21};
22
23#[derive(Clone, Copy)]
24struct DomainSpec {
25 name: &'static str,
26 parse_priority: i32,
27}
28
29const FIXED_DOMAINS: [DomainSpec; 11] = [
30 DomainSpec {
31 name: "i8",
32 parse_priority: 1,
33 },
34 DomainSpec {
35 name: "u8",
36 parse_priority: 1,
37 },
38 DomainSpec {
39 name: "i16",
40 parse_priority: 1,
41 },
42 DomainSpec {
43 name: "u16",
44 parse_priority: 1,
45 },
46 DomainSpec {
47 name: "i32",
48 parse_priority: 1,
49 },
50 DomainSpec {
51 name: "u32",
52 parse_priority: 1,
53 },
54 DomainSpec {
55 name: "u64",
56 parse_priority: 1,
57 },
58 DomainSpec {
59 name: "i128",
60 parse_priority: 5,
61 },
62 DomainSpec {
63 name: "u128",
64 parse_priority: 5,
65 },
66 DomainSpec {
67 name: "isize",
68 parse_priority: 1,
69 },
70 DomainSpec {
71 name: "usize",
72 parse_priority: 1,
73 },
74];
75
76pub struct FixedNumbersLib;
98
99impl FixedNumbersLib {
100 pub fn new() -> Self {
102 Self
103 }
104}
105
106impl Default for FixedNumbersLib {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl Lib for FixedNumbersLib {
113 fn manifest(&self) -> LibManifest {
114 let mut exports = Vec::new();
115 for spec in FIXED_DOMAINS {
116 exports.push(Export::NumberDomain {
117 symbol: domain_symbol(spec),
118 number_domain_id: None,
119 });
120 exports.push(Export::Class {
121 symbol: literal_class_symbol(spec),
122 class_id: None,
123 });
124 exports.push(Export::Shape {
125 symbol: literal_instance_shape_symbol(spec),
126 shape_id: None,
127 });
128 exports.push(Export::Shape {
129 symbol: value_shape_symbol(spec),
130 shape_id: None,
131 });
132 }
133 LibManifest {
134 id: domains::fixed(),
135 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
136 abi: AbiVersion { major: 0, minor: 1 },
137 target: LibTarget::HostRegistered,
138 requires: Vec::<Dependency>::new(),
139 capabilities: Vec::new(),
140 exports,
141 }
142 }
143
144 fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
145 for spec in FIXED_DOMAINS {
146 install_domain(linker, spec)?;
147 }
148 for rule in promotion_rules() {
149 linker.promotion_rule(rule);
150 }
151 for rule in value_promotion_rules() {
152 linker.value_promotion_rule(rule);
153 }
154 Ok(())
155 }
156}
157
158fn install_domain(linker: &mut Linker<'_>, spec: DomainSpec) -> Result<()> {
159 let domain = Arc::new(FixedNumberDomain { spec });
160 let literal_shape = Arc::new(NumberLiteralShape::new(
161 domain_symbol(spec),
162 "FixedIntegerLiteral",
163 [
164 "number literal in a fixed integer domain",
165 "matches Expr::Number where domain matches the fixed integer domain",
166 ],
167 ));
168 let literal_class = Arc::new(NumberLiteralClass::new(
169 literal_class_symbol(spec),
170 domain_symbol(spec),
171 "integer",
172 spec.name,
173 literal_shape.clone(),
174 ));
175 let value_shape = Arc::new(DomainNumberValueShape::new(
176 domain_symbol(spec),
177 "FixedIntegerValue",
178 [
179 "number value in a fixed integer domain",
180 "accepts any NumberValue where domain matches the fixed integer domain",
181 ],
182 ));
183 linker.number_domain_value(
184 domain_symbol(spec),
185 DefaultFactory
186 .opaque(domain)
187 .expect("number domain should be boxable"),
188 )?;
189 let class_id = linker.class_value(
190 literal_class_symbol(spec),
191 DefaultFactory
192 .opaque(literal_class.clone())
193 .expect("number literal class should be boxable"),
194 )?;
195 literal_class.set_id(class_id);
196 linker.shape_value(
197 literal_instance_shape_symbol(spec),
198 shape_value(literal_instance_shape_symbol(spec), literal_shape),
199 )?;
200 linker.shape_value(
201 value_shape_symbol(spec),
202 shape_value(value_shape_symbol(spec), value_shape),
203 )?;
204 Ok(())
205}
206
207#[sim_citizen_derive::non_citizen(
208 reason = "fixed-width number-domain marker; reconstruct by loading the fixed number lib",
209 kind = "marker",
210 descriptor = "numbers/fixed"
211)]
212struct FixedNumberDomain {
213 spec: DomainSpec,
214}
215
216impl NumberDomain for FixedNumberDomain {
217 fn symbol(&self) -> Symbol {
218 domain_symbol(self.spec)
219 }
220
221 fn parse_priority(&self) -> i32 {
222 self.spec.parse_priority
223 }
224
225 fn parse_literal(&self, cx: &mut sim_kernel::Cx, text: &str) -> Result<Option<Value>> {
226 if text.contains(['.', '/']) {
227 return Ok(None);
228 }
229 let canonical = match self.spec.name {
230 "i8" => text.parse::<i8>().ok().map(|value| value.to_string()),
231 "u8" => text.parse::<u8>().ok().map(|value| value.to_string()),
232 "i16" => text.parse::<i16>().ok().map(|value| value.to_string()),
233 "u16" => text.parse::<u16>().ok().map(|value| value.to_string()),
234 "i32" => text.parse::<i32>().ok().map(|value| value.to_string()),
235 "u32" => text.parse::<u32>().ok().map(|value| value.to_string()),
236 "u64" => text.parse::<u64>().ok().map(|value| value.to_string()),
237 "i128" => text.parse::<i128>().ok().map(|value| value.to_string()),
238 "u128" => text.parse::<u128>().ok().map(|value| value.to_string()),
239 "isize" => text.parse::<isize>().ok().map(|value| value.to_string()),
240 "usize" => text.parse::<usize>().ok().map(|value| value.to_string()),
241 _ => None,
242 };
243 match canonical {
244 Some(canonical) => cx
245 .factory()
246 .number_literal(self.symbol(), canonical)
247 .map(Some),
248 None => Ok(None),
249 }
250 }
251
252 fn encode_literal(
253 &self,
254 cx: &mut sim_kernel::Cx,
255 value: Value,
256 ) -> Result<Option<NumberLiteral>> {
257 match value.object().as_expr(cx)? {
258 Expr::Number(number) if number.domain == self.symbol() => Ok(Some(number)),
259 _ => Ok(None),
260 }
261 }
262}
263
264impl Object for FixedNumberDomain {
265 fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
266 Ok(format!("#<number-domain {}>", domain_symbol(self.spec)))
267 }
268
269 fn as_any(&self) -> &dyn std::any::Any {
270 self
271 }
272}
273
274impl sim_kernel::ObjectCompat for FixedNumberDomain {
275 fn class(&self, cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
276 sim_lib_numbers_core::number_domain_class_stub(cx)
277 }
278 fn as_expr(&self, _cx: &mut sim_kernel::Cx) -> Result<Expr> {
279 Ok(Expr::Symbol(domain_symbol(self.spec)))
280 }
281 fn as_table(&self, cx: &mut sim_kernel::Cx) -> Result<Value> {
282 let literal_class = class_surface_or_symbol(cx, literal_class_symbol(self.spec))?;
283 let instance_shape = shape_surface_or_symbol(cx, literal_instance_shape_symbol(self.spec))?;
284 let value_shape = shape_surface_or_symbol(cx, value_shape_symbol(self.spec))?;
285 number_domain_table(
286 cx,
287 NumberDomainTableSpec::new(
288 domain_symbol(self.spec),
289 "integer",
290 self.spec.name,
291 self.spec.parse_priority,
292 literal_class,
293 instance_shape,
294 value_shape,
295 ),
296 )
297 }
298 fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
299 Some(self)
300 }
301}
302
303#[derive(Clone, Copy)]
304struct PromotionSpec {
305 from: &'static str,
306 to: &'static str,
307 cost: u16,
308 literal_convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
309 value_convert: fn(&mut sim_kernel::Cx, Value) -> Result<Value>,
310}
311
312fn promotion_specs() -> Vec<PromotionSpec> {
313 vec![
314 spec("i8", "i16", 1, promote_to_i16, promote_value_to_i16),
315 spec("i16", "i32", 1, promote_to_i32, promote_value_to_i32),
316 spec("i32", "i64", 1, promote_to_i64, promote_value_to_i64),
317 spec("i64", "i128", 1, promote_to_i128, promote_value_to_i128),
318 spec("u8", "u16", 1, promote_to_u16, promote_value_to_u16),
319 spec("u16", "u32", 1, promote_to_u32, promote_value_to_u32),
320 spec("u32", "u64", 1, promote_to_u64, promote_value_to_u64),
321 spec("u64", "u128", 1, promote_to_u128, promote_value_to_u128),
322 spec("u8", "i16", 1, promote_to_i16, promote_value_to_i16),
323 spec("u16", "i32", 1, promote_to_i32, promote_value_to_i32),
324 spec("u32", "i64", 1, promote_to_i64, promote_value_to_i64),
325 spec("u64", "i128", 1, promote_to_i128, promote_value_to_i128),
326 spec("isize", "i128", 1, promote_to_i128, promote_value_to_i128),
327 spec("usize", "u128", 1, promote_to_u128, promote_value_to_u128),
328 spec("i8", "f64", 50, promote_to_f64, promote_value_to_f64),
329 spec("u8", "f64", 50, promote_to_f64, promote_value_to_f64),
330 spec("i16", "f64", 50, promote_to_f64, promote_value_to_f64),
331 spec("u16", "f64", 50, promote_to_f64, promote_value_to_f64),
332 spec("i32", "f64", 50, promote_to_f64, promote_value_to_f64),
333 spec("u32", "f64", 50, promote_to_f64, promote_value_to_f64),
334 spec("i64", "f64", 50, promote_to_f64, promote_value_to_f64),
335 spec("u64", "f64", 50, promote_to_f64, promote_value_to_f64),
336 spec("i128", "f64", 50, promote_to_f64, promote_value_to_f64),
337 spec("u128", "f64", 50, promote_to_f64, promote_value_to_f64),
338 spec("isize", "f64", 50, promote_to_f64, promote_value_to_f64),
339 spec("usize", "f64", 50, promote_to_f64, promote_value_to_f64),
340 spec("i8", "f32", 100, promote_to_f32, promote_value_to_f32),
341 spec("u8", "f32", 100, promote_to_f32, promote_value_to_f32),
342 spec("i16", "f32", 100, promote_to_f32, promote_value_to_f32),
343 spec("u16", "f32", 100, promote_to_f32, promote_value_to_f32),
344 spec("i32", "f32", 100, promote_to_f32, promote_value_to_f32),
345 spec("u32", "f32", 100, promote_to_f32, promote_value_to_f32),
346 spec("i64", "f32", 100, promote_to_f32, promote_value_to_f32),
347 spec("u64", "f32", 100, promote_to_f32, promote_value_to_f32),
348 spec("i128", "f32", 100, promote_to_f32, promote_value_to_f32),
349 spec("u128", "f32", 100, promote_to_f32, promote_value_to_f32),
350 spec("isize", "f32", 100, promote_to_f32, promote_value_to_f32),
351 spec("usize", "f32", 100, promote_to_f32, promote_value_to_f32),
352 ]
353}
354
355fn spec(
356 from: &'static str,
357 to: &'static str,
358 cost: u16,
359 literal_convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
360 value_convert: fn(&mut sim_kernel::Cx, Value) -> Result<Value>,
361) -> PromotionSpec {
362 PromotionSpec {
363 from,
364 to,
365 cost,
366 literal_convert,
367 value_convert,
368 }
369}
370
371fn promotion_rules() -> Vec<PromotionRule> {
372 promotion_specs()
373 .into_iter()
374 .map(|spec| PromotionRule {
375 from_domain: domains::domain(spec.from),
376 to_domain: domains::domain(spec.to),
377 cost: spec.cost,
378 convert: spec.literal_convert,
379 })
380 .collect()
381}
382
383fn value_promotion_rules() -> Vec<ValuePromotionRule> {
384 promotion_specs()
385 .into_iter()
386 .map(|spec| ValuePromotionRule {
387 from_domain: domains::domain(spec.from),
388 to_domain: domains::domain(spec.to),
389 cost: spec.cost,
390 convert: spec.value_convert,
391 })
392 .collect()
393}
394
395fn promote_value_to_i16(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
396 promote_value_to_target(cx, value, "i16", promote_to_i16)
397}
398fn promote_value_to_i32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
399 promote_value_to_target(cx, value, "i32", promote_to_i32)
400}
401fn promote_value_to_i64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
402 promote_value_to_target(cx, value, "i64", promote_to_i64)
403}
404fn promote_value_to_i128(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
405 promote_value_to_target(cx, value, "i128", promote_to_i128)
406}
407fn promote_value_to_u16(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
408 promote_value_to_target(cx, value, "u16", promote_to_u16)
409}
410fn promote_value_to_u32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
411 promote_value_to_target(cx, value, "u32", promote_to_u32)
412}
413fn promote_value_to_u64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
414 promote_value_to_target(cx, value, "u64", promote_to_u64)
415}
416fn promote_value_to_u128(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
417 promote_value_to_target(cx, value, "u128", promote_to_u128)
418}
419fn promote_value_to_f32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
420 promote_value_to_target(cx, value, "f32", promote_to_f32)
421}
422fn promote_value_to_f64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
423 promote_value_to_target(cx, value, "f64", promote_to_f64)
424}
425
426fn promote_value_to_target(
427 cx: &mut sim_kernel::Cx,
428 value: Value,
429 target: &str,
430 convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
431) -> Result<Value> {
432 let Some(number) = cx.number_value_ref(value)? else {
433 return Err(sim_kernel::Error::Eval(format!(
434 "fixed promotion to {} expected a number value",
435 domains::domain(target)
436 )));
437 };
438 let literal = number.literal.ok_or_else(|| {
439 sim_kernel::Error::Eval(format!(
440 "fixed promotion from {} to {} requires a canonical literal form",
441 number.domain,
442 domains::domain(target)
443 ))
444 })?;
445 let promoted = convert(cx, literal)?;
446 cx.factory()
447 .number_literal(promoted.domain, promoted.canonical)
448}
449
450fn promote_to_i16(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
451 promote_to_target(number, "i16")
452}
453fn promote_to_i32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
454 promote_to_target(number, "i32")
455}
456fn promote_to_i64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
457 promote_to_target(number, "i64")
458}
459fn promote_to_i128(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
460 promote_to_target(number, "i128")
461}
462fn promote_to_u16(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
463 promote_to_target(number, "u16")
464}
465fn promote_to_u32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
466 promote_to_target(number, "u32")
467}
468fn promote_to_u64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
469 promote_to_target(number, "u64")
470}
471fn promote_to_u128(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
472 promote_to_target(number, "u128")
473}
474fn promote_to_f32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
475 promote_to_target(number, "f32")
476}
477fn promote_to_f64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
478 promote_to_target(number, "f64")
479}
480
481fn promote_to_target(number: NumberLiteral, target: &str) -> Result<NumberLiteral> {
482 Ok(NumberLiteral {
483 domain: domains::domain(target),
484 canonical: number.canonical,
485 })
486}
487
488fn domain_symbol(spec: DomainSpec) -> Symbol {
489 domains::domain(spec.name)
490}
491
492fn literal_class_symbol(spec: DomainSpec) -> Symbol {
493 domains::literal_class(spec.name)
494}
495
496fn literal_instance_shape_symbol(spec: DomainSpec) -> Symbol {
497 Symbol::qualified(literal_class_symbol(spec).to_string(), "instance-shape")
498}
499
500fn value_shape_symbol(spec: DomainSpec) -> Symbol {
501 domains::value_shape(&domain_symbol(spec))
502}