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 ("VisitResult.java".to_string(), gen_visit_result(package)),
626 ("Visitor.java".to_string(), gen_visitor_interface(package, class_name)),
627 (
628 "VisitorBridge.java".to_string(),
629 gen_visitor_bridge(package, class_name),
630 ),
631 ]
632}
633
634pub fn gen_native_lib_visitor_handles(prefix: &str) -> String {
638 let mut out = String::with_capacity(512);
639 let pu = prefix.to_uppercase();
640
641 writeln!(out).ok();
642 writeln!(out, " // Visitor FFI handles").ok();
643 writeln!(
644 out,
645 " static final MethodHandle {pu}_VISITOR_CREATE = LINKER.downcallHandle("
646 )
647 .ok();
648 writeln!(out, " LIB.find(\"{prefix}_visitor_create\").orElseThrow(),").ok();
649 writeln!(
650 out,
651 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
652 )
653 .ok();
654 writeln!(out, " );").ok();
655 writeln!(out).ok();
656 writeln!(
657 out,
658 " static final MethodHandle {pu}_VISITOR_FREE = LINKER.downcallHandle("
659 )
660 .ok();
661 writeln!(out, " LIB.find(\"{prefix}_visitor_free\").orElseThrow(),").ok();
662 writeln!(out, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
663 writeln!(out, " );").ok();
664 writeln!(out).ok();
665 writeln!(
666 out,
667 " static final MethodHandle {pu}_CONVERT_WITH_VISITOR = LINKER.downcallHandle("
668 )
669 .ok();
670 writeln!(
671 out,
672 " LIB.find(\"{prefix}_convert_with_visitor\").orElseThrow(),"
673 )
674 .ok();
675 writeln!(
676 out,
677 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
678 )
679 .ok();
680 writeln!(out, " );").ok();
681
682 out
683}
684
685pub fn gen_convert_with_visitor_method(class_name: &str, prefix: &str) -> String {
689 let mut out = String::with_capacity(2048);
690 let pu = prefix.to_uppercase();
691 let exc = format!("{class_name}Exception");
692
693 writeln!(
694 out,
695 " public static ConversionResult convertWithVisitor(String html, ConversionOptions options, Visitor visitor) throws {exc} {{"
696 )
697 .ok();
698 writeln!(out, " try (var arena = Arena.ofConfined();").ok();
699 writeln!(out, " var bridge = new VisitorBridge(visitor)) {{").ok();
700 writeln!(out, " var cHtml = arena.allocateFrom(html);").ok();
701 writeln!(out).ok();
702 writeln!(out, " MemorySegment optionsPtr = MemorySegment.NULL;").ok();
703 writeln!(out, " if (options != null) {{").ok();
704 writeln!(
705 out,
706 " var optJson = arena.allocateFrom(createObjectMapper().writeValueAsString(options));"
707 )
708 .ok();
709 writeln!(
710 out,
711 " optionsPtr = (MemorySegment) NativeLib.{pu}_CONVERSION_OPTIONS_FROM_JSON.invoke(optJson);"
712 )
713 .ok();
714 writeln!(out, " }}").ok();
715 writeln!(out).ok();
716 writeln!(
717 out,
718 " var visitorHandle = (MemorySegment) NativeLib.{pu}_VISITOR_CREATE.invoke(bridge.callbacksStruct());"
719 )
720 .ok();
721 writeln!(out, " if (visitorHandle.equals(MemorySegment.NULL)) {{").ok();
722 writeln!(
723 out,
724 " throw new {exc}(\"Failed to create visitor handle\", null);"
725 )
726 .ok();
727 writeln!(out, " }}").ok();
728 writeln!(out).ok();
729 writeln!(out, " try {{").ok();
730 writeln!(
731 out,
732 " var resultPtr = (MemorySegment) NativeLib.{pu}_CONVERT_WITH_VISITOR.invoke(cHtml, optionsPtr, visitorHandle);"
733 )
734 .ok();
735 writeln!(out, " if (!optionsPtr.equals(MemorySegment.NULL)) {{").ok();
736 writeln!(
737 out,
738 " NativeLib.{pu}_CONVERSION_OPTIONS_FREE.invoke(optionsPtr);"
739 )
740 .ok();
741 writeln!(out, " }}").ok();
742 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
743 writeln!(out, " checkLastError();").ok();
744 writeln!(out, " return null;").ok();
745 writeln!(out, " }}").ok();
746 writeln!(
747 out,
748 " var json = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
749 )
750 .ok();
751 writeln!(out, " NativeLib.{pu}_FREE_STRING.invoke(resultPtr);").ok();
752 writeln!(
753 out,
754 " return createObjectMapper().readValue(json, ConversionResult.class);"
755 )
756 .ok();
757 writeln!(out, " }} catch (Throwable e) {{").ok();
758 writeln!(out, " throw new {exc}(\"FFI call failed\", e);").ok();
759 writeln!(out, " }} finally {{").ok();
760 writeln!(
761 out,
762 " NativeLib.{pu}_VISITOR_FREE.invoke(visitorHandle);"
763 )
764 .ok();
765 writeln!(out, " }}").ok();
766 writeln!(out, " }} catch ({exc} e) {{").ok();
767 writeln!(out, " throw e;").ok();
768 writeln!(out, " }} catch (Throwable e) {{").ok();
769 writeln!(out, " throw new {exc}(\"FFI call failed\", e);").ok();
770 writeln!(out, " }}").ok();
771 writeln!(out, " }}").ok();
772
773 out
774}
775
776fn gen_node_context(package: &str) -> String {
781 let mut out = String::with_capacity(1024);
782 out.push_str(&hash::header(CommentStyle::DoubleSlash));
783 writeln!(out, "package {package};").ok();
784 writeln!(out).ok();
785 writeln!(out, "/** Context passed to every visitor callback. */").ok();
786 writeln!(out, "public record NodeContext(").ok();
787 writeln!(out, " /** Coarse-grained node type tag. */").ok();
788 writeln!(out, " int nodeType,").ok();
789 writeln!(out, " /** HTML element tag name (e.g. \"div\"). */").ok();
790 writeln!(out, " String tagName,").ok();
791 writeln!(out, " /** DOM depth (0 = root). */").ok();
792 writeln!(out, " long depth,").ok();
793 writeln!(out, " /** 0-based sibling index. */").ok();
794 writeln!(out, " long indexInParent,").ok();
795 writeln!(out, " /** Parent element tag name, or null at the root. */").ok();
796 writeln!(out, " String parentTag,").ok();
797 writeln!(out, " /** True when this element is treated as inline. */").ok();
798 writeln!(out, " boolean isInline").ok();
799 writeln!(out, ") {{}}").ok();
800 out
801}
802
803fn gen_visit_result(package: &str) -> String {
804 let mut out = String::with_capacity(2048);
805 out.push_str(&hash::header(CommentStyle::DoubleSlash));
806 writeln!(out, "package {package};").ok();
807 writeln!(out).ok();
808 writeln!(out, "/** Controls how the visitor affects the conversion pipeline. */").ok();
809 writeln!(out, "public sealed interface VisitResult").ok();
810 writeln!(
811 out,
812 " permits VisitResult.Continue, VisitResult.Skip, VisitResult.PreserveHtml,"
813 )
814 .ok();
815 writeln!(out, " VisitResult.Custom, VisitResult.Error {{").ok();
816 writeln!(out).ok();
817 writeln!(out, " /** Proceed with default conversion. */").ok();
818 writeln!(out, " record Continue() implements VisitResult {{}}").ok();
819 writeln!(out).ok();
820 writeln!(out, " /** Omit this element from output entirely. */").ok();
821 writeln!(out, " record Skip() implements VisitResult {{}}").ok();
822 writeln!(out).ok();
823 writeln!(out, " /** Keep original HTML verbatim. */").ok();
824 writeln!(out, " record PreserveHtml() implements VisitResult {{}}").ok();
825 writeln!(out).ok();
826 writeln!(out, " /** Replace with custom Markdown. */").ok();
827 writeln!(out, " record Custom(String markdown) implements VisitResult {{}}").ok();
828 writeln!(out).ok();
829 writeln!(out, " /** Abort conversion with an error message. */").ok();
830 writeln!(out, " record Error(String message) implements VisitResult {{}}").ok();
831 writeln!(out).ok();
832 writeln!(out, " /** Convenience: continue with default conversion. */").ok();
833 writeln!(
834 out,
835 " static VisitResult continueDefault() {{ return new Continue(); }}"
836 )
837 .ok();
838 writeln!(out).ok();
839 writeln!(out, " /** Convenience: skip this element. */").ok();
840 writeln!(out, " static VisitResult skip() {{ return new Skip(); }}").ok();
841 writeln!(out).ok();
842 writeln!(out, " /** Convenience: preserve original HTML. */").ok();
843 writeln!(
844 out,
845 " static VisitResult preserveHtml() {{ return new PreserveHtml(); }}"
846 )
847 .ok();
848 writeln!(out).ok();
849 writeln!(out, " /** Convenience: emit custom Markdown. */").ok();
850 writeln!(
851 out,
852 " static VisitResult custom(String markdown) {{ return new Custom(markdown); }}"
853 )
854 .ok();
855 writeln!(out).ok();
856 writeln!(out, " /** Convenience: abort with error. */").ok();
857 writeln!(
858 out,
859 " static VisitResult error(String message) {{ return new Error(message); }}"
860 )
861 .ok();
862 writeln!(out, "}}").ok();
863 out
864}
865
866fn gen_visitor_interface(package: &str, _class_name: &str) -> String {
867 let mut out = String::with_capacity(4096);
868 out.push_str(&hash::header(CommentStyle::DoubleSlash));
869 writeln!(out, "package {package};").ok();
870 writeln!(out).ok();
871 writeln!(
872 out,
873 "/** Visitor interface for the HTML-to-Markdown conversion pipeline. */"
874 )
875 .ok();
876 writeln!(out, "public interface Visitor {{").ok();
877 for spec in CALLBACKS {
878 let params = iface_param_str(spec);
879 writeln!(out, " /** {} */", spec.doc).ok();
880 writeln!(
881 out,
882 " default VisitResult {}({}) {{ return VisitResult.continueDefault(); }}",
883 spec.java_method, params
884 )
885 .ok();
886 }
887 writeln!(out, "}}").ok();
888 out
889}
890
891fn gen_visitor_bridge(package: &str, _class_name: &str) -> String {
894 let mut out = String::with_capacity(32_768);
895 out.push_str(&hash::header(CommentStyle::DoubleSlash));
896 writeln!(out, "package {package};").ok();
897 writeln!(out).ok();
898 writeln!(out, "import java.lang.foreign.Arena;").ok();
899 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
900 writeln!(out, "import java.lang.foreign.Linker;").ok();
901 writeln!(out, "import java.lang.foreign.MemoryLayout;").ok();
902 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
903 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
904 writeln!(out, "import java.lang.invoke.MethodHandles;").ok();
905 writeln!(out, "import java.lang.invoke.MethodType;").ok();
906 writeln!(out, "import java.util.ArrayList;").ok();
907 writeln!(out, "import java.util.List;").ok();
908 writeln!(out).ok();
909
910 writeln!(out, "/**").ok();
911 writeln!(out, " * Allocates Panama FFM upcall stubs for a Visitor and assembles").ok();
912 writeln!(out, " * the C HTMHtmVisitorCallbacks struct in native memory.").ok();
913 writeln!(out, " */").ok();
914 writeln!(out, "final class VisitorBridge implements AutoCloseable {{").ok();
915 writeln!(out, " private static final Linker LINKER = Linker.nativeLinker();").ok();
916 writeln!(out, " private static final MethodHandles.Lookup LOOKUP =").ok();
917 writeln!(out, " MethodHandles.lookup();").ok();
918 writeln!(out).ok();
919 writeln!(out, " // VisitResult discriminant codes returned to C").ok();
921 writeln!(out, " private static final int VISIT_RESULT_CONTINUE = 0;").ok();
922 writeln!(out, " private static final int VISIT_RESULT_SKIP = 1;").ok();
923 writeln!(out, " private static final int VISIT_RESULT_PRESERVE_HTML = 2;").ok();
924 writeln!(out, " private static final int VISIT_RESULT_CUSTOM = 3;").ok();
925 writeln!(out, " private static final int VISIT_RESULT_ERROR = 4;").ok();
926 writeln!(out).ok();
927
928 let num_fields = CALLBACKS.len() + 1; writeln!(
931 out,
932 " // HTMHtmVisitorCallbacks: user_data + {n} callbacks",
933 n = CALLBACKS.len(),
934 )
935 .ok();
936 writeln!(out, " // = {total} pointer-sized slots", total = num_fields,).ok();
937 writeln!(out, " private static final long CALLBACKS_STRUCT_SIZE =").ok();
938 writeln!(out, " (long) ValueLayout.ADDRESS.byteSize() * {num_fields}L;").ok();
939 writeln!(out).ok();
940 writeln!(out, " // HTMHtmNodeContext field offsets").ok();
942 writeln!(out, " private static final long CTX_OFFSET_TAG_NAME = 8L;").ok();
943 writeln!(out, " private static final long CTX_OFFSET_DEPTH = 16L;").ok();
944 writeln!(out, " private static final long CTX_OFFSET_INDEX_IN_PARENT = 24L;").ok();
945 writeln!(out, " private static final long CTX_OFFSET_PARENT_TAG = 32L;").ok();
946 writeln!(out, " private static final long CTX_OFFSET_IS_INLINE = 40L;").ok();
947 writeln!(out).ok();
948 writeln!(out, " private final Arena arena;").ok();
949 writeln!(out, " private final MemorySegment struct;").ok();
950 writeln!(out, " private final Visitor visitor;").ok();
951 writeln!(out).ok();
952 writeln!(out, " VisitorBridge(Visitor visitor) {{").ok();
953 writeln!(out, " this.visitor = visitor;").ok();
954 writeln!(out, " this.arena = Arena.ofConfined();").ok();
955 writeln!(out, " this.struct = arena.allocate(CALLBACKS_STRUCT_SIZE);").ok();
956 writeln!(out, " // Slot 0: user_data = NULL").ok();
957 writeln!(out, " // (visitor captured via lambda closure)").ok();
958 writeln!(out, " struct.set(ValueLayout.ADDRESS, 0L, MemorySegment.NULL);").ok();
959 writeln!(out, " try {{").ok();
960 writeln!(out, " long offset = ValueLayout.ADDRESS.byteSize();").ok();
961 let num_chunks = CALLBACKS.chunks(10).count();
963 for i in 1..=num_chunks {
964 if i < num_chunks {
965 writeln!(out, " offset = registerStubs{}(offset);", i).ok();
966 } else {
967 writeln!(out, " registerStubs{}(offset);", i).ok();
968 }
969 }
970 writeln!(out, " }} catch (ReflectiveOperationException e) {{").ok();
971 writeln!(out, " arena.close();").ok();
972 writeln!(out, " throw new RuntimeException(").ok();
973 writeln!(out, " \"Failed to create visitor upcall stubs\", e);").ok();
974 writeln!(out, " }}").ok();
975 writeln!(out, " }}").ok();
976 writeln!(out).ok();
977
978 const CHUNK_SIZE: usize = 10;
980 for (chunk_idx, chunk) in CALLBACKS.chunks(CHUNK_SIZE).enumerate() {
981 let method_num = chunk_idx + 1;
982 writeln!(
983 out,
984 " private long registerStubs{}(\n final long offset)\n throws ReflectiveOperationException {{",
985 method_num
986 )
987 .ok();
988 writeln!(out, " long off = offset;").ok();
989 for spec in chunk {
990 let descriptor = callback_descriptor(spec);
991 let method_type = callback_method_type(spec);
992 let stub_var = stub_var_name(spec.java_method);
993 writeln!(out, " // {}", spec.c_field).ok();
994 writeln!(out, " var {} = LINKER.upcallStub(", stub_var).ok();
995 writeln!(out, " LOOKUP.bind(",).ok();
996 writeln!(
997 out,
998 " this, \"{}\",",
999 handle_method_name(spec.java_method),
1000 )
1001 .ok();
1002 writeln!(out, " {}),", method_type).ok();
1003 writeln!(out, " {},", descriptor).ok();
1004 writeln!(out, " arena);").ok();
1005 writeln!(out, " struct.set(ValueLayout.ADDRESS, off, {});", stub_var).ok();
1006 writeln!(out, " off += ValueLayout.ADDRESS.byteSize();").ok();
1007 }
1008 writeln!(out, " return off;").ok();
1009 writeln!(out, " }}").ok();
1010 writeln!(out).ok();
1011 }
1012 writeln!(out).ok();
1013 writeln!(
1014 out,
1015 " /** Returns the native HTMHtmVisitorCallbacks struct pointer. */"
1016 )
1017 .ok();
1018 writeln!(out, " MemorySegment callbacksStruct() {{").ok();
1019 writeln!(out, " return struct;").ok();
1020 writeln!(out, " }}").ok();
1021 writeln!(out).ok();
1022
1023 for spec in CALLBACKS {
1025 gen_handle_method(&mut out, spec);
1026 }
1027
1028 writeln!(
1030 out,
1031 " // HTMHtmNodeContext: int32 node_type, char* tag_name, uintptr depth,"
1032 )
1033 .ok();
1034 writeln!(out, " // uintptr index_in_parent, char* parent_tag,").ok();
1035 writeln!(out, " // int32 is_inline").ok();
1036 writeln!(out, " private static final MemoryLayout CTX_LAYOUT =").ok();
1037 writeln!(out, " MemoryLayout.structLayout(").ok();
1038 writeln!(out, " ValueLayout.JAVA_INT.withName(\"node_type\"),").ok();
1039 writeln!(out, " MemoryLayout.paddingLayout(4),").ok();
1040 writeln!(out, " ValueLayout.ADDRESS.withName(\"tag_name\"),").ok();
1041 writeln!(out, " ValueLayout.JAVA_LONG.withName(\"depth\"),").ok();
1042 writeln!(out, " ValueLayout.JAVA_LONG.withName(\"index_in_parent\"),").ok();
1043 writeln!(out, " ValueLayout.ADDRESS.withName(\"parent_tag\"),").ok();
1044 writeln!(out, " ValueLayout.JAVA_INT.withName(\"is_inline\"),").ok();
1045 writeln!(out, " MemoryLayout.paddingLayout(4)").ok();
1046 writeln!(out, " );").ok();
1047 writeln!(out).ok();
1048 writeln!(out, " private static NodeContext decodeNodeContext(").ok();
1049 writeln!(out, " final MemorySegment ctxPtr) {{").ok();
1050 writeln!(out, " var ctx = ctxPtr.reinterpret(").ok();
1051 writeln!(out, " CTX_LAYOUT.byteSize());").ok();
1052 writeln!(out, " int nodeType = ctx.get(").ok();
1053 writeln!(out, " ValueLayout.JAVA_INT, 0L);").ok();
1054 writeln!(out, " var tagNamePtr = ctx.get(").ok();
1055 writeln!(out, " ValueLayout.ADDRESS, CTX_OFFSET_TAG_NAME);").ok();
1056 writeln!(out, " String tagName = tagNamePtr").ok();
1057 writeln!(out, " .reinterpret(Long.MAX_VALUE).getString(0);").ok();
1058 writeln!(
1059 out,
1060 " long depth = ctx.get(ValueLayout.JAVA_LONG, CTX_OFFSET_DEPTH);"
1061 )
1062 .ok();
1063 writeln!(
1064 out,
1065 " long indexInParent = ctx.get(ValueLayout.JAVA_LONG, CTX_OFFSET_INDEX_IN_PARENT);"
1066 )
1067 .ok();
1068 writeln!(
1069 out,
1070 " var parentTagPtr = ctx.get(ValueLayout.ADDRESS, CTX_OFFSET_PARENT_TAG);"
1071 )
1072 .ok();
1073 writeln!(
1074 out,
1075 " String parentTag = parentTagPtr.equals(MemorySegment.NULL) ? null"
1076 )
1077 .ok();
1078 writeln!(
1079 out,
1080 " : parentTagPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1081 )
1082 .ok();
1083 writeln!(
1084 out,
1085 " int isInlineRaw = ctx.get(ValueLayout.JAVA_INT, CTX_OFFSET_IS_INLINE);"
1086 )
1087 .ok();
1088 writeln!(
1089 out,
1090 " return new NodeContext(nodeType, tagName, depth, indexInParent, parentTag, isInlineRaw != 0);"
1091 )
1092 .ok();
1093 writeln!(out, " }}").ok();
1094 writeln!(out).ok();
1095
1096 writeln!(
1098 out,
1099 " private static List<String> decodeCells(MemorySegment cellsPtr, long count) {{"
1100 )
1101 .ok();
1102 writeln!(out, " var result = new ArrayList<String>((int) count);").ok();
1103 writeln!(out, " for (long i = 0; i < count; i++) {{").ok();
1104 writeln!(
1105 out,
1106 " var ptr = cellsPtr.get(ValueLayout.ADDRESS, i * ValueLayout.ADDRESS.byteSize());"
1107 )
1108 .ok();
1109 writeln!(
1110 out,
1111 " result.add(ptr.reinterpret(Long.MAX_VALUE).getString(0));"
1112 )
1113 .ok();
1114 writeln!(out, " }}").ok();
1115 writeln!(out, " return result;").ok();
1116 writeln!(out, " }}").ok();
1117 writeln!(out).ok();
1118
1119 writeln!(
1121 out,
1122 " private static int encodeVisitResult(VisitResult result, MemorySegment outCustom, MemorySegment outLen, Arena encArena) {{"
1123 )
1124 .ok();
1125 writeln!(out, " return switch (result) {{").ok();
1126 writeln!(
1127 out,
1128 " case VisitResult.Continue ignored -> VISIT_RESULT_CONTINUE;"
1129 )
1130 .ok();
1131 writeln!(out, " case VisitResult.Skip ignored -> VISIT_RESULT_SKIP;").ok();
1132 writeln!(
1133 out,
1134 " case VisitResult.PreserveHtml ignored -> VISIT_RESULT_PRESERVE_HTML;"
1135 )
1136 .ok();
1137 writeln!(out, " case VisitResult.Custom c -> {{").ok();
1138 writeln!(out, " var buf = encArena.allocateFrom(c.markdown());").ok();
1139 writeln!(out, " outCustom.set(ValueLayout.ADDRESS, 0L, buf);").ok();
1140 writeln!(
1141 out,
1142 " outLen.set(ValueLayout.JAVA_LONG, 0L, (long) c.markdown().getBytes(java.nio.charset.StandardCharsets.UTF_8).length);"
1143 )
1144 .ok();
1145 writeln!(out, " yield VISIT_RESULT_CUSTOM;").ok();
1146 writeln!(out, " }}").ok();
1147 writeln!(out, " case VisitResult.Error e -> {{").ok();
1148 writeln!(out, " var buf = encArena.allocateFrom(e.message());").ok();
1149 writeln!(out, " outCustom.set(ValueLayout.ADDRESS, 0L, buf);").ok();
1150 writeln!(
1151 out,
1152 " outLen.set(ValueLayout.JAVA_LONG, 0L, (long) e.message().getBytes(java.nio.charset.StandardCharsets.UTF_8).length);"
1153 )
1154 .ok();
1155 writeln!(out, " yield VISIT_RESULT_ERROR;").ok();
1156 writeln!(out, " }}").ok();
1157 writeln!(out, " }};").ok();
1158 writeln!(out, " }}").ok();
1159 writeln!(out).ok();
1160
1161 writeln!(out, " @Override").ok();
1162 writeln!(out, " public void close() {{").ok();
1163 writeln!(out, " arena.close();").ok();
1164 writeln!(out, " }}").ok();
1165 writeln!(out, "}}").ok();
1166 out
1167}
1168
1169fn stub_var_name(java_method: &str) -> String {
1176 let mut name = String::with_capacity(5 + java_method.len());
1177 name.push_str("stub");
1178 let mut chars = java_method.chars();
1179 if let Some(first) = chars.next() {
1180 for c in first.to_uppercase() {
1181 name.push(c);
1182 }
1183 name.push_str(chars.as_str());
1184 }
1185 name
1186}
1187
1188fn handle_method_name(java_method: &str) -> String {
1189 let mut name = String::with_capacity(7 + java_method.len());
1191 name.push_str("handle");
1192 let mut chars = java_method.chars();
1193 if let Some(first) = chars.next() {
1194 for c in first.to_uppercase() {
1195 name.push(c);
1196 }
1197 name.push_str(chars.as_str());
1198 }
1199 name
1200}
1201
1202fn iface_param_str(spec: &CallbackSpec) -> String {
1203 let mut params = vec!["final NodeContext context".to_string()];
1204 for ep in spec.extra {
1205 params.push(format!("final {} {}", ep.java_type, ep.java_name));
1206 }
1207 if spec.has_is_header {
1208 params.push("final boolean isHeader".to_string());
1209 }
1210 params.join(", ")
1211}
1212
1213fn callback_descriptor(spec: &CallbackSpec) -> String {
1217 let mut layouts = vec![
1218 "ValueLayout.ADDRESS".to_string(), "ValueLayout.ADDRESS".to_string(), ];
1221 for ep in spec.extra {
1222 for layout in ep.c_layouts {
1223 layouts.push((*layout).to_string());
1224 }
1225 }
1226 if spec.has_is_header {
1227 layouts.push("ValueLayout.JAVA_INT".to_string());
1228 }
1229 layouts.push("ValueLayout.ADDRESS".to_string()); layouts.push("ValueLayout.ADDRESS".to_string()); let indent = " ";
1232 let args = layouts.join(&format!(",\n{indent}"));
1233 format!("FunctionDescriptor.of(\n{indent}ValueLayout.JAVA_INT,\n{indent}{args})")
1234}
1235
1236fn callback_method_type(spec: &CallbackSpec) -> String {
1239 let mut types = vec![
1240 "MemorySegment.class".to_string(), "MemorySegment.class".to_string(), ];
1243 for ep in spec.extra {
1244 for layout in ep.c_layouts {
1245 types.push(layout_to_java_class(layout).to_string());
1246 }
1247 }
1248 if spec.has_is_header {
1249 types.push("int.class".to_string());
1250 }
1251 types.push("MemorySegment.class".to_string()); types.push("MemorySegment.class".to_string()); let indent = " ";
1254 let args = types.join(&format!(",\n{indent}"));
1255 format!("MethodType.methodType(\n{indent}int.class,\n{indent}{args})")
1256}
1257
1258fn layout_to_java_class(layout: &str) -> &'static str {
1259 match layout {
1260 "ValueLayout.ADDRESS" => "MemorySegment.class",
1261 "ValueLayout.JAVA_INT" => "int.class",
1262 "ValueLayout.JAVA_LONG" => "long.class",
1263 _ => "long.class",
1264 }
1265}
1266
1267fn gen_handle_method(out: &mut String, spec: &CallbackSpec) {
1269 let mut params = vec![
1271 "final MemorySegment ctx".to_string(),
1272 "final MemorySegment userData".to_string(),
1273 ];
1274 for ep in spec.extra {
1275 for (c_idx, layout) in ep.c_layouts.iter().enumerate() {
1276 let java_ptype = match *layout {
1277 "ValueLayout.JAVA_INT" => "int",
1278 "ValueLayout.JAVA_LONG" => "long",
1279 _ => "MemorySegment",
1280 };
1281 params.push(format!("final {java_ptype} {}", raw_var_name(ep.java_name, c_idx)));
1282 }
1283 }
1284 if spec.has_is_header {
1285 params.push("final int isHeader".to_string());
1286 }
1287 params.push("final MemorySegment outCustom".to_string());
1288 params.push("final MemorySegment outLen".to_string());
1289
1290 let method_name = handle_method_name(spec.java_method);
1291 let single_line = format!(" int {}({}) {{", method_name, params.join(", "));
1292 if single_line.len() <= 80 {
1293 writeln!(out, "{}", single_line).ok();
1294 } else {
1295 let indent = " ";
1296 writeln!(
1297 out,
1298 " int {}(\n{}{}) {{",
1299 method_name,
1300 indent,
1301 params.join(&format!(",\n{indent}"))
1302 )
1303 .ok();
1304 }
1305 writeln!(out, " try (var encArena = Arena.ofConfined()) {{").ok();
1306 writeln!(out, " var context = decodeNodeContext(ctx);").ok();
1307
1308 for ep in spec.extra {
1310 let mut decode = ep.decode.to_string();
1311 for (c_idx, _) in ep.c_layouts.iter().enumerate() {
1312 let placeholder = format!("raw_{}_{}", ep.java_name, c_idx);
1313 let var = raw_var_name(ep.java_name, c_idx);
1314 decode = decode.replace(&placeholder, &var);
1315 }
1316 writeln!(out, " var {} = {};", ep.java_name, decode).ok();
1317 }
1318 if spec.has_is_header {
1319 writeln!(out, " var goIsHeader = isHeader != 0;").ok();
1320 }
1321
1322 let mut call_args = vec!["context".to_string()];
1324 for ep in spec.extra {
1325 call_args.push(ep.java_name.to_string());
1326 }
1327 if spec.has_is_header {
1328 call_args.push("goIsHeader".to_string());
1329 }
1330
1331 writeln!(
1332 out,
1333 " var result = visitor.{}({});",
1334 spec.java_method,
1335 call_args.join(", ")
1336 )
1337 .ok();
1338 writeln!(
1339 out,
1340 " return encodeVisitResult(result, outCustom, outLen, encArena);"
1341 )
1342 .ok();
1343 writeln!(out, " }} catch (Throwable ignored) {{").ok();
1344 writeln!(out, " return 0;").ok();
1345 writeln!(out, " }}").ok();
1346 writeln!(out, " }}").ok();
1347 writeln!(out).ok();
1348}
1349
1350fn raw_var_name(java_name: &str, c_idx: usize) -> String {
1351 let mut name = String::with_capacity(4 + java_name.len() + 2);
1354 name.push_str("raw");
1355 let mut chars = java_name.chars();
1356 if let Some(first) = chars.next() {
1357 for c in first.to_uppercase() {
1358 name.push(c);
1359 }
1360 name.push_str(chars.as_str());
1361 }
1362 name.push_str(&c_idx.to_string());
1363 name
1364}