1use serde::Deserialize;
5
6use crate::types::{Effect, StackType, Type};
7
8#[derive(Debug, Clone, Deserialize, PartialEq)]
10#[serde(rename_all = "snake_case")]
11pub enum FfiType {
12 Int,
14 String,
16 Ptr,
18 Void,
20}
21
22#[derive(Debug, Clone, Deserialize, PartialEq)]
24#[serde(rename_all = "snake_case")]
25pub enum PassMode {
26 CString,
28 Ptr,
30 Int,
32 ByRef,
34}
35
36#[derive(Debug, Clone, Deserialize, PartialEq)]
38#[serde(rename_all = "snake_case")]
39pub enum Ownership {
40 CallerFrees,
42 Static,
44 Borrowed,
46}
47
48#[derive(Debug, Clone, Deserialize)]
50pub struct FfiArg {
51 #[serde(rename = "type")]
53 pub arg_type: FfiType,
54 #[serde(default = "default_pass_mode")]
56 pub pass: PassMode,
57 pub value: Option<String>,
59}
60
61fn default_pass_mode() -> PassMode {
62 PassMode::CString
63}
64
65#[derive(Debug, Clone, Deserialize)]
67pub struct FfiReturn {
68 #[serde(rename = "type")]
70 pub return_type: FfiType,
71 #[serde(default = "default_ownership")]
73 pub ownership: Ownership,
74}
75
76fn default_ownership() -> Ownership {
77 Ownership::Borrowed
78}
79
80#[derive(Debug, Clone, Deserialize)]
82pub struct FfiFunction {
83 pub c_name: String,
85 pub seq_name: String,
87 pub stack_effect: String,
89 #[serde(default)]
91 pub args: Vec<FfiArg>,
92 #[serde(rename = "return")]
94 pub return_spec: Option<FfiReturn>,
95}
96
97#[derive(Debug, Clone, Deserialize)]
99pub struct FfiLibrary {
100 pub name: String,
102 pub link: String,
104 #[serde(rename = "function", default)]
106 pub functions: Vec<FfiFunction>,
107}
108
109#[derive(Debug, Clone, Deserialize)]
111pub struct FfiManifest {
112 #[serde(rename = "library")]
114 pub libraries: Vec<FfiLibrary>,
115}
116
117impl FfiManifest {
118 pub fn parse(content: &str) -> Result<Self, String> {
125 let manifest: Self =
126 toml::from_str(content).map_err(|e| format!("Failed to parse FFI manifest: {}", e))?;
127 manifest.validate()?;
128 Ok(manifest)
129 }
130
131 pub(super) fn validate(&self) -> Result<(), String> {
133 if self.libraries.is_empty() {
134 return Err("FFI manifest must define at least one library".to_string());
135 }
136
137 for (lib_idx, lib) in self.libraries.iter().enumerate() {
138 if lib.name.trim().is_empty() {
140 return Err(format!("FFI library {} has empty name", lib_idx + 1));
141 }
142
143 if lib.link.trim().is_empty() {
145 return Err(format!("FFI library '{}' has empty linker flag", lib.name));
146 }
147 for c in lib.link.chars() {
149 if !c.is_alphanumeric() && c != '-' && c != '_' && c != '.' {
150 return Err(format!(
151 "FFI library '{}' has invalid character '{}' in linker flag '{}'. \
152 Only alphanumeric, dash, underscore, and dot are allowed.",
153 lib.name, c, lib.link
154 ));
155 }
156 }
157
158 for (func_idx, func) in lib.functions.iter().enumerate() {
160 if func.c_name.trim().is_empty() {
162 return Err(format!(
163 "FFI function {} in library '{}' has empty c_name",
164 func_idx + 1,
165 lib.name
166 ));
167 }
168
169 if func.seq_name.trim().is_empty() {
171 return Err(format!(
172 "FFI function '{}' in library '{}' has empty seq_name",
173 func.c_name, lib.name
174 ));
175 }
176
177 if func.stack_effect.trim().is_empty() {
179 return Err(format!(
180 "FFI function '{}' has empty stack_effect",
181 func.seq_name
182 ));
183 }
184
185 if let Err(e) = func.effect() {
187 return Err(format!(
188 "FFI function '{}' has malformed stack_effect '{}': {}",
189 func.seq_name, func.stack_effect, e
190 ));
191 }
192 }
193 }
194
195 Ok(())
196 }
197
198 pub fn linker_flags(&self) -> Vec<String> {
200 self.libraries.iter().map(|lib| lib.link.clone()).collect()
201 }
202
203 pub fn functions(&self) -> impl Iterator<Item = &FfiFunction> {
205 self.libraries.iter().flat_map(|lib| lib.functions.iter())
206 }
207}
208
209impl FfiFunction {
210 pub fn effect(&self) -> Result<Effect, String> {
212 parse_stack_effect(&self.stack_effect)
213 }
214}
215
216pub(super) fn parse_stack_effect(s: &str) -> Result<Effect, String> {
218 let s = s.trim();
220 let s = s
221 .strip_prefix('(')
222 .ok_or("Stack effect must start with '('")?;
223 let s = s
224 .strip_suffix(')')
225 .ok_or("Stack effect must end with ')'")?;
226 let s = s.trim();
227
228 let parts: Vec<&str> = s.split("--").collect();
230 if parts.len() != 2 {
231 return Err(format!(
232 "Stack effect must contain exactly one '--', got: {}",
233 s
234 ));
235 }
236
237 let inputs_str = parts[0].trim();
238 let outputs_str = parts[1].trim();
239
240 let mut inputs = StackType::RowVar("a".to_string());
242 for type_name in inputs_str.split_whitespace() {
243 let ty = parse_type_name(type_name)?;
244 inputs = inputs.push(ty);
245 }
246
247 let mut outputs = StackType::RowVar("a".to_string());
249 for type_name in outputs_str.split_whitespace() {
250 let ty = parse_type_name(type_name)?;
251 outputs = outputs.push(ty);
252 }
253
254 Ok(Effect::new(inputs, outputs))
255}
256
257pub(super) fn parse_type_name(name: &str) -> Result<Type, String> {
259 match name {
260 "Int" => Ok(Type::Int),
261 "Float" => Ok(Type::Float),
262 "Bool" => Ok(Type::Bool),
263 "String" => Ok(Type::String),
264 _ => Err(format!("Unknown type '{}' in stack effect", name)),
265 }
266}
267
268pub const LIBEDIT_MANIFEST: &str = include_str!("../../ffi/libedit.toml");
274
275pub fn get_ffi_manifest(name: &str) -> Option<&'static str> {
277 match name {
278 "libedit" => Some(LIBEDIT_MANIFEST),
279 _ => None,
280 }
281}
282
283pub fn has_ffi_manifest(name: &str) -> bool {
285 get_ffi_manifest(name).is_some()
286}
287
288pub fn list_ffi_manifests() -> &'static [&'static str] {
290 &["libedit"]
291}