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 function_names(&self) -> Vec<&str> {
388 self.functions.keys().map(|s| s.as_str()).collect()
389 }
390}
391
392impl Default for FfiBindings {
393 fn default() -> Self {
394 Self::new()
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn test_parse_manifest() {
404 let content = r#"
405[[library]]
406name = "example"
407link = "example"
408
409[[library.function]]
410c_name = "example_func"
411seq_name = "example-func"
412stack_effect = "( String -- String )"
413args = [
414 { type = "string", pass = "c_string" }
415]
416return = { type = "string", ownership = "caller_frees" }
417"#;
418
419 let manifest = FfiManifest::parse(content).unwrap();
420 assert_eq!(manifest.libraries.len(), 1);
421 assert_eq!(manifest.libraries[0].name, "example");
422 assert_eq!(manifest.libraries[0].link, "example");
423 assert_eq!(manifest.libraries[0].functions.len(), 1);
424
425 let func = &manifest.libraries[0].functions[0];
426 assert_eq!(func.c_name, "example_func");
427 assert_eq!(func.seq_name, "example-func");
428 assert_eq!(func.args.len(), 1);
429 assert_eq!(func.args[0].arg_type, FfiType::String);
430 assert_eq!(func.args[0].pass, PassMode::CString);
431 }
432
433 #[test]
434 fn test_parse_stack_effect() {
435 let effect = parse_stack_effect("( String -- String )").unwrap();
436 let (rest, top) = effect.inputs.clone().pop().unwrap();
438 assert_eq!(top, Type::String);
439 assert_eq!(rest, StackType::RowVar("a".to_string()));
440 let (rest, top) = effect.outputs.clone().pop().unwrap();
442 assert_eq!(top, Type::String);
443 assert_eq!(rest, StackType::RowVar("a".to_string()));
444 }
445
446 #[test]
447 fn test_parse_stack_effect_void() {
448 let effect = parse_stack_effect("( String -- )").unwrap();
449 let (rest, top) = effect.inputs.clone().pop().unwrap();
451 assert_eq!(top, Type::String);
452 assert_eq!(rest, StackType::RowVar("a".to_string()));
453 assert_eq!(effect.outputs, StackType::RowVar("a".to_string()));
455 }
456
457 #[test]
458 fn test_ffi_bindings() {
459 let content = r#"
460[[library]]
461name = "example"
462link = "example"
463
464[[library.function]]
465c_name = "example_read"
466seq_name = "example-read"
467stack_effect = "( String -- String )"
468args = [{ type = "string", pass = "c_string" }]
469return = { type = "string", ownership = "caller_frees" }
470
471[[library.function]]
472c_name = "example_store"
473seq_name = "example-store"
474stack_effect = "( String -- )"
475args = [{ type = "string", pass = "c_string" }]
476return = { type = "void" }
477"#;
478
479 let manifest = FfiManifest::parse(content).unwrap();
480 let mut bindings = FfiBindings::new();
481 bindings.add_manifest(&manifest).unwrap();
482
483 assert!(bindings.is_ffi_function("example-read"));
484 assert!(bindings.is_ffi_function("example-store"));
485 assert!(!bindings.is_ffi_function("not-defined"));
486
487 assert_eq!(bindings.linker_flags, vec!["example"]);
488 }
489
490 #[test]
493 fn test_validate_empty_library_name() {
494 let content = r#"
495[[library]]
496name = ""
497link = "example"
498
499[[library.function]]
500c_name = "example_func"
501seq_name = "example-func"
502stack_effect = "( String -- String )"
503"#;
504
505 let result = FfiManifest::parse(content);
506 assert!(result.is_err());
507 assert!(result.unwrap_err().contains("empty name"));
508 }
509
510 #[test]
511 fn test_validate_empty_link() {
512 let content = r#"
513[[library]]
514name = "example"
515link = " "
516
517[[library.function]]
518c_name = "example_func"
519seq_name = "example-func"
520stack_effect = "( String -- String )"
521"#;
522
523 let result = FfiManifest::parse(content);
524 assert!(result.is_err());
525 assert!(result.unwrap_err().contains("empty linker flag"));
526 }
527
528 #[test]
529 fn test_validate_empty_c_name() {
530 let content = r#"
531[[library]]
532name = "mylib"
533link = "mylib"
534
535[[library.function]]
536c_name = ""
537seq_name = "my-func"
538stack_effect = "( -- Int )"
539"#;
540
541 let result = FfiManifest::parse(content);
542 assert!(result.is_err());
543 assert!(result.unwrap_err().contains("empty c_name"));
544 }
545
546 #[test]
547 fn test_validate_empty_seq_name() {
548 let content = r#"
549[[library]]
550name = "mylib"
551link = "mylib"
552
553[[library.function]]
554c_name = "my_func"
555seq_name = ""
556stack_effect = "( -- Int )"
557"#;
558
559 let result = FfiManifest::parse(content);
560 assert!(result.is_err());
561 assert!(result.unwrap_err().contains("empty seq_name"));
562 }
563
564 #[test]
565 fn test_validate_empty_stack_effect() {
566 let content = r#"
567[[library]]
568name = "mylib"
569link = "mylib"
570
571[[library.function]]
572c_name = "my_func"
573seq_name = "my-func"
574stack_effect = ""
575"#;
576
577 let result = FfiManifest::parse(content);
578 assert!(result.is_err());
579 assert!(result.unwrap_err().contains("empty stack_effect"));
580 }
581
582 #[test]
583 fn test_validate_malformed_stack_effect_no_parens() {
584 let content = r#"
585[[library]]
586name = "mylib"
587link = "mylib"
588
589[[library.function]]
590c_name = "my_func"
591seq_name = "my-func"
592stack_effect = "String -- Int"
593"#;
594
595 let result = FfiManifest::parse(content);
596 assert!(result.is_err());
597 let err = result.unwrap_err();
598 assert!(err.contains("malformed stack_effect"));
599 }
600
601 #[test]
602 fn test_validate_malformed_stack_effect_no_separator() {
603 let content = r#"
604[[library]]
605name = "mylib"
606link = "mylib"
607
608[[library.function]]
609c_name = "my_func"
610seq_name = "my-func"
611stack_effect = "( String Int )"
612"#;
613
614 let result = FfiManifest::parse(content);
615 assert!(result.is_err());
616 let err = result.unwrap_err();
617 assert!(err.contains("malformed stack_effect"));
618 assert!(err.contains("--"));
619 }
620
621 #[test]
622 fn test_validate_malformed_stack_effect_unknown_type() {
623 let content = r#"
624[[library]]
625name = "mylib"
626link = "mylib"
627
628[[library.function]]
629c_name = "my_func"
630seq_name = "my-func"
631stack_effect = "( UnknownType -- Int )"
632"#;
633
634 let result = FfiManifest::parse(content);
635 assert!(result.is_err());
636 let err = result.unwrap_err();
637 assert!(err.contains("malformed stack_effect"));
638 assert!(err.contains("Unknown type"));
639 }
640
641 #[test]
642 fn test_validate_no_libraries() {
643 let content = r#"
647library = []
648"#;
649
650 let result = FfiManifest::parse(content);
651 assert!(result.is_err());
652 assert!(result.unwrap_err().contains("at least one library"));
653 }
654
655 #[test]
656 fn test_validate_linker_flag_injection() {
657 let content = r#"
659[[library]]
660name = "evil"
661link = "evil -Wl,-rpath,/malicious"
662
663[[library.function]]
664c_name = "func"
665seq_name = "func"
666stack_effect = "( -- )"
667"#;
668
669 let result = FfiManifest::parse(content);
670 assert!(result.is_err());
671 let err = result.unwrap_err();
672 assert!(err.contains("invalid character"));
673 }
674
675 #[test]
676 fn test_validate_linker_flag_valid() {
677 let content = r#"
679[[library]]
680name = "test"
681link = "my-lib_2.0"
682
683[[library.function]]
684c_name = "func"
685seq_name = "func"
686stack_effect = "( -- )"
687"#;
688
689 let result = FfiManifest::parse(content);
690 assert!(result.is_ok());
691 }
692}