1use std::fmt::Write;
2
3use crate::analysis::registry::TypeRegistry;
4use crate::ast::stmt::{Stmt, TypeExpr};
5use crate::intern::Interner;
6use super::ffi::{CAbiClass, classify_type_for_c_abi};
7
8pub fn generate_python_bindings(
10 stmts: &[Stmt],
11 module_name: &str,
12 interner: &Interner,
13 registry: &TypeRegistry,
14) -> String {
15 let mut out = String::new();
16
17 writeln!(out, "\"\"\"Auto-generated Python bindings for {}.\"\"\"", module_name).unwrap();
18 writeln!(out, "import ctypes").unwrap();
19 writeln!(out, "from ctypes import c_int64, c_uint64, c_double, c_bool, c_char_p, c_void_p, c_size_t, POINTER").unwrap();
20 writeln!(out, "import os").unwrap();
21 writeln!(out, "import sys\n").unwrap();
22
23 writeln!(out, "class LogosError(Exception):").unwrap();
24 writeln!(out, " pass\n").unwrap();
25
26 writeln!(out, "class LogosRefinementError(LogosError):").unwrap();
27 writeln!(out, " pass\n").unwrap();
28
29 writeln!(out, "def _lib_ext():").unwrap();
30 writeln!(out, " if sys.platform == \"darwin\":").unwrap();
31 writeln!(out, " return \".dylib\"").unwrap();
32 writeln!(out, " elif sys.platform == \"win32\":").unwrap();
33 writeln!(out, " return \".dll\"").unwrap();
34 writeln!(out, " else:").unwrap();
35 writeln!(out, " return \".so\"\n").unwrap();
36
37 let class_name = module_name.chars().next().unwrap_or('M').to_uppercase().to_string()
38 + &module_name[1..];
39
40 writeln!(out, "class {}:", class_name).unwrap();
41 writeln!(out, " OK = 0").unwrap();
42 writeln!(out, " ERROR = 1").unwrap();
43 writeln!(out, " REFINEMENT_VIOLATION = 2").unwrap();
44 writeln!(out, " NULL_POINTER = 3").unwrap();
45 writeln!(out, " OUT_OF_BOUNDS = 4\n").unwrap();
46
47 writeln!(out, " def __init__(self, path=None):").unwrap();
48 writeln!(out, " if path is None:").unwrap();
49 writeln!(out, " path = os.path.join(os.path.dirname(__file__), \"lib{}\" + _lib_ext())", module_name).unwrap();
50 writeln!(out, " self._lib = ctypes.CDLL(path)").unwrap();
51 writeln!(out, " self._setup()\n").unwrap();
52
53 writeln!(out, " def _check(self, status):").unwrap();
54 writeln!(out, " if status != self.OK:").unwrap();
55 writeln!(out, " err = self._lib.logos_get_last_error()").unwrap();
56 writeln!(out, " msg = err.decode(\"utf-8\") if err else \"Unknown error\"").unwrap();
57 writeln!(out, " self._lib.logos_clear_error()").unwrap();
58 writeln!(out, " if status == self.REFINEMENT_VIOLATION:").unwrap();
59 writeln!(out, " raise LogosRefinementError(msg)").unwrap();
60 writeln!(out, " raise LogosError(msg)\n").unwrap();
61
62 writeln!(out, " def _setup(self):").unwrap();
64 writeln!(out, " self._lib.logos_get_last_error.restype = c_char_p").unwrap();
65 writeln!(out, " self._lib.logos_clear_error.restype = None").unwrap();
66 writeln!(out, " self._lib.logos_free_string.argtypes = [c_char_p]").unwrap();
67 writeln!(out, " self._lib.logos_free_string.restype = None").unwrap();
68
69 for stmt in stmts {
71 if let Stmt::FunctionDef { name, is_exported: true, export_target, params, return_type, .. } = stmt {
72 let is_c = match export_target {
73 None => true,
74 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
75 };
76 if !is_c { continue; }
77
78 let func_name = format!("logos_{}", interner.resolve(*name));
79 let mut argtypes = Vec::new();
80 for (_, ptype) in params.iter() {
81 argtypes.push(python_ctypes_type(ptype, interner, registry));
82 }
83 let restype = return_type
84 .map(|ty| python_ctypes_type(ty, interner, registry))
85 .unwrap_or_else(|| "None".to_string());
86
87 writeln!(out, " self._lib.{}.argtypes = [{}]", func_name, argtypes.join(", ")).unwrap();
88 writeln!(out, " self._lib.{}.restype = {}", func_name, restype).unwrap();
89 }
90 }
91 writeln!(out).unwrap();
92
93 for stmt in stmts {
95 if let Stmt::FunctionDef { name, is_exported: true, export_target, params, return_type, .. } = stmt {
96 let is_c = match export_target {
97 None => true,
98 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
99 };
100 if !is_c { continue; }
101
102 let raw_name = interner.resolve(*name);
103 let c_func_name = format!("logos_{}", raw_name);
104 let param_names: Vec<String> = params.iter()
105 .map(|(pname, _)| interner.resolve(*pname).to_string())
106 .collect();
107 let type_hints: Vec<String> = params.iter()
108 .map(|(pname, ptype)| {
109 format!("{}: {}", interner.resolve(*pname), python_type_hint(ptype, interner))
110 })
111 .collect();
112 let ret_hint = return_type
113 .map(|ty| format!(" -> {}", python_type_hint(ty, interner)))
114 .unwrap_or_default();
115
116 writeln!(out, " def {}(self, {}){}:", raw_name, type_hints.join(", "), ret_hint).unwrap();
118 writeln!(out, " return self._lib.{}({})", c_func_name, param_names.join(", ")).unwrap();
119 writeln!(out).unwrap();
120 }
121 }
122
123 out
124}
125
126fn python_ctypes_type(ty: &TypeExpr, interner: &Interner, registry: &TypeRegistry) -> String {
127 match classify_type_for_c_abi(ty, interner, registry) {
128 CAbiClass::ReferenceType => "c_void_p".to_string(),
129 CAbiClass::ValueType => {
130 match ty {
131 TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
132 let name = interner.resolve(*sym);
133 match name {
134 "Int" => "c_int64".to_string(),
135 "Nat" => "c_uint64".to_string(),
136 "Real" | "Float" => "c_double".to_string(),
137 "Bool" | "Boolean" => "c_bool".to_string(),
138 "Text" | "String" => "c_char_p".to_string(),
139 _ => "c_void_p".to_string(),
140 }
141 }
142 _ => "c_void_p".to_string(),
143 }
144 }
145 }
146}
147
148fn python_type_hint(ty: &TypeExpr, interner: &Interner) -> String {
149 match ty {
150 TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
151 let name = interner.resolve(*sym);
152 match name {
153 "Int" | "Nat" => "int".to_string(),
154 "Real" | "Float" => "float".to_string(),
155 "Bool" | "Boolean" => "bool".to_string(),
156 "Text" | "String" => "str".to_string(),
157 other => other.to_string(),
158 }
159 }
160 _ => "object".to_string(),
161 }
162}
163
164pub fn generate_typescript_bindings(
166 stmts: &[Stmt],
167 module_name: &str,
168 interner: &Interner,
169 registry: &TypeRegistry,
170) -> (String, String) {
171 let mut dts = String::new();
172 let mut js = String::new();
173
174 writeln!(dts, "// Auto-generated TypeScript definitions for {}", module_name).unwrap();
176 let mut ffi_entries = Vec::new();
177
178 for stmt in stmts {
179 if let Stmt::FunctionDef { name, is_exported: true, export_target, params, return_type, .. } = stmt {
180 let is_c = match export_target {
181 None => true,
182 Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
183 };
184 if !is_c { continue; }
185
186 let raw_name = interner.resolve(*name);
187 let c_symbol = format!("logos_{}", raw_name);
188 let ts_params: Vec<String> = params.iter()
189 .map(|(pname, ptype)| format!("{}: {}", interner.resolve(*pname), typescript_type(ptype, interner)))
190 .collect();
191 let ts_ret = return_type
192 .map(|ty| typescript_type(ty, interner))
193 .unwrap_or_else(|| "void".to_string());
194 writeln!(dts, "export declare function {}({}): {};", raw_name, ts_params.join(", "), ts_ret).unwrap();
195
196 let ffi_params: Vec<String> = params.iter()
198 .map(|(_, ptype)| ffi_napi_type(ptype, interner, registry))
199 .collect();
200 let ffi_ret = return_type
201 .map(|ty| ffi_napi_type(ty, interner, registry))
202 .unwrap_or_else(|| "'void'".to_string());
203 ffi_entries.push((raw_name.to_string(), c_symbol, ffi_ret, ffi_params));
204 }
205 }
206
207 writeln!(js, "const koffi = require('koffi');").unwrap();
209 writeln!(js, "const path = require('path');\n").unwrap();
210 writeln!(js, "const libPath = path.join(__dirname, 'lib{}');", module_name).unwrap();
211 writeln!(js, "const lib = koffi.load(libPath);\n").unwrap();
212
213 writeln!(js, "const logos_get_last_error = lib.func('const char* logos_get_last_error()');").unwrap();
215 writeln!(js, "const logos_clear_error = lib.func('void logos_clear_error()');").unwrap();
216 writeln!(js, "const logos_free_string = lib.func('void logos_free_string(void* ptr)');\n").unwrap();
217
218 for (raw_name, c_symbol, ffi_ret, ffi_params) in &ffi_entries {
220 let koffi_ret = ffi_napi_to_koffi(ffi_ret);
221 let koffi_params: Vec<String> = ffi_params.iter()
222 .enumerate()
223 .map(|(i, p)| format!("{} arg{}", ffi_napi_to_koffi(p), i))
224 .collect();
225 writeln!(js, "const _{} = lib.func('{} {}({})');\n", raw_name, koffi_ret, c_symbol, koffi_params.join(", ")).unwrap();
226 }
227
228 writeln!(js, "function checkStatus(status) {{").unwrap();
229 writeln!(js, " if (status !== 0) {{").unwrap();
230 writeln!(js, " const err = logos_get_last_error();").unwrap();
231 writeln!(js, " logos_clear_error();").unwrap();
232 writeln!(js, " throw new Error(err || 'Unknown LogicAffeine error');").unwrap();
233 writeln!(js, " }}").unwrap();
234 writeln!(js, "}}\n").unwrap();
235
236 for (raw_name, _, _, _) in &ffi_entries {
237 let params_from_stmts = stmts.iter().find_map(|s| {
238 if let Stmt::FunctionDef { name, is_exported: true, params, .. } = s {
239 if interner.resolve(*name) == raw_name.as_str() {
240 Some(params)
241 } else {
242 None
243 }
244 } else {
245 None
246 }
247 });
248 if let Some(params) = params_from_stmts {
249 let param_names: Vec<String> = params.iter()
250 .map(|(pname, _)| interner.resolve(*pname).to_string())
251 .collect();
252 writeln!(js, "module.exports.{} = ({}) => _{}({});", raw_name, param_names.join(", "), raw_name, param_names.join(", ")).unwrap();
253 }
254 }
255
256 (js, dts)
257}
258
259fn typescript_type(ty: &TypeExpr, interner: &Interner) -> String {
260 match ty {
261 TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
262 let name = interner.resolve(*sym);
263 match name {
264 "Int" | "Nat" | "Real" | "Float" | "Byte" => "number".to_string(),
265 "Bool" | "Boolean" => "boolean".to_string(),
266 "Text" | "String" | "Char" => "string".to_string(),
267 "Unit" => "void".to_string(),
268 other => other.to_string(),
269 }
270 }
271 TypeExpr::Generic { base, params } => {
272 let base_name = interner.resolve(*base);
273 match base_name {
274 "Seq" | "List" | "Vec" if !params.is_empty() => {
275 format!("{}[]", typescript_type(¶ms[0], interner))
276 }
277 "Option" | "Maybe" if !params.is_empty() => {
278 format!("{} | null", typescript_type(¶ms[0], interner))
279 }
280 _ => "any".to_string(),
281 }
282 }
283 _ => "any".to_string(),
284 }
285}
286
287fn ffi_napi_to_koffi(ffi_type: &str) -> &str {
289 match ffi_type {
290 "'int64'" => "int64_t",
291 "'uint64'" => "uint64_t",
292 "'double'" => "double",
293 "'bool'" => "bool",
294 "'string'" => "const char*",
295 "'pointer'" => "void*",
296 "'void'" => "void",
297 _ => "void*",
298 }
299}
300
301fn ffi_napi_type(ty: &TypeExpr, interner: &Interner, registry: &TypeRegistry) -> String {
302 match classify_type_for_c_abi(ty, interner, registry) {
303 CAbiClass::ReferenceType => "'pointer'".to_string(),
304 CAbiClass::ValueType => {
305 match ty {
306 TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
307 let name = interner.resolve(*sym);
308 match name {
309 "Int" => "'int64'".to_string(),
310 "Nat" => "'uint64'".to_string(),
311 "Real" | "Float" => "'double'".to_string(),
312 "Bool" | "Boolean" => "'bool'".to_string(),
313 "Text" | "String" => "'string'".to_string(),
314 _ => "'pointer'".to_string(),
315 }
316 }
317 _ => "'pointer'".to_string(),
318 }
319 }
320 }
321}