1use alef_core::hash::{self, CommentStyle};
20use std::fmt::Write;
21
22pub struct CallbackSpec {
27 pub c_field: &'static str,
29 pub java_method: &'static str,
31 pub doc: &'static str,
33 pub extra: &'static [ExtraParam],
35 pub has_is_header: bool,
37}
38
39pub struct ExtraParam {
40 pub java_name: &'static str,
42 pub java_type: &'static str,
44 pub c_layouts: &'static [&'static str],
47 pub decode: &'static str,
50}
51
52pub const CALLBACKS: &[CallbackSpec] = &[
53 CallbackSpec {
54 c_field: "visit_text",
55 java_method: "visitText",
56 doc: "Called for text nodes.",
57 extra: &[ExtraParam {
58 java_name: "text",
59 java_type: "String",
60 c_layouts: &["ValueLayout.ADDRESS"],
61 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
62 }],
63 has_is_header: false,
64 },
65 CallbackSpec {
66 c_field: "visit_element_start",
67 java_method: "visitElementStart",
68 doc: "Called before entering any element.",
69 extra: &[],
70 has_is_header: false,
71 },
72 CallbackSpec {
73 c_field: "visit_element_end",
74 java_method: "visitElementEnd",
75 doc: "Called after exiting any element; receives the default markdown output.",
76 extra: &[ExtraParam {
77 java_name: "output",
78 java_type: "String",
79 c_layouts: &["ValueLayout.ADDRESS"],
80 decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
81 }],
82 has_is_header: false,
83 },
84 CallbackSpec {
85 c_field: "visit_link",
86 java_method: "visitLink",
87 doc: "Called for anchor links. title is null when the attribute is absent.",
88 extra: &[
89 ExtraParam {
90 java_name: "href",
91 java_type: "String",
92 c_layouts: &["ValueLayout.ADDRESS"],
93 decode: "raw_href_0.reinterpret(Long.MAX_VALUE).getString(0)",
94 },
95 ExtraParam {
96 java_name: "text",
97 java_type: "String",
98 c_layouts: &["ValueLayout.ADDRESS"],
99 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
100 },
101 ExtraParam {
102 java_name: "title",
103 java_type: "String",
104 c_layouts: &["ValueLayout.ADDRESS"],
105 decode: "raw_title_0.equals(MemorySegment.NULL) ? null : raw_title_0.reinterpret(Long.MAX_VALUE).getString(0)",
106 },
107 ],
108 has_is_header: false,
109 },
110 CallbackSpec {
111 c_field: "visit_image",
112 java_method: "visitImage",
113 doc: "Called for images. title is null when absent.",
114 extra: &[
115 ExtraParam {
116 java_name: "src",
117 java_type: "String",
118 c_layouts: &["ValueLayout.ADDRESS"],
119 decode: "raw_src_0.reinterpret(Long.MAX_VALUE).getString(0)",
120 },
121 ExtraParam {
122 java_name: "alt",
123 java_type: "String",
124 c_layouts: &["ValueLayout.ADDRESS"],
125 decode: "raw_alt_0.reinterpret(Long.MAX_VALUE).getString(0)",
126 },
127 ExtraParam {
128 java_name: "title",
129 java_type: "String",
130 c_layouts: &["ValueLayout.ADDRESS"],
131 decode: "raw_title_0.equals(MemorySegment.NULL) ? null : raw_title_0.reinterpret(Long.MAX_VALUE).getString(0)",
132 },
133 ],
134 has_is_header: false,
135 },
136 CallbackSpec {
137 c_field: "visit_heading",
138 java_method: "visitHeading",
139 doc: "Called for heading elements h1-h6. id is null when absent.",
140 extra: &[
141 ExtraParam {
142 java_name: "level",
143 java_type: "int",
144 c_layouts: &["ValueLayout.JAVA_INT"],
145 decode: "(int) raw_level_0",
146 },
147 ExtraParam {
148 java_name: "text",
149 java_type: "String",
150 c_layouts: &["ValueLayout.ADDRESS"],
151 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
152 },
153 ExtraParam {
154 java_name: "id",
155 java_type: "String",
156 c_layouts: &["ValueLayout.ADDRESS"],
157 decode: "raw_id_0.equals(MemorySegment.NULL) ? null : raw_id_0.reinterpret(Long.MAX_VALUE).getString(0)",
158 },
159 ],
160 has_is_header: false,
161 },
162 CallbackSpec {
163 c_field: "visit_code_block",
164 java_method: "visitCodeBlock",
165 doc: "Called for code blocks. lang is null when absent.",
166 extra: &[
167 ExtraParam {
168 java_name: "lang",
169 java_type: "String",
170 c_layouts: &["ValueLayout.ADDRESS"],
171 decode: "raw_lang_0.equals(MemorySegment.NULL) ? null : raw_lang_0.reinterpret(Long.MAX_VALUE).getString(0)",
172 },
173 ExtraParam {
174 java_name: "code",
175 java_type: "String",
176 c_layouts: &["ValueLayout.ADDRESS"],
177 decode: "raw_code_0.reinterpret(Long.MAX_VALUE).getString(0)",
178 },
179 ],
180 has_is_header: false,
181 },
182 CallbackSpec {
183 c_field: "visit_code_inline",
184 java_method: "visitCodeInline",
185 doc: "Called for inline code elements.",
186 extra: &[ExtraParam {
187 java_name: "code",
188 java_type: "String",
189 c_layouts: &["ValueLayout.ADDRESS"],
190 decode: "raw_code_0.reinterpret(Long.MAX_VALUE).getString(0)",
191 }],
192 has_is_header: false,
193 },
194 CallbackSpec {
195 c_field: "visit_list_item",
196 java_method: "visitListItem",
197 doc: "Called for list items.",
198 extra: &[
199 ExtraParam {
200 java_name: "ordered",
201 java_type: "boolean",
202 c_layouts: &["ValueLayout.JAVA_INT"],
203 decode: "((int) raw_ordered_0) != 0",
204 },
205 ExtraParam {
206 java_name: "marker",
207 java_type: "String",
208 c_layouts: &["ValueLayout.ADDRESS"],
209 decode: "raw_marker_0.reinterpret(Long.MAX_VALUE).getString(0)",
210 },
211 ExtraParam {
212 java_name: "text",
213 java_type: "String",
214 c_layouts: &["ValueLayout.ADDRESS"],
215 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
216 },
217 ],
218 has_is_header: false,
219 },
220 CallbackSpec {
221 c_field: "visit_list_start",
222 java_method: "visitListStart",
223 doc: "Called before processing a list.",
224 extra: &[ExtraParam {
225 java_name: "ordered",
226 java_type: "boolean",
227 c_layouts: &["ValueLayout.JAVA_INT"],
228 decode: "((int) raw_ordered_0) != 0",
229 }],
230 has_is_header: false,
231 },
232 CallbackSpec {
233 c_field: "visit_list_end",
234 java_method: "visitListEnd",
235 doc: "Called after processing a list.",
236 extra: &[
237 ExtraParam {
238 java_name: "ordered",
239 java_type: "boolean",
240 c_layouts: &["ValueLayout.JAVA_INT"],
241 decode: "((int) raw_ordered_0) != 0",
242 },
243 ExtraParam {
244 java_name: "output",
245 java_type: "String",
246 c_layouts: &["ValueLayout.ADDRESS"],
247 decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
248 },
249 ],
250 has_is_header: false,
251 },
252 CallbackSpec {
253 c_field: "visit_table_start",
254 java_method: "visitTableStart",
255 doc: "Called before processing a table.",
256 extra: &[],
257 has_is_header: false,
258 },
259 CallbackSpec {
260 c_field: "visit_table_row",
261 java_method: "visitTableRow",
262 doc: "Called for table rows. cells contains the cell text values.",
263 extra: &[ExtraParam {
264 java_name: "cells",
265 java_type: "java.util.List<String>",
266 c_layouts: &["ValueLayout.ADDRESS", "ValueLayout.JAVA_LONG"],
267 decode: "decodeCells(raw_cells_0, (long) raw_cells_1)",
268 }],
269 has_is_header: true,
270 },
271 CallbackSpec {
272 c_field: "visit_table_end",
273 java_method: "visitTableEnd",
274 doc: "Called after processing a table.",
275 extra: &[ExtraParam {
276 java_name: "output",
277 java_type: "String",
278 c_layouts: &["ValueLayout.ADDRESS"],
279 decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
280 }],
281 has_is_header: false,
282 },
283 CallbackSpec {
284 c_field: "visit_blockquote",
285 java_method: "visitBlockquote",
286 doc: "Called for blockquote elements.",
287 extra: &[
288 ExtraParam {
289 java_name: "content",
290 java_type: "String",
291 c_layouts: &["ValueLayout.ADDRESS"],
292 decode: "raw_content_0.reinterpret(Long.MAX_VALUE).getString(0)",
293 },
294 ExtraParam {
295 java_name: "depth",
296 java_type: "long",
297 c_layouts: &["ValueLayout.JAVA_LONG"],
298 decode: "(long) raw_depth_0",
299 },
300 ],
301 has_is_header: false,
302 },
303 CallbackSpec {
304 c_field: "visit_strong",
305 java_method: "visitStrong",
306 doc: "Called for strong/bold elements.",
307 extra: &[ExtraParam {
308 java_name: "text",
309 java_type: "String",
310 c_layouts: &["ValueLayout.ADDRESS"],
311 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
312 }],
313 has_is_header: false,
314 },
315 CallbackSpec {
316 c_field: "visit_emphasis",
317 java_method: "visitEmphasis",
318 doc: "Called for emphasis/italic elements.",
319 extra: &[ExtraParam {
320 java_name: "text",
321 java_type: "String",
322 c_layouts: &["ValueLayout.ADDRESS"],
323 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
324 }],
325 has_is_header: false,
326 },
327 CallbackSpec {
328 c_field: "visit_strikethrough",
329 java_method: "visitStrikethrough",
330 doc: "Called for strikethrough elements.",
331 extra: &[ExtraParam {
332 java_name: "text",
333 java_type: "String",
334 c_layouts: &["ValueLayout.ADDRESS"],
335 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
336 }],
337 has_is_header: false,
338 },
339 CallbackSpec {
340 c_field: "visit_underline",
341 java_method: "visitUnderline",
342 doc: "Called for underline elements.",
343 extra: &[ExtraParam {
344 java_name: "text",
345 java_type: "String",
346 c_layouts: &["ValueLayout.ADDRESS"],
347 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
348 }],
349 has_is_header: false,
350 },
351 CallbackSpec {
352 c_field: "visit_subscript",
353 java_method: "visitSubscript",
354 doc: "Called for subscript elements.",
355 extra: &[ExtraParam {
356 java_name: "text",
357 java_type: "String",
358 c_layouts: &["ValueLayout.ADDRESS"],
359 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
360 }],
361 has_is_header: false,
362 },
363 CallbackSpec {
364 c_field: "visit_superscript",
365 java_method: "visitSuperscript",
366 doc: "Called for superscript elements.",
367 extra: &[ExtraParam {
368 java_name: "text",
369 java_type: "String",
370 c_layouts: &["ValueLayout.ADDRESS"],
371 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
372 }],
373 has_is_header: false,
374 },
375 CallbackSpec {
376 c_field: "visit_mark",
377 java_method: "visitMark",
378 doc: "Called for mark/highlight elements.",
379 extra: &[ExtraParam {
380 java_name: "text",
381 java_type: "String",
382 c_layouts: &["ValueLayout.ADDRESS"],
383 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
384 }],
385 has_is_header: false,
386 },
387 CallbackSpec {
388 c_field: "visit_line_break",
389 java_method: "visitLineBreak",
390 doc: "Called for line break elements.",
391 extra: &[],
392 has_is_header: false,
393 },
394 CallbackSpec {
395 c_field: "visit_horizontal_rule",
396 java_method: "visitHorizontalRule",
397 doc: "Called for horizontal rule elements.",
398 extra: &[],
399 has_is_header: false,
400 },
401 CallbackSpec {
402 c_field: "visit_custom_element",
403 java_method: "visitCustomElement",
404 doc: "Called for custom or unknown elements.",
405 extra: &[
406 ExtraParam {
407 java_name: "tagName",
408 java_type: "String",
409 c_layouts: &["ValueLayout.ADDRESS"],
410 decode: "raw_tagName_0.reinterpret(Long.MAX_VALUE).getString(0)",
411 },
412 ExtraParam {
413 java_name: "html",
414 java_type: "String",
415 c_layouts: &["ValueLayout.ADDRESS"],
416 decode: "raw_html_0.reinterpret(Long.MAX_VALUE).getString(0)",
417 },
418 ],
419 has_is_header: false,
420 },
421 CallbackSpec {
422 c_field: "visit_definition_list_start",
423 java_method: "visitDefinitionListStart",
424 doc: "Called before a definition list.",
425 extra: &[],
426 has_is_header: false,
427 },
428 CallbackSpec {
429 c_field: "visit_definition_term",
430 java_method: "visitDefinitionTerm",
431 doc: "Called for definition term elements.",
432 extra: &[ExtraParam {
433 java_name: "text",
434 java_type: "String",
435 c_layouts: &["ValueLayout.ADDRESS"],
436 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
437 }],
438 has_is_header: false,
439 },
440 CallbackSpec {
441 c_field: "visit_definition_description",
442 java_method: "visitDefinitionDescription",
443 doc: "Called for definition description elements.",
444 extra: &[ExtraParam {
445 java_name: "text",
446 java_type: "String",
447 c_layouts: &["ValueLayout.ADDRESS"],
448 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
449 }],
450 has_is_header: false,
451 },
452 CallbackSpec {
453 c_field: "visit_definition_list_end",
454 java_method: "visitDefinitionListEnd",
455 doc: "Called after a definition list.",
456 extra: &[ExtraParam {
457 java_name: "output",
458 java_type: "String",
459 c_layouts: &["ValueLayout.ADDRESS"],
460 decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
461 }],
462 has_is_header: false,
463 },
464 CallbackSpec {
465 c_field: "visit_form",
466 java_method: "visitForm",
467 doc: "Called for form elements. action and method may be null.",
468 extra: &[
469 ExtraParam {
470 java_name: "action",
471 java_type: "String",
472 c_layouts: &["ValueLayout.ADDRESS"],
473 decode: "raw_action_0.equals(MemorySegment.NULL) ? null : raw_action_0.reinterpret(Long.MAX_VALUE).getString(0)",
474 },
475 ExtraParam {
476 java_name: "method",
477 java_type: "String",
478 c_layouts: &["ValueLayout.ADDRESS"],
479 decode: "raw_method_0.equals(MemorySegment.NULL) ? null : raw_method_0.reinterpret(Long.MAX_VALUE).getString(0)",
480 },
481 ],
482 has_is_header: false,
483 },
484 CallbackSpec {
485 c_field: "visit_input",
486 java_method: "visitInput",
487 doc: "Called for input elements. name and value may be null.",
488 extra: &[
489 ExtraParam {
490 java_name: "inputType",
491 java_type: "String",
492 c_layouts: &["ValueLayout.ADDRESS"],
493 decode: "raw_inputType_0.reinterpret(Long.MAX_VALUE).getString(0)",
494 },
495 ExtraParam {
496 java_name: "name",
497 java_type: "String",
498 c_layouts: &["ValueLayout.ADDRESS"],
499 decode: "raw_name_0.equals(MemorySegment.NULL) ? null : raw_name_0.reinterpret(Long.MAX_VALUE).getString(0)",
500 },
501 ExtraParam {
502 java_name: "value",
503 java_type: "String",
504 c_layouts: &["ValueLayout.ADDRESS"],
505 decode: "raw_value_0.equals(MemorySegment.NULL) ? null : raw_value_0.reinterpret(Long.MAX_VALUE).getString(0)",
506 },
507 ],
508 has_is_header: false,
509 },
510 CallbackSpec {
511 c_field: "visit_button",
512 java_method: "visitButton",
513 doc: "Called for button elements.",
514 extra: &[ExtraParam {
515 java_name: "text",
516 java_type: "String",
517 c_layouts: &["ValueLayout.ADDRESS"],
518 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
519 }],
520 has_is_header: false,
521 },
522 CallbackSpec {
523 c_field: "visit_audio",
524 java_method: "visitAudio",
525 doc: "Called for audio elements. src may be null.",
526 extra: &[ExtraParam {
527 java_name: "src",
528 java_type: "String",
529 c_layouts: &["ValueLayout.ADDRESS"],
530 decode: "raw_src_0.equals(MemorySegment.NULL) ? null : raw_src_0.reinterpret(Long.MAX_VALUE).getString(0)",
531 }],
532 has_is_header: false,
533 },
534 CallbackSpec {
535 c_field: "visit_video",
536 java_method: "visitVideo",
537 doc: "Called for video elements. src may be null.",
538 extra: &[ExtraParam {
539 java_name: "src",
540 java_type: "String",
541 c_layouts: &["ValueLayout.ADDRESS"],
542 decode: "raw_src_0.equals(MemorySegment.NULL) ? null : raw_src_0.reinterpret(Long.MAX_VALUE).getString(0)",
543 }],
544 has_is_header: false,
545 },
546 CallbackSpec {
547 c_field: "visit_iframe",
548 java_method: "visitIframe",
549 doc: "Called for iframe elements. src may be null.",
550 extra: &[ExtraParam {
551 java_name: "src",
552 java_type: "String",
553 c_layouts: &["ValueLayout.ADDRESS"],
554 decode: "raw_src_0.equals(MemorySegment.NULL) ? null : raw_src_0.reinterpret(Long.MAX_VALUE).getString(0)",
555 }],
556 has_is_header: false,
557 },
558 CallbackSpec {
559 c_field: "visit_details",
560 java_method: "visitDetails",
561 doc: "Called for details elements.",
562 extra: &[ExtraParam {
563 java_name: "open",
564 java_type: "boolean",
565 c_layouts: &["ValueLayout.JAVA_INT"],
566 decode: "((int) raw_open_0) != 0",
567 }],
568 has_is_header: false,
569 },
570 CallbackSpec {
571 c_field: "visit_summary",
572 java_method: "visitSummary",
573 doc: "Called for summary elements.",
574 extra: &[ExtraParam {
575 java_name: "text",
576 java_type: "String",
577 c_layouts: &["ValueLayout.ADDRESS"],
578 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
579 }],
580 has_is_header: false,
581 },
582 CallbackSpec {
583 c_field: "visit_figure_start",
584 java_method: "visitFigureStart",
585 doc: "Called before a figure element.",
586 extra: &[],
587 has_is_header: false,
588 },
589 CallbackSpec {
590 c_field: "visit_figcaption",
591 java_method: "visitFigcaption",
592 doc: "Called for figcaption elements.",
593 extra: &[ExtraParam {
594 java_name: "text",
595 java_type: "String",
596 c_layouts: &["ValueLayout.ADDRESS"],
597 decode: "raw_text_0.reinterpret(Long.MAX_VALUE).getString(0)",
598 }],
599 has_is_header: false,
600 },
601 CallbackSpec {
602 c_field: "visit_figure_end",
603 java_method: "visitFigureEnd",
604 doc: "Called after a figure element.",
605 extra: &[ExtraParam {
606 java_name: "output",
607 java_type: "String",
608 c_layouts: &["ValueLayout.ADDRESS"],
609 decode: "raw_output_0.reinterpret(Long.MAX_VALUE).getString(0)",
610 }],
611 has_is_header: false,
612 },
613];
614
615pub fn gen_visitor_files(package: &str, class_name: &str) -> Vec<(String, String)> {
623 vec![
624 ("NodeContext.java".to_string(), gen_node_context(package)),
625 ("VisitContext.java".to_string(), gen_visit_context(package)),
626 ("VisitResult.java".to_string(), gen_visit_result(package)),
627 ("Visitor.java".to_string(), gen_visitor_interface(package, class_name)),
628 (
629 "VisitorBridge.java".to_string(),
630 gen_visitor_bridge(package, class_name),
631 ),
632 ("TestVisitor.java".to_string(), gen_test_visitor_interface(package)),
633 ("TestVisitorAdapter.java".to_string(), gen_test_visitor_adapter(package)),
634 ]
635}
636
637pub fn gen_native_lib_visitor_handles(prefix: &str) -> String {
641 let mut out = String::with_capacity(512);
642 let pu = prefix.to_uppercase();
643
644 writeln!(out).ok();
645 writeln!(out, " // Visitor FFI handles").ok();
646 writeln!(
647 out,
648 " static final MethodHandle {pu}_VISITOR_CREATE = LINKER.downcallHandle("
649 )
650 .ok();
651 writeln!(out, " LIB.find(\"{prefix}_visitor_create\").orElseThrow(),").ok();
652 writeln!(
653 out,
654 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
655 )
656 .ok();
657 writeln!(out, " );").ok();
658 writeln!(out).ok();
659 writeln!(
660 out,
661 " static final MethodHandle {pu}_VISITOR_FREE = LINKER.downcallHandle("
662 )
663 .ok();
664 writeln!(out, " LIB.find(\"{prefix}_visitor_free\").orElseThrow(),").ok();
665 writeln!(out, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
666 writeln!(out, " );").ok();
667 writeln!(out).ok();
668 writeln!(
669 out,
670 " static final MethodHandle {pu}_CONVERT_WITH_VISITOR = LINKER.downcallHandle("
671 )
672 .ok();
673 writeln!(
674 out,
675 " LIB.find(\"{prefix}_convert_with_visitor\").orElseThrow(),"
676 )
677 .ok();
678 writeln!(
679 out,
680 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
681 )
682 .ok();
683 writeln!(out, " );").ok();
684
685 out
686}
687
688pub fn gen_convert_with_visitor_method(class_name: &str, prefix: &str) -> String {
692 let mut out = String::with_capacity(2048);
693 let pu = prefix.to_uppercase();
694 let exc = format!("{class_name}Exception");
695
696 writeln!(
697 out,
698 " public static ConversionResult convertWithVisitor(String html, ConversionOptions options, Visitor visitor) throws {exc} {{"
699 )
700 .ok();
701 writeln!(out, " try (var arena = Arena.ofConfined();").ok();
702 writeln!(out, " var bridge = new VisitorBridge(visitor)) {{").ok();
703 writeln!(out, " var cHtml = arena.allocateFrom(html);").ok();
704 writeln!(out).ok();
705 writeln!(out, " MemorySegment optionsPtr = MemorySegment.NULL;").ok();
706 writeln!(out, " if (options != null) {{").ok();
707 writeln!(
708 out,
709 " var optJson = arena.allocateFrom(createObjectMapper().writeValueAsString(options));"
710 )
711 .ok();
712 writeln!(
713 out,
714 " optionsPtr = (MemorySegment) NativeLib.{pu}_CONVERSION_OPTIONS_FROM_JSON.invoke(optJson);"
715 )
716 .ok();
717 writeln!(out, " }}").ok();
718 writeln!(out).ok();
719 writeln!(
720 out,
721 " var visitorHandle = (MemorySegment) NativeLib.{pu}_VISITOR_CREATE.invoke(bridge.callbacksStruct());"
722 )
723 .ok();
724 writeln!(out, " if (visitorHandle.equals(MemorySegment.NULL)) {{").ok();
725 writeln!(
726 out,
727 " throw new {exc}(\"Failed to create visitor handle\", null);"
728 )
729 .ok();
730 writeln!(out, " }}").ok();
731 writeln!(out).ok();
732 writeln!(out, " try {{").ok();
733 writeln!(
734 out,
735 " var resultPtr = (MemorySegment) NativeLib.{pu}_CONVERT_WITH_VISITOR.invoke(cHtml, optionsPtr, visitorHandle);"
736 )
737 .ok();
738 writeln!(out, " if (!optionsPtr.equals(MemorySegment.NULL)) {{").ok();
739 writeln!(
740 out,
741 " NativeLib.{pu}_CONVERSION_OPTIONS_FREE.invoke(optionsPtr);"
742 )
743 .ok();
744 writeln!(out, " }}").ok();
745 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
746 writeln!(out, " checkLastError();").ok();
747 writeln!(out, " return null;").ok();
748 writeln!(out, " }}").ok();
749 writeln!(
750 out,
751 " var markdown = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
752 )
753 .ok();
754 writeln!(out, " NativeLib.{pu}_FREE_STRING.invoke(resultPtr);").ok();
755 writeln!(
756 out,
757 " return new ConversionResult(java.util.Optional.of(markdown), java.util.Optional.empty(), null, null, null, null);"
758 )
759 .ok();
760 writeln!(out, " }} catch (Throwable e) {{").ok();
761 writeln!(out, " throw new {exc}(\"FFI call failed\", e);").ok();
762 writeln!(out, " }} finally {{").ok();
763 writeln!(
764 out,
765 " NativeLib.{pu}_VISITOR_FREE.invoke(visitorHandle);"
766 )
767 .ok();
768 writeln!(out, " }}").ok();
769 writeln!(out, " }} catch ({exc} e) {{").ok();
770 writeln!(out, " throw e;").ok();
771 writeln!(out, " }} catch (Throwable e) {{").ok();
772 writeln!(out, " throw new {exc}(\"FFI call failed\", e);").ok();
773 writeln!(out, " }}").ok();
774 writeln!(out, " }}").ok();
775
776 out
777}
778
779pub fn gen_convert_with_test_visitor_method(raw_class: &str) -> String {
781 let mut out = String::with_capacity(512);
782 let exc = format!("{raw_class}Exception");
783 writeln!(
784 out,
785 " public static ConversionResult convert(final String html, final ConversionOptions options,"
786 )
787 .ok();
788 writeln!(out, " final TestVisitor visitor) throws {exc} {{").ok();
789 writeln!(
790 out,
791 " return {raw_class}.convertWithVisitor(html, options, new TestVisitorAdapter(visitor));"
792 )
793 .ok();
794 writeln!(out, " }}").ok();
795 out
796}
797
798fn gen_node_context(package: &str) -> String {
803 let mut out = String::with_capacity(1024);
804 out.push_str(&hash::header(CommentStyle::DoubleSlash));
805 writeln!(out, "package {package};").ok();
806 writeln!(out).ok();
807 writeln!(out, "/** Context passed to every visitor callback. */").ok();
808 writeln!(out, "public record NodeContext(").ok();
809 writeln!(out, " /** Coarse-grained node type tag. */").ok();
810 writeln!(out, " int nodeType,").ok();
811 writeln!(out, " /** HTML element tag name (e.g. \"div\"). */").ok();
812 writeln!(out, " String tagName,").ok();
813 writeln!(out, " /** DOM depth (0 = root). */").ok();
814 writeln!(out, " long depth,").ok();
815 writeln!(out, " /** 0-based sibling index. */").ok();
816 writeln!(out, " long indexInParent,").ok();
817 writeln!(out, " /** Parent element tag name, or null at the root. */").ok();
818 writeln!(out, " String parentTag,").ok();
819 writeln!(out, " /** True when this element is treated as inline. */").ok();
820 writeln!(out, " boolean isInline").ok();
821 writeln!(out, ") {{}}").ok();
822 out
823}
824
825fn gen_visit_context(package: &str) -> String {
827 let mut out = String::with_capacity(1024);
828 out.push_str(&hash::header(CommentStyle::DoubleSlash));
829 writeln!(out, "package {package};").ok();
830 writeln!(out).ok();
831 writeln!(out, "/** Context passed to every visitor callback. */").ok();
832 writeln!(out, "public record VisitContext(").ok();
833 writeln!(out, " /** Coarse-grained node type tag. */").ok();
834 writeln!(out, " int nodeType,").ok();
835 writeln!(out, " /** HTML element tag name (e.g. \"div\"). */").ok();
836 writeln!(out, " String tagName,").ok();
837 writeln!(out, " /** DOM depth (0 = root). */").ok();
838 writeln!(out, " long depth,").ok();
839 writeln!(out, " /** 0-based sibling index. */").ok();
840 writeln!(out, " long indexInParent,").ok();
841 writeln!(out, " /** Parent element tag name, or null at the root. */").ok();
842 writeln!(out, " String parentTag,").ok();
843 writeln!(out, " /** True when this element is treated as inline. */").ok();
844 writeln!(out, " boolean isInline").ok();
845 writeln!(out, ") {{}}").ok();
846 out
847}
848
849fn gen_visit_result(package: &str) -> String {
850 let mut out = String::with_capacity(2048);
851 out.push_str(&hash::header(CommentStyle::DoubleSlash));
852 writeln!(out, "package {package};").ok();
853 writeln!(out).ok();
854 writeln!(out, "/** Controls how the visitor affects the conversion pipeline. */").ok();
855 writeln!(out, "public sealed interface VisitResult").ok();
856 writeln!(
857 out,
858 " permits VisitResult.Continue, VisitResult.Skip, VisitResult.PreserveHtml,"
859 )
860 .ok();
861 writeln!(out, " VisitResult.Custom, VisitResult.Error {{").ok();
862 writeln!(out).ok();
863 writeln!(out, " /** Proceed with default conversion. */").ok();
864 writeln!(out, " record Continue() implements VisitResult {{}}").ok();
865 writeln!(out).ok();
866 writeln!(out, " /** Omit this element from output entirely. */").ok();
867 writeln!(out, " record Skip() implements VisitResult {{}}").ok();
868 writeln!(out).ok();
869 writeln!(out, " /** Keep original HTML verbatim. */").ok();
870 writeln!(out, " record PreserveHtml() implements VisitResult {{}}").ok();
871 writeln!(out).ok();
872 writeln!(out, " /** Replace with custom Markdown. */").ok();
873 writeln!(out, " record Custom(String markdown) implements VisitResult {{}}").ok();
874 writeln!(out).ok();
875 writeln!(out, " /** Abort conversion with an error message. */").ok();
876 writeln!(out, " record Error(String message) implements VisitResult {{}}").ok();
877 writeln!(out).ok();
878 writeln!(out, " /** Convenience: continue with default conversion. */").ok();
879 writeln!(
880 out,
881 " static VisitResult continueDefault() {{ return new Continue(); }}"
882 )
883 .ok();
884 writeln!(out).ok();
885 writeln!(out, " /** Convenience: skip this element. */").ok();
886 writeln!(out, " static VisitResult skip() {{ return new Skip(); }}").ok();
887 writeln!(out).ok();
888 writeln!(out, " /** Convenience: preserve original HTML. */").ok();
889 writeln!(
890 out,
891 " static VisitResult preserveHtml() {{ return new PreserveHtml(); }}"
892 )
893 .ok();
894 writeln!(out).ok();
895 writeln!(out, " /** Convenience: emit custom Markdown. */").ok();
896 writeln!(
897 out,
898 " static VisitResult custom(String markdown) {{ return new Custom(markdown); }}"
899 )
900 .ok();
901 writeln!(out).ok();
902 writeln!(out, " /** Convenience: abort with error. */").ok();
903 writeln!(
904 out,
905 " static VisitResult error(String message) {{ return new Error(message); }}"
906 )
907 .ok();
908 writeln!(out).ok();
909 writeln!(out, " /** Alias for {{@link #continueDefault()}}. */").ok();
910 writeln!(out, " static VisitResult continue_() {{ return new Continue(); }}").ok();
911 writeln!(out, "}}").ok();
912 out
913}
914
915fn gen_visitor_interface(package: &str, _class_name: &str) -> String {
916 let mut out = String::with_capacity(4096);
917 out.push_str(&hash::header(CommentStyle::DoubleSlash));
918 writeln!(out, "package {package};").ok();
919 writeln!(out).ok();
920 writeln!(
921 out,
922 "/** Visitor interface for the HTML-to-Markdown conversion pipeline. */"
923 )
924 .ok();
925 writeln!(out, "public interface Visitor {{").ok();
926 for spec in CALLBACKS {
927 let params = iface_param_str(spec);
928 writeln!(out, " /** {} */", spec.doc).ok();
929 writeln!(
930 out,
931 " default VisitResult {}({}) {{ return VisitResult.continueDefault(); }}",
932 spec.java_method, params
933 )
934 .ok();
935 }
936 writeln!(out, "}}").ok();
937 out
938}
939
940fn gen_test_visitor_interface(package: &str) -> String {
945 let mut out = String::with_capacity(4096);
946 out.push_str(&hash::header(CommentStyle::DoubleSlash));
947 writeln!(out, "package {package};").ok();
948 writeln!(out).ok();
949 writeln!(
950 out,
951 "/** Test-friendly visitor interface using VisitContext instead of NodeContext. */"
952 )
953 .ok();
954 writeln!(out, "public interface TestVisitor {{").ok();
955 for spec in CALLBACKS {
956 let params = test_iface_param_str(spec);
957 writeln!(out, " /** {} */", spec.doc).ok();
958 writeln!(
959 out,
960 " default VisitResult {}({}) {{ return VisitResult.continueDefault(); }}",
961 spec.java_method, params
962 )
963 .ok();
964 }
965 writeln!(out, "}}").ok();
966 out
967}
968
969fn gen_test_visitor_adapter(package: &str) -> String {
972 let mut out = String::with_capacity(4096);
973 out.push_str(&hash::header(CommentStyle::DoubleSlash));
974 writeln!(out, "package {package};").ok();
975 writeln!(out).ok();
976 writeln!(
977 out,
978 "/** Adapts a {{@link TestVisitor}} to the {{@link Visitor}} interface. */"
979 )
980 .ok();
981 writeln!(out, "final class TestVisitorAdapter implements Visitor {{").ok();
982 writeln!(out, " private final TestVisitor delegate;").ok();
983 writeln!(out).ok();
984 writeln!(out, " TestVisitorAdapter(final TestVisitor delegate) {{").ok();
985 writeln!(
986 out,
987 " java.util.Objects.requireNonNull(delegate, \"delegate must not be null\");"
988 )
989 .ok();
990 writeln!(out, " this.delegate = delegate;").ok();
991 writeln!(out, " }}").ok();
992 writeln!(out).ok();
993 writeln!(
994 out,
995 " private static VisitContext toVisitContext(final NodeContext ctx) {{"
996 )
997 .ok();
998 writeln!(
999 out,
1000 " return new VisitContext(ctx.nodeType(), ctx.tagName(), ctx.depth(), ctx.indexInParent(), ctx.parentTag(), ctx.isInline());"
1001 )
1002 .ok();
1003 writeln!(out, " }}").ok();
1004 writeln!(out).ok();
1005 for spec in CALLBACKS {
1006 let node_params = iface_param_str(spec);
1007 let visit_params = test_iface_param_str(spec);
1008 let mut call_args = vec!["visitCtx".to_string()];
1010 for ep in spec.extra {
1011 call_args.push(ep.java_name.to_string());
1012 }
1013 if spec.has_is_header {
1014 call_args.push("isHeader".to_string());
1015 }
1016 writeln!(out, " @Override").ok();
1017 let single = format!(" public VisitResult {}({}) {{", spec.java_method, node_params);
1019 if single.len() <= 100 {
1020 writeln!(out, "{}", single).ok();
1021 } else {
1022 writeln!(out, " public VisitResult {}(", spec.java_method).ok();
1023 writeln!(out, " {}) {{", node_params).ok();
1024 }
1025 writeln!(out, " var visitCtx = toVisitContext(context);").ok();
1026 let _ = visit_params; writeln!(
1030 out,
1031 " return delegate.{}({});",
1032 spec.java_method,
1033 call_args.join(", ")
1034 )
1035 .ok();
1036 writeln!(out, " }}").ok();
1037 writeln!(out).ok();
1038 }
1039 writeln!(out, "}}").ok();
1040 out
1041}
1042
1043fn test_iface_param_str(spec: &CallbackSpec) -> String {
1045 let mut params = vec!["final VisitContext ctx".to_string()];
1046 for ep in spec.extra {
1047 params.push(format!("final {} {}", ep.java_type, ep.java_name));
1048 }
1049 if spec.has_is_header {
1050 params.push("final boolean isHeader".to_string());
1051 }
1052 params.join(", ")
1053}
1054
1055fn gen_visitor_bridge(package: &str, _class_name: &str) -> String {
1058 let mut out = String::with_capacity(32_768);
1059 out.push_str(&hash::header(CommentStyle::DoubleSlash));
1060 writeln!(out, "package {package};").ok();
1061 writeln!(out).ok();
1062 writeln!(out, "import java.lang.foreign.Arena;").ok();
1063 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
1064 writeln!(out, "import java.lang.foreign.Linker;").ok();
1065 writeln!(out, "import java.lang.foreign.MemoryLayout;").ok();
1066 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
1067 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
1068 writeln!(out, "import java.lang.invoke.MethodHandles;").ok();
1069 writeln!(out, "import java.lang.invoke.MethodType;").ok();
1070 writeln!(out, "import java.util.ArrayList;").ok();
1071 writeln!(out, "import java.util.List;").ok();
1072 writeln!(out).ok();
1073
1074 writeln!(out, "/**").ok();
1075 writeln!(out, " * Allocates Panama FFM upcall stubs for a Visitor and assembles").ok();
1076 writeln!(out, " * the C HTMHtmVisitorCallbacks struct in native memory.").ok();
1077 writeln!(out, " */").ok();
1078 writeln!(out, "final class VisitorBridge implements AutoCloseable {{").ok();
1079 writeln!(out, " private static final Linker LINKER = Linker.nativeLinker();").ok();
1080 writeln!(out, " private static final MethodHandles.Lookup LOOKUP =").ok();
1081 writeln!(out, " MethodHandles.lookup();").ok();
1082 writeln!(out).ok();
1083 writeln!(out, " // VisitResult discriminant codes returned to C").ok();
1085 writeln!(out, " private static final int VISIT_RESULT_CONTINUE = 0;").ok();
1086 writeln!(out, " private static final int VISIT_RESULT_SKIP = 1;").ok();
1087 writeln!(out, " private static final int VISIT_RESULT_PRESERVE_HTML = 2;").ok();
1088 writeln!(out, " private static final int VISIT_RESULT_CUSTOM = 3;").ok();
1089 writeln!(out, " private static final int VISIT_RESULT_ERROR = 4;").ok();
1090 writeln!(out).ok();
1091
1092 let num_fields = CALLBACKS.len() + 1; writeln!(
1095 out,
1096 " // HTMHtmVisitorCallbacks: user_data + {n} callbacks",
1097 n = CALLBACKS.len(),
1098 )
1099 .ok();
1100 writeln!(out, " // = {total} pointer-sized slots", total = num_fields,).ok();
1101 writeln!(out, " private static final long CALLBACKS_STRUCT_SIZE =").ok();
1102 writeln!(out, " (long) ValueLayout.ADDRESS.byteSize() * {num_fields}L;").ok();
1103 writeln!(out).ok();
1104 writeln!(out, " // HTMHtmNodeContext field offsets").ok();
1106 writeln!(out, " private static final long CTX_OFFSET_TAG_NAME = 8L;").ok();
1107 writeln!(out, " private static final long CTX_OFFSET_DEPTH = 16L;").ok();
1108 writeln!(out, " private static final long CTX_OFFSET_INDEX_IN_PARENT = 24L;").ok();
1109 writeln!(out, " private static final long CTX_OFFSET_PARENT_TAG = 32L;").ok();
1110 writeln!(out, " private static final long CTX_OFFSET_IS_INLINE = 40L;").ok();
1111 writeln!(out).ok();
1112 writeln!(out, " private final Arena arena;").ok();
1113 writeln!(out, " private final MemorySegment struct;").ok();
1114 writeln!(out, " private final Visitor visitor;").ok();
1115 writeln!(out).ok();
1116 writeln!(out, " VisitorBridge(Visitor visitor) {{").ok();
1117 writeln!(out, " this.visitor = visitor;").ok();
1118 writeln!(out, " this.arena = Arena.ofConfined();").ok();
1119 writeln!(out, " this.struct = arena.allocate(CALLBACKS_STRUCT_SIZE);").ok();
1120 writeln!(out, " // Slot 0: user_data = NULL").ok();
1121 writeln!(out, " // (visitor captured via lambda closure)").ok();
1122 writeln!(out, " struct.set(ValueLayout.ADDRESS, 0L, MemorySegment.NULL);").ok();
1123 writeln!(out, " try {{").ok();
1124 writeln!(out, " long offset = ValueLayout.ADDRESS.byteSize();").ok();
1125 let num_chunks = CALLBACKS.chunks(10).count();
1127 for i in 1..=num_chunks {
1128 if i < num_chunks {
1129 writeln!(out, " offset = registerStubs{}(offset);", i).ok();
1130 } else {
1131 writeln!(out, " registerStubs{}(offset);", i).ok();
1132 }
1133 }
1134 writeln!(out, " }} catch (ReflectiveOperationException e) {{").ok();
1135 writeln!(out, " arena.close();").ok();
1136 writeln!(out, " throw new RuntimeException(").ok();
1137 writeln!(out, " \"Failed to create visitor upcall stubs\", e);").ok();
1138 writeln!(out, " }}").ok();
1139 writeln!(out, " }}").ok();
1140 writeln!(out).ok();
1141
1142 const CHUNK_SIZE: usize = 10;
1144 for (chunk_idx, chunk) in CALLBACKS.chunks(CHUNK_SIZE).enumerate() {
1145 let method_num = chunk_idx + 1;
1146 writeln!(
1147 out,
1148 " private long registerStubs{}(\n final long offset)\n throws ReflectiveOperationException {{",
1149 method_num
1150 )
1151 .ok();
1152 writeln!(out, " long off = offset;").ok();
1153 for spec in chunk {
1154 let descriptor = callback_descriptor(spec);
1155 let method_type = callback_method_type(spec);
1156 let stub_var = stub_var_name(spec.java_method);
1157 writeln!(out, " // {}", spec.c_field).ok();
1158 writeln!(out, " var {} = LINKER.upcallStub(", stub_var).ok();
1159 writeln!(out, " LOOKUP.bind(",).ok();
1160 writeln!(
1161 out,
1162 " this, \"{}\",",
1163 handle_method_name(spec.java_method),
1164 )
1165 .ok();
1166 writeln!(out, " {}),", method_type).ok();
1167 writeln!(out, " {},", descriptor).ok();
1168 writeln!(out, " arena);").ok();
1169 writeln!(out, " struct.set(ValueLayout.ADDRESS, off, {});", stub_var).ok();
1170 writeln!(out, " off += ValueLayout.ADDRESS.byteSize();").ok();
1171 }
1172 writeln!(out, " return off;").ok();
1173 writeln!(out, " }}").ok();
1174 writeln!(out).ok();
1175 }
1176 writeln!(out).ok();
1177 writeln!(
1178 out,
1179 " /** Returns the native HTMHtmVisitorCallbacks struct pointer. */"
1180 )
1181 .ok();
1182 writeln!(out, " MemorySegment callbacksStruct() {{").ok();
1183 writeln!(out, " return struct;").ok();
1184 writeln!(out, " }}").ok();
1185 writeln!(out).ok();
1186
1187 for spec in CALLBACKS {
1189 gen_handle_method(&mut out, spec);
1190 }
1191
1192 writeln!(
1194 out,
1195 " // HTMHtmNodeContext: int32 node_type, char* tag_name, uintptr depth,"
1196 )
1197 .ok();
1198 writeln!(out, " // uintptr index_in_parent, char* parent_tag,").ok();
1199 writeln!(out, " // int32 is_inline").ok();
1200 writeln!(out, " private static final MemoryLayout CTX_LAYOUT =").ok();
1201 writeln!(out, " MemoryLayout.structLayout(").ok();
1202 writeln!(out, " ValueLayout.JAVA_INT.withName(\"node_type\"),").ok();
1203 writeln!(out, " MemoryLayout.paddingLayout(4),").ok();
1204 writeln!(out, " ValueLayout.ADDRESS.withName(\"tag_name\"),").ok();
1205 writeln!(out, " ValueLayout.JAVA_LONG.withName(\"depth\"),").ok();
1206 writeln!(out, " ValueLayout.JAVA_LONG.withName(\"index_in_parent\"),").ok();
1207 writeln!(out, " ValueLayout.ADDRESS.withName(\"parent_tag\"),").ok();
1208 writeln!(out, " ValueLayout.JAVA_INT.withName(\"is_inline\"),").ok();
1209 writeln!(out, " MemoryLayout.paddingLayout(4)").ok();
1210 writeln!(out, " );").ok();
1211 writeln!(out).ok();
1212 writeln!(out, " private static NodeContext decodeNodeContext(").ok();
1213 writeln!(out, " final MemorySegment ctxPtr) {{").ok();
1214 writeln!(out, " var ctx = ctxPtr.reinterpret(").ok();
1215 writeln!(out, " CTX_LAYOUT.byteSize());").ok();
1216 writeln!(out, " int nodeType = ctx.get(").ok();
1217 writeln!(out, " ValueLayout.JAVA_INT, 0L);").ok();
1218 writeln!(out, " var tagNamePtr = ctx.get(").ok();
1219 writeln!(out, " ValueLayout.ADDRESS, CTX_OFFSET_TAG_NAME);").ok();
1220 writeln!(out, " String tagName = tagNamePtr").ok();
1221 writeln!(out, " .reinterpret(Long.MAX_VALUE).getString(0);").ok();
1222 writeln!(
1223 out,
1224 " long depth = ctx.get(ValueLayout.JAVA_LONG, CTX_OFFSET_DEPTH);"
1225 )
1226 .ok();
1227 writeln!(
1228 out,
1229 " long indexInParent = ctx.get(ValueLayout.JAVA_LONG, CTX_OFFSET_INDEX_IN_PARENT);"
1230 )
1231 .ok();
1232 writeln!(
1233 out,
1234 " var parentTagPtr = ctx.get(ValueLayout.ADDRESS, CTX_OFFSET_PARENT_TAG);"
1235 )
1236 .ok();
1237 writeln!(
1238 out,
1239 " String parentTag = parentTagPtr.equals(MemorySegment.NULL) ? null"
1240 )
1241 .ok();
1242 writeln!(
1243 out,
1244 " : parentTagPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1245 )
1246 .ok();
1247 writeln!(
1248 out,
1249 " int isInlineRaw = ctx.get(ValueLayout.JAVA_INT, CTX_OFFSET_IS_INLINE);"
1250 )
1251 .ok();
1252 writeln!(
1253 out,
1254 " return new NodeContext(nodeType, tagName, depth, indexInParent, parentTag, isInlineRaw != 0);"
1255 )
1256 .ok();
1257 writeln!(out, " }}").ok();
1258 writeln!(out).ok();
1259
1260 writeln!(
1262 out,
1263 " private static List<String> decodeCells(MemorySegment cellsPtr, long count) {{"
1264 )
1265 .ok();
1266 writeln!(out, " var result = new ArrayList<String>((int) count);").ok();
1267 writeln!(out, " for (long i = 0; i < count; i++) {{").ok();
1268 writeln!(
1269 out,
1270 " var ptr = cellsPtr.get(ValueLayout.ADDRESS, i * ValueLayout.ADDRESS.byteSize());"
1271 )
1272 .ok();
1273 writeln!(
1274 out,
1275 " result.add(ptr.reinterpret(Long.MAX_VALUE).getString(0));"
1276 )
1277 .ok();
1278 writeln!(out, " }}").ok();
1279 writeln!(out, " return result;").ok();
1280 writeln!(out, " }}").ok();
1281 writeln!(out).ok();
1282
1283 writeln!(
1289 out,
1290 " private static int encodeVisitResult(VisitResult result, MemorySegment outCustom, MemorySegment outLen) {{"
1291 )
1292 .ok();
1293 writeln!(out, " return switch (result) {{").ok();
1294 writeln!(
1295 out,
1296 " case VisitResult.Continue ignored -> VISIT_RESULT_CONTINUE;"
1297 )
1298 .ok();
1299 writeln!(out, " case VisitResult.Skip ignored -> VISIT_RESULT_SKIP;").ok();
1300 writeln!(
1301 out,
1302 " case VisitResult.PreserveHtml ignored -> VISIT_RESULT_PRESERVE_HTML;"
1303 )
1304 .ok();
1305 writeln!(out, " case VisitResult.Custom c -> {{").ok();
1306 writeln!(
1307 out,
1308 " var buf = Arena.global().allocateFrom(c.markdown());"
1309 )
1310 .ok();
1311 writeln!(
1312 out,
1313 " outCustom.reinterpret(ValueLayout.ADDRESS.byteSize()).set(ValueLayout.ADDRESS, 0L, buf);"
1314 )
1315 .ok();
1316 writeln!(
1317 out,
1318 " outLen.reinterpret(ValueLayout.JAVA_LONG.byteSize()).set(ValueLayout.JAVA_LONG, 0L, (long) c.markdown().getBytes(java.nio.charset.StandardCharsets.UTF_8).length);"
1319 )
1320 .ok();
1321 writeln!(out, " yield VISIT_RESULT_CUSTOM;").ok();
1322 writeln!(out, " }}").ok();
1323 writeln!(out, " case VisitResult.Error e -> {{").ok();
1324 writeln!(
1325 out,
1326 " var buf = Arena.global().allocateFrom(e.message());"
1327 )
1328 .ok();
1329 writeln!(
1330 out,
1331 " outCustom.reinterpret(ValueLayout.ADDRESS.byteSize()).set(ValueLayout.ADDRESS, 0L, buf);"
1332 )
1333 .ok();
1334 writeln!(
1335 out,
1336 " outLen.reinterpret(ValueLayout.JAVA_LONG.byteSize()).set(ValueLayout.JAVA_LONG, 0L, (long) e.message().getBytes(java.nio.charset.StandardCharsets.UTF_8).length);"
1337 )
1338 .ok();
1339 writeln!(out, " yield VISIT_RESULT_ERROR;").ok();
1340 writeln!(out, " }}").ok();
1341 writeln!(out, " }};").ok();
1342 writeln!(out, " }}").ok();
1343 writeln!(out).ok();
1344
1345 writeln!(out, " @Override").ok();
1346 writeln!(out, " public void close() {{").ok();
1347 writeln!(out, " arena.close();").ok();
1348 writeln!(out, " }}").ok();
1349 writeln!(out, "}}").ok();
1350 out
1351}
1352
1353fn stub_var_name(java_method: &str) -> String {
1360 let mut name = String::with_capacity(5 + java_method.len());
1361 name.push_str("stub");
1362 let mut chars = java_method.chars();
1363 if let Some(first) = chars.next() {
1364 for c in first.to_uppercase() {
1365 name.push(c);
1366 }
1367 name.push_str(chars.as_str());
1368 }
1369 name
1370}
1371
1372fn handle_method_name(java_method: &str) -> String {
1373 let mut name = String::with_capacity(7 + java_method.len());
1375 name.push_str("handle");
1376 let mut chars = java_method.chars();
1377 if let Some(first) = chars.next() {
1378 for c in first.to_uppercase() {
1379 name.push(c);
1380 }
1381 name.push_str(chars.as_str());
1382 }
1383 name
1384}
1385
1386fn iface_param_str(spec: &CallbackSpec) -> String {
1387 let mut params = vec!["final NodeContext context".to_string()];
1388 for ep in spec.extra {
1389 params.push(format!("final {} {}", ep.java_type, ep.java_name));
1390 }
1391 if spec.has_is_header {
1392 params.push("final boolean isHeader".to_string());
1393 }
1394 params.join(", ")
1395}
1396
1397fn callback_descriptor(spec: &CallbackSpec) -> String {
1401 let mut layouts = vec![
1402 "ValueLayout.ADDRESS".to_string(), "ValueLayout.ADDRESS".to_string(), ];
1405 for ep in spec.extra {
1406 for layout in ep.c_layouts {
1407 layouts.push((*layout).to_string());
1408 }
1409 }
1410 if spec.has_is_header {
1411 layouts.push("ValueLayout.JAVA_INT".to_string());
1412 }
1413 layouts.push("ValueLayout.ADDRESS".to_string()); layouts.push("ValueLayout.ADDRESS".to_string()); let indent = " ";
1416 let args = layouts.join(&format!(",\n{indent}"));
1417 format!("FunctionDescriptor.of(\n{indent}ValueLayout.JAVA_INT,\n{indent}{args})")
1418}
1419
1420fn callback_method_type(spec: &CallbackSpec) -> String {
1423 let mut types = vec![
1424 "MemorySegment.class".to_string(), "MemorySegment.class".to_string(), ];
1427 for ep in spec.extra {
1428 for layout in ep.c_layouts {
1429 types.push(layout_to_java_class(layout).to_string());
1430 }
1431 }
1432 if spec.has_is_header {
1433 types.push("int.class".to_string());
1434 }
1435 types.push("MemorySegment.class".to_string()); types.push("MemorySegment.class".to_string()); let indent = " ";
1438 let args = types.join(&format!(",\n{indent}"));
1439 format!("MethodType.methodType(\n{indent}int.class,\n{indent}{args})")
1440}
1441
1442fn layout_to_java_class(layout: &str) -> &'static str {
1443 match layout {
1444 "ValueLayout.ADDRESS" => "MemorySegment.class",
1445 "ValueLayout.JAVA_INT" => "int.class",
1446 "ValueLayout.JAVA_LONG" => "long.class",
1447 _ => "long.class",
1448 }
1449}
1450
1451fn gen_handle_method(out: &mut String, spec: &CallbackSpec) {
1453 let mut params = vec![
1455 "final MemorySegment ctx".to_string(),
1456 "final MemorySegment userData".to_string(),
1457 ];
1458 for ep in spec.extra {
1459 for (c_idx, layout) in ep.c_layouts.iter().enumerate() {
1460 let java_ptype = match *layout {
1461 "ValueLayout.JAVA_INT" => "int",
1462 "ValueLayout.JAVA_LONG" => "long",
1463 _ => "MemorySegment",
1464 };
1465 params.push(format!("final {java_ptype} {}", raw_var_name(ep.java_name, c_idx)));
1466 }
1467 }
1468 if spec.has_is_header {
1469 params.push("final int isHeader".to_string());
1470 }
1471 params.push("final MemorySegment outCustom".to_string());
1472 params.push("final MemorySegment outLen".to_string());
1473
1474 let method_name = handle_method_name(spec.java_method);
1475 let single_line = format!(" int {}({}) {{", method_name, params.join(", "));
1476 if single_line.len() <= 80 {
1477 writeln!(out, "{}", single_line).ok();
1478 } else {
1479 let indent = " ";
1480 writeln!(
1481 out,
1482 " int {}(\n{}{}) {{",
1483 method_name,
1484 indent,
1485 params.join(&format!(",\n{indent}"))
1486 )
1487 .ok();
1488 }
1489 writeln!(out, " try {{").ok();
1490 writeln!(out, " var context = decodeNodeContext(ctx);").ok();
1491
1492 for ep in spec.extra {
1494 let mut decode = ep.decode.to_string();
1495 for (c_idx, _) in ep.c_layouts.iter().enumerate() {
1496 let placeholder = format!("raw_{}_{}", ep.java_name, c_idx);
1497 let var = raw_var_name(ep.java_name, c_idx);
1498 decode = decode.replace(&placeholder, &var);
1499 }
1500 writeln!(out, " var {} = {};", ep.java_name, decode).ok();
1501 }
1502 if spec.has_is_header {
1503 writeln!(out, " var goIsHeader = isHeader != 0;").ok();
1504 }
1505
1506 let mut call_args = vec!["context".to_string()];
1508 for ep in spec.extra {
1509 call_args.push(ep.java_name.to_string());
1510 }
1511 if spec.has_is_header {
1512 call_args.push("goIsHeader".to_string());
1513 }
1514
1515 writeln!(
1516 out,
1517 " var result = visitor.{}({});",
1518 spec.java_method,
1519 call_args.join(", ")
1520 )
1521 .ok();
1522 writeln!(out, " return encodeVisitResult(result, outCustom, outLen);").ok();
1523 writeln!(out, " }} catch (Throwable ignored) {{").ok();
1524 writeln!(out, " return 0;").ok();
1525 writeln!(out, " }}").ok();
1526 writeln!(out, " }}").ok();
1527 writeln!(out).ok();
1528}
1529
1530fn raw_var_name(java_name: &str, c_idx: usize) -> String {
1531 let mut name = String::with_capacity(4 + java_name.len() + 2);
1534 name.push_str("raw");
1535 let mut chars = java_name.chars();
1536 if let Some(first) = chars.next() {
1537 for c in first.to_uppercase() {
1538 name.push(c);
1539 }
1540 name.push_str(chars.as_str());
1541 }
1542 name.push_str(&c_idx.to_string());
1543 name
1544}