use crate::{
js::{any::Any, js_array::JsArrayRef, js_object::JsObjectRef, type_::Type},
mem::manager::Dealloc,
};
use core::{
fmt::{self},
result,
};
use std::collections::HashMap;
use super::to_json::WriteJson;
#[derive(PartialEq)]
enum Seen {
Once,
Repeatedly,
}
struct ConstTracker<D: Dealloc> {
visited: HashMap<Any<D>, Seen>,
}
impl<D: Dealloc> ConstTracker<D> {
fn is_visited(&mut self, any: &Any<D>) -> bool {
let optional_seen = self.visited.get_mut(any);
if let Some(seen) = optional_seen {
*seen = Seen::Repeatedly;
true
} else {
self.visited.insert(any.clone(), Seen::Once);
false
}
}
fn track_consts_for_any(&mut self, any: &Any<D>) -> fmt::Result {
match any.get_type() {
Type::Array | Type::Object => {
if !self.is_visited(any) {
any.for_each::<fmt::Error>(|_k, v| self.track_consts_for_any(v))?;
}
Ok(())
}
_ => Ok(()),
}
}
}
fn write_compound_const<D: Dealloc>(
write_json: &mut (impl WriteJson + ?Sized),
any: &Any<D>,
to_be_consts: &mut HashMap<Any<D>, Seen>,
const_refs: &mut HashMap<Any<D>, usize>,
) -> fmt::Result {
any.for_each(|_k, v| write_consts_and_any(write_json, v, to_be_consts, const_refs))?;
if to_be_consts.remove(any).is_some() {
let id = const_refs.len();
write_json.write_str("const _")?;
write_json.write_str(id.to_string().as_str())?;
write_json.write_char('=')?;
write_with_const_refs(write_json, any.clone(), const_refs)?;
const_refs.insert(any.clone(), id);
write_json.write_char(';')
} else {
fmt::Result::Ok(())
}
}
fn write_consts_and_any<D: Dealloc>(
write_json: &mut (impl WriteJson + ?Sized),
any: &Any<D>,
to_be_consts: &mut HashMap<Any<D>, Seen>,
const_refs: &mut HashMap<Any<D>, usize>,
) -> fmt::Result {
match any.get_type() {
Type::Array | Type::Object => {
write_compound_const(write_json, any, to_be_consts, const_refs)?;
}
_ => {}
}
fmt::Result::Ok(())
}
fn peek<D: Dealloc>(hash_map: &HashMap<Any<D>, Seen>) -> Option<Any<D>> {
Some(hash_map.iter().next()?.0.clone())
}
fn write_consts<D: Dealloc>(
write_json: &mut (impl WriteJson + ?Sized),
to_be_consts: &mut HashMap<Any<D>, Seen>,
const_refs: &mut HashMap<Any<D>, usize>,
) -> fmt::Result {
while let Some(any) = peek(to_be_consts) {
write_consts_and_any(write_json, &any, to_be_consts, const_refs)?;
}
fmt::Result::Ok(())
}
fn write_with_const_refs<D: Dealloc>(
write_json: &mut (impl WriteJson + ?Sized),
any: Any<D>,
const_refs: &HashMap<Any<D>, usize>,
) -> fmt::Result {
match any.get_type() {
Type::Object => {
if let Some(n) = const_refs.get(&any) {
write_json.write_str("_")?;
write_json.write_str(n.to_string().as_str())
} else {
write_json.write_list(
'{',
'}',
any.try_move::<JsObjectRef<D>>().unwrap(),
|w, (k, v)| {
w.write_js_string(k)?;
w.write_char(':')?;
write_with_const_refs(w, v.clone(), const_refs)
},
)
}
}
Type::Array => {
if let Some(n) = const_refs.get(&any) {
write_json.write_str("_")?;
write_json.write_str(n.to_string().as_str())
} else {
write_json.write_list(
'[',
']',
any.try_move::<JsArrayRef<D>>().unwrap(),
|w, i| write_with_const_refs(w, i.clone(), const_refs),
)
}
}
_ => write_json.write_json(any),
}
}
pub trait WriteDjs: WriteJson {
fn write_djs<D: Dealloc>(&mut self, any: Any<D>, common_js: bool) -> fmt::Result {
let mut const_refs = HashMap::<Any<D>, usize>::new();
let mut const_tracker = ConstTracker {
visited: HashMap::new(),
};
const_tracker.track_consts_for_any(&any)?;
const_tracker
.visited
.retain(|_, seen| *seen == Seen::Repeatedly);
write_consts(self, &mut const_tracker.visited, &mut const_refs)?;
if common_js {
self.write_str("module.exports=")?;
} else {
self.write_str("export default ")?;
}
write_with_const_refs(self, any, &const_refs)
}
}
impl<T: WriteJson> WriteDjs for T {}
pub fn to_djs(any: Any<impl Dealloc>, common_js: bool) -> result::Result<String, fmt::Error> {
let mut s = String::default();
s.write_djs(any, common_js)?;
Ok(s)
}
#[cfg(test)]
mod test {
use wasm_bindgen_test::wasm_bindgen_test;
use crate::{
js::{any::Any, any_cast::AnyCast, js_string::new_string, new::New, null::Null},
mem::global::{Global, GLOBAL},
serializer::to_djs::WriteDjs,
};
#[test]
#[wasm_bindgen_test]
fn test() {
type A = Any<Global>;
let s = new_string(
GLOBAL,
['a' as u16, '\\' as u16, 'b' as u16, '"' as u16, 31],
)
.to_ref();
let o = GLOBAL.new_js_object([(s, 2.0.move_to_any())]);
let a0 = GLOBAL.new_js_array([
1.0.move_to_any(),
true.move_to_any(),
Null().move_to_any(),
GLOBAL.new_js_array([]),
GLOBAL.new_js_string([]),
o.clone(),
]);
let a0_as_any: Any<Global> = a0;
let a1: A = GLOBAL.new_js_array([a0_as_any.clone(), a0_as_any, o]);
let mut s = String::new();
s.write_djs(a1, false).unwrap();
assert_eq!(
s,
r#"const _0={"a\\b\"\u001F":2};const _1=[1,true,null,[],"",_0];export default [_1,_1,_0]"#
);
}
}