use serde::Deserialize;
use crate::types::{Effect, StackType, Type};
#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum FfiType {
Int,
String,
Ptr,
Void,
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PassMode {
CString,
Ptr,
Int,
ByRef,
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Ownership {
CallerFrees,
Static,
Borrowed,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FfiArg {
#[serde(rename = "type")]
pub arg_type: FfiType,
#[serde(default = "default_pass_mode")]
pub pass: PassMode,
pub value: Option<String>,
}
fn default_pass_mode() -> PassMode {
PassMode::CString
}
#[derive(Debug, Clone, Deserialize)]
pub struct FfiReturn {
#[serde(rename = "type")]
pub return_type: FfiType,
#[serde(default = "default_ownership")]
pub ownership: Ownership,
}
fn default_ownership() -> Ownership {
Ownership::Borrowed
}
#[derive(Debug, Clone, Deserialize)]
pub struct FfiFunction {
pub c_name: String,
pub seq_name: String,
pub stack_effect: String,
#[serde(default)]
pub args: Vec<FfiArg>,
#[serde(rename = "return")]
pub return_spec: Option<FfiReturn>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FfiLibrary {
pub name: String,
pub link: String,
#[serde(rename = "function", default)]
pub functions: Vec<FfiFunction>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FfiManifest {
#[serde(rename = "library")]
pub libraries: Vec<FfiLibrary>,
}
impl FfiManifest {
pub fn parse(content: &str) -> Result<Self, String> {
let manifest: Self =
toml::from_str(content).map_err(|e| format!("Failed to parse FFI manifest: {}", e))?;
manifest.validate()?;
Ok(manifest)
}
pub(super) fn validate(&self) -> Result<(), String> {
if self.libraries.is_empty() {
return Err("FFI manifest must define at least one library".to_string());
}
for (lib_idx, lib) in self.libraries.iter().enumerate() {
if lib.name.trim().is_empty() {
return Err(format!("FFI library {} has empty name", lib_idx + 1));
}
if lib.link.trim().is_empty() {
return Err(format!("FFI library '{}' has empty linker flag", lib.name));
}
for c in lib.link.chars() {
if !c.is_alphanumeric() && c != '-' && c != '_' && c != '.' {
return Err(format!(
"FFI library '{}' has invalid character '{}' in linker flag '{}'. \
Only alphanumeric, dash, underscore, and dot are allowed.",
lib.name, c, lib.link
));
}
}
for (func_idx, func) in lib.functions.iter().enumerate() {
if func.c_name.trim().is_empty() {
return Err(format!(
"FFI function {} in library '{}' has empty c_name",
func_idx + 1,
lib.name
));
}
if func.seq_name.trim().is_empty() {
return Err(format!(
"FFI function '{}' in library '{}' has empty seq_name",
func.c_name, lib.name
));
}
if func.stack_effect.trim().is_empty() {
return Err(format!(
"FFI function '{}' has empty stack_effect",
func.seq_name
));
}
if let Err(e) = func.effect() {
return Err(format!(
"FFI function '{}' has malformed stack_effect '{}': {}",
func.seq_name, func.stack_effect, e
));
}
}
}
Ok(())
}
pub fn linker_flags(&self) -> Vec<String> {
self.libraries.iter().map(|lib| lib.link.clone()).collect()
}
pub fn functions(&self) -> impl Iterator<Item = &FfiFunction> {
self.libraries.iter().flat_map(|lib| lib.functions.iter())
}
}
impl FfiFunction {
pub fn effect(&self) -> Result<Effect, String> {
parse_stack_effect(&self.stack_effect)
}
}
pub(super) fn parse_stack_effect(s: &str) -> Result<Effect, String> {
let s = s.trim();
let s = s
.strip_prefix('(')
.ok_or("Stack effect must start with '('")?;
let s = s
.strip_suffix(')')
.ok_or("Stack effect must end with ')'")?;
let s = s.trim();
let parts: Vec<&str> = s.split("--").collect();
if parts.len() != 2 {
return Err(format!(
"Stack effect must contain exactly one '--', got: {}",
s
));
}
let inputs_str = parts[0].trim();
let outputs_str = parts[1].trim();
let mut inputs = StackType::RowVar("a".to_string());
for type_name in inputs_str.split_whitespace() {
let ty = parse_type_name(type_name)?;
inputs = inputs.push(ty);
}
let mut outputs = StackType::RowVar("a".to_string());
for type_name in outputs_str.split_whitespace() {
let ty = parse_type_name(type_name)?;
outputs = outputs.push(ty);
}
Ok(Effect::new(inputs, outputs))
}
pub(super) fn parse_type_name(name: &str) -> Result<Type, String> {
match name {
"Int" => Ok(Type::Int),
"Float" => Ok(Type::Float),
"Bool" => Ok(Type::Bool),
"String" => Ok(Type::String),
_ => Err(format!("Unknown type '{}' in stack effect", name)),
}
}
pub const LIBEDIT_MANIFEST: &str = include_str!("../../ffi/libedit.toml");
pub fn get_ffi_manifest(name: &str) -> Option<&'static str> {
match name {
"libedit" => Some(LIBEDIT_MANIFEST),
_ => None,
}
}
pub fn has_ffi_manifest(name: &str) -> bool {
get_ffi_manifest(name).is_some()
}
pub fn list_ffi_manifests() -> &'static [&'static str] {
&["libedit"]
}