use plotnik_core::utils::to_pascal_case;
use plotnik_bytecode::{TypeData, TypeDef, TypeId, TypeKind};
use super::Emitter;
impl Emitter<'_> {
pub(super) fn prepare_emission(&mut self) {
for i in 0..self.entrypoints.len() {
let ep = self.entrypoints.get(i);
let name = self.strings.get(ep.name);
self.used_names.insert(to_pascal_case(name));
}
for i in 0..self.types.names_count() {
let type_name = self.types.get_name(i);
let name = self.strings.get(type_name.name);
self.type_names
.insert(type_name.type_id, to_pascal_case(name));
}
self.assign_generated_names();
self.collect_builtin_references();
if self.config.emit_node_type && self.node_referenced {
self.emit_node_interface();
}
}
pub(super) fn emit_generated_or_custom(&mut self, type_id: TypeId) {
if self.emitted.contains(&type_id) {
return;
}
let Some(type_def) = self.types.get(type_id) else {
return;
};
match type_def.classify() {
TypeData::Primitive(_) => (),
TypeData::Wrapper {
kind: TypeKind::Alias,
..
} => {
if let Some(name) = self.type_names.get(&type_id).cloned() {
self.emit_custom_type_alias(&name);
self.emitted.insert(type_id);
}
}
TypeData::Wrapper { .. } => (),
TypeData::Composite { .. } => {
if let Some(name) = self.type_names.get(&type_id).cloned() {
self.emit_generated_type_def(type_id, &name);
}
}
}
}
fn emit_generated_type_def(&mut self, type_id: TypeId, name: &str) {
self.emitted.insert(type_id);
let Some(type_def) = self.types.get(type_id) else {
return;
};
match type_def.classify() {
TypeData::Composite {
kind: TypeKind::Struct,
..
} => self.emit_interface(name, &type_def),
TypeData::Composite {
kind: TypeKind::Enum,
..
} => self.emit_tagged_union(name, &type_def),
_ => {}
}
}
pub(super) fn emit_type_definition(&mut self, name: &str, type_id: TypeId) {
self.emitted.insert(type_id);
let type_name = to_pascal_case(name);
let Some(type_def) = self.types.get(type_id) else {
let ts_type = self.type_to_ts(type_id);
self.emit_type_decl(&type_name, &ts_type);
return;
};
match type_def.classify() {
TypeData::Composite {
kind: TypeKind::Struct,
..
} => self.emit_interface(&type_name, &type_def),
TypeData::Composite {
kind: TypeKind::Enum,
..
} => self.emit_tagged_union(&type_name, &type_def),
_ => {
let ts_type = self.type_to_ts(type_id);
self.emit_type_decl(&type_name, &ts_type);
}
}
}
pub(super) fn emit_type_decl(&mut self, name: &str, body: &str) {
let c = self.c();
if self.config.export {
self.output
.push_str(&format!("{}export{} ", c.dim, c.reset));
}
self.output.push_str(&format!(
"{}type{} {}{}{} {}={} {}{};\n\n",
c.dim, c.reset, c.blue, name, c.reset, c.dim, c.reset, body, c.dim
));
self.output.push_str(c.reset);
}
fn emit_interface(&mut self, name: &str, type_def: &TypeDef) {
let c = self.c();
if self.config.export {
self.output
.push_str(&format!("{}export{} ", c.dim, c.reset));
}
self.output.push_str(&format!(
"{}interface{} {}{}{} {}{{\n",
c.dim, c.reset, c.blue, name, c.reset, c.dim
));
let mut fields: Vec<(String, TypeId, bool)> = self
.types
.members_of(type_def)
.map(|member| {
let field_name = self.strings.get(member.name).to_string();
let (inner_type, optional) = self.types.unwrap_optional(member.type_id);
(field_name, inner_type, optional)
})
.collect();
fields.sort_by(|a, b| a.0.cmp(&b.0));
for (field_name, field_type, optional) in fields {
let ts_type = self.type_to_ts(field_type);
let opt_marker = if optional { "?" } else { "" };
self.output.push_str(&format!(
"{} {}{}{}{}: {}{}{};\n",
c.reset, field_name, c.dim, opt_marker, c.dim, c.reset, ts_type, c.dim
));
}
self.output.push_str(&format!("{}}}{}\n\n", c.dim, c.reset));
}
fn emit_tagged_union(&mut self, name: &str, type_def: &TypeDef) {
let c = self.c();
let mut variant_types = Vec::new();
for member in self.types.members_of(type_def) {
let variant_name = self.strings.get(member.name);
let variant_type_name = format!("{}{}", name, to_pascal_case(variant_name));
variant_types.push(variant_type_name.clone());
let is_void = self.is_void_type(member.type_id);
if self.config.export {
self.output
.push_str(&format!("{}export{} ", c.dim, c.reset));
}
self.output.push_str(&format!(
"{}interface{} {}{}{} {}{{\n",
c.dim, c.reset, c.blue, variant_type_name, c.reset, c.dim
));
self.output.push_str(&format!(
"{} $tag{}:{} {}\"{}\"{}{};{}\n",
c.reset, c.dim, c.reset, c.green, variant_name, c.reset, c.dim, c.reset
));
if !is_void {
let data_str = self.inline_data_type(member.type_id);
self.output.push_str(&format!(
" $data{}:{} {}{};\n",
c.dim, c.reset, data_str, c.dim
));
}
self.output.push_str(&format!("{}}}{}\n\n", c.dim, c.reset));
}
let union = variant_types
.iter()
.map(|v| format!("{}{}{}", c.blue, v, c.reset))
.collect::<Vec<_>>()
.join(&format!(" {}|{} ", c.dim, c.reset));
self.emit_type_decl(name, &union);
}
fn emit_custom_type_alias(&mut self, name: &str) {
self.emit_type_decl(name, "Node");
}
pub(super) fn emit_type_alias(&mut self, alias_name: &str, target_name: &str) {
let c = self.c();
self.emit_type_decl(alias_name, &format!("{}{}{}", c.blue, target_name, c.reset));
}
fn emit_node_interface(&mut self) {
let c = self.c();
if self.config.export {
self.output
.push_str(&format!("{}export{} ", c.dim, c.reset));
}
self.output.push_str(&format!(
"{}interface{} {}Node{} {}{{\n",
c.dim, c.reset, c.blue, c.reset, c.dim
));
self.output.push_str(&format!(
"{} kind{}:{} string{};\n",
c.reset, c.dim, c.reset, c.dim
));
self.output.push_str(&format!(
"{} text{}:{} string{};\n",
c.reset, c.dim, c.reset, c.dim
));
self.output.push_str(&format!(
"{} span{}:{} {}[{}number{}, {}number{}]{};\n",
c.reset, c.dim, c.reset, c.dim, c.reset, c.dim, c.reset, c.dim, c.dim
));
if self.config.verbose_nodes {
let pos_type = format!(
"{}{{{} row{}:{} number{}; column{}:{} number{}; {}}}",
c.dim, c.reset, c.dim, c.reset, c.dim, c.dim, c.reset, c.dim, c.dim
);
self.output.push_str(&format!(
"{} startPosition{}:{} {}{};\n",
c.reset, c.dim, c.reset, pos_type, c.dim
));
self.output.push_str(&format!(
"{} endPosition{}:{} {}{};\n",
c.reset, c.dim, c.reset, pos_type, c.dim
));
}
self.output.push_str(&format!("{}}}{}\n\n", c.dim, c.reset));
}
}