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)]
211struct FixedNumberDomain {
212 spec: DomainSpec,
213}
214
215impl NumberDomain for FixedNumberDomain {
216 fn symbol(&self) -> Symbol {
217 domain_symbol(self.spec)
218 }
219
220 fn parse_priority(&self) -> i32 {
221 self.spec.parse_priority
222 }
223
224 fn parse_literal(&self, cx: &mut sim_kernel::Cx, text: &str) -> Result<Option<Value>> {
225 if text.contains(['.', '/']) {
226 return Ok(None);
227 }
228 let canonical = match self.spec.name {
229 "i8" => text.parse::<i8>().ok().map(|value| value.to_string()),
230 "u8" => text.parse::<u8>().ok().map(|value| value.to_string()),
231 "i16" => text.parse::<i16>().ok().map(|value| value.to_string()),
232 "u16" => text.parse::<u16>().ok().map(|value| value.to_string()),
233 "i32" => text.parse::<i32>().ok().map(|value| value.to_string()),
234 "u32" => text.parse::<u32>().ok().map(|value| value.to_string()),
235 "u64" => text.parse::<u64>().ok().map(|value| value.to_string()),
236 "i128" => text.parse::<i128>().ok().map(|value| value.to_string()),
237 "u128" => text.parse::<u128>().ok().map(|value| value.to_string()),
238 "isize" => text.parse::<isize>().ok().map(|value| value.to_string()),
239 "usize" => text.parse::<usize>().ok().map(|value| value.to_string()),
240 _ => None,
241 };
242 match canonical {
243 Some(canonical) => cx
244 .factory()
245 .number_literal(self.symbol(), canonical)
246 .map(Some),
247 None => Ok(None),
248 }
249 }
250
251 fn encode_literal(
252 &self,
253 cx: &mut sim_kernel::Cx,
254 value: Value,
255 ) -> Result<Option<NumberLiteral>> {
256 match value.object().as_expr(cx)? {
257 Expr::Number(number) if number.domain == self.symbol() => Ok(Some(number)),
258 _ => Ok(None),
259 }
260 }
261}
262
263impl Object for FixedNumberDomain {
264 fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
265 Ok(format!("#<number-domain {}>", domain_symbol(self.spec)))
266 }
267
268 fn as_any(&self) -> &dyn std::any::Any {
269 self
270 }
271}
272
273impl sim_kernel::ObjectCompat for FixedNumberDomain {
274 fn class(&self, cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
275 sim_lib_numbers_core::number_domain_class_stub(cx)
276 }
277 fn as_expr(&self, _cx: &mut sim_kernel::Cx) -> Result<Expr> {
278 Ok(Expr::Symbol(domain_symbol(self.spec)))
279 }
280 fn as_table(&self, cx: &mut sim_kernel::Cx) -> Result<Value> {
281 let literal_class = class_surface_or_symbol(cx, literal_class_symbol(self.spec))?;
282 let instance_shape = shape_surface_or_symbol(cx, literal_instance_shape_symbol(self.spec))?;
283 let value_shape = shape_surface_or_symbol(cx, value_shape_symbol(self.spec))?;
284 number_domain_table(
285 cx,
286 NumberDomainTableSpec::new(
287 domain_symbol(self.spec),
288 "integer",
289 self.spec.name,
290 self.spec.parse_priority,
291 literal_class,
292 instance_shape,
293 value_shape,
294 ),
295 )
296 }
297 fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
298 Some(self)
299 }
300}
301
302#[derive(Clone, Copy)]
303struct PromotionSpec {
304 from: &'static str,
305 to: &'static str,
306 cost: u16,
307 literal_convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
308 value_convert: fn(&mut sim_kernel::Cx, Value) -> Result<Value>,
309}
310
311fn promotion_specs() -> Vec<PromotionSpec> {
312 vec![
313 spec("i8", "i16", 1, promote_to_i16, promote_value_to_i16),
314 spec("i16", "i32", 1, promote_to_i32, promote_value_to_i32),
315 spec("i32", "i64", 1, promote_to_i64, promote_value_to_i64),
316 spec("i64", "i128", 1, promote_to_i128, promote_value_to_i128),
317 spec("u8", "u16", 1, promote_to_u16, promote_value_to_u16),
318 spec("u16", "u32", 1, promote_to_u32, promote_value_to_u32),
319 spec("u32", "u64", 1, promote_to_u64, promote_value_to_u64),
320 spec("u64", "u128", 1, promote_to_u128, promote_value_to_u128),
321 spec("u8", "i16", 1, promote_to_i16, promote_value_to_i16),
322 spec("u16", "i32", 1, promote_to_i32, promote_value_to_i32),
323 spec("u32", "i64", 1, promote_to_i64, promote_value_to_i64),
324 spec("u64", "i128", 1, promote_to_i128, promote_value_to_i128),
325 spec("isize", "i128", 1, promote_to_i128, promote_value_to_i128),
326 spec("usize", "u128", 1, promote_to_u128, promote_value_to_u128),
327 spec("i8", "f64", 50, promote_to_f64, promote_value_to_f64),
328 spec("u8", "f64", 50, promote_to_f64, promote_value_to_f64),
329 spec("i16", "f64", 50, promote_to_f64, promote_value_to_f64),
330 spec("u16", "f64", 50, promote_to_f64, promote_value_to_f64),
331 spec("i32", "f64", 50, promote_to_f64, promote_value_to_f64),
332 spec("u32", "f64", 50, promote_to_f64, promote_value_to_f64),
333 spec("i64", "f64", 50, promote_to_f64, promote_value_to_f64),
334 spec("u64", "f64", 50, promote_to_f64, promote_value_to_f64),
335 spec("i128", "f64", 50, promote_to_f64, promote_value_to_f64),
336 spec("u128", "f64", 50, promote_to_f64, promote_value_to_f64),
337 spec("isize", "f64", 50, promote_to_f64, promote_value_to_f64),
338 spec("usize", "f64", 50, promote_to_f64, promote_value_to_f64),
339 spec("i8", "f32", 100, promote_to_f32, promote_value_to_f32),
340 spec("u8", "f32", 100, promote_to_f32, promote_value_to_f32),
341 spec("i16", "f32", 100, promote_to_f32, promote_value_to_f32),
342 spec("u16", "f32", 100, promote_to_f32, promote_value_to_f32),
343 spec("i32", "f32", 100, promote_to_f32, promote_value_to_f32),
344 spec("u32", "f32", 100, promote_to_f32, promote_value_to_f32),
345 spec("i64", "f32", 100, promote_to_f32, promote_value_to_f32),
346 spec("u64", "f32", 100, promote_to_f32, promote_value_to_f32),
347 spec("i128", "f32", 100, promote_to_f32, promote_value_to_f32),
348 spec("u128", "f32", 100, promote_to_f32, promote_value_to_f32),
349 spec("isize", "f32", 100, promote_to_f32, promote_value_to_f32),
350 spec("usize", "f32", 100, promote_to_f32, promote_value_to_f32),
351 ]
352}
353
354fn spec(
355 from: &'static str,
356 to: &'static str,
357 cost: u16,
358 literal_convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
359 value_convert: fn(&mut sim_kernel::Cx, Value) -> Result<Value>,
360) -> PromotionSpec {
361 PromotionSpec {
362 from,
363 to,
364 cost,
365 literal_convert,
366 value_convert,
367 }
368}
369
370fn promotion_rules() -> Vec<PromotionRule> {
371 promotion_specs()
372 .into_iter()
373 .map(|spec| PromotionRule {
374 from_domain: domains::domain(spec.from),
375 to_domain: domains::domain(spec.to),
376 cost: spec.cost,
377 convert: spec.literal_convert,
378 })
379 .collect()
380}
381
382fn value_promotion_rules() -> Vec<ValuePromotionRule> {
383 promotion_specs()
384 .into_iter()
385 .map(|spec| ValuePromotionRule {
386 from_domain: domains::domain(spec.from),
387 to_domain: domains::domain(spec.to),
388 cost: spec.cost,
389 convert: spec.value_convert,
390 })
391 .collect()
392}
393
394fn promote_value_to_i16(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
395 promote_value_to_target(cx, value, "i16", promote_to_i16)
396}
397fn promote_value_to_i32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
398 promote_value_to_target(cx, value, "i32", promote_to_i32)
399}
400fn promote_value_to_i64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
401 promote_value_to_target(cx, value, "i64", promote_to_i64)
402}
403fn promote_value_to_i128(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
404 promote_value_to_target(cx, value, "i128", promote_to_i128)
405}
406fn promote_value_to_u16(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
407 promote_value_to_target(cx, value, "u16", promote_to_u16)
408}
409fn promote_value_to_u32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
410 promote_value_to_target(cx, value, "u32", promote_to_u32)
411}
412fn promote_value_to_u64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
413 promote_value_to_target(cx, value, "u64", promote_to_u64)
414}
415fn promote_value_to_u128(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
416 promote_value_to_target(cx, value, "u128", promote_to_u128)
417}
418fn promote_value_to_f32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
419 promote_value_to_target(cx, value, "f32", promote_to_f32)
420}
421fn promote_value_to_f64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
422 promote_value_to_target(cx, value, "f64", promote_to_f64)
423}
424
425fn promote_value_to_target(
426 cx: &mut sim_kernel::Cx,
427 value: Value,
428 target: &str,
429 convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
430) -> Result<Value> {
431 let Some(number) = cx.number_value_ref(value)? else {
432 return Err(sim_kernel::Error::Eval(format!(
433 "fixed promotion to {} expected a number value",
434 domains::domain(target)
435 )));
436 };
437 let literal = number.literal.ok_or_else(|| {
438 sim_kernel::Error::Eval(format!(
439 "fixed promotion from {} to {} requires a canonical literal form",
440 number.domain,
441 domains::domain(target)
442 ))
443 })?;
444 let promoted = convert(cx, literal)?;
445 cx.factory()
446 .number_literal(promoted.domain, promoted.canonical)
447}
448
449fn promote_to_i16(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
450 promote_to_target(number, "i16")
451}
452fn promote_to_i32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
453 promote_to_target(number, "i32")
454}
455fn promote_to_i64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
456 promote_to_target(number, "i64")
457}
458fn promote_to_i128(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
459 promote_to_target(number, "i128")
460}
461fn promote_to_u16(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
462 promote_to_target(number, "u16")
463}
464fn promote_to_u32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
465 promote_to_target(number, "u32")
466}
467fn promote_to_u64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
468 promote_to_target(number, "u64")
469}
470fn promote_to_u128(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
471 promote_to_target(number, "u128")
472}
473fn promote_to_f32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
474 promote_to_target(number, "f32")
475}
476fn promote_to_f64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
477 promote_to_target(number, "f64")
478}
479
480fn promote_to_target(number: NumberLiteral, target: &str) -> Result<NumberLiteral> {
481 Ok(NumberLiteral {
482 domain: domains::domain(target),
483 canonical: number.canonical,
484 })
485}
486
487fn domain_symbol(spec: DomainSpec) -> Symbol {
488 domains::domain(spec.name)
489}
490
491fn literal_class_symbol(spec: DomainSpec) -> Symbol {
492 domains::literal_class(spec.name)
493}
494
495fn literal_instance_shape_symbol(spec: DomainSpec) -> Symbol {
496 Symbol::qualified(literal_class_symbol(spec).to_string(), "instance-shape")
497}
498
499fn value_shape_symbol(spec: DomainSpec) -> Symbol {
500 domains::value_shape(&domain_symbol(spec))
501}