1use crate::types::{Effect, StackType, Type};
27use serde::Deserialize;
28use std::collections::HashMap;
29
30#[derive(Debug, Clone, Deserialize, PartialEq)]
32#[serde(rename_all = "snake_case")]
33pub enum FfiType {
34 Int,
36 String,
38 Ptr,
40 Void,
42}
43
44#[derive(Debug, Clone, Deserialize, PartialEq)]
46#[serde(rename_all = "snake_case")]
47pub enum PassMode {
48 CString,
50 Ptr,
52 Int,
54 ByRef,
56}
57
58#[derive(Debug, Clone, Deserialize, PartialEq)]
60#[serde(rename_all = "snake_case")]
61pub enum Ownership {
62 CallerFrees,
64 Static,
66 Borrowed,
68}
69
70#[derive(Debug, Clone, Deserialize)]
72pub struct FfiArg {
73 #[serde(rename = "type")]
75 pub arg_type: FfiType,
76 #[serde(default = "default_pass_mode")]
78 pub pass: PassMode,
79 pub value: Option<String>,
81}
82
83fn default_pass_mode() -> PassMode {
84 PassMode::CString
85}
86
87#[derive(Debug, Clone, Deserialize)]
89pub struct FfiReturn {
90 #[serde(rename = "type")]
92 pub return_type: FfiType,
93 #[serde(default = "default_ownership")]
95 pub ownership: Ownership,
96}
97
98fn default_ownership() -> Ownership {
99 Ownership::Borrowed
100}
101
102#[derive(Debug, Clone, Deserialize)]
104pub struct FfiFunction {
105 pub c_name: String,
107 pub seq_name: String,
109 pub stack_effect: String,
111 #[serde(default)]
113 pub args: Vec<FfiArg>,
114 #[serde(rename = "return")]
116 pub return_spec: Option<FfiReturn>,
117}
118
119#[derive(Debug, Clone, Deserialize)]
121pub struct FfiLibrary {
122 pub name: String,
124 pub link: String,
126 #[serde(rename = "function", default)]
128 pub functions: Vec<FfiFunction>,
129}
130
131#[derive(Debug, Clone, Deserialize)]
133pub struct FfiManifest {
134 #[serde(rename = "library")]
136 pub libraries: Vec<FfiLibrary>,
137}
138
139impl FfiManifest {
140 pub fn parse(content: &str) -> Result<Self, String> {
147 let manifest: Self =
148 toml::from_str(content).map_err(|e| format!("Failed to parse FFI manifest: {}", e))?;
149 manifest.validate()?;
150 Ok(manifest)
151 }
152
153 fn validate(&self) -> Result<(), String> {
155 if self.libraries.is_empty() {
156 return Err("FFI manifest must define at least one library".to_string());
157 }
158
159 for (lib_idx, lib) in self.libraries.iter().enumerate() {
160 if lib.name.trim().is_empty() {
162 return Err(format!("FFI library {} has empty name", lib_idx + 1));
163 }
164
165 if lib.link.trim().is_empty() {
167 return Err(format!("FFI library '{}' has empty linker flag", lib.name));
168 }
169 for c in lib.link.chars() {
171 if !c.is_alphanumeric() && c != '-' && c != '_' && c != '.' {
172 return Err(format!(
173 "FFI library '{}' has invalid character '{}' in linker flag '{}'. \
174 Only alphanumeric, dash, underscore, and dot are allowed.",
175 lib.name, c, lib.link
176 ));
177 }
178 }
179
180 for (func_idx, func) in lib.functions.iter().enumerate() {
182 if func.c_name.trim().is_empty() {
184 return Err(format!(
185 "FFI function {} in library '{}' has empty c_name",
186 func_idx + 1,
187 lib.name
188 ));
189 }
190
191 if func.seq_name.trim().is_empty() {
193 return Err(format!(
194 "FFI function '{}' in library '{}' has empty seq_name",
195 func.c_name, lib.name
196 ));
197 }
198
199 if func.stack_effect.trim().is_empty() {
201 return Err(format!(
202 "FFI function '{}' has empty stack_effect",
203 func.seq_name
204 ));
205 }
206
207 if let Err(e) = func.effect() {
209 return Err(format!(
210 "FFI function '{}' has malformed stack_effect '{}': {}",
211 func.seq_name, func.stack_effect, e
212 ));
213 }
214 }
215 }
216
217 Ok(())
218 }
219
220 pub fn linker_flags(&self) -> Vec<String> {
222 self.libraries.iter().map(|lib| lib.link.clone()).collect()
223 }
224
225 pub fn functions(&self) -> impl Iterator<Item = &FfiFunction> {
227 self.libraries.iter().flat_map(|lib| lib.functions.iter())
228 }
229}
230
231impl FfiFunction {
232 pub fn effect(&self) -> Result<Effect, String> {
234 parse_stack_effect(&self.stack_effect)
235 }
236}
237
238fn parse_stack_effect(s: &str) -> Result<Effect, String> {
240 let s = s.trim();
242 let s = s
243 .strip_prefix('(')
244 .ok_or("Stack effect must start with '('")?;
245 let s = s
246 .strip_suffix(')')
247 .ok_or("Stack effect must end with ')'")?;
248 let s = s.trim();
249
250 let parts: Vec<&str> = s.split("--").collect();
252 if parts.len() != 2 {
253 return Err(format!(
254 "Stack effect must contain exactly one '--', got: {}",
255 s
256 ));
257 }
258
259 let inputs_str = parts[0].trim();
260 let outputs_str = parts[1].trim();
261
262 let mut inputs = StackType::RowVar("a".to_string());
264 for type_name in inputs_str.split_whitespace() {
265 let ty = parse_type_name(type_name)?;
266 inputs = inputs.push(ty);
267 }
268
269 let mut outputs = StackType::RowVar("a".to_string());
271 for type_name in outputs_str.split_whitespace() {
272 let ty = parse_type_name(type_name)?;
273 outputs = outputs.push(ty);
274 }
275
276 Ok(Effect::new(inputs, outputs))
277}
278
279fn parse_type_name(name: &str) -> Result<Type, String> {
281 match name {
282 "Int" => Ok(Type::Int),
283 "Float" => Ok(Type::Float),
284 "Bool" => Ok(Type::Bool),
285 "String" => Ok(Type::String),
286 _ => Err(format!("Unknown type '{}' in stack effect", name)),
287 }
288}
289
290pub const LIBEDIT_MANIFEST: &str = include_str!("../ffi/libedit.toml");
296
297pub fn get_ffi_manifest(name: &str) -> Option<&'static str> {
299 match name {
300 "libedit" => Some(LIBEDIT_MANIFEST),
301 _ => None,
302 }
303}
304
305pub fn has_ffi_manifest(name: &str) -> bool {
307 get_ffi_manifest(name).is_some()
308}
309
310pub fn list_ffi_manifests() -> &'static [&'static str] {
312 &["libedit"]
313}
314
315#[derive(Debug, Clone)]
321pub struct FfiBindings {
322 pub functions: HashMap<String, FfiFunctionInfo>,
324 pub linker_flags: Vec<String>,
326}
327
328#[derive(Debug, Clone)]
330pub struct FfiFunctionInfo {
331 pub c_name: String,
333 pub seq_name: String,
335 pub effect: Effect,
337 pub args: Vec<FfiArg>,
339 pub return_spec: Option<FfiReturn>,
341}
342
343impl FfiBindings {
344 pub fn new() -> Self {
346 FfiBindings {
347 functions: HashMap::new(),
348 linker_flags: Vec::new(),
349 }
350 }
351
352 pub fn add_manifest(&mut self, manifest: &FfiManifest) -> Result<(), String> {
354 self.linker_flags.extend(manifest.linker_flags());
356
357 for func in manifest.functions() {
359 let effect = func.effect()?;
360 let info = FfiFunctionInfo {
361 c_name: func.c_name.clone(),
362 seq_name: func.seq_name.clone(),
363 effect,
364 args: func.args.clone(),
365 return_spec: func.return_spec.clone(),
366 };
367
368 if self.functions.contains_key(&func.seq_name) {
369 return Err(format!(
370 "FFI function '{}' is already defined",
371 func.seq_name
372 ));
373 }
374
375 self.functions.insert(func.seq_name.clone(), info);
376 }
377
378 Ok(())
379 }
380
381 pub fn is_ffi_function(&self, name: &str) -> bool {
383 self.functions.contains_key(name)
384 }
385
386 pub fn get_function(&self, name: &str) -> Option<&FfiFunctionInfo> {
388 self.functions.get(name)
389 }
390
391 pub fn function_names(&self) -> Vec<&str> {
393 self.functions.keys().map(|s| s.as_str()).collect()
394 }
395}
396
397impl Default for FfiBindings {
398 fn default() -> Self {
399 Self::new()
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn test_parse_manifest() {
409 let content = r#"
410[[library]]
411name = "example"
412link = "example"
413
414[[library.function]]
415c_name = "example_func"
416seq_name = "example-func"
417stack_effect = "( String -- String )"
418args = [
419 { type = "string", pass = "c_string" }
420]
421return = { type = "string", ownership = "caller_frees" }
422"#;
423
424 let manifest = FfiManifest::parse(content).unwrap();
425 assert_eq!(manifest.libraries.len(), 1);
426 assert_eq!(manifest.libraries[0].name, "example");
427 assert_eq!(manifest.libraries[0].link, "example");
428 assert_eq!(manifest.libraries[0].functions.len(), 1);
429
430 let func = &manifest.libraries[0].functions[0];
431 assert_eq!(func.c_name, "example_func");
432 assert_eq!(func.seq_name, "example-func");
433 assert_eq!(func.args.len(), 1);
434 assert_eq!(func.args[0].arg_type, FfiType::String);
435 assert_eq!(func.args[0].pass, PassMode::CString);
436 }
437
438 #[test]
439 fn test_parse_stack_effect() {
440 let effect = parse_stack_effect("( String -- String )").unwrap();
441 let (rest, top) = effect.inputs.clone().pop().unwrap();
443 assert_eq!(top, Type::String);
444 assert_eq!(rest, StackType::RowVar("a".to_string()));
445 let (rest, top) = effect.outputs.clone().pop().unwrap();
447 assert_eq!(top, Type::String);
448 assert_eq!(rest, StackType::RowVar("a".to_string()));
449 }
450
451 #[test]
452 fn test_parse_stack_effect_void() {
453 let effect = parse_stack_effect("( String -- )").unwrap();
454 let (rest, top) = effect.inputs.clone().pop().unwrap();
456 assert_eq!(top, Type::String);
457 assert_eq!(rest, StackType::RowVar("a".to_string()));
458 assert_eq!(effect.outputs, StackType::RowVar("a".to_string()));
460 }
461
462 #[test]
463 fn test_ffi_bindings() {
464 let content = r#"
465[[library]]
466name = "example"
467link = "example"
468
469[[library.function]]
470c_name = "example_read"
471seq_name = "example-read"
472stack_effect = "( String -- String )"
473args = [{ type = "string", pass = "c_string" }]
474return = { type = "string", ownership = "caller_frees" }
475
476[[library.function]]
477c_name = "example_store"
478seq_name = "example-store"
479stack_effect = "( String -- )"
480args = [{ type = "string", pass = "c_string" }]
481return = { type = "void" }
482"#;
483
484 let manifest = FfiManifest::parse(content).unwrap();
485 let mut bindings = FfiBindings::new();
486 bindings.add_manifest(&manifest).unwrap();
487
488 assert!(bindings.is_ffi_function("example-read"));
489 assert!(bindings.is_ffi_function("example-store"));
490 assert!(!bindings.is_ffi_function("not-defined"));
491
492 assert_eq!(bindings.linker_flags, vec!["example"]);
493 }
494
495 #[test]
498 fn test_validate_empty_library_name() {
499 let content = r#"
500[[library]]
501name = ""
502link = "example"
503
504[[library.function]]
505c_name = "example_func"
506seq_name = "example-func"
507stack_effect = "( String -- String )"
508"#;
509
510 let result = FfiManifest::parse(content);
511 assert!(result.is_err());
512 assert!(result.unwrap_err().contains("empty name"));
513 }
514
515 #[test]
516 fn test_validate_empty_link() {
517 let content = r#"
518[[library]]
519name = "example"
520link = " "
521
522[[library.function]]
523c_name = "example_func"
524seq_name = "example-func"
525stack_effect = "( String -- String )"
526"#;
527
528 let result = FfiManifest::parse(content);
529 assert!(result.is_err());
530 assert!(result.unwrap_err().contains("empty linker flag"));
531 }
532
533 #[test]
534 fn test_validate_empty_c_name() {
535 let content = r#"
536[[library]]
537name = "mylib"
538link = "mylib"
539
540[[library.function]]
541c_name = ""
542seq_name = "my-func"
543stack_effect = "( -- Int )"
544"#;
545
546 let result = FfiManifest::parse(content);
547 assert!(result.is_err());
548 assert!(result.unwrap_err().contains("empty c_name"));
549 }
550
551 #[test]
552 fn test_validate_empty_seq_name() {
553 let content = r#"
554[[library]]
555name = "mylib"
556link = "mylib"
557
558[[library.function]]
559c_name = "my_func"
560seq_name = ""
561stack_effect = "( -- Int )"
562"#;
563
564 let result = FfiManifest::parse(content);
565 assert!(result.is_err());
566 assert!(result.unwrap_err().contains("empty seq_name"));
567 }
568
569 #[test]
570 fn test_validate_empty_stack_effect() {
571 let content = r#"
572[[library]]
573name = "mylib"
574link = "mylib"
575
576[[library.function]]
577c_name = "my_func"
578seq_name = "my-func"
579stack_effect = ""
580"#;
581
582 let result = FfiManifest::parse(content);
583 assert!(result.is_err());
584 assert!(result.unwrap_err().contains("empty stack_effect"));
585 }
586
587 #[test]
588 fn test_validate_malformed_stack_effect_no_parens() {
589 let content = r#"
590[[library]]
591name = "mylib"
592link = "mylib"
593
594[[library.function]]
595c_name = "my_func"
596seq_name = "my-func"
597stack_effect = "String -- Int"
598"#;
599
600 let result = FfiManifest::parse(content);
601 assert!(result.is_err());
602 let err = result.unwrap_err();
603 assert!(err.contains("malformed stack_effect"));
604 }
605
606 #[test]
607 fn test_validate_malformed_stack_effect_no_separator() {
608 let content = r#"
609[[library]]
610name = "mylib"
611link = "mylib"
612
613[[library.function]]
614c_name = "my_func"
615seq_name = "my-func"
616stack_effect = "( String Int )"
617"#;
618
619 let result = FfiManifest::parse(content);
620 assert!(result.is_err());
621 let err = result.unwrap_err();
622 assert!(err.contains("malformed stack_effect"));
623 assert!(err.contains("--"));
624 }
625
626 #[test]
627 fn test_validate_malformed_stack_effect_unknown_type() {
628 let content = r#"
629[[library]]
630name = "mylib"
631link = "mylib"
632
633[[library.function]]
634c_name = "my_func"
635seq_name = "my-func"
636stack_effect = "( UnknownType -- Int )"
637"#;
638
639 let result = FfiManifest::parse(content);
640 assert!(result.is_err());
641 let err = result.unwrap_err();
642 assert!(err.contains("malformed stack_effect"));
643 assert!(err.contains("Unknown type"));
644 }
645
646 #[test]
647 fn test_validate_no_libraries() {
648 let content = r#"
652library = []
653"#;
654
655 let result = FfiManifest::parse(content);
656 assert!(result.is_err());
657 assert!(result.unwrap_err().contains("at least one library"));
658 }
659
660 #[test]
661 fn test_validate_linker_flag_injection() {
662 let content = r#"
664[[library]]
665name = "evil"
666link = "evil -Wl,-rpath,/malicious"
667
668[[library.function]]
669c_name = "func"
670seq_name = "func"
671stack_effect = "( -- )"
672"#;
673
674 let result = FfiManifest::parse(content);
675 assert!(result.is_err());
676 let err = result.unwrap_err();
677 assert!(err.contains("invalid character"));
678 }
679
680 #[test]
681 fn test_validate_linker_flag_valid() {
682 let content = r#"
684[[library]]
685name = "test"
686link = "my-lib_2.0"
687
688[[library.function]]
689c_name = "func"
690seq_name = "func"
691stack_effect = "( -- )"
692"#;
693
694 let result = FfiManifest::parse(content);
695 assert!(result.is_ok());
696 }
697}