1use alef_codegen::generators::trait_bridge::{BridgeOutput, TraitBridgeGenerator, TraitBridgeSpec, gen_bridge_all};
7use alef_core::config::TraitBridgeConfig;
8use alef_core::ir::{ApiSurface, MethodDef, TypeDef, TypeRef};
9use std::collections::HashMap;
10use std::fmt::Write;
11
12pub struct PhpBridgeGenerator {
15 pub core_import: String,
17 pub type_paths: HashMap<String, String>,
19 pub error_type: String,
21}
22
23impl TraitBridgeGenerator for PhpBridgeGenerator {
24 fn foreign_object_type(&self) -> &str {
25 "*mut ext_php_rs::types::ZendObject"
26 }
27
28 fn bridge_imports(&self) -> Vec<String> {
29 vec!["std::sync::Arc".to_string()]
30 }
31
32 fn gen_sync_method_body(&self, method: &MethodDef, _spec: &TraitBridgeSpec) -> String {
33 let name = &method.name;
34 let mut out = String::with_capacity(512);
35
36 writeln!(
38 out,
39 "// SAFETY: PHP objects are single-threaded; method calls are safe within a request."
40 )
41 .ok();
42
43 let has_args = !method.params.is_empty();
44 if has_args {
45 writeln!(out, "let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").ok();
46 for p in &method.params {
47 let arg_expr = match &p.ty {
48 TypeRef::String => format!("ext_php_rs::types::Zval::try_from({}).unwrap_or_default()", p.name),
49 TypeRef::Path => format!(
50 "ext_php_rs::types::Zval::try_from({}.to_string_lossy().to_string()).unwrap_or_default()",
51 p.name
52 ),
53 TypeRef::Bytes => format!(
54 "ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default()",
55 p.name
56 ),
57 TypeRef::Named(_) => {
58 format!(
59 "ext_php_rs::types::Zval::try_from(serde_json::to_string(&{}).unwrap_or_default()).unwrap_or_default()",
60 p.name
61 )
62 }
63 TypeRef::Primitive(_) => {
64 format!("ext_php_rs::types::Zval::try_from({}).unwrap_or_default()", p.name)
65 }
66 _ => format!(
67 "ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default()",
68 p.name
69 ),
70 };
71 writeln!(out, "args.push({arg_expr});").ok();
72 }
73 }
74
75 let args_expr = if has_args {
76 "args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect()"
77 } else {
78 "vec![]"
79 };
80
81 writeln!(
82 out,
83 "let result = unsafe {{ (*self.inner).try_call_method(\"{name}\", {args_expr}) }};"
84 )
85 .ok();
86
87 if method.error_type.is_some() {
89 writeln!(out, "match result {{").ok();
90 if matches!(method.return_type, TypeRef::Unit) {
92 writeln!(out, " Ok(_) => Ok(()),").ok();
93 } else {
94 writeln!(out, " Ok(val) => {{").ok();
96 writeln!(out, " let json_str = val.string().unwrap_or_default();").ok();
97 writeln!(out, " serde_json::from_str(&json_str).map_err(|e| {}::KreuzbergError::Other(format!(\"Deserialize error: {{}}\", e)))", self.core_import).ok();
98 writeln!(out, " }}").ok();
99 }
100 writeln!(
103 out,
104 " Err(e) => Err({}::KreuzbergError::Other(e.to_string())),",
105 self.core_import
106 )
107 .ok();
108 writeln!(out, "}}").ok();
109 } else {
110 writeln!(out, "match result {{").ok();
111 if matches!(method.return_type, TypeRef::Unit) {
113 writeln!(out, " Ok(_) => (),").ok();
114 } else {
115 writeln!(out, " Ok(val) => {{").ok();
118 writeln!(out, " let json_str = val.string().unwrap_or_default();").ok();
119 writeln!(out, " serde_json::from_str(&json_str).unwrap_or_default()").ok();
120 writeln!(out, " }}").ok();
121 }
122 writeln!(out, " Err(_) => Default::default(),").ok();
123 writeln!(out, "}}").ok();
124 }
125
126 out
127 }
128
129 fn gen_async_method_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
130 let name = &method.name;
131 let mut out = String::with_capacity(1024);
132
133 writeln!(out, "let inner_obj = self.inner.clone();").ok();
134 writeln!(out, "let cached_name = self.cached_name.clone();").ok();
135
136 for p in &method.params {
138 match &p.ty {
139 TypeRef::String => {
140 writeln!(out, "let {} = {}.clone();", p.name, p.name).ok();
141 }
142 _ => {
143 writeln!(out, "let {} = {};", p.name, p.name).ok();
144 }
145 }
146 }
147
148 writeln!(out).ok();
149 writeln!(out, "// SAFETY: PHP objects are single-threaded within a request.").ok();
150 writeln!(out, "// The block_on executes within the async runtime.").ok();
151 writeln!(out, "let result = WORKER_RUNTIME.block_on(async {{").ok();
152
153 let has_args = !method.params.is_empty();
154 if has_args {
155 writeln!(out, " let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").ok();
156 for p in &method.params {
157 let arg_expr = match &p.ty {
158 TypeRef::String => format!("ext_php_rs::types::Zval::try_from({}).unwrap_or_default()", p.name),
159 TypeRef::Path => format!(
160 "ext_php_rs::types::Zval::try_from({}.to_string_lossy().to_string()).unwrap_or_default()",
161 p.name
162 ),
163 TypeRef::Bytes => format!(
164 "ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default()",
165 p.name
166 ),
167 TypeRef::Named(_) => {
168 format!(
169 "ext_php_rs::types::Zval::try_from(serde_json::to_string(&{}).unwrap_or_default()).unwrap_or_default()",
170 p.name
171 )
172 }
173 TypeRef::Primitive(_) => {
174 format!("ext_php_rs::types::Zval::try_from({}).unwrap_or_default()", p.name)
175 }
176 _ => format!(
177 "ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default()",
178 p.name
179 ),
180 };
181 writeln!(out, " args.push({arg_expr});").ok();
182 }
183 }
184
185 let args_expr = if has_args {
186 "args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect()"
187 } else {
188 "vec![]"
189 };
190
191 writeln!(
194 out,
195 " match unsafe {{ (*inner_obj).try_call_method(\"{name}\", {args_expr}) }} {{"
196 )
197 .ok();
198
199 if method.error_type.is_some() {
201 writeln!(out, " Ok(val) => {{").ok();
202 writeln!(out, " let json_str = val.string().unwrap_or_default();").ok();
203 writeln!(
204 out,
205 " serde_json::from_str(&json_str).map_err(|e| {}::KreuzbergError::Other(format!(\"Deserialize error: {{}}\", e)))",
206 spec.core_import
207 )
208 .ok();
209 writeln!(out, " }}").ok();
210 } else {
211 writeln!(
212 out,
213 " Ok(val) => val.string().unwrap_or_default().parse().unwrap_or_default(),"
214 )
215 .ok();
216 }
217
218 writeln!(
219 out,
220 " Err(e) => Err({}::KreuzbergError::Plugin {{",
221 spec.core_import
222 )
223 .ok();
224 writeln!(
225 out,
226 " message: format!(\"Plugin '{{}}' method '{name}' failed: {{}}\", cached_name, e),"
227 )
228 .ok();
229 writeln!(out, " plugin_name: cached_name.clone(),").ok();
230 writeln!(out, " }}),").ok();
231 writeln!(out, " }}").ok();
232 writeln!(out, "}});").ok();
233 writeln!(out, "result").ok();
234
235 out
236 }
237
238 fn gen_constructor(&self, spec: &TraitBridgeSpec) -> String {
239 let wrapper = spec.wrapper_name();
240 let mut out = String::with_capacity(512);
241
242 writeln!(out, "impl {wrapper} {{").ok();
243 writeln!(out, " /// Create a new bridge wrapping a PHP object.").ok();
244 writeln!(out, " ///").ok();
245 writeln!(
246 out,
247 " /// Validates that the PHP object provides all required methods."
248 )
249 .ok();
250 writeln!(
251 out,
252 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
253 )
254 .ok();
255
256 writeln!(out, " let cached_name = php_obj").ok();
261 writeln!(out, " .try_call_method(\"name\", vec![])").ok();
262 writeln!(out, " .ok()").ok();
263 writeln!(out, " .and_then(|v| v.string())").ok();
264 writeln!(out, " .unwrap_or(\"unknown\".into())").ok();
265 writeln!(out, " .to_string();").ok();
266
267 writeln!(out).ok();
268 writeln!(out, " Self {{").ok();
269 writeln!(out, " inner: php_obj as *mut _,").ok();
270 writeln!(out, " cached_name,").ok();
271 writeln!(out, " }}").ok();
272 writeln!(out, " }}").ok();
273 writeln!(out, "}}").ok();
274
275 writeln!(out).ok();
279 writeln!(out, "// SAFETY: PHP is single-threaded within a request context.").ok();
280 writeln!(
281 out,
282 "// The raw pointer to ZendObject is only used within a single PHP request"
283 )
284 .ok();
285 writeln!(out, "// and is never accessed concurrently from multiple threads.").ok();
286 writeln!(out, "unsafe impl Send for {wrapper} {{}}").ok();
287 writeln!(out, "unsafe impl Sync for {wrapper} {{}}").ok();
288
289 out
290 }
291
292 fn gen_registration_fn(&self, spec: &TraitBridgeSpec) -> String {
293 let Some(register_fn) = spec.bridge_config.register_fn.as_deref() else {
294 return String::new();
295 };
296 let Some(registry_getter) = spec.bridge_config.registry_getter.as_deref() else {
297 return String::new();
298 };
299 let wrapper = spec.wrapper_name();
300 let trait_path = spec.trait_path();
301
302 let mut out = String::with_capacity(1024);
303
304 writeln!(out, "#[php_function]").ok();
305 writeln!(
306 out,
307 "pub fn {register_fn}(backend: &mut ext_php_rs::types::ZendObject) -> ext_php_rs::prelude::PhpResult<()> {{"
308 )
309 .ok();
310
311 let req_methods: Vec<&MethodDef> = spec.required_methods();
313 if !req_methods.is_empty() {
314 for method in &req_methods {
315 writeln!(
317 out,
318 r#" if backend.try_call_method("{}".into(), vec![]).is_err() {{"#,
319 method.name
320 )
321 .ok();
322 writeln!(out, " return Err(ext_php_rs::exception::PhpException::default(").ok();
323 writeln!(
324 out,
325 " format!(\"Backend missing required method: {{}}\", \"{}\")",
326 method.name
327 )
328 .ok();
329 writeln!(out, " ).into());").ok();
330 writeln!(out, " }}").ok();
331 }
332 }
333
334 writeln!(out).ok();
335 writeln!(out, " let wrapper = {wrapper}::new(backend);").ok();
336 writeln!(
337 out,
338 " let arc: std::sync::Arc<dyn {trait_path}> = std::sync::Arc::new(wrapper);"
339 )
340 .ok();
341 writeln!(out).ok();
342
343 let extra = spec
344 .bridge_config
345 .register_extra_args
346 .as_deref()
347 .map(|a| format!(", {a}"))
348 .unwrap_or_default();
349 writeln!(out, " let registry = {registry_getter}();").ok();
350 writeln!(out, " let mut registry = registry.write();").ok();
351 writeln!(
352 out,
353 " registry.register(arc{extra}).map_err(|e| ext_php_rs::exception::PhpException::default("
354 )
355 .ok();
356 writeln!(out, " format!(\"Failed to register backend: {{}}\", e)").ok();
357 writeln!(out, " ))?;").ok();
358 writeln!(out, " Ok(())").ok();
359 writeln!(out, "}}").ok();
360
361 out
362 }
363}
364
365pub fn gen_trait_bridge(
367 trait_type: &TypeDef,
368 bridge_cfg: &TraitBridgeConfig,
369 core_import: &str,
370 error_type: &str,
371 error_constructor: &str,
372 api: &ApiSurface,
373) -> BridgeOutput {
374 let type_paths: HashMap<String, String> = api
376 .types
377 .iter()
378 .map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
379 .chain(
380 api.enums
381 .iter()
382 .map(|e| (e.name.clone(), e.rust_path.replace('-', "_"))),
383 )
384 .collect();
385
386 let is_visitor_bridge = bridge_cfg.type_alias.is_some()
388 && bridge_cfg.register_fn.is_none()
389 && bridge_cfg.super_trait.is_none()
390 && trait_type.methods.iter().all(|m| m.has_default_impl);
391
392 if is_visitor_bridge {
393 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
394 let trait_path = trait_type.rust_path.replace('-', "_");
395 let code = gen_visitor_bridge(trait_type, bridge_cfg, &struct_name, &trait_path, &type_paths);
396 BridgeOutput { imports: vec![], code }
397 } else {
398 let generator = PhpBridgeGenerator {
400 core_import: core_import.to_string(),
401 type_paths: type_paths.clone(),
402 error_type: error_type.to_string(),
403 };
404 let spec = TraitBridgeSpec {
405 trait_def: trait_type,
406 bridge_config: bridge_cfg,
407 core_import,
408 wrapper_prefix: "Php",
409 type_paths,
410 error_type: error_type.to_string(),
411 error_constructor: error_constructor.to_string(),
412 };
413 gen_bridge_all(&spec, &generator)
414 }
415}
416
417fn gen_visitor_bridge(
422 trait_type: &TypeDef,
423 _bridge_cfg: &TraitBridgeConfig,
424 struct_name: &str,
425 trait_path: &str,
426 type_paths: &HashMap<String, String>,
427) -> String {
428 let mut out = String::with_capacity(4096);
429 let core_crate = trait_path
430 .split("::")
431 .next()
432 .unwrap_or("html_to_markdown_rs")
433 .to_string();
434 writeln!(out, "fn nodecontext_to_php_array(").unwrap();
436 writeln!(out, " ctx: &{core_crate}::visitor::NodeContext,").unwrap();
437 writeln!(out, ") -> ext_php_rs::boxed::ZBox<ext_php_rs::types::ZendHashTable> {{").unwrap();
438 writeln!(out, " let mut arr = ext_php_rs::types::ZendHashTable::new();").unwrap();
439 writeln!(
440 out,
441 " arr.insert(\"nodeType\", ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", ctx.node_type)).unwrap_or_default()).ok();"
442 )
443 .unwrap();
444 writeln!(
445 out,
446 " arr.insert(\"tagName\", ext_php_rs::types::Zval::try_from(ctx.tag_name.clone()).unwrap_or_default()).ok();"
447 )
448 .unwrap();
449 writeln!(
450 out,
451 " arr.insert(\"depth\", ext_php_rs::types::Zval::try_from(ctx.depth as i64).unwrap_or_default()).ok();"
452 )
453 .unwrap();
454 writeln!(
455 out,
456 " arr.insert(\"indexInParent\", ext_php_rs::types::Zval::try_from(ctx.index_in_parent as i64).unwrap_or_default()).ok();"
457 )
458 .unwrap();
459 writeln!(
460 out,
461 " arr.insert(\"isInline\", ext_php_rs::types::Zval::try_from(ctx.is_inline).unwrap_or_default()).ok();"
462 )
463 .unwrap();
464 writeln!(out, " if let Some(ref pt) = ctx.parent_tag {{").unwrap();
465 writeln!(
466 out,
467 " arr.insert(\"parentTag\", ext_php_rs::types::Zval::try_from(pt.clone()).unwrap_or_default()).ok();"
468 )
469 .unwrap();
470 writeln!(out, " }}").unwrap();
471 writeln!(out, " let mut attrs = ext_php_rs::types::ZendHashTable::new();").unwrap();
472 writeln!(out, " for (k, v) in &ctx.attributes {{").unwrap();
473 writeln!(
474 out,
475 " attrs.insert(k.as_str(), ext_php_rs::types::Zval::try_from(v.clone()).unwrap_or_default()).ok();"
476 )
477 .unwrap();
478 writeln!(out, " }}").unwrap();
479 writeln!(out, " let mut attrs_zval = ext_php_rs::types::Zval::new();").unwrap();
480 writeln!(out, " attrs_zval.set_hashtable(attrs);").unwrap();
481 writeln!(out, " arr.insert(\"attributes\", attrs_zval).ok();").unwrap();
482 writeln!(out, " arr").unwrap();
483 writeln!(out, "}}").unwrap();
484 writeln!(out).unwrap();
485
486 writeln!(out, "pub struct {struct_name} {{").unwrap();
490 writeln!(out, " php_obj: *mut ext_php_rs::types::ZendObject,").unwrap();
491 writeln!(out, " cached_name: String,").unwrap();
492 writeln!(out, "}}").unwrap();
493 writeln!(out).unwrap();
494
495 writeln!(out, "// SAFETY: PHP objects are single-threaded; the bridge is used").unwrap();
498 writeln!(out, "// only within a single PHP request, never across threads.").unwrap();
499 writeln!(out, "unsafe impl Send for {struct_name} {{}}").unwrap();
500 writeln!(out, "unsafe impl Sync for {struct_name} {{}}").unwrap();
501 writeln!(out).unwrap();
502
503 writeln!(out, "impl Clone for {struct_name} {{").unwrap();
504 writeln!(out, " fn clone(&self) -> Self {{").unwrap();
505 writeln!(out, " Self {{").unwrap();
506 writeln!(out, " php_obj: self.php_obj,").unwrap();
507 writeln!(out, " cached_name: self.cached_name.clone(),").unwrap();
508 writeln!(out, " }}").unwrap();
509 writeln!(out, " }}").unwrap();
510 writeln!(out, "}}").unwrap();
511 writeln!(out).unwrap();
512
513 writeln!(out, "impl std::fmt::Debug for {struct_name} {{").unwrap();
515 writeln!(
516 out,
517 " fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
518 )
519 .unwrap();
520 writeln!(out, " write!(f, \"{struct_name}\")").unwrap();
521 writeln!(out, " }}").unwrap();
522 writeln!(out, "}}").unwrap();
523 writeln!(out).unwrap();
524
525 writeln!(out, "impl {struct_name} {{").unwrap();
529 writeln!(
530 out,
531 " pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {{"
532 )
533 .unwrap();
534 writeln!(out, " let cached_name = php_obj").unwrap();
535 writeln!(out, " .try_call_method(\"name\", vec![])").unwrap();
536 writeln!(out, " .ok()").unwrap();
537 writeln!(out, " .and_then(|v| v.string())").unwrap();
538 writeln!(out, " .unwrap_or(\"unknown\".into())").unwrap();
539 writeln!(out, " .to_string();").unwrap();
540 writeln!(out, " Self {{ php_obj: php_obj as *mut _, cached_name }}").unwrap();
541 writeln!(out, " }}").unwrap();
542 writeln!(out, "}}").unwrap();
543 writeln!(out).unwrap();
544
545 writeln!(out, "impl {trait_path} for {struct_name} {{").unwrap();
547 for method in &trait_type.methods {
548 if method.trait_source.is_some() {
549 continue;
550 }
551 gen_visitor_method_php(&mut out, method, type_paths);
552 }
553 writeln!(out, "}}").unwrap();
554 writeln!(out).unwrap();
555
556 out
557}
558
559fn visitor_param_type(ty: &TypeRef, is_ref: bool, optional: bool, tp: &HashMap<String, String>) -> String {
561 if optional && matches!(ty, TypeRef::String) && is_ref {
562 return "Option<&str>".to_string();
563 }
564 if is_ref {
565 if let TypeRef::Vec(inner) = ty {
566 let inner_str = param_type(inner, "", false, tp);
567 return format!("&[{inner_str}]");
568 }
569 }
570 param_type(ty, "", is_ref, tp)
571}
572
573fn gen_visitor_method_php(out: &mut String, method: &MethodDef, type_paths: &HashMap<String, String>) {
575 let name = &method.name;
576 let php_name = to_camel_case(name);
577
578 let mut sig_parts = vec!["&mut self".to_string()];
579 for p in &method.params {
580 let ty_str = visitor_param_type(&p.ty, p.is_ref, p.optional, type_paths);
581 sig_parts.push(format!("{}: {}", p.name, ty_str));
582 }
583 let sig = sig_parts.join(", ");
584
585 let ret_ty = match &method.return_type {
586 TypeRef::Named(n) => type_paths.get(n).cloned().unwrap_or_else(|| n.clone()),
587 other => param_type(other, "", false, type_paths),
588 };
589
590 writeln!(out, " fn {name}({sig}) -> {ret_ty} {{").unwrap();
591
592 writeln!(
594 out,
595 " // SAFETY: php_obj is a valid ZendObject pointer for the duration of this call."
596 )
597 .unwrap();
598 writeln!(out, " let php_obj_ref = unsafe {{ &mut *self.php_obj }};").unwrap();
599
600 let has_args = !method.params.is_empty();
602 if has_args {
603 writeln!(out, " let mut args: Vec<ext_php_rs::types::Zval> = Vec::new();").unwrap();
604 for p in &method.params {
605 if let TypeRef::Named(n) = &p.ty {
606 if n == "NodeContext" {
607 writeln!(
608 out,
609 " let ctx_arr = nodecontext_to_php_array({}{});",
610 if p.is_ref { "" } else { "&" },
611 p.name
612 )
613 .unwrap();
614 writeln!(
615 out,
616 " args.push(ext_php_rs::convert::IntoZval::into_zval(ctx_arr, false).unwrap_or_default());"
617 )
618 .unwrap();
619 continue;
620 }
621 }
622 if p.optional && matches!(&p.ty, TypeRef::String) && p.is_ref {
625 writeln!(
626 out,
627 " args.push(match {0} {{ Some(s) => ext_php_rs::types::Zval::try_from(s.to_string()).unwrap_or_default(), None => ext_php_rs::types::Zval::new() }});",
628 p.name
629 )
630 .unwrap();
631 continue;
632 }
633 if matches!(&p.ty, TypeRef::String) {
634 if p.is_ref {
635 writeln!(
636 out,
637 " args.push(ext_php_rs::types::Zval::try_from({}.to_string()).unwrap_or_default());",
638 p.name
639 )
640 .unwrap();
641 } else {
642 writeln!(
643 out,
644 " args.push(ext_php_rs::types::Zval::try_from({}.clone()).unwrap_or_default());",
645 p.name
646 )
647 .unwrap();
648 }
649 continue;
650 }
651 if matches!(&p.ty, TypeRef::Primitive(alef_core::ir::PrimitiveType::Bool)) {
652 writeln!(
653 out,
654 " {{ let mut _zv = ext_php_rs::types::Zval::new(); _zv.set_bool({}); args.push(_zv); }}",
655 p.name
656 )
657 .unwrap();
658 continue;
659 }
660 writeln!(
662 out,
663 " args.push(ext_php_rs::types::Zval::try_from(format!(\"{{:?}}\", {})).unwrap_or_default());",
664 p.name
665 )
666 .unwrap();
667 }
668 }
669
670 if has_args {
674 writeln!(
675 out,
676 " let dyn_args: Vec<&dyn ext_php_rs::convert::IntoZvalDyn> = args.iter().map(|z| z as &dyn ext_php_rs::convert::IntoZvalDyn).collect();"
677 )
678 .unwrap();
679 }
680 let args_expr = if has_args { "dyn_args" } else { "vec![]" };
681 writeln!(
682 out,
683 " let result = php_obj_ref.try_call_method(\"{php_name}\", {args_expr});"
684 )
685 .unwrap();
686
687 writeln!(out, " match result {{").unwrap();
689 writeln!(out, " Err(_) => {ret_ty}::Continue,").unwrap();
690 writeln!(out, " Ok(val) => {{").unwrap();
691 writeln!(
692 out,
693 " let s = val.string().unwrap_or_default().to_lowercase();"
694 )
695 .unwrap();
696 writeln!(out, " match s.as_str() {{").unwrap();
697 writeln!(out, " \"continue\" => {ret_ty}::Continue,").unwrap();
698 writeln!(out, " \"skip\" => {ret_ty}::Skip,").unwrap();
699 writeln!(
700 out,
701 " \"preserve_html\" | \"preservehtml\" => {ret_ty}::PreserveHtml,"
702 )
703 .unwrap();
704 writeln!(out, " other => {ret_ty}::Custom(other.to_string()),").unwrap();
705 writeln!(out, " }}").unwrap();
706 writeln!(out, " }}").unwrap();
707 writeln!(out, " }}").unwrap();
708 writeln!(out, " }}").unwrap();
709 writeln!(out).unwrap();
710}
711
712fn to_camel_case(name: &str) -> String {
714 let mut result = String::with_capacity(name.len());
715 let mut capitalize_next = false;
716 for (i, c) in name.chars().enumerate() {
717 if c == '_' {
718 capitalize_next = true;
719 } else if capitalize_next {
720 result.extend(c.to_uppercase());
721 capitalize_next = false;
722 } else if i == 0 {
723 result.extend(c.to_lowercase());
724 } else {
725 result.push(c);
726 }
727 }
728 result
729}
730
731fn param_type(ty: &TypeRef, ci: &str, is_ref: bool, tp: &HashMap<String, String>) -> String {
733 match ty {
734 TypeRef::Bytes if is_ref => "&[u8]".into(),
735 TypeRef::Bytes => "Vec<u8>".into(),
736 TypeRef::String if is_ref => "&str".into(),
737 TypeRef::String => "String".into(),
738 TypeRef::Path if is_ref => "&std::path::Path".into(),
739 TypeRef::Path => "std::path::PathBuf".into(),
740 TypeRef::Named(n) => {
741 let qualified = tp.get(n).cloned().unwrap_or_else(|| format!("{ci}::{n}"));
742 if is_ref { format!("&{qualified}") } else { qualified }
743 }
744 TypeRef::Vec(inner) => format!("Vec<{}>", param_type(inner, ci, false, tp)),
745 TypeRef::Optional(inner) => format!("Option<{}>", param_type(inner, ci, false, tp)),
746 TypeRef::Primitive(p) => prim(p).into(),
747 TypeRef::Unit => "()".into(),
748 TypeRef::Char => "char".into(),
749 TypeRef::Map(k, v) => format!(
750 "std::collections::HashMap<{}, {}>",
751 param_type(k, ci, false, tp),
752 param_type(v, ci, false, tp)
753 ),
754 TypeRef::Json => "serde_json::Value".into(),
755 TypeRef::Duration => "std::time::Duration".into(),
756 }
757}
758
759fn prim(p: &alef_core::ir::PrimitiveType) -> &'static str {
760 use alef_core::ir::PrimitiveType::*;
761 match p {
762 Bool => "bool",
763 U8 => "u8",
764 U16 => "u16",
765 U32 => "u32",
766 U64 => "u64",
767 I8 => "i8",
768 I16 => "i16",
769 I32 => "i32",
770 I64 => "i64",
771 F32 => "f32",
772 F64 => "f64",
773 Usize => "usize",
774 Isize => "isize",
775 }
776}
777
778pub fn find_bridge_param<'a>(
783 func: &alef_core::ir::FunctionDef,
784 bridges: &'a [TraitBridgeConfig],
785) -> Option<(usize, &'a TraitBridgeConfig)> {
786 for (idx, param) in func.params.iter().enumerate() {
787 let named = match ¶m.ty {
788 TypeRef::Named(n) => Some(n.as_str()),
789 TypeRef::Optional(inner) => {
790 if let TypeRef::Named(n) = inner.as_ref() {
791 Some(n.as_str())
792 } else {
793 None
794 }
795 }
796 _ => None,
797 };
798 for bridge in bridges {
799 if let Some(type_name) = named {
800 if bridge.type_alias.as_deref() == Some(type_name) {
801 return Some((idx, bridge));
802 }
803 }
804 if bridge.param_name.as_deref() == Some(param.name.as_str()) {
805 return Some((idx, bridge));
806 }
807 }
808 }
809 None
810}
811
812#[allow(clippy::too_many_arguments)]
815pub fn gen_bridge_function(
816 func: &alef_core::ir::FunctionDef,
817 bridge_param_idx: usize,
818 bridge_cfg: &TraitBridgeConfig,
819 mapper: &dyn alef_codegen::type_mapper::TypeMapper,
820 opaque_types: &ahash::AHashSet<String>,
821 core_import: &str,
822) -> String {
823 use alef_core::ir::TypeRef;
824
825 let struct_name = format!("Php{}Bridge", bridge_cfg.trait_name);
826 let handle_path = format!("{core_import}::visitor::VisitorHandle");
827 let param_name = &func.params[bridge_param_idx].name;
828 let bridge_param = &func.params[bridge_param_idx];
829 let is_optional = bridge_param.optional || matches!(&bridge_param.ty, TypeRef::Optional(_));
830
831 let mut sig_parts = Vec::new();
833 for (idx, p) in func.params.iter().enumerate() {
834 if idx == bridge_param_idx {
835 let php_obj_ty = "&mut ext_php_rs::types::ZendObject";
839 if is_optional {
840 sig_parts.push(format!("{}: Option<{php_obj_ty}>", p.name));
841 } else {
842 sig_parts.push(format!("{}: {php_obj_ty}", p.name));
843 }
844 } else {
845 let promoted = idx > bridge_param_idx || func.params[..idx].iter().any(|pp| pp.optional);
846 let base = mapper.map_type(&p.ty);
847 let ty = match &p.ty {
850 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
851 if p.optional || promoted {
852 format!("Option<&mut {base}>")
853 } else {
854 format!("&mut {base}")
855 }
856 }
857 TypeRef::Optional(inner) => {
858 if let TypeRef::Named(n) = inner.as_ref() {
859 if !opaque_types.contains(n.as_str()) {
860 format!("Option<&mut {base}>")
861 } else if p.optional || promoted {
862 format!("Option<{base}>")
863 } else {
864 base
865 }
866 } else if p.optional || promoted {
867 format!("Option<{base}>")
868 } else {
869 base
870 }
871 }
872 _ => {
873 if p.optional || promoted {
874 format!("Option<{base}>")
875 } else {
876 base
877 }
878 }
879 };
880 sig_parts.push(format!("{}: {}", p.name, ty));
881 }
882 }
883
884 let params_str = sig_parts.join(", ");
885 let return_type = mapper.map_type(&func.return_type);
886 let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
887
888 let err_conv = ".map_err(|e| ext_php_rs::exception::PhpException::default(e.to_string()))";
889
890 let bridge_wrap = if is_optional {
892 format!(
893 "let {param_name} = {param_name}.map(|v| {{\n \
894 let bridge = {struct_name}::new(v);\n \
895 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
896 }});"
897 )
898 } else {
899 format!(
900 "let {param_name} = {{\n \
901 let bridge = {struct_name}::new({param_name});\n \
902 std::rc::Rc::new(std::cell::RefCell::new(bridge)) as {handle_path}\n \
903 }};"
904 )
905 };
906
907 let serde_bindings: String = func
909 .params
910 .iter()
911 .enumerate()
912 .filter(|(idx, p)| {
913 if *idx == bridge_param_idx {
914 return false;
915 }
916 let named = match &p.ty {
917 TypeRef::Named(n) => Some(n.as_str()),
918 TypeRef::Optional(inner) => {
919 if let TypeRef::Named(n) = inner.as_ref() {
920 Some(n.as_str())
921 } else {
922 None
923 }
924 }
925 _ => None,
926 };
927 named.is_some_and(|n| !opaque_types.contains(n))
928 })
929 .map(|(_, p)| {
930 let name = &p.name;
931 let core_path = format!(
932 "{core_import}::{}",
933 match &p.ty {
934 TypeRef::Named(n) => n.clone(),
935 TypeRef::Optional(inner) =>
936 if let TypeRef::Named(n) = inner.as_ref() {
937 n.clone()
938 } else {
939 String::new()
940 },
941 _ => String::new(),
942 }
943 );
944 if p.optional || matches!(&p.ty, TypeRef::Optional(_)) {
945 format!(
946 "let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n \
947 let json = serde_json::to_string(&v){err_conv}?;\n \
948 serde_json::from_str(&json){err_conv}\n \
949 }}).transpose()?;\n "
950 )
951 } else {
952 format!(
953 "let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n \
954 let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n "
955 )
956 }
957 })
958 .collect();
959
960 let call_args: Vec<String> = func
962 .params
963 .iter()
964 .enumerate()
965 .map(|(idx, p)| {
966 if idx == bridge_param_idx {
967 return p.name.clone();
968 }
969 match &p.ty {
970 TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
971 if p.optional {
972 format!("{}.as_ref().map(|v| &v.inner)", p.name)
973 } else {
974 format!("&{}.inner", p.name)
975 }
976 }
977 TypeRef::Named(_) => format!("{}_core", p.name),
978 TypeRef::Optional(inner) => {
979 if let TypeRef::Named(n) = inner.as_ref() {
980 if opaque_types.contains(n.as_str()) {
981 format!("{}.as_ref().map(|v| &v.inner)", p.name)
982 } else {
983 format!("{}_core", p.name)
984 }
985 } else {
986 p.name.clone()
987 }
988 }
989 TypeRef::String | TypeRef::Char => {
990 if p.is_ref {
991 format!("&{}", p.name)
992 } else {
993 p.name.clone()
994 }
995 }
996 _ => p.name.clone(),
997 }
998 })
999 .collect();
1000 let call_args_str = call_args.join(", ");
1001
1002 let core_fn_path = {
1003 let path = func.rust_path.replace('-', "_");
1004 if path.starts_with(core_import) {
1005 path
1006 } else {
1007 format!("{core_import}::{}", func.name)
1008 }
1009 };
1010 let core_call = format!("{core_fn_path}({call_args_str})");
1011
1012 let return_wrap = match &func.return_type {
1013 TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
1014 format!("{name} {{ inner: std::sync::Arc::new(val) }}")
1015 }
1016 TypeRef::Named(_) => "val.into()".to_string(),
1017 TypeRef::String | TypeRef::Bytes => "val.into()".to_string(),
1018 _ => "val".to_string(),
1019 };
1020
1021 let body = if func.error_type.is_some() {
1022 if return_wrap == "val" {
1023 format!("{bridge_wrap}\n {serde_bindings}{core_call}{err_conv}")
1024 } else {
1025 format!("{bridge_wrap}\n {serde_bindings}{core_call}.map(|val| {return_wrap}){err_conv}")
1026 }
1027 } else {
1028 format!("{bridge_wrap}\n {serde_bindings}{core_call}")
1029 };
1030
1031 let func_name = &func.name;
1032 let mut out = String::with_capacity(1024);
1033 if func.error_type.is_some() {
1034 writeln!(out, "#[allow(clippy::missing_errors_doc)]").ok();
1035 }
1036 writeln!(out, "pub fn {func_name}({params_str}) -> {ret} {{").ok();
1037 writeln!(out, " {body}").ok();
1038 writeln!(out, "}}").ok();
1039
1040 out
1041}