use crate::codegen::type_mapper::TypeMapper;
use crate::core::ir::{EnumDef, ErrorDef, FieldDef, FunctionDef, MethodDef, ParamDef, TypeDef, TypeRef};
use std::collections::BTreeSet;
use crate::backends::gleam::type_map::GleamMapper;
use super::helpers::emit_cleaned_gleam_doc;
use super::variant_collision::variant_constructor_name;
pub(crate) fn emit_from_json_fn(ty: &TypeDef, nif_module: &str, out: &mut String) {
let type_name = &ty.name;
let snake = gleam_public_member_name(type_name);
let fn_name = format!("{snake}_from_json");
out.push_str(&crate::backends::gleam::template_env::render(
"function_external.jinja",
minijinja::context! { nif_module => nif_module, name => &fn_name },
));
out.push_str(&crate::backends::gleam::template_env::render(
"function_signature.jinja",
minijinja::context! {
name => &fn_name,
params => "json: String",
return_type => &format!("Result({type_name}, String)"),
},
));
}
pub(crate) fn emit_type(ty: &TypeDef, out: &mut String, imports: &mut BTreeSet<&'static str>) {
emit_cleaned_gleam_doc(out, &ty.doc, "");
if ty.fields.is_empty() {
out.push_str(&crate::backends::gleam::template_env::render(
"type_opaque.jinja",
minijinja::context! {
name => &ty.name,
},
));
return;
}
out.push_str(&crate::backends::gleam::template_env::render(
"type_header.jinja",
minijinja::context! {
name => &ty.name,
},
));
for (idx, field) in ty.fields.iter().enumerate() {
let ty_str = gleam_type(&field.ty, field.optional, imports);
let comma = if idx + 1 == ty.fields.len() { "" } else { "," };
out.push_str(&crate::backends::gleam::template_env::render(
"field_labeled.jinja",
minijinja::context! {
name => &field.name,
ty => &ty_str,
comma => comma,
},
));
}
out.push_str(" )\n}\n");
}
fn is_positional_field(name: &str) -> bool {
name.starts_with('_') && name[1..].parse::<usize>().is_ok()
}
pub(crate) fn emit_variant_fields(fields: &[FieldDef], out: &mut String, imports: &mut BTreeSet<&'static str>) {
for (idx, field) in fields.iter().enumerate() {
let ty_str = gleam_type(&field.ty, field.optional, imports);
let comma = if idx + 1 == fields.len() { "" } else { "," };
if is_positional_field(&field.name) || field.name.is_empty() {
out.push_str(&crate::backends::gleam::template_env::render(
"field_positional.jinja",
minijinja::context! {
ty => &ty_str,
comma => comma,
},
));
} else {
out.push_str(&crate::backends::gleam::template_env::render(
"field_labeled.jinja",
minijinja::context! {
name => &field.name,
ty => &ty_str,
comma => comma,
},
));
}
}
}
pub(crate) fn emit_enum(
en: &EnumDef,
collisions: &std::collections::HashSet<String>,
out: &mut String,
imports: &mut BTreeSet<&'static str>,
) {
emit_cleaned_gleam_doc(out, &en.doc, "");
out.push_str(&crate::backends::gleam::template_env::render(
"enum_header.jinja",
minijinja::context! {
name => &en.name,
},
));
for variant in &en.variants {
let ctor = variant_constructor_name(&en.name, &variant.name, collisions);
if variant.fields.is_empty() {
out.push_str(&crate::backends::gleam::template_env::render(
"variant_simple.jinja",
minijinja::context! {
ctor => &ctor,
},
));
} else {
out.push_str(&crate::backends::gleam::template_env::render(
"variant_with_fields.jinja",
minijinja::context! {
ctor => &ctor,
},
));
emit_variant_fields(&variant.fields, out, imports);
out.push_str(" )\n");
}
}
out.push_str("}\n");
}
pub(crate) fn emit_error_type(
err: &ErrorDef,
collisions: &std::collections::HashSet<String>,
out: &mut String,
imports: &mut BTreeSet<&'static str>,
) {
emit_cleaned_gleam_doc(out, &err.doc, "");
out.push_str(&crate::backends::gleam::template_env::render(
"error_header.jinja",
minijinja::context! {
name => &err.name,
},
));
for variant in &err.variants {
let ctor = variant_constructor_name(&err.name, &variant.name, collisions);
if variant.fields.is_empty() {
out.push_str(&crate::backends::gleam::template_env::render(
"variant_simple.jinja",
minijinja::context! {
ctor => &ctor,
},
));
} else {
out.push_str(&crate::backends::gleam::template_env::render(
"variant_with_fields.jinja",
minijinja::context! {
ctor => &ctor,
},
));
emit_variant_fields(&variant.fields, out, imports);
out.push_str(" )\n");
}
}
out.push_str("}\n");
}
pub(crate) fn emit_function(
f: &FunctionDef,
nif_module: &str,
declared_errors: &[String],
out: &mut String,
imports: &mut BTreeSet<&'static str>,
) {
emit_cleaned_gleam_doc(out, &f.doc, "");
out.push_str(&crate::backends::gleam::template_env::render(
"function_external.jinja",
minijinja::context! {
nif_module => nif_module,
name => &f.name,
},
));
let snake_name = gleam_public_member_name(&f.name);
let return_ty = gleam_type(&f.return_type, false, imports);
let return_str = if let Some(err_ty) = &f.error_type {
let resolved = resolve_gleam_error_type(err_ty, declared_errors);
format!("Result({return_ty}, {resolved})")
} else {
return_ty
};
out.push_str(&crate::backends::gleam::template_env::render(
"function_signature.jinja",
minijinja::context! {
name => &snake_name,
params => ¶ms_string(f, imports),
return_type => &return_str,
},
));
}
fn params_string(f: &FunctionDef, imports: &mut BTreeSet<&'static str>) -> String {
let params: Vec<String> = f.params.iter().map(|p| format_param(p, imports)).collect();
params.join(", ")
}
pub(crate) fn resolve_gleam_error_type(error_type: &str, declared: &[String]) -> String {
let last = error_type.rsplit("::").next().unwrap_or(error_type);
if declared.iter().any(|d| d == last) {
return last.to_string();
}
declared.first().cloned().unwrap_or_else(|| "String".to_string())
}
fn format_param(p: &ParamDef, imports: &mut BTreeSet<&'static str>) -> String {
let ty_str = gleam_type(&p.ty, p.optional, imports);
format!("{}: {}", p.name, ty_str)
}
pub(crate) fn gleam_type(ty: &TypeRef, optional: bool, imports: &mut BTreeSet<&'static str>) -> String {
let mapper = GleamMapper;
let inner = render_type_ref_with_imports(ty, imports, &mapper);
if optional {
imports.insert("import gleam/option.{type Option}");
format!("Option({inner})")
} else {
inner
}
}
fn render_type_ref_with_imports(ty: &TypeRef, imports: &mut BTreeSet<&'static str>, mapper: &GleamMapper) -> String {
match ty {
TypeRef::Optional(inner) => {
imports.insert("import gleam/option.{type Option}");
format!("Option({})", render_type_ref_with_imports(inner, imports, mapper))
}
TypeRef::Vec(inner) => {
format!("List({})", render_type_ref_with_imports(inner, imports, mapper))
}
TypeRef::Map(k, v) => {
imports.insert("import gleam/dict.{type Dict}");
format!(
"Dict({}, {})",
render_type_ref_with_imports(k, imports, mapper),
render_type_ref_with_imports(v, imports, mapper)
)
}
_ => mapper.map_type(ty),
}
}
pub(crate) fn emit_resource_type(ty: &TypeDef, out: &mut String, imports: &mut BTreeSet<&'static str>) {
imports.insert("import gleam/dynamic");
emit_cleaned_gleam_doc(out, &ty.doc, "");
out.push_str(&crate::backends::gleam::template_env::render(
"type_opaque_resource.jinja",
minijinja::context! {
name => &ty.name,
},
));
}
pub(crate) fn emit_method(
method: &MethodDef,
type_name: &str,
nif_module: &str,
declared_errors: &[String],
out: &mut String,
imports: &mut BTreeSet<&'static str>,
) {
emit_cleaned_gleam_doc(out, &method.doc, "");
let snake_type = gleam_public_member_name(type_name);
let snake_method = gleam_public_member_name(&method.name);
let nif_fn_name = format!("{snake_type}_{snake_method}");
out.push_str(&crate::backends::gleam::template_env::render(
"resource_method_external.jinja",
minijinja::context! {
nif_module => nif_module,
nif_fn_name => &nif_fn_name,
},
));
let return_ty = gleam_type(&method.return_type, false, imports);
let return_str = if let Some(err_ty) = &method.error_type {
let resolved = resolve_gleam_error_type(err_ty, declared_errors);
format!("Result({return_ty}, {resolved})")
} else {
return_ty
};
let self_param = format!("self_: {type_name}");
let rest_params: Vec<String> = method.params.iter().map(|p| format_param(p, imports)).collect();
let all_params = if rest_params.is_empty() {
self_param
} else {
format!("{self_param}, {}", rest_params.join(", "))
};
out.push_str(&crate::backends::gleam::template_env::render(
"function_signature.jinja",
minijinja::context! {
name => &snake_method,
params => &all_params,
return_type => &return_str,
},
));
}
fn gleam_public_member_name(name: &str) -> String {
crate::codegen::naming::public_host_identifier(
crate::core::config::Language::Gleam,
crate::codegen::naming::PublicIdentifierKind::Function,
name,
)
}