Skip to main content

datex_core/libs/
core.rs

1use crate::{
2    collections::HashMap,
3    references::{
4        reference::Reference,
5        type_reference::{NominalTypeDeclaration, TypeReference},
6    },
7    runtime::memory::Memory,
8    types::definition::TypeDefinition,
9    values::{
10        core_value::CoreValue,
11        core_values::{
12            callable::{CallableBody, CallableKind, CallableSignature},
13            decimal::typed_decimal::DecimalTypeVariant,
14            integer::typed_integer::IntegerTypeVariant,
15            map::Map,
16            r#type::Type,
17        },
18        pointer::PointerAddress,
19        value::Value,
20        value_container::ValueContainer,
21    },
22};
23
24use crate::prelude::*;
25use core::{cell::RefCell, iter::once, result::Result};
26use datex_macros_internal::LibTypeString;
27use log::info;
28use strum::IntoEnumIterator;
29
30type CoreLibTypes = HashMap<CoreLibPointerId, Type>;
31type CoreLibVals = HashMap<CoreLibPointerId, ValueContainer>;
32
33#[cfg_attr(not(feature = "embassy_runtime"), thread_local)]
34pub static mut CORE_LIB_TYPES: Option<CoreLibTypes> = None;
35
36#[cfg_attr(not(feature = "embassy_runtime"), thread_local)]
37pub static mut CORE_LIB_VALS: Option<CoreLibVals> = None;
38
39fn with_full_core_lib<R>(
40    handler: impl FnOnce(&CoreLibTypes, &CoreLibVals) -> R,
41) -> R {
42    unsafe {
43        if CORE_LIB_TYPES.is_none() {
44            CORE_LIB_TYPES.replace(create_core_lib_types());
45        }
46        if CORE_LIB_VALS.is_none() {
47            CORE_LIB_VALS.replace(create_core_lib_vals());
48        }
49        handler(
50            CORE_LIB_TYPES.as_ref().unwrap_unchecked(),
51            CORE_LIB_VALS.as_ref().unwrap_unchecked(),
52        )
53    }
54}
55
56fn with_core_lib_types<R>(handler: impl FnOnce(&CoreLibTypes) -> R) -> R {
57    unsafe {
58        if CORE_LIB_TYPES.is_none() {
59            CORE_LIB_TYPES.replace(create_core_lib_types());
60        }
61        handler(CORE_LIB_TYPES.as_ref().unwrap_unchecked())
62    }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash, LibTypeString)]
66pub enum CoreLibPointerId {
67    Core,                                // #core
68    Type,                                // #core.type
69    Null,                                // #core.null
70    Boolean,                             // #core.boolean
71    Integer(Option<IntegerTypeVariant>), // #core.integer
72    Decimal(Option<DecimalTypeVariant>), // #core.decimal
73    Text,                                // #core.text
74    Endpoint,                            // #core.endpoint
75    List,                                // #core.List
76    Map,                                 // #core.Map
77    Callable,                            // #core.Callable
78    Unit,                                // #core.Unit
79    Never,                               // #core.never
80    Unknown,                             // #core.unknown
81    Print, // #core.print (function, might be removed later)
82    Range,
83}
84
85impl CoreLibPointerId {
86    const INTEGER_BASE: u16 = 100;
87    const DECIMAL_BASE: u16 = 300;
88
89    pub fn to_u16(&self) -> u16 {
90        match self {
91            CoreLibPointerId::Core => 0,
92            CoreLibPointerId::Null => 1,
93            CoreLibPointerId::Type => 2,
94            CoreLibPointerId::Boolean => 3,
95            CoreLibPointerId::Callable => 5,
96            CoreLibPointerId::Endpoint => 7,
97            CoreLibPointerId::Text => 8,
98            CoreLibPointerId::List => 9,
99            CoreLibPointerId::Unit => 11,
100            CoreLibPointerId::Map => 12,
101            CoreLibPointerId::Never => 13,
102            CoreLibPointerId::Unknown => 14,
103            CoreLibPointerId::Print => 15,
104            CoreLibPointerId::Range => 16,
105            CoreLibPointerId::Integer(None) => Self::INTEGER_BASE,
106            CoreLibPointerId::Integer(Some(v)) => {
107                let v: u8 = (*v).into();
108                CoreLibPointerId::Integer(None).to_u16() + v as u16
109            }
110            CoreLibPointerId::Decimal(None) => Self::DECIMAL_BASE,
111            CoreLibPointerId::Decimal(Some(v)) => {
112                let v: u8 = (*v).into();
113                CoreLibPointerId::Decimal(None).to_u16() + v as u16
114            }
115        }
116    }
117
118    pub fn from_u16(id: u16) -> Option<Self> {
119        match id {
120            0 => Some(CoreLibPointerId::Core),
121            1 => Some(CoreLibPointerId::Null),
122            2 => Some(CoreLibPointerId::Type),
123            3 => Some(CoreLibPointerId::Boolean),
124            5 => Some(CoreLibPointerId::Callable),
125            7 => Some(CoreLibPointerId::Endpoint),
126            8 => Some(CoreLibPointerId::Text),
127            9 => Some(CoreLibPointerId::List),
128            11 => Some(CoreLibPointerId::Unit),
129            12 => Some(CoreLibPointerId::Map),
130            13 => Some(CoreLibPointerId::Never),
131            14 => Some(CoreLibPointerId::Unknown),
132            15 => Some(CoreLibPointerId::Print),
133            16 => Some(CoreLibPointerId::Range),
134
135            Self::INTEGER_BASE => Some(CoreLibPointerId::Integer(None)),
136            n if (Self::INTEGER_BASE + 1..Self::DECIMAL_BASE).contains(&n) => {
137                IntegerTypeVariant::try_from((n - Self::INTEGER_BASE) as u8)
138                    .ok()
139                    .map(|v| CoreLibPointerId::Integer(Some(v)))
140            }
141
142            Self::DECIMAL_BASE => Some(CoreLibPointerId::Decimal(None)),
143            n if n > Self::DECIMAL_BASE => {
144                DecimalTypeVariant::try_from((n - Self::DECIMAL_BASE) as u8)
145                    .ok()
146                    .map(|v| CoreLibPointerId::Decimal(Some(v)))
147            }
148
149            _ => None,
150        }
151    }
152}
153
154impl From<CoreLibPointerId> for PointerAddress {
155    fn from(id: CoreLibPointerId) -> Self {
156        let id_bytes: [u8; 3] =
157            (id.to_u16() as u32).to_le_bytes()[0..3].try_into().unwrap();
158        PointerAddress::Internal(id_bytes)
159    }
160}
161
162impl TryFrom<&PointerAddress> for CoreLibPointerId {
163    type Error = String;
164    fn try_from(address: &PointerAddress) -> Result<Self, Self::Error> {
165        match address {
166            PointerAddress::Internal(id_bytes) => {
167                let mut id_array = [0u8; 4];
168                id_array[0..3].copy_from_slice(id_bytes);
169                let id = u32::from_le_bytes(id_array);
170                match CoreLibPointerId::from_u16(id as u16) {
171                    Some(core_id) => Ok(core_id),
172                    None => Err("Invalid CoreLibPointerId".to_string()),
173                }
174            }
175            e => Err(format!(
176                "CoreLibPointerId can only be created from Internal PointerAddress, got: {:?}",
177                e
178            )),
179        }
180    }
181}
182
183pub fn get_core_lib_type(id: impl Into<CoreLibPointerId>) -> Type {
184    with_core_lib_types(|core_lib_types| {
185        core_lib_types.get(&id.into()).unwrap().clone()
186    })
187}
188
189pub fn get_core_lib_type_reference(
190    id: impl Into<CoreLibPointerId>,
191) -> Rc<RefCell<TypeReference>> {
192    let type_container = get_core_lib_type(id);
193    match type_container.type_definition {
194        TypeDefinition::Reference(tr) => tr,
195        _ => core::panic!("Core lib type is not a TypeReference"),
196    }
197}
198
199/// Retrieves either a core library type or value by its CoreLibPointerId.
200pub fn get_core_lib_value(
201    id: impl Into<CoreLibPointerId>,
202) -> Option<ValueContainer> {
203    let id = id.into();
204    with_full_core_lib(|core_lib_types, core_lib_values| {
205        // try types first
206        if let Some(ty) = core_lib_types.get(&id) {
207            match &ty.type_definition {
208                TypeDefinition::Reference(tr) => {
209                    Some(ValueContainer::Reference(Reference::TypeReference(
210                        tr.clone(),
211                    )))
212                }
213                _ => core::panic!("Core lib type is not a TypeReference"),
214            }
215        } else {
216            core_lib_values.get(&id).cloned()
217        }
218    })
219}
220
221pub fn get_core_lib_type_definition(
222    id: impl Into<CoreLibPointerId>,
223) -> TypeDefinition {
224    get_core_lib_type(id).type_definition
225}
226
227fn has_core_lib_type<T>(id: T) -> bool
228where
229    T: Into<CoreLibPointerId>,
230{
231    with_core_lib_types(|core_lib_types| {
232        core_lib_types.contains_key(&id.into())
233    })
234}
235
236/// Loads the core library into the provided memory instance.
237pub fn load_core_lib(memory: &mut Memory) {
238    with_full_core_lib(|core_lib_types, core_lib_values| {
239        let mut types_structure = core_lib_types
240            .values()
241            .map(|ty| match &ty.type_definition {
242                TypeDefinition::Reference(type_reference) => {
243                    let name = type_reference
244                        .borrow()
245                        .nominal_type_declaration
246                        .as_ref()
247                        .unwrap()
248                        .to_string();
249                    let reference =
250                        Reference::TypeReference(type_reference.clone());
251                    memory.register_reference(&reference);
252                    (name, ValueContainer::Reference(reference))
253                }
254                _ => core::panic!("Core lib type is not a TypeReference"),
255            })
256            .collect::<Vec<(String, ValueContainer)>>();
257
258        // add core lib values
259        for (name, val) in core_lib_values.iter() {
260            let name = name.to_string();
261            types_structure.push((name, val.clone()));
262        }
263
264        // TODO #455: dont store variants as separate entries in core_struct (e.g., integer/u8, integer/i32, only keep integer)
265        // Import variants directly by variant access operator from base type (e.g., integer -> integer/u8)
266        let core_struct = Reference::from(ValueContainer::from(
267            Map::from_iter(types_structure),
268        ));
269        core_struct.set_pointer_address(CoreLibPointerId::Core.into());
270        memory.register_reference(&core_struct);
271    });
272}
273
274/// Creates a new instance of the core library as a ValueContainer
275/// including all core types as properties.
276pub fn create_core_lib_types() -> HashMap<CoreLibPointerId, Type> {
277    let integer = integer();
278    let decimal = decimal();
279    vec![
280        ty(),
281        text(),
282        list(),
283        boolean(),
284        endpoint(),
285        unit(),
286        never(),
287        unknown(),
288        map(),
289        null(),
290        callable(),
291        range(),
292    ]
293    .into_iter()
294    .chain(once(integer.clone()))
295    .chain(
296        IntegerTypeVariant::iter()
297            .map(|variant| integer_variant(integer.1.clone(), variant)),
298    )
299    .chain(once(decimal.clone()))
300    .chain(
301        DecimalTypeVariant::iter()
302            .map(|variant| decimal_variant(decimal.1.clone(), variant)),
303    )
304    .collect::<HashMap<CoreLibPointerId, Type>>()
305}
306
307pub fn create_core_lib_vals() -> HashMap<CoreLibPointerId, ValueContainer> {
308    vec![print()]
309        .into_iter()
310        .collect::<HashMap<CoreLibPointerId, ValueContainer>>()
311}
312
313type CoreLibTypeDefinition = (CoreLibPointerId, Type);
314pub fn ty() -> CoreLibTypeDefinition {
315    create_core_type("type", None, None, CoreLibPointerId::Type)
316}
317pub fn null() -> CoreLibTypeDefinition {
318    create_core_type("null", None, None, CoreLibPointerId::Null)
319}
320pub fn list() -> CoreLibTypeDefinition {
321    create_core_type("List", None, None, CoreLibPointerId::List)
322}
323pub fn map() -> CoreLibTypeDefinition {
324    create_core_type("Map", None, None, CoreLibPointerId::Map)
325}
326
327pub fn unit() -> CoreLibTypeDefinition {
328    create_core_type("Unit", None, None, CoreLibPointerId::Unit)
329}
330
331pub fn never() -> CoreLibTypeDefinition {
332    create_core_type("never", None, None, CoreLibPointerId::Never)
333}
334
335pub fn unknown() -> CoreLibTypeDefinition {
336    create_core_type("unknown", None, None, CoreLibPointerId::Unknown)
337}
338
339pub fn boolean() -> CoreLibTypeDefinition {
340    create_core_type("boolean", None, None, CoreLibPointerId::Boolean)
341}
342
343pub fn decimal() -> CoreLibTypeDefinition {
344    create_core_type("decimal", None, None, CoreLibPointerId::Decimal(None))
345}
346
347pub fn callable() -> CoreLibTypeDefinition {
348    create_core_type("Callable", None, None, CoreLibPointerId::Callable)
349}
350
351pub fn range() -> CoreLibTypeDefinition {
352    create_core_type("range", None, None, CoreLibPointerId::Range)
353}
354
355pub fn decimal_variant(
356    base_type: Type,
357    variant: DecimalTypeVariant,
358) -> CoreLibTypeDefinition {
359    let variant_name = variant.as_ref().to_string();
360    create_core_type(
361        "decimal",
362        Some(variant_name),
363        Some(base_type),
364        CoreLibPointerId::Decimal(Some(variant)),
365    )
366}
367pub fn endpoint() -> CoreLibTypeDefinition {
368    create_core_type("endpoint", None, None, CoreLibPointerId::Endpoint)
369}
370
371pub fn text() -> CoreLibTypeDefinition {
372    create_core_type("text", None, None, CoreLibPointerId::Text)
373}
374
375pub fn integer() -> CoreLibTypeDefinition {
376    create_core_type("integer", None, None, CoreLibPointerId::Integer(None))
377}
378
379pub fn integer_variant(
380    base_type: Type,
381    variant: IntegerTypeVariant,
382) -> CoreLibTypeDefinition {
383    let variant_name = variant.as_ref().to_string();
384    create_core_type(
385        "integer",
386        Some(variant_name),
387        Some(base_type),
388        CoreLibPointerId::Integer(Some(variant)),
389    )
390}
391
392pub fn print() -> (CoreLibPointerId, ValueContainer) {
393    (
394        CoreLibPointerId::Print,
395        ValueContainer::Value(Value::callable(
396            Some("print".to_string()),
397            CallableSignature {
398                kind: CallableKind::Function,
399                parameter_types: vec![],
400                rest_parameter_type: Some((
401                    Some("values".to_string()),
402                    Box::new(Type::unknown()),
403                )),
404                return_type: None,
405                yeet_type: None,
406            },
407            CallableBody::Native(|mut args: &[ValueContainer]| {
408                // TODO #680: add I/O abstraction layer / interface
409
410                let mut output = String::new();
411
412                // if first argument is a string value, print it directly
413                if let Some(ValueContainer::Value(Value {
414                    inner: CoreValue::Text(text),
415                    ..
416                })) = args.first()
417                {
418                    output.push_str(&text.0);
419                    // remove first argument from args
420                    args = &args[1..];
421                    // if there are still arguments, add a space
422                    if !args.is_empty() {
423                        output.push(' ');
424                    }
425                }
426
427                #[cfg(feature = "decompiler")]
428                let args_string = args
429                    .iter()
430                    .map(|v| {
431                        crate::decompiler::decompile_value(
432                            v,
433                            crate::decompiler::DecompileOptions::colorized(),
434                        )
435                    })
436                    .collect::<Vec<_>>()
437                    .join(" ");
438                #[cfg(not(feature = "decompiler"))]
439                let args_string = args
440                    .iter()
441                    .map(|v| v.to_string())
442                    .collect::<Vec<_>>()
443                    .join(" ");
444                output.push_str(&args_string);
445
446                #[cfg(feature = "std")]
447                println!("[PRINT] {}", output);
448                info!("[PRINT] {}", output);
449                Ok(None)
450            }),
451        )),
452    )
453}
454
455/// Creates a core type with the given parameters.
456fn create_core_type(
457    name: &str,
458    variant: Option<String>,
459    base_type: Option<Type>,
460    pointer_id: CoreLibPointerId,
461) -> CoreLibTypeDefinition {
462    let base_type_ref = match base_type {
463        Some(Type {
464            type_definition: TypeDefinition::Reference(reference),
465            ..
466        }) => Some(reference),
467        Some(_) => {
468            core::panic!("Base type must be a Reference")
469        }
470        None => None,
471    };
472    (
473        pointer_id.clone(),
474        Type::new(
475            TypeDefinition::reference(Rc::new(RefCell::new(TypeReference {
476                nominal_type_declaration: Some(NominalTypeDeclaration {
477                    name: name.to_string(),
478                    variant,
479                }),
480                type_value: Type {
481                    base_type: base_type_ref,
482                    reference_mutability: None,
483                    type_definition: TypeDefinition::Unit,
484                },
485                pointer_address: Some(PointerAddress::from(pointer_id)),
486            }))),
487            None,
488        ),
489    )
490}
491
492#[cfg(test)]
493mod tests {
494    use core::str::FromStr;
495
496    use crate::values::core_values::endpoint::Endpoint;
497
498    use super::*;
499
500    use itertools::Itertools;
501
502    #[test]
503    fn core_lib() {
504        assert!(has_core_lib_type(CoreLibPointerId::Endpoint));
505        assert!(has_core_lib_type(CoreLibPointerId::Null));
506        assert!(has_core_lib_type(CoreLibPointerId::Boolean));
507        assert!(has_core_lib_type(CoreLibPointerId::Integer(None)));
508        assert!(has_core_lib_type(CoreLibPointerId::Decimal(None)));
509        assert!(has_core_lib_type(CoreLibPointerId::Type));
510        assert!(has_core_lib_type(CoreLibPointerId::Text));
511        assert!(has_core_lib_type(CoreLibPointerId::List));
512        assert!(has_core_lib_type(CoreLibPointerId::Map));
513        assert!(has_core_lib_type(CoreLibPointerId::Range));
514        assert!(has_core_lib_type(CoreLibPointerId::Callable));
515        assert!(has_core_lib_type(CoreLibPointerId::Unit));
516        assert!(has_core_lib_type(CoreLibPointerId::Never));
517        assert!(has_core_lib_type(CoreLibPointerId::Unknown));
518        for variant in IntegerTypeVariant::iter() {
519            assert!(has_core_lib_type(CoreLibPointerId::Integer(Some(
520                variant
521            ))));
522        }
523        for variant in DecimalTypeVariant::iter() {
524            assert!(has_core_lib_type(CoreLibPointerId::Decimal(Some(
525                variant
526            ))));
527        }
528    }
529
530    #[test]
531    fn debug() {
532        let mut memory = Memory::new(Endpoint::LOCAL);
533        load_core_lib(&mut memory);
534        info!(
535            "{}",
536            memory
537                .get_value_reference(&CoreLibPointerId::Core.into())
538                .unwrap()
539                .borrow()
540                .value_container
541        );
542    }
543
544    #[test]
545    fn core_lib_type_addresses() {
546        let integer_base = "integer";
547        let integer_u8 = "integer/u8";
548        let integer_i32 = "integer/i32";
549        let decimal_base = "decimal";
550        let decimal_f64 = "decimal/f64";
551
552        assert_eq!(
553            CoreLibPointerId::from_str(integer_base),
554            Ok(CoreLibPointerId::Integer(None))
555        );
556        assert_eq!(
557            CoreLibPointerId::from_str(integer_u8),
558            Ok(CoreLibPointerId::Integer(Some(IntegerTypeVariant::U8)))
559        );
560        assert_eq!(
561            CoreLibPointerId::from_str(integer_i32),
562            Ok(CoreLibPointerId::Integer(Some(IntegerTypeVariant::I32)))
563        );
564        assert_eq!(
565            CoreLibPointerId::from_str(decimal_base),
566            Ok(CoreLibPointerId::Decimal(None))
567        );
568        assert_eq!(
569            CoreLibPointerId::from_str(decimal_f64),
570            Ok(CoreLibPointerId::Decimal(Some(DecimalTypeVariant::F64)))
571        );
572
573        assert_eq!(CoreLibPointerId::Integer(None).to_string(), integer_base);
574        assert_eq!(
575            CoreLibPointerId::Integer(Some(IntegerTypeVariant::U8)).to_string(),
576            integer_u8
577        );
578        assert_eq!(
579            CoreLibPointerId::Integer(Some(IntegerTypeVariant::I32))
580                .to_string(),
581            integer_i32
582        );
583        assert_eq!(CoreLibPointerId::Decimal(None).to_string(), decimal_base);
584        assert_eq!(
585            CoreLibPointerId::Decimal(Some(DecimalTypeVariant::F64))
586                .to_string(),
587            decimal_f64
588        );
589    }
590
591    #[test]
592    fn core_lib_pointer_id_conversion() {
593        let core_id = CoreLibPointerId::Core;
594        let pointer_address: PointerAddress = core_id.clone().into();
595        let converted_id: CoreLibPointerId =
596            (&pointer_address).try_into().unwrap();
597        assert_eq!(core_id, converted_id);
598
599        let boolean_id = CoreLibPointerId::Boolean;
600        let pointer_address: PointerAddress = boolean_id.clone().into();
601        let converted_id: CoreLibPointerId =
602            (&pointer_address).try_into().unwrap();
603        assert_eq!(boolean_id, converted_id);
604
605        let integer_id =
606            CoreLibPointerId::Integer(Some(IntegerTypeVariant::I32));
607        let pointer_address: PointerAddress = integer_id.clone().into();
608        let converted_id: CoreLibPointerId =
609            (&pointer_address).try_into().unwrap();
610        assert_eq!(integer_id, converted_id);
611
612        let decimal_id =
613            CoreLibPointerId::Decimal(Some(DecimalTypeVariant::F64));
614        let pointer_address: PointerAddress = decimal_id.clone().into();
615        let converted_id: CoreLibPointerId =
616            (&pointer_address).try_into().unwrap();
617        assert_eq!(decimal_id, converted_id);
618
619        let type_id = CoreLibPointerId::Type;
620        let pointer_address: PointerAddress = type_id.clone().into();
621        let converted_id: CoreLibPointerId =
622            (&pointer_address).try_into().unwrap();
623        assert_eq!(type_id, converted_id);
624    }
625
626    #[test]
627    fn base_type_simple() {
628        // integer -> integer -> integer ...
629        let integer_type = get_core_lib_type(CoreLibPointerId::Integer(None));
630        let integer_base = integer_type.base_type_reference();
631        assert_eq!(integer_base.unwrap().borrow().to_string(), "integer");
632    }
633
634    #[test]
635    fn base_type_complex() {
636        // integer/u8 -> integer -> integer -> integer ...
637        let integer_u8_type = get_core_lib_type(CoreLibPointerId::Integer(
638            Some(IntegerTypeVariant::U8),
639        ));
640        assert_eq!(integer_u8_type.to_string(), "integer/u8");
641
642        let integer = integer_u8_type.base_type_reference();
643        assert_eq!(integer.unwrap().borrow().to_string(), "integer");
644    }
645
646    #[ignore]
647    #[test]
648    #[cfg(feature = "std")]
649    fn print_core_lib_addresses_as_hex() {
650        with_full_core_lib(|core_lib_types, _| {
651            let sorted_entries = core_lib_types
652                .keys()
653                .map(|k| (k.clone(), PointerAddress::from(k.clone())))
654                .sorted_by_key(|(_, address)| address.bytes().to_vec())
655                .collect::<Vec<_>>();
656            for (core_lib_id, address) in sorted_entries {
657                println!("{:?}: {}", core_lib_id, address);
658            }
659        });
660    }
661
662    #[test]
663    #[ignore]
664    #[cfg(feature = "std")]
665    /// Generates a TypeScript mapping of core type addresses to their names.
666    /// Run this test and copy the output into `src/dif/definitions.ts`.
667    ///
668    /// `cargo test create_core_type_ts_mapping -- --show-output --ignored`
669    fn create_core_type_ts_mapping() {
670        let core_lib = create_core_lib_types();
671        let mut core_lib: Vec<(CoreLibPointerId, PointerAddress)> = core_lib
672            .keys()
673            .map(|key| (key.clone(), PointerAddress::from(key.clone())))
674            .collect();
675        core_lib.sort_by_key(|(key, _)| {
676            PointerAddress::from(key.clone()).bytes().to_vec()
677        });
678
679        println!("export const CoreTypeAddress = {{");
680        for (core_lib_id, address) in core_lib {
681            println!(
682                "    {}: \"{}\",",
683                core_lib_id.to_string().replace("/", "_"),
684                address.to_string().strip_prefix("$").unwrap()
685            );
686        }
687        println!("}} as const;");
688    }
689}