#![allow(unsafe_code)]
use std::ffi::CString;
use std::panic::{self, AssertUnwindSafe};
use std::ptr::NonNull;
use lean_rs_sys::lean_object;
#[cfg(doc)]
use crate::error::HostStage;
use crate::error::io::decode_io;
use crate::error::{LeanError, LeanResult};
use crate::runtime::LeanRuntime;
use crate::runtime::obj::Obj;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct InitializerName {
symbol: CString,
legacy_symbol: CString,
display: String,
}
impl InitializerName {
pub(crate) fn from_lake_names(package: &str, module: &str) -> LeanResult<Self> {
validate_component(package, "package name")?;
let mut display = String::new();
display.push_str(package);
display.push_str("::");
display.push_str(module);
let mut mangled = String::new();
mangled.push_str("initialize_");
push_mangled(&mut mangled, package);
for component in module.split('.') {
validate_component(component, "module component")?;
mangled.push('_');
push_mangled(&mut mangled, component);
}
let mut legacy = String::new();
legacy.push_str("initialize_");
let mut first = true;
for component in module.split('.') {
if !first {
legacy.push('_');
}
first = false;
push_mangled(&mut legacy, component);
}
let symbol = CString::new(mangled)
.map_err(|_| LeanError::linking("internal: mangled initializer symbol contained NUL byte"))?;
let legacy_symbol = CString::new(legacy)
.map_err(|_| LeanError::linking("internal: legacy initializer symbol contained NUL byte"))?;
Ok(Self {
symbol,
legacy_symbol,
display,
})
}
pub(crate) fn symbol_bytes(&self) -> &[u8] {
self.symbol.as_bytes_with_nul()
}
pub(crate) fn legacy_symbol_bytes(&self) -> &[u8] {
self.legacy_symbol.as_bytes_with_nul()
}
pub(crate) fn symbol_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(self.symbol.as_bytes()) }
}
pub(crate) fn legacy_symbol_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(self.legacy_symbol.as_bytes()) }
}
pub(crate) fn display(&self) -> &str {
&self.display
}
}
fn push_mangled(out: &mut String, component: &str) {
for ch in component.chars() {
if ch == '_' {
out.push_str("__");
} else {
out.push(ch);
}
}
}
fn validate_component(component: &str, kind: &str) -> LeanResult<()> {
if component.is_empty() {
return Err(LeanError::linking(format!("invalid {kind}: empty component")));
}
let mut chars = component.chars();
let first = chars.next().unwrap_or('\0');
if !is_ident_start(first) {
return Err(LeanError::linking(format!(
"invalid {kind} '{component}': first character must be ASCII letter or underscore"
)));
}
for ch in chars {
if !is_ident_continue(ch) {
return Err(LeanError::linking(format!(
"invalid {kind} '{component}': character {ch:?} is not in [A-Za-z0-9_]"
)));
}
}
Ok(())
}
fn is_ident_start(ch: char) -> bool {
ch == '_' || ch.is_ascii_alphabetic()
}
fn is_ident_continue(ch: char) -> bool {
ch == '_' || ch.is_ascii_alphanumeric()
}
const BUILTIN_RUNTIME: u8 = 1;
pub(crate) type RawInitializer = unsafe extern "C" fn(u8) -> *mut lean_object;
pub(crate) fn call_initializer(runtime: &LeanRuntime, init: RawInitializer, name: &InitializerName) -> LeanResult<()> {
let _span = tracing::debug_span!(
target: "lean_rs",
"lean_rs.module.initializer.call",
initializer = name.display(),
)
.entered();
let outcome = panic::catch_unwind(AssertUnwindSafe(|| unsafe { init(BUILTIN_RUNTIME) }));
let raw_result = match outcome {
Ok(ptr) => ptr,
Err(payload) => {
return Err(LeanError::module_init_panic(payload.as_ref()));
}
};
let Some(non_null) = NonNull::new(raw_result) else {
return Err(LeanError::module_init(format!(
"module '{}' initializer returned a null IO result pointer",
name.display()
)));
};
let result = unsafe { Obj::from_owned_raw(runtime, non_null.as_ptr()) };
match decode_io(runtime, result) {
Ok(unit_obj) => {
drop(unit_obj);
Ok(())
}
Err(LeanError::LeanException(exc)) => Err(LeanError::module_init(format!(
"module '{}' initializer raised: {}",
name.display(),
exc.message()
))),
Err(other) => Err(other),
}
}