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