Skip to main content

stryke/
serialize_normalize.rs

1//! Free-function recursive flatten of stryke `ClassInstance` /
2//! `StructInstance` values into plain hashref / arrayref trees, for use
3//! by serializers (`to_json`, `to_xml`, `to_yaml`, `to_toml`, `to_html`,
4//! `ddump`) that take `&[PerlValue]` and don't have a `&VMHelper` to
5//! consult.
6//!
7//! Inheritance fields (parents declared via `extends`) are looked up
8//! through a thread-local `CLASS_DEFS_REGISTRY` that the VM populates
9//! on entry to `execute` and on each `ClassDecl` statement. When the
10//! registry is empty (e.g. a serializer is called outside a normal VM
11//! run), the helper falls back to the class's own field definitions
12//! only — covers the no-inheritance case correctly.
13
14use indexmap::IndexMap;
15use parking_lot::RwLock;
16use std::cell::RefCell;
17use std::collections::HashMap;
18use std::sync::Arc;
19
20use crate::ast::ClassDef;
21use crate::value::PerlValue;
22
23thread_local! {
24    /// Per-thread registry of class definitions, keyed by class name.
25    /// VM execution sites snapshot the helper's `class_defs` into this
26    /// cell so the free serializers can reach the same MRO information
27    /// without taking a `&VMHelper`.
28    pub(crate) static CLASS_DEFS_REGISTRY: RefCell<HashMap<String, Arc<ClassDef>>> =
29        RefCell::new(HashMap::new());
30}
31
32/// Replace this thread's class registry with `defs`. Returns the
33/// previous registry so callers can restore it on exit (RAII pattern is
34/// preferred — see [`ClassDefsGuard`]).
35pub(crate) fn install_class_defs(
36    defs: HashMap<String, Arc<ClassDef>>,
37) -> HashMap<String, Arc<ClassDef>> {
38    CLASS_DEFS_REGISTRY.with(|cell| std::mem::replace(&mut *cell.borrow_mut(), defs))
39}
40
41/// Add or update a single class definition in this thread's registry.
42/// Used when a `class C { ... }` statement runs at the top level.
43pub(crate) fn register_class_def(def: Arc<ClassDef>) {
44    CLASS_DEFS_REGISTRY.with(|cell| {
45        cell.borrow_mut().insert(def.name.clone(), def);
46    });
47}
48
49/// Walk a class's full inheritance chain and return field names in MRO
50/// order (parent fields first, then own). Mirrors
51/// `VMHelper::collect_class_fields_full` but reads from the thread-
52/// local registry. Returns an empty vec if the def has parents that
53/// aren't registered (e.g. the serializer ran in an isolated context).
54fn class_field_names(def: &ClassDef) -> Vec<String> {
55    let mut names = Vec::new();
56    for parent_name in &def.extends {
57        let parent_def_opt =
58            CLASS_DEFS_REGISTRY.with(|cell| cell.borrow().get(parent_name).cloned());
59        if let Some(parent_def) = parent_def_opt {
60            names.extend(class_field_names(&parent_def));
61        }
62    }
63    for f in &def.fields {
64        names.push(f.name.clone());
65    }
66    names
67}
68
69/// Recursively convert any `ClassInstance` / `StructInstance` /
70/// `EnumInstance` reachable inside `v` into plain hashrefs (using the
71/// field name as the key). Hashrefs and arrayrefs are walked in place;
72/// every other value (numbers, strings, undef, code refs, blessed
73/// non-hash refs, …) round-trips unchanged.
74///
75/// The intent is "make this value JSON-serializable end-to-end" — call
76/// it once at the top of every serializer that doesn't already know
77/// about stryke-native OO instances.
78pub fn deep_normalize(v: &PerlValue) -> PerlValue {
79    if let Some(c) = v.as_class_inst() {
80        let names = class_field_names(&c.def);
81        let values = c.get_values();
82        let mut map = IndexMap::new();
83        // If the registry didn't resolve some parents, the names vec
84        // can be shorter than values. Iterate by min length so we still
85        // emit something useful instead of panicking.
86        let n = names.len().min(values.len());
87        for i in 0..n {
88            map.insert(names[i].clone(), deep_normalize(&values[i]));
89        }
90        return PerlValue::hash_ref(Arc::new(RwLock::new(map)));
91    }
92    if let Some(s) = v.as_struct_inst() {
93        let values = s.get_values();
94        let mut map = IndexMap::new();
95        for (i, field) in s.def.fields.iter().enumerate() {
96            if let Some(elem) = values.get(i) {
97                map.insert(field.name.clone(), deep_normalize(elem));
98            }
99        }
100        return PerlValue::hash_ref(Arc::new(RwLock::new(map)));
101    }
102    if let Some(e) = v.as_enum_inst() {
103        // Enum: emit `{ variant => "Name", value => recursive(payload) }`
104        // when there's a payload; otherwise `{ variant => "Name" }`.
105        // Lets serializers preserve enum identity instead of stringifying.
106        let mut map = IndexMap::new();
107        map.insert(
108            "variant".to_string(),
109            PerlValue::string(e.variant_name().to_string()),
110        );
111        if !e.data.is_undef() {
112            map.insert("value".to_string(), deep_normalize(&e.data));
113        }
114        return PerlValue::hash_ref(Arc::new(RwLock::new(map)));
115    }
116    if let Some(r) = v.as_hash_ref() {
117        let inner = r.read().clone();
118        let mut map = IndexMap::new();
119        for (k, val) in inner.into_iter() {
120            map.insert(k, deep_normalize(&val));
121        }
122        return PerlValue::hash_ref(Arc::new(RwLock::new(map)));
123    }
124    if let Some(r) = v.as_array_ref() {
125        let inner = r.read().clone();
126        let out: Vec<PerlValue> = inner.iter().map(deep_normalize).collect();
127        return PerlValue::array_ref(Arc::new(RwLock::new(out)));
128    }
129    v.clone()
130}
131
132/// Convenience: normalize the first arg in place and return a Vec the
133/// caller can hand to its existing serializer logic. Use when the
134/// serializer takes `&[PerlValue]` and only the first element is the
135/// data to serialize.
136pub fn normalize_args_head(args: &[PerlValue]) -> Vec<PerlValue> {
137    if args.is_empty() {
138        return Vec::new();
139    }
140    let mut out = Vec::with_capacity(args.len());
141    out.push(deep_normalize(&args[0]));
142    out.extend(args[1..].iter().cloned());
143    out
144}