Skip to main content

alef_backend_java/gen_visitor/
callbacks.rs

1//! Callback specification table for the Java visitor bridge.
2//!
3//! Mirrors `crates/alef-backend-go/src/gen_visitor.rs`.
4
5pub struct CallbackSpec {
6    /// Field name in `HTMHtmVisitorCallbacks` (snake_case). Used for documentation.
7    pub c_field: &'static str,
8    /// Java interface method name (camelCase).
9    pub java_method: &'static str,
10    /// Javadoc line.
11    pub doc: &'static str,
12    /// Extra parameters beyond `NodeContext` in the Java interface.
13    pub extra: &'static [ExtraParam],
14    /// If true, add `boolean isHeader` (only visit_table_row).
15    pub has_is_header: bool,
16}
17
18pub struct ExtraParam {
19    /// Java parameter name in the interface.
20    pub java_name: &'static str,
21    /// Java type in the interface method signature.
22    pub java_type: &'static str,
23    /// Panama `ValueLayout` constants for each C-level argument that maps to this Java param.
24    /// One Java param can correspond to multiple C args (e.g. cells = ptr + count).
25    pub c_layouts: &'static [&'static str],
26    /// Java expression to build the interface-typed value from the raw C parameters.
27    /// Raw variables are named `raw_<java_name>_<idx>` where idx counts within c_layouts.
28    pub decode: &'static str,
29}
30
31pub const CALLBACKS: &[CallbackSpec] = &[
32    CallbackSpec {
33        c_field: "visit_text",
34        java_method: "visitText",
35        doc: "Called for text nodes.",
36        extra: &[ExtraParam {
37            java_name: "text",
38            java_type: "String",
39            c_layouts: &["ValueLayout.ADDRESS"],
40            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
41        }],
42        has_is_header: false,
43    },
44    CallbackSpec {
45        c_field: "visit_element_start",
46        java_method: "visitElementStart",
47        doc: "Called before entering any element.",
48        extra: &[],
49        has_is_header: false,
50    },
51    CallbackSpec {
52        c_field: "visit_element_end",
53        java_method: "visitElementEnd",
54        doc: "Called after exiting any element; receives the default markdown output.",
55        extra: &[ExtraParam {
56            java_name: "output",
57            java_type: "String",
58            c_layouts: &["ValueLayout.ADDRESS"],
59            decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
60        }],
61        has_is_header: false,
62    },
63    CallbackSpec {
64        c_field: "visit_link",
65        java_method: "visitLink",
66        doc: "Called for anchor links. title is null when the attribute is absent.",
67        extra: &[
68            ExtraParam {
69                java_name: "href",
70                java_type: "String",
71                c_layouts: &["ValueLayout.ADDRESS"],
72                decode: "raw_href_0.reinterpret(Long.MAX_VALUE).getString(0)",
73            },
74            ExtraParam {
75                java_name: "text",
76                java_type: "String",
77                c_layouts: &["ValueLayout.ADDRESS"],
78                decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
79            },
80            ExtraParam {
81                java_name: "title",
82                java_type: "String",
83                c_layouts: &["ValueLayout.ADDRESS"],
84                decode: "raw_title_0.equals(MemorySegment.NULL) ? null : raw_title_0.reinterpret(Long.MAX_VALUE).getString(0)",
85            },
86        ],
87        has_is_header: false,
88    },
89    CallbackSpec {
90        c_field: "visit_image",
91        java_method: "visitImage",
92        doc: "Called for images. title is null when absent.",
93        extra: &[
94            ExtraParam {
95                java_name: "src",
96                java_type: "String",
97                c_layouts: &["ValueLayout.ADDRESS"],
98                decode: "raw_src_0.reinterpret(Long.MAX_VALUE).getString(0)",
99            },
100            ExtraParam {
101                java_name: "alt",
102                java_type: "String",
103                c_layouts: &["ValueLayout.ADDRESS"],
104                decode: "raw_alt_0.reinterpret(Long.MAX_VALUE).getString(0)",
105            },
106            ExtraParam {
107                java_name: "title",
108                java_type: "String",
109                c_layouts: &["ValueLayout.ADDRESS"],
110                decode: "raw_title_0.equals(MemorySegment.NULL) ? null : raw_title_0.reinterpret(Long.MAX_VALUE).getString(0)",
111            },
112        ],
113        has_is_header: false,
114    },
115    CallbackSpec {
116        c_field: "visit_heading",
117        java_method: "visitHeading",
118        doc: "Called for heading elements h1-h6. id is null when absent.",
119        extra: &[
120            ExtraParam {
121                java_name: "level",
122                java_type: "int",
123                c_layouts: &["ValueLayout.JAVA_INT"],
124                decode: "(int) raw_level_0",
125            },
126            ExtraParam {
127                java_name: "text",
128                java_type: "String",
129                c_layouts: &["ValueLayout.ADDRESS"],
130                decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
131            },
132            ExtraParam {
133                java_name: "id",
134                java_type: "String",
135                c_layouts: &["ValueLayout.ADDRESS"],
136                decode: "raw_id_0.equals(MemorySegment.NULL) ? null : raw_id_0.reinterpret(Long.MAX_VALUE).getString(0)",
137            },
138        ],
139        has_is_header: false,
140    },
141    CallbackSpec {
142        c_field: "visit_code_block",
143        java_method: "visitCodeBlock",
144        doc: "Called for code blocks. lang is null when absent.",
145        extra: &[
146            ExtraParam {
147                java_name: "lang",
148                java_type: "String",
149                c_layouts: &["ValueLayout.ADDRESS"],
150                decode: "raw_lang_0.equals(MemorySegment.NULL) ? null : raw_lang_0.reinterpret(Long.MAX_VALUE).getString(0)",
151            },
152            ExtraParam {
153                java_name: "code",
154                java_type: "String",
155                c_layouts: &["ValueLayout.ADDRESS"],
156                decode: "raw_code_0.reinterpret(Long.MAX_VALUE).getString(0)",
157            },
158        ],
159        has_is_header: false,
160    },
161    CallbackSpec {
162        c_field: "visit_code_inline",
163        java_method: "visitCodeInline",
164        doc: "Called for inline code elements.",
165        extra: &[ExtraParam {
166            java_name: "code",
167            java_type: "String",
168            c_layouts: &["ValueLayout.ADDRESS"],
169            decode: "raw_code_0.reinterpret(Long.MAX_VALUE).getString(0)",
170        }],
171        has_is_header: false,
172    },
173    CallbackSpec {
174        c_field: "visit_list_item",
175        java_method: "visitListItem",
176        doc: "Called for list items.",
177        extra: &[
178            ExtraParam {
179                java_name: "ordered",
180                java_type: "boolean",
181                c_layouts: &["ValueLayout.JAVA_INT"],
182                decode: "((int) raw_ordered_0) != 0",
183            },
184            ExtraParam {
185                java_name: "marker",
186                java_type: "String",
187                c_layouts: &["ValueLayout.ADDRESS"],
188                decode: "raw_marker_0.reinterpret(Long.MAX_VALUE).getString(0)",
189            },
190            ExtraParam {
191                java_name: "text",
192                java_type: "String",
193                c_layouts: &["ValueLayout.ADDRESS"],
194                decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
195            },
196        ],
197        has_is_header: false,
198    },
199    CallbackSpec {
200        c_field: "visit_list_start",
201        java_method: "visitListStart",
202        doc: "Called before processing a list.",
203        extra: &[ExtraParam {
204            java_name: "ordered",
205            java_type: "boolean",
206            c_layouts: &["ValueLayout.JAVA_INT"],
207            decode: "((int) raw_ordered_0) != 0",
208        }],
209        has_is_header: false,
210    },
211    CallbackSpec {
212        c_field: "visit_list_end",
213        java_method: "visitListEnd",
214        doc: "Called after processing a list.",
215        extra: &[
216            ExtraParam {
217                java_name: "ordered",
218                java_type: "boolean",
219                c_layouts: &["ValueLayout.JAVA_INT"],
220                decode: "((int) raw_ordered_0) != 0",
221            },
222            ExtraParam {
223                java_name: "output",
224                java_type: "String",
225                c_layouts: &["ValueLayout.ADDRESS"],
226                decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
227            },
228        ],
229        has_is_header: false,
230    },
231    CallbackSpec {
232        c_field: "visit_table_start",
233        java_method: "visitTableStart",
234        doc: "Called before processing a table.",
235        extra: &[],
236        has_is_header: false,
237    },
238    CallbackSpec {
239        c_field: "visit_table_row",
240        java_method: "visitTableRow",
241        doc: "Called for table rows. cells contains the cell text values.",
242        extra: &[ExtraParam {
243            java_name: "cells",
244            java_type: "java.util.List<String>",
245            c_layouts: &["ValueLayout.ADDRESS", "ValueLayout.JAVA_LONG"],
246            decode: "decodeCells(raw_cells_0, (long) raw_cells_1)",
247        }],
248        has_is_header: true,
249    },
250    CallbackSpec {
251        c_field: "visit_table_end",
252        java_method: "visitTableEnd",
253        doc: "Called after processing a table.",
254        extra: &[ExtraParam {
255            java_name: "output",
256            java_type: "String",
257            c_layouts: &["ValueLayout.ADDRESS"],
258            decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
259        }],
260        has_is_header: false,
261    },
262    CallbackSpec {
263        c_field: "visit_blockquote",
264        java_method: "visitBlockquote",
265        doc: "Called for blockquote elements.",
266        extra: &[
267            ExtraParam {
268                java_name: "content",
269                java_type: "String",
270                c_layouts: &["ValueLayout.ADDRESS"],
271                decode: "raw_content_0.reinterpret(Long.MAX_VALUE).getString(0)",
272            },
273            ExtraParam {
274                java_name: "depth",
275                java_type: "long",
276                c_layouts: &["ValueLayout.JAVA_LONG"],
277                decode: "(long) raw_depth_0",
278            },
279        ],
280        has_is_header: false,
281    },
282    CallbackSpec {
283        c_field: "visit_strong",
284        java_method: "visitStrong",
285        doc: "Called for strong/bold elements.",
286        extra: &[ExtraParam {
287            java_name: "text",
288            java_type: "String",
289            c_layouts: &["ValueLayout.ADDRESS"],
290            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
291        }],
292        has_is_header: false,
293    },
294    CallbackSpec {
295        c_field: "visit_emphasis",
296        java_method: "visitEmphasis",
297        doc: "Called for emphasis/italic elements.",
298        extra: &[ExtraParam {
299            java_name: "text",
300            java_type: "String",
301            c_layouts: &["ValueLayout.ADDRESS"],
302            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
303        }],
304        has_is_header: false,
305    },
306    CallbackSpec {
307        c_field: "visit_strikethrough",
308        java_method: "visitStrikethrough",
309        doc: "Called for strikethrough elements.",
310        extra: &[ExtraParam {
311            java_name: "text",
312            java_type: "String",
313            c_layouts: &["ValueLayout.ADDRESS"],
314            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
315        }],
316        has_is_header: false,
317    },
318    CallbackSpec {
319        c_field: "visit_underline",
320        java_method: "visitUnderline",
321        doc: "Called for underline elements.",
322        extra: &[ExtraParam {
323            java_name: "text",
324            java_type: "String",
325            c_layouts: &["ValueLayout.ADDRESS"],
326            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
327        }],
328        has_is_header: false,
329    },
330    CallbackSpec {
331        c_field: "visit_subscript",
332        java_method: "visitSubscript",
333        doc: "Called for subscript elements.",
334        extra: &[ExtraParam {
335            java_name: "text",
336            java_type: "String",
337            c_layouts: &["ValueLayout.ADDRESS"],
338            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
339        }],
340        has_is_header: false,
341    },
342    CallbackSpec {
343        c_field: "visit_superscript",
344        java_method: "visitSuperscript",
345        doc: "Called for superscript elements.",
346        extra: &[ExtraParam {
347            java_name: "text",
348            java_type: "String",
349            c_layouts: &["ValueLayout.ADDRESS"],
350            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
351        }],
352        has_is_header: false,
353    },
354    CallbackSpec {
355        c_field: "visit_mark",
356        java_method: "visitMark",
357        doc: "Called for mark/highlight elements.",
358        extra: &[ExtraParam {
359            java_name: "text",
360            java_type: "String",
361            c_layouts: &["ValueLayout.ADDRESS"],
362            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
363        }],
364        has_is_header: false,
365    },
366    CallbackSpec {
367        c_field: "visit_line_break",
368        java_method: "visitLineBreak",
369        doc: "Called for line break elements.",
370        extra: &[],
371        has_is_header: false,
372    },
373    CallbackSpec {
374        c_field: "visit_horizontal_rule",
375        java_method: "visitHorizontalRule",
376        doc: "Called for horizontal rule elements.",
377        extra: &[],
378        has_is_header: false,
379    },
380    CallbackSpec {
381        c_field: "visit_custom_element",
382        java_method: "visitCustomElement",
383        doc: "Called for custom or unknown elements.",
384        extra: &[
385            ExtraParam {
386                java_name: "tagName",
387                java_type: "String",
388                c_layouts: &["ValueLayout.ADDRESS"],
389                decode: "raw_tagName_0.reinterpret(Long.MAX_VALUE).getString(0)",
390            },
391            ExtraParam {
392                java_name: "html",
393                java_type: "String",
394                c_layouts: &["ValueLayout.ADDRESS"],
395                decode: "raw_html_0.reinterpret(Long.MAX_VALUE).getString(0)",
396            },
397        ],
398        has_is_header: false,
399    },
400    CallbackSpec {
401        c_field: "visit_definition_list_start",
402        java_method: "visitDefinitionListStart",
403        doc: "Called before a definition list.",
404        extra: &[],
405        has_is_header: false,
406    },
407    CallbackSpec {
408        c_field: "visit_definition_term",
409        java_method: "visitDefinitionTerm",
410        doc: "Called for definition term elements.",
411        extra: &[ExtraParam {
412            java_name: "text",
413            java_type: "String",
414            c_layouts: &["ValueLayout.ADDRESS"],
415            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
416        }],
417        has_is_header: false,
418    },
419    CallbackSpec {
420        c_field: "visit_definition_description",
421        java_method: "visitDefinitionDescription",
422        doc: "Called for definition description elements.",
423        extra: &[ExtraParam {
424            java_name: "text",
425            java_type: "String",
426            c_layouts: &["ValueLayout.ADDRESS"],
427            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
428        }],
429        has_is_header: false,
430    },
431    CallbackSpec {
432        c_field: "visit_definition_list_end",
433        java_method: "visitDefinitionListEnd",
434        doc: "Called after a definition list.",
435        extra: &[ExtraParam {
436            java_name: "output",
437            java_type: "String",
438            c_layouts: &["ValueLayout.ADDRESS"],
439            decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
440        }],
441        has_is_header: false,
442    },
443    CallbackSpec {
444        c_field: "visit_form",
445        java_method: "visitForm",
446        doc: "Called for form elements. action and method may be null.",
447        extra: &[
448            ExtraParam {
449                java_name: "action",
450                java_type: "String",
451                c_layouts: &["ValueLayout.ADDRESS"],
452                decode: "raw_action_0.equals(MemorySegment.NULL) ? null : raw_action_0.reinterpret(Long.MAX_VALUE).getString(0)",
453            },
454            ExtraParam {
455                java_name: "method",
456                java_type: "String",
457                c_layouts: &["ValueLayout.ADDRESS"],
458                decode: "raw_method_0.equals(MemorySegment.NULL) ? null : raw_method_0.reinterpret(Long.MAX_VALUE).getString(0)",
459            },
460        ],
461        has_is_header: false,
462    },
463    CallbackSpec {
464        c_field: "visit_input",
465        java_method: "visitInput",
466        doc: "Called for input elements. name and value may be null.",
467        extra: &[
468            ExtraParam {
469                java_name: "inputType",
470                java_type: "String",
471                c_layouts: &["ValueLayout.ADDRESS"],
472                decode: "raw_inputType_0.reinterpret(Long.MAX_VALUE).getString(0)",
473            },
474            ExtraParam {
475                java_name: "name",
476                java_type: "String",
477                c_layouts: &["ValueLayout.ADDRESS"],
478                decode: "raw_name_0.equals(MemorySegment.NULL) ? null : raw_name_0.reinterpret(Long.MAX_VALUE).getString(0)",
479            },
480            ExtraParam {
481                java_name: "value",
482                java_type: "String",
483                c_layouts: &["ValueLayout.ADDRESS"],
484                decode: "raw_value_0.equals(MemorySegment.NULL) ? null : raw_value_0.reinterpret(Long.MAX_VALUE).getString(0)",
485            },
486        ],
487        has_is_header: false,
488    },
489    CallbackSpec {
490        c_field: "visit_button",
491        java_method: "visitButton",
492        doc: "Called for button elements.",
493        extra: &[ExtraParam {
494            java_name: "text",
495            java_type: "String",
496            c_layouts: &["ValueLayout.ADDRESS"],
497            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
498        }],
499        has_is_header: false,
500    },
501    CallbackSpec {
502        c_field: "visit_audio",
503        java_method: "visitAudio",
504        doc: "Called for audio elements. src may be null.",
505        extra: &[ExtraParam {
506            java_name: "src",
507            java_type: "String",
508            c_layouts: &["ValueLayout.ADDRESS"],
509            decode: "raw_src_0.equals(MemorySegment.NULL) ? null : raw_src_0.reinterpret(Long.MAX_VALUE).getString(0)",
510        }],
511        has_is_header: false,
512    },
513    CallbackSpec {
514        c_field: "visit_video",
515        java_method: "visitVideo",
516        doc: "Called for video elements. src may be null.",
517        extra: &[ExtraParam {
518            java_name: "src",
519            java_type: "String",
520            c_layouts: &["ValueLayout.ADDRESS"],
521            decode: "raw_src_0.equals(MemorySegment.NULL) ? null : raw_src_0.reinterpret(Long.MAX_VALUE).getString(0)",
522        }],
523        has_is_header: false,
524    },
525    CallbackSpec {
526        c_field: "visit_iframe",
527        java_method: "visitIframe",
528        doc: "Called for iframe elements. src may be null.",
529        extra: &[ExtraParam {
530            java_name: "src",
531            java_type: "String",
532            c_layouts: &["ValueLayout.ADDRESS"],
533            decode: "raw_src_0.equals(MemorySegment.NULL) ? null : raw_src_0.reinterpret(Long.MAX_VALUE).getString(0)",
534        }],
535        has_is_header: false,
536    },
537    CallbackSpec {
538        c_field: "visit_details",
539        java_method: "visitDetails",
540        doc: "Called for details elements.",
541        extra: &[ExtraParam {
542            java_name: "open",
543            java_type: "boolean",
544            c_layouts: &["ValueLayout.JAVA_INT"],
545            decode: "((int) raw_open_0) != 0",
546        }],
547        has_is_header: false,
548    },
549    CallbackSpec {
550        c_field: "visit_summary",
551        java_method: "visitSummary",
552        doc: "Called for summary elements.",
553        extra: &[ExtraParam {
554            java_name: "text",
555            java_type: "String",
556            c_layouts: &["ValueLayout.ADDRESS"],
557            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
558        }],
559        has_is_header: false,
560    },
561    CallbackSpec {
562        c_field: "visit_figure_start",
563        java_method: "visitFigureStart",
564        doc: "Called before a figure element.",
565        extra: &[],
566        has_is_header: false,
567    },
568    CallbackSpec {
569        c_field: "visit_figcaption",
570        java_method: "visitFigcaption",
571        doc: "Called for figcaption elements.",
572        extra: &[ExtraParam {
573            java_name: "text",
574            java_type: "String",
575            c_layouts: &["ValueLayout.ADDRESS"],
576            decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
577        }],
578        has_is_header: false,
579    },
580    CallbackSpec {
581        c_field: "visit_figure_end",
582        java_method: "visitFigureEnd",
583        doc: "Called after a figure element.",
584        extra: &[ExtraParam {
585            java_name: "output",
586            java_type: "String",
587            c_layouts: &["ValueLayout.ADDRESS"],
588            decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
589        }],
590        has_is_header: false,
591    },
592];
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597
598    #[test]
599    fn callbacks_table_is_non_empty() {
600        assert!(!CALLBACKS.is_empty(), "CALLBACKS must have at least one entry");
601    }
602
603    #[test]
604    fn all_callbacks_have_c_field_and_java_method() {
605        for spec in CALLBACKS {
606            assert!(!spec.c_field.is_empty(), "c_field must not be empty");
607            assert!(!spec.java_method.is_empty(), "java_method must not be empty");
608        }
609    }
610
611    #[test]
612    fn visit_table_row_has_is_header() {
613        let row = CALLBACKS
614            .iter()
615            .find(|s| s.c_field == "visit_table_row")
616            .expect("must have visit_table_row");
617        assert!(row.has_is_header, "visit_table_row must have has_is_header = true");
618    }
619
620    #[test]
621    fn callbacks_with_no_extra_have_empty_extra_slice() {
622        let start = CALLBACKS
623            .iter()
624            .find(|s| s.c_field == "visit_element_start")
625            .expect("must have visit_element_start");
626        assert!(start.extra.is_empty(), "visit_element_start must have no extra params");
627    }
628
629    #[test]
630    fn callbacks_count_matches_expected() {
631        // 40 callbacks as documented in the module-level comment
632        assert_eq!(CALLBACKS.len(), 40, "expected 40 visitor callbacks");
633    }
634}