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!(
916 out,
917 " private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();"
918 )
919 .ok();
920 writeln!(out).ok();
921 writeln!(out, " // VisitResult discriminant codes returned to C").ok();
923 writeln!(out, " private static final int VISIT_RESULT_CONTINUE = 0;").ok();
924 writeln!(out, " private static final int VISIT_RESULT_SKIP = 1;").ok();
925 writeln!(out, " private static final int VISIT_RESULT_PRESERVE_HTML = 2;").ok();
926 writeln!(out, " private static final int VISIT_RESULT_CUSTOM = 3;").ok();
927 writeln!(out, " private static final int VISIT_RESULT_ERROR = 4;").ok();
928 writeln!(out).ok();
929
930 let num_fields = CALLBACKS.len() + 1; writeln!(
933 out,
934 " // HTMHtmVisitorCallbacks: user_data + {n} callback pointers = {total} pointer-sized slots",
935 n = CALLBACKS.len(),
936 total = num_fields,
937 )
938 .ok();
939 writeln!(
940 out,
941 " private static final long CALLBACKS_STRUCT_SIZE = (long) ValueLayout.ADDRESS.byteSize() * {num_fields}L;"
942 )
943 .ok();
944 writeln!(out).ok();
945 writeln!(out, " // HTMHtmNodeContext field offsets").ok();
947 writeln!(out, " private static final long CTX_OFFSET_TAG_NAME = 8L;").ok();
948 writeln!(out, " private static final long CTX_OFFSET_DEPTH = 16L;").ok();
949 writeln!(out, " private static final long CTX_OFFSET_INDEX_IN_PARENT = 24L;").ok();
950 writeln!(out, " private static final long CTX_OFFSET_PARENT_TAG = 32L;").ok();
951 writeln!(out, " private static final long CTX_OFFSET_IS_INLINE = 40L;").ok();
952 writeln!(out).ok();
953 writeln!(out, " private final Arena arena;").ok();
954 writeln!(out, " private final MemorySegment struct;").ok();
955 writeln!(out, " private final Visitor visitor;").ok();
956 writeln!(out).ok();
957 writeln!(out, " VisitorBridge(Visitor visitor) {{").ok();
958 writeln!(out, " this.visitor = visitor;").ok();
959 writeln!(out, " this.arena = Arena.ofConfined();").ok();
960 writeln!(out, " this.struct = arena.allocate(CALLBACKS_STRUCT_SIZE);").ok();
961 writeln!(
962 out,
963 " // Slot 0: user_data = NULL (not needed; visitor captured via lambda closure)"
964 )
965 .ok();
966 writeln!(out, " struct.set(ValueLayout.ADDRESS, 0L, MemorySegment.NULL);").ok();
967 writeln!(out, " try {{").ok();
968 writeln!(out, " long offset = ValueLayout.ADDRESS.byteSize();").ok();
969 let num_chunks = CALLBACKS.chunks(10).count();
971 for i in 1..=num_chunks {
972 if i < num_chunks {
973 writeln!(out, " offset = registerStubs{}(offset);", i).ok();
974 } else {
975 writeln!(out, " registerStubs{}(offset);", i).ok();
976 }
977 }
978 writeln!(out, " }} catch (ReflectiveOperationException e) {{").ok();
979 writeln!(out, " arena.close();").ok();
980 writeln!(
981 out,
982 " throw new RuntimeException(\"Failed to create visitor upcall stubs\", e);"
983 )
984 .ok();
985 writeln!(out, " }}").ok();
986 writeln!(out, " }}").ok();
987 writeln!(out).ok();
988
989 const CHUNK_SIZE: usize = 10;
991 for (chunk_idx, chunk) in CALLBACKS.chunks(CHUNK_SIZE).enumerate() {
992 let method_num = chunk_idx + 1;
993 writeln!(
994 out,
995 " private long registerStubs{}(long offset) throws ReflectiveOperationException {{",
996 method_num
997 )
998 .ok();
999 for spec in chunk {
1000 let descriptor = callback_descriptor(spec);
1001 let method_type = callback_method_type(spec);
1002 let stub_var = stub_var_name(spec.java_method);
1003 writeln!(out, " // {}", spec.c_field).ok();
1004 writeln!(out, " var {} = LINKER.upcallStub(", stub_var).ok();
1005 writeln!(
1006 out,
1007 " LOOKUP.bind(this, \"{}\", {}),",
1008 handle_method_name(spec.java_method),
1009 method_type
1010 )
1011 .ok();
1012 writeln!(out, " {},", descriptor).ok();
1013 writeln!(out, " arena);").ok();
1014 writeln!(out, " struct.set(ValueLayout.ADDRESS, offset, {});", stub_var).ok();
1015 writeln!(out, " offset += ValueLayout.ADDRESS.byteSize();").ok();
1016 }
1017 writeln!(out, " return offset;").ok();
1018 writeln!(out, " }}").ok();
1019 writeln!(out).ok();
1020 }
1021 writeln!(out).ok();
1022 writeln!(
1023 out,
1024 " /** Returns the native HTMHtmVisitorCallbacks struct pointer. */"
1025 )
1026 .ok();
1027 writeln!(out, " MemorySegment callbacksStruct() {{").ok();
1028 writeln!(out, " return struct;").ok();
1029 writeln!(out, " }}").ok();
1030 writeln!(out).ok();
1031
1032 for spec in CALLBACKS {
1034 gen_handle_method(&mut out, spec);
1035 }
1036
1037 writeln!(
1039 out,
1040 " // HTMHtmNodeContext: int32 node_type, char* tag_name, uintptr depth,"
1041 )
1042 .ok();
1043 writeln!(
1044 out,
1045 " // uintptr index_in_parent, char* parent_tag, int32 is_inline"
1046 )
1047 .ok();
1048 writeln!(
1049 out,
1050 " private static final MemoryLayout CTX_LAYOUT = MemoryLayout.structLayout("
1051 )
1052 .ok();
1053 writeln!(out, " ValueLayout.JAVA_INT.withName(\"node_type\"),").ok();
1054 writeln!(out, " MemoryLayout.paddingLayout(4),").ok();
1055 writeln!(out, " ValueLayout.ADDRESS.withName(\"tag_name\"),").ok();
1056 writeln!(out, " ValueLayout.JAVA_LONG.withName(\"depth\"),").ok();
1057 writeln!(out, " ValueLayout.JAVA_LONG.withName(\"index_in_parent\"),").ok();
1058 writeln!(out, " ValueLayout.ADDRESS.withName(\"parent_tag\"),").ok();
1059 writeln!(out, " ValueLayout.JAVA_INT.withName(\"is_inline\"),").ok();
1060 writeln!(out, " MemoryLayout.paddingLayout(4)").ok();
1061 writeln!(out, " );").ok();
1062 writeln!(out).ok();
1063 writeln!(
1064 out,
1065 " private static NodeContext decodeNodeContext(MemorySegment ctxPtr) {{"
1066 )
1067 .ok();
1068 writeln!(out, " var ctx = ctxPtr.reinterpret(CTX_LAYOUT.byteSize());").ok();
1069 writeln!(out, " int nodeType = ctx.get(ValueLayout.JAVA_INT, 0L);").ok();
1070 writeln!(
1071 out,
1072 " var tagNamePtr = ctx.get(ValueLayout.ADDRESS, CTX_OFFSET_TAG_NAME);"
1073 )
1074 .ok();
1075 writeln!(
1076 out,
1077 " String tagName = tagNamePtr.reinterpret(Long.MAX_VALUE).getString(0);"
1078 )
1079 .ok();
1080 writeln!(
1081 out,
1082 " long depth = ctx.get(ValueLayout.JAVA_LONG, CTX_OFFSET_DEPTH);"
1083 )
1084 .ok();
1085 writeln!(
1086 out,
1087 " long indexInParent = ctx.get(ValueLayout.JAVA_LONG, CTX_OFFSET_INDEX_IN_PARENT);"
1088 )
1089 .ok();
1090 writeln!(
1091 out,
1092 " var parentTagPtr = ctx.get(ValueLayout.ADDRESS, CTX_OFFSET_PARENT_TAG);"
1093 )
1094 .ok();
1095 writeln!(
1096 out,
1097 " String parentTag = parentTagPtr.equals(MemorySegment.NULL) ? null"
1098 )
1099 .ok();
1100 writeln!(
1101 out,
1102 " : parentTagPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1103 )
1104 .ok();
1105 writeln!(
1106 out,
1107 " int isInlineRaw = ctx.get(ValueLayout.JAVA_INT, CTX_OFFSET_IS_INLINE);"
1108 )
1109 .ok();
1110 writeln!(
1111 out,
1112 " return new NodeContext(nodeType, tagName, depth, indexInParent, parentTag, isInlineRaw != 0);"
1113 )
1114 .ok();
1115 writeln!(out, " }}").ok();
1116 writeln!(out).ok();
1117
1118 writeln!(
1120 out,
1121 " private static List<String> decodeCells(MemorySegment cellsPtr, long count) {{"
1122 )
1123 .ok();
1124 writeln!(out, " var result = new ArrayList<String>((int) count);").ok();
1125 writeln!(out, " for (long i = 0; i < count; i++) {{").ok();
1126 writeln!(
1127 out,
1128 " var ptr = cellsPtr.get(ValueLayout.ADDRESS, i * ValueLayout.ADDRESS.byteSize());"
1129 )
1130 .ok();
1131 writeln!(
1132 out,
1133 " result.add(ptr.reinterpret(Long.MAX_VALUE).getString(0));"
1134 )
1135 .ok();
1136 writeln!(out, " }}").ok();
1137 writeln!(out, " return result;").ok();
1138 writeln!(out, " }}").ok();
1139 writeln!(out).ok();
1140
1141 writeln!(
1143 out,
1144 " private static int encodeVisitResult(VisitResult result, MemorySegment outCustom, MemorySegment outLen, Arena encArena) {{"
1145 )
1146 .ok();
1147 writeln!(out, " return switch (result) {{").ok();
1148 writeln!(
1149 out,
1150 " case VisitResult.Continue ignored -> VISIT_RESULT_CONTINUE;"
1151 )
1152 .ok();
1153 writeln!(out, " case VisitResult.Skip ignored -> VISIT_RESULT_SKIP;").ok();
1154 writeln!(
1155 out,
1156 " case VisitResult.PreserveHtml ignored -> VISIT_RESULT_PRESERVE_HTML;"
1157 )
1158 .ok();
1159 writeln!(out, " case VisitResult.Custom c -> {{").ok();
1160 writeln!(out, " var buf = encArena.allocateFrom(c.markdown());").ok();
1161 writeln!(out, " outCustom.set(ValueLayout.ADDRESS, 0L, buf);").ok();
1162 writeln!(
1163 out,
1164 " outLen.set(ValueLayout.JAVA_LONG, 0L, (long) c.markdown().getBytes(java.nio.charset.StandardCharsets.UTF_8).length);"
1165 )
1166 .ok();
1167 writeln!(out, " yield VISIT_RESULT_CUSTOM;").ok();
1168 writeln!(out, " }}").ok();
1169 writeln!(out, " case VisitResult.Error e -> {{").ok();
1170 writeln!(out, " var buf = encArena.allocateFrom(e.message());").ok();
1171 writeln!(out, " outCustom.set(ValueLayout.ADDRESS, 0L, buf);").ok();
1172 writeln!(
1173 out,
1174 " outLen.set(ValueLayout.JAVA_LONG, 0L, (long) e.message().getBytes(java.nio.charset.StandardCharsets.UTF_8).length);"
1175 )
1176 .ok();
1177 writeln!(out, " yield VISIT_RESULT_ERROR;").ok();
1178 writeln!(out, " }}").ok();
1179 writeln!(out, " }};").ok();
1180 writeln!(out, " }}").ok();
1181 writeln!(out).ok();
1182
1183 writeln!(out, " @Override").ok();
1184 writeln!(out, " public void close() {{").ok();
1185 writeln!(out, " arena.close();").ok();
1186 writeln!(out, " }}").ok();
1187 writeln!(out, "}}").ok();
1188 out
1189}
1190
1191fn stub_var_name(java_method: &str) -> String {
1198 let mut name = String::with_capacity(5 + java_method.len());
1199 name.push_str("stub");
1200 let mut chars = java_method.chars();
1201 if let Some(first) = chars.next() {
1202 for c in first.to_uppercase() {
1203 name.push(c);
1204 }
1205 name.push_str(chars.as_str());
1206 }
1207 name
1208}
1209
1210fn handle_method_name(java_method: &str) -> String {
1211 let mut name = String::with_capacity(7 + java_method.len());
1213 name.push_str("handle");
1214 let mut chars = java_method.chars();
1215 if let Some(first) = chars.next() {
1216 for c in first.to_uppercase() {
1217 name.push(c);
1218 }
1219 name.push_str(chars.as_str());
1220 }
1221 name
1222}
1223
1224fn iface_param_str(spec: &CallbackSpec) -> String {
1225 let mut params = vec!["NodeContext context".to_string()];
1226 for ep in spec.extra {
1227 params.push(format!("{} {}", ep.java_type, ep.java_name));
1228 }
1229 if spec.has_is_header {
1230 params.push("boolean isHeader".to_string());
1231 }
1232 params.join(", ")
1233}
1234
1235fn callback_descriptor(spec: &CallbackSpec) -> String {
1238 let mut layouts = vec![
1239 "ValueLayout.ADDRESS".to_string(), "ValueLayout.ADDRESS".to_string(), ];
1242 for ep in spec.extra {
1243 for layout in ep.c_layouts {
1244 layouts.push((*layout).to_string());
1245 }
1246 }
1247 if spec.has_is_header {
1248 layouts.push("ValueLayout.JAVA_INT".to_string());
1249 }
1250 layouts.push("ValueLayout.ADDRESS".to_string()); layouts.push("ValueLayout.ADDRESS".to_string()); format!("FunctionDescriptor.of(ValueLayout.JAVA_INT, {})", layouts.join(", "))
1253}
1254
1255fn callback_method_type(spec: &CallbackSpec) -> String {
1257 let mut types = vec![
1258 "MemorySegment.class".to_string(), "MemorySegment.class".to_string(), ];
1261 for ep in spec.extra {
1262 for layout in ep.c_layouts {
1263 types.push(layout_to_java_class(layout).to_string());
1264 }
1265 }
1266 if spec.has_is_header {
1267 types.push("int.class".to_string());
1268 }
1269 types.push("MemorySegment.class".to_string()); types.push("MemorySegment.class".to_string()); format!("MethodType.methodType(int.class, {})", types.join(", "))
1272}
1273
1274fn layout_to_java_class(layout: &str) -> &'static str {
1275 match layout {
1276 "ValueLayout.ADDRESS" => "MemorySegment.class",
1277 "ValueLayout.JAVA_INT" => "int.class",
1278 "ValueLayout.JAVA_LONG" => "long.class",
1279 _ => "long.class",
1280 }
1281}
1282
1283fn gen_handle_method(out: &mut String, spec: &CallbackSpec) {
1285 let mut params = vec!["MemorySegment ctx".to_string(), "MemorySegment userData".to_string()];
1287 for ep in spec.extra {
1288 for (c_idx, layout) in ep.c_layouts.iter().enumerate() {
1289 let java_ptype = match *layout {
1290 "ValueLayout.JAVA_INT" => "int",
1291 "ValueLayout.JAVA_LONG" => "long",
1292 _ => "MemorySegment",
1293 };
1294 params.push(format!("{java_ptype} {}", raw_var_name(ep.java_name, c_idx)));
1295 }
1296 }
1297 if spec.has_is_header {
1298 params.push("int isHeader".to_string());
1299 }
1300 params.push("MemorySegment outCustom".to_string());
1301 params.push("MemorySegment outLen".to_string());
1302
1303 writeln!(
1304 out,
1305 " int {}({}) {{",
1306 handle_method_name(spec.java_method),
1307 params.join(", ")
1308 )
1309 .ok();
1310 writeln!(out, " try (var encArena = Arena.ofConfined()) {{").ok();
1311 writeln!(out, " var context = decodeNodeContext(ctx);").ok();
1312
1313 for ep in spec.extra {
1315 let mut decode = ep.decode.to_string();
1316 for (c_idx, _) in ep.c_layouts.iter().enumerate() {
1317 let placeholder = format!("raw_{}_{}", ep.java_name, c_idx);
1318 let var = raw_var_name(ep.java_name, c_idx);
1319 decode = decode.replace(&placeholder, &var);
1320 }
1321 writeln!(out, " var {} = {};", ep.java_name, decode).ok();
1322 }
1323 if spec.has_is_header {
1324 writeln!(out, " var goIsHeader = isHeader != 0;").ok();
1325 }
1326
1327 let mut call_args = vec!["context".to_string()];
1329 for ep in spec.extra {
1330 call_args.push(ep.java_name.to_string());
1331 }
1332 if spec.has_is_header {
1333 call_args.push("goIsHeader".to_string());
1334 }
1335
1336 writeln!(
1337 out,
1338 " var result = visitor.{}({});",
1339 spec.java_method,
1340 call_args.join(", ")
1341 )
1342 .ok();
1343 writeln!(
1344 out,
1345 " return encodeVisitResult(result, outCustom, outLen, encArena);"
1346 )
1347 .ok();
1348 writeln!(out, " }} catch (Throwable ignored) {{").ok();
1349 writeln!(out, " return 0;").ok();
1350 writeln!(out, " }}").ok();
1351 writeln!(out, " }}").ok();
1352 writeln!(out).ok();
1353}
1354
1355fn raw_var_name(java_name: &str, c_idx: usize) -> String {
1356 let mut name = String::with_capacity(4 + java_name.len() + 2);
1359 name.push_str("raw");
1360 let mut chars = java_name.chars();
1361 if let Some(first) = chars.next() {
1362 for c in first.to_uppercase() {
1363 name.push(c);
1364 }
1365 name.push_str(chars.as_str());
1366 }
1367 name.push_str(&c_idx.to_string());
1368 name
1369}