1use crate::{
2 js::{any::Any, js_array::JsArrayRef, js_object::JsObjectRef, type_::Type},
3 mem::manager::Dealloc,
4};
5
6use core::{
7 fmt::{self},
8 result,
9};
10
11use std::collections::HashMap;
12
13use super::to_json::WriteJson;
14
15#[derive(PartialEq)]
20enum Seen {
21 Once,
22 Repeatedly,
23}
24
25struct ConstTracker<D: Dealloc> {
33 visited: HashMap<Any<D>, Seen>,
34}
35
36impl<D: Dealloc> ConstTracker<D> {
37 fn is_visited(&mut self, any: &Any<D>) -> bool {
41 let optional_seen = self.visited.get_mut(any);
42 if let Some(seen) = optional_seen {
43 *seen = Seen::Repeatedly;
44 true
45 } else {
46 self.visited.insert(any.clone(), Seen::Once);
47 false
48 }
49 }
50
51 fn track_consts_for_any(&mut self, any: &Any<D>) -> fmt::Result {
54 match any.get_type() {
55 Type::Array | Type::Object => {
56 if !self.is_visited(any) {
57 any.for_each::<fmt::Error>(|_k, v| self.track_consts_for_any(v))?;
58 }
59 Ok(())
60 }
61 _ => Ok(()),
62 }
63 }
64}
65
66fn write_compound_const<D: Dealloc>(
68 write_json: &mut (impl WriteJson + ?Sized),
69 any: &Any<D>,
70 to_be_consts: &mut HashMap<Any<D>, Seen>,
71 const_refs: &mut HashMap<Any<D>, usize>,
72) -> fmt::Result {
73 any.for_each(|_k, v| write_consts_and_any(write_json, v, to_be_consts, const_refs))?;
74 if to_be_consts.remove(any).is_some() {
75 let id = const_refs.len();
76 write_json.write_str("const _")?;
77 write_json.write_str(id.to_string().as_str())?;
78 write_json.write_char('=')?;
79 write_with_const_refs(write_json, any.clone(), const_refs)?;
80 const_refs.insert(any.clone(), id);
81 write_json.write_char(';')
82 } else {
83 fmt::Result::Ok(())
84 }
85}
86
87fn write_consts_and_any<D: Dealloc>(
91 write_json: &mut (impl WriteJson + ?Sized),
92 any: &Any<D>,
93 to_be_consts: &mut HashMap<Any<D>, Seen>,
94 const_refs: &mut HashMap<Any<D>, usize>,
95) -> fmt::Result {
96 match any.get_type() {
97 Type::Array | Type::Object => {
98 write_compound_const(write_json, any, to_be_consts, const_refs)?;
99 }
100 _ => {}
101 }
102 fmt::Result::Ok(())
103}
104
105fn peek<D: Dealloc>(hash_map: &HashMap<Any<D>, Seen>) -> Option<Any<D>> {
108 Some(hash_map.iter().next()?.0.clone())
109}
110
111fn write_consts<D: Dealloc>(
113 write_json: &mut (impl WriteJson + ?Sized),
114 to_be_consts: &mut HashMap<Any<D>, Seen>,
115 const_refs: &mut HashMap<Any<D>, usize>,
116) -> fmt::Result {
117 while let Some(any) = peek(to_be_consts) {
118 write_consts_and_any(write_json, &any, to_be_consts, const_refs)?;
119 }
120 fmt::Result::Ok(())
121}
122
123fn write_with_const_refs<D: Dealloc>(
125 write_json: &mut (impl WriteJson + ?Sized),
126 any: Any<D>,
127 const_refs: &HashMap<Any<D>, usize>,
128) -> fmt::Result {
129 match any.get_type() {
130 Type::Object => {
131 if let Some(n) = const_refs.get(&any) {
132 write_json.write_str("_")?;
133 write_json.write_str(n.to_string().as_str())
134 } else {
135 write_json.write_list(
136 '{',
137 '}',
138 any.try_move::<JsObjectRef<D>>().unwrap(),
139 |w, (k, v)| {
140 w.write_js_string(k)?;
141 w.write_char(':')?;
142 write_with_const_refs(w, v.clone(), const_refs)
143 },
144 )
145 }
146 }
147 Type::Array => {
148 if let Some(n) = const_refs.get(&any) {
149 write_json.write_str("_")?;
150 write_json.write_str(n.to_string().as_str())
151 } else {
152 write_json.write_list(
153 '[',
154 ']',
155 any.try_move::<JsArrayRef<D>>().unwrap(),
156 |w, i| write_with_const_refs(w, i.clone(), const_refs),
157 )
158 }
159 }
160 _ => write_json.write_json(any),
161 }
162}
163
164pub trait WriteDjs: WriteJson {
165 fn write_djs<D: Dealloc>(&mut self, any: Any<D>, common_js: bool) -> fmt::Result {
168 let mut const_refs = HashMap::<Any<D>, usize>::new();
169 let mut const_tracker = ConstTracker {
170 visited: HashMap::new(),
171 };
172 const_tracker.track_consts_for_any(&any)?;
173 const_tracker
174 .visited
175 .retain(|_, seen| *seen == Seen::Repeatedly);
176 write_consts(self, &mut const_tracker.visited, &mut const_refs)?;
177 if common_js {
178 self.write_str("module.exports=")?;
179 } else {
180 self.write_str("export default ")?;
181 }
182 write_with_const_refs(self, any, &const_refs)
183 }
184}
185
186impl<T: WriteJson> WriteDjs for T {}
187
188pub fn to_djs(any: Any<impl Dealloc>, common_js: bool) -> result::Result<String, fmt::Error> {
189 let mut s = String::default();
190 s.write_djs(any, common_js)?;
191 Ok(s)
192}
193
194#[cfg(test)]
195mod test {
196 use wasm_bindgen_test::wasm_bindgen_test;
197
198 use crate::{
199 js::{any::Any, any_cast::AnyCast, js_string::new_string, new::New, null::Null},
200 mem::global::{Global, GLOBAL},
201 serializer::to_djs::WriteDjs,
202 };
203
204 #[test]
205 #[wasm_bindgen_test]
206 fn test() {
207 type A = Any<Global>;
208 let s = new_string(
209 GLOBAL,
210 ['a' as u16, '\\' as u16, 'b' as u16, '"' as u16, 31],
211 )
212 .to_ref();
213 let o = GLOBAL.new_js_object([(s, 2.0.move_to_any())]);
214 let a0 = GLOBAL.new_js_array([
215 1.0.move_to_any(),
216 true.move_to_any(),
217 Null().move_to_any(),
218 GLOBAL.new_js_array([]),
219 GLOBAL.new_js_string([]),
220 o.clone(),
221 ]);
222 let a0_as_any: Any<Global> = a0;
223 let a1: A = GLOBAL.new_js_array([a0_as_any.clone(), a0_as_any, o]);
224 let mut s = String::new();
225 s.write_djs(a1, false).unwrap();
226 assert_eq!(
227 s,
228 r#"const _0={"a\\b\"\u001F":2};const _1=[1,true,null,[],"",_0];export default [_1,_1,_0]"#
229 );
230 }
231}