authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-06 14:21:22-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-10 17:51:06-07:00
log0b1b3f02252ec92dc361480d7659d28eb685e610
treeec2d1559c6e8d734eab6090c2345f41dc6b1eb68
parent356e653f9bd52d4aebf8ef380a4dcb9de88caf06

upstream new autodocs implementation


9 files changed, 5832 insertions(+), 6172 deletions(-)

lib/docs/index.html+225-1081
......@@ -1,634 +1,168 @@
11<!doctype html>
2<html lang="en">
2<html>
33 <head>
44 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <title>Documentation - Zig</title>
7 <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg==">
5 <title>Zig Documentation</title>
86 <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTMgMTQwIj48ZyBmaWxsPSIjRjdBNDFEIj48Zz48cG9seWdvbiBwb2ludHM9IjQ2LDIyIDI4LDQ0IDE5LDMwIi8+PHBvbHlnb24gcG9pbnRzPSI0NiwyMiAzMywzMyAyOCw0NCAyMiw0NCAyMiw5NSAzMSw5NSAyMCwxMDAgMTIsMTE3IDAsMTE3IDAsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMzEsOTUgMTIsMTE3IDQsMTA2Ii8+PC9nPjxnPjxwb2x5Z29uIHBvaW50cz0iNTYsMjIgNjIsMzYgMzcsNDQiLz48cG9seWdvbiBwb2ludHM9IjU2LDIyIDExMSwyMiAxMTEsNDQgMzcsNDQgNTYsMzIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTE2LDk1IDk3LDExNyA5MCwxMDQiLz48cG9seWdvbiBwb2ludHM9IjExNiw5NSAxMDAsMTA0IDk3LDExNyA0MiwxMTcgNDIsOTUiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTUwLDAgNTIsMTE3IDMsMTQwIDEwMSwyMiIvPjwvZz48Zz48cG9seWdvbiBwb2ludHM9IjE0MSwyMiAxNDAsNDAgMTIyLDQ1Ii8+PHBvbHlnb24gcG9pbnRzPSIxNTMsMjIgMTUzLDExNyAxMDYsMTE3IDEyMCwxMDUgMTI1LDk1IDEzMSw5NSAxMzEsNDUgMTIyLDQ1IDEzMiwzNiAxNDEsMjIiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxwb2x5Z29uIHBvaW50cz0iMTI1LDk1IDEzMCwxMTAgMTA2LDExNyIvPjwvZz48L2c+PC9zdmc+">
9 <style>
10 :root {
11 font-size: 1em;
12 --ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
13 --mono: "Source Code Pro", monospace;
14 --tx-color: #141414;
15 --bg-color: #ffffff;
16 --link-color: #2A6286;
17 --sidebar-sh-color: rgba(0, 0, 0, 0.09);
18 --sidebar-mod-bg-color: #f1f1f1;
19 --sidebar-modlnk-tx-color: #141414;
20 --sidebar-modlnk-tx-color-hover: #fff;
21 --sidebar-modlnk-tx-color-active: #000;
22 --sidebar-modlnk-bg-color: transparent;
23 --sidebar-modlnk-bg-color-hover: #555;
24 --sidebar-modlnk-bg-color-active: #FFBB4D;
25 --search-bg-color: #f3f3f3;
26 --search-bg-color-focus: #ffffff;
27 --search-sh-color: rgba(0, 0, 0, 0.18);
28 --search-other-results-color: rgb(100, 100, 100);
29 --modal-sh-color: rgba(0, 0, 0, 0.75);
30 --modal-bg-color: #aaa;
31 --warning-popover-bg-color: #ff4747;
7 <style type="text/css">
8 body {
9 font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;
10 color: #000000;
3211 }
33
34 html, body { margin: 0; padding: 0; height: 100%; }
35
36 a {
37 text-decoration: none;
38 }
39
40 pre a {
41 text-decoration: underline;
42 color: unset;
43 }
44
45 a:hover {
46 text-decoration: underline;
47 }
48
49 a[href^="src/"] {
50 border-bottom: 2px dotted var(--tx-color);
51 }
52
5312 .hidden {
54 display: none !important;
55 }
56
57 /* layout */
58 .canvas {
59 display:flex;
60 flex-direction: column;
61 width: 100vw;
62 height: 100vh;
63 margin: 0;
64 padding: 0;
65 font-family: var(--ui);
66 color: var(--tx-color);
67 background-color: var(--bg-color);
68 }
69
70 .flex-main {
71 display: flex;
72 flex-direction: column;
73 justify-content: center;
74
75 height: 100%;
76 overflow: hidden;
77
78
79 z-index: 100;
80 }
81
82 .flex-horizontal {
83 display: flex;
84 flex-direction: row;
85 align-items: center;
86 }
87
88 .flex-filler {
89 flex-grow: 1;
90 flex-shrink: 1;
91 }
92
93 .flex-left {
94 overflow: auto;
95 -webkit-overflow-scrolling: touch;
96 overflow-wrap: break-word;
97 flex-shrink: 0;
98 flex-grow: 0;
99 margin-right: 0.5rem;
100
101 z-index: 300;
102 }
103
104 .flex-right {
105 display: flex;
106 flex-direction: column;
107 overflow: auto;
108 -webkit-overflow-scrolling: touch;
109 flex-grow: 1;
110 flex-shrink: 1;
111
112 z-index: 200;
113 }
114
115 .flex-right > .wrap {
116 width: 60rem;
117 max-width: 85vw;
118 flex-shrink: 1;
119 }
120
121 .modal-container {
122 z-index: 400;
123 }
124
125 .understated {
126 color: var(--search-other-results-color);
127 }
128
129 .sidebar {
130 background-color: var(--bg-color);
131 box-shadow: 0 0 1rem var(--sidebar-sh-color);
132 clip-path: inset(0px -15px 0px 0px);
133 }
134
135 .logo {
136 margin: 0.5rem;
137 width: 130px;
13 display: none;
13814 }
139
140 .logo > svg {
141 display: block;
15 a {
16 color: #2A6286;
14217 }
143
144 ul.guides-api-switch {
145 display: flex;
146 flex-direction: row;
147 justify-content: center;
148 text-align: center;
149 list-style-type: none;
18 pre{
19 font-family:"Source Code Pro",monospace;
20 font-size:1em;
21 background-color:#F5F5F5;
22 padding: 1em;
15023 margin: 0;
151 padding: 0;
152 }
153
154 .guides-api-switch a {
155 display: block;
156 padding: 0.5rem 1rem;
157 color: var(--sidebar-modlnk-tx-color);
158 background-color: var(--sidebar-modlnk-bg-color);
159 border: 1px solid var(--tx-color);
24 overflow-x: auto;
16025 }
161
162
163 #ApiSwitch {
164 border-radius: 10px 0 0 10px;
26 code {
27 font-family:"Source Code Pro",monospace;
28 font-size:1em;
16529 }
166
167 #guideSwitch {
168 border-radius: 0 10px 10px 0;
30 code a {
31 color: #000000;
16932 }
170
171
172 #ApiSwitch:hover, #guideSwitch:hover {
173 text-decoration: none;
33 #listFields > div {
34 margin-bottom: 1em;
17435 }
175
176 #ApiSwitch:hover:not(.active), #guideSwitch:hover:not(.active) {
177 color: var(--sidebar-modlnk-tx-color-hover);
178 background-color: var(--sidebar-modlnk-bg-color-hover);
36 .fieldDocs {
37 border: 1px solid #F5F5F5;
38 border-top: 0px;
39 padding: 1px 1em;
17940 }
18041
181 .guides-api-switch .active {
182 color: var(--sidebar-modlnk-tx-color-active);
183 background-color: var(--sidebar-modlnk-bg-color-active);
184 }
18542
186 #guidesMenu {
187 height: 100%;
188 overflow: hidden;
189 width: 30%;
190 margin-right: 2rem;
191 }
192
193 #activeGuide {
194 overflow-y: scroll;
195 height: 100%;
196 width: 70%;
197 padding-right: 1rem;
198 }
199 .sidebar h2 {
200 margin: 0.5rem;
201 padding: 0;
202 font-size: 1.2rem;
43 #navWrap {
44 float: left;
45 width: 47em;
46 margin-left: 1em;
20347 }
20448
205 .sidebar h2 > span {
206 border-bottom: 0.125rem dotted var(--tx-color);
49 nav {
50 width: 10em;
51 float: left;
20752 }
208
209 .sidebar .modules {
210 list-style-type: none;
53 nav h2 {
54 font-size: 1.2em;
55 text-decoration: underline;
21156 margin: 0;
212 padding: 0;
213 background-color: var(--sidebar-mod-bg-color);
214 }
215
216 .sidebar .modules > li > a {
217 display: block;
218 padding: 0.5rem 1rem;
219 color: var(--sidebar-modlnk-tx-color);
220 background-color: var(--sidebar-modlnk-bg-color);
221 text-decoration: none;
222 }
223
224 .sidebar .modules > li > a:hover {
225 color: var(--sidebar-modlnk-tx-color-hover);
226 background-color: var(--sidebar-modlnk-bg-color-hover);
227 }
228
229 .sidebar .modules > li > a.active {
230 color: var(--sidebar-modlnk-tx-color-active);
231 background-color: var(--sidebar-modlnk-bg-color-active);
232 }
233
234 .sidebar p.str {
235 margin: 0.5rem;
236 font-family: var(--mono);
237 }
238
239 #guideTocList {
240 padding: 0 1rem;
57 padding: 0.5em 0;
58 text-align: center;
24159 }
242
243 #guideTocList ul {
244 padding-left: 1rem;
60 nav p {
24561 margin: 0;
246 }
247
248 #guides {
249 box-sizing: border-box;
250 font-size: 1rem;
251 background-color: var(--bg-color);
252 overflow-wrap: break-word;
253 }
254
255 /* docs section */
256 .docs {
257 flex-grow: 2;
258 padding: 0rem 0.7rem 0rem 1.4rem;
259 font-size: 1rem;
260 background-color: var(--bg-color);
261 overflow-wrap: break-word;
262 height: 100%;
263 overflow-y: scroll;
264 }
265
266 #noDocsNamespaces {
267 margin: 1rem;
268 border: 1px solid var(--search-other-results-color);
269 padding: 0.5rem 1rem;
270 background-color: var(--help-bg-color);
271 }
272
273 .column {
274 flex-basis: 0;
275 flex-grow: 1;
276 min-width: min(24rem, 90%);
277 }
278
279
280 .search-container {
281 flex-grow: 2;
282 }
283
284 .search {
285 width: 100%;
286 padding: 0.5rem;
287 font-family: var(--ui);
288 font-size: 1rem;
289 color: var(--tx-color);
290 background-color: var(--search-bg-color);
291 border-top: 0;
292 border-left: 0;
293 border-right: 0;
294 border-bottom-width: 0.125rem;
295 border-bottom-style: solid;
296 border-bottom-color: var(--tx-color);
297 outline: none;
298 transition: border-bottom-color 0.35s, background 0.35s, box-shadow 0.35s;
299 border-radius: 0;
300 -webkit-appearance: none;
301 }
302
303 .search:focus {
304 background-color: var(--search-bg-color-focus);
305 border-bottom-color: #ffbb4d;
306 box-shadow: 0 0.3em 1em 0.125em var(--search-sh-color);
307 }
308
309 #searchPlaceholder {
310 position: absolute;
311 pointer-events: none;
312 height: 100%;
313 display: flex;
314 align-items: center;
315 padding-left: 5px;
316 }
317
318 #searchPlaceholderTextMobile {
319 display: none;
320 }
321
322 #dotsPopover:before {
323 position: absolute;
324 content: "";
325 left: 20px;
326 top: -8px;
327 border-style: solid;
328 border-width: 0 10px 10px 10px;
329 border-color: transparent transparent var(--warning-popover-bg-color) transparent;
330 transition-duration: 0.3s;
331 transition-property: transform;
332 z-index: 10;
333 }
334
335 #dotsPopover {
336 position: absolute;
337 opacity: 0;
338 visibility: hidden;
339 background-color: var(--warning-popover-bg-color);
340 border-radius: 10px;
341 left: 10px;
342 transform: translate(0, -20px);
343 padding: 0.5rem 1rem;
344 box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
345 transition: all 0.5s cubic-bezier(0.75, -0.02, 0.2, 0.97);
346 z-index: 20;
347 }
348
349 #dotsPopover.active {
350 opacity: 1;
351 visibility: visible;
352 transform: translate(0, 0);
353 }
354
355 #sectSearchResults {
356 box-sizing: border-box;
357 }
358
359 #searchHelp summary {
360 color: red;
361 list-style-position: outside;
362 }
363
364 #searchHelp summary.normal {
365 color: var(--search-other-results-color);
366 transition: all 0.5s cubic-bezier(0.75, -0.02, 0.2, 0.97);
367 }
368
369 #searchHelp div {
370 background-color: var(--modal-bg-color);
371 padding: 0.5rem 1rem;
372 }
373
374 .other-results {
375 line-height: 1em;
376 position: relative;
377 outline: 0;
378 border: 0;
379 color: var(--search-other-results-color);
62 padding: 0;
38063 text-align: center;
381 height: 1.5em;
382 opacity: .5;
38364 }
384 .other-results:before {
385 content: '';
386 background: var(--search-other-results-color);
387 position: absolute;
388 left: 0;
389 top: 50%;
390 width: 100%;
391 height: 1px;
65 section {
66 clear: both;
67 padding-top: 1em;
39268 }
393
394 .other-results:after {
395 content: "other results";
396 position: relative;
397 display: inline-block;
398 padding: 0 .5em;
399 line-height: 1.5em;
400 color: var(--search-other-results-color);
401 background-color: var(--bg-color);
402 }
403
404
405 a {
406 color: var(--link-color);
69 section h1 {
70 border-bottom: 1px dashed;
71 margin: 0 0;
40772 }
408
409 p {
410 margin: 0.8rem 0;
411 }
412
413 pre {
414 font-family: var(--mono);
415 font-size: 1em;
416 background-color: #F5F5F5;
417 padding: 1em;
418 overflow-x: auto;
419 }
420
421 pre.inline {
422 background-color: var(--bg-color);
423 padding: 0;
424 display: inline;
425 }
426
427
428 code {
429 font-family: var(--mono);
430 font-size: 1em;
431 }
432
433 h1 {
434 font-size: 1.4em;
435 margin: 0.8em 0;
436 padding: 0;
437 border-bottom: 0.0625rem dashed;
438 }
439
440 h2 {
73 section h2 {
44174 font-size: 1.3em;
44275 margin: 0.5em 0;
44376 padding: 0;
444 border-bottom: 0.0625rem solid;
77 border-bottom: 1px solid;
44578 }
446 .listNav {
79 #listNav {
44780 list-style-type: none;
448 margin: 0;
81 margin: 0.5em 0 0 0;
44982 padding: 0;
45083 overflow: hidden;
45184 background-color: #f1f1f1;
452 display: flex;
453 flex-direction: row;
45485 }
455 .listNav li {
456
86 #listNav li {
87 float:left;
45788 }
458 .listNav li a {
89 #listNav li a {
45990 display: block;
46091 color: #000;
46192 text-align: center;
46293 padding: .5em .8em;
46394 text-decoration: none;
46495 }
465 .listNav li a:hover {
96 #listNav li a:hover {
46697 background-color: #555;
46798 color: #fff;
46899 }
469 .listNav li a.active {
100 #listNav li a.active {
470101 background-color: #FFBB4D;
471102 color: #000;
472103 }
473104
474 #listSearchResults li.selected {
475 background-color: #93e196;
476 }
477
478 #tableFnErrors dt {
479 font-weight: bold;
480 }
481
482 .expand[open] .sum-less {
483 display: none;
484 }
485
486 .expand[open] .sum-more {
487 display: block;
488 }
489
490 .expand .sum-more {
491 display: none;
492 }
493
494 .expand {
495 position: relative;
496 }
497
498 .expand .button:before {
499 content: "[+] ";
500 font-family: var(--mono);
501 color: var(--link-color);
502 position: sticky;
503 float: left;
504 top: 0.5em;
505 right: -16px;
506 z-index: 1;
507 margin-left: -2em;
508 pointer-events: all;
509 cursor: pointer;
105 #logo {
106 width: 8em;
107 padding: 0.5em 1em;
510108 }
511109
512 .expand[open] .button:before {
513 content: "[-] ";
514 }
515
516 .examples {
517 list-style-type: none;
518 margin: 0;
519 padding: 0;
520 }
521 .examples li {
522 padding: 0.5em 0;
523 white-space: nowrap;
524 overflow-x: auto;
525 }
526
527 .docs td {
528 margin: 0;
529 padding: 0.5em;
530 max-width: 27em;
531 text-overflow: ellipsis;
532 overflow-x: hidden;
533 }
534
535 .fieldHasDocs {
536 margin-bottom: 0;
537 }
538
539 .fieldDocs {
540 border: 1px solid #F5F5F5;
541 border-top: 0px;
542 padding: 1px 1em;
110 #search {
111 width: 100%;
543112 }
544113
545 /* modals */
546 .modal-container {
547 display: flex;
548 width: 100%;
549 height: 100%;
114 #helpDialog {
115 width: 21em;
116 height: 21em;
550117 position: fixed;
551118 top: 0;
552119 left: 0;
553 justify-content: center;
554 align-items: center;
555 background-color: rgba(0, 0, 0, 0.15);
556 backdrop-filter: blur(0.3em);
557 }
558
559 .modal-container > .modal {
560 max-width: 97vw;
561 max-height: 97vh;
562 overflow: auto;
563 font-size: 1rem;
120 background-color: #333;
564121 color: #fff;
565 background-color: var(--modal-bg-color);
566 border: 0.125rem solid #000;
567 box-shadow: 0 0.5rem 2.5rem 0.3rem var(--modal-sh-color);
122 border: 1px solid #fff;
568123 }
569
570 .modal-container h1 {
571 margin: 0.75em 2.5em 1em 2.5em;
572 font-size: 1.5em;
124 #helpDialog h1 {
573125 text-align: center;
126 font-size: 1.5em;
574127 }
575
576 .modal-container dt, .modal-container dd {
128 #helpDialog dt, #helpDialog dd {
577129 display: inline;
578130 margin: 0 0.2em;
579131 }
580
581 .modal-container dl {
582 margin-left: 0.5em;
583 margin-right: 0.5em;
584 }
585
586 .prefs-list {
587 list-style: none;
588 padding: 0;
589 margin-left: 0.5em;
590 margin-right: 0.5em;
591 }
592
593132 kbd {
594 display: inline-block;
595 padding: 0.3em 0.2em;
596 font-family: var(--mono);
597 font-size: 1em;
598 line-height: 0.8em;
599 vertical-align: middle;
600133 color: #000;
601134 background-color: #fafbfc;
602135 border-color: #d1d5da;
603136 border-bottom-color: #c6cbd1;
604 border: solid 0.0625em;
605 border-radius: 0.1875em;
606 box-shadow: inset 0 -0.2em 0 #c6cbd1;
137 box-shadow-color: #c6cbd1;
138 display: inline-block;
139 padding: 0.3em 0.2em;
140 font: 1.2em monospace;
141 line-height: 0.8em;
142 vertical-align: middle;
143 border: solid 1px;
144 border-radius: 3px;
145 box-shadow: inset 0 -1px 0;
607146 cursor: default;
608147 }
609
610 #listFns > div {
611 padding-bottom: 10px;
148
149 #listSearchResults li.selected {
150 background-color: #93e196;
612151 }
613152
614 #listFns dt {
615 font-family: var(--mono);
616 display: flex;
617 flex-direction: colunm;
618 justify-content: space-between;
153 #tableFnErrors dt {
154 font-weight: bold;
619155 }
620
621 #listFns dt .fnSignature {
622 overflow-x: hidden;
623 white-space: nowrap;
156
157 td {
158 vertical-align: top;
159 margin: 0;
160 padding: 0.5em;
161 max-width: 20em;
624162 text-overflow: ellipsis;
625 }
626
627 .argBreaker {
628 display: none;
163 overflow-x: hidden;
629164 }
630165
631 /* tokens */
632166 .tok-kw {
633167 color: #333;
634168 font-weight: bold;
......@@ -657,51 +191,35 @@
657191 color: #458;
658192 font-weight: bold;
659193 }
660 .tok-decl-ref {
661 color: #0086b3;
662 font-weight: bold;
663 }
664194
665 /* dark mode */
666195 @media (prefers-color-scheme: dark) {
667 :root {
668 --tx-color: #bbb;
669 --bg-color: #111;
670 --link-color: #88f;
671 --sidebar-sh-color: rgba(128, 128, 128, 0.5);
672 --sidebar-mod-bg-color: #333;
673 --sidebar-modlnk-tx-color: #fff;
674 --sidebar-modlnk-tx-color-hover: #fff;
675 --sidebar-modlnk-tx-color-active: #000;
676 --sidebar-modlnk-bg-color: transparent;
677 --sidebar-modlnk-bg-color-hover: #555;
678 --sidebar-modlnk-bg-color-active: #FFBB4D;
679 --search-bg-color: #3c3c3c;
680 --search-bg-color-focus: #000;
681 --search-sh-color: rgba(255, 255, 255, 0.28);
682 --search-other-results-color: rgba(255, 255, 255, 0.28);
683 --modal-sh-color: rgba(142, 142, 142, 0.5);
684 --modal-bg-color: #333;
685 --warning-popover-bg-color: #600000;
196 body{
197 background-color: #111;
198 color: #bbb;
686199 }
687
688 pre {
200 a {
201 color: #88f;
202 }
203 code a {
204 color: #bbb;
205 }
206 pre{
689207 background-color:#2A2A2A;
690208 }
691209 .fieldDocs {
692210 border-color:#2A2A2A;
693211 }
694 .listNav {
212 #listNav {
695213 background-color: #333;
696214 }
697 .listNav li a {
215 #listNav li a {
698216 color: #fff;
699217 }
700 .listNav li a:hover {
218 #listNav li a:hover {
701219 background-color: #555;
702220 color: #fff;
703221 }
704 .listNav li a.active {
222 #listNav li a.active {
705223 background-color: #FFBB4D;
706224 color: #000;
707225 }
......@@ -735,511 +253,137 @@
735253 .tok-type {
736254 color: #68f;
737255 }
738 .tok-decl-ref {
739 color: lightblue;
740 }
741 }
742
743 @media only screen and (max-width: 750px) {
744 .canvas {
745 overflow: auto;
746 }
747 .flex-main {
748 flex-direction: column;
749 }
750 .sidebar {
751 min-width: calc(100vw - 2.8rem);
752 padding-left: 1.4rem;
753 padding-right: 1.4rem;
754 }
755 .flex-main > .flex-filler {
756 display: none;
757 }
758 .flex-main > .flex-right > .flex-filler {
759 display: none;
760 }
761 .flex-main > .flex-right > .wrap {
762 max-width: 100vw;
763 }
764 .flex-main > .flex-right > .wrap > .docs {
765 padding-right: 1.4rem;
766 background: transparent;
767 }
768 .modules {
769 display: flex;
770 flex-wrap: wrap;
771 }
772 .table-container table {
773 display: flex;
774 flex-direction: column;
775 }
776 .table-container tr {
777 display: flex;
778 flex-direction: column;
779 }
780 .examples {
781 overflow-x: scroll;
782 -webkit-overflow-scrolling: touch;
783 max-width: 100vw;
784 margin-left: -1.4rem;
785 margin-right: -1.4rem;
786 }
787 .examples li {
788 width: max-content;
789 padding-left: 1.4rem;
790 padding-right: 1.4rem;
791 }
792 .mobile-scroll-container {
793 overflow-x: scroll;
794 -webkit-overflow-scrolling: touch;
795 margin-left: -1.4rem;
796 margin-right: -1.4rem;
797 max-width: 100vw;
798 }
799 .mobile-scroll-container > .scroll-item {
800 margin-left: 1.4rem;
801 margin-right: 1.4rem;
802 box-sizing: border-box;
803 width: max-content;
804 display: inline-block;
805 min-width: calc(100% - 2.8rem);
806 }
807 #searchPlaceholderText {
808 display: none;
809 }
810 #searchPlaceholderTextMobile {
811 display: inline;
812 }
813 }
814 .banner {
815 background-color: orange;
816 text-align: center;
817 color: black;
818 padding: 5px 5px;
819 }
820 .banner a {
821 color: black;
822 text-decoration: underline;
823 }
824
825 </style>
826
827 <style>
828 pre {
829 --zig-keyword: #333;
830 --zig-builtin: #0086b3;
831 --zig-identifier: black;
832 --zig-decl-identifier: #0086b3;
833 --zig-string-literal: #d14;
834 --zig-type: #458;
835 --zig-fn: #900;
836 }
837
838 @media (prefers-color-scheme: dark) {
839 pre {
840 --zig-keyword: #eee;
841 --zig-builtin: #ff894c;
842 --zig-identifier: #bbbbbb;
843 --zig-decl-identifier: lightblue;
844 --zig-string-literal: #2e5;
845 --zig-type: #68f;
846 --zig-fn: #e33;
847 }
848256 }
849
850 .zig_keyword_addrspace,
851 .zig_keyword_align,
852 .zig_keyword_and,
853 .zig_keyword_asm,
854 .zig_keyword_async,
855 .zig_keyword_await,
856 .zig_keyword_break,
857 .zig_keyword_catch,
858 .zig_keyword_comptime,
859 .zig_keyword_const,
860 .zig_keyword_continue,
861 .zig_keyword_defer,
862 .zig_keyword_else,
863 .zig_keyword_enum,
864 .zig_keyword_errdefer,
865 .zig_keyword_error,
866 .zig_keyword_export,
867 .zig_keyword_extern,
868 .zig_keyword_for,
869 .zig_keyword_if,
870 .zig_keyword_inline,
871 .zig_keyword_noalias,
872 .zig_keyword_noinline,
873 .zig_keyword_nosuspend,
874 .zig_keyword_opaque,
875 .zig_keyword_or,
876 .zig_keyword_orelse,
877 .zig_keyword_packed,
878 .zig_keyword_anyframe,
879 .zig_keyword_pub,
880 .zig_keyword_resume,
881 .zig_keyword_return,
882 .zig_keyword_linksection,
883 .zig_keyword_callconv,
884 .zig_keyword_struct,
885 .zig_keyword_suspend,
886 .zig_keyword_switch,
887 .zig_keyword_test,
888 .zig_keyword_threadlocal,
889 .zig_keyword_try,
890 .zig_keyword_union,
891 .zig_keyword_unreachable,
892 .zig_keyword_usingnamespace,
893 .zig_keyword_var,
894 .zig_keyword_volatile,
895 .zig_keyword_allowzero,
896 .zig_keyword_while,
897 .zig_keyword_anytype,
898 .zig_keyword_fn
899 {
900 color: var(--zig-keyword);
901 font-weight: bold;
902 }
903
904
905 .zig_string_literal,
906 .zig_multiline_string_literal_line,
907 .zig_char_literal
908 {
909 color: var(--zig-string-literal);
910 }
911
912 .zig_builtin
913 {
914 color: var(--zig-builtin);
915 }
916
917 .zig_doc_comment,
918 .zig_container_doc_comment,
919 .zig_line_comment {
920 color: #545454;
921 font-style: italic;
922 }
923
924 .zig_identifier {
925 color: var(--zig-identifier);
926 font-weight: bold;
927 }
928
929 .zig_decl_identifier {
930 color: var(--zig-decl-identifier);
931 font-weight: bold;
932 }
933
934 .zig_number_literal,
935 .zig_special {
936 color: #ff8080;
937 }
938
939 .zig_type {
940 color: var(--zig-type);
941 font-weight: bold;
942 }
943
944 .zig_fn {
945 color: var(--zig-fn);
946 font-weight: bold;
947 }
948
949257 </style>
950258 </head>
951 <body class="canvas">
952 <div id="banner" class="banner">
953 This is a beta autodoc build; expect bugs and missing information.
954 <a href="https://github.com/ziglang/zig/wiki/How-to-contribute-to-Autodoc">Report an Issue</a>,
955 <a href="https://github.com/ziglang/zig/wiki/How-to-contribute-to-Autodoc">Contribute</a>,
956 <a href="https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code">Learn more about stdlib source code</a>.
957 </div>
958 <div id="main" class="flex-main">
959 <div class="flex-horizontal" style="justify-content: center; padding: 0 0.5rem;">
960 <div class="flex-left">
961 <div class="logo">
962 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140">
963 <g fill="#F7A41D">
964 <g>
965 <polygon points="46,22 28,44 19,30"/>
966 <polygon points="46,22 33,33 28,44 22,44 22,95 31,95 20,100 12,117 0,117 0,22" shape-rendering="crispEdges"/>
967 <polygon points="31,95 12,117 4,106"/>
968 </g>
969 <g>
970 <polygon points="56,22 62,36 37,44"/>
971 <polygon points="56,22 111,22 111,44 37,44 56,32" shape-rendering="crispEdges"/>
972 <polygon points="116,95 97,117 90,104"/>
973 <polygon points="116,95 100,104 97,117 42,117 42,95" shape-rendering="crispEdges"/>
974 <polygon points="150,0 52,117 3,140 101,22"/>
975 </g>
976 <g>
977 <polygon points="141,22 140,40 122,45"/>
978 <polygon points="153,22 153,117 106,117 120,105 125,95 131,95 131,45 122,45 132,36 141,22" shape-rendering="crispEdges"/>
979 <polygon points="125,95 130,110 106,117"/>
980 </g>
981 </g>
982 <style>
983 #text { fill: #121212 }
984 @media (prefers-color-scheme: dark) { #text { fill: #f2f2f2 } }
985 </style>
986 <g id="text">
987 <g>
988 <polygon points="260,22 260,37 229,40 177,40 177,22" shape-rendering="crispEdges"/>
989 <polygon points="260,37 207,99 207,103 176,103 229,40 229,37"/>
990 <polygon points="261,99 261,117 176,117 176,103 206,99" shape-rendering="crispEdges"/>
991 </g>
992 <rect x="272" y="22" shape-rendering="crispEdges" width="22" height="95"/>
993 <g>
994 <polygon points="394,67 394,106 376,106 376,81 360,70 346,67" shape-rendering="crispEdges"/>
995 <polygon points="360,68 376,81 346,67"/>
996 <path d="M394,106c-10.2,7.3-24,12-37.7,12c-29,0-51.1-20.8-51.1-48.3c0-27.3,22.5-48.1,52-48.1 c14.3,0,29.2,5.5,38.9,14l-13,15c-7.1-6.3-16.8-10-25.9-10c-17,0-30.2,12.9-30.2,29.5c0,16.8,13.3,29.6,30.3,29.6 c5.7,0,12.8-2.3,19-5.5L394,106z"/>
997 </g>
998 </g>
999 </svg>
1000 </div>
1001 <div id="sectGuideApiSwitch">
1002 <ul class="guides-api-switch">
1003 <li><a id="ApiSwitch" class="active" href="#A;">API</a></li>
1004 <li><a id="guideSwitch" class="" href="#G;">Guides</a></li>
1005 </ul>
1006 </div>
1007 </div>
1008 <div class="flex-right" style="padding-top: 0.5rem;overflow:visible;">
1009 <div class="search-container" style="position:relative;">
1010 <div id="searchPlaceholder">
1011 <span id="searchPlaceholderText"><!-- populated by setPrefSlashSearch --></span>
1012 <span id="searchPlaceholderTextMobile">Search</span>
1013 </div>
1014 <input type="search" class="search" id="search" autocomplete="off" spellcheck="false" disabled>
1015 <div id="dotsPopover">
1016 Use spaces instead of dots. See $resource for more info.
1017 </div>
1018 </div>
1019 <div id="sectNavAPI" style="margin-top: 0.5rem;"><ul id="listNavAPI" class="listNav"></ul></div>
1020 <div id="sectNavGuides" class="hidden" style="margin-top: 0.5rem">
1021 <ul id="listNavGuides" class="listNav">
1022 <li>
1023 <a href="#G;" class="active">Index</a>
1024 </li>
1025 <li style="flex-grow:1;">
1026 <a href="#G;" class="active" onclick="scrollGuidesTop(event);"></a>
1027 </li>
1028 </ul>
1029 </div>
1030 </div>
1031 </div>
1032 <div style="height:100%; overflow:hidden;">
1033 <div id="sectSearchResults" class="docs hidden">
1034 <details id="searchHelp">
1035 <summary id="searchHelpSummary" class="normal">How to search effectively</summary>
1036 <div>
1037 <h2>How To Search Effectively</h2>
1038 <h3>Matching</h3>
1039 <ul>
1040 <li>Search is case-insensitive by default.</li>
1041 <li>Using uppercase letters in your query will make the search
1042 case-sensitive.</li>
1043 <li>Given <code>ArrayListUnmanaged</code>:
1044 <ul>
1045 <li>the following search terms (and their prefixes) will match:
1046 <ul>
1047 <li><code>array</code></li>
1048 <li><code>list</code></li>
1049 <li><code>unmanaged</code></li>
1050 </ul>
1051 </li>
1052 <li>the following search terms will <b>NOT</b> match:
1053 <ul>
1054 <li><code>stun</code></li>
1055 <li><code>ray</code></li>
1056 <li><code>managed</code></li>
1057 </ul>
1058 </li>
1059 </ul>
1060 </li>
1061 <li>More precisely, the search system is based on a Radix Tree. The Radix Tree contains full decl names plus some suffixes, split by following the official style guide (e.g. <code>HashMapUnmanaged</code> also produces <code>MapUnmanaged</code> and <code>Unmanaged</code>, same with snake_case and camelCase names). </li>
1062 </ul>
1063
1064 <h3>Multiple terms</h3>
1065
1066 <ul>
1067 <li>When a search query contains multiple terms, order doesn't matter when
1068 all terms match within a single decl name (e.g. "map auto" will match <code>AutoHashMap</code>).</li>
1069 <li>Query term order does matter when matching different decls alognside
1070 a path (e.g. "js parse" matching <code>std.json.parse</code>), in which
1071 case the order of the terms will determine whether the match goes above or
1072 below the "other results" line.</li>
1073 <li>As an example, "fs create" will put above the line all things related to the creation of files and directories inside of `std.fs`, while still showing (but below the line) matches from `std.Bulild`.</li>
1074 <li>As another example, "fs windows" will prioritize windows-related results in `std.fs`, while "windows fs" will prioritize "fs"-related results in `std.windows`.</li>
1075 <li>This means that if you're searching inside a target namespace, you never have to read below the "other results" line.</li>
1076 <li>Since matching doesn't have to be perfect, you can also target a group of namespaces to search into. For example "array orderedremove" will show you all "Array-" namespaces that support <code>orderedRemove</code>.</li>
1077 <li>Periods are replaced by spaces because the Radix Tree doesn't index full paths, and in practice you should expect the match scoring system to consistently give you what you're looking for even when your query path is split into multiple terms.</li>
1078 </ul>
1079 </div>
1080 </details>
1081 <h2>Search Results</h2>
1082 <ul id="listSearchResults"></ul>
1083 <p id="sectSearchAllResultsLink" class="hidden"><a href="">show all results</a></p>
1084 </div>
1085 <div id="sectSearchNoResults" class="docs hidden">
1086 <h2>No Results Found</h2>
1087 <p>Here are some things you can try:</p>
1088 <ul>
1089 <li>Check out the <a id="langRefLink">Language Reference</a> for the language itself.</li>
1090 <li>Check out the <a href="https://ziglang.org/learn/">Learn page</a> for other helpful resources for learning Zig.</li>
1091 <li>Use your search engine.</li>
1092 </ul>
1093 <p>Press <kbd>?</kbd> to see keyboard shortcuts and <kbd>Esc</kbd> to return.</p>
1094 </div>
1095 <div id="guides" class="flex-horizontal hidden" style="align-items:flex-start;height:100%;overflow:hidden;">
1096 <div id="guidesMenu" class="sidebar">
1097 <h2 id="guidesMenuTitle">Table of Contents</h2>
1098 <div id="guideTocListEmpty" style="margin:0 1rem;"><i>No content to display.</i></div>
1099 <div id="guideTocList" style="height:100%;overflow-y:scroll;"></div>
1100 </div>
1101 <div id="activeGuide" class="hidden"></div>
1102 </div>
1103 <div id="docs" class="hidden" style="align-items:flex-start;height:100%;overflow:hidden;">
1104 <section id="docs-scroll" class="docs">
1105 <p id="status">Loading...</p>
1106 <div id="fnProto" class="hidden">
1107 <div class="mobile-scroll-container"><pre id="fnProtoCode" class="scroll-item"></pre></div>
1108 <div id="fnSourceLink" style="display:flex;flex-direction:row;justify-content:flex-end;"></div>
1109 </div>
1110 <h1 id="hdrName" class="hidden"></h1>
1111 <div id="fnNoExamples" class="hidden">
1112 <p>This function is not tested or referenced.</p>
1113 </div>
1114 <div id="declNoRef" class="hidden">
1115 <p>
1116 This declaration is not tested or referenced, and it has therefore not been included in
1117 semantic analysis, which means the only documentation available is whatever is in the
1118 doc comments.
1119 </p>
1120 </div>
1121 <div id="tldDocs" class="hidden"></div>
1122 <div id="sectParams" class="hidden">
1123 <h2>Parameters</h2>
1124 <div id="listParams"></div>
1125 </div>
1126 <div id="sectFnErrors" class="hidden">
1127 <h2>Errors</h2>
1128 <div id="fnErrorsAnyError">
1129 <p><span class="tok-type">anyerror</span> means the error set is known only at runtime.</p>
1130 </div>
1131 <div id="tableFnErrors"><dl id="listFnErrors"></dl></div>
1132 </div>
1133 <div id="sectFields" class="hidden">
1134 <h2>Fields</h2>
1135 <div id="listFields"></div>
1136 </div>
1137 <div id="sectNamespaces" class="hidden">
1138 <div style="position:relative;">
1139 <h2 style="position:sticky; top:0; background-color:var(--bg-color)">Namespaces</h2>
1140 <div class="flex-horizontal" style="justify-content:space-around;align-items:flex-start;flex-wrap:wrap;">
1141 <ul id="listNamespacesLeft" class="column"></ul>
1142 <ul id="listNamespacesRight" class="column"></ul>
1143 </div>
1144 </div>
1145 <h3>Other Namespaces <span style="font-size:1.1rem; cursor:pointer;" title="This box contains namespaces that are exported without a doc comment.">&#9432;</span></h3>
1146 <div id="noDocsNamespaces"></div>
1147 </div>
1148 <div id="sectTypes" class="hidden">
1149 <div style="position:relative;">
1150 <h2 style="position:sticky; top:0; background-color:var(--bg-color)">Types</h2>
1151 <div class="flex-horizontal" style="justify-content:space-around;align-items:flex-start;flex-wrap:wrap;">
1152 <ul id="listTypesLeft" class="column"></ul>
1153 <ul id="listTypesRight" class="column"></ul>
1154 </div>
1155 </div>
1156 </div>
1157 <div id="sectGlobalVars" class="hidden">
1158 <h2>Global Variables</h2>
1159 <div class="table-container">
1160 <table>
1161 <tbody id="listGlobalVars"></tbody>
1162 </table>
1163 </div>
1164 </div>
1165 <div id="sectFns" class="hidden">
1166 <h2>Functions</h2>
1167 <div class="table-container">
1168 <dl id="listFns"></dl>
1169 </div>
1170 </div>
1171 <div id="sectValues" class="hidden">
1172 <h2>Values</h2>
1173 <div class="table-container">
1174 <table>
1175 <tbody id="listValues"></tbody>
1176 </table>
1177 </div>
1178 </div>
1179 <div id="sectErrSets" class="hidden">
1180 <h2>Error Sets</h2>
1181 <ul id="listErrSets"></ul>
1182 </div>
1183 <div id="fnExamples" class="hidden">
1184 <h2>Examples</h2>
1185 <ul id="listFnExamples" class="examples"></ul>
1186 </div>
1187 <div id="sectDocTests" class="hidden">
1188 <h2>Usage Examples <span style="font-size:1.1rem; cursor:pointer;" title="See `doctests` in the language reference to learn more.">&#9432;</span></h2>
1189 <pre id="docTestsCode"></pre>
1190 </div>
1191 <div id="sectTests" class="hidden">
1192 <h2>Tests</h2>
1193 <div class="table-container">
1194 <table>
1195 <tbody id="listTests"></tbody>
1196 </table>
1197 </div>
1198 </div>
1199 </section>
1200 </div>
1201 <div class="flex-filler"></div>
259 <body>
260 <nav>
261 <div class="logo">
262 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140">
263 <g fill="#F7A41D">
264 <g>
265 <polygon points="46,22 28,44 19,30"/>
266 <polygon points="46,22 33,33 28,44 22,44 22,95 31,95 20,100 12,117 0,117 0,22" shape-rendering="crispEdges"/>
267 <polygon points="31,95 12,117 4,106"/>
268 </g>
269 <g>
270 <polygon points="56,22 62,36 37,44"/>
271 <polygon points="56,22 111,22 111,44 37,44 56,32" shape-rendering="crispEdges"/>
272 <polygon points="116,95 97,117 90,104"/>
273 <polygon points="116,95 100,104 97,117 42,117 42,95" shape-rendering="crispEdges"/>
274 <polygon points="150,0 52,117 3,140 101,22"/>
275 </g>
276 <g>
277 <polygon points="141,22 140,40 122,45"/>
278 <polygon points="153,22 153,117 106,117 120,105 125,95 131,95 131,45 122,45 132,36 141,22" shape-rendering="crispEdges"/>
279 <polygon points="125,95 130,110 106,117"/>
280 </g>
281 </g>
282 <style>
283 #text { fill: #121212 }
284 @media (prefers-color-scheme: dark) { #text { fill: #f2f2f2 } }
285 </style>
286 <g id="text">
287 <g>
288 <polygon points="260,22 260,37 229,40 177,40 177,22" shape-rendering="crispEdges"/>
289 <polygon points="260,37 207,99 207,103 176,103 229,40 229,37"/>
290 <polygon points="261,99 261,117 176,117 176,103 206,99" shape-rendering="crispEdges"/>
291 </g>
292 <rect x="272" y="22" shape-rendering="crispEdges" width="22" height="95"/>
293 <g>
294 <polygon points="394,67 394,106 376,106 376,81 360,70 346,67" shape-rendering="crispEdges"/>
295 <polygon points="360,68 376,81 346,67"/>
296 <path d="M394,106c-10.2,7.3-24,12-37.7,12c-29,0-51.1-20.8-51.1-48.3c0-27.3,22.5-48.1,52-48.1 c14.3,0,29.2,5.5,38.9,14l-13,15c-7.1-6.3-16.8-10-25.9-10c-17,0-30.2,12.9-30.2,29.5c0,16.8,13.3,29.6,30.3,29.6 c5.7,0,12.8-2.3,19-5.5L394,106z"/>
297 </g>
298 </g>
299 </svg>
1202300 </div>
301 </nav>
302 <div id="navWrap">
303 <input type="search" id="search" autocomplete="off" spellcheck="false" placeholder="`s` to search, `?` to see more options">
304 <div id="sectNav" class="hidden"><ul id="listNav"></ul></div>
305 </div>
306 <section>
307 <p id="status">Loading...</p>
308 <div id="fnProto" class="hidden">
309 <pre><code id="fnProtoCode"></code></pre>
1203310 </div>
1204 <div id="helpModal" class="hidden">
1205 <div class="modal-container">
1206 <div class="modal">
1207 <h1>Keyboard Shortcuts</h1>
1208 <dl><dt><kbd>?</kbd></dt><dd>Toggle this help modal</dd></dl>
1209 <dl><dt id="searchKeys"><!-- populated by setPrefSlashSearch --></dt><dd>Focus the search field</dd></dl>
1210 <div style="margin-left: 1em">
1211 <dl><dt><kbd>↑</kbd></dt><dd>Move up in search results</dd></dl>
1212 <dl><dt><kbd>↓</kbd></dt><dd>Move down in search results</dd></dl>
1213 <dl><dt><kbd>⏎</kbd></dt><dd>Go to active search result</dd></dl>
1214 </div>
1215 <dl><dt><kbd>p</kbd></dt><dd>Open preferences</dd></dl>
1216 <dl><dt><kbd>Esc</kbd></dt><dd>Clear focus; close this modal</dd></dl>
1217 </div>
311 <h1 id="hdrName" class="hidden"></h1>
312 <div id="tldDocs" class="hidden"></div>
313 <div id="sectFnErrors" class="hidden">
314 <h2>Errors</h2>
315 <div id="fnErrorsAnyError">
316 <p><span class="tok-type">anyerror</span> means the error set is known only at runtime.</p>
1218317 </div>
318 <div id="tableFnErrors"><dl id="listFnErrors"></dl></div>
1219319 </div>
1220 <div id="prefsModal" class="hidden">
1221 <div class="modal-container">
1222 <div class="modal">
1223 <h1>Preferences</h1>
1224 <ul class="prefs-list">
1225 <li><input id="prefSlashSearch" type="checkbox"><label for="prefSlashSearch">Enable <kbd>/</kbd> for search</label></li>
1226 </ul>
1227 </div>
320 <div id="sectSearchResults" class="hidden">
321 <h2>Search Results</h2>
322 <ul id="listSearchResults"></ul>
323 </div>
324 <div id="sectSearchNoResults" class="hidden">
325 <h2>No Results Found</h2>
326 <p>Press escape to exit search and then '?' to see more options.</p>
327 </div>
328 <div id="sectFields" class="hidden">
329 <h2>Fields</h2>
330 <div id="listFields">
1228331 </div>
1229332 </div>
1230 <script src="data-typeKinds.js"></script>
1231 <script src="data-rootMod.js"></script>
1232 <script src="data-modules.js"></script>
1233 <script src="data-files.js"></script>
1234 <script src="data-calls.js"></script>
1235 <script src="data-types.js"></script>
1236 <script src="data-decls.js"></script>
1237 <script src="data-exprs.js"></script>
1238 <script src="data-astNodes.js"></script>
1239 <script src="data-comptimeExprs.js"></script>
1240 <script src="data-guideSections.js"></script>
1241 <script src="commonmark.js"></script>
1242 <script src="ziglexer.js"></script>
333 <div id="sectTypes" class="hidden">
334 <h2>Types</h2>
335 <ul id="listTypes">
336 </ul>
337 </div>
338 <div id="sectNamespaces" class="hidden">
339 <h2>Namespaces</h2>
340 <ul id="listNamespaces">
341 </ul>
342 </div>
343 <div id="sectGlobalVars" class="hidden">
344 <h2>Global Variables</h2>
345 <table>
346 <tbody id="listGlobalVars">
347 </tbody>
348 </table>
349 </div>
350 <div id="sectFns" class="hidden">
351 <h2>Functions</h2>
352 <dl id="listFns">
353 </dl>
354 </div>
355 <div id="sectValues" class="hidden">
356 <h2>Values</h2>
357 <table>
358 <tbody id="listValues">
359 </tbody>
360 </table>
361 </div>
362 <div id="sectErrSets" class="hidden">
363 <h2>Error Sets</h2>
364 <ul id="listErrSets">
365 </ul>
366 </div>
367 <div id="sectDocTests" class="hidden">
368 <h2>Example Usage</h2>
369 <pre><code id="docTestsCode"></code></pre>
370 </div>
371 <div id="sectSource" class="hidden">
372 <h2>Source Code</h2>
373 <pre><code id="sourceText"></code></pre>
374 </div>
375 </section>
376 <div id="helpDialog" class="hidden">
377 <h1>Keyboard Shortcuts</h1>
378 <dl><dt><kbd>?</kbd></dt><dd>Show this help dialog</dd></dl>
379 <dl><dt><kbd>Esc</kbd></dt><dd>Clear focus; close this dialog</dd></dl>
380 <dl><dt><kbd>s</kbd></dt><dd>Focus the search field</dd></dl>
381 <dl><dt><kbd>u</kbd></dt><dd>Go to source code</dd></dl>
382 <dl><dt><kbd>↑</kbd></dt><dd>Move up in search results</dd></dl>
383 <dl><dt><kbd>↓</kbd></dt><dd>Move down in search results</dd></dl>
384 <dl><dt><kbd>⏎</kbd></dt><dd>Go to active search result</dd></dl>
385 </div>
1243386 <script src="main.js"></script>
1244387 </body>
1245388</html>
389
lib/docs/main.js+700-5091
......@@ -1,5242 +1,851 @@
1"use strict";
2
3var zigAnalysis = {
4 typeKinds,
5 rootMod,
6 modules,
7 astNodes,
8 calls,
9 files,
10 decls,
11 exprs,
12 types,
13 comptimeExprs,
14 guideSections
15};
16
17let skipNextHashChange = null;
18
19const NAV_MODES = {
20 API: "#A;",
21 GUIDES: "#G;",
22};
23
24
25var scrollHistory = {};
26
271(function() {
28 const domBanner = document.getElementById("banner");
29 const domMain = document.getElementById("main");
30 const domStatus = document.getElementById("status");
31 const domSectNavAPI = document.getElementById("sectNavAPI");
32 const domListNavAPI = document.getElementById("listNavAPI");
33 const domSectNavGuides = document.getElementById("sectNavGuides");
34 const domListNavGuides = document.getElementById("listNavGuides");
35 const domApiSwitch = document.getElementById("ApiSwitch");
36 const domGuideSwitch = document.getElementById("guideSwitch");
37 const domGuidesMenu = document.getElementById("guidesMenu");
38 const domGuidesMenuTitle = document.getElementById("guidesMenuTitle");
39 const domGuideTocList = document.getElementById("guideTocList");
40 const domGuideTocListEmtpy = document.getElementById("guideTocListEmpty");
41 const domListMods = document.getElementById("listMods");
42 const domSectTypes = document.getElementById("sectTypes");
43 const domListTypesLeft = document.getElementById("listTypesLeft");
44 const domListTypesRight = document.getElementById("listTypesRight");
45 const domSectTests = document.getElementById("sectTests");
46 const domListTests = document.getElementById("listTests");
47 const domSectDocTests = document.getElementById("sectDocTests");
48 const domDocTestsCode = document.getElementById("docTestsCode");
49 const domSectNamespaces = document.getElementById("sectNamespaces");
50 const domListNamespacesLeft = document.getElementById("listNamespacesLeft");
51 const domListNamespacesRight = document.getElementById("listNamespacesRight");
52 const domNoDocsNamespaces = document.getElementById("noDocsNamespaces");
53 const domSectErrSets = document.getElementById("sectErrSets");
54 const domListErrSets = document.getElementById("listErrSets");
55 const domSectFns = document.getElementById("sectFns");
56 const domListFns = document.getElementById("listFns");
57 const domSectFields = document.getElementById("sectFields");
58 const domListFields = document.getElementById("listFields");
59 const domSectGlobalVars = document.getElementById("sectGlobalVars");
60 const domListGlobalVars = document.getElementById("listGlobalVars");
61 const domSectValues = document.getElementById("sectValues");
62 const domListValues = document.getElementById("listValues");
63 const domFnProto = document.getElementById("fnProto");
64 const domFnProtoCode = document.getElementById("fnProtoCode");
65 const domFnSourceLink = document.getElementById("fnSourceLink");
66 const domSectParams = document.getElementById("sectParams");
67 const domListParams = document.getElementById("listParams");
68 const domTldDocs = document.getElementById("tldDocs");
69 const domSectFnErrors = document.getElementById("sectFnErrors");
70 const domListFnErrors = document.getElementById("listFnErrors");
71 const domTableFnErrors = document.getElementById("tableFnErrors");
72 const domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");
73 const domFnExamples = document.getElementById("fnExamples");
74 // const domListFnExamples = (document.getElementById("listFnExamples"));
75 const domFnNoExamples = document.getElementById("fnNoExamples");
76 const domDeclNoRef = document.getElementById("declNoRef");
77 const domSearch = document.getElementById("search");
78 const domSearchHelp = document.getElementById("searchHelp");
79 const domSearchHelpSummary = document.getElementById("searchHelpSummary");
80 const domSectSearchResults = document.getElementById("sectSearchResults");
81 const domSectSearchAllResultsLink = document.getElementById("sectSearchAllResultsLink");
82 const domDocs = document.getElementById("docs");
83 const domDocsScroll = document.getElementById("docs-scroll");
84 const domGuidesSection = document.getElementById("guides");
85 const domActiveGuide = document.getElementById("activeGuide");
86
87 const domListSearchResults = document.getElementById("listSearchResults");
88 const domSectSearchNoResults = document.getElementById("sectSearchNoResults");
89 // const domTdTarget = (document.getElementById("tdTarget"));
90 const domTdZigVer = document.getElementById("tdZigVer");
91 const domHdrName = document.getElementById("hdrName");
92 const domHelpModal = document.getElementById("helpModal");
93 const domSearchKeys = document.getElementById("searchKeys");
94 const domPrefsModal = document.getElementById("prefsModal");
95 const domSearchPlaceholder = document.getElementById("searchPlaceholder");
96 const domSearchPlaceholderText = document.getElementById("searchPlaceholderText");
97 const sourceFileUrlTemplate = "src/{{mod}}/{{file}}.html#L{{line}}"
98 const domLangRefLink = document.getElementById("langRefLink");
99
100 const domPrefSlashSearch = document.getElementById("prefSlashSearch");
101 const prefs = getLocalStorage();
102 loadPrefs();
103
104 domPrefSlashSearch.addEventListener("change", () => setPrefSlashSearch(domPrefSlashSearch.checked));
105
106 const scrollMonitor = [
107 domActiveGuide,
108 domGuideTocList,
109 domDocsScroll,
110 domSectSearchResults,
111 ];
112
113 computeGuideHashes();
114
115 let searchTimer = null;
116 let searchTrimResults = true;
117
118 let escapeHtmlReplacements = {
119 "&": "&amp;",
120 '"': "&quot;",
121 "<": "&lt;",
122 ">": "&gt;",
123 };
124
125 let typeKinds = indexTypeKinds();
126 let typeTypeId = findTypeTypeId();
127 let pointerSizeEnum = { One: 0, Many: 1, Slice: 2, C: 3 };
128
129 let declSearchIndex = new RadixTree();
130 window.search = declSearchIndex;
131
132 // for each module, is an array with modules to get to this one
133 let canonModPaths = computeCanonicalModulePaths();
134
135 // for each decl, is an array with {declNames, modNames} to get to this one
136 let canonDeclPaths = null; // lazy; use getCanonDeclPath
137
138 // for each type, is an array with {declNames, modNames} to get to this one
139 let canonTypeDecls = null; // lazy; use getCanonTypeDecl
140
141 let curNav = {
142 hash: "",
143 mode: NAV_MODES.API,
144 activeGuide: "",
145 activeGuideScrollTo: null,
146 // each element is a module name, e.g. @import("a") then within there @import("b")
147 // starting implicitly from root module
148 modNames: [],
149 // same as above except actual modules, not names
150 modObjs: [],
151 // Each element is a decl name, `a.b.c`, a is 0, b is 1, c is 2, etc.
152 // empty array means refers to the module itself
153 declNames: [],
154 // these will be all types, except the last one may be a type or a decl
155 declObjs: [],
156 // (a, b, c, d) comptime call; result is the value the docs refer to
157 callName: null,
158 };
159
160 let curNavSearch = "";
161 let curSearchIndex = -1;
162 let imFeelingLucky = false;
163
164 let rootIsStd = detectRootIsStd();
165
166 // map of decl index to list of non-generic fn indexes
167 // let nodesToFnsMap = indexNodesToFns();
168 // map of decl index to list of comptime fn calls
169 // let nodesToCallsMap = indexNodesToCalls();
170
171 let guidesSearchIndex = {};
172 window.guideSearch = guidesSearchIndex;
173 parseGuides();
174
175 // identifiers can contain modal trigger characters so we want to allow typing
176 // such characters when the search is focused instead of toggling the modal
177 let canToggleModal = true;
178
179 domSearch.disabled = false;
180 domSearch.addEventListener("keydown", onSearchKeyDown, false);
181 domSearch.addEventListener("input", onSearchInput, false);
182 domSearch.addEventListener("focus", ev => {
183 domSearchPlaceholder.classList.add("hidden");
184 canToggleModal = false;
185 });
186 domSearch.addEventListener("blur", ev => {
187 if (domSearch.value.length == 0)
188 domSearchPlaceholder.classList.remove("hidden");
189 canToggleModal = true;
190 });
191 domSectSearchAllResultsLink.addEventListener('click', onClickSearchShowAllResults, false);
192 function onClickSearchShowAllResults(ev) {
193 ev.preventDefault();
194 ev.stopPropagation();
195 searchTrimResults = false;
196 onHashChange();
197 }
2 const CAT_namespace = 0;
3 const CAT_global_variable = 1;
4 const CAT_function = 2;
5 const CAT_primitive = 3;
6 const CAT_error_set = 4;
7 const CAT_global_const = 5;
8 const CAT_alias = 6;
9 const CAT_type = 7;
10
11 const domDocTestsCode = document.getElementById("docTestsCode");
12 const domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");
13 const domFnProto = document.getElementById("fnProto");
14 const domFnProtoCode = document.getElementById("fnProtoCode");
15 const domHdrName = document.getElementById("hdrName");
16 const domHelpModal = document.getElementById("helpDialog");
17 const domListErrSets = document.getElementById("listErrSets");
18 const domListFields = document.getElementById("listFields");
19 const domListFnErrors = document.getElementById("listFnErrors");
20 const domListFns = document.getElementById("listFns");
21 const domListGlobalVars = document.getElementById("listGlobalVars");
22 const domListInfo = document.getElementById("listInfo");
23 const domListNamespaces = document.getElementById("listNamespaces");
24 const domListNav = document.getElementById("listNav");
25 const domListSearchResults = document.getElementById("listSearchResults");
26 const domListTypes = document.getElementById("listTypes");
27 const domListValues = document.getElementById("listValues");
28 const domSearch = document.getElementById("search");
29 const domSectDocTests = document.getElementById("sectDocTests");
30 const domSectErrSets = document.getElementById("sectErrSets");
31 const domSectFields = document.getElementById("sectFields");
32 const domSectFnErrors = document.getElementById("sectFnErrors");
33 const domSectFns = document.getElementById("sectFns");
34 const domSectGlobalVars = document.getElementById("sectGlobalVars");
35 const domSectNamespaces = document.getElementById("sectNamespaces");
36 const domSectNav = document.getElementById("sectNav");
37 const domSectSearchNoResults = document.getElementById("sectSearchNoResults");
38 const domSectSearchResults = document.getElementById("sectSearchResults");
39 const domSectSource = document.getElementById("sectSource");
40 const domSectTypes = document.getElementById("sectTypes");
41 const domSectValues = document.getElementById("sectValues");
42 const domSourceText = document.getElementById("sourceText");
43 const domStatus = document.getElementById("status");
44 const domTableFnErrors = document.getElementById("tableFnErrors");
45 const domTldDocs = document.getElementById("tldDocs");
46
47 var searchTimer = null;
48
49 const curNav = {
50 // 0 = home
51 // 1 = decl (decl)
52 // 2 = source (path)
53 tag: 0,
54 // unsigned int: decl index
55 decl: null,
56 // string file name matching tarball path
57 path: null,
58
59 // when this is populated, pressing the "view source" command will
60 // navigate to this hash.
61 viewSourceHash: null,
62 };
63 var curNavSearch = "";
64 var curSearchIndex = -1;
65 var imFeelingLucky = false;
19866
199 if (location.hash == "") {
200 location.hash = "#A;";
201 }
67 // names of packages in the same order as wasm
68 const packageList = [];
20269
203 // make the modal disappear if you click outside it
204 function handleModalClick(ev) {
205 if (ev.target.classList.contains("modal-container")) {
206 hideModal(this);
207 }
208 }
209 domHelpModal.addEventListener("click", handleModalClick);
210 domPrefsModal.addEventListener("click", handleModalClick);
70 let wasm_promise = fetch("main.wasm");
71 let sources_promise = fetch("sources.tar").then(function(response) {
72 if (!response.ok) throw new Error("unable to download sources");
73 return response.arrayBuffer();
74 });
75 var wasm_exports = null;
21176
212 window.addEventListener("hashchange", onHashChange, false);
213 window.addEventListener("keydown", onWindowKeyDown, false);
214 onHashChange();
77 const text_decoder = new TextDecoder();
78 const text_encoder = new TextEncoder();
21579
216 // TODO: fix this once langref becomes part of autodoc
217 let langRefVersion = "master";
218 domLangRefLink.href = `https://ziglang.org/documentation/${langRefVersion}/`;
80 WebAssembly.instantiateStreaming(wasm_promise, {
81 js: {
82 log: function(ptr, len) {
83 const msg = decodeString(ptr, len);
84 console.log(msg);
85 },
86 panic: function (ptr, len) {
87 const msg = decodeString(ptr, len);
88 throw new Error("panic: " + msg);
89 },
90 },
91 }).then(function(obj) {
92 wasm_exports = obj.instance.exports;
93 window.wasm = obj; // for debugging
94
95 sources_promise.then(function(buffer) {
96 const js_array = new Uint8Array(buffer);
97 const ptr = wasm_exports.alloc(js_array.length);
98 const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length);
99 wasm_array.set(js_array);
100 wasm_exports.unpack(ptr, js_array.length);
101
102 updatePackageList();
103
104 window.addEventListener('hashchange', onHashChange, false);
105 domSearch.addEventListener('keydown', onSearchKeyDown, false);
106 domSearch.addEventListener('input', onSearchChange, false);
107 window.addEventListener('keydown', onWindowKeyDown, false);
108 onHashChange();
109 });
110 });
219111
220 function renderTitle() {
221 let suffix = " - Zig";
222 switch (curNav.mode) {
223 case NAV_MODES.API:
224 let list = curNav.modNames.concat(curNav.declNames);
225 if (list.length === 0) {
226 document.title = zigAnalysis.modules[zigAnalysis.rootMod].name + suffix;
227 } else {
228 document.title = list.join(".") + suffix;
112 function renderTitle() {
113 const suffix = " - Zig Documentation";
114 if (curNavSearch.length > 0) {
115 document.title = curNavSearch + " - Search" + suffix;
116 } else if (curNav.decl != null) {
117 document.title = fullyQualifiedName(curNav.decl) + suffix;
118 } else if (curNav.path != null) {
119 document.title = curNav.path + suffix;
120 } else {
121 document.title = packageList[0] + suffix; // Home
122 }
123 }
124
125 function render() {
126 domFnErrorsAnyError.classList.add("hidden");
127 domFnProto.classList.add("hidden");
128 domHdrName.classList.add("hidden");
129 domHelpModal.classList.add("hidden");
130 domSectErrSets.classList.add("hidden");
131 domSectDocTests.classList.add("hidden");
132 domSectFields.classList.add("hidden");
133 domSectFnErrors.classList.add("hidden");
134 domSectFns.classList.add("hidden");
135 domSectGlobalVars.classList.add("hidden");
136 domSectNamespaces.classList.add("hidden");
137 domSectNav.classList.add("hidden");
138 domSectSearchNoResults.classList.add("hidden");
139 domSectSearchResults.classList.add("hidden");
140 domSectSource.classList.add("hidden");
141 domSectTypes.classList.add("hidden");
142 domSectValues.classList.add("hidden");
143 domStatus.classList.add("hidden");
144 domTableFnErrors.classList.add("hidden");
145 domTldDocs.classList.add("hidden");
146
147 renderTitle();
148
149 if (curNavSearch !== "") return renderSearch();
150
151 switch (curNav.tag) {
152 case 0: return renderHome();
153 case 1:
154 if (curNav.decl == null) {
155 return render404();
156 } else {
157 return renderDecl(curNav.decl);
158 }
159 case 2: return renderSource(curNav.path);
160 default: throw new Error("invalid navigation state");
229161 }
230 return;
231 case NAV_MODES.GUIDES:
232 document.title = "[G] " + curNav.activeGuide + suffix;
233 return;
234162 }
235 }
236163
237 function isDecl(x) {
238 return "value" in x;
239 }
164 function renderHome() {
165 if (packageList.length == 1) return renderPackage(0);
240166
241 function isType(x) {
242 return "kind" in x && !("value" in x);
243 }
244
245 function isContainerType(x) {
246 return isType(x) && typeKindIsContainer(x.kind);
247 }
167 domStatus.textContent = "TODO implement renderHome for multiple packages";
168 domStatus.classList.remove("hidden");
169 }
248170
249 function typeShorthandName(expr) {
250 let resolvedExpr = resolveValue({ expr: expr });
251 if (!("type" in resolvedExpr)) {
252 return null;
171 function renderPackage(pkg_index) {
172 const root_decl = wasm_exports.find_package_root(pkg_index);
173 return renderDecl(root_decl);
253174 }
254 let type = getType(resolvedExpr.type);
255175
256 outer: for (let i = 0; i < 10000; i += 1) {
257 switch (type.kind) {
258 case typeKinds.Optional:
259 case typeKinds.Pointer:
260 let child = type.child;
261 let resolvedChild = resolveValue(child);
262 if ("type" in resolvedChild) {
263 type = getType(resolvedChild.type);
264 continue;
265 } else {
266 return null;
267 }
268 default:
269 break outer;
176 function renderDecl(decl_index) {
177 const category = wasm_exports.categorize_decl(decl_index, 0);
178 switch (category) {
179 case CAT_namespace: return renderNamespace(decl_index);
180 case CAT_global_variable: throw new Error("TODO: CAT_GLOBAL_VARIABLE");
181 case CAT_function: return renderFunction(decl_index);
182 case CAT_primitive: throw new Error("TODO CAT_primitive");
183 case CAT_error_set: throw new Error("TODO CAT_error_set");
184 case CAT_global_const: return renderGlobalConst(decl_index);
185 case CAT_alias: return renderDecl(wasm_exports.get_aliasee());
186 case CAT_type: throw new Error("TODO CAT_type");
187 default: throw new Error("unrecognized category " + category);
270188 }
271
272 if (i == 9999) throw "Exhausted typeShorthandName quota";
273189 }
274190
275 let name = undefined;
276 if (type.kind === typeKinds.Struct) {
277 name = "struct";
278 } else if (type.kind === typeKinds.Enum) {
279 name = "enum";
280 } else if (type.kind === typeKinds.Union) {
281 name = "union";
282 } else {
283 console.log("TODO: unhandled case in typeShortName");
284 return null;
285 }
191 function renderSource(path) {
192 const decl_index = findFileRoot(path);
193 if (decl_index == null) return render404();
286194
287 return escapeHtml(name);
288 }
195 renderNav(decl_index);
289196
290 function typeKindIsContainer(typeKind) {
291 return (
292 typeKind === typeKinds.Struct ||
293 typeKind === typeKinds.Union ||
294 typeKind === typeKinds.Enum ||
295 typeKind === typeKinds.Opaque
296 );
297 }
197 domSourceText.innerHTML = declSourceHtml(decl_index);
298198
299 function declCanRepresentTypeKind(typeKind) {
300 return typeKind === typeKinds.ErrorSet || typeKindIsContainer(typeKind);
301 }
199 domSectSource.classList.remove("hidden");
200 }
302201
303 //
304 // function findCteInRefPath(path) {
305 // for (let i = path.length - 1; i >= 0; i -= 1) {
306 // const ref = path[i];
307 // if ("string" in ref) continue;
308 // if ("comptimeExpr" in ref) return ref;
309 // if ("refPath" in ref) return findCteInRefPath(ref.refPath);
310 // return null;
311 // }
202 function renderDeclHeading(decl_index) {
203 domHdrName.innerText = unwrapString(wasm_exports.decl_category_name(decl_index));
204 domHdrName.classList.remove("hidden");
312205
313 // return null;
314 // }
206 renderTopLevelDocs(decl_index);
207 }
315208
316 function resolveValue(value, trackDecls) {
317 let seenDecls = [];
318 let i = 0;
319 while (true) {
320 i += 1;
321 if (i >= 10000) {
322 throw "resolveValue quota exceeded"
209 function renderTopLevelDocs(decl_index) {
210 const tld_docs_html = unwrapString(wasm_exports.decl_docs_html(decl_index, false));
211 if (tld_docs_html.length > 0) {
212 domTldDocs.innerHTML = tld_docs_html;
213 domTldDocs.classList.remove("hidden");
323214 }
215 }
324216
325 if ("refPath" in value.expr) {
326 value = { expr: value.expr.refPath[value.expr.refPath.length - 1] };
327 continue;
328 }
217 function renderNav(cur_nav_decl) {
218 const list = [];
219 {
220 // First, walk backwards the decl parents within a file.
221 let decl_it = cur_nav_decl;
222 let prev_decl_it = null;
223 while (decl_it != null) {
224 list.push({
225 name: declIndexName(decl_it),
226 href: navLinkDeclIndex(decl_it),
227 });
228 prev_decl_it = decl_it;
229 decl_it = declParent(decl_it);
230 }
231
232 // Next, walk backwards the file path segments.
233 if (prev_decl_it != null) {
234 const file_path = fullyQualifiedName(prev_decl_it);
235 const parts = file_path.split(".");
236 parts.pop(); // skip last
237 for (;;) {
238 let part = parts.pop();
239 if (!part) break;
240 list.push({
241 name: part,
242 href: navLinkFqn(parts.join(".")),
243 });
244 }
245 }
329246
330 if ("declRef" in value.expr) {
331 seenDecls.push(value.expr.declRef);
332 value = getDecl(value.expr.declRef).value;
333 continue;
247 list.reverse();
334248 }
249 resizeDomList(domListNav, list.length, '<li><a href="#"></a></li>');
335250
336 if ("as" in value.expr) {
337 value = {
338 typeRef: zigAnalysis.exprs[value.expr.as.typeRefArg],
339 expr: zigAnalysis.exprs[value.expr.as.exprArg],
340 };
341 continue;
251 for (let i = 0; i < list.length; i += 1) {
252 const liDom = domListNav.children[i];
253 const aDom = liDom.children[0];
254 aDom.textContent = list[i].name;
255 aDom.setAttribute('href', list[i].href);
256 if (i + 1 == list.length) {
257 aDom.classList.add("active");
258 } else {
259 aDom.classList.remove("active");
260 }
342261 }
343262
344 if (trackDecls) return { value, seenDecls };
345 return value;
263 domSectNav.classList.remove("hidden");
346264 }
347 }
348
349 function resolveGenericRet(genericFunc) {
350 if (genericFunc.generic_ret == null) return null;
351 let result = resolveValue({ expr: genericFunc.generic_ret });
352265
353 let i = 0;
354 while (true) {
355 i += 1;
356 if (i >= 10000) {
357 throw "resolveGenericRet quota exceeded"
358 }
266 function render404() {
267 domStatus.textContent = "404 Not Found";
268 domStatus.classList.remove("hidden");
269 }
359270
360 if ("call" in result.expr) {
361 let call = zigAnalysis.calls[result.expr.call];
362 let resolvedFunc = resolveValue({ expr: call.func });
363 if (!("type" in resolvedFunc.expr)) return null;
364 let callee = getType(resolvedFunc.expr.type);
365 if (!callee.generic_ret) return null;
366 result = resolveValue({ expr: callee.generic_ret });
367 continue;
368 }
271 function navLinkFqn(full_name) {
272 return '#' + full_name;
273 }
369274
370 return result;
275 function navLinkDeclIndex(decl_index) {
276 return navLinkFqn(fullyQualifiedName(decl_index));
371277 }
372 }
373278
374 // function typeOfDecl(decl){
375 // return decl.value.typeRef;
376 //
377 // let i = 0;
378 // while(i < 1000) {
379 // i += 1;
380 // console.assert(isDecl(decl));
381 // if ("type" in decl.value) {
382 // return ({ type: typeTypeId });
383 // }
384 //
385 //// if ("string" in decl.value) {
386 //// return ({ type: {
387 //// kind: typeKinds.Pointer,
388 //// size: pointerSizeEnum.One,
389 //// child: });
390 //// }
391 //
392 // if ("refPath" in decl.value) {
393 // decl = ({
394 // value: decl.value.refPath[decl.value.refPath.length -1]
395 // });
396 // continue;
397 // }
398 //
399 // if ("declRef" in decl.value) {
400 // decl = zigAnalysis.decls[decl.value.declRef];
401 // continue;
402 // }
403 //
404 // if ("int" in decl.value) {
405 // return decl.value.int.typeRef;
406 // }
407 //
408 // if ("float" in decl.value) {
409 // return decl.value.float.typeRef;
410 // }
411 //
412 // if ("array" in decl.value) {
413 // return decl.value.array.typeRef;
414 // }
415 //
416 // if ("struct" in decl.value) {
417 // return decl.value.struct.typeRef;
418 // }
419 //
420 // if ("comptimeExpr" in decl.value) {
421 // const cte = zigAnalysis.comptimeExprs[decl.value.comptimeExpr];
422 // return cte.typeRef;
423 // }
424 //
425 // if ("call" in decl.value) {
426 // const fn_call = zigAnalysis.calls[decl.value.call];
427 // let fn_decl = undefined;
428 // if ("declRef" in fn_call.func) {
429 // fn_decl = zigAnalysis.decls[fn_call.func.declRef];
430 // } else if ("refPath" in fn_call.func) {
431 // console.assert("declRef" in fn_call.func.refPath[fn_call.func.refPath.length -1]);
432 // fn_decl = zigAnalysis.decls[fn_call.func.refPath[fn_call.func.refPath.length -1].declRef];
433 // } else throw {};
434 //
435 // const fn_decl_value = resolveValue(fn_decl.value);
436 // console.assert("type" in fn_decl_value); //TODO handle comptimeExpr
437 // const fn_type = (zigAnalysis.types[fn_decl_value.type]);
438 // console.assert(fn_type.kind === typeKinds.Fn);
439 // return fn_type.ret;
440 // }
441 //
442 // if ("void" in decl.value) {
443 // return ({ type: typeTypeId });
444 // }
445 //
446 // if ("bool" in decl.value) {
447 // return ({ type: typeKinds.Bool });
448 // }
449 //
450 // console.log("TODO: handle in `typeOfDecl` more cases: ", decl);
451 // console.assert(false);
452 // throw {};
453 // }
454 // console.assert(false);
455 // return ({});
456 // }
457 function detectDeclPath(text, context) {
458 let result = "";
459 let separator = ":";
460 const components = text.split(".");
461 let curDeclOrType = undefined;
462
463 let curContext = context;
464 let limit = 10000;
465 while (curContext) {
466 limit -= 1;
467
468 if (limit == 0) {
469 throw "too many iterations";
279 function resizeDomList(listDom, desiredLen, templateHtml) {
280 // add the missing dom entries
281 var i, ev;
282 for (i = listDom.childElementCount; i < desiredLen; i += 1) {
283 listDom.insertAdjacentHTML('beforeend', templateHtml);
470284 }
471
472 curDeclOrType = findSubDecl(curContext, components[0]);
473
474 if (!curDeclOrType) {
475 if (curContext.parent_container == null) break;
476 curContext = getType(curContext.parent_container);
477 continue;
285 // remove extra dom entries
286 while (desiredLen < listDom.childElementCount) {
287 listDom.removeChild(listDom.lastChild);
478288 }
289 }
479290
480 if (curContext == context) {
481 separator = '.';
482 result = location.hash + separator + components[0];
483 } else {
484 // We had to go up, which means we need a new path!
485 const canonPath = getCanonDeclPath(curDeclOrType.find_subdecl_idx);
486 if (!canonPath) return;
487
488 let lastModName = canonPath.modNames[canonPath.modNames.length - 1];
489 let fullPath = lastModName + ":" + canonPath.declNames.join(".");
490
491 separator = '.';
492 result = "#A;" + fullPath;
493 }
291 function setViewSourceDecl(decl_index) {
292 curNav.viewSourceHash = "#src/" + unwrapString(wasm_exports.decl_file_path(decl_index));
293 }
494294
495 break;
496 }
295 function renderFunction(decl_index) {
296 renderNav(decl_index);
297 setViewSourceDecl(decl_index);
497298
498 if (!curDeclOrType) {
499 for (let i = 0; i < zigAnalysis.modules.length; i += 1){
500 const p = zigAnalysis.modules[i];
501 if (p.name == components[0]) {
502 curDeclOrType = getType(p.main);
503 result += "#A;" + components[0];
504 break;
505 }
506 }
507 }
299 domFnProtoCode.innerHTML = fnProtoHtml(decl_index);
300 renderTopLevelDocs(decl_index);
301 domSourceText.innerHTML = declSourceHtml(decl_index);
508302
509 if (!curDeclOrType) return null;
510
511 for (let i = 1; i < components.length; i += 1) {
512 curDeclOrType = findSubDecl(curDeclOrType, components[i]);
513 if (!curDeclOrType) return null;
514 result += separator + components[i];
515 separator = '.';
303 const doctest_html = declDoctestHtml(decl_index);
304 if (doctest_html.length > 0) {
305 domDocTestsCode.innerHTML = doctest_html;
306 domSectDocTests.classList.remove("hidden");
516307 }
517308
518 return result;
519
309 domSectSource.classList.remove("hidden");
310 domFnProto.classList.remove("hidden");
520311 }
521
522 function renderGuides() {
523 renderTitle();
524312
525 // set guide mode
526 domGuideSwitch.classList.add("active");
527 domApiSwitch.classList.remove("active");
528 domDocs.classList.add("hidden");
529 domSectNavAPI.classList.add("hidden");
530 domSectNavGuides.classList.remove("hidden");
531 domGuidesSection.classList.remove("hidden");
532 domActiveGuide.classList.add("hidden");
533 domSectSearchResults.classList.add("hidden");
534 domSectSearchAllResultsLink.classList.add("hidden");
535 domSectSearchNoResults.classList.add("hidden");
536 if (curNavSearch !== "") {
537 return renderSearchGuides();
538 }
313 function renderGlobalConst(decl_index) {
314 renderNav(decl_index);
315 setViewSourceDecl(decl_index);
539316
540 let activeGuide = undefined;
541 outer: for (let i = 0; i < zigAnalysis.guideSections.length; i += 1) {
542 const section = zigAnalysis.guideSections[i];
543 for (let j = 0; j < section.guides.length; j += 1) {
544 const guide = section.guides[j];
545 if (guide.name == curNav.activeGuide) {
546 activeGuide = guide;
547 break outer;
548 }
317 const docs_html = declDocsHtmlShort(decl_index);
318 if (docs_html.length > 0) {
319 domTldDocs.innerHTML = docs_html;
320 domTldDocs.classList.remove("hidden");
549321 }
550 }
551
552322
553 // navigation bar
554
555 const guideIndexDom = domListNavGuides.children[0].children[0];
556 const guideDom = domListNavGuides.children[1].children[0];
557 if (activeGuide){
558 guideDom.textContent = activeGuide.title;
559 guideDom.setAttribute("href", location.hash);
560 guideDom.classList.remove("hidden");
561 guideIndexDom.classList.remove("active");
562 } else {
563 guideDom.classList.add("hidden");
564 guideIndexDom.classList.add("active");
565 }
566
567 // main content
568 domGuidesMenuTitle.textContent = "Table of Contents";
569 if (activeGuide) {
570 if (activeGuide.toc != "") {
571 domGuideTocList.innerHTML = activeGuide.toc;
572 // add js callbacks to all links
573 function onLinkClick(ev) {
574 const link = ev.target.getAttribute("href");
575 skipNextHashChange = link;
576 location.replace(link);
577 scrollToHeading(":" + link.split(":")[1], true);
578 ev.preventDefault();
579 ev.stopPropagation();
580 }
581 for (let a of domGuideTocList.querySelectorAll("a")) {
582 a.addEventListener('click', onLinkClick, false);
583 }
584 domGuideTocList.classList.remove("hidden");
585 domGuideTocListEmtpy.classList.add("hidden");
586 } else {
587 domGuideTocListEmtpy.classList.remove("hidden");
588 domGuideTocList.classList.add("hidden");
589 }
590
591 let reader = new commonmark.Parser({
592 smart: true,
593 autoDoc: {
594 detectDeclPath: detectDeclPath,
323 domSourceText.innerHTML = declSourceHtml(decl_index);
324 domSectSource.classList.remove("hidden");
325 }
326
327 function renderNamespace(decl_index) {
328 renderNav(decl_index);
329 renderDeclHeading(decl_index);
330 setViewSourceDecl(decl_index);
331
332 const typesList = [];
333 const namespacesList = [];
334 const errSetsList = [];
335 const fnsList = [];
336 const varsList = [];
337 const valsList = [];
338 const members = namespaceMembers(decl_index, false);
339
340 member_loop: for (let i = 0; i < members.length; i += 1) {
341 let member = members[i];
342 while (true) {
343 const member_category = wasm_exports.categorize_decl(member, 0);
344 switch (member_category) {
345 case CAT_namespace:
346 namespacesList.push(member);
347 continue member_loop;
348 case CAT_global_variable:
349 varsList.push(member);
350 continue member_loop;
351 case CAT_function:
352 fnsList.push(member);
353 continue member_loop;
354 case CAT_type:
355 typesList.push(member);
356 continue member_loop;
357 case CAT_error_set:
358 errSetsList.push(member);
359 continue member_loop;
360 case CAT_global_const:
361 case CAT_primitive:
362 valsList.push(member);
363 continue member_loop;
364 case CAT_alias:
365 // TODO: handle aliasing loop
366 member = wasm_exports.get_aliasee();
367 continue;
368 default:
369 throw new Error("uknown category: " + member_category);
370 }
371 }
595372 }
596 });
597 let ast = reader.parse(activeGuide.body);
598 let writer = new commonmark.HtmlRenderer();
599 let result = writer.render(ast);
600 domActiveGuide.innerHTML = result;
601 if (curNav.activeGuideScrollTo !== null) {
602 scrollToHeading(curNav.activeGuideScrollTo, false);
603 }
604 } else {
605 domGuideTocList.classList.add("hidden");
606 domGuideTocListEmtpy.classList.remove("hidden");
607
608 if (zigAnalysis.guideSections.length > 1 || (zigAnalysis.guideSections[0].guides.length > 0)) {
609 renderGuidesIndex();
610 } else {
611 noGuidesAtAll();
612 }
613 }
614373
615 domGuidesMenu.classList.remove("hidden");
616 domActiveGuide.classList.remove("hidden");
617 }
618
619 // TODO: ensure unique hashes
620 // TODO: hash also guides and their headings
621 function computeGuideHashes() {
622 for (let i = 1; i < zigAnalysis.guideSections.length; i += 1) {
623 const section = zigAnalysis.guideSections[i];
624 section.hash = "section-" + slugify(section.name || i);
625 }
626 }
627
628 function renderGuidesIndex() {
629 // main content
630 {
631 let html = "";
632 for (let i = 0; i < zigAnalysis.guideSections.length; i += 1) {
633 const section = zigAnalysis.guideSections[i];
634 if (i != 0) { // first section is the default section
635 html += "<h2 id='"+ section.hash +"'>" + section.name + "</h2>";
636 }
637 for (let guide of section.guides) {
638 html += "<ol><li><a href='"+ NAV_MODES.GUIDES + guide.name +"'>" + (guide.title || guide.name) + "</a></li>";
639 html += guide.toc + "</ol>";
640 }
641 }
642 domActiveGuide.innerHTML = html;
643 }
374 typesList.sort(byDeclIndexName);
375 namespacesList.sort(byDeclIndexName);
376 errSetsList.sort(byDeclIndexName);
377 fnsList.sort(byDeclIndexName);
378 varsList.sort(byDeclIndexName);
379 valsList.sort(byDeclIndexName);
644380
645 // sidebar / fast navigation
646 {
647 domGuidesMenuTitle.textContent = "Sections";
648 if (zigAnalysis.guideSections.length > 1) {
649 let html = "";
650 for (let i = 1; i < zigAnalysis.guideSections.length; i += 1) {
651 const section = zigAnalysis.guideSections[i];
652 html += "<li><a href='"+ NAV_MODES.GUIDES + ":" + section.hash +"'>" + section.name + "</a></li>";
381 if (typesList.length !== 0) {
382 resizeDomList(domListTypes, typesList.length, '<li><a href="#"></a></li>');
383 for (let i = 0; i < typesList.length; i += 1) {
384 const liDom = domListTypes.children[i];
385 const aDom = liDom.children[0];
386 const decl = typesList[i];
387 aDom.textContent = declIndexName(decl);
388 aDom.setAttribute('href', navLinkDeclIndex(decl));
389 }
390 domSectTypes.classList.remove("hidden");
653391 }
654 domGuideTocList.innerHTML = "<ul>"+html+"</ul>";
655
656 function onLinkClick(ev) {
657 const link = ev.target.getAttribute("href");
658 skipNextHashChange = link;
659 location.replace(link);
660 scrollToHeading(link.split(":")[1], true);
661 ev.preventDefault();
662 ev.stopPropagation();
392 if (namespacesList.length !== 0) {
393 resizeDomList(domListNamespaces, namespacesList.length, '<li><a href="#"></a></li>');
394 for (let i = 0; i < namespacesList.length; i += 1) {
395 const liDom = domListNamespaces.children[i];
396 const aDom = liDom.children[0];
397 const decl = namespacesList[i];
398 aDom.textContent = declIndexName(decl);
399 aDom.setAttribute('href', navLinkDeclIndex(decl));
400 }
401 domSectNamespaces.classList.remove("hidden");
663402 }
664 for (let a of domGuideTocList.querySelectorAll("a")) {
665 a.addEventListener('click', onLinkClick, false);
403
404 if (errSetsList.length !== 0) {
405 resizeDomList(domListErrSets, errSetsList.length, '<li><a href="#"></a></li>');
406 for (let i = 0; i < errSetsList.length; i += 1) {
407 const liDom = domListErrSets.children[i];
408 const aDom = liDom.children[0];
409 const decl = errSetsList[i];
410 aDom.textContent = declIndexName(decl);
411 aDom.setAttribute('href', navLinkDeclIndex(decl));
412 }
413 domSectErrSets.classList.remove("hidden");
666414 }
667
668 domGuideTocList.classList.remove("hidden");
669 domGuideTocListEmtpy.classList.add("hidden");
670 } else {
671 domGuideTocList.classList.add("hidden");
672 domGuideTocListEmtpy.classList.remove("hidden");
673 }
674 }
675 }
676415
677 function noGuidesAtAll() {
678 const root_file_idx = zigAnalysis.modules[zigAnalysis.rootMod].file;
679 const root_file_name = getFile(root_file_idx).name;
680 let reader = new commonmark.Parser({smart: true});
681 let ast = reader.parse(`
682# No Guides
683These autodocs don't contain any guide.
416 if (fnsList.length !== 0) {
417 resizeDomList(domListFns, fnsList.length,
418 '<div><dt><a href="#"></a></dt><dd></dd><details><summary>source</summary><pre><code></code></pre></details></div>');
419 for (let i = 0; i < fnsList.length; i += 1) {
420 const decl = fnsList[i];
421 const divDom = domListFns.children[i];
684422
685While the API section is a reference guide autogenerated from Zig source code,
686guides are meant to be handwritten explanations that provide for example:
423 const dtName = divDom.children[0];
424 const ddDocs = divDom.children[1];
425 const codeDom = divDom.children[2].children[1].children[0];
687426
688- how-to explanations for common use-cases
689- technical documentation
690- information about advanced usage patterns
427 const nameLinkDom = dtName.children[0];
428 const expandSourceDom = dtName.children[1];
691429
692You can add guides by specifying which markdown files to include
693in the top level doc comment of your root file, like so:
430 nameLinkDom.setAttribute('href', navLinkDeclIndex(decl));
431 nameLinkDom.textContent = declIndexName(decl);
694432
695(At the top of *${root_file_name}*)
696\`\`\`
697//!zig-autodoc-guide: intro.md
698//!zig-autodoc-guide: quickstart.md
699//!zig-autodoc-guide: advanced-docs/advanced-stuff.md
700\`\`\`
433 ddDocs.innerHTML = declDocsHtmlShort(decl);
701434
702You can also create sections to group guides together:
435 codeDom.innerHTML = declSourceHtml(decl);
436 }
437 domSectFns.classList.remove("hidden");
438 }
703439
704\`\`\`
705//!zig-autodoc-section: CLI Usage
706//!zig-autodoc-guide: cli-basics.md
707//!zig-autodoc-guide: cli-advanced.md
708\`\`\`
709
440 // Prevent fields from being emptied next time wasm calls memory.grow.
441 const fields = declFields(decl_index).slice();
442 if (fields.length !== 0) {
443 resizeDomList(domListFields, fields.length, '<div></div>');
444 for (let i = 0; i < fields.length; i += 1) {
445 const divDom = domListFields.children[i];
446 divDom.innerHTML = unwrapString(wasm_exports.decl_field_html(decl_index, fields[i]));
447 }
448 domSectFields.classList.remove("hidden");
449 }
710450
711**Note that this feature is still under heavy development so expect bugs**
712**and missing features!**
451 if (varsList.length !== 0) {
452 resizeDomList(domListGlobalVars, varsList.length,
453 '<tr><td><a href="#"></a></td><td></td><td></td></tr>');
454 for (let i = 0; i < varsList.length; i += 1) {
455 const decl = varsList[i];
456 const trDom = domListGlobalVars.children[i];
713457
714Happy writing!
715`);
458 const tdName = trDom.children[0];
459 const tdNameA = tdName.children[0];
460 const tdType = trDom.children[1];
461 const tdDesc = trDom.children[2];
716462
717 let writer = new commonmark.HtmlRenderer();
718 let result = writer.render(ast);
719 domActiveGuide.innerHTML = result;
463 tdNameA.setAttribute('href', navLinkDeclIndex(decl));
464 tdNameA.textContent = declIndexName(decl);
720465
721 }
466 tdType.innerHTML = declTypeHtml(decl);
467 tdDesc.innerHTML = declDocsHtmlShort(decl);
468 }
469 domSectGlobalVars.classList.remove("hidden");
470 }
722471
723 function renderApi() {
724 // set Api mode
725 domApiSwitch.classList.add("active");
726 domGuideSwitch.classList.remove("active");
727 domGuidesSection.classList.add("hidden");
728 domSectNavAPI.classList.remove("hidden");
729 domSectNavGuides.classList.add("hidden");
730 domDocs.classList.remove("hidden");
731 domGuidesMenu.classList.add("hidden");
732 domStatus.classList.add("hidden");
733 domFnProto.classList.add("hidden");
734 domSectParams.classList.add("hidden");
735 domTldDocs.classList.add("hidden");
736 domSectTypes.classList.add("hidden");
737 domSectTests.classList.add("hidden");
738 domSectDocTests.classList.add("hidden");
739 domSectNamespaces.classList.add("hidden");
740 domListNamespacesLeft.classList.add("hidden");
741 domListNamespacesRight.classList.add("hidden");
742 domNoDocsNamespaces.classList.add("hidden");
743 domSectErrSets.classList.add("hidden");
744 domSectFns.classList.add("hidden");
745 domSectFields.classList.add("hidden");
746 domSectSearchResults.classList.add("hidden");
747 domSectSearchAllResultsLink.classList.add("hidden");
748 domSectSearchNoResults.classList.add("hidden");
749 domHdrName.classList.add("hidden");
750 domSectFnErrors.classList.add("hidden");
751 domFnExamples.classList.add("hidden");
752 domFnNoExamples.classList.add("hidden");
753 domFnSourceLink.classList.add("hidden");
754 domDeclNoRef.classList.add("hidden");
755 domFnErrorsAnyError.classList.add("hidden");
756 domTableFnErrors.classList.add("hidden");
757 domSectGlobalVars.classList.add("hidden");
758 domSectValues.classList.add("hidden");
472 if (valsList.length !== 0) {
473 resizeDomList(domListValues, valsList.length,
474 '<tr><td><a href="#"></a></td><td></td><td></td></tr>');
475 for (let i = 0; i < valsList.length; i += 1) {
476 const decl = valsList[i];
477 const trDom = domListValues.children[i];
759478
760 renderTitle();
479 const tdName = trDom.children[0];
480 const tdNameA = tdName.children[0];
481 const tdType = trDom.children[1];
482 const tdDesc = trDom.children[2];
761483
762 if (curNavSearch !== "") {
763 return renderSearchAPI();
764 }
484 tdNameA.setAttribute('href', navLinkDeclIndex(decl));
485 tdNameA.textContent = declIndexName(decl);
765486
766 let rootMod = zigAnalysis.modules[zigAnalysis.rootMod];
767 let mod = rootMod;
768 curNav.modObjs = [mod];
769 for (let i = 0; i < curNav.modNames.length; i += 1) {
770 let childMod = zigAnalysis.modules[mod.table[curNav.modNames[i]]];
771 if (childMod == null) {
772 return render404();
773 }
774 mod = childMod;
775 curNav.modObjs.push(mod);
487 tdType.innerHTML = declTypeHtml(decl);
488 tdDesc.innerHTML = declDocsHtmlShort(decl);
489 }
490 domSectValues.classList.remove("hidden");
491 }
776492 }
777493
778 let currentType = getType(mod.main);
779 curNav.declObjs = [currentType];
780 let lastDecl = mod.main;
781 for (let i = 0; i < curNav.declNames.length; i += 1) {
782 let childDecl = findSubDecl(currentType, curNav.declNames[i]);
783 window.last_decl = childDecl;
784 if (childDecl == null || childDecl.is_private === true) {
785 return render404();
786 }
787 lastDecl = childDecl;
788
789 let childDeclValue = resolveValue(childDecl.value).expr;
790 if ("type" in childDeclValue) {
791 const t = getType(childDeclValue.type);
792 if (t.kind != typeKinds.Fn) {
793 childDecl = t;
494 function operatorCompare(a, b) {
495 if (a === b) {
496 return 0;
497 } else if (a < b) {
498 return -1;
499 } else {
500 return 1;
794501 }
795 }
796
797 currentType = childDecl;
798 curNav.declObjs.push(currentType);
799502 }
800503
504 function updateCurNav(location_hash) {
505 curNav.tag = 0;
506 curNav.decl = null;
507 curNav.path = null;
508 curNav.viewSourceHash = null;
509 curNavSearch = "";
801510
511 if (location_hash[0] === '#' && location_hash.length > 1) {
512 const query = location_hash.substring(1);
513 const qpos = query.indexOf("?");
514 let nonSearchPart;
515 if (qpos === -1) {
516 nonSearchPart = query;
517 } else {
518 nonSearchPart = query.substring(0, qpos);
519 curNavSearch = decodeURIComponent(query.substring(qpos + 1));
520 }
802521
803 window.x = currentType;
804
805 renderNav();
806
807 let last = curNav.declObjs[curNav.declObjs.length - 1];
808 let lastIsDecl = isDecl(last);
809 let lastIsType = isType(last);
810 let lastIsContainerType = isContainerType(last);
811
812 renderDocTest(lastDecl);
813
814 if (lastIsContainerType) {
815 return renderContainer(last);
522 if (nonSearchPart.length > 0) {
523 const source_mode = nonSearchPart.startsWith("src/");
524 if (source_mode) {
525 curNav.tag = 2;
526 curNav.path = nonSearchPart.substring(4);
527 } else {
528 curNav.tag = 1;
529 curNav.decl = findDecl(nonSearchPart);
530 }
531 }
532 }
816533 }
817534
818 if (!lastIsDecl && !lastIsType) {
819 return renderUnknownDecl(last);
535 function onHashChange() {
536 navigate(location.hash);
820537 }
821538
822 if (lastIsType) {
823 return renderType(last);
539 function navigate(location_hash) {
540 updateCurNav(location_hash);
541 if (domSearch.value !== curNavSearch) {
542 domSearch.value = curNavSearch;
543 }
544 render();
545 if (imFeelingLucky) {
546 imFeelingLucky = false;
547 activateSelectedResult();
548 }
824549 }
825550
826 if (lastIsDecl && last.kind === "var") {
827 return renderVar(last);
828 }
551 function activateSelectedResult() {
552 if (domSectSearchResults.classList.contains("hidden")) {
553 return;
554 }
829555
830 if (lastIsDecl && last.kind === "const") {
831 const value = resolveValue(last.value);
832 if ("type" in value.expr) {
833 let typeObj = getType(value.expr.type);
834 if (typeObj.kind === typeKinds.Fn) {
835 return renderFn(last);
556 var liDom = domListSearchResults.children[curSearchIndex];
557 if (liDom == null && domListSearchResults.children.length !== 0) {
558 liDom = domListSearchResults.children[0];
836559 }
837 }
838 return renderValue(last);
560 if (liDom != null) {
561 var aDom = liDom.children[0];
562 location.href = aDom.getAttribute("href");
563 curSearchIndex = -1;
564 }
565 domSearch.blur();
839566 }
840567
841 }
568 function onSearchKeyDown(ev) {
569 switch (ev.which) {
570 case 13:
571 if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
842572
843 function render() {
844 switch (curNav.mode) {
845 case NAV_MODES.API:
846 return renderApi();
847 case NAV_MODES.GUIDES:
848 return renderGuides();
849 default:
850 throw "?";
851 }
852 }
573 clearAsyncSearch();
574 imFeelingLucky = true;
575 location.hash = computeSearchHash();
853576
577 ev.preventDefault();
578 ev.stopPropagation();
579 return;
580 case 27:
581 if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
854582
855 function renderDocTest(decl) {
856 if (!decl.decltest) return;
857 const astNode = getAstNode(decl.decltest);
858 domSectDocTests.classList.remove("hidden");
859 domDocTestsCode.innerHTML = renderTokens(
860 DecoratedTokenizer(astNode.code, decl));
861 }
583 domSearch.value = "";
584 domSearch.blur();
585 curSearchIndex = -1;
586 ev.preventDefault();
587 ev.stopPropagation();
588 startSearch();
589 return;
590 case 38:
591 if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
862592
863 function renderUnknownDecl(decl) {
864 domDeclNoRef.classList.remove("hidden");
593 moveSearchCursor(-1);
594 ev.preventDefault();
595 ev.stopPropagation();
596 return;
597 case 40:
598 if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
865599
866 let docs = getAstNode(decl.src).docs;
867 if (docs != null) {
868 domTldDocs.innerHTML = markdown(docs);
869 } else {
870 domTldDocs.innerHTML =
871 "<p>There are no doc comments for this declaration.</p>";
600 moveSearchCursor(1);
601 ev.preventDefault();
602 ev.stopPropagation();
603 return;
604 default:
605 ev.stopPropagation(); // prevent keyboard shortcuts
606 return;
607 }
872608 }
873 domTldDocs.classList.remove("hidden");
874 }
875
876 function typeIsErrSet(typeIndex) {
877 let typeObj = getType(typeIndex);
878 return typeObj.kind === typeKinds.ErrorSet;
879 }
880609
881 function typeIsStructWithNoFields(typeIndex) {
882 let typeObj = getType(typeIndex);
883 if (typeObj.kind !== typeKinds.Struct) return false;
884 return typeObj.field_types.length == 0;
885 }
886
887 function typeIsGenericFn(typeIndex) {
888 let typeObj = getType(typeIndex);
889 if (typeObj.kind !== typeKinds.Fn) {
890 return false;
610 function onSearchChange(ev) {
611 curSearchIndex = -1;
612 startAsyncSearch();
891613 }
892 return typeObj.generic_ret != null;
893 }
894614
895 function renderFn(fnDecl) {
896 if ("refPath" in fnDecl.value.expr) {
897 let last = fnDecl.value.expr.refPath.length - 1;
898 let lastExpr = fnDecl.value.expr.refPath[last];
899 console.assert("declRef" in lastExpr);
900 fnDecl = getDecl(lastExpr.declRef);
615 function moveSearchCursor(dir) {
616 if (curSearchIndex < 0 || curSearchIndex >= domListSearchResults.children.length) {
617 if (dir > 0) {
618 curSearchIndex = -1 + dir;
619 } else if (dir < 0) {
620 curSearchIndex = domListSearchResults.children.length + dir;
621 }
622 } else {
623 curSearchIndex += dir;
624 }
625 if (curSearchIndex < 0) {
626 curSearchIndex = 0;
627 }
628 if (curSearchIndex >= domListSearchResults.children.length) {
629 curSearchIndex = domListSearchResults.children.length - 1;
630 }
631 renderSearchCursor();
901632 }
902633
903 let value = resolveValue(fnDecl.value);
904 console.assert("type" in value.expr);
905 let typeObj = getType(value.expr.type);
906
907 domFnProtoCode.innerHTML = renderTokens(ex(value.expr, { fnDecl: fnDecl }));
908 domFnSourceLink.classList.remove("hidden");
909 domFnSourceLink.innerHTML = "[<a target=\"_blank\" href=\"" + sourceFileLink(fnDecl) + "\">src</a>]";
910
911 let docsSource = null;
912 let srcNode = getAstNode(fnDecl.src);
913 if (srcNode.docs != null) {
914 docsSource = srcNode.docs;
634 function onWindowKeyDown(ev) {
635 switch (ev.which) {
636 case 27:
637 if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
638 if (!domHelpModal.classList.contains("hidden")) {
639 domHelpModal.classList.add("hidden");
640 ev.preventDefault();
641 ev.stopPropagation();
642 }
643 break;
644 case 83:
645 if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
646 domSearch.focus();
647 domSearch.select();
648 ev.preventDefault();
649 ev.stopPropagation();
650 startAsyncSearch();
651 break;
652 case 85:
653 if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
654 ev.preventDefault();
655 ev.stopPropagation();
656 navigateToSource();
657 break;
658 case 191:
659 if (!ev.shiftKey || ev.ctrlKey || ev.altKey) return;
660 ev.preventDefault();
661 ev.stopPropagation();
662 showHelpModal();
663 break;
664 }
915665 }
916666
917 renderFnParamDocs(fnDecl, typeObj);
918
919 let retExpr = resolveValue({ expr: typeObj.ret }).expr;
920 if ("type" in retExpr) {
921 let retIndex = retExpr.type;
922 let errSetTypeIndex = null;
923 let retType = getType(retIndex);
924 if (retType.kind === typeKinds.ErrorSet) {
925 errSetTypeIndex = retIndex;
926 } else if (retType.kind === typeKinds.ErrorUnion) {
927 errSetTypeIndex = retType.err.type;
928 }
929 if (errSetTypeIndex != null) {
930 let errSetType = getType(errSetTypeIndex);
931 renderErrorSet(errSetType);
932 }
667 function showHelpModal() {
668 domHelpModal.classList.remove("hidden");
669 domHelpModal.style.left = (window.innerWidth / 2 - domHelpModal.clientWidth / 2) + "px";
670 domHelpModal.style.top = (window.innerHeight / 2 - domHelpModal.clientHeight / 2) + "px";
671 domHelpModal.focus();
933672 }
934673
935 let protoSrcIndex = fnDecl.src;
936 if (typeIsGenericFn(value.expr.type)) {
937 // does the generic_ret contain a container?
938 var resolvedGenericRet = resolveValue({ expr: typeObj.generic_ret });
939
940 if ("call" in resolvedGenericRet.expr) {
941 let call = zigAnalysis.calls[resolvedGenericRet.expr.call];
942 let resolvedFunc = resolveValue({ expr: call.func });
943 if (!("type" in resolvedFunc.expr)) return;
944 let callee = getType(resolvedFunc.expr.type);
945 if (!callee.generic_ret) return;
946 resolvedGenericRet = resolveValue({ expr: callee.generic_ret });
947 }
948
949 // TODO: see if unwrapping the `as` here is a good idea or not.
950 if ("as" in resolvedGenericRet.expr) {
951 resolvedGenericRet = {
952 expr: zigAnalysis.exprs[resolvedGenericRet.expr.as.exprArg],
953 };
954 }
955
956 if (!("type" in resolvedGenericRet.expr)) return;
957 const genericType = getType(resolvedGenericRet.expr.type);
958 if (isContainerType(genericType)) {
959 renderContainer(genericType);
674 function navigateToSource() {
675 if (curNav.viewSourceHash != null) {
676 location.hash = curNav.viewSourceHash;
960677 }
961
962 // old code
963 // let instantiations = nodesToFnsMap[protoSrcIndex];
964 // let calls = nodesToCallsMap[protoSrcIndex];
965 // if (instantiations == null && calls == null) {
966 // domFnNoExamples.classList.remove("hidden");
967 // } else if (calls != null) {
968 // // if (fnObj.combined === undefined) fnObj.combined = allCompTimeFnCallsResult(calls);
969 // if (fnObj.combined != null) renderContainer(fnObj.combined);
970
971 // resizeDomList(domListFnExamples, calls.length, '<li></li>');
972
973 // for (let callI = 0; callI < calls.length; callI += 1) {
974 // let liDom = domListFnExamples.children[callI];
975 // liDom.innerHTML = getCallHtml(fnDecl, calls[callI]);
976 // }
977
978 // domFnExamples.classList.remove("hidden");
979 // } else if (instantiations != null) {
980 // // TODO
981 // }
982 } else {
983 domFnExamples.classList.add("hidden");
984 domFnNoExamples.classList.add("hidden");
985678 }
986679
987 let protoSrcNode = getAstNode(protoSrcIndex);
988 if (
989 docsSource == null &&
990 protoSrcNode != null &&
991 protoSrcNode.docs != null
992 ) {
993 docsSource = protoSrcNode.docs;
994 }
995 if (docsSource != null) {
996 domTldDocs.innerHTML = markdown(docsSource, fnDecl);
997 domTldDocs.classList.remove("hidden");
680 function clearAsyncSearch() {
681 if (searchTimer != null) {
682 clearTimeout(searchTimer);
683 searchTimer = null;
684 }
998685 }
999 domFnProto.classList.remove("hidden");
1000 }
1001
1002 function renderFnParamDocs(fnDecl, typeObj) {
1003 let docCount = 0;
1004686
1005 let fnNode = getAstNode(fnDecl.src);
1006 let fields = fnNode.fields;
1007 if (fields === null) {
1008 fields = getAstNode(typeObj.src).fields;
687 function startAsyncSearch() {
688 clearAsyncSearch();
689 searchTimer = setTimeout(startSearch, 100);
1009690 }
1010 let isVarArgs = typeObj.is_var_args;
1011
1012 for (let i = 0; i < fields.length; i += 1) {
1013 let field = fields[i];
1014 let fieldNode = getAstNode(field);
1015 if (fieldNode.docs != null) {
1016 docCount += 1;
1017 }
691 function computeSearchHash() {
692 const oldHash = location.hash;
693 const parts = oldHash.split("?");
694 const newPart2 = (domSearch.value === "") ? "" : ("?" + domSearch.value);
695 return (parts.length === 1) ? (oldHash + newPart2) : ("#" + parts[0] + newPart2);
1018696 }
1019 if (docCount == 0) {
1020 return;
697 function startSearch() {
698 clearAsyncSearch();
699 navigate(computeSearchHash());
1021700 }
701 function renderSearch() {
702 renderNav(curNav.decl);
1022703
1023 resizeDomList(domListParams, docCount, "<div></div>");
1024 let domIndex = 0;
704 const ignoreCase = (curNavSearch.toLowerCase() === curNavSearch);
705 const results = executeQuery(curNavSearch, ignoreCase);
1025706
1026 for (let i = 0; i < fields.length; i += 1) {
1027 let field = fields[i];
1028 let fieldNode = getAstNode(field);
1029 let docs = fieldNode.docs;
1030 if (fieldNode.docs == null) {
1031 continue;
1032 }
1033 let docsNonEmpty = docs !== "";
1034 let divDom = domListParams.children[domIndex];
1035 domIndex += 1;
707 if (results.length !== 0) {
708 resizeDomList(domListSearchResults, results.length, '<li><a href="#"></a></li>');
709
710 for (let i = 0; i < results.length; i += 1) {
711 const liDom = domListSearchResults.children[i];
712 const aDom = liDom.children[0];
713 const match = results[i];
714 const full_name = fullyQualifiedName(match);
715 aDom.textContent = full_name;
716 aDom.setAttribute('href', navLinkFqn(full_name));
717 }
718 renderSearchCursor();
1036719
1037 let value = typeObj.params[i];
1038 let preClass = docsNonEmpty ? ' class="fieldHasDocs"' : "";
1039 let html = "<pre" + preClass + ">" + renderTokens((function*() {
1040 yield Tok.identifier(fieldNode.name);
1041 yield Tok.colon;
1042 yield Tok.space;
1043 if (isVarArgs && i === typeObj.params.length - 1) {
1044 yield Tok.period;
1045 yield Tok.period;
1046 yield Tok.period;
720 domSectSearchResults.classList.remove("hidden");
1047721 } else {
1048 yield* ex(value, {});
722 domSectSearchNoResults.classList.remove("hidden");
1049723 }
1050 yield Tok.comma;
1051 }()));
1052
1053 html += "</pre>";
1054
1055 if (docsNonEmpty) {
1056 html += '<div class="fieldDocs">' + markdown(docs) + "</div>";
1057 }
1058 divDom.innerHTML = html;
1059724 }
1060 domSectParams.classList.remove("hidden");
1061 }
1062725
1063 function renderNav() {
1064 let len = curNav.modNames.length + curNav.declNames.length;
1065 resizeDomList(domListNavAPI, len, '<li><a href="#"></a></li>');
1066 let list = [];
1067 let hrefModNames = [];
1068 let hrefDeclNames = [];
1069 for (let i = 0; i < curNav.modNames.length; i += 1) {
1070 hrefModNames.push(curNav.modNames[i]);
1071 let name = curNav.modNames[i];
1072 list.push({
1073 name: name,
1074 link: navLink(hrefModNames, hrefDeclNames),
1075 });
1076 }
1077 for (let i = 0; i < curNav.declNames.length; i += 1) {
1078 hrefDeclNames.push(curNav.declNames[i]);
1079 list.push({
1080 name: curNav.declNames[i],
1081 link: navLink(hrefModNames, hrefDeclNames),
1082 });
726 function renderSearchCursor() {
727 for (let i = 0; i < domListSearchResults.children.length; i += 1) {
728 var liDom = domListSearchResults.children[i];
729 if (curSearchIndex === i) {
730 liDom.classList.add("selected");
731 } else {
732 liDom.classList.remove("selected");
733 }
734 }
1083735 }
1084736
1085 for (let i = 0; i < list.length; i += 1) {
1086 let liDom = domListNavAPI.children[i];
1087 let aDom = liDom.children[0];
1088 aDom.textContent = list[i].name;
1089 aDom.setAttribute("href", list[i].link);
1090 if (i + 1 == list.length) {
1091 aDom.classList.add("active");
1092 } else {
1093 aDom.classList.remove("active");
737 function updatePackageList() {
738 packageList.length = 0;
739 for (let i = 0;; i += 1) {
740 const name = unwrapString(wasm_exports.package_name(i));
741 if (name.length == 0) break;
742 packageList.push(name);
1094743 }
1095744 }
1096745
1097 }
1098
1099
1100 function render404() {
1101 domStatus.textContent = "404 Not Found";
1102 domStatus.classList.remove("hidden");
1103 }
1104
1105 // function renderModList() {
1106 // const rootMod = zigAnalysis.modules[zigAnalysis.rootMod];
1107 // let list = [];
1108 // for (let key in rootMod.table) {
1109 // let modIndex = rootMod.table[key];
1110 // if (zigAnalysis.modules[modIndex] == null) continue;
1111 // if (key == rootMod.name) continue;
1112 // list.push({
1113 // name: key,
1114 // mod: modIndex,
1115 // });
1116 // }
1117
1118 // {
1119 // let aDom = domSectMainMod.children[1].children[0].children[0];
1120 // aDom.textContent = rootMod.name;
1121 // aDom.setAttribute("href", navLinkMod(zigAnalysis.rootMod));
1122 // if (rootMod.name === curNav.modNames[0]) {
1123 // aDom.classList.add("active");
1124 // } else {
1125 // aDom.classList.remove("active");
1126 // }
1127 // domSectMainMod.classList.remove("hidden");
1128 // }
1129
1130 // list.sort(function (a, b) {
1131 // return operatorCompare(a.name.toLowerCase(), b.name.toLowerCase());
1132 // });
1133
1134 // if (list.length !== 0) {
1135 // resizeDomList(domListMods, list.length, '<li><a href="#"></a></li>');
1136 // for (let i = 0; i < list.length; i += 1) {
1137 // let liDom = domListMods.children[i];
1138 // let aDom = liDom.children[0];
1139 // aDom.textContent = list[i].name;
1140 // aDom.setAttribute("href", navLinkMod(list[i].mod));
1141 // if (list[i].name === curNav.modNames[0]) {
1142 // aDom.classList.add("active");
1143 // } else {
1144 // aDom.classList.remove("active");
1145 // }
1146 // }
1147
1148 // domSectMods.classList.remove("hidden");
1149 // }
1150 // }
1151
1152 function navLink(modNames, declNames, callName) {
1153 let base = curNav.mode;
1154
1155 if (modNames.length === 0 && declNames.length === 0) {
1156 return base;
1157 } else if (declNames.length === 0 && callName == null) {
1158 return base + modNames.join(".");
1159 } else if (callName == null) {
1160 return base + modNames.join(".") + ":" + declNames.join(".");
1161 } else {
1162 return (
1163 base + modNames.join(".") + ":" + declNames.join(".") + ";" + callName
1164 );
746 function byDeclIndexName(a, b) {
747 const a_name = declIndexName(a);
748 const b_name = declIndexName(b);
749 return operatorCompare(a_name, b_name);
1165750 }
1166 }
1167
1168 function navLinkMod(modIndex) {
1169 return navLink(canonModPaths[modIndex], []);
1170 }
1171
1172 function navLinkDecl(childName) {
1173 return navLink(curNav.modNames, curNav.declNames.concat([childName]));
1174 }
1175
1176 function findDeclNavLink(declName) {
1177 if (curNav.declObjs.length == 0) return null;
1178 const curFile = getAstNode(curNav.declObjs[curNav.declObjs.length - 1].src).file;
1179
1180 for (let i = curNav.declObjs.length - 1; i >= 0; i--) {
1181 const curDecl = curNav.declObjs[i];
1182 const curDeclName = curNav.declNames[i - 1];
1183 if (curDeclName == declName) {
1184 const declPath = curNav.declNames.slice(0, i);
1185 return navLink(curNav.modNames, declPath);
1186 }
1187
1188 const subDecl = findSubDecl(curDecl, declName);
1189751
1190 if (subDecl != null) {
1191 if (subDecl.is_private === true) {
1192 return sourceFileLink(subDecl);
1193 } else {
1194 const declPath = curNav.declNames.slice(0, i).concat([declName]);
1195 return navLink(curNav.modNames, declPath);
1196 }
1197 }
752 function decodeString(ptr, len) {
753 if (len === 0) return "";
754 return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len));
1198755 }
1199756
1200 //throw("could not resolve links for '" + declName + "'");
1201 }
1202
1203 //
1204 // function navLinkCall(callObj) {
1205 // let declNamesCopy = curNav.declNames.concat([]);
1206 // let callName = (declNamesCopy.pop());
1207
1208 // callName += '(';
1209 // for (let arg_i = 0; arg_i < callObj.args.length; arg_i += 1) {
1210 // if (arg_i !== 0) callName += ',';
1211 // let argObj = callObj.args[arg_i];
1212 // callName += getValueText(argObj, argObj, false, false);
1213 // }
1214 // callName += ')';
1215
1216 // declNamesCopy.push(callName);
1217 // return navLink(curNav.modNames, declNamesCopy);
1218 // }
1219
1220 function resizeDomListDl(dlDom, desiredLen) {
1221 // add the missing dom entries
1222 for (let i = dlDom.childElementCount / 2; i < desiredLen; i += 1) {
1223 dlDom.insertAdjacentHTML("beforeend", "<dt></dt><dd></dd>");
1224 }
1225 // remove extra dom entries
1226 while (desiredLen < dlDom.childElementCount / 2) {
1227 dlDom.removeChild(dlDom.lastChild);
1228 dlDom.removeChild(dlDom.lastChild);
757 function unwrapString(bigint) {
758 const ptr = Number(bigint & 0xffffffffn);
759 const len = Number(bigint >> 32n);
760 return decodeString(ptr, len);
1229761 }
1230 }
1231762
1232 function resizeDomList(listDom, desiredLen, templateHtml) {
1233 // add the missing dom entries
1234 for (let i = listDom.childElementCount; i < desiredLen; i += 1) {
1235 listDom.insertAdjacentHTML("beforeend", templateHtml);
763 function declTypeHtml(decl_index) {
764 return unwrapString(wasm_exports.decl_type_html(decl_index));
1236765 }
1237 // remove extra dom entries
1238 while (desiredLen < listDom.childElementCount) {
1239 listDom.removeChild(listDom.lastChild);
1240 }
1241 }
1242766
1243 function walkResultTypeRef(wr) {
1244 if (wr.typeRef) return wr.typeRef;
1245 let resolved = resolveValue(wr);
1246 if (wr === resolved) {
1247 return { "undefined": {} };
767 function declDocsHtmlShort(decl_index) {
768 return unwrapString(wasm_exports.decl_docs_html(decl_index, true));
1248769 }
1249 return walkResultTypeRef(resolved);
1250 }
1251
1252 function* DecoratedTokenizer(src, context) {
1253 let tok_it = Tokenizer(src);
1254 for (let t of tok_it) {
1255 if (t.tag == Tag.identifier) {
1256 const link = detectDeclPath(t.src, context);
1257 if (link) {
1258 t.link = link;
1259 }
1260 }
1261770
1262 yield t;
771 function fullyQualifiedName(decl_index) {
772 return unwrapString(wasm_exports.decl_fqn(decl_index));
1263773 }
1264 }
1265774
1266
1267 function renderSingleToken(t) {
1268
1269 if (t.tag == Tag.whitespace) {
1270 return t.src;
775 function declIndexName(decl_index) {
776 return unwrapString(wasm_exports.decl_name(decl_index));
1271777 }
1272778
1273 let src = t.src;
1274 // if (t.tag == Tag.identifier) {
1275 // src = escapeHtml(src);
1276 // }
1277 let result = "";
1278 if (t.tag == Tag.identifier && isSimpleType(t.src)) {
1279 result = `<span class="zig_type">${src}</span>`;
1280 } else if (t.tag == Tag.identifier && isSpecialIndentifier(t.src)) {
1281 result = `<span class="zig_special">${src}</span>`;
1282 } else if (t.tag == Tag.identifier && t.fnDecl) {
1283 result = `<span class="zig_fn">${src}</span>`;
1284 } else if (t.tag == Tag.identifier && t.isDecl) {
1285 result = `<span class="zig_decl_identifier">${src}</span>`;
1286 } else {
1287 result = `<span class="zig_${t.tag}">${src}</span>`;
779 function declSourceHtml(decl_index) {
780 return unwrapString(wasm_exports.decl_source_html(decl_index));
1288781 }
1289782
1290 if (t.link) {
1291 result = `<a href="${t.link}">` + result + "</a>";
783 function declDoctestHtml(decl_index) {
784 return unwrapString(wasm_exports.decl_doctest_html(decl_index));
1292785 }
1293786
1294 return result;
1295 }
1296
1297 function renderTokens(tok_it) {
1298 var html = [];
1299
1300 const max_iter = 100000;
1301 let i = 0;
1302 for (const t of tok_it) {
1303 i += 1;
1304 if (i > max_iter)
1305 throw "too many iterations";
787 function fnProtoHtml(decl_index) {
788 return unwrapString(wasm_exports.decl_fn_proto_html(decl_index));
789 }
1306790
1307 if (t.tag == Tag.eof)
1308 break;
791 function setQueryString(s) {
792 const jsArray = text_encoder.encode(s);
793 const len = jsArray.length;
794 const ptr = wasm_exports.query_begin(len);
795 const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, len);
796 wasmArray.set(jsArray);
797 }
1309798
1310 html.push(renderSingleToken(t));
799 function executeQuery(query_string, ignore_case) {
800 setQueryString(query_string);
801 const ptr = wasm_exports.query_exec(ignore_case);
802 const head = new Uint32Array(wasm_exports.memory.buffer, ptr, 1);
803 const len = head[0];
804 return new Uint32Array(wasm_exports.memory.buffer, ptr + 4, len);
1311805 }
1312806
1313 return html.join("");
1314 }
807 function namespaceMembers(decl_index, include_private) {
808 const bigint = wasm_exports.namespace_members(decl_index, include_private);
809 const ptr = Number(bigint & 0xffffffffn);
810 const len = Number(bigint >> 32n);
811 if (len == 0) return [];
812 return new Uint32Array(wasm_exports.memory.buffer, ptr, len);
813 }
1315814
1316 function* ex(expr, opts) {
1317 switch (Object.keys(expr)[0]) {
1318 default:
1319 throw "this expression is not implemented yet: " + Object.keys(expr)[0];
1320 case "comptimeExpr": {
1321 const src = zigAnalysis.comptimeExprs[expr.comptimeExpr].code;
1322 yield* DecoratedTokenizer(src);
1323 return;
1324 }
1325 case "declName": {
1326 yield { src: expr.declName, tag: Tag.identifier };
1327 return;
1328 }
1329 case "declRef": {
1330 const name = getDecl(expr.declRef).name;
1331 const link = declLinkOrSrcLink(expr.declRef);
1332 if (link) {
1333 yield { src: name, tag: Tag.identifier, isDecl: true, link };
1334 } else {
1335 yield { src: name, tag: Tag.identifier, isDecl: true };
1336 }
1337 return;
1338 }
1339 case "refPath": {
1340 for (let i = 0; i < expr.refPath.length; i += 1) {
1341 if (i > 0) yield Tok.period;
1342 yield* ex(expr.refPath[i], opts);
1343 }
1344 return;
1345 }
1346 case "fieldRef": {
1347 const field_idx = expr.fieldRef.index;
1348 const type = getType(expr.fieldRef.type);
1349 const field = getAstNode(type.src).fields[field_idx];
1350 const name = getAstNode(field).name;
1351 yield { src: name, tag: Tag.identifier };
1352 return;
1353 }
1354 case "bool": {
1355 if (expr.bool) {
1356 yield { src: "true", tag: Tag.identifier };
1357 return;
1358 }
1359 yield { src: "false", tag: Tag.identifier };
1360 return;
1361 }
815 function declFields(decl_index) {
816 const bigint = wasm_exports.decl_fields(decl_index);
817 const ptr = Number(bigint & 0xffffffffn);
818 const len = Number(bigint >> 32n);
819 if (len === 0) return [];
820 return new Uint32Array(wasm_exports.memory.buffer, ptr, len);
821 }
1362822
1363 case "unreachable": {
1364 yield { src: "unreachable", tag: Tag.identifier };
1365 return;
1366 }
823 function findDecl(fqn) {
824 setInputString(fqn);
825 const result = wasm_exports.find_decl();
826 if (result === -1) return null;
827 return result;
828 }
1367829
1368 case "&": {
1369 yield { src: "&", tag: Tag.ampersand };
1370 yield* ex(zigAnalysis.exprs[expr["&"]], opts);
1371 return;
1372 }
830 function findFileRoot(path) {
831 setInputString(path);
832 const result = wasm_exports.find_file_root();
833 if (result === -1) return null;
834 return result;
835 }
1373836
1374 case "load": {
1375 yield* ex(zigAnalysis.exprs[expr.load], opts);
1376 yield Tok.period;
1377 yield Tok.asterisk;
1378 return;
1379 }
837 function declParent(decl_index) {
838 const result = wasm_exports.decl_parent(decl_index);
839 if (result === -1) return null;
840 return result;
841 }
1380842
1381 case "call": {
1382
1383 let call = zigAnalysis.calls[expr.call];
1384
1385 switch (Object.keys(call.func)[0]) {
1386 default:
1387 throw "TODO";
1388 case "declRef":
1389 case "refPath": {
1390 yield* ex(call.func, opts);
1391 break;
1392 }
1393 }
1394 yield Tok.l_paren;
1395
1396 for (let i = 0; i < call.args.length; i++) {
1397 if (i != 0) {
1398 yield Tok.comma;
1399 yield Tok.space;
1400 }
1401 yield* ex(call.args[i], opts);
1402 }
1403
1404 yield Tok.r_paren;
1405 return;
1406 }
1407 case "typeOf_peer": {
1408 yield { src: "@TypeOf", tag: Tag.builtin };
1409 yield { src: "(", tag: Tag.l_paren };
1410 for (let i = 0; i < expr.typeOf_peer.length; i+=1) {
1411 const elem = zigAnalysis.exprs[expr.typeOf_peer[i]];
1412 yield* ex(elem, opts);
1413 if (i != expr.typeOf_peer.length - 1) {
1414 yield Tok.comma;
1415 yield Tok.space;
1416 }
1417 }
1418 yield { src: ")", tag: Tag.r_paren };
1419 return;
1420 }
1421 case "sizeOf": {
1422 const sizeOf = zigAnalysis.exprs[expr.sizeOf];
1423 yield { src: "@sizeOf", tag: Tag.builtin };
1424 yield Tok.l_paren;
1425 yield* ex(sizeOf, opts);
1426 yield Tok.r_paren;
1427 return;
1428 }
1429 case "bitSizeOf": {
1430 const bitSizeOf = zigAnalysis.exprs[expr.bitSizeOf];
1431 yield { src: "@bitSizeOf", tag: Tag.builtin };
1432 yield Tok.l_paren;
1433 yield* ex(bitSizeOf, opts);
1434 yield Tok.r_paren;
1435 return;
1436 }
1437
1438 case "as": {
1439 const exprArg = zigAnalysis.exprs[expr.as.exprArg];
1440 yield* ex(exprArg, opts);
1441 return;
1442 }
1443
1444 case "int": {
1445 yield { src: expr.int, tag: Tag.number_literal };
1446 return;
1447 }
1448
1449 case "int_big": {
1450 if (expr.int_big.negated) {
1451 yield { src: "-", tag: Tag.minus };
1452 }
1453 yield { src: expr.int_big.value, tag: Tag.number_literal };
1454 return;
1455 }
1456
1457 case "float": {
1458 let float = expr.float;
1459 if (Number.isSafeInteger(float)) float = float.toFixed(1);
1460 yield { src: float, tag: Tag.number_literal };
1461 return;
1462 }
1463
1464 case "float128": {
1465 yield { src: expr.float128, tag: Tag.number_literal };
1466 return;
1467 }
1468
1469 case "array": {
1470 yield Tok.period;
1471 yield Tok.l_brace;
1472 for (let i = 0; i < expr.array.length; i++) {
1473 if (i != 0) {
1474 yield Tok.comma;
1475 yield Tok.space;
1476 }
1477 let elem = zigAnalysis.exprs[expr.array[i]];
1478 yield* ex(elem, opts);
1479 }
1480 yield Tok.r_brace;
1481 return;
1482 }
1483
1484 case "compileError": {
1485 yield { src: "@compileError", tag: Tag.builtin };
1486 yield Tok.l_paren;
1487 yield* ex(zigAnalysis.exprs[expr.compileError], opts);
1488 yield Tok.r_paren;
1489 return;
1490 }
1491
1492 case "optionalPayload": {
1493 const opt = zigAnalysis.exprs[expr.optionalPayload];
1494 yield* ex(opt, opts);
1495 yield Tok.period;
1496 yield Tok.question_mark;
1497 return;
1498 }
1499
1500 case "elemVal": {
1501 const lhs = zigAnalysis.exprs[expr.elemVal.lhs];
1502 const rhs = zigAnalysis.exprs[expr.elemVal.rhs];
1503 yield* ex(lhs);
1504 yield Tok.l_bracket;
1505 yield* ex(rhs);
1506 yield Tok.r_bracket;
1507 return;
1508 }
1509
1510 case "sliceIndex": {
1511 const slice = zigAnalysis.exprs[expr.sliceIndex];
1512 yield* ex(slice, opts);
1513 return;
1514 }
1515
1516 case "slice": {
1517 const slice = expr.slice;
1518 const lhs = zigAnalysis.exprs[slice.lhs];
1519 const start = zigAnalysis.exprs[slice.start];
1520 yield* ex(lhs, opts);
1521 yield Tok.l_bracket;
1522 yield* ex(start, opts);
1523 yield Tok.period;
1524 yield Tok.period;
1525 if (slice.end !== null) {
1526 const end = zigAnalysis.exprs[slice.end];
1527 yield* ex(end, opts);
1528 }
1529 if (slice.sentinel !== null) {
1530 yield Tok.colon;
1531 const sent = zigAnalysis.exprs[slice.sentinel];
1532 yield* ex(sent, opts);
1533 }
1534 yield Tok.r_brace;
1535 return;
1536 }
1537
1538 case "sliceLength": {
1539 const slice = expr.sliceLength;
1540 const lhs = zigAnalysis.exprs[slice.lhs];
1541 const start = zigAnalysis.exprs[slice.start];
1542 const len = zigAnalysis.exprs[slice.len];
1543 yield* ex(lhs, opts);
1544 yield Tok.l_bracket;
1545 yield* ex(start, opts);
1546 yield Tok.period;
1547 yield Tok.period;
1548 yield Tok.r_bracket;
1549 yield Tok.l_bracket;
1550 yield { src: "0", tag: Tag.number_literal };
1551 yield Tok.period;
1552 yield Tok.period;
1553 yield* ex(len, opts);
1554 if (slice.sentinel !== null) {
1555 yield Tok.colon;
1556 const sent = zigAnalysis.exprs[slice.sentinel];
1557 yield* ex(sent, opts);
1558 }
1559 yield Tok.r_brace;
1560 return;
1561 }
1562
1563 case "string": {
1564 yield { src: '"' + expr.string + '"', tag: Tag.string_literal };
1565 return;
1566 }
1567
1568 case "struct": {
1569 yield Tok.period;
1570 yield Tok.l_brace;
1571 if (expr.struct.length > 0) yield Tok.space;
1572
1573 for (let i = 0; i < expr.struct.length; i++) {
1574 const fv = expr.struct[i];
1575 const field_name = fv.name;
1576 const field_expr = zigAnalysis.exprs[fv.val.expr];
1577 const field_value = ex(field_expr, opts);
1578 yield Tok.period;
1579 yield { src: field_name, tag: Tag.identifier };
1580 yield Tok.space;
1581 yield Tok.eql;
1582 yield Tok.space;
1583 yield* field_value;
1584 if (i !== expr.struct.length - 1) {
1585 yield Tok.comma;
1586 yield Tok.space;
1587 } else {
1588 yield Tok.space;
1589 }
1590 }
1591 yield Tok.r_brace;
1592 return;
1593 }
1594
1595 case "unOpIndex": {
1596 const unOp = zigAnalysis.exprs[expr.unOpIndex];
1597 yield* ex(unOp, opts);
1598 return;
1599 }
1600
1601 case "unOp": {
1602 const param = zigAnalysis.exprs[expr.unOp.param];
1603
1604 switch (expr.unOp.name) {
1605 case "bit_not": {
1606 yield { src: "~", tag: Tag.tilde };
1607 break;
1608 }
1609 case "bool_not": {
1610 yield { src: "!", tag: Tag.bang };
1611 break;
1612 }
1613 case "negate_wrap": {
1614 yield { src: "-%", tag: Tag.minus_percent };
1615 break;
1616 }
1617 case "negate": {
1618 yield { src: "-", tag: Tag.minus };
1619 break;
1620 }
1621 default:
1622 throw "unOp: `" + expr.unOp.name + "` not implemented yet!"
1623 }
1624
1625 if (param["binOpIndex"] !== undefined) {
1626 yield Tok.l_paren;
1627 yield* ex(param, opts);
1628 yield Tok.r_paren;
1629 } else {
1630 yield* ex(param, opts);
1631 }
1632 return;
1633 }
1634
1635 case "fieldVal": {
1636 const fv = expr.fieldVal;
1637 const field_name = fv.name;
1638 yield { src: field_name, tag: Tag.identifier };
1639 return;
1640 }
1641
1642 case "binOpIndex": {
1643 const binOp = zigAnalysis.exprs[expr.binOpIndex];
1644 yield* ex(binOp, opts);
1645 return;
1646 }
1647
1648 case "binOp": {
1649 const lhsOp = zigAnalysis.exprs[expr.binOp.lhs];
1650 const rhsOp = zigAnalysis.exprs[expr.binOp.rhs];
1651
1652 if (lhsOp["binOpIndex"] !== undefined) {
1653 yield Tok.l_paren;
1654 yield* ex(lhsOp, opts);
1655 yield Tok.r_paren;
1656 } else {
1657 yield* ex(lhsOp, opts);
1658 }
1659
1660 yield Tok.space;
1661
1662 switch (expr.binOp.name) {
1663 case "add": {
1664 yield { src: "+", tag: Tag.plus };
1665 break;
1666 }
1667 case "addwrap": {
1668 yield { src: "+%", tag: Tag.plus_percent };
1669 break;
1670 }
1671 case "add_sat": {
1672 yield { src: "+|", tag: Tag.plus_pipe };
1673 break;
1674 }
1675 case "sub": {
1676 yield { src: "-", tag: Tag.minus };
1677 break;
1678 }
1679 case "subwrap": {
1680 yield { src: "-%", tag: Tag.minus_percent };
1681 break;
1682 }
1683 case "sub_sat": {
1684 yield { src: "-|", tag: Tag.minus_pipe };
1685 break;
1686 }
1687 case "mul": {
1688 yield { src: "*", tag: Tag.asterisk };
1689 break;
1690 }
1691 case "mulwrap": {
1692 yield { src: "*%", tag: Tag.asterisk_percent };
1693 break;
1694 }
1695 case "mul_sat": {
1696 yield { src: "*|", tag: Tag.asterisk_pipe };
1697 break;
1698 }
1699 case "div": {
1700 yield { src: "/", tag: Tag.slash };
1701 break;
1702 }
1703 case "xor": {
1704 yield { src: "^", tag: Tag.caret };
1705 break;
1706 }
1707 case "shl": {
1708 yield { src: "<<", tag: Tag.angle_bracket_angle_bracket_left };
1709 break;
1710 }
1711 case "shl_sat": {
1712 yield { src: "<<|", tag: Tag.angle_bracket_angle_bracket_left_pipe };
1713 break;
1714 }
1715 case "shr": {
1716 yield { src: ">>", tag: Tag.angle_bracket_angle_bracket_right };
1717 break;
1718 }
1719 case "bit_or": {
1720 yield { src: "|", tag: Tag.pipe };
1721 break;
1722 }
1723 case "bit_and": {
1724 yield { src: "&", tag: Tag.ampersand };
1725 break;
1726 }
1727 case "array_cat": {
1728 yield { src: "++", tag: Tag.plus_plus };
1729 break;
1730 }
1731 case "array_mul": {
1732 yield { src: "**", tag: Tag.asterisk_asterisk };
1733 break;
1734 }
1735 case "cmp_eq": {
1736 yield { src: "==", tag: Tag.equal_equal };
1737 break;
1738 }
1739 case "cmp_neq": {
1740 yield { src: "!=", tag: Tag.bang_equal };
1741 break;
1742 }
1743 case "cmp_gt": {
1744 yield { src: ">", tag: Tag.angle_bracket_right };
1745 break;
1746 }
1747 case "cmp_gte": {
1748 yield { src: ">=", tag: Tag.angle_bracket_right_equal };
1749 break;
1750 }
1751 case "cmp_lt": {
1752 yield { src: "<", tag: Tag.angle_bracket_left };
1753 break;
1754 }
1755 case "cmp_lte": {
1756 yield { src: "<=", tag: Tag.angle_bracket_left_equal };
1757 break;
1758 }
1759 case "bool_br_and": {
1760 yield { src: "and", tag: Tag.keyword_and };
1761 break;
1762 }
1763 case "bool_br_or": {
1764 yield { src: "or", tag: Tag.keyword_or };
1765 break;
1766 }
1767 default:
1768 console.log("operator not handled yet or doesn't exist!");
1769 }
1770
1771 yield Tok.space;
1772
1773 if (rhsOp["binOpIndex"] !== undefined) {
1774 yield Tok.l_paren;
1775 yield* ex(rhsOp, opts);
1776 yield Tok.r_paren;
1777 } else {
1778 yield* ex(rhsOp, opts);
1779 }
1780 return;
1781 }
1782
1783 case "builtinIndex": {
1784 const builtin = zigAnalysis.exprs[expr.builtinIndex];
1785 yield* ex(builtin, opts);
1786 return;
1787 }
1788
1789 case "builtin": {
1790 const builtin = expr.builtin;
1791 let name = "@";
1792 const param = zigAnalysis.exprs[builtin.param];
1793 switch (builtin.name) {
1794 case "align_of": { name += "alignOf"; break; }
1795 case "int_from_bool": { name += "intFromBool"; break; }
1796 case "embed_file": { name += "embedFile"; break; }
1797 case "error_name": { name += "errorName"; break; }
1798 case "panic": { name += "panic"; break; }
1799 case "set_runtime_safety": { name += "setRuntimeSafety"; break; }
1800 case "sqrt": { name += "sqrt"; break; }
1801 case "sin": { name += "sin"; break; }
1802 case "cos": { name += "cos"; break; }
1803 case "tan": { name += "tan"; break; }
1804 case "exp": { name += "exp"; break; }
1805 case "exp2": { name += "exp2"; break; }
1806 case "log": { name += "log"; break; }
1807 case "log2": { name += "log2"; break; }
1808 case "log10": { name += "log10"; break; }
1809 case "fabs": { name += "fabs"; break; }
1810 case "floor": { name += "floor"; break; }
1811 case "ceil": { name += "ceil"; break; }
1812 case "trunc": { name += "trunc"; break; }
1813 case "round": { name += "round"; break; }
1814 case "tag_name": { name += "tagName"; break; }
1815 case "type_name": { name += "typeName"; break; }
1816 case "type_info": { name += "typeInfo"; break; }
1817 case "frame_type": { name += "Frame"; break; }
1818 case "frame_size": { name += "frameSize"; break; }
1819 case "int_from_ptr": { name += "intFromPtr"; break; }
1820 case "int_from_enum": { name += "intFromEnum"; break; }
1821 case "clz": { name += "clz"; break; }
1822 case "ctz": { name += "ctz"; break; }
1823 case "pop_count": { name += "popCount"; break; }
1824 case "byte_swap": { name += "byteSwap"; break; }
1825 case "bit_reverse": { name += "bitReverse"; break; }
1826 default: throw "builtin: `" + builtin.name + "` not implemented yet!";
1827 }
1828 yield { src: name, tag: Tag.builtin };
1829 yield Tok.l_paren;
1830 yield* ex(param, opts);
1831 yield Tok.r_paren;
1832 return;
1833 }
1834
1835 case "builtinBinIndex": {
1836 const builtinBinIndex = zigAnalysis.exprs[expr.builtinBinIndex];
1837 yield* ex(builtinBinIndex, opts);
1838 return;
1839 }
1840
1841 case "builtinBin": {
1842 const lhsOp = zigAnalysis.exprs[expr.builtinBin.lhs];
1843 const rhsOp = zigAnalysis.exprs[expr.builtinBin.rhs];
1844
1845 let builtinName = "@";
1846 switch (expr.builtinBin.name) {
1847 case "int_from_float": {
1848 builtinName += "intFromFloat";
1849 break;
1850 }
1851 case "float_from_int": {
1852 builtinName += "floatFromInt";
1853 break;
1854 }
1855 case "ptr_from_int": {
1856 builtinName += "ptrFromInt";
1857 break;
1858 }
1859 case "enum_from_int": {
1860 builtinName += "enumFromInt";
1861 break;
1862 }
1863 case "float_cast": {
1864 builtinName += "floatCast";
1865 break;
1866 }
1867 case "int_cast": {
1868 builtinName += "intCast";
1869 break;
1870 }
1871 case "ptr_cast": {
1872 builtinName += "ptrCast";
1873 break;
1874 }
1875 case "const_cast": {
1876 builtinName += "constCast";
1877 break;
1878 }
1879 case "volatile_cast": {
1880 builtinName += "volatileCast";
1881 break;
1882 }
1883 case "truncate": {
1884 builtinName += "truncate";
1885 break;
1886 }
1887 case "has_decl": {
1888 builtinName += "hasDecl";
1889 break;
1890 }
1891 case "has_field": {
1892 builtinName += "hasField";
1893 break;
1894 }
1895 case "bit_reverse": {
1896 builtinName += "bitReverse";
1897 break;
1898 }
1899 case "div_exact": {
1900 builtinName += "divExact";
1901 break;
1902 }
1903 case "div_floor": {
1904 builtinName += "divFloor";
1905 break;
1906 }
1907 case "div_trunc": {
1908 builtinName += "divTrunc";
1909 break;
1910 }
1911 case "mod": {
1912 builtinName += "mod";
1913 break;
1914 }
1915 case "rem": {
1916 builtinName += "rem";
1917 break;
1918 }
1919 case "mod_rem": {
1920 builtinName += "rem";
1921 break;
1922 }
1923 case "shl_exact": {
1924 builtinName += "shlExact";
1925 break;
1926 }
1927 case "shr_exact": {
1928 builtinName += "shrExact";
1929 break;
1930 }
1931 case "bitcast": {
1932 builtinName += "bitCast";
1933 break;
1934 }
1935 case "align_cast": {
1936 builtinName += "alignCast";
1937 break;
1938 }
1939 case "vector_type": {
1940 builtinName += "Vector";
1941 break;
1942 }
1943 case "reduce": {
1944 builtinName += "reduce";
1945 break;
1946 }
1947 case "splat": {
1948 builtinName += "splat";
1949 break;
1950 }
1951 case "offset_of": {
1952 builtinName += "offsetOf";
1953 break;
1954 }
1955 case "bit_offset_of": {
1956 builtinName += "bitOffsetOf";
1957 break;
1958 }
1959 default:
1960 console.log("builtin function not handled yet or doesn't exist!");
1961 }
1962
1963 yield { src: builtinName, tag: Tag.builtin };
1964 yield Tok.l_paren;
1965 yield* ex(lhsOp, opts);
1966 yield Tok.comma;
1967 yield Tok.space;
1968 yield* ex(rhsOp, opts);
1969 yield Tok.r_paren;
1970 return;
1971 }
1972
1973 case "unionInit": {
1974 let ui = expr.unionInit;
1975 let type = zigAnalysis.exprs[ui.type];
1976 let field = zigAnalysis.exprs[ui.field];
1977 let init = zigAnalysis.exprs[ui.init];
1978 yield { src: "@unionInit", tag: Tag.builtin };
1979 yield Tok.l_paren;
1980 yield* ex(type, opts);
1981 yield Tok.comma;
1982 yield Tok.space;
1983 yield* ex(field, opts);
1984 yield Tok.comma;
1985 yield Tok.space;
1986 yield* ex(init, opts);
1987 yield Tok.r_paren;
1988 return;
1989 }
1990
1991 case "builtinCall": {
1992 let bcall = expr.builtinCall;
1993 let mods = zigAnalysis.exprs[bcall.modifier];
1994 let calee = zigAnalysis.exprs[bcall.function];
1995 let args = zigAnalysis.exprs[bcall.args];
1996 yield { src: "@call", tag: Tag.builtin };
1997 yield Tok.l_paren;
1998 yield* ex(mods, opts);
1999 yield Tok.comma;
2000 yield Tok.space;
2001 yield* ex(calee, opts);
2002 yield Tok.comma;
2003 yield Tok.space;
2004 yield* ex(args, opts);
2005 yield Tok.r_paren;
2006 return;
2007 }
2008
2009 case "mulAdd": {
2010 let muladd = expr.mulAdd;
2011 let mul1 = zigAnalysis.exprs[muladd.mulend1];
2012 let mul2 = zigAnalysis.exprs[muladd.mulend2];
2013 let add = zigAnalysis.exprs[muladd.addend];
2014 let type = zigAnalysis.exprs[muladd.type];
2015 yield { src: "@mulAdd", tag: Tag.builtin };
2016 yield Tok.l_paren;
2017 yield* ex(type, opts);
2018 yield Tok.comma;
2019 yield Tok.space;
2020 yield* ex(mul1, opts);
2021 yield Tok.comma;
2022 yield Tok.space;
2023 yield* ex(mul2, opts);
2024 yield Tok.comma;
2025 yield Tok.space;
2026 yield* ex(add, opts);
2027 yield Tok.r_paren;
2028 return;
2029 }
2030
2031 case "cmpxchgIndex": {
2032 const cmpxchg = zigAnalysis.exprs[expr.cmpxchgIndex];
2033 yield* ex(cmpxchg, opts);
2034 return;
2035 }
2036
2037 case "cmpxchg": {
2038 const type = zigAnalysis.exprs[expr.cmpxchg.type];
2039 const ptr = zigAnalysis.exprs[expr.cmpxchg.ptr];
2040 const expectedValue = zigAnalysis.exprs[expr.cmpxchg.expected_value];
2041 const newValue = zigAnalysis.exprs[expr.cmpxchg.new_value];
2042 const successOrder = zigAnalysis.exprs[expr.cmpxchg.success_order];
2043 const failureOrder = zigAnalysis.exprs[expr.cmpxchg.failure_order];
2044
2045 let fnName = "@";
2046 switch (expr.cmpxchg.name) {
2047 case "cmpxchg_strong": {
2048 fnName += "cmpxchgStrong";
2049 break;
2050 }
2051 case "cmpxchg_weak": {
2052 fnName += "cmpxchgWeak";
2053 break;
2054 }
2055 default:
2056 throw "Unexpected cmpxchg name: `" + expr.cmpxchg.name + "`!";
2057 }
2058 yield { src: fnName, tag: Tag.builtin };
2059 yield Tok.l_paren;
2060 yield* ex(type, opts);
2061 yield Tok.comma;
2062 yield Tok.space;
2063 yield* ex(ptr, opts);
2064 yield Tok.comma;
2065 yield Tok.space;
2066 yield* ex(expectedValue, opts);
2067 yield Tok.comma;
2068 yield Tok.space;
2069 yield* ex(newValue, opts);
2070 yield Tok.comma;
2071 yield Tok.space;
2072 yield* ex(successOrder, opts);
2073 yield Tok.comma;
2074 yield Tok.space;
2075 yield* ex(failureOrder, opts);
2076 yield Tok.r_paren;
2077 return;
2078 }
2079
2080 case "enumLiteral": {
2081 let literal = expr.enumLiteral;
2082 yield Tok.period;
2083 yield { src: literal, tag: Tag.identifier };
2084 return;
2085 }
2086
2087 case "void": {
2088 yield { src: "void", tag: Tag.identifier };
2089 return;
2090 }
2091
2092 case "null": {
2093 yield { src: "null", tag: Tag.identifier };
2094 return;
2095 }
2096
2097 case "undefined": {
2098 yield { src: "undefined", tag: Tag.identifier };
2099 return;
2100 }
2101
2102 case "anytype": {
2103 yield { src: "anytype", tag: Tag.keyword_anytype };
2104 return;
2105 }
2106
2107 case "this": {
2108 yield { src: "@This", tag: Tag.builtin };
2109 yield Tok.l_paren;
2110 yield Tok.r_paren;
2111 return;
2112 }
2113
2114 case "switchIndex": {
2115 const switchIndex = zigAnalysis.exprs[expr.switchIndex];
2116 yield* ex(switchIndex, opts);
2117 return;
2118 }
2119
2120 case "errorSets": {
2121 const errSetsObj = getType(expr.errorSets);
2122 yield* ex(errSetsObj.lhs, opts);
2123 yield Tok.space;
2124 yield { src: "||", tag: Tag.pipe_pipe };
2125 yield Tok.space;
2126 yield* ex(errSetsObj.rhs, opts);
2127 return;
2128 }
2129
2130 case "errorUnion": {
2131 const errUnionObj = getType(expr.errorUnion);
2132 yield* ex(errUnionObj.lhs, opts);
2133 yield { src: "!", tag: Tag.bang };
2134 yield* ex(errUnionObj.rhs, opts);
2135 return;
2136 }
2137
2138 case "type": {
2139 let name = "";
2140
2141 let typeObj = expr.type;
2142 if (typeof typeObj === "number") typeObj = getType(typeObj);
2143 switch (typeObj.kind) {
2144 default:
2145 throw "TODO: " + typeObj.kind;
2146 case typeKinds.Type: {
2147 yield { src: typeObj.name, tag: Tag.identifier };
2148 return;
2149 }
2150 case typeKinds.Void: {
2151 yield { src: "void", tag: Tag.identifier };
2152 return;
2153 }
2154 case typeKinds.NoReturn: {
2155 yield { src: "noreturn", tag: Tag.identifier };
2156 return;
2157 }
2158 case typeKinds.ComptimeExpr: {
2159 yield { src: "anyopaque", tag: Tag.identifier };
2160 return;
2161 }
2162 case typeKinds.Bool: {
2163 yield { src: "bool", tag: Tag.identifier };
2164 return;
2165 }
2166 case typeKinds.ComptimeInt: {
2167 yield { src: "comptime_int", tag: Tag.identifier };
2168 return;
2169 }
2170 case typeKinds.ComptimeFloat: {
2171 yield { src: "comptime_float", tag: Tag.identifier };
2172 return;
2173 }
2174 case typeKinds.Int: {
2175 yield { src: typeObj.name, tag: Tag.identifier };
2176 return;
2177 }
2178 case typeKinds.Float: {
2179 yield { src: typeObj.name, tag: Tag.identifier };
2180 return;
2181 }
2182 case typeKinds.Array: {
2183 yield Tok.l_bracket;
2184 yield* ex(typeObj.len, opts);
2185 if (typeObj.sentinel) {
2186 yield Tok.colon;
2187 yield* ex(typeObj.sentinel, opts);
2188 }
2189 yield Tok.r_bracket;
2190 yield* ex(typeObj.child, opts);
2191 return;
2192 }
2193 case typeKinds.Optional: {
2194 yield Tok.question_mark;
2195 yield* ex(typeObj.child, opts);
2196 return;
2197 }
2198 case typeKinds.Pointer: {
2199 let ptrObj = typeObj;
2200 switch (ptrObj.size) {
2201 default:
2202 console.log("TODO: implement unhandled pointer size case");
2203 case pointerSizeEnum.One:
2204 yield { src: "*", tag: Tag.asterisk };
2205 break;
2206 case pointerSizeEnum.Many:
2207 yield Tok.l_bracket;
2208 yield { src: "*", tag: Tag.asterisk };
2209 if (ptrObj.sentinel !== null) {
2210 yield Tok.colon;
2211 yield* ex(ptrObj.sentinel, opts);
2212 }
2213 yield Tok.r_bracket;
2214 break;
2215 case pointerSizeEnum.Slice:
2216 if (ptrObj.is_ref) {
2217 yield { src: "*", tag: Tag.asterisk };
2218 }
2219 yield Tok.l_bracket;
2220 if (ptrObj.sentinel !== null) {
2221 yield Tok.colon;
2222 yield* ex(ptrObj.sentinel, opts);
2223 }
2224 yield Tok.r_bracket;
2225 break;
2226 case pointerSizeEnum.C:
2227 yield Tok.l_bracket;
2228 yield { src: "*", tag: Tag.asterisk };
2229 yield { src: "c", tag: Tag.identifier };
2230 if (typeObj.sentinel !== null) {
2231 yield Tok.colon;
2232 yield* ex(ptrObj.sentinel, opts);
2233 }
2234 yield Tok.r_bracket;
2235 break;
2236 }
2237 if (!ptrObj.is_mutable) {
2238 yield Tok.const;
2239 yield Tok.space;
2240 }
2241 if (ptrObj.is_allowzero) {
2242 yield { src: "allowzero", tag: Tag.keyword_allowzero };
2243 yield Tok.space;
2244 }
2245 if (ptrObj.is_volatile) {
2246 yield { src: "volatile", tag: Tag.keyword_volatile };
2247 }
2248 if (ptrObj.has_addrspace) {
2249 yield { src: "addrspace", tag: Tag.keyword_addrspace };
2250 yield Tok.l_paren;
2251 yield Tok.period;
2252 yield Tok.r_paren;
2253 }
2254 if (ptrObj.has_align) {
2255 yield { src: "align", tag: Tag.keyword_align };
2256 yield Tok.l_paren;
2257 yield* ex(ptrObj.align, opts);
2258 if (ptrObj.hostIntBytes !== undefined && ptrObj.hostIntBytes !== null) {
2259 yield Tok.colon;
2260 yield* ex(ptrObj.bitOffsetInHost, opts);
2261 yield Tok.colon;
2262 yield* ex(ptrObj.hostIntBytes, opts);
2263 }
2264 yield Tok.r_paren;
2265 yield Tok.space;
2266 }
2267 yield* ex(ptrObj.child, opts);
2268 return;
2269 }
2270 case typeKinds.Struct: {
2271 let structObj = typeObj;
2272 if (structObj.layout !== null) {
2273 switch (structObj.layout.enumLiteral) {
2274 case "Packed": {
2275 yield { src: "packed", tag: Tag.keyword_packed };
2276 break;
2277 }
2278 case "Extern": {
2279 yield { src: "extern", tag: Tag.keyword_extern };
2280 break;
2281 }
2282 }
2283 yield Tok.space;
2284 }
2285 yield { src: "struct", tag: Tag.keyword_struct };
2286 if (structObj.backing_int !== null) {
2287 yield Tok.l_paren;
2288 yield* ex(structObj.backing_int, opts);
2289 yield Tok.r_paren;
2290 }
2291 yield Tok.space;
2292 yield Tok.l_brace;
2293
2294 if (structObj.field_types.length > 1) {
2295 yield Tok.enter;
2296 } else {
2297 yield Tok.space;
2298 }
2299
2300 let indent = 0;
2301 if (structObj.field_types.length > 1) {
2302 indent = 1;
2303 }
2304 if (opts.indent && structObj.field_types.length > 1) {
2305 indent += opts.ident;
2306 }
2307
2308 let structNode = getAstNode(structObj.src);
2309 for (let i = 0; i < structObj.field_types.length; i += 1) {
2310 let fieldNode = getAstNode(structNode.fields[i]);
2311 let fieldName = fieldNode.name;
2312
2313 for (let j = 0; j < indent; j += 1) {
2314 yield Tok.tab;
2315 }
2316
2317 if (!typeObj.is_tuple) {
2318 yield { src: fieldName, tag: Tag.identifier };
2319 }
2320
2321 let fieldTypeExpr = structObj.field_types[i];
2322 if (!typeObj.is_tuple) {
2323 yield Tok.colon;
2324 yield Tok.space;
2325 }
2326 yield* ex(fieldTypeExpr, { ...opts, indent: indent });
2327
2328 if (structObj.field_defaults[i] !== null) {
2329 yield Tok.space;
2330 yield Tok.eql;
2331 yield Tok.space;
2332 yield* ex(structObj.field_defaults[i], opts);
2333 }
2334
2335 if (structObj.field_types.length > 1) {
2336 yield Tok.comma;
2337 yield Tok.enter;
2338 } else {
2339 yield Tok.space;
2340 }
2341 }
2342 yield Tok.r_brace;
2343 return;
2344 }
2345 case typeKinds.Enum: {
2346 let enumObj = typeObj;
2347 yield { src: "enum", tag: Tag.keyword_enum };
2348 if (enumObj.tag) {
2349 yield Tok.l_paren;
2350 yield* ex(enumObj.tag, opts);
2351 yield Tok.r_paren;
2352 }
2353 yield Tok.space;
2354 yield Tok.l_brace;
2355
2356 let enumNode = getAstNode(enumObj.src);
2357 let fields_len = enumNode.fields.length;
2358 if (enumObj.nonexhaustive) {
2359 fields_len += 1;
2360 }
2361
2362 if (fields_len > 1) {
2363 yield Tok.enter;
2364 } else {
2365 yield Tok.space;
2366 }
2367
2368 let indent = 0;
2369 if (fields_len > 1) {
2370 indent = 1;
2371 }
2372 if (opts.indent) {
2373 indent += opts.indent;
2374 }
2375
2376 for (let i = 0; i < enumNode.fields.length; i += 1) {
2377 let fieldNode = getAstNode(enumNode.fields[i]);
2378 let fieldName = fieldNode.name;
2379
2380 for (let j = 0; j < indent; j += 1) yield Tok.tab;
2381 yield { src: fieldName, tag: Tag.identifier };
2382
2383 if (enumObj.values[i] !== null) {
2384 yield Tok.space;
2385 yield Tok.eql;
2386 yield Tok.space;
2387 yield* ex(enumObj.values[i], opts);
2388 }
2389
2390 if (fields_len > 1) {
2391 yield Tok.comma;
2392 yield Tok.enter;
2393 }
2394 }
2395 if (enumObj.nonexhaustive) {
2396 for (let j = 0; j < indent; j += 1) yield Tok.tab;
2397
2398 yield { src: "_", tag: Tag.identifier };
2399
2400 if (fields_len > 1) {
2401 yield Tok.comma;
2402 yield Tok.enter;
2403 }
2404 }
2405 if (opts.indent) {
2406 for (let j = 0; j < opts.indent; j += 1) yield Tok.tab;
2407 }
2408 yield Tok.r_brace;
2409 return;
2410 }
2411 case typeKinds.Union: {
2412 let unionObj = typeObj;
2413 if (unionObj.layout !== null) {
2414 switch (unionObj.layout.enumLiteral) {
2415 case "Packed": {
2416 yield { src: "packed", tag: Tag.keyword_packed };
2417 break;
2418 }
2419 case "Extern": {
2420 yield { src: "extern", tag: Tag.keyword_extern };
2421 break;
2422 }
2423 }
2424 yield Tok.space;
2425 }
2426 yield { src: "union", tag: Tag.keyword_union };
2427 if (unionObj.auto_tag) {
2428 yield Tok.l_paren;
2429 yield { src: "enum", tag: Tag.keyword_enum };
2430 if (unionObj.tag) {
2431 yield Tok.l_paren;
2432 yield* ex(unionObj.tag, opts);
2433 yield Tok.r_paren;
2434 yield Tok.r_paren;
2435 } else {
2436 yield Tok.r_paren;
2437 }
2438 } else if (unionObj.tag) {
2439 yield Tok.l_paren;
2440 yield* ex(unionObj.tag, opts);
2441 yield Tok.r_paren;
2442 }
2443 yield Tok.space;
2444 yield Tok.l_brace;
2445 if (unionObj.field_types.length > 1) {
2446 yield Tok.enter;
2447 } else {
2448 yield Tok.space;
2449 }
2450 let indent = 0;
2451 if (unionObj.field_types.length > 1) {
2452 indent = 1;
2453 }
2454 if (opts.indent) {
2455 indent += opts.indent;
2456 }
2457 let unionNode = getAstNode(unionObj.src);
2458 for (let i = 0; i < unionObj.field_types.length; i += 1) {
2459 let fieldNode = getAstNode(unionNode.fields[i]);
2460 let fieldName = fieldNode.name;
2461 for (let j = 0; j < indent; j += 1) yield Tok.tab;
2462 yield { src: fieldName, tag: Tag.identifier };
2463
2464 let fieldTypeExpr = unionObj.field_types[i];
2465 yield Tok.colon;
2466 yield Tok.space;
2467
2468 yield* ex(fieldTypeExpr, { ...opts, indent: indent });
2469
2470 if (unionObj.field_types.length > 1) {
2471 yield Tok.comma;
2472 yield Tok.enter;
2473 } else {
2474 yield Tok.space;
2475 }
2476 }
2477 if (opts.indent) {
2478 for (let j = 0; j < opts.indent; j += 1) yield Tok.tab;
2479 }
2480 yield Tok.r_brace;
2481 return;
2482 }
2483 case typeKinds.Opaque: {
2484 yield { src: "opaque", tag: Tag.keyword_opaque };
2485 yield Tok.space;
2486 yield Tok.l_brace;
2487 yield Tok.r_brace;
2488 return;
2489 }
2490 case typeKinds.EnumLiteral: {
2491 yield { src: "(enum literal)", tag: Tag.identifier };
2492 return;
2493 }
2494 case typeKinds.ErrorSet: {
2495 let errSetObj = typeObj;
2496 if (errSetObj.fields === null) {
2497 yield { src: "anyerror", tag: Tag.identifier };
2498 } else if (errSetObj.fields.length == 0) {
2499 yield { src: "error", tag: Tag.keyword_error };
2500 yield Tok.l_brace;
2501 yield Tok.r_brace;
2502 } else if (errSetObj.fields.length == 1) {
2503 yield { src: "error", tag: Tag.keyword_error };
2504 yield Tok.l_brace;
2505 yield { src: errSetObj.fields[0].name, tag: Tag.identifier };
2506 yield Tok.r_brace;
2507 } else {
2508 yield { src: "error", tag: Tag.keyword_error };
2509 yield Tok.l_brace;
2510 yield { src: errSetObj.fields[0].name, tag: Tag.identifier };
2511 for (let i = 1; i < errSetObj.fields.length; i++) {
2512 yield Tok.comma;
2513 yield Tok.space;
2514 yield { src: errSetObj.fields[i].name, tag: Tag.identifier };
2515 }
2516 yield Tok.r_brace;
2517 }
2518 return;
2519 }
2520 case typeKinds.ErrorUnion: {
2521 let errUnionObj = typeObj;
2522 yield* ex(errUnionObj.lhs, opts);
2523 yield { src: "!", tag: Tag.bang };
2524 yield* ex(errUnionObj.rhs, opts);
2525 return;
2526 }
2527 case typeKinds.InferredErrorUnion: {
2528 let errUnionObj = typeObj;
2529 yield { src: "!", tag: Tag.bang };
2530 yield* ex(errUnionObj.payload, opts);
2531 return;
2532 }
2533 case typeKinds.Fn: {
2534 let fnObj = typeObj;
2535 let fnDecl = opts.fnDecl;
2536 let linkFnNameDecl = opts.linkFnNameDecl;
2537 opts.fnDecl = null;
2538 opts.linkFnNameDecl = null;
2539 if (opts.addParensIfFnSignature && fnObj.src == 0) {
2540 yield Tok.l_paren;
2541 }
2542 if (fnObj.is_extern) {
2543 yield { src: "extern", tag: Tag.keyword_extern };
2544 yield Tok.space;
2545 } else if (fnObj.has_cc) {
2546 let cc_expr = zigAnalysis.exprs[fnObj.cc];
2547 if (cc_expr.enumLiteral === "Inline") {
2548 yield { src: "inline", tag: Tag.keyword_inline };
2549 yield Tok.space;
2550 }
2551 }
2552 if (fnObj.has_lib_name) {
2553 yield { src: '"' + fnObj.lib_name + '"', tag: Tag.string_literal };
2554 yield Tok.space;
2555 }
2556 yield { src: "fn", tag: Tag.keyword_fn };
2557 yield Tok.space;
2558 if (fnDecl) {
2559 if (linkFnNameDecl) {
2560 yield { src: fnDecl.name, tag: Tag.identifier, link: linkFnNameDecl, fnDecl: false };
2561 } else {
2562 yield { src: fnDecl.name, tag: Tag.identifier, fnDecl: true };
2563 }
2564 }
2565 yield Tok.l_paren;
2566 if (fnObj.params) {
2567 let fields = null;
2568 let isVarArgs = false;
2569 if (fnObj.src != 0) {
2570 let fnNode = getAstNode(fnObj.src);
2571 fields = fnNode.fields;
2572 isVarArgs = fnNode.varArgs;
2573 }
2574
2575 for (let i = 0; i < fnObj.params.length; i += 1) {
2576 if (i != 0) {
2577 yield Tok.comma;
2578 yield Tok.space;
2579 }
2580
2581 let value = fnObj.params[i];
2582 let paramValue = resolveValue({ expr: value });
2583
2584 if (fields != null) {
2585 let paramNode = getAstNode(fields[i]);
2586
2587 if (paramNode.varArgs) {
2588 yield Tok.period;
2589 yield Tok.period;
2590 yield Tok.period;
2591 continue;
2592 }
2593
2594 if (paramNode.noalias) {
2595 yield { src: "noalias", tag: Tag.keyword_noalias };
2596 yield Tok.space;
2597 }
2598
2599 if (paramNode.comptime) {
2600 yield { src: "comptime", tag: Tag.keyword_comptime };
2601 yield Tok.space;
2602 }
2603
2604 let paramName = paramNode.name;
2605 if (paramName != null) {
2606 // skip if it matches the type name
2607 if (!shouldSkipParamName(paramValue, paramName)) {
2608 if (paramName === "") {
2609 paramName = "_";
2610 }
2611 yield { src: paramName, tag: Tag.identifier };
2612 yield Tok.colon;
2613 yield Tok.space;
2614 }
2615 }
2616 }
2617
2618 // TODO: most of this seems redundant
2619 if (isVarArgs && i === fnObj.params.length - 1) {
2620 yield Tok.period;
2621 yield Tok.period;
2622 yield Tok.period;
2623 } else if ("alignOf" in value) {
2624 yield* ex(value, opts);
2625 } else if ("typeOf" in value) {
2626 yield* ex(value, opts);
2627 } else if ("typeOf_peer" in value) {
2628 yield* ex(value, opts);
2629 } else if ("declRef" in value) {
2630 yield* ex(value, opts);
2631 } else if ("call" in value) {
2632 yield* ex(value, opts);
2633 } else if ("refPath" in value) {
2634 yield* ex(value, opts);
2635 } else if ("type" in value) {
2636 yield* ex(value, opts);
2637 //payloadHtml += '<span class="tok-kw">' + name + "</span>";
2638 } else if ("binOpIndex" in value) {
2639 yield* ex(value, opts);
2640 } else if ("comptimeExpr" in value) {
2641 let comptimeExpr =
2642 zigAnalysis.comptimeExprs[value.comptimeExpr].code;
2643 yield* Tokenizer(comptimeExpr);
2644 } else {
2645 yield { src: "anytype", tag: Tag.keyword_anytype };
2646 }
2647 }
2648 }
2649
2650 yield Tok.r_paren;
2651 yield Tok.space;
2652
2653 if (fnObj.has_align) {
2654 let align = zigAnalysis.exprs[fnObj.align];
2655 yield { src: "align", tag: Tag.keyword_align };
2656 yield Tok.l_paren;
2657 yield* ex(align, opts);
2658 yield Tok.r_paren;
2659 yield Tok.space;
2660 }
2661 if (fnObj.has_cc) {
2662 let cc = zigAnalysis.exprs[fnObj.cc];
2663 if (cc) {
2664 if (cc.enumLiteral !== "Inline") {
2665 yield { src: "callconv", tag: Tag.keyword_callconv };
2666 yield Tok.l_paren;
2667 yield* ex(cc, opts);
2668 yield Tok.r_paren;
2669 yield Tok.space;
2670 }
2671 }
2672 }
2673
2674 if (fnObj.is_inferred_error) {
2675 yield { src: "!", tag: Tag.bang };
2676 }
2677 if (fnObj.ret != null) {
2678 yield* ex(fnObj.ret, {
2679 ...opts,
2680 addParensIfFnSignature: true,
2681 });
2682 } else {
2683 yield { src: "anytype", tag: Tag.keyword_anytype };
2684 }
2685
2686 if (opts.addParensIfFnSignature && fnObj.src == 0) {
2687 yield Tok.r_paren;
2688 }
2689 return;
2690 }
2691 }
2692 }
2693
2694 case "typeOf": {
2695 const typeRefArg = zigAnalysis.exprs[expr.typeOf];
2696 yield { src: "@TypeOf", tag: Tag.builtin };
2697 yield Tok.l_paren;
2698 yield* ex(typeRefArg, opts);
2699 yield Tok.r_paren;
2700 return;
2701 }
2702
2703 case "builtinField": {
2704 yield { src: expr.builtinField, tag: Tag.identifier };
2705 return;
2706 }
2707 }
2708
2709
2710 }
2711
2712
2713
2714 function shouldSkipParamName(typeRef, paramName) {
2715 let resolvedTypeRef = resolveValue({ expr: typeRef });
2716 if ("type" in resolvedTypeRef) {
2717 let typeObj = getType(resolvedTypeRef.type);
2718 if (typeObj.kind === typeKinds.Pointer) {
2719 let ptrObj = typeObj;
2720 if (getPtrSize(ptrObj) === pointerSizeEnum.One) {
2721 const value = resolveValue(ptrObj.child);
2722 return typeValueName(value, false, true).toLowerCase() === paramName;
2723 }
2724 }
2725 }
2726 return false;
2727 }
2728
2729 function getPtrSize(typeObj) {
2730 return typeObj.size == null ? pointerSizeEnum.One : typeObj.size;
2731 }
2732
2733 function renderType(typeObj) {
2734 let name;
2735 if (
2736 rootIsStd &&
2737 typeObj ===
2738 getType(zigAnalysis.modules[zigAnalysis.rootMod].main)
2739 ) {
2740 name = renderSingleToken(Tok.identifier("std"));
2741 } else {
2742 name = renderTokens(ex({ type: typeObj }));
2743 }
2744 if (name != null && name != "") {
2745 domHdrName.innerHTML = "<pre class='inline'>" + name + "</pre> ("
2746 + zigAnalysis.typeKinds[typeObj.kind] + ")";
2747 domHdrName.classList.remove("hidden");
2748 }
2749 if (typeObj.kind == typeKinds.ErrorSet) {
2750 renderErrorSet(typeObj);
2751 }
2752 }
2753
2754 function renderErrorSet(errSetType) {
2755 if (errSetType.fields == null) {
2756 domFnErrorsAnyError.classList.remove("hidden");
2757 } else {
2758 let errorList = [];
2759 for (let i = 0; i < errSetType.fields.length; i += 1) {
2760 let errObj = errSetType.fields[i];
2761 //let srcObj = zigAnalysis.astNodes[errObj.src];
2762 errorList.push(errObj);
2763 }
2764 errorList.sort(function(a, b) {
2765 return operatorCompare(a.name.toLowerCase(), b.name.toLowerCase());
2766 });
2767
2768 resizeDomListDl(domListFnErrors, errorList.length);
2769 for (let i = 0; i < errorList.length; i += 1) {
2770 let nameTdDom = domListFnErrors.children[i * 2 + 0];
2771 let descTdDom = domListFnErrors.children[i * 2 + 1];
2772 nameTdDom.textContent = errorList[i].name;
2773 let docs = errorList[i].docs;
2774 if (docs != null) {
2775 descTdDom.innerHTML = markdown(docs);
2776 } else {
2777 descTdDom.textContent = "";
2778 }
2779 }
2780 domTableFnErrors.classList.remove("hidden");
2781 }
2782 domSectFnErrors.classList.remove("hidden");
2783 }
2784
2785 // function allCompTimeFnCallsHaveTypeResult(typeIndex, value) {
2786 // let srcIndex = zigAnalysis.fns[value].src;
2787 // let calls = nodesToCallsMap[srcIndex];
2788 // if (calls == null) return false;
2789 // for (let i = 0; i < calls.length; i += 1) {
2790 // let call = zigAnalysis.calls[calls[i]];
2791 // if (call.result.type !== typeTypeId) return false;
2792 // }
2793 // return true;
2794 // }
2795 //
2796 // function allCompTimeFnCallsResult(calls) {
2797 // let firstTypeObj = null;
2798 // let containerObj = {
2799 // privDecls: [],
2800 // };
2801 // for (let callI = 0; callI < calls.length; callI += 1) {
2802 // let call = zigAnalysis.calls[calls[callI]];
2803 // if (call.result.type !== typeTypeId) return null;
2804 // let typeObj = zigAnalysis.types[call.result.value];
2805 // if (!typeKindIsContainer(typeObj.kind)) return null;
2806 // if (firstTypeObj == null) {
2807 // firstTypeObj = typeObj;
2808 // containerObj.src = typeObj.src;
2809 // } else if (firstTypeObj.src !== typeObj.src) {
2810 // return null;
2811 // }
2812 //
2813 // if (containerObj.fields == null) {
2814 // containerObj.fields = (typeObj.fields || []).concat([]);
2815 // } else for (let fieldI = 0; fieldI < typeObj.fields.length; fieldI += 1) {
2816 // let prev = containerObj.fields[fieldI];
2817 // let next = typeObj.fields[fieldI];
2818 // if (prev === next) continue;
2819 // if (typeof(prev) === 'object') {
2820 // if (prev[next] == null) prev[next] = typeObj;
2821 // } else {
2822 // containerObj.fields[fieldI] = {};
2823 // containerObj.fields[fieldI][prev] = firstTypeObj;
2824 // containerObj.fields[fieldI][next] = typeObj;
2825 // }
2826 // }
2827 //
2828 // if (containerObj.pubDecls == null) {
2829 // containerObj.pubDecls = (typeObj.pubDecls || []).concat([]);
2830 // } else for (let declI = 0; declI < typeObj.pubDecls.length; declI += 1) {
2831 // let prev = containerObj.pubDecls[declI];
2832 // let next = typeObj.pubDecls[declI];
2833 // if (prev === next) continue;
2834 // // TODO instead of showing "examples" as the public declarations,
2835 // // do logic like this:
2836 // //if (typeof(prev) !== 'object') {
2837 // // let newDeclId = zigAnalysis.decls.length;
2838 // // prev = clone(zigAnalysis.decls[prev]);
2839 // // prev.id = newDeclId;
2840 // // zigAnalysis.decls.push(prev);
2841 // // containerObj.pubDecls[declI] = prev;
2842 // //}
2843 // //mergeDecls(prev, next, firstTypeObj, typeObj);
2844 // }
2845 // }
2846 // for (let declI = 0; declI < containerObj.pubDecls.length; declI += 1) {
2847 // let decl = containerObj.pubDecls[declI];
2848 // if (typeof(decl) === 'object') {
2849 // containerObj.pubDecls[declI] = containerObj.pubDecls[declI].id;
2850 // }
2851 // }
2852 // return containerObj;
2853 // }
2854
2855 function renderValue(decl) {
2856 let resolvedValue = resolveValue(decl.value);
2857 if (resolvedValue.expr.fieldRef) {
2858 const declRef = decl.value.expr.refPath[0].declRef;
2859 const type = getDecl(declRef);
2860
2861 domFnProtoCode.innerHTML = renderTokens(
2862 (function*() {
2863 yield Tok.const;
2864 yield Tok.space;
2865 yield Tok.identifier(decl.name);
2866 yield Tok.colon;
2867 yield Tok.space;
2868 yield Tok.identifier(type.name);
2869 yield Tok.space;
2870 yield Tok.eql;
2871 yield Tok.space;
2872 yield* ex(decl.value.expr, {});
2873 yield Tok.semi;
2874 })());
2875 } else if (
2876 resolvedValue.expr.string !== undefined ||
2877 resolvedValue.expr.call !== undefined ||
2878 resolvedValue.expr.comptimeExpr !== undefined
2879 ) {
2880 // TODO: we're using the resolved value but
2881 // not keeping track of how we got there
2882 // that's important context that should
2883 // be shown to the user!
2884 domFnProtoCode.innerHTML = renderTokens(
2885 (function*() {
2886 yield Tok.const;
2887 yield Tok.space;
2888 yield Tok.identifier(decl.name);
2889 if (decl.value.typeRef) {
2890 yield Tok.colon;
2891 yield Tok.space;
2892 yield* ex(decl.value.typeRef, {});
2893 }
2894 yield Tok.space;
2895 yield Tok.eql;
2896 yield Tok.space;
2897 yield* ex(resolvedValue.expr, {});
2898 yield Tok.semi;
2899 })());
2900 } else if (resolvedValue.expr.compileError) {
2901 domFnProtoCode.innerHTML = renderTokens(
2902 (function*() {
2903 yield Tok.const;
2904 yield Tok.space;
2905 yield Tok.identifier(decl.name);
2906 yield Tok.space;
2907 yield Tok.eql;
2908 yield Tok.space;
2909 yield* ex(decl.value.expr, {});
2910 yield Tok.semi;
2911 })());
2912 } else {
2913 const parent = getType(decl.parent_container);
2914 domFnProtoCode.innerHTML = renderTokens(
2915 (function*() {
2916 yield Tok.const;
2917 yield Tok.space;
2918 yield Tok.identifier(decl.name);
2919 if (decl.value.typeRef !== null) {
2920 yield Tok.colon;
2921 yield Tok.space;
2922 yield* ex(decl.value.typeRef, {});
2923 }
2924 yield Tok.space;
2925 yield Tok.eql;
2926 yield Tok.space;
2927 yield* ex(decl.value.expr, {});
2928 yield Tok.semi;
2929 })());
2930 }
2931
2932 let docs = getAstNode(decl.src).docs;
2933 if (docs != null) {
2934 // TODO: it shouldn't just be decl.parent_container, but rather
2935 // the type that the decl holds (if the value is a type)
2936 domTldDocs.innerHTML = markdown(docs, decl);
2937
2938 domTldDocs.classList.remove("hidden");
2939 }
2940
2941 domFnProto.classList.remove("hidden");
2942 }
2943
2944 function renderVar(decl) {
2945 let resolvedVar = resolveValue(decl.value);
2946
2947 if (resolvedVar.expr.fieldRef) {
2948 const declRef = decl.value.expr.refPath[0].declRef;
2949 const type = getDecl(declRef);
2950 domFnProtoCode.innerHTML = renderTokens(
2951 (function*() {
2952 yield Tok.var;
2953 yield Tok.space;
2954 yield Tok.identifier(decl.name);
2955 yield Tok.colon;
2956 yield Tok.space;
2957 yield Tok.identifier(type.name);
2958 yield Tok.space;
2959 yield Tok.eql;
2960 yield Tok.space;
2961 yield* ex(decl.value.expr, {});
2962 yield Tok.semi;
2963 })());
2964 } else if (
2965 resolvedVar.expr.string !== undefined ||
2966 resolvedVar.expr.call !== undefined ||
2967 resolvedVar.expr.comptimeExpr !== undefined
2968 ) {
2969 domFnProtoCode.innerHTML = renderTokens(
2970 (function*() {
2971 yield Tok.var;
2972 yield Tok.space;
2973 yield Tok.identifier(decl.name);
2974 if (decl.value.typeRef) {
2975 yield Tok.colon;
2976 yield Tok.space;
2977 yield* ex(decl.value.typeRef, {});
2978 }
2979 yield Tok.space;
2980 yield Tok.eql;
2981 yield Tok.space;
2982 yield* ex(decl.value.expr, {});
2983 yield Tok.semi;
2984 })());
2985 } else if (resolvedVar.expr.compileError) {
2986 domFnProtoCode.innerHTML = renderTokens(
2987 (function*() {
2988 yield Tok.var;
2989 yield Tok.space;
2990 yield Tok.identifier(decl.name);
2991 yield Tok.space;
2992 yield Tok.eql;
2993 yield Tok.space;
2994 yield* ex(decl.value.expr, {});
2995 yield Tok.semi;
2996 })());
2997 } else {
2998 domFnProtoCode.innerHTML = renderTokens(
2999 (function*() {
3000 yield Tok.var;
3001 yield Tok.space;
3002 yield Tok.identifier(decl.name);
3003 yield Tok.colon;
3004 yield Tok.space;
3005 yield* ex(resolvedVar.typeRef, {});
3006 yield Tok.space;
3007 yield Tok.eql;
3008 yield Tok.space;
3009 yield* ex(decl.value.expr, {});
3010 yield Tok.semi;
3011 })());
3012 }
3013
3014 let docs = getAstNode(decl.src).docs;
3015 if (docs != null) {
3016 domTldDocs.innerHTML = markdown(docs);
3017 domTldDocs.classList.remove("hidden");
3018 }
3019
3020 domFnProto.classList.remove("hidden");
3021 }
3022
3023 function categorizeDecls(
3024 decls,
3025 typesList,
3026 namespacesWithDocsList,
3027 namespacesNoDocsList,
3028 errSetsList,
3029 fnsList,
3030 varsList,
3031 valsList,
3032 testsList,
3033 unsList
3034 ) {
3035 for (let i = 0; i < decls.length; i += 1) {
3036 let decl = getDecl(decls[i]);
3037 let declValue = resolveValue(decl.value);
3038
3039 // if (decl.isTest) {
3040 // testsList.push(decl);
3041 // continue;
3042 // }
3043
3044 if (decl.kind === "var") {
3045 varsList.push(decl);
3046 continue;
3047 }
3048
3049 if (decl.kind === "const") {
3050 if ("type" in declValue.expr) {
3051 // We have the actual type expression at hand.
3052 const typeExpr = getType(declValue.expr.type);
3053 if (typeExpr.kind == typeKinds.Fn) {
3054 const funcRetExpr = resolveValue({
3055 expr: typeExpr.ret,
3056 });
3057 if (
3058 "type" in funcRetExpr.expr &&
3059 funcRetExpr.expr.type == typeTypeId
3060 ) {
3061 if (typeIsErrSet(declValue.expr.type)) {
3062 errSetsList.push(decl);
3063 } else if (typeIsStructWithNoFields(declValue.expr.type)) {
3064
3065 let docs = getAstNode(decl.src).docs;
3066 if (!docs) {
3067 // If this is a re-export, try to fetch docs from the actual definition
3068 const { value, seenDecls } = resolveValue(decl.value, true);
3069 if (seenDecls.length > 0) {
3070 const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]);
3071 docs = getAstNode(definitionDecl.src).docs;
3072 } else {
3073 docs = getAstNode(getType(value.expr.type).src).docs;
3074 }
3075 }
3076
3077 if (docs) {
3078 namespacesWithDocsList.push({decl, docs});
3079 } else {
3080 namespacesNoDocsList.push(decl);
3081 }
3082 } else {
3083 typesList.push(decl);
3084 }
3085 } else {
3086 fnsList.push(decl);
3087 }
3088 } else {
3089 if (typeIsErrSet(declValue.expr.type)) {
3090 errSetsList.push(decl);
3091 } else if (typeIsStructWithNoFields(declValue.expr.type)) {
3092 let docs = getAstNode(decl.src).docs;
3093 if (!docs) {
3094 // If this is a re-export, try to fetch docs from the actual definition
3095 const { value, seenDecls } = resolveValue(decl.value, true);
3096 if (seenDecls.length > 0) {
3097 const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]);
3098 docs = getAstNode(definitionDecl.src).docs;
3099 } else {
3100 docs = getAstNode(getType(value.expr.type).src).docs;
3101 }
3102 }
3103 if (docs) {
3104 namespacesWithDocsList.push({decl, docs});
3105 } else {
3106 namespacesNoDocsList.push(decl);
3107 }
3108 } else {
3109 typesList.push(decl);
3110 }
3111 }
3112 } else if (declValue.typeRef) {
3113 if ("type" in declValue.typeRef && declValue.typeRef == typeTypeId) {
3114 // We don't know what the type expression is, but we know it's a type.
3115 typesList.push(decl);
3116 } else {
3117 valsList.push(decl);
3118 }
3119 } else {
3120 valsList.push(decl);
3121 }
3122 }
3123
3124 if (decl.is_uns) {
3125 unsList.push(decl);
3126 }
3127 }
3128 }
3129
3130 function sourceFileLink(decl) {
3131 const srcNode = getAstNode(decl.src);
3132 const srcFile = getFile(srcNode.file);
3133 return sourceFileUrlTemplate.
3134 replace("{{mod}}", zigAnalysis.modules[srcFile.modIndex].name).
3135 replace("{{file}}", srcFile.name).
3136 replace("{{line}}", srcNode.line + 1);
3137 }
3138
3139 function renderContainer(container) {
3140 let typesList = [];
3141
3142 let namespacesWithDocsList = [];
3143 let namespacesNoDocsList = [];
3144
3145 let errSetsList = [];
3146
3147 let fnsList = [];
3148
3149 let varsList = [];
3150
3151 let valsList = [];
3152
3153 let testsList = [];
3154
3155 let unsList = [];
3156
3157 categorizeDecls(
3158 container.pubDecls,
3159 typesList,
3160 namespacesWithDocsList,
3161 namespacesNoDocsList,
3162 errSetsList,
3163 fnsList,
3164 varsList,
3165 valsList,
3166 testsList,
3167 unsList
3168 );
3169 if (curNav.showPrivDecls)
3170 categorizeDecls(
3171 container.privDecls,
3172 typesList,
3173 namespacesWithDocsList,
3174 namespacesNoDocsList,
3175 errSetsList,
3176 fnsList,
3177 varsList,
3178 valsList,
3179 testsList,
3180 unsList
3181 );
3182
3183 while (unsList.length > 0) {
3184 let uns = unsList.shift();
3185 let declValue = resolveValue(uns.value);
3186 if (!("type" in declValue.expr)) continue;
3187 let uns_container = getType(declValue.expr.type);
3188 if (!isContainerType(uns_container)) continue;
3189 categorizeDecls(
3190 uns_container.pubDecls,
3191 typesList,
3192 namespacesWithDocsList,
3193 namespacesNoDocsList,
3194 errSetsList,
3195 fnsList,
3196 varsList,
3197 valsList,
3198 testsList,
3199 unsList
3200 );
3201 if (curNav.showPrivDecls)
3202 categorizeDecls(
3203 uns_container.privDecls,
3204 typesList,
3205 namespacesWithDocsList,
3206 namespacesNoDocsList,
3207 errSetsList,
3208 fnsList,
3209 varsList,
3210 valsList,
3211 testsList,
3212 unsList
3213 );
3214 }
3215
3216 typesList.sort(byNameProperty);
3217 namespacesWithDocsList.sort(byNameProperty);
3218 namespacesNoDocsList.sort(byNameProperty);
3219 errSetsList.sort(byNameProperty);
3220 fnsList.sort(byNameProperty);
3221 varsList.sort(byNameProperty);
3222 valsList.sort(byNameProperty);
3223 testsList.sort(byNameProperty);
3224
3225 if (container.src != null) {
3226 let docs = getAstNode(container.src).docs;
3227 if (docs != null) {
3228 domTldDocs.innerHTML = markdown(docs, container);
3229 domTldDocs.classList.remove("hidden");
3230 }
3231 }
3232
3233 if (typesList.length !== 0) {
3234 const splitPoint = Math.ceil(typesList.length / 2);
3235 const template = '<li><a href="#"></a><div></div></li>';
3236 resizeDomList(domListTypesLeft, splitPoint, template);
3237 resizeDomList(domListTypesRight, typesList.length - splitPoint, template);
3238
3239 let activeList = domListTypesLeft;
3240 let offset = 0;
3241 for (let i = 0; i < typesList.length; i += 1) {
3242 let liDom = activeList.children[i - offset];
3243 let aDom = liDom.children[0];
3244 let decl = typesList[i];
3245 aDom.textContent = decl.name;
3246 aDom.setAttribute("href", navLinkDecl(decl.name));
3247
3248 let descDom = liDom.children[1];
3249 let docs = getAstNode(decl.src).docs;
3250 if (!docs) {
3251 // If this is a re-export, try to fetch docs from the actual definition
3252 const { value, seenDecls } = resolveValue(decl.value, true);
3253 if (seenDecls.length > 0) {
3254 const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]);
3255 docs = getAstNode(definitionDecl.src).docs;
3256 } else {
3257 const type = getType(value.expr.type);
3258 if ("src" in type) {
3259 docs = getAstNode(type.src).docs;
3260 }
3261 }
3262 }
3263
3264 if (docs) {
3265 descDom.innerHTML = markdown(shortDesc(docs));
3266 } else {
3267 descDom.innerHTML = "<p class='understated'><i>No documentation provided.</i></p>";
3268 }
3269 if (i == splitPoint - 1) {
3270 activeList = domListTypesRight;
3271 offset = splitPoint;
3272 }
3273 }
3274 domSectTypes.classList.remove("hidden");
3275 }
3276
3277 if (namespacesWithDocsList.length !== 0) {
3278 const splitPoint = Math.ceil(namespacesWithDocsList.length / 2);
3279 const template = '<li><a href="#"></a><div></div></li>';
3280 resizeDomList(domListNamespacesLeft, splitPoint, template);
3281 resizeDomList(domListNamespacesRight,
3282 namespacesWithDocsList.length - splitPoint,
3283 template);
3284
3285 let activeList = domListNamespacesLeft;
3286 let offset = 0;
3287 for (let i = 0; i < namespacesWithDocsList.length; i += 1) {
3288 let liDom = activeList.children[i - offset];
3289 let aDom = liDom.children[0];
3290 let { decl, docs } = namespacesWithDocsList[i];
3291 aDom.textContent = decl.name;
3292 aDom.setAttribute("href", navLinkDecl(decl.name));
3293
3294
3295 let descDom = liDom.children[1];
3296 descDom.innerHTML = markdown(shortDesc(docs));
3297 if (i == splitPoint - 1) {
3298 activeList = domListNamespacesRight;
3299 offset = splitPoint;
3300 }
3301 }
3302
3303 domListNamespacesLeft.classList.remove("hidden");
3304 domListNamespacesRight.classList.remove("hidden");
3305 domSectNamespaces.classList.remove("hidden");
3306 }
3307
3308 if (namespacesNoDocsList.length !== 0) {
3309 resizeDomList(
3310 domNoDocsNamespaces,
3311 namespacesNoDocsList.length,
3312 '<span><a href="#"></a><span></span></span>'
3313 );
3314 for (let i = 0; i < namespacesNoDocsList.length; i += 1) {
3315 let aDom = domNoDocsNamespaces.children[i].children[0];
3316 let decl = namespacesNoDocsList[i];
3317 aDom.textContent = decl.name;
3318 aDom.setAttribute("href", navLinkDecl(decl.name));
3319 let comma = domNoDocsNamespaces.children[i].children[1];
3320 if (i == namespacesNoDocsList.length - 1) {
3321 comma.textContent = "";
3322 } else {
3323 comma.textContent = ", ";
3324 }
3325 }
3326
3327 domNoDocsNamespaces.classList.remove("hidden");
3328 domSectNamespaces.classList.remove("hidden");
3329 }
3330
3331
3332
3333
3334 if (errSetsList.length !== 0) {
3335 resizeDomList(
3336 domListErrSets,
3337 errSetsList.length,
3338 '<li><a href="#"></a></li>'
3339 );
3340 for (let i = 0; i < errSetsList.length; i += 1) {
3341 let liDom = domListErrSets.children[i];
3342 let aDom = liDom.children[0];
3343 let decl = errSetsList[i];
3344 aDom.textContent = decl.name;
3345 aDom.setAttribute("href", navLinkDecl(decl.name));
3346 }
3347 domSectErrSets.classList.remove("hidden");
3348 }
3349
3350 if (fnsList.length !== 0) {
3351 resizeDomList(
3352 domListFns,
3353 fnsList.length,
3354 '<div><dt><pre class="inline fnSignature"></pre><div></div></dt><dd></dd></div>'
3355 );
3356
3357 for (let i = 0; i < fnsList.length; i += 1) {
3358 let decl = fnsList[i];
3359 let trDom = domListFns.children[i];
3360
3361 let tdFnSignature = trDom.children[0].children[0];
3362 let tdFnSrc = trDom.children[0].children[1];
3363 let tdDesc = trDom.children[1];
3364
3365 let declType = resolveValue(decl.value);
3366 console.assert("type" in declType.expr);
3367 tdFnSignature.innerHTML = renderTokens(ex(declType.expr, {
3368 fnDecl: decl,
3369 linkFnNameDecl: navLinkDecl(decl.name),
3370 }));
3371 tdFnSrc.innerHTML = "<a style=\"float: right;\" target=\"_blank\" href=\"" +
3372 sourceFileLink(decl) + "\">[src]</a>";
3373
3374 let docs = getAstNode(decl.src).docs;
3375 if (docs != null) {
3376 docs = docs.trim();
3377 var short = shortDesc(docs);
3378 if (short != docs) {
3379 short = markdown(short, container);
3380 var long = markdown(docs, container); // TODO: this needs to be the file top lvl struct
3381 tdDesc.innerHTML =
3382 "<div class=\"expand\" ><span class=\"button\" onclick=\"toggleExpand(event)\"></span><div class=\"sum-less\">" + short + "</div>" + "<div class=\"sum-more\">" + long + "</div></details>";
3383 }
3384 else {
3385 tdDesc.innerHTML = markdown(short, container);
3386 }
3387 } else {
3388 tdDesc.innerHTML = "<p class='understated'><i>No documentation provided.</i><p>";
3389 }
3390 }
3391 domSectFns.classList.remove("hidden");
3392 }
3393
3394 let containerNode = getAstNode(container.src);
3395 if (containerNode.fields && containerNode.fields.length > 0) {
3396 resizeDomList(domListFields, containerNode.fields.length, "<div></div>");
3397
3398 for (let i = 0; i < containerNode.fields.length; i += 1) {
3399 let fieldNode = getAstNode(containerNode.fields[i]);
3400 let divDom = domListFields.children[i];
3401 let fieldName = fieldNode.name;
3402 let docs = fieldNode.docs;
3403 let docsNonEmpty = docs != null && docs !== "";
3404 let extraPreClass = docsNonEmpty ? " fieldHasDocs" : "";
3405
3406 let html =
3407 '<div class="mobile-scroll-container"><pre class="scroll-item' +
3408 extraPreClass +
3409 '">' +
3410 escapeHtml(fieldName);
3411
3412 if (container.kind === typeKinds.Enum) {
3413 let value = container.values[i];
3414 if (value !== null) {
3415 html += renderTokens((function*() {
3416 yield Tok.space;
3417 yield Tok.eql;
3418 yield Tok.space;
3419 yield* ex(value, {});
3420 })());
3421 }
3422 } else {
3423 let fieldTypeExpr = container.field_types[i];
3424 if (container.kind !== typeKinds.Struct || !container.is_tuple) {
3425 html += renderTokens((function*() {
3426 yield Tok.colon;
3427 yield Tok.space;
3428 })());
3429 }
3430 html += renderTokens(ex(fieldTypeExpr, {}));
3431 let tsn = typeShorthandName(fieldTypeExpr);
3432 if (tsn) {
3433 html += "<span> (" + tsn + ")</span>";
3434 }
3435 if (container.kind === typeKinds.Struct && !container.is_tuple) {
3436 let defaultInitExpr = container.field_defaults[i];
3437 if (defaultInitExpr !== null) {
3438 html += renderTokens((function*() {
3439 yield Tok.space;
3440 yield Tok.eql;
3441 yield Tok.space;
3442 yield* ex(defaultInitExpr, {});
3443 })());
3444 }
3445 }
3446 }
3447
3448 html += ",</pre></div>";
3449
3450 if (docsNonEmpty) {
3451 html += '<div class="fieldDocs">' + markdown(docs) + "</div>";
3452 }
3453 divDom.innerHTML = html;
3454 }
3455 domSectFields.classList.remove("hidden");
3456 }
3457
3458 if (varsList.length !== 0) {
3459 resizeDomList(
3460 domListGlobalVars,
3461 varsList.length,
3462 '<tr><td><a href="#"></a></td><td><pre class="inline"></pre></td><td></td></tr>'
3463 );
3464 for (let i = 0; i < varsList.length; i += 1) {
3465 let decl = varsList[i];
3466 let trDom = domListGlobalVars.children[i];
3467
3468 let tdName = trDom.children[0];
3469 let tdNameA = tdName.children[0];
3470 let tdType = trDom.children[1];
3471 let preType = tdType.children[0];
3472 let tdDesc = trDom.children[2];
3473
3474 tdNameA.setAttribute("href", navLinkDecl(decl.name));
3475 tdNameA.textContent = decl.name;
3476
3477 preType.innerHTML = renderTokens(ex(walkResultTypeRef(decl.value), {}));
3478
3479 let docs = getAstNode(decl.src).docs;
3480 if (docs != null) {
3481 tdDesc.innerHTML = shortDescMarkdown(docs);
3482 } else {
3483 tdDesc.textContent = "";
3484 }
3485 }
3486 domSectGlobalVars.classList.remove("hidden");
3487 }
3488
3489 if (valsList.length !== 0) {
3490 resizeDomList(
3491 domListValues,
3492 valsList.length,
3493 '<tr><td><a href="#"></a></td><td><pre class="inline"></pre></td><td></td></tr>'
3494 );
3495 for (let i = 0; i < valsList.length; i += 1) {
3496 let decl = valsList[i];
3497 let trDom = domListValues.children[i];
3498
3499 let tdName = trDom.children[0];
3500 let tdNameA = tdName.children[0];
3501 let tdType = trDom.children[1];
3502 let preType = tdType.children[0];
3503 let tdDesc = trDom.children[2];
3504
3505 tdNameA.setAttribute("href", navLinkDecl(decl.name));
3506 tdNameA.textContent = decl.name;
3507
3508 preType.innerHTML = renderTokens(ex(walkResultTypeRef(decl.value), {}));
3509
3510 let docs = getAstNode(decl.src).docs;
3511 if (docs != null) {
3512 tdDesc.innerHTML = shortDescMarkdown(docs);
3513 } else {
3514 tdDesc.textContent = "";
3515 }
3516 }
3517 domSectValues.classList.remove("hidden");
3518 }
3519
3520 if (testsList.length !== 0) {
3521 resizeDomList(
3522 domListTests,
3523 testsList.length,
3524 '<tr><td><pre class="inline"></pre></td><td><pre class="inline"></pre></td><td></td></tr>'
3525 );
3526 for (let i = 0; i < testsList.length; i += 1) {
3527 let decl = testsList[i];
3528 let trDom = domListTests.children[i];
3529
3530 let tdName = trDom.children[0];
3531 let tdNamePre = tdName.children[0];
3532 let tdType = trDom.children[1];
3533 let tdTypePre = tdType.children[0];
3534 let tdDesc = trDom.children[2];
3535
3536 tdNamePre.innerHTML = renderSingleToken(Tok.identifier(decl.name));
3537
3538 tdTypePre.innerHTML = ex(walkResultTypeRef(decl.value), {});
3539
3540 let docs = getAstNode(decl.src).docs;
3541 if (docs != null) {
3542 tdDesc.innerHTML = shortDescMarkdown(docs);
3543 } else {
3544 tdDesc.textContent = "";
3545 }
3546 }
3547 domSectTests.classList.remove("hidden");
3548 }
3549
3550 if (container.kind !== typeKinds.Struct || containerNode.fields.length > 0) {
3551 domHdrName.innerHTML = "<pre class='inline'>" +
3552 zigAnalysis.typeKinds[container.kind] +
3553 "</pre>";
3554 domHdrName.classList.remove("hidden");
3555 }
3556 }
3557
3558 function operatorCompare(a, b) {
3559 if (a === b) {
3560 return 0;
3561 } else if (a < b) {
3562 return -1;
3563 } else {
3564 return 1;
3565 }
3566 }
3567
3568 function detectRootIsStd() {
3569 let rootMod = zigAnalysis.modules[zigAnalysis.rootMod];
3570 if (rootMod.table["std"] == null) {
3571 // no std mapped into the root module
3572 return false;
3573 }
3574 let stdMod = zigAnalysis.modules[rootMod.table["std"]];
3575 if (stdMod == null) return false;
3576 return rootMod.file === stdMod.file;
3577 }
3578
3579 function indexTypeKinds() {
3580 let map = {};
3581 for (let i = 0; i < zigAnalysis.typeKinds.length; i += 1) {
3582 map[zigAnalysis.typeKinds[i]] = i;
3583 }
3584 // This is just for debugging purposes, not needed to function
3585 let assertList = [
3586 "Type",
3587 "Void",
3588 "Bool",
3589 "NoReturn",
3590 "Int",
3591 "Float",
3592 "Pointer",
3593 "Array",
3594 "Struct",
3595 "ComptimeFloat",
3596 "ComptimeInt",
3597 "Undefined",
3598 "Null",
3599 "Optional",
3600 "ErrorUnion",
3601 "ErrorSet",
3602 "Enum",
3603 "Union",
3604 "Fn",
3605 "Opaque",
3606 "Frame",
3607 "AnyFrame",
3608 "Vector",
3609 "EnumLiteral",
3610 ];
3611 for (let i = 0; i < assertList.length; i += 1) {
3612 if (map[assertList[i]] == null)
3613 throw new Error("No type kind '" + assertList[i] + "' found");
3614 }
3615 return map;
3616 }
3617
3618 function findTypeTypeId() {
3619 for (let i = 0; i < zigAnalysis.types.length; i += 1) {
3620 if (getType(i).kind == typeKinds.Type) {
3621 return i;
3622 }
3623 }
3624 throw new Error("No type 'type' found");
3625 }
3626
3627
3628 function updateCurNav() {
3629 curNav = {
3630 hash: location.hash,
3631 mode: NAV_MODES.API,
3632 modNames: [],
3633 modObjs: [],
3634 declNames: [],
3635 declObjs: [],
3636 callName: null,
3637 activeGuide: null,
3638 activeGuideScrollTo: null,
3639 };
3640 curNavSearch = "";
3641
3642 const mode = location.hash.substring(0, 3);
3643 let query = location.hash.substring(3);
3644
3645 let qpos = query.indexOf("?");
3646 let nonSearchPart;
3647 if (qpos === -1) {
3648 nonSearchPart = query;
3649 } else {
3650 nonSearchPart = query.substring(0, qpos);
3651 curNavSearch = decodeURIComponent(query.substring(qpos + 1));
3652 }
3653
3654 const DEFAULT_HASH = NAV_MODES.API + zigAnalysis.modules[zigAnalysis.rootMod].name;
3655 switch (mode) {
3656 case NAV_MODES.API:
3657 // #A;MODULE:decl.decl.decl?search-term
3658 curNav.mode = mode;
3659 {
3660 let parts = nonSearchPart.split(":");
3661 if (parts[0] == "") {
3662 location.hash = DEFAULT_HASH;
3663 } else {
3664 curNav.modNames = decodeURIComponent(parts[0]).split(".");
3665 }
3666
3667 if (parts[1] != null) {
3668 curNav.declNames = decodeURIComponent(parts[1]).split(".");
3669 }
3670 }
3671 return;
3672 case NAV_MODES.GUIDES:
3673 curNav.mode = mode;
3674
3675 {
3676 let parts = nonSearchPart.split(":");
3677 curNav.activeGuide = parts[0];
3678 if (parts[1] != null) {
3679 curNav.activeGuideScrollTo = decodeURIComponent(":" + parts[1]);
3680 }
3681 }
3682 return;
3683 default:
3684 location.hash = DEFAULT_HASH;
3685 return;
3686 }
3687 }
3688
3689 function onHashChange(ev) {
3690 scrollHistory[curNav.hash] = scrollMonitor.map(function (x) {
3691 return [x, x.scrollTop]
3692 });
3693
3694 if (skipNextHashChange == decodeURIComponent(location.hash)) {
3695 skipNextHashChange = null;
3696 return;
3697 }
3698 skipNextHashChange = null;
3699 updateCurNav();
3700
3701 if (domSearch.value !== curNavSearch) {
3702 domSearch.value = curNavSearch;
3703 if (domSearch.value.length == 0)
3704 domSearchPlaceholder.classList.remove("hidden");
3705 else
3706 domSearchPlaceholder.classList.add("hidden");
3707 }
3708 render();
3709 if (imFeelingLucky) {
3710 imFeelingLucky = false;
3711 activateSelectedResult();
3712 }
3713
3714 scroll();
3715 }
3716
3717 function scroll() {
3718 const cur = scrollHistory[location.hash];
3719 if (cur) {
3720 for (let [elem, offset] of cur) {
3721 elem.scrollTo(0, offset);
3722 }
3723 } else {
3724 if (curNav.activeGuideScrollTo) return;
3725 for (let elem of scrollMonitor) {
3726 elem.scrollTo(0, 0);
3727 }
3728 }
3729 }
3730
3731 function findSubDecl(parentTypeOrDecl, childName) {
3732 let parentType = parentTypeOrDecl;
3733 {
3734 // Generic functions / resolving decls
3735 if ("value" in parentType) {
3736 const rv = resolveValue(parentType.value);
3737 if ("type" in rv.expr) {
3738 const t = getType(rv.expr.type);
3739 parentType = t;
3740 if (t.kind == typeKinds.Fn && t.generic_ret != null) {
3741 let resolvedGenericRet = resolveValue({ expr: t.generic_ret });
3742
3743 if ("call" in resolvedGenericRet.expr) {
3744 let call = zigAnalysis.calls[resolvedGenericRet.expr.call];
3745 let resolvedFunc = resolveValue({ expr: call.func });
3746 if (!("type" in resolvedFunc.expr)) return null;
3747 let callee = getType(resolvedFunc.expr.type);
3748 if (!callee.generic_ret) return null;
3749 resolvedGenericRet = resolveValue({ expr: callee.generic_ret });
3750 }
3751
3752 if ("type" in resolvedGenericRet.expr) {
3753 parentType = getType(resolvedGenericRet.expr.type);
3754 }
3755 }
3756 }
3757 }
3758 }
3759
3760 if (parentType.pubDecls) {
3761 for (let i = 0; i < parentType.pubDecls.length; i += 1) {
3762 let declIndex = parentType.pubDecls[i];
3763 let childDecl = getDecl(declIndex);
3764 if (childDecl.name === childName) {
3765 childDecl.find_subdecl_idx = declIndex;
3766 return childDecl;
3767 } else if (childDecl.is_uns) {
3768 let declValue = resolveValue(childDecl.value);
3769 if (!("type" in declValue.expr)) continue;
3770 let uns_container = getType(declValue.expr.type);
3771 let uns_res = findSubDecl(uns_container, childName);
3772 if (uns_res !== null) return uns_res;
3773 }
3774 }
3775 }
3776
3777 if (parentType.privDecls) {
3778 for (let i = 0; i < parentType.privDecls.length; i += 1) {
3779 let declIndex = parentType.privDecls[i];
3780 let childDecl = getDecl(declIndex);
3781 if (childDecl.name === childName) {
3782 childDecl.find_subdecl_idx = declIndex;
3783 childDecl.is_private = true;
3784 return childDecl;
3785 } else if (childDecl.is_uns) {
3786 let declValue = resolveValue(childDecl.value);
3787 if (!("type" in declValue.expr)) continue;
3788 let uns_container = getType(declValue.expr.type);
3789 let uns_res = findSubDecl(uns_container, childName);
3790 uns_res.is_private = true;
3791 if (uns_res !== null) return uns_res;
3792 }
3793 }
3794 }
3795
3796 return null;
3797 }
3798
3799 function computeCanonicalModulePaths() {
3800 let list = new Array(zigAnalysis.modules.length);
3801 // Now we try to find all the modules from root.
3802 let rootMod = zigAnalysis.modules[zigAnalysis.rootMod];
3803 // Breadth-first to keep the path shortest possible.
3804 let stack = [
3805 {
3806 path: [],
3807 mod: rootMod,
3808 },
3809 ];
3810 while (stack.length !== 0) {
3811 let item = stack.shift();
3812 for (let key in item.mod.table) {
3813 let childModIndex = item.mod.table[key];
3814 if (list[childModIndex] != null) continue;
3815 let childMod = zigAnalysis.modules[childModIndex];
3816 if (childMod == null) continue;
3817
3818 let newPath = item.path.concat([key]);
3819 list[childModIndex] = newPath;
3820 stack.push({
3821 path: newPath,
3822 mod: childMod,
3823 });
3824 }
3825 }
3826
3827 for (let i = 0; i < zigAnalysis.modules.length; i += 1) {
3828 const p = zigAnalysis.modules[i];
3829 // TODO
3830 // declSearchIndex.add(p.name, {moduleId: i});
3831 }
3832 return list;
3833 }
3834
3835 function computeCanonDeclPaths() {
3836 let list = new Array(zigAnalysis.decls.length);
3837 canonTypeDecls = new Array(zigAnalysis.types.length);
3838
3839 for (let modI = 0; modI < zigAnalysis.modules.length; modI += 1) {
3840 let mod = zigAnalysis.modules[modI];
3841 let modNames = canonModPaths[modI];
3842 if (modNames === undefined) continue;
3843
3844 let stack = [
3845 {
3846 declNames: [],
3847 declIndexes: [],
3848 type: getType(mod.main),
3849 },
3850 ];
3851 while (stack.length !== 0) {
3852 let item = stack.shift();
3853
3854 if (isContainerType(item.type)) {
3855 let t = item.type;
3856
3857 let len = t.pubDecls ? t.pubDecls.length : 0;
3858 for (let declI = 0; declI < len; declI += 1) {
3859 let declIndex = t.pubDecls[declI];
3860 if (list[declIndex] != null) continue;
3861
3862 let decl = getDecl(declIndex);
3863
3864 if (decl.is_uns) {
3865 let unsDeclList = [decl];
3866 while (unsDeclList.length != 0) {
3867 let unsDecl = unsDeclList.pop();
3868 let unsDeclVal = resolveValue(unsDecl.value);
3869 if (!("type" in unsDeclVal.expr)) continue;
3870 let unsType = getType(unsDeclVal.expr.type);
3871 if (!isContainerType(unsType)) continue;
3872 let unsPubDeclLen = unsType.pubDecls ? unsType.pubDecls.length : 0;
3873 for (let unsDeclI = 0; unsDeclI < unsPubDeclLen; unsDeclI += 1) {
3874 let childDeclIndex = unsType.pubDecls[unsDeclI];
3875 let childDecl = getDecl(childDeclIndex);
3876
3877 if (childDecl.is_uns) {
3878 unsDeclList.push(childDecl);
3879 } else {
3880 addDeclToSearchResults(childDecl, childDeclIndex, modNames, item, list, stack);
3881 }
3882 }
3883 }
3884 } else {
3885 addDeclToSearchResults(decl, declIndex, modNames, item, list, stack);
3886 }
3887 }
3888 }
3889 }
3890 }
3891 window.cdp = list;
3892 return list;
3893 }
3894
3895 function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
3896 let {value: declVal, seenDecls} = resolveValue(decl.value, true);
3897 let declNames = item.declNames.concat([decl.name]);
3898 let declIndexes = item.declIndexes.concat([declIndex]);
3899
3900 if (list[declIndex] != null) return;
3901 list[declIndex] = {
3902 modNames: modNames,
3903 declNames: declNames,
3904 declIndexes: declIndexes,
3905 };
3906
3907 for (let sd of seenDecls) {
3908 if (list[sd] != null) continue;
3909 list[sd] = {
3910 modNames: modNames,
3911 declNames: declNames,
3912 declIndexes: declIndexes,
3913 };
3914 }
3915
3916 // add to search index
3917 {
3918 declSearchIndex.add(decl.name, { declIndex });
3919 }
3920
3921
3922 if ("type" in declVal.expr) {
3923 let value = getType(declVal.expr.type);
3924 if (declCanRepresentTypeKind(value.kind)) {
3925 canonTypeDecls[declVal.type] = declIndex;
3926 }
3927
3928 if (isContainerType(value)) {
3929 stack.push({
3930 declNames: declNames,
3931 declIndexes: declIndexes,
3932 type: value,
3933 });
3934 }
3935
3936 // Generic function
3937 if (typeIsGenericFn(declVal.expr.type)) {
3938 let ret = resolveGenericRet(value);
3939 if (ret != null && "type" in ret.expr) {
3940 let generic_type = getType(ret.expr.type);
3941 if (isContainerType(generic_type)) {
3942 stack.push({
3943 declNames: declNames,
3944 declIndexes: declIndexes,
3945 type: generic_type,
3946 });
3947 }
3948 }
3949 }
3950 }
3951 }
3952
3953 function declLinkOrSrcLink(index) {
3954
3955 let match = getCanonDeclPath(index);
3956 if (match) return navLink(match.modNames, match.declNames);
3957
3958 // could not find a precomputed decl path
3959 const decl = getDecl(index);
3960
3961 // try to find a public decl by scanning declRefs and declPaths
3962 let value = decl.value;
3963 let i = 0;
3964 while (true) {
3965 i += 1;
3966 if (i >= 10000) {
3967 throw "getCanonDeclPath quota exceeded"
3968 }
3969
3970 if ("refPath" in value.expr) {
3971 value = { expr: value.expr.refPath[value.expr.refPath.length - 1] };
3972 continue;
3973 }
3974
3975 if ("declRef" in value.expr) {
3976 let cp = canonDeclPaths[value.expr.declRef];
3977 if (cp) return navLink(cp.modNames, cp.declNames);
3978
3979 value = getDecl(value.expr.declRef).value;
3980 continue;
3981 }
3982
3983 if ("as" in value.expr) {
3984 value = {
3985 typeRef: zigAnalysis.exprs[value.expr.as.typeRefArg],
3986 expr: zigAnalysis.exprs[value.expr.as.exprArg],
3987 };
3988 continue;
3989 }
3990
3991 // if we got here it means that we failed
3992 // produce a link to source code instead
3993 return sourceFileLink(decl);
3994
3995 }
3996
3997 }
3998
3999 function getCanonDeclPath(index) {
4000 if (canonDeclPaths == null) {
4001 canonDeclPaths = computeCanonDeclPaths();
4002 }
4003
4004 return canonDeclPaths[index];
4005
4006
4007 }
4008
4009 function getCanonTypeDecl(index) {
4010 getCanonDeclPath(0);
4011 //let ct = (canonTypeDecls);
4012 return canonTypeDecls[index];
4013 }
4014
4015 function escapeHtml(text) {
4016 return text.replace(/[&"<>]/g, function(m) {
4017 return escapeHtmlReplacements[m];
4018 });
4019 }
4020
4021 function shortDesc(docs) {
4022 const trimmed_docs = docs.trim();
4023 let index = trimmed_docs.indexOf("\n\n");
4024 let cut = false;
4025
4026 if (index < 0 || index > 130) {
4027 if (trimmed_docs.length > 130) {
4028 index = 130;
4029 cut = true;
4030 } else {
4031 index = trimmed_docs.length;
4032 }
4033 }
4034
4035 let slice = trimmed_docs.slice(0, index);
4036 if (cut) slice += "...";
4037 return slice;
4038 }
4039
4040 function shortDescMarkdown(docs) {
4041 return markdown(shortDesc(docs));
4042 }
4043
4044 function parseGuides() {
4045 for (let j = 0; j < zigAnalysis.guideSections.length; j += 1) {
4046 const section = zigAnalysis.guideSections[j];
4047 for (let i = 0; i < section.guides.length; i += 1) {
4048 let reader = new commonmark.Parser({ smart: true });
4049 const guide = section.guides[i];
4050
4051 // Find the first text thing to use as a sidebar title
4052 guide.title = null;
4053 guide.toc = "";
4054
4055 // Discover Title & TOC for this guide
4056 {
4057 let reader = new commonmark.Parser({smart: true});
4058 let ast = reader.parse(guide.body);
4059 let walker = ast.walker();
4060 let heading_idx = 0;
4061 let event, node, doc, last, last_ul;
4062 while ((event = walker.next())) {
4063 node = event.node;
4064 if (event.entering) {
4065 if (node.type === 'document') {
4066 doc = node;
4067 continue;
4068 }
4069
4070
4071 if (node.next) {
4072 walker.resumeAt(node.next, true);
4073 } else {
4074 walker.resumeAt(node, false);
4075 }
4076 node.unlink();
4077
4078 if (node.type === 'heading') {
4079 if (node.level == 1) {
4080 if (guide.title == null) {
4081 let doc_node = new commonmark.Node("document", node.sourcepos);
4082 while (node.firstChild) {
4083 doc_node.appendChild(node.firstChild);
4084 }
4085 let writer = new commonmark.HtmlRenderer();
4086 let result = writer.render(doc_node);
4087 guide.title = result;
4088 }
4089
4090 continue; // don't index H1
4091 }
4092
4093 // turn heading node into list item & add link node to it
4094 {
4095 node._type = "link";
4096 node.destination = NAV_MODES.GUIDES + guide.name + ":" + heading_idx;
4097 heading_idx += 1;
4098 let listItem = new commonmark.Node("item", node.sourcepos);
4099 // TODO: strip links from inside node
4100 listItem.appendChild(node);
4101 listItem.level = node.level;
4102 node = listItem;
4103 }
4104
4105 if (last_ul) {
4106 // are we inside or outside of it?
4107
4108 let target_ul = last_ul;
4109 while(target_ul.level > node.level) {
4110 target_ul = target_ul.parent;
4111 }
4112 while(target_ul.level < node.level) {
4113 let ul_node = new commonmark.Node("list", node.sourcepos);
4114 ul_node.level = target_ul.level + 1;
4115 ul_node.listType = "bullet";
4116 ul_node.listStart = null;
4117 target_ul.appendChild(ul_node);
4118 target_ul = ul_node;
4119 }
4120
4121 target_ul.appendChild(node);
4122 last_ul = target_ul;
4123 } else {
4124 let ul_node = new commonmark.Node("list", node.sourcepos);
4125 ul_node.level = 2;
4126 ul_node.listType = "bullet";
4127 ul_node.listStart = null;
4128 doc.prependChild(ul_node);
4129
4130 while (ul_node.level < node.level) {
4131 let current_ul_node = new commonmark.Node("list", node.sourcepos);
4132 current_ul_node.level = ul_node.level + 1;
4133 current_ul_node.listType = "bullet";
4134 current_ul_node.listStart = null;
4135 ul_node.appendChild(current_ul_node);
4136 ul_node = current_ul_node;
4137 }
4138
4139 last_ul = ul_node;
4140
4141 ul_node.appendChild(node);
4142 }
4143 }
4144 }
4145 }
4146
4147 let writer = new commonmark.HtmlRenderer();
4148 let result = writer.render(ast);
4149 guide.toc = result;
4150 }
4151
4152 // Index this guide
4153 {
4154 // let walker = guide.ast.walker();
4155 // let event, node;
4156 // while ((event = walker.next())) {
4157 // node = event.node;
4158 // if (event.entering == true && node.type === 'text') {
4159 // indexTextForGuide(j, i, node);
4160 // }
4161 // }
4162 }
4163 }
4164 }
4165 }
4166
4167 function indexTextForGuide(section_idx, guide_idx, node) {
4168 const terms = node.literal.split(" ");
4169 for (let i = 0; i < terms.length; i += 1) {
4170 const t = terms[i];
4171 if (!guidesSearchIndex[t]) guidesSearchIndex[t] = new Set();
4172 node.guide = { section_idx, guide_idx };
4173 guidesSearchIndex[t].add(node);
4174 }
4175 }
4176
4177
4178 function markdown(input, contextType) {
4179 const parsed = new commonmark.Parser({ smart: true }).parse(input);
4180
4181 // Look for decl references in inline code (`ref`)
4182 const walker = parsed.walker();
4183 let event;
4184 while ((event = walker.next())) {
4185 const node = event.node;
4186 if (node.type === "code") {
4187 const declHash = detectDeclPath(node.literal, contextType);
4188 if (declHash) {
4189 const link = new commonmark.Node("link");
4190 link.destination = declHash;
4191 node.insertBefore(link);
4192 link.appendChild(node);
4193 }
4194 }
4195 }
4196
4197 return new commonmark.HtmlRenderer({ safe: true }).render(parsed);
4198
4199 }
4200
4201
4202
4203 // function detectDeclPath(text, context) {
4204 // let result = "";
4205 // let separator = ":";
4206 // const components = text.split(".");
4207 // let curDeclOrType = undefined;
4208
4209 // let curContext = context;
4210 // let limit = 10000;
4211 // while (curContext) {
4212 // limit -= 1;
4213
4214 // if (limit == 0) {
4215 // throw "too many iterations";
4216 // }
4217
4218 // curDeclOrType = findSubDecl(curContext, components[0]);
4219
4220 // if (!curDeclOrType) {
4221 // if (curContext.parent_container == null) break;
4222 // curContext = getType(curContext.parent_container);
4223 // continue;
4224 // }
4225
4226 // if (curContext == context) {
4227 // separator = '.';
4228 // result = location.hash + separator + components[0];
4229 // } else {
4230 // // We had to go up, which means we need a new path!
4231 // const canonPath = getCanonDeclPath(curDeclOrType.find_subdecl_idx);
4232 // if (!canonPath) return;
4233
4234 // let lastModName = canonPath.modNames[canonPath.modNames.length - 1];
4235 // let fullPath = lastModName + ":" + canonPath.declNames.join(".");
4236
4237 // separator = '.';
4238 // result = "#A;" + fullPath;
4239 // }
4240
4241 // break;
4242 // }
4243
4244 // if (!curDeclOrType) {
4245 // for (let i = 0; i < zigAnalysis.modules.length; i += 1) {
4246 // const p = zigAnalysis.modules[i];
4247 // if (p.name == components[0]) {
4248 // curDeclOrType = getType(p.main);
4249 // result += "#A;" + components[0];
4250 // break;
4251 // }
4252 // }
4253 // }
4254
4255 // if (!curDeclOrType) return null;
4256
4257 // for (let i = 1; i < components.length; i += 1) {
4258 // curDeclOrType = findSubDecl(curDeclOrType, components[i]);
4259 // if (!curDeclOrType) return null;
4260 // result += separator + components[i];
4261 // separator = '.';
4262 // }
4263
4264 // return result;
4265
4266 // }
4267
4268 function activateSelectedResult() {
4269 if (domSectSearchResults.classList.contains("hidden")) {
4270 return;
4271 }
4272
4273 const searchResults = domListSearchResults.getElementsByTagName("li");
4274 let liDom = searchResults[curSearchIndex];
4275 if (liDom == null && searchResults.length !== 0) {
4276 liDom = searchResults[0];
4277 }
4278 if (liDom != null) {
4279 let aDom = liDom.children[0];
4280 location.href = aDom.getAttribute("href");
4281 curSearchIndex = -1;
4282 }
4283 domSearch.blur();
4284 }
4285
4286 // hide the modal if it's visible or return to the previous result page and unfocus the search
4287 function onEscape(ev) {
4288 if (isModalVisible(domHelpModal)) {
4289 hideModal(domHelpModal);
4290 ev.preventDefault();
4291 ev.stopPropagation();
4292 } else if (isModalVisible(domPrefsModal)) {
4293 hideModal(domPrefsModal);
4294 ev.preventDefault();
4295 ev.stopPropagation();
4296 } else {
4297 domSearch.value = "";
4298 domSearch.blur();
4299 domSearchPlaceholder.classList.remove("hidden");
4300 curSearchIndex = -1;
4301 ev.preventDefault();
4302 ev.stopPropagation();
4303 startSearch();
4304 }
4305 }
4306
4307
4308 function onSearchKeyDown(ev) {
4309 switch (getKeyString(ev)) {
4310 case "Enter":
4311 // detect if this search changes anything
4312 let terms1 = getSearchTerms();
4313 startSearch();
4314 updateCurNav();
4315 let terms2 = getSearchTerms();
4316 // we might have to wait for onHashChange to trigger
4317 imFeelingLucky = terms1.join(" ") !== terms2.join(" ");
4318 if (!imFeelingLucky) activateSelectedResult();
4319
4320 ev.preventDefault();
4321 ev.stopPropagation();
4322 return;
4323 case "Esc":
4324 onEscape(ev);
4325 return
4326 case "Up":
4327 moveSearchCursor(-1);
4328 ev.preventDefault();
4329 ev.stopPropagation();
4330 return;
4331 case "Down":
4332 // TODO: make the page scroll down if the search cursor is out of the screen
4333 moveSearchCursor(1);
4334 ev.preventDefault();
4335 ev.stopPropagation();
4336 return;
4337 default:
4338 // Search is triggered via an `input` event handler, not on arbitrary `keydown` events.
4339 ev.stopPropagation();
4340 return;
4341 }
4342 }
4343
4344 let domDotsToggleTimeout = null;
4345 function onSearchInput(ev) {
4346 curSearchIndex = -1;
4347
4348 let replaced = domSearch.value.replaceAll(".", " ")
4349
4350 // Ping red the help text if the user typed a dot.
4351 if (replaced != domSearch.value) {
4352 domSearchHelpSummary.classList.remove("normal");
4353 if (domDotsToggleTimeout != null) {
4354 clearTimeout(domDotsToggleTimeout);
4355 domDotsToggleTimeout = null;
4356 }
4357 domDotsToggleTimeout = setTimeout(function () {
4358 domSearchHelpSummary.classList.add("normal");
4359 }, 1000);
4360 }
4361
4362 replaced = replaced.replace(/ +/g, ' ');
4363 if (replaced != domSearch.value) {
4364 domSearch.value = replaced;
4365 }
4366
4367 startAsyncSearch();
4368 }
4369
4370 function moveSearchCursor(dir) {
4371 const searchResults = domListSearchResults.getElementsByTagName("li");
4372 if (
4373 curSearchIndex < 0 ||
4374 curSearchIndex >= searchResults.length
4375 ) {
4376 if (dir > 0) {
4377 curSearchIndex = -1 + dir;
4378 } else if (dir < 0) {
4379 curSearchIndex = searchResults.length + dir;
4380 }
4381 } else {
4382 curSearchIndex += dir;
4383 }
4384 if (curSearchIndex < 0) {
4385 curSearchIndex = 0;
4386 }
4387 if (curSearchIndex >= searchResults.length) {
4388 curSearchIndex = searchResults.length - 1;
4389 }
4390 renderSearchCursor();
4391 }
4392
4393 function getKeyString(ev) {
4394 let name;
4395 let ignoreShift = false;
4396 switch (ev.which) {
4397 case 13:
4398 name = "Enter";
4399 break;
4400 case 27:
4401 name = "Esc";
4402 break;
4403 case 38:
4404 name = "Up";
4405 break;
4406 case 40:
4407 name = "Down";
4408 break;
4409 default:
4410 ignoreShift = true;
4411 name =
4412 ev.key != null
4413 ? ev.key
4414 : String.fromCharCode(ev.charCode || ev.keyCode);
4415 }
4416 if (!ignoreShift && ev.shiftKey) name = "Shift+" + name;
4417 if (ev.altKey) name = "Alt+" + name;
4418 if (ev.ctrlKey) name = "Ctrl+" + name;
4419 return name;
4420 }
4421
4422 function onWindowKeyDown(ev) {
4423 switch (getKeyString(ev)) {
4424 case "Esc":
4425 onEscape(ev);
4426 break;
4427 case "/":
4428 if (!getPrefSlashSearch()) break;
4429 // fallthrough
4430 case "s":
4431 if (!isModalVisible(domHelpModal) && !isModalVisible(domPrefsModal)) {
4432 if (ev.target == domSearch) break;
4433
4434 domSearch.focus();
4435 domSearch.select();
4436 domDocs.scrollTo(0, 0);
4437 ev.preventDefault();
4438 ev.stopPropagation();
4439 startAsyncSearch();
4440 }
4441 break;
4442 case "?":
4443 if (!canToggleModal) break;
4444
4445 if (isModalVisible(domPrefsModal)) {
4446 hideModal(domPrefsModal);
4447 }
4448
4449 // toggle the help modal
4450 if (isModalVisible(domHelpModal)) {
4451 hideModal(domHelpModal);
4452 } else {
4453 showModal(domHelpModal);
4454 }
4455 ev.preventDefault();
4456 ev.stopPropagation();
4457 break;
4458 case "p":
4459 if (!canToggleModal) break;
4460
4461 if (isModalVisible(domHelpModal)) {
4462 hideModal(domHelpModal);
4463 }
4464
4465 // toggle the preferences modal
4466 if (isModalVisible(domPrefsModal)) {
4467 hideModal(domPrefsModal);
4468 } else {
4469 showModal(domPrefsModal);
4470 }
4471 ev.preventDefault();
4472 ev.stopPropagation();
4473 }
4474 }
4475
4476 function isModalVisible(modal) {
4477 return !modal.classList.contains("hidden");
4478 }
4479
4480 function showModal(modal) {
4481 modal.classList.remove("hidden");
4482 modal.style.left =
4483 window.innerWidth / 2 - modal.clientWidth / 2 + "px";
4484 modal.style.top =
4485 window.innerHeight / 2 - modal.clientHeight / 2 + "px";
4486 const firstInput = modal.querySelector("input");
4487 if (firstInput) {
4488 firstInput.focus();
4489 } else {
4490 modal.focus();
4491 }
4492 domSearch.blur();
4493 domBanner.inert = true;
4494 domMain.inert = true;
4495 }
4496
4497 function hideModal(modal) {
4498 modal.classList.add("hidden");
4499 domBanner.inert = false;
4500 domMain.inert = false;
4501 modal.blur();
4502 }
4503
4504 function clearAsyncSearch() {
4505 if (searchTimer != null) {
4506 clearTimeout(searchTimer);
4507 searchTimer = null;
4508 }
4509 }
4510
4511 function startAsyncSearch() {
4512 clearAsyncSearch();
4513 searchTimer = setTimeout(startSearch, 100);
4514 }
4515 function startSearch() {
4516 clearAsyncSearch();
4517 let oldHash = location.hash;
4518 let parts = oldHash.split("?");
4519 let newPart2 = domSearch.value === "" ? "" : "?" + domSearch.value;
4520 location.replace(parts.length === 1 ? oldHash + newPart2 : parts[0] + newPart2);
4521 }
4522 function getSearchTerms() {
4523 let list = curNavSearch.trim().split(/[ \r\n\t]+/);
4524 return list;
4525 }
4526
4527 function renderSearchGuides() {
4528 const searchTrimmed = false;
4529 let ignoreCase = curNavSearch.toLowerCase() === curNavSearch;
4530
4531 let terms = getSearchTerms();
4532 let matchedItems = new Set();
4533
4534 for (let i = 0; i < terms.length; i += 1) {
4535 const nodes = guidesSearchIndex[terms[i]];
4536 if (nodes) {
4537 for (const n of nodes) {
4538 matchedItems.add(n);
4539 }
4540 }
4541 }
4542
4543
4544
4545 if (matchedItems.size !== 0) {
4546 // Build up the list of search results
4547 let matchedItemsHTML = "";
4548
4549 for (const node of matchedItems) {
4550 const text = node.literal;
4551 const href = "";
4552
4553 matchedItemsHTML += "<li><a href=\"" + href + "\">" + text + "</a></li>";
4554 }
4555
4556 // Replace the search results using our newly constructed HTML string
4557 domListSearchResults.innerHTML = matchedItemsHTML;
4558 if (searchTrimmed) {
4559 domSectSearchAllResultsLink.classList.remove("hidden");
4560 }
4561 renderSearchCursor();
4562
4563 domSectSearchResults.classList.remove("hidden");
4564 } else {
4565 domSectSearchNoResults.classList.remove("hidden");
4566 }
4567 }
4568
4569 function renderSearchAPI() {
4570 domSectSearchResults.prepend(
4571 domSearchHelp.parentElement.removeChild(domSearchHelp)
4572 );
4573 if (canonDeclPaths == null) {
4574 canonDeclPaths = computeCanonDeclPaths();
4575 }
4576 let declSet = new Set();
4577 let otherDeclSet = new Set(); // for low quality results
4578 let declScores = {};
4579
4580 let ignoreCase = curNavSearch.toLowerCase() === curNavSearch;
4581 let term_list = getSearchTerms();
4582 for (let i = 0; i < term_list.length; i += 1) {
4583 let term = term_list[i];
4584 let result = declSearchIndex.search(term.toLowerCase());
4585 if (result == null) {
4586 domSectSearchNoResults.prepend(
4587 domSearchHelp.parentElement.removeChild(domSearchHelp)
4588 );
4589 domSectSearchNoResults.classList.remove("hidden");
4590
4591 domSectSearchResults.classList.add("hidden");
4592 return;
4593 }
4594
4595 let termSet = new Set();
4596 let termOtherSet = new Set();
4597
4598 for (let list of [result.full, result.partial]) {
4599 for (let r of list) {
4600 const d = r.declIndex;
4601 const decl = getDecl(d);
4602 const canonPath = getCanonDeclPath(d);
4603
4604 // collect unconditionally for the first term
4605 if (i == 0) {
4606 declSet.add(d);
4607 } else {
4608 // path intersection for subsequent terms
4609 let found = false;
4610 for (let p of canonPath.declIndexes) {
4611 if (declSet.has(p)) {
4612 found = true;
4613 break;
4614 }
4615 }
4616 if (!found) {
4617 otherDeclSet.add(d);
4618 } else {
4619 termSet.add(d);
4620 }
4621 }
4622
4623 if (declScores[d] == undefined) declScores[d] = 0;
4624
4625 // scores (lower is better)
4626 let decl_name = decl.name;
4627 if (ignoreCase) decl_name = decl_name.toLowerCase();
4628
4629 // shallow path are preferable
4630 const path_depth = canonPath.declNames.length * 50;
4631 // matching the start of a decl name is good
4632 const match_from_start = decl_name.startsWith(term) ? -term.length * (2 - ignoreCase) : (decl_name.length - term.length) + 1;
4633 // being a perfect match is good
4634 const is_full_match = (decl_name === term) ? -decl_name.length * (1 - ignoreCase) : Math.abs(decl_name.length - term.length);
4635 // matching the end of a decl name is good
4636 const matches_the_end = decl_name.endsWith(term) ? -term.length * (1 - ignoreCase) : (decl_name.length - term.length) + 1;
4637 // explicitly penalizing scream case decls
4638 const decl_is_scream_case = decl.name.toUpperCase() != decl.name ? 0 : decl.name.length;
4639
4640 const score = path_depth
4641 + match_from_start
4642 + is_full_match
4643 + matches_the_end
4644 + decl_is_scream_case;
4645
4646 declScores[d] += score;
4647 }
4648 }
4649 if (i != 0) {
4650 for (let d of declSet) {
4651 if (termSet.has(d)) continue;
4652 let found = false;
4653 for (let p of getCanonDeclPath(d).declIndexes) {
4654 if (termSet.has(p) || otherDeclSet.has(p)) {
4655 found = true;
4656 break;
4657 }
4658 }
4659 if (found) {
4660 declScores[d] = declScores[d] / term_list.length;
4661 }
4662
4663 termOtherSet.add(d);
4664 }
4665 declSet = termSet;
4666 for (let d of termOtherSet) {
4667 otherDeclSet.add(d);
4668 }
4669
4670 }
843 function setInputString(s) {
844 const jsArray = text_encoder.encode(s);
845 const len = jsArray.length;
846 const ptr = wasm_exports.set_input_string(len);
847 const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, len);
848 wasmArray.set(jsArray);
4671849 }
4672
4673 let matchedItems = {
4674 high_quality: [],
4675 low_quality: [],
4676 };
4677 for (let idx of declSet) {
4678 matchedItems.high_quality.push({ points: declScores[idx], declIndex: idx })
4679 }
4680 for (let idx of otherDeclSet) {
4681 matchedItems.low_quality.push({ points: declScores[idx], declIndex: idx })
4682 }
4683
4684 matchedItems.high_quality.sort(function(a, b) {
4685 let cmp = operatorCompare(a.points, b.points);
4686 return cmp;
4687 });
4688 matchedItems.low_quality.sort(function(a, b) {
4689 let cmp = operatorCompare(a.points, b.points);
4690 return cmp;
4691 });
4692
4693 // Build up the list of search results
4694 let matchedItemsHTML = "";
4695
4696 for (let list of [matchedItems.high_quality, matchedItems.low_quality]) {
4697 if (list == matchedItems.low_quality && list.length > 0) {
4698 matchedItemsHTML += "<hr class='other-results'>"
4699 }
4700 for (let result of list) {
4701 const points = result.points;
4702 const match = result.declIndex;
4703
4704 let canonPath = getCanonDeclPath(match);
4705 if (canonPath == null) continue;
4706
4707 let lastModName = canonPath.modNames[canonPath.modNames.length - 1];
4708 let text = lastModName + "." + canonPath.declNames.join(".");
4709
4710
4711 const href = navLink(canonPath.modNames, canonPath.declNames);
4712
4713 matchedItemsHTML += "<li><a href=\"" + href + "\">" + text + "</a></li>";
4714 }
4715 }
4716
4717 // Replace the search results using our newly constructed HTML string
4718 domListSearchResults.innerHTML = matchedItemsHTML;
4719 renderSearchCursor();
4720
4721 domSectSearchResults.classList.remove("hidden");
4722 }
4723
4724
4725
4726 function renderSearchCursor() {
4727 const searchResults = domListSearchResults.getElementsByTagName("li");
4728 for (let i = 0; i < searchResults.length; i += 1) {
4729 let liDom = searchResults[i];
4730 if (curSearchIndex === i) {
4731 liDom.classList.add("selected");
4732 } else {
4733 liDom.classList.remove("selected");
4734 }
4735 }
4736 }
4737
4738 function scrollGuidesTop(ev) {
4739 document.getElementById("activeGuide").children[0].scrollIntoView({
4740 behavior: "smooth",
4741 });
4742 ev.preventDefault();
4743 ev.stopPropagation();
4744 }
4745 document.scrollGuidesTop = scrollGuidesTop;
4746
4747 function scrollToHeading(id, alreadyThere) {
4748 // Don't scroll if the current location has a scrolling history.
4749 if (scrollHistory[location.hash]) return;
4750
4751 const c = document.getElementById(id);
4752 if (c && alreadyThere) {
4753 requestAnimationFrame(() => c.scrollIntoView({behavior: "smooth"}));
4754 } else {
4755 requestAnimationFrame(() => c.scrollIntoView());
4756 }
4757 return;
4758 }
4759 // function indexNodesToCalls() {
4760 // let map = {};
4761 // for (let i = 0; i < zigAnalysis.calls.length; i += 1) {
4762 // let call = zigAnalysis.calls[i];
4763 // let fn = zigAnalysis.fns[call.fn];
4764 // if (map[fn.src] == null) {
4765 // map[fn.src] = [i];
4766 // } else {
4767 // map[fn.src].push(i);
4768 // }
4769 // }
4770 // return map;
4771 // }
4772
4773 function byNameProperty(a, b) {
4774 return operatorCompare(a.name, b.name);
4775 }
4776
4777
4778 function getDecl(idx) {
4779 const decl = zigAnalysis.decls[idx];
4780 return {
4781 name: decl[0],
4782 kind: decl[1],
4783 src: decl[2],
4784 value: decl[3],
4785 decltest: decl[4],
4786 is_uns: decl[5],
4787 parent_container: decl[6],
4788 };
4789 }
4790
4791 function getAstNode(idx) {
4792 const ast = zigAnalysis.astNodes[idx];
4793 return {
4794 file: ast[0],
4795 line: ast[1],
4796 col: ast[2],
4797 name: ast[3],
4798 code: ast[4],
4799 docs: ast[5],
4800 fields: ast[6],
4801 comptime: ast[7],
4802 };
4803 }
4804
4805 function getFile(idx) {
4806 const file = zigAnalysis.files[idx];
4807 return {
4808 name: file[0],
4809 modIndex: file[1],
4810 };
4811 }
4812
4813 function getType(idx) {
4814 const ty = zigAnalysis.types[idx];
4815 switch (ty[0]) {
4816 default:
4817 throw "unhandled type kind!";
4818 case typeKinds.Unanalyzed:
4819 throw "unanalyzed type!";
4820 case typeKinds.Type:
4821 case typeKinds.Void:
4822 case typeKinds.Bool:
4823 case typeKinds.NoReturn:
4824 case typeKinds.Int:
4825 case typeKinds.Float:
4826 return { kind: ty[0], name: ty[1] };
4827 case typeKinds.Pointer:
4828 return {
4829 kind: ty[0],
4830 size: ty[1],
4831 child: ty[2],
4832 sentinel: ty[3],
4833 align: ty[4],
4834 address_space: ty[5],
4835 bit_start: ty[6],
4836 host_size: ty[7],
4837 is_ref: ty[8],
4838 is_allowzero: ty[9],
4839 is_mutable: ty[10],
4840 is_volatile: ty[11],
4841 has_sentinel: ty[12],
4842 has_align: ty[13],
4843 has_addrspace: ty[14],
4844 has_bit_range: ty[15],
4845 };
4846 case typeKinds.Array:
4847 return {
4848 kind: ty[0],
4849 len: ty[1],
4850 child: ty[2],
4851 sentinel: ty[3],
4852 };
4853 case typeKinds.Struct:
4854 return {
4855 kind: ty[0],
4856 name: ty[1],
4857 src: ty[2],
4858 privDecls: ty[3],
4859 pubDecls: ty[4],
4860 field_types: ty[5],
4861 field_defaults: ty[6],
4862 backing_int: ty[7],
4863 is_tuple: ty[8],
4864 line_number: ty[9],
4865 parent_container: ty[10],
4866 layout: ty[11],
4867 };
4868 case typeKinds.ComptimeExpr:
4869 case typeKinds.ComptimeFloat:
4870 case typeKinds.ComptimeInt:
4871 case typeKinds.Undefined:
4872 case typeKinds.Null:
4873 return { kind: ty[0], name: ty[1] };
4874 case typeKinds.Optional:
4875 return {
4876 kind: ty[0],
4877 name: ty[1],
4878 child: ty[2],
4879 };
4880 case typeKinds.ErrorUnion:
4881 return {
4882 kind: ty[0],
4883 lhs: ty[1],
4884 rhs: ty[2],
4885 };
4886 case typeKinds.InferredErrorUnion:
4887 return {
4888 kind: ty[0],
4889 payload: ty[1],
4890 };
4891 case typeKinds.ErrorSet:
4892 return {
4893 kind: ty[0],
4894 name: ty[1],
4895 fields: ty[2],
4896 };
4897 case typeKinds.Enum:
4898 return {
4899 kind: ty[0],
4900 name: ty[1],
4901 src: ty[2],
4902 privDecls: ty[3],
4903 pubDecls: ty[4],
4904 tag: ty[5],
4905 values: ty[6],
4906 nonexhaustive: ty[7],
4907 parent_container: ty[8],
4908 };
4909 case typeKinds.Union:
4910 return {
4911 kind: ty[0],
4912 name: ty[1],
4913 src: ty[2],
4914 privDecls: ty[3],
4915 pubDecls: ty[4],
4916 field_types: ty[5],
4917 tag: ty[6],
4918 auto_tag: ty[7],
4919 parent_container: ty[8],
4920 layout: ty[9],
4921 };
4922 case typeKinds.Fn:
4923 return {
4924 kind: ty[0],
4925 name: ty[1],
4926 src: ty[2],
4927 ret: ty[3],
4928 generic_ret: ty[4],
4929 params: ty[5],
4930 lib_name: ty[6],
4931 is_var_args: ty[7],
4932 is_inferred_error: ty[8],
4933 has_lib_name: ty[9],
4934 has_cc: ty[10],
4935 cc: ty[11],
4936 align: ty[12],
4937 has_align: ty[13],
4938 is_test: ty[14],
4939 is_extern: ty[15],
4940 };
4941 case typeKinds.Opaque:
4942 return {
4943 kind: ty[0],
4944 name: ty[1],
4945 src: ty[2],
4946 privDecls: ty[3],
4947 pubDecls: ty[4],
4948 parent_container: ty[5],
4949 };
4950 case typeKinds.Frame:
4951 case typeKinds.AnyFrame:
4952 case typeKinds.Vector:
4953 case typeKinds.EnumLiteral:
4954 return { kind: ty[0], name: ty[1] };
4955 }
4956 }
4957
4958 function getLocalStorage() {
4959 if ("localStorage" in window) {
4960 try {
4961 return window.localStorage;
4962 } catch (ignored) {
4963 // localStorage may be disabled (SecurityError)
4964 }
4965 }
4966 // If localStorage isn't available, persist preferences only for the current session
4967 const sessionPrefs = {};
4968 return {
4969 getItem(key) {
4970 return key in sessionPrefs ? sessionPrefs[key] : null;
4971 },
4972 setItem(key, value) {
4973 sessionPrefs[key] = String(value);
4974 },
4975 };
4976 }
4977
4978 function loadPrefs() {
4979 const storedPrefSlashSearch = prefs.getItem("slashSearch");
4980 if (storedPrefSlashSearch === null) {
4981 // Slash search defaults to enabled for all browsers except Firefox
4982 setPrefSlashSearch(navigator.userAgent.indexOf("Firefox") === -1);
4983 } else {
4984 setPrefSlashSearch(storedPrefSlashSearch === "true");
4985 }
4986 }
4987
4988 function getPrefSlashSearch() {
4989 return prefs.getItem("slashSearch") === "true";
4990 }
4991
4992 function setPrefSlashSearch(enabled) {
4993 prefs.setItem("slashSearch", String(enabled));
4994 domPrefSlashSearch.checked = enabled;
4995 const searchKeys = enabled ? "<kbd>/</kbd> or <kbd>s</kbd>" : "<kbd>s</kbd>";
4996 domSearchKeys.innerHTML = searchKeys;
4997 domSearchPlaceholderText.innerHTML = searchKeys + " to search, <kbd>?</kbd> for more options";
4998 }
4999850})();
5000851
5001function toggleExpand(event) {
5002 const parent = event.target.parentElement;
5003 parent.toggleAttribute("open");
5004
5005 if (!parent.open && parent.getBoundingClientRect().top < 0) {
5006 parent.parentElement.parentElement.scrollIntoView(true);
5007 }
5008}
5009
5010function RadixTree() {
5011 this.root = null;
5012
5013 RadixTree.prototype.search = function(query) {
5014 return this.root.search(query);
5015
5016 }
5017
5018 RadixTree.prototype.add = function(declName, value) {
5019 if (this.root == null) {
5020 this.root = new Node(declName.toLowerCase(), null, [value]);
5021 } else {
5022 this.root.add(declName.toLowerCase(), value);
5023 }
5024
5025 const not_scream_case = declName.toUpperCase() != declName;
5026 let found_separator = false;
5027 for (let i = 1; i < declName.length; i += 1) {
5028 if (declName[i] == '_' || declName[i] == '.') {
5029 found_separator = true;
5030 continue;
5031 }
5032
5033
5034 if (found_separator || (declName[i].toLowerCase() !== declName[i])) {
5035 if (declName.length > i + 1
5036 && declName[i + 1].toLowerCase() != declName[i + 1]) continue;
5037 let suffix = declName.slice(i);
5038 this.root.add(suffix.toLowerCase(), value);
5039 found_separator = false;
5040 }
5041 }
5042 }
5043
5044 function Node(labels, next, values) {
5045 this.labels = labels;
5046 this.next = next;
5047 this.values = values;
5048 }
5049
5050 Node.prototype.isCompressed = function() {
5051 return !Array.isArray(this.next);
5052 }
5053
5054 Node.prototype.search = function(word) {
5055 let full_matches = [];
5056 let partial_matches = [];
5057 let subtree_root = null;
5058
5059 let cn = this;
5060 char_loop: for (let i = 0; i < word.length;) {
5061 if (cn.isCompressed()) {
5062 for (let j = 0; j < cn.labels.length; j += 1) {
5063 let current_idx = i + j;
5064
5065 if (current_idx == word.length) {
5066 partial_matches = cn.values;
5067 subtree_root = cn.next;
5068 break char_loop;
5069 }
5070
5071 if (word[current_idx] != cn.labels[j]) return null;
5072 }
5073
5074 // the full label matched
5075 let new_idx = i + cn.labels.length;
5076 if (new_idx == word.length) {
5077 full_matches = cn.values;
5078 subtree_root = cn.next;
5079 break char_loop;
5080 }
5081
5082
5083 i = new_idx;
5084 cn = cn.next;
5085 continue;
5086 } else {
5087 for (let j = 0; j < cn.labels.length; j += 1) {
5088 if (word[i] == cn.labels[j]) {
5089 if (i == word.length - 1) {
5090 full_matches = cn.values[j];
5091 subtree_root = cn.next[j];
5092 break char_loop;
5093 }
5094
5095 let next = cn.next[j];
5096 if (next == null) return null;
5097 cn = next;
5098 i += 1;
5099 continue char_loop;
5100 }
5101 }
5102
5103 // didn't find a match
5104 return null;
5105 }
5106 }
5107
5108 // Match was found, let's collect all other
5109 // partial matches from the subtree
5110 let stack = [subtree_root];
5111 let node;
5112 while (node = stack.pop()) {
5113 if (node.isCompressed()) {
5114 partial_matches = partial_matches.concat(node.values);
5115 if (node.next != null) {
5116 stack.push(node.next);
5117 }
5118 } else {
5119 for (let v of node.values) {
5120 partial_matches = partial_matches.concat(v);
5121 }
5122
5123 for (let n of node.next) {
5124 if (n != null) stack.push(n);
5125 }
5126 }
5127 }
5128
5129 return { full: full_matches, partial: partial_matches };
5130 }
5131
5132 Node.prototype.add = function(word, value) {
5133 let cn = this;
5134 char_loop: for (let i = 0; i < word.length;) {
5135 if (cn.isCompressed()) {
5136 for (let j = 0; j < cn.labels.length; j += 1) {
5137 let current_idx = i + j;
5138
5139 if (current_idx == word.length) {
5140 if (j < cn.labels.length - 1) {
5141 let node = new Node(cn.labels.slice(j), cn.next, cn.values);
5142 cn.labels = cn.labels.slice(0, j);
5143 cn.next = node;
5144 cn.values = [];
5145 }
5146 cn.values.push(value);
5147 return;
5148 }
5149
5150 if (word[current_idx] == cn.labels[j]) continue;
5151
5152 // if we're here, a mismatch was found
5153 if (j != cn.labels.length - 1) {
5154 // create a suffix node
5155 const label_suffix = cn.labels.slice(j + 1);
5156 let node = new Node(label_suffix, cn.next, [...cn.values]);
5157 cn.next = node;
5158 cn.values = [];
5159 }
5160
5161 // turn current node into a split node
5162 let node = null;
5163 let word_values = [];
5164 if (current_idx == word.length - 1) {
5165 // mismatch happened in the last character of word
5166 // meaning that the current node should hold its value
5167 word_values.push(value);
5168 } else {
5169 node = new Node(word.slice(current_idx + 1), null, [value]);
5170 }
5171
5172 cn.labels = cn.labels[j] + word[current_idx];
5173 cn.next = [cn.next, node];
5174 cn.values = [cn.values, word_values];
5175
5176 if (j != 0) {
5177 // current node must be turned into a prefix node
5178 let splitNode = new Node(cn.labels, cn.next, cn.values);
5179 cn.labels = word.slice(i, current_idx);
5180 cn.next = splitNode;
5181 cn.values = [];
5182 }
5183
5184 return;
5185 }
5186 // label matched fully with word, are there any more chars?
5187 const new_idx = i + cn.labels.length;
5188 if (new_idx == word.length) {
5189 cn.values.push(value);
5190 return;
5191 } else {
5192 if (cn.next == null) {
5193 let node = new Node(word.slice(new_idx), null, [value]);
5194 cn.next = node;
5195 return;
5196 } else {
5197 cn = cn.next;
5198 i = new_idx;
5199 continue;
5200 }
5201 }
5202 } else { // node is not compressed
5203 let letter = word[i];
5204 for (let j = 0; j < cn.labels.length; j += 1) {
5205 if (letter == cn.labels[j]) {
5206 if (i == word.length - 1) {
5207 cn.values[j].push(value);
5208 return;
5209 }
5210 if (cn.next[j] == null) {
5211 let node = new Node(word.slice(i + 1), null, [value]);
5212 cn.next[j] = node;
5213 return;
5214 } else {
5215 cn = cn.next[j];
5216 i += 1;
5217 continue char_loop;
5218 }
5219 }
5220 }
5221
5222 // if we're here we didn't find a match
5223 cn.labels += letter;
5224 if (i == word.length - 1) {
5225 cn.next.push(null);
5226 cn.values.push([value]);
5227 } else {
5228 let node = new Node(word.slice(i + 1), null, [value]);
5229 cn.next.push(node);
5230 cn.values.push([]);
5231 }
5232 return;
5233 }
5234 }
5235 }
5236}
5237
5238
5239function slugify(str) {
5240 return str.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, '');
5241}
5242
lib/docs/wasm/Decl.zig created+180
......@@ -0,0 +1,180 @@
1ast_node: Ast.Node.Index,
2file: Walk.File.Index,
3/// The decl whose namespace this is in.
4parent: Index,
5
6pub const ExtraInfo = struct {
7 is_pub: bool,
8 name: []const u8,
9 /// This might not be a doc_comment token in which case there are no doc comments.
10 first_doc_comment: Ast.TokenIndex,
11};
12
13pub const Index = enum(u32) {
14 none = std.math.maxInt(u32),
15 _,
16
17 pub fn get(i: Index) *Decl {
18 return &Walk.decls.items[@intFromEnum(i)];
19 }
20};
21
22pub fn is_pub(d: *const Decl) bool {
23 return d.extra_info().is_pub;
24}
25
26pub fn extra_info(d: *const Decl) ExtraInfo {
27 const ast = d.file.get_ast();
28 const token_tags = ast.tokens.items(.tag);
29 const node_tags = ast.nodes.items(.tag);
30 switch (node_tags[d.ast_node]) {
31 .root => return .{
32 .name = "",
33 .is_pub = true,
34 .first_doc_comment = if (token_tags[0] == .container_doc_comment)
35 0
36 else
37 token_tags.len - 1,
38 },
39
40 .global_var_decl,
41 .local_var_decl,
42 .simple_var_decl,
43 .aligned_var_decl,
44 => {
45 const var_decl = ast.fullVarDecl(d.ast_node).?;
46 const name_token = var_decl.ast.mut_token + 1;
47 assert(token_tags[name_token] == .identifier);
48 const ident_name = ast.tokenSlice(name_token);
49 return .{
50 .name = ident_name,
51 .is_pub = var_decl.visib_token != null,
52 .first_doc_comment = findFirstDocComment(ast, var_decl.firstToken()),
53 };
54 },
55
56 .fn_proto,
57 .fn_proto_multi,
58 .fn_proto_one,
59 .fn_proto_simple,
60 .fn_decl,
61 => {
62 var buf: [1]Ast.Node.Index = undefined;
63 const fn_proto = ast.fullFnProto(&buf, d.ast_node).?;
64 const name_token = fn_proto.name_token.?;
65 assert(token_tags[name_token] == .identifier);
66 const ident_name = ast.tokenSlice(name_token);
67 return .{
68 .name = ident_name,
69 .is_pub = fn_proto.visib_token != null,
70 .first_doc_comment = findFirstDocComment(ast, fn_proto.firstToken()),
71 };
72 },
73
74 else => |t| {
75 log.debug("hit '{s}'", .{@tagName(t)});
76 unreachable;
77 },
78 }
79}
80
81pub fn value_node(d: *const Decl) ?Ast.Node.Index {
82 const ast = d.file.get_ast();
83 const node_tags = ast.nodes.items(.tag);
84 const token_tags = ast.tokens.items(.tag);
85 return switch (node_tags[d.ast_node]) {
86 .fn_proto,
87 .fn_proto_multi,
88 .fn_proto_one,
89 .fn_proto_simple,
90 .fn_decl,
91 .root,
92 => d.ast_node,
93
94 .global_var_decl,
95 .local_var_decl,
96 .simple_var_decl,
97 .aligned_var_decl,
98 => {
99 const var_decl = ast.fullVarDecl(d.ast_node).?;
100 if (token_tags[var_decl.ast.mut_token] == .keyword_const)
101 return var_decl.ast.init_node;
102
103 return null;
104 },
105
106 else => null,
107 };
108}
109
110pub fn categorize(decl: *const Decl) Walk.Category {
111 return decl.file.categorize_decl(decl.ast_node);
112}
113
114pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void {
115 try decl.reset_with_path(out);
116 if (decl.parent != .none) {
117 try append_parent_ns(out, decl.parent);
118 try out.appendSlice(gpa, decl.extra_info().name);
119 } else {
120 out.items.len -= 1; // remove the trailing '.'
121 }
122}
123
124pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void {
125 list.clearRetainingCapacity();
126
127 // Prefer the package name alias.
128 for (Walk.packages.keys(), Walk.packages.values()) |pkg_name, pkg_file| {
129 if (pkg_file == decl.file) {
130 try list.ensureUnusedCapacity(gpa, pkg_name.len + 1);
131 list.appendSliceAssumeCapacity(pkg_name);
132 list.appendAssumeCapacity('.');
133 return;
134 }
135 }
136
137 const file_path = decl.file.path();
138 try list.ensureUnusedCapacity(gpa, file_path.len + 1);
139 list.appendSliceAssumeCapacity(file_path);
140 for (list.items) |*byte| switch (byte.*) {
141 '/' => byte.* = '.',
142 else => continue,
143 };
144 if (std.mem.endsWith(u8, list.items, ".zig")) {
145 list.items.len -= 3;
146 } else {
147 list.appendAssumeCapacity('.');
148 }
149}
150
151pub fn append_parent_ns(list: *std.ArrayListUnmanaged(u8), parent: Decl.Index) Oom!void {
152 assert(parent != .none);
153 const decl = parent.get();
154 if (decl.parent != .none) {
155 try append_parent_ns(list, decl.parent);
156 try list.appendSlice(gpa, decl.extra_info().name);
157 try list.append(gpa, '.');
158 }
159}
160
161pub fn findFirstDocComment(ast: *const Ast, token: Ast.TokenIndex) Ast.TokenIndex {
162 const token_tags = ast.tokens.items(.tag);
163 var it = token;
164 while (it > 0) {
165 it -= 1;
166 if (token_tags[it] != .doc_comment) {
167 return it + 1;
168 }
169 }
170 return it;
171}
172
173const Decl = @This();
174const std = @import("std");
175const Ast = std.zig.Ast;
176const Walk = @import("Walk.zig");
177const gpa = std.heap.wasm_allocator;
178const assert = std.debug.assert;
179const log = std.log;
180const Oom = error{OutOfMemory};
lib/docs/wasm/Walk.zig created+928
......@@ -0,0 +1,928 @@
1//! Find and annotate identifiers with links to their declarations.
2pub var files: std.StringArrayHashMapUnmanaged(File) = .{};
3pub var decls: std.ArrayListUnmanaged(Decl) = .{};
4pub var packages: std.StringArrayHashMapUnmanaged(File.Index) = .{};
5
6arena: std.mem.Allocator,
7file: File.Index,
8
9/// keep in sync with "CAT_" constants in main.js
10pub const Category = union(enum(u8)) {
11 namespace: Ast.Node.Index,
12 global_variable: Ast.Node.Index,
13 function: Ast.Node.Index,
14 primitive: Ast.Node.Index,
15 error_set: Ast.Node.Index,
16 global_const: Ast.Node.Index,
17 alias: Decl.Index,
18 type,
19
20 pub const Tag = @typeInfo(Category).Union.tag_type.?;
21};
22
23pub const File = struct {
24 ast: Ast,
25 /// Maps identifiers to the declarations they point to.
26 ident_decls: std.AutoArrayHashMapUnmanaged(Ast.TokenIndex, Ast.Node.Index) = .{},
27 /// Maps field access identifiers to the containing field access node.
28 token_parents: std.AutoArrayHashMapUnmanaged(Ast.TokenIndex, Ast.Node.Index) = .{},
29 /// Maps declarations to their global index.
30 node_decls: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Decl.Index) = .{},
31 /// Maps function declarations to doctests.
32 doctests: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .{},
33
34 pub fn lookup_token(file: *File, token: Ast.TokenIndex) Decl.Index {
35 const decl_node = file.ident_decls.get(token) orelse return .none;
36 return file.node_decls.get(decl_node) orelse return .none;
37 }
38
39 pub const Index = enum(u32) {
40 _,
41
42 fn add_decl(i: Index, node: Ast.Node.Index, parent_decl: Decl.Index) Oom!Decl.Index {
43 try decls.append(gpa, .{
44 .ast_node = node,
45 .file = i,
46 .parent = parent_decl,
47 });
48 const decl_index: Decl.Index = @enumFromInt(decls.items.len - 1);
49 try i.get().node_decls.put(gpa, node, decl_index);
50 return decl_index;
51 }
52
53 pub fn get(i: File.Index) *File {
54 return &files.values()[@intFromEnum(i)];
55 }
56
57 pub fn get_ast(i: File.Index) *Ast {
58 return &i.get().ast;
59 }
60
61 pub fn path(i: File.Index) []const u8 {
62 return files.keys()[@intFromEnum(i)];
63 }
64
65 pub fn findRootDecl(file_index: File.Index) Decl.Index {
66 return file_index.get().node_decls.values()[0];
67 }
68
69 pub fn categorize_decl(file_index: File.Index, node: Ast.Node.Index) Category {
70 const ast = file_index.get_ast();
71 const node_tags = ast.nodes.items(.tag);
72 const token_tags = ast.tokens.items(.tag);
73 switch (node_tags[node]) {
74 .root => return .{ .namespace = node },
75
76 .global_var_decl,
77 .local_var_decl,
78 .simple_var_decl,
79 .aligned_var_decl,
80 => {
81 const var_decl = ast.fullVarDecl(node).?;
82 if (token_tags[var_decl.ast.mut_token] == .keyword_var)
83 return .{ .global_variable = node };
84
85 return categorize_expr(file_index, var_decl.ast.init_node);
86 },
87
88 .fn_proto,
89 .fn_proto_multi,
90 .fn_proto_one,
91 .fn_proto_simple,
92 .fn_decl,
93 => return .{ .function = node },
94
95 else => unreachable,
96 }
97 }
98
99 pub fn categorize_expr(file_index: File.Index, node: Ast.Node.Index) Category {
100 const ast = file_index.get_ast();
101 const node_tags = ast.nodes.items(.tag);
102 const node_datas = ast.nodes.items(.data);
103 return switch (node_tags[node]) {
104 .container_decl,
105 .container_decl_trailing,
106 .container_decl_arg,
107 .container_decl_arg_trailing,
108 .container_decl_two,
109 .container_decl_two_trailing,
110 .tagged_union,
111 .tagged_union_trailing,
112 .tagged_union_enum_tag,
113 .tagged_union_enum_tag_trailing,
114 .tagged_union_two,
115 .tagged_union_two_trailing,
116 => .{ .namespace = node },
117
118 .error_set_decl,
119 => .{ .error_set = node },
120
121 .identifier => {
122 const name_token = ast.nodes.items(.main_token)[node];
123 const ident_name = ast.tokenSlice(name_token);
124 if (std.zig.primitives.isPrimitive(ident_name)) {
125 return .{ .primitive = node };
126 }
127
128 const decl_index = file_index.get().lookup_token(name_token);
129 if (decl_index != .none) return .{ .alias = decl_index };
130
131 return .{ .global_const = node };
132 },
133
134 .field_access => {
135 // TODO:
136 //return .alias;
137 return .{ .global_const = node };
138 },
139
140 .builtin_call_two, .builtin_call_two_comma => {
141 if (node_datas[node].lhs == 0) {
142 const params = [_]Ast.Node.Index{};
143 return categorize_builtin_call(file_index, node, &params);
144 } else if (node_datas[node].rhs == 0) {
145 const params = [_]Ast.Node.Index{node_datas[node].lhs};
146 return categorize_builtin_call(file_index, node, &params);
147 } else {
148 const params = [_]Ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs };
149 return categorize_builtin_call(file_index, node, &params);
150 }
151 },
152 .builtin_call, .builtin_call_comma => {
153 const params = ast.extra_data[node_datas[node].lhs..node_datas[node].rhs];
154 return categorize_builtin_call(file_index, node, params);
155 },
156
157 else => .{ .global_const = node },
158 };
159 }
160
161 fn categorize_builtin_call(
162 file_index: File.Index,
163 node: Ast.Node.Index,
164 params: []const Ast.Node.Index,
165 ) Category {
166 const ast = file_index.get_ast();
167 const main_tokens = ast.nodes.items(.main_token);
168 const builtin_token = main_tokens[node];
169 const builtin_name = ast.tokenSlice(builtin_token);
170 if (std.mem.eql(u8, builtin_name, "@import")) {
171 const str_lit_token = main_tokens[params[0]];
172 const str_bytes = ast.tokenSlice(str_lit_token);
173 const file_path = std.zig.string_literal.parseAlloc(gpa, str_bytes) catch @panic("OOM");
174 defer gpa.free(file_path);
175 const base_path = file_index.path();
176 const resolved_path = std.fs.path.resolvePosix(gpa, &.{
177 base_path, "..", file_path,
178 }) catch @panic("OOM");
179 defer gpa.free(resolved_path);
180 log.debug("from '{s}' @import '{s}' resolved='{s}'", .{
181 base_path, file_path, resolved_path,
182 });
183 if (Walk.files.getIndex(resolved_path)) |imported_file_index| {
184 return .{ .alias = Walk.File.Index.findRootDecl(@enumFromInt(imported_file_index)) };
185 } else {
186 log.warn("import target '{s}' did not resolve to any file", .{resolved_path});
187 }
188 } else if (std.mem.eql(u8, builtin_name, "@This")) {
189 if (file_index.get().node_decls.get(node)) |decl_index| {
190 return .{ .alias = decl_index };
191 } else {
192 log.warn("@This() is missing link to Decl.Index", .{});
193 }
194 }
195
196 return .{ .global_const = node };
197 }
198 };
199};
200
201pub const PackageIndex = enum(u32) {
202 _,
203};
204
205pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {
206 const ast = try parse(bytes);
207 const file_index: File.Index = @enumFromInt(files.entries.len);
208 try files.put(gpa, file_name, .{ .ast = ast });
209
210 if (ast.errors.len > 0) {
211 // TODO: expose this in the UI
212 log.err("can't index '{s}' because it has syntax errors", .{file_index.path()});
213 return file_index;
214 }
215
216 var arena_instance = std.heap.ArenaAllocator.init(gpa);
217 defer arena_instance.deinit();
218
219 var w: Walk = .{
220 .arena = arena_instance.allocator(),
221 .file = file_index,
222 };
223 var scope: Scope = .{ .tag = .top };
224
225 const decl_index = try file_index.add_decl(0, .none);
226 try struct_decl(&w, &scope, decl_index, ast.containerDeclRoot());
227
228 const file = file_index.get();
229 shrinkToFit(&file.ident_decls);
230 shrinkToFit(&file.token_parents);
231 shrinkToFit(&file.node_decls);
232 shrinkToFit(&file.doctests);
233
234 return file_index;
235}
236
237fn parse(source: []u8) Oom!Ast {
238 // Require every source file to end with a newline so that Zig's tokenizer
239 // can continue to require null termination and Autodoc implementation can
240 // avoid copying source bytes from the decompressed tar file buffer.
241 const adjusted_source: [:0]const u8 = s: {
242 if (source.len == 0)
243 break :s "";
244
245 assert(source[source.len - 1] == '\n');
246 source[source.len - 1] = 0;
247 break :s source[0 .. source.len - 1 :0];
248 };
249
250 return Ast.parse(gpa, adjusted_source, .zig);
251}
252
253const Scope = struct {
254 tag: Tag,
255
256 const Tag = enum { top, local, namespace };
257
258 const Local = struct {
259 base: Scope = .{ .tag = .local },
260 parent: *Scope,
261 var_node: Ast.Node.Index,
262 };
263
264 const Namespace = struct {
265 base: Scope = .{ .tag = .namespace },
266 parent: *Scope,
267 names: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .{},
268 doctests: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .{},
269 decl_index: Decl.Index,
270 };
271
272 fn getNamespaceDecl(start_scope: *Scope) Decl.Index {
273 var it: *Scope = start_scope;
274 while (true) switch (it.tag) {
275 .top => unreachable,
276 .local => {
277 const local = @fieldParentPtr(Local, "base", it);
278 it = local.parent;
279 },
280 .namespace => {
281 const namespace = @fieldParentPtr(Namespace, "base", it);
282 return namespace.decl_index;
283 },
284 };
285 }
286
287 fn lookup(start_scope: *Scope, ast: *const Ast, name: []const u8) ?Ast.Node.Index {
288 const main_tokens = ast.nodes.items(.main_token);
289 var it: *Scope = start_scope;
290 while (true) switch (it.tag) {
291 .top => break,
292 .local => {
293 const local = @fieldParentPtr(Local, "base", it);
294 const name_token = main_tokens[local.var_node] + 1;
295 const ident_name = ast.tokenSlice(name_token);
296 if (std.mem.eql(u8, ident_name, name)) {
297 return local.var_node;
298 }
299 it = local.parent;
300 },
301 .namespace => {
302 const namespace = @fieldParentPtr(Namespace, "base", it);
303 if (namespace.names.get(name)) |node| {
304 return node;
305 }
306 it = namespace.parent;
307 },
308 };
309 return null;
310 }
311};
312
313fn struct_decl(
314 w: *Walk,
315 scope: *Scope,
316 parent_decl: Decl.Index,
317 container_decl: Ast.full.ContainerDecl,
318) Oom!void {
319 const ast = w.file.get_ast();
320 const node_tags = ast.nodes.items(.tag);
321 const node_datas = ast.nodes.items(.data);
322
323 var namespace: Scope.Namespace = .{
324 .parent = scope,
325 .decl_index = parent_decl,
326 };
327 try w.scanDecls(&namespace, container_decl.ast.members);
328
329 for (container_decl.ast.members) |member| switch (node_tags[member]) {
330 .container_field_init,
331 .container_field_align,
332 .container_field,
333 => try w.container_field(&namespace.base, parent_decl, ast.fullContainerField(member).?),
334
335 .fn_proto,
336 .fn_proto_multi,
337 .fn_proto_one,
338 .fn_proto_simple,
339 .fn_decl,
340 => {
341 var buf: [1]Ast.Node.Index = undefined;
342 const full = ast.fullFnProto(&buf, member).?;
343 const fn_name_token = full.ast.fn_token + 1;
344 const fn_name = ast.tokenSlice(fn_name_token);
345 if (namespace.doctests.get(fn_name)) |doctest_node| {
346 try w.file.get().doctests.put(gpa, member, doctest_node);
347 }
348 const decl_index = try w.file.add_decl(member, parent_decl);
349 const body = if (node_tags[member] == .fn_decl) node_datas[member].rhs else 0;
350 try w.fn_decl(&namespace.base, decl_index, body, full);
351 },
352
353 .global_var_decl,
354 .local_var_decl,
355 .simple_var_decl,
356 .aligned_var_decl,
357 => {
358 const decl_index = try w.file.add_decl(member, parent_decl);
359 try w.global_var_decl(&namespace.base, decl_index, ast.fullVarDecl(member).?);
360 },
361
362 .@"comptime",
363 .@"usingnamespace",
364 => try w.expr(&namespace.base, parent_decl, node_datas[member].lhs),
365
366 .test_decl => try w.expr(&namespace.base, parent_decl, node_datas[member].rhs),
367
368 else => unreachable,
369 };
370}
371
372fn comptime_decl(
373 w: *Walk,
374 scope: *Scope,
375 parent_decl: Decl.Index,
376 full: Ast.full.VarDecl,
377) Oom!void {
378 try w.expr(scope, parent_decl, full.ast.type_node);
379 try w.maybe_expr(scope, parent_decl, full.ast.align_node);
380 try w.maybe_expr(scope, parent_decl, full.ast.addrspace_node);
381 try w.maybe_expr(scope, parent_decl, full.ast.section_node);
382 try w.expr(scope, parent_decl, full.ast.init_node);
383}
384
385fn global_var_decl(
386 w: *Walk,
387 scope: *Scope,
388 parent_decl: Decl.Index,
389 full: Ast.full.VarDecl,
390) Oom!void {
391 try w.maybe_expr(scope, parent_decl, full.ast.type_node);
392 try w.maybe_expr(scope, parent_decl, full.ast.align_node);
393 try w.maybe_expr(scope, parent_decl, full.ast.addrspace_node);
394 try w.maybe_expr(scope, parent_decl, full.ast.section_node);
395 try w.maybe_expr(scope, parent_decl, full.ast.init_node);
396}
397
398fn container_field(
399 w: *Walk,
400 scope: *Scope,
401 parent_decl: Decl.Index,
402 full: Ast.full.ContainerField,
403) Oom!void {
404 try w.maybe_expr(scope, parent_decl, full.ast.type_expr);
405 try w.maybe_expr(scope, parent_decl, full.ast.align_expr);
406 try w.maybe_expr(scope, parent_decl, full.ast.value_expr);
407}
408
409fn fn_decl(
410 w: *Walk,
411 scope: *Scope,
412 parent_decl: Decl.Index,
413 body: Ast.Node.Index,
414 full: Ast.full.FnProto,
415) Oom!void {
416 for (full.ast.params) |param| {
417 try expr(w, scope, parent_decl, param);
418 }
419 try expr(w, scope, parent_decl, full.ast.return_type);
420 try maybe_expr(w, scope, parent_decl, full.ast.align_expr);
421 try maybe_expr(w, scope, parent_decl, full.ast.addrspace_expr);
422 try maybe_expr(w, scope, parent_decl, full.ast.section_expr);
423 try maybe_expr(w, scope, parent_decl, full.ast.callconv_expr);
424 try maybe_expr(w, scope, parent_decl, body);
425}
426
427fn maybe_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index) Oom!void {
428 if (node != 0) return expr(w, scope, parent_decl, node);
429}
430
431fn expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index) Oom!void {
432 assert(node != 0);
433 const ast = w.file.get_ast();
434 const node_tags = ast.nodes.items(.tag);
435 const node_datas = ast.nodes.items(.data);
436 const main_tokens = ast.nodes.items(.main_token);
437 switch (node_tags[node]) {
438 .root => unreachable, // Top-level declaration.
439 .@"usingnamespace" => unreachable, // Top-level declaration.
440 .test_decl => unreachable, // Top-level declaration.
441 .container_field_init => unreachable, // Top-level declaration.
442 .container_field_align => unreachable, // Top-level declaration.
443 .container_field => unreachable, // Top-level declaration.
444 .fn_decl => unreachable, // Top-level declaration.
445
446 .global_var_decl => unreachable, // Handled in `block`.
447 .local_var_decl => unreachable, // Handled in `block`.
448 .simple_var_decl => unreachable, // Handled in `block`.
449 .aligned_var_decl => unreachable, // Handled in `block`.
450 .@"defer" => unreachable, // Handled in `block`.
451 .@"errdefer" => unreachable, // Handled in `block`.
452
453 .switch_case => unreachable, // Handled in `switchExpr`.
454 .switch_case_inline => unreachable, // Handled in `switchExpr`.
455 .switch_case_one => unreachable, // Handled in `switchExpr`.
456 .switch_case_inline_one => unreachable, // Handled in `switchExpr`.
457
458 .asm_output => unreachable, // Handled in `asmExpr`.
459 .asm_input => unreachable, // Handled in `asmExpr`.
460
461 .for_range => unreachable, // Handled in `forExpr`.
462
463 .assign,
464 .assign_shl,
465 .assign_shl_sat,
466 .assign_shr,
467 .assign_bit_and,
468 .assign_bit_or,
469 .assign_bit_xor,
470 .assign_div,
471 .assign_sub,
472 .assign_sub_wrap,
473 .assign_sub_sat,
474 .assign_mod,
475 .assign_add,
476 .assign_add_wrap,
477 .assign_add_sat,
478 .assign_mul,
479 .assign_mul_wrap,
480 .assign_mul_sat,
481 .shl,
482 .shr,
483 .add,
484 .add_wrap,
485 .add_sat,
486 .sub,
487 .sub_wrap,
488 .sub_sat,
489 .mul,
490 .mul_wrap,
491 .mul_sat,
492 .div,
493 .mod,
494 .shl_sat,
495
496 .bit_and,
497 .bit_or,
498 .bit_xor,
499 .bang_equal,
500 .equal_equal,
501 .greater_than,
502 .greater_or_equal,
503 .less_than,
504 .less_or_equal,
505 .array_cat,
506
507 .array_mult,
508 .error_union,
509 .merge_error_sets,
510 .bool_and,
511 .bool_or,
512 .@"catch",
513 .@"orelse",
514 .array_type,
515 .array_access,
516 .switch_range,
517 => {
518 try expr(w, scope, parent_decl, node_datas[node].lhs);
519 try expr(w, scope, parent_decl, node_datas[node].rhs);
520 },
521
522 .assign_destructure => {
523 const extra_index = node_datas[node].lhs;
524 const lhs_count = ast.extra_data[extra_index];
525 const lhs_nodes: []const Ast.Node.Index = @ptrCast(ast.extra_data[extra_index + 1 ..][0..lhs_count]);
526 const rhs = node_datas[node].rhs;
527 for (lhs_nodes) |lhs_node| try expr(w, scope, parent_decl, lhs_node);
528 _ = try expr(w, scope, parent_decl, rhs);
529 },
530
531 .bool_not,
532 .bit_not,
533 .negation,
534 .negation_wrap,
535 .@"return",
536 .deref,
537 .address_of,
538 .optional_type,
539 .unwrap_optional,
540 .grouped_expression,
541 .@"comptime",
542 .@"nosuspend",
543 .@"suspend",
544 .@"await",
545 .@"resume",
546 .@"try",
547 => try maybe_expr(w, scope, parent_decl, node_datas[node].lhs),
548
549 .anyframe_type,
550 .@"break",
551 => try maybe_expr(w, scope, parent_decl, node_datas[node].rhs),
552
553 .identifier => {
554 const ident_token = main_tokens[node];
555 const ident_name = ast.tokenSlice(ident_token);
556 if (scope.lookup(ast, ident_name)) |var_node| {
557 try w.file.get().ident_decls.put(gpa, ident_token, var_node);
558 }
559 },
560 .field_access => {
561 const object_node = node_datas[node].lhs;
562 const dot_token = main_tokens[node];
563 const field_ident = dot_token + 1;
564 try w.file.get().token_parents.put(gpa, field_ident, node);
565 // This will populate the left-most field object if it is an
566 // identifier, allowing rendering code to piece together the link.
567 try expr(w, scope, parent_decl, object_node);
568 },
569
570 .string_literal,
571 .multiline_string_literal,
572 .number_literal,
573 .unreachable_literal,
574 .enum_literal,
575 .error_value,
576 .anyframe_literal,
577 .@"continue",
578 .char_literal,
579 .error_set_decl,
580 => {},
581
582 .asm_simple,
583 .@"asm",
584 => {
585 const full = ast.fullAsm(node).?;
586 for (full.ast.items) |n| {
587 // TODO handle .asm_input, .asm_output
588 _ = n;
589 }
590 try expr(w, scope, parent_decl, full.ast.template);
591 },
592
593 .builtin_call_two, .builtin_call_two_comma => {
594 if (node_datas[node].lhs == 0) {
595 const params = [_]Ast.Node.Index{};
596 return builtin_call(w, scope, parent_decl, node, &params);
597 } else if (node_datas[node].rhs == 0) {
598 const params = [_]Ast.Node.Index{node_datas[node].lhs};
599 return builtin_call(w, scope, parent_decl, node, &params);
600 } else {
601 const params = [_]Ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs };
602 return builtin_call(w, scope, parent_decl, node, &params);
603 }
604 },
605 .builtin_call, .builtin_call_comma => {
606 const params = ast.extra_data[node_datas[node].lhs..node_datas[node].rhs];
607 return builtin_call(w, scope, parent_decl, node, params);
608 },
609
610 .call_one,
611 .call_one_comma,
612 .async_call_one,
613 .async_call_one_comma,
614 .call,
615 .call_comma,
616 .async_call,
617 .async_call_comma,
618 => {
619 var buf: [1]Ast.Node.Index = undefined;
620 const full = ast.fullCall(&buf, node).?;
621 try expr(w, scope, parent_decl, full.ast.fn_expr);
622 for (full.ast.params) |param| {
623 try expr(w, scope, parent_decl, param);
624 }
625 },
626
627 .if_simple,
628 .@"if",
629 => {
630 const full = ast.fullIf(node).?;
631 try expr(w, scope, parent_decl, full.ast.cond_expr);
632 try expr(w, scope, parent_decl, full.ast.then_expr);
633 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
634 },
635
636 .while_simple,
637 .while_cont,
638 .@"while",
639 => {
640 try while_expr(w, scope, parent_decl, ast.fullWhile(node).?);
641 },
642
643 .for_simple, .@"for" => {
644 const full = ast.fullFor(node).?;
645 for (full.ast.inputs) |input| {
646 if (node_tags[input] == .for_range) {
647 try expr(w, scope, parent_decl, node_datas[input].lhs);
648 try maybe_expr(w, scope, parent_decl, node_datas[input].rhs);
649 } else {
650 try expr(w, scope, parent_decl, input);
651 }
652 }
653 try expr(w, scope, parent_decl, full.ast.then_expr);
654 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
655 },
656
657 .slice => return slice(w, scope, parent_decl, ast.slice(node)),
658 .slice_open => return slice(w, scope, parent_decl, ast.sliceOpen(node)),
659 .slice_sentinel => return slice(w, scope, parent_decl, ast.sliceSentinel(node)),
660
661 .block_two, .block_two_semicolon => {
662 const statements = [2]Ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs };
663 if (node_datas[node].lhs == 0) {
664 return block(w, scope, parent_decl, statements[0..0]);
665 } else if (node_datas[node].rhs == 0) {
666 return block(w, scope, parent_decl, statements[0..1]);
667 } else {
668 return block(w, scope, parent_decl, statements[0..2]);
669 }
670 },
671 .block, .block_semicolon => {
672 const statements = ast.extra_data[node_datas[node].lhs..node_datas[node].rhs];
673 return block(w, scope, parent_decl, statements);
674 },
675
676 .ptr_type_aligned,
677 .ptr_type_sentinel,
678 .ptr_type,
679 .ptr_type_bit_range,
680 => {
681 const full = ast.fullPtrType(node).?;
682 try maybe_expr(w, scope, parent_decl, full.ast.align_node);
683 try maybe_expr(w, scope, parent_decl, full.ast.addrspace_node);
684 try maybe_expr(w, scope, parent_decl, full.ast.sentinel);
685 try maybe_expr(w, scope, parent_decl, full.ast.bit_range_start);
686 try maybe_expr(w, scope, parent_decl, full.ast.bit_range_end);
687 try expr(w, scope, parent_decl, full.ast.child_type);
688 },
689
690 .container_decl,
691 .container_decl_trailing,
692 .container_decl_arg,
693 .container_decl_arg_trailing,
694 .container_decl_two,
695 .container_decl_two_trailing,
696 .tagged_union,
697 .tagged_union_trailing,
698 .tagged_union_enum_tag,
699 .tagged_union_enum_tag_trailing,
700 .tagged_union_two,
701 .tagged_union_two_trailing,
702 => {
703 var buf: [2]Ast.Node.Index = undefined;
704 return struct_decl(w, scope, parent_decl, ast.fullContainerDecl(&buf, node).?);
705 },
706
707 .array_type_sentinel => {
708 const extra = ast.extraData(node_datas[node].rhs, Ast.Node.ArrayTypeSentinel);
709 try expr(w, scope, parent_decl, node_datas[node].lhs);
710 try expr(w, scope, parent_decl, extra.elem_type);
711 try expr(w, scope, parent_decl, extra.sentinel);
712 },
713 .@"switch", .switch_comma => {
714 const operand_node = node_datas[node].lhs;
715 try expr(w, scope, parent_decl, operand_node);
716 const extra = ast.extraData(node_datas[node].rhs, Ast.Node.SubRange);
717 const case_nodes = ast.extra_data[extra.start..extra.end];
718 for (case_nodes) |case_node| {
719 const case = ast.fullSwitchCase(case_node).?;
720 for (case.ast.values) |value_node| {
721 try expr(w, scope, parent_decl, value_node);
722 }
723 try expr(w, scope, parent_decl, case.ast.target_expr);
724 }
725 },
726
727 .array_init_one,
728 .array_init_one_comma,
729 .array_init_dot_two,
730 .array_init_dot_two_comma,
731 .array_init_dot,
732 .array_init_dot_comma,
733 .array_init,
734 .array_init_comma,
735 => {
736 var buf: [2]Ast.Node.Index = undefined;
737 const full = ast.fullArrayInit(&buf, node).?;
738 try maybe_expr(w, scope, parent_decl, full.ast.type_expr);
739 for (full.ast.elements) |elem| {
740 try expr(w, scope, parent_decl, elem);
741 }
742 },
743
744 .struct_init_one,
745 .struct_init_one_comma,
746 .struct_init_dot_two,
747 .struct_init_dot_two_comma,
748 .struct_init_dot,
749 .struct_init_dot_comma,
750 .struct_init,
751 .struct_init_comma,
752 => {
753 var buf: [2]Ast.Node.Index = undefined;
754 const full = ast.fullStructInit(&buf, node).?;
755 try maybe_expr(w, scope, parent_decl, full.ast.type_expr);
756 for (full.ast.fields) |field| {
757 try expr(w, scope, parent_decl, field);
758 }
759 },
760
761 .fn_proto_simple,
762 .fn_proto_multi,
763 .fn_proto_one,
764 .fn_proto,
765 => {
766 var buf: [1]Ast.Node.Index = undefined;
767 return fn_decl(w, scope, parent_decl, 0, ast.fullFnProto(&buf, node).?);
768 },
769 }
770}
771
772fn slice(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.Slice) Oom!void {
773 try expr(w, scope, parent_decl, full.ast.sliced);
774 try expr(w, scope, parent_decl, full.ast.start);
775 try maybe_expr(w, scope, parent_decl, full.ast.end);
776 try maybe_expr(w, scope, parent_decl, full.ast.sentinel);
777}
778
779fn builtin_call(
780 w: *Walk,
781 scope: *Scope,
782 parent_decl: Decl.Index,
783 node: Ast.Node.Index,
784 params: []const Ast.Node.Index,
785) Oom!void {
786 const ast = w.file.get_ast();
787 const main_tokens = ast.nodes.items(.main_token);
788 const builtin_token = main_tokens[node];
789 const builtin_name = ast.tokenSlice(builtin_token);
790 if (std.mem.eql(u8, builtin_name, "@This")) {
791 try w.file.get().node_decls.put(gpa, node, scope.getNamespaceDecl());
792 }
793
794 for (params) |param| {
795 try expr(w, scope, parent_decl, param);
796 }
797}
798
799fn block(
800 w: *Walk,
801 parent_scope: *Scope,
802 parent_decl: Decl.Index,
803 statements: []const Ast.Node.Index,
804) Oom!void {
805 const ast = w.file.get_ast();
806 const arena = w.arena;
807 const node_tags = ast.nodes.items(.tag);
808 const node_datas = ast.nodes.items(.data);
809
810 var scope = parent_scope;
811
812 for (statements) |node| {
813 switch (node_tags[node]) {
814 .global_var_decl,
815 .local_var_decl,
816 .simple_var_decl,
817 .aligned_var_decl,
818 => {
819 const full = ast.fullVarDecl(node).?;
820 try global_var_decl(w, scope, parent_decl, full);
821 const local = try arena.create(Scope.Local);
822 local.* = .{
823 .parent = scope,
824 .var_node = node,
825 };
826 scope = &local.base;
827 },
828
829 .assign_destructure => {
830 // TODO
831 },
832
833 .grouped_expression => try expr(w, scope, parent_decl, node_datas[node].lhs),
834
835 .@"defer",
836 .@"errdefer",
837 => try expr(w, scope, parent_decl, node_datas[node].rhs),
838
839 else => try expr(w, scope, parent_decl, node),
840 }
841 }
842}
843
844fn while_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.While) Oom!void {
845 try expr(w, scope, parent_decl, full.ast.cond_expr);
846 try maybe_expr(w, scope, parent_decl, full.ast.cont_expr);
847 try expr(w, scope, parent_decl, full.ast.then_expr);
848 try maybe_expr(w, scope, parent_decl, full.ast.else_expr);
849}
850
851fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.Index) Oom!void {
852 const arena = w.arena;
853 const ast = w.file.get_ast();
854 const node_tags = ast.nodes.items(.tag);
855 const main_tokens = ast.nodes.items(.main_token);
856 const token_tags = ast.tokens.items(.tag);
857 const node_datas = ast.nodes.items(.data);
858
859 for (members) |member_node| {
860 const name_token = switch (node_tags[member_node]) {
861 .global_var_decl,
862 .local_var_decl,
863 .simple_var_decl,
864 .aligned_var_decl,
865 => main_tokens[member_node] + 1,
866
867 .fn_proto_simple,
868 .fn_proto_multi,
869 .fn_proto_one,
870 .fn_proto,
871 .fn_decl,
872 => blk: {
873 const ident = main_tokens[member_node] + 1;
874 if (token_tags[ident] != .identifier) continue;
875 break :blk ident;
876 },
877
878 .test_decl => {
879 const ident_token = node_datas[member_node].lhs;
880 const is_doctest = token_tags[ident_token] == .identifier;
881 if (is_doctest) {
882 const token_bytes = ast.tokenSlice(ident_token);
883 try namespace.doctests.put(arena, token_bytes, member_node);
884 }
885 continue;
886 },
887
888 else => continue,
889 };
890
891 const token_bytes = ast.tokenSlice(name_token);
892 try namespace.names.put(arena, token_bytes, member_node);
893 }
894}
895
896//test {
897// const gpa = std.testing.allocator;
898//
899// var arena_instance = std.heap.ArenaAllocator.init(gpa);
900// defer arena_instance.deinit();
901// const arena = arena_instance.allocator();
902//
903// // example test command:
904// // zig test --dep input.zig -Mroot=src/Walk.zig -Minput.zig=/home/andy/dev/zig/lib/std/fs/File/zig
905// var ast = try Ast.parse(gpa, @embedFile("input.zig"), .zig);
906// defer ast.deinit(gpa);
907//
908// var w: Walk = .{
909// .arena = arena,
910// .token_links = .{},
911// .ast = &ast,
912// };
913//
914// try w.root();
915//}
916
917const Walk = @This();
918const std = @import("std");
919const Ast = std.zig.Ast;
920const assert = std.debug.assert;
921const Decl = @import("Decl.zig");
922const log = std.log;
923const gpa = std.heap.wasm_allocator;
924const Oom = error{OutOfMemory};
925
926fn shrinkToFit(m: anytype) void {
927 m.shrinkAndFree(gpa, m.entries.len);
928}
lib/docs/wasm/main.zig created+920
......@@ -0,0 +1,920 @@
1const gpa = std.heap.wasm_allocator;
2
3const std = @import("std");
4const log = std.log;
5const assert = std.debug.assert;
6const Ast = std.zig.Ast;
7const Walk = @import("Walk.zig");
8const markdown = @import("markdown.zig");
9const Decl = @import("Decl.zig");
10
11const js = struct {
12 extern "js" fn log(ptr: [*]const u8, len: usize) void;
13 extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;
14};
15
16pub const std_options: std.Options = .{
17 .logFn = logFn,
18 //.log_level = .debug,
19};
20
21pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace, addr: ?usize) noreturn {
22 _ = st;
23 _ = addr;
24 log.err("panic: {s}", .{msg});
25 @trap();
26}
27
28fn logFn(
29 comptime message_level: log.Level,
30 comptime scope: @TypeOf(.enum_literal),
31 comptime format: []const u8,
32 args: anytype,
33) void {
34 const level_txt = comptime message_level.asText();
35 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
36 var buf: [500]u8 = undefined;
37 const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: {
38 buf[buf.len - 3 ..][0..3].* = "...".*;
39 break :l &buf;
40 };
41 js.log(line.ptr, line.len);
42}
43
44export fn alloc(n: usize) [*]u8 {
45 const slice = gpa.alloc(u8, n) catch @panic("OOM");
46 return slice.ptr;
47}
48
49export fn unpack(tar_ptr: [*]u8, tar_len: usize) void {
50 const tar_bytes = tar_ptr[0..tar_len];
51 //log.debug("received {d} bytes of tar file", .{tar_bytes.len});
52
53 unpack_inner(tar_bytes) catch |err| {
54 fatal("unable to unpack tar: {s}", .{@errorName(err)});
55 };
56}
57
58var query_string: std.ArrayListUnmanaged(u8) = .{};
59var query_results: std.ArrayListUnmanaged(Decl.Index) = .{};
60
61/// Resizes the query string to be the correct length; returns the pointer to
62/// the query string.
63export fn query_begin(query_string_len: usize) [*]u8 {
64 query_string.resize(gpa, query_string_len) catch @panic("OOM");
65 return query_string.items.ptr;
66}
67
68/// Executes the query. Returns the pointer to the query results which is an
69/// array of u32.
70/// The first element is the length of the array.
71/// Subsequent elements are Decl.Index values which are all public
72/// declarations.
73export fn query_exec(ignore_case: bool) [*]Decl.Index {
74 const query = query_string.items;
75 log.debug("querying '{s}'", .{query});
76 query_exec_fallible(query, ignore_case) catch |err| switch (err) {
77 error.OutOfMemory => @panic("OOM"),
78 };
79 query_results.items[0] = @enumFromInt(query_results.items.len - 1);
80 return query_results.items.ptr;
81}
82
83const max_matched_items = 2000;
84
85fn query_exec_fallible(query: []const u8, ignore_case: bool) !void {
86 const Score = packed struct(u32) {
87 points: u16,
88 segments: u16,
89 };
90 const g = struct {
91 var full_path_search_text: std.ArrayListUnmanaged(u8) = .{};
92 var full_path_search_text_lower: std.ArrayListUnmanaged(u8) = .{};
93 var doc_search_text: std.ArrayListUnmanaged(u8) = .{};
94 /// Each element matches a corresponding query_results element.
95 var scores: std.ArrayListUnmanaged(Score) = .{};
96 };
97
98 // First element stores the size of the list.
99 try query_results.resize(gpa, 1);
100 // Corresponding point value is meaningless and therefore undefined.
101 try g.scores.resize(gpa, 1);
102
103 decl_loop: for (Walk.decls.items, 0..) |*decl, decl_index| {
104 const info = decl.extra_info();
105 if (!info.is_pub) continue;
106
107 try decl.reset_with_path(&g.full_path_search_text);
108 if (decl.parent != .none)
109 try Decl.append_parent_ns(&g.full_path_search_text, decl.parent);
110 try g.full_path_search_text.appendSlice(gpa, info.name);
111
112 try g.full_path_search_text_lower.resize(gpa, g.full_path_search_text.items.len);
113 @memcpy(g.full_path_search_text_lower.items, g.full_path_search_text.items);
114
115 const ast = decl.file.get_ast();
116 try collect_docs(&g.doc_search_text, ast, info.first_doc_comment);
117
118 if (ignore_case) {
119 ascii_lower(g.full_path_search_text_lower.items);
120 ascii_lower(g.doc_search_text.items);
121 }
122
123 var it = std.mem.tokenizeScalar(u8, query, ' ');
124 var points: u16 = 0;
125 var bypass_limit = false;
126 while (it.next()) |term| {
127 // exact, case sensitive match of full decl path
128 if (std.mem.eql(u8, g.full_path_search_text.items, term)) {
129 points += 4;
130 bypass_limit = true;
131 continue;
132 }
133 // exact, case sensitive match of just decl name
134 if (std.mem.eql(u8, info.name, term)) {
135 points += 3;
136 bypass_limit = true;
137 continue;
138 }
139 // substring, case insensitive match of full decl path
140 if (std.mem.indexOf(u8, g.full_path_search_text_lower.items, term) != null) {
141 points += 2;
142 continue;
143 }
144 if (std.mem.indexOf(u8, g.doc_search_text.items, term) != null) {
145 points += 1;
146 continue;
147 }
148 continue :decl_loop;
149 }
150
151 if (query_results.items.len < max_matched_items or bypass_limit) {
152 try query_results.append(gpa, @enumFromInt(decl_index));
153 try g.scores.append(gpa, .{
154 .points = points,
155 .segments = @intCast(count_scalar(g.full_path_search_text.items, '.')),
156 });
157 }
158 }
159
160 const sort_context: struct {
161 pub fn swap(sc: @This(), a_index: usize, b_index: usize) void {
162 _ = sc;
163 std.mem.swap(Score, &g.scores.items[a_index], &g.scores.items[b_index]);
164 std.mem.swap(Decl.Index, &query_results.items[a_index], &query_results.items[b_index]);
165 }
166
167 pub fn lessThan(sc: @This(), a_index: usize, b_index: usize) bool {
168 _ = sc;
169 const a_score = g.scores.items[a_index];
170 const b_score = g.scores.items[b_index];
171 if (b_score.points < a_score.points) {
172 return true;
173 } else if (b_score.points > a_score.points) {
174 return false;
175 } else if (a_score.segments < b_score.segments) {
176 return true;
177 } else if (a_score.segments > b_score.segments) {
178 return false;
179 } else {
180 const a_decl = query_results.items[a_index];
181 const b_decl = query_results.items[b_index];
182 const a_file_path = a_decl.get().file.path();
183 const b_file_path = b_decl.get().file.path();
184 // TODO Also check the local namespace inside the file
185 return std.mem.lessThan(u8, b_file_path, a_file_path);
186 }
187 }
188 } = .{};
189
190 std.mem.sortUnstableContext(1, query_results.items.len, sort_context);
191
192 if (query_results.items.len > max_matched_items)
193 query_results.shrinkRetainingCapacity(max_matched_items);
194}
195
196const String = Slice(u8);
197
198fn Slice(T: type) type {
199 return packed struct(u64) {
200 ptr: u32,
201 len: u32,
202
203 fn init(s: []const T) @This() {
204 return .{
205 .ptr = @intFromPtr(s.ptr),
206 .len = s.len,
207 };
208 }
209 };
210}
211
212var string_result: std.ArrayListUnmanaged(u8) = .{};
213
214export fn decl_fields(decl_index: Decl.Index) Slice(Ast.Node.Index) {
215 return Slice(Ast.Node.Index).init(decl_fields_fallible(decl_index) catch @panic("OOM"));
216}
217
218fn decl_fields_fallible(decl_index: Decl.Index) ![]Ast.Node.Index {
219 const g = struct {
220 var result: std.ArrayListUnmanaged(Ast.Node.Index) = .{};
221 };
222 g.result.clearRetainingCapacity();
223 const decl = decl_index.get();
224 const ast = decl.file.get_ast();
225 const node_tags = ast.nodes.items(.tag);
226 const value_node = decl.value_node() orelse return &.{};
227 var buf: [2]Ast.Node.Index = undefined;
228 const container_decl = ast.fullContainerDecl(&buf, value_node) orelse return &.{};
229 for (container_decl.ast.members) |member_node| switch (node_tags[member_node]) {
230 .container_field_init,
231 .container_field_align,
232 .container_field,
233 => try g.result.append(gpa, member_node),
234
235 else => continue,
236 };
237 return g.result.items;
238}
239
240export fn decl_field_html(decl_index: Decl.Index, field_node: Ast.Node.Index) String {
241 string_result.clearRetainingCapacity();
242 decl_field_html_fallible(&string_result, decl_index, field_node) catch @panic("OOM");
243 return String.init(string_result.items);
244}
245
246fn decl_field_html_fallible(
247 out: *std.ArrayListUnmanaged(u8),
248 decl_index: Decl.Index,
249 field_node: Ast.Node.Index,
250) !void {
251 const decl = decl_index.get();
252 const ast = decl.file.get_ast();
253 try out.appendSlice(gpa, "<pre><code>");
254 try file_source_html(decl.file, out, field_node);
255 try out.appendSlice(gpa, "</code></pre>");
256
257 const field = ast.fullContainerField(field_node).?;
258 const first_doc_comment = Decl.findFirstDocComment(ast, field.firstToken());
259
260 if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) {
261 try out.appendSlice(gpa, "<div class=\"fieldDocs\">");
262 try render_docs(out, ast, first_doc_comment, false);
263 try out.appendSlice(gpa, "</div>");
264 }
265}
266
267export fn decl_fn_proto_html(decl_index: Decl.Index) String {
268 const decl = decl_index.get();
269 const ast = decl.file.get_ast();
270 const node_tags = ast.nodes.items(.tag);
271 const node_datas = ast.nodes.items(.data);
272 const proto_node = switch (node_tags[decl.ast_node]) {
273 .fn_decl => node_datas[decl.ast_node].lhs,
274
275 .fn_proto,
276 .fn_proto_one,
277 .fn_proto_simple,
278 .fn_proto_multi,
279 => decl.ast_node,
280
281 else => unreachable,
282 };
283
284 string_result.clearRetainingCapacity();
285 file_source_html(decl.file, &string_result, proto_node) catch |err| {
286 fatal("unable to render source: {s}", .{@errorName(err)});
287 };
288 return String.init(string_result.items);
289}
290
291export fn decl_source_html(decl_index: Decl.Index) String {
292 const decl = decl_index.get();
293
294 string_result.clearRetainingCapacity();
295 file_source_html(decl.file, &string_result, decl.ast_node) catch |err| {
296 fatal("unable to render source: {s}", .{@errorName(err)});
297 };
298 return String.init(string_result.items);
299}
300
301export fn decl_doctest_html(decl_index: Decl.Index) String {
302 const decl = decl_index.get();
303 const doctest_ast_node = decl.file.get().doctests.get(decl.ast_node) orelse
304 return String.init("");
305
306 string_result.clearRetainingCapacity();
307 file_source_html(decl.file, &string_result, doctest_ast_node) catch |err| {
308 fatal("unable to render source: {s}", .{@errorName(err)});
309 };
310 return String.init(string_result.items);
311}
312
313export fn decl_fqn(decl_index: Decl.Index) String {
314 const decl = decl_index.get();
315 decl.fqn(&string_result) catch @panic("OOM");
316 return String.init(string_result.items);
317}
318
319export fn decl_parent(decl_index: Decl.Index) Decl.Index {
320 const decl = decl_index.get();
321 return decl.parent;
322}
323
324export fn decl_file_path(decl_index: Decl.Index) String {
325 string_result.clearRetainingCapacity();
326 string_result.appendSlice(gpa, decl_index.get().file.path()) catch @panic("OOM");
327 return String.init(string_result.items);
328}
329
330export fn decl_category_name(decl_index: Decl.Index) String {
331 const decl = decl_index.get();
332 const ast = decl.file.get_ast();
333 const token_tags = ast.tokens.items(.tag);
334 const name = switch (decl.categorize()) {
335 .namespace => |node| {
336 const node_tags = ast.nodes.items(.tag);
337 if (node_tags[decl.ast_node] == .root)
338 return String.init("struct");
339 string_result.clearRetainingCapacity();
340 var buf: [2]Ast.Node.Index = undefined;
341 const container_decl = ast.fullContainerDecl(&buf, node).?;
342 if (container_decl.layout_token) |t| {
343 if (token_tags[t] == .keyword_extern) {
344 string_result.appendSlice(gpa, "extern ") catch @panic("OOM");
345 }
346 }
347 const main_token_tag = token_tags[container_decl.ast.main_token];
348 string_result.appendSlice(gpa, main_token_tag.lexeme().?) catch @panic("OOM");
349 return String.init(string_result.items);
350 },
351 .global_variable => "Global Variable",
352 .function => "Function",
353 .type => "Type",
354 .error_set => "Error Set",
355 .global_const => "Constant",
356 .primitive => "Primitive Value",
357 .alias => "Alias",
358 };
359 return String.init(name);
360}
361
362export fn decl_name(decl_index: Decl.Index) String {
363 const decl = decl_index.get();
364 string_result.clearRetainingCapacity();
365 const name = n: {
366 if (decl.parent == .none) {
367 // Then it is the root struct of a file.
368 break :n std.fs.path.stem(decl.file.path());
369 }
370 break :n decl.extra_info().name;
371 };
372 string_result.appendSlice(gpa, name) catch @panic("OOM");
373 return String.init(string_result.items);
374}
375
376export fn decl_docs_html(decl_index: Decl.Index, short: bool) String {
377 const decl = decl_index.get();
378 const ast = decl.file.get_ast();
379 string_result.clearRetainingCapacity();
380 render_docs(&string_result, ast, decl.extra_info().first_doc_comment, short) catch @panic("OOM");
381 return String.init(string_result.items);
382}
383
384fn collect_docs(
385 list: *std.ArrayListUnmanaged(u8),
386 ast: *const Ast,
387 first_doc_comment: Ast.TokenIndex,
388) Oom!void {
389 const token_tags = ast.tokens.items(.tag);
390 list.clearRetainingCapacity();
391 var it = first_doc_comment;
392 while (true) : (it += 1) switch (token_tags[it]) {
393 .doc_comment, .container_doc_comment => {
394 // It is tempting to trim this string but think carefully about how
395 // that will affect the markdown parser.
396 const line = ast.tokenSlice(it)[3..];
397 try list.appendSlice(gpa, line);
398 },
399 else => break,
400 };
401}
402
403fn render_docs(
404 out: *std.ArrayListUnmanaged(u8),
405 ast: *const Ast,
406 first_doc_comment: Ast.TokenIndex,
407 short: bool,
408) Oom!void {
409 const token_tags = ast.tokens.items(.tag);
410
411 var parser = try markdown.Parser.init(gpa);
412 defer parser.deinit();
413 var it = first_doc_comment;
414 while (true) : (it += 1) switch (token_tags[it]) {
415 .doc_comment, .container_doc_comment => {
416 const line = ast.tokenSlice(it)[3..];
417 if (short and line.len == 0) break;
418 try parser.feedLine(line);
419 },
420 else => break,
421 };
422
423 var parsed_doc = try parser.endInput();
424 defer parsed_doc.deinit(gpa);
425
426 const Writer = std.ArrayListUnmanaged(u8).Writer;
427 const Renderer = markdown.Renderer(Writer, void);
428 const renderer: Renderer = .{
429 .context = {},
430 .renderFn = struct {
431 fn render(
432 r: Renderer,
433 doc: markdown.Document,
434 node: markdown.Document.Node.Index,
435 writer: Writer,
436 ) !void {
437 const data = doc.nodes.items(.data)[@intFromEnum(node)];
438 switch (doc.nodes.items(.tag)[@intFromEnum(node)]) {
439 // TODO: detect identifier references (dotted paths) in
440 // these three node types and render them appropriately.
441 // Also, syntax highlighting can be applied in code blocks
442 // unless the tag says otherwise.
443 .code_block => {
444 const tag = doc.string(data.code_block.tag);
445 _ = tag;
446 const content = doc.string(data.code_block.content);
447 try writer.print("<pre><code>{}</code></pre>\n", .{markdown.fmtHtml(content)});
448 },
449 .code_span => {
450 const content = doc.string(data.text.content);
451 try writer.print("<code>{}</code>", .{markdown.fmtHtml(content)});
452 },
453 .text => {
454 const content = doc.string(data.text.content);
455 try writer.print("{}", .{markdown.fmtHtml(content)});
456 },
457
458 else => try Renderer.renderDefault(r, doc, node, writer),
459 }
460 }
461 }.render,
462 };
463 try renderer.render(parsed_doc, out.writer(gpa));
464}
465
466export fn decl_type_html(decl_index: Decl.Index) String {
467 const decl = decl_index.get();
468 const ast = decl.file.get_ast();
469 string_result.clearRetainingCapacity();
470 _ = ast; // TODO
471 string_result.appendSlice(gpa, "TODO_type_here") catch @panic("OOM");
472 return String.init(string_result.items);
473}
474
475const Oom = error{OutOfMemory};
476
477fn unpack_inner(tar_bytes: []u8) !void {
478 var fbs = std.io.fixedBufferStream(tar_bytes);
479 var file_name_buffer: [1024]u8 = undefined;
480 var link_name_buffer: [1024]u8 = undefined;
481 var it = std.tar.iterator(fbs.reader(), .{
482 .file_name_buffer = &file_name_buffer,
483 .link_name_buffer = &link_name_buffer,
484 });
485 while (try it.next()) |tar_file| {
486 switch (tar_file.kind) {
487 .normal => {
488 if (tar_file.size == 0 and tar_file.name.len == 0) break;
489 if (std.mem.endsWith(u8, tar_file.name, ".zig")) {
490 log.debug("found file: '{s}'", .{tar_file.name});
491 const file_name = try gpa.dupe(u8, tar_file.name);
492 if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| {
493 const pkg_name = file_name[0..pkg_name_end];
494 const gop = try Walk.packages.getOrPut(gpa, pkg_name);
495 const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len);
496 if (!gop.found_existing or
497 std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or
498 std.mem.eql(u8, file_name[pkg_name_end + 1 .. file_name.len - ".zig".len], pkg_name))
499 {
500 gop.value_ptr.* = file;
501 }
502 const file_bytes = tar_bytes[fbs.pos..][0..@intCast(tar_file.size)];
503 assert(file == try Walk.add_file(file_name, file_bytes));
504 }
505 } else {
506 log.warn("skipping: '{s}' - the tar creation should have done that", .{
507 tar_file.name,
508 });
509 }
510 try tar_file.skip();
511 },
512 else => continue,
513 }
514 }
515}
516
517fn fatal(comptime format: []const u8, args: anytype) noreturn {
518 var buf: [500]u8 = undefined;
519 const line = std.fmt.bufPrint(&buf, format, args) catch l: {
520 buf[buf.len - 3 ..][0..3].* = "...".*;
521 break :l &buf;
522 };
523 js.panic(line.ptr, line.len);
524}
525
526fn ascii_lower(bytes: []u8) void {
527 for (bytes) |*b| b.* = std.ascii.toLower(b.*);
528}
529
530export fn package_name(index: u32) String {
531 const names = Walk.packages.keys();
532 return String.init(if (index >= names.len) "" else names[index]);
533}
534
535export fn find_package_root(pkg: Walk.PackageIndex) Decl.Index {
536 const root_file = Walk.packages.values()[@intFromEnum(pkg)];
537 const result = root_file.findRootDecl();
538 assert(result != .none);
539 return result;
540}
541
542/// Set by `set_input_string`.
543var input_string: std.ArrayListUnmanaged(u8) = .{};
544
545export fn set_input_string(len: usize) [*]u8 {
546 input_string.resize(gpa, len) catch @panic("OOM");
547 return input_string.items.ptr;
548}
549
550/// Looks up the root struct decl corresponding to a file by path.
551/// Uses `input_string`.
552export fn find_file_root() Decl.Index {
553 const file: Walk.File.Index = @enumFromInt(Walk.files.getIndex(input_string.items) orelse return .none);
554 return file.findRootDecl();
555}
556
557/// Uses `input_string`.
558export fn find_decl() Decl.Index {
559 const g = struct {
560 var match_fqn: std.ArrayListUnmanaged(u8) = .{};
561 };
562 for (Walk.decls.items, 0..) |*decl, decl_index| {
563 decl.fqn(&g.match_fqn) catch @panic("OOM");
564 if (std.mem.eql(u8, g.match_fqn.items, input_string.items)) {
565 //const path = @as(Decl.Index, @enumFromInt(decl_index)).get().file.path();
566 //log.debug("find_decl '{s}' found in {s}", .{ input_string.items, path });
567 return @enumFromInt(decl_index);
568 }
569 }
570 return .none;
571}
572
573/// Set only by `categorize_decl`; read only by `get_aliasee`, valid only
574/// when `categorize_decl` returns `.alias`.
575var global_aliasee: Decl.Index = .none;
576
577export fn get_aliasee() Decl.Index {
578 return global_aliasee;
579}
580export fn categorize_decl(decl_index: Decl.Index, resolve_alias_count: usize) Walk.Category.Tag {
581 global_aliasee = .none;
582 var chase_alias_n = resolve_alias_count;
583 var decl = decl_index.get();
584 while (true) {
585 const result = decl.categorize();
586 switch (decl.categorize()) {
587 .alias => |new_index| {
588 assert(new_index != .none);
589 global_aliasee = new_index;
590 if (chase_alias_n > 0) {
591 chase_alias_n -= 1;
592 decl = new_index.get();
593 continue;
594 }
595 },
596 else => {},
597 }
598 return result;
599 }
600}
601
602export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Decl.Index) {
603 const g = struct {
604 var members: std.ArrayListUnmanaged(Decl.Index) = .{};
605 };
606
607 g.members.clearRetainingCapacity();
608
609 for (Walk.decls.items, 0..) |*decl, i| {
610 if (decl.parent == parent) {
611 if (include_private or decl.is_pub()) {
612 g.members.append(gpa, @enumFromInt(i)) catch @panic("OOM");
613 }
614 }
615 }
616
617 return Slice(Decl.Index).init(g.members.items);
618}
619
620fn file_source_html(
621 file_index: Walk.File.Index,
622 out: *std.ArrayListUnmanaged(u8),
623 root_node: Ast.Node.Index,
624) !void {
625 const ast = file_index.get_ast();
626 const file = file_index.get();
627
628 const g = struct {
629 var field_access_buffer: std.ArrayListUnmanaged(u8) = .{};
630 };
631
632 const token_tags = ast.tokens.items(.tag);
633 const token_starts = ast.tokens.items(.start);
634
635 const start_token = ast.firstToken(root_node);
636 const end_token = ast.lastToken(root_node) + 1;
637
638 var cursor: usize = token_starts[start_token];
639
640 for (
641 token_tags[start_token..end_token],
642 token_starts[start_token..end_token],
643 start_token..,
644 ) |tag, start, token_index| {
645 const between = ast.source[cursor..start];
646 try appendEscaped(out, between);
647 if (tag == .eof) break;
648 const slice = ast.tokenSlice(token_index);
649 cursor = start + slice.len;
650 switch (tag) {
651 .eof => unreachable,
652
653 .keyword_addrspace,
654 .keyword_align,
655 .keyword_and,
656 .keyword_asm,
657 .keyword_async,
658 .keyword_await,
659 .keyword_break,
660 .keyword_catch,
661 .keyword_comptime,
662 .keyword_const,
663 .keyword_continue,
664 .keyword_defer,
665 .keyword_else,
666 .keyword_enum,
667 .keyword_errdefer,
668 .keyword_error,
669 .keyword_export,
670 .keyword_extern,
671 .keyword_for,
672 .keyword_if,
673 .keyword_inline,
674 .keyword_noalias,
675 .keyword_noinline,
676 .keyword_nosuspend,
677 .keyword_opaque,
678 .keyword_or,
679 .keyword_orelse,
680 .keyword_packed,
681 .keyword_anyframe,
682 .keyword_pub,
683 .keyword_resume,
684 .keyword_return,
685 .keyword_linksection,
686 .keyword_callconv,
687 .keyword_struct,
688 .keyword_suspend,
689 .keyword_switch,
690 .keyword_test,
691 .keyword_threadlocal,
692 .keyword_try,
693 .keyword_union,
694 .keyword_unreachable,
695 .keyword_usingnamespace,
696 .keyword_var,
697 .keyword_volatile,
698 .keyword_allowzero,
699 .keyword_while,
700 .keyword_anytype,
701 .keyword_fn,
702 => {
703 try out.appendSlice(gpa, "<span class=\"tok-kw\">");
704 try appendEscaped(out, slice);
705 try out.appendSlice(gpa, "</span>");
706 },
707
708 .string_literal,
709 .char_literal,
710 .multiline_string_literal_line,
711 => {
712 try out.appendSlice(gpa, "<span class=\"tok-str\">");
713 try appendEscaped(out, slice);
714 try out.appendSlice(gpa, "</span>");
715 },
716
717 .builtin => {
718 try out.appendSlice(gpa, "<span class=\"tok-builtin\">");
719 try appendEscaped(out, slice);
720 try out.appendSlice(gpa, "</span>");
721 },
722
723 .doc_comment,
724 .container_doc_comment,
725 => {
726 try out.appendSlice(gpa, "<span class=\"tok-comment\">");
727 try appendEscaped(out, slice);
728 try out.appendSlice(gpa, "</span>");
729 },
730
731 .identifier => i: {
732 if (std.mem.eql(u8, slice, "undefined") or
733 std.mem.eql(u8, slice, "null") or
734 std.mem.eql(u8, slice, "true") or
735 std.mem.eql(u8, slice, "false"))
736 {
737 try out.appendSlice(gpa, "<span class=\"tok-null\">");
738 try appendEscaped(out, slice);
739 try out.appendSlice(gpa, "</span>");
740 break :i;
741 }
742
743 if (std.zig.primitives.isPrimitive(slice)) {
744 try out.appendSlice(gpa, "<span class=\"tok-type\">");
745 try appendEscaped(out, slice);
746 try out.appendSlice(gpa, "</span>");
747 break :i;
748 }
749
750 if (file.token_parents.get(token_index)) |field_access_node| {
751 g.field_access_buffer.clearRetainingCapacity();
752 try walk_field_accesses(file_index, &g.field_access_buffer, field_access_node);
753 if (g.field_access_buffer.items.len > 0) {
754 try out.appendSlice(gpa, "<a href=\"#");
755 try out.appendSlice(gpa, g.field_access_buffer.items); // TODO url escape
756 try out.appendSlice(gpa, "\">");
757 try appendEscaped(out, slice);
758 try out.appendSlice(gpa, "</a>");
759 } else {
760 try appendEscaped(out, slice);
761 }
762 break :i;
763 }
764
765 {
766 g.field_access_buffer.clearRetainingCapacity();
767 try resolve_ident_link(file_index, &g.field_access_buffer, token_index);
768 if (g.field_access_buffer.items.len > 0) {
769 try out.appendSlice(gpa, "<a href=\"#");
770 try out.appendSlice(gpa, g.field_access_buffer.items); // TODO url escape
771 try out.appendSlice(gpa, "\">");
772 try appendEscaped(out, slice);
773 try out.appendSlice(gpa, "</a>");
774 break :i;
775 }
776 }
777
778 try appendEscaped(out, slice);
779 },
780
781 .number_literal => {
782 try out.appendSlice(gpa, "<span class=\"tok-number\">");
783 try appendEscaped(out, slice);
784 try out.appendSlice(gpa, "</span>");
785 },
786
787 .bang,
788 .pipe,
789 .pipe_pipe,
790 .pipe_equal,
791 .equal,
792 .equal_equal,
793 .equal_angle_bracket_right,
794 .bang_equal,
795 .l_paren,
796 .r_paren,
797 .semicolon,
798 .percent,
799 .percent_equal,
800 .l_brace,
801 .r_brace,
802 .l_bracket,
803 .r_bracket,
804 .period,
805 .period_asterisk,
806 .ellipsis2,
807 .ellipsis3,
808 .caret,
809 .caret_equal,
810 .plus,
811 .plus_plus,
812 .plus_equal,
813 .plus_percent,
814 .plus_percent_equal,
815 .plus_pipe,
816 .plus_pipe_equal,
817 .minus,
818 .minus_equal,
819 .minus_percent,
820 .minus_percent_equal,
821 .minus_pipe,
822 .minus_pipe_equal,
823 .asterisk,
824 .asterisk_equal,
825 .asterisk_asterisk,
826 .asterisk_percent,
827 .asterisk_percent_equal,
828 .asterisk_pipe,
829 .asterisk_pipe_equal,
830 .arrow,
831 .colon,
832 .slash,
833 .slash_equal,
834 .comma,
835 .ampersand,
836 .ampersand_equal,
837 .question_mark,
838 .angle_bracket_left,
839 .angle_bracket_left_equal,
840 .angle_bracket_angle_bracket_left,
841 .angle_bracket_angle_bracket_left_equal,
842 .angle_bracket_angle_bracket_left_pipe,
843 .angle_bracket_angle_bracket_left_pipe_equal,
844 .angle_bracket_right,
845 .angle_bracket_right_equal,
846 .angle_bracket_angle_bracket_right,
847 .angle_bracket_angle_bracket_right_equal,
848 .tilde,
849 => try appendEscaped(out, slice),
850
851 .invalid, .invalid_periodasterisks => return error.InvalidToken,
852 }
853 }
854}
855
856fn resolve_ident_link(
857 file_index: Walk.File.Index,
858 out: *std.ArrayListUnmanaged(u8),
859 ident_token: Ast.TokenIndex,
860) Oom!void {
861 const decl_index = file_index.get().lookup_token(ident_token);
862 if (decl_index == .none) return;
863
864 const decl = decl_index.get();
865 switch (decl.categorize()) {
866 .alias => |alias_decl| try alias_decl.get().fqn(out),
867 else => try decl.fqn(out),
868 }
869}
870
871fn walk_field_accesses(
872 file_index: Walk.File.Index,
873 out: *std.ArrayListUnmanaged(u8),
874 node: Ast.Node.Index,
875) Oom!void {
876 const ast = file_index.get_ast();
877 const node_tags = ast.nodes.items(.tag);
878 assert(node_tags[node] == .field_access);
879 const node_datas = ast.nodes.items(.data);
880 const main_tokens = ast.nodes.items(.main_token);
881 const object_node = node_datas[node].lhs;
882 const dot_token = main_tokens[node];
883 const field_ident = dot_token + 1;
884 switch (node_tags[object_node]) {
885 .identifier => {
886 const lhs_ident = main_tokens[object_node];
887 try resolve_ident_link(file_index, out, lhs_ident);
888 },
889 .field_access => {
890 try walk_field_accesses(file_index, out, object_node);
891 },
892 else => {},
893 }
894 if (out.items.len > 0) {
895 try out.append(gpa, '.');
896 try out.appendSlice(gpa, ast.tokenSlice(field_ident));
897 }
898}
899
900fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void {
901 for (s) |c| {
902 try out.ensureUnusedCapacity(gpa, 6);
903 switch (c) {
904 '&' => out.appendSliceAssumeCapacity("&amp;"),
905 '<' => out.appendSliceAssumeCapacity("&lt;"),
906 '>' => out.appendSliceAssumeCapacity("&gt;"),
907 '"' => out.appendSliceAssumeCapacity("&quot;"),
908 else => out.appendAssumeCapacity(c),
909 }
910 }
911}
912
913fn count_scalar(haystack: []const u8, needle: u8) usize {
914 var total: usize = 0;
915 for (haystack) |elem| {
916 if (elem == needle)
917 total += 1;
918 }
919 return total;
920}
lib/docs/wasm/markdown.zig created+933
......@@ -0,0 +1,933 @@
1//! Markdown parsing and rendering support.
2//!
3//! A Markdown document consists of a series of blocks. Depending on its type,
4//! each block may contain other blocks, inline content, or nothing. The
5//! supported blocks are as follows:
6//!
7//! - **List** - a sequence of list items of the same type.
8//!
9//! - **List item** - unordered list items start with `-`, `*`, or `+` followed
10//! by a space. Ordered list items start with a number between 0 and
11//! 999,999,999, followed by a `.` or `)` and a space. The number of an
12//! ordered list item only matters for the first item in the list (to
13//! determine the starting number of the list). All subsequent ordered list
14//! items will have sequentially increasing numbers.
15//!
16//! All list items may contain block content. Any content indented at least as
17//! far as the end of the list item marker (including the space after it) is
18//! considered part of the list item.
19//!
20//! Lists which have no blank lines between items or between direct children
21//! of items are considered _tight_, and direct child paragraphs of tight list
22//! items are rendered without `<p>` tags.
23//!
24//! - **Table** - a sequence of adjacent table row lines, where each line starts
25//! and ends with a `|`, and cells within the row are delimited by `|`s.
26//!
27//! The first or second row of a table may be a _header delimiter row_, which
28//! is a row consisting of cells of the pattern `---` (for unset column
29//! alignment), `:--` (for left alignment), `:-:` (for center alignment), or
30//! `--:` (for right alignment). The number of `-`s must be at least one, but
31//! is otherwise arbitrary. If there is a row just before the header delimiter
32//! row, it becomes the header row for the table (a table need not have a
33//! header row at all).
34//!
35//! - **Heading** - a sequence of between 1 and 6 `#` characters, followed by a
36//! space and further inline content on the same line.
37//!
38//! - **Code block** - a sequence of at least 3 `` ` `` characters (a _fence_),
39//! optionally followed by a "tag" on the same line, and continuing until a
40//! line consisting only of a closing fence whose length matches the opening
41//! fence, or until the end of the containing block.
42//!
43//! The content of a code block is not parsed as inline content. It is
44//! included verbatim in the output document (minus leading indentation up to
45//! the position of the opening fence).
46//!
47//! - **Blockquote** - a sequence of lines preceded by `>` characters.
48//!
49//! - **Paragraph** - ordinary text, parsed as inline content, ending with a
50//! blank line or the end of the containing block.
51//!
52//! Paragraphs which are part of another block may be "lazily" continued by
53//! subsequent paragraph lines even if those lines would not ordinarily be
54//! considered part of the containing block. For example, this is a single
55//! list item, not a list item followed by a paragraph:
56//!
57//! ```markdown
58//! - First line of content.
59//! This content is still part of the paragraph,
60//! even though it isn't indented far enough.
61//! ```
62//!
63//! - **Thematic break** - a line consisting of at least three matching `-`,
64//! `_`, or `*` characters and, optionally, spaces.
65//!
66//! Indentation may consist of spaces and tabs. The use of tabs is not
67//! recommended: a tab is treated the same as a single space for the purpose of
68//! determining the indentation level, and is not recognized as a space for
69//! block starters which require one (for example, `-` followed by a tab is not
70//! a valid list item).
71//!
72//! The supported inlines are as follows:
73//!
74//! - **Link** - of the format `[text](target)`. `text` may contain inline
75//! content. `target` may contain `\`-escaped characters and balanced
76//! parentheses.
77//!
78//! - **Image** - a link directly preceded by a `!`. The link text is
79//! interpreted as the alt text of the image.
80//!
81//! - **Emphasis** - a run of `*` or `_` characters may be an emphasis opener,
82//! closer, or both. For `*` characters, the run may be an opener as long as
83//! it is not directly followed by a whitespace character (or the end of the
84//! inline content) and a closer as long as it is not directly preceded by
85//! one. For `_` characters, this rule is strengthened by requiring that the
86//! run also be preceded by a whitespace or punctuation character (for
87//! openers) or followed by one (for closers), to avoid mangling `snake_case`
88//! words.
89//!
90//! The rule for emphasis handling is greedy: any run that can close existing
91//! emphasis will do so, otherwise it will open emphasis. A single run may
92//! serve both functions: the middle `**` in the following example both closes
93//! the initial emphasis and opens a new one:
94//!
95//! ```markdown
96//! *one**two*
97//! ```
98//!
99//! A single `*` or `_` is used for normal emphasis (HTML `<em>`), and a
100//! double `**` or `__` is used for strong emphasis (HTML `<strong>`). Even
101//! longer runs may be used to produce further nested emphasis (though only
102//! `***` and `___` to produce `<em><strong>` is really useful).
103//!
104//! - **Code span** - a run of `` ` `` characters, terminated by a matching run
105//! or the end of inline content. The content of a code span is not parsed
106//! further.
107//!
108//! - **Text** - normal text is interpreted as-is, except that `\` may be used
109//! to escape any punctuation character, preventing it from being interpreted
110//! according to other syntax rules. A `\` followed by a line break within a
111//! paragraph is interpreted as a hard line break.
112//!
113//! Any null bytes or invalid UTF-8 bytes within text are replaced with Unicode
114//! replacement characters, `U+FFFD`.
115
116const std = @import("std");
117const testing = std.testing;
118
119pub const Document = @import("markdown/Document.zig");
120pub const Parser = @import("markdown/Parser.zig");
121pub const Renderer = @import("markdown/renderer.zig").Renderer;
122pub const renderNodeInlineText = @import("markdown/renderer.zig").renderNodeInlineText;
123pub const fmtHtml = @import("markdown/renderer.zig").fmtHtml;
124
125// Avoid exposing main to other files merely importing this one.
126pub const main = if (@import("root") == @This())
127 mainImpl
128else
129 @compileError("only available as root source file");
130
131fn mainImpl() !void {
132 const gpa = std.heap.c_allocator;
133
134 var parser = try Parser.init(gpa);
135 defer parser.deinit();
136
137 var stdin_buf = std.io.bufferedReader(std.io.getStdIn().reader());
138 var line_buf = std.ArrayList(u8).init(gpa);
139 defer line_buf.deinit();
140 while (stdin_buf.reader().streamUntilDelimiter(line_buf.writer(), '\n', null)) {
141 if (line_buf.getLastOrNull() == '\r') _ = line_buf.pop();
142 try parser.feedLine(line_buf.items);
143 line_buf.clearRetainingCapacity();
144 } else |err| switch (err) {
145 error.EndOfStream => {},
146 else => |e| return e,
147 }
148
149 var doc = try parser.endInput();
150 defer doc.deinit(gpa);
151
152 var stdout_buf = std.io.bufferedWriter(std.io.getStdOut().writer());
153 try doc.render(stdout_buf.writer());
154 try stdout_buf.flush();
155}
156
157test "empty document" {
158 try testRender("", "");
159 try testRender(" ", "");
160 try testRender("\n \n\t\n \n", "");
161}
162
163test "unordered lists" {
164 try testRender(
165 \\- Spam
166 \\- Spam
167 \\- Spam
168 \\- Eggs
169 \\- Bacon
170 \\- Spam
171 \\
172 \\* Spam
173 \\* Spam
174 \\* Spam
175 \\* Eggs
176 \\* Bacon
177 \\* Spam
178 \\
179 \\+ Spam
180 \\+ Spam
181 \\+ Spam
182 \\+ Eggs
183 \\+ Bacon
184 \\+ Spam
185 \\
186 ,
187 \\<ul>
188 \\<li>Spam</li>
189 \\<li>Spam</li>
190 \\<li>Spam</li>
191 \\<li>Eggs</li>
192 \\<li>Bacon</li>
193 \\<li>Spam</li>
194 \\</ul>
195 \\<ul>
196 \\<li>Spam</li>
197 \\<li>Spam</li>
198 \\<li>Spam</li>
199 \\<li>Eggs</li>
200 \\<li>Bacon</li>
201 \\<li>Spam</li>
202 \\</ul>
203 \\<ul>
204 \\<li>Spam</li>
205 \\<li>Spam</li>
206 \\<li>Spam</li>
207 \\<li>Eggs</li>
208 \\<li>Bacon</li>
209 \\<li>Spam</li>
210 \\</ul>
211 \\
212 );
213}
214
215test "ordered lists" {
216 try testRender(
217 \\1. Breakfast
218 \\2. Second breakfast
219 \\3. Lunch
220 \\2. Afternoon snack
221 \\1. Dinner
222 \\6. Dessert
223 \\7. Midnight snack
224 \\
225 \\1) Breakfast
226 \\2) Second breakfast
227 \\3) Lunch
228 \\2) Afternoon snack
229 \\1) Dinner
230 \\6) Dessert
231 \\7) Midnight snack
232 \\
233 \\1001. Breakfast
234 \\2. Second breakfast
235 \\3. Lunch
236 \\2. Afternoon snack
237 \\1. Dinner
238 \\6. Dessert
239 \\7. Midnight snack
240 \\
241 \\1001) Breakfast
242 \\2) Second breakfast
243 \\3) Lunch
244 \\2) Afternoon snack
245 \\1) Dinner
246 \\6) Dessert
247 \\7) Midnight snack
248 \\
249 ,
250 \\<ol>
251 \\<li>Breakfast</li>
252 \\<li>Second breakfast</li>
253 \\<li>Lunch</li>
254 \\<li>Afternoon snack</li>
255 \\<li>Dinner</li>
256 \\<li>Dessert</li>
257 \\<li>Midnight snack</li>
258 \\</ol>
259 \\<ol>
260 \\<li>Breakfast</li>
261 \\<li>Second breakfast</li>
262 \\<li>Lunch</li>
263 \\<li>Afternoon snack</li>
264 \\<li>Dinner</li>
265 \\<li>Dessert</li>
266 \\<li>Midnight snack</li>
267 \\</ol>
268 \\<ol start="1001">
269 \\<li>Breakfast</li>
270 \\<li>Second breakfast</li>
271 \\<li>Lunch</li>
272 \\<li>Afternoon snack</li>
273 \\<li>Dinner</li>
274 \\<li>Dessert</li>
275 \\<li>Midnight snack</li>
276 \\</ol>
277 \\<ol start="1001">
278 \\<li>Breakfast</li>
279 \\<li>Second breakfast</li>
280 \\<li>Lunch</li>
281 \\<li>Afternoon snack</li>
282 \\<li>Dinner</li>
283 \\<li>Dessert</li>
284 \\<li>Midnight snack</li>
285 \\</ol>
286 \\
287 );
288}
289
290test "nested lists" {
291 try testRender(
292 \\- - Item 1.
293 \\ - Item 2.
294 \\Item 2 continued.
295 \\ * New list.
296 \\
297 ,
298 \\<ul>
299 \\<li><ul>
300 \\<li>Item 1.</li>
301 \\<li>Item 2.
302 \\Item 2 continued.</li>
303 \\</ul>
304 \\<ul>
305 \\<li>New list.</li>
306 \\</ul>
307 \\</li>
308 \\</ul>
309 \\
310 );
311}
312
313test "lists with block content" {
314 try testRender(
315 \\1. Item 1.
316 \\2. Item 2.
317 \\
318 \\ This one has another paragraph.
319 \\3. Item 3.
320 \\
321 \\- > Blockquote.
322 \\- - Sub-list.
323 \\ - Sub-list continued.
324 \\ * Different sub-list.
325 \\- ## Heading.
326 \\
327 \\ Some contents below the heading.
328 \\ 1. Item 1.
329 \\ 2. Item 2.
330 \\ 3. Item 3.
331 \\
332 ,
333 \\<ol>
334 \\<li><p>Item 1.</p>
335 \\</li>
336 \\<li><p>Item 2.</p>
337 \\<p>This one has another paragraph.</p>
338 \\</li>
339 \\<li><p>Item 3.</p>
340 \\</li>
341 \\</ol>
342 \\<ul>
343 \\<li><blockquote>
344 \\<p>Blockquote.</p>
345 \\</blockquote>
346 \\</li>
347 \\<li><ul>
348 \\<li>Sub-list.</li>
349 \\<li>Sub-list continued.</li>
350 \\</ul>
351 \\<ul>
352 \\<li>Different sub-list.</li>
353 \\</ul>
354 \\</li>
355 \\<li><h2>Heading.</h2>
356 \\<p>Some contents below the heading.</p>
357 \\<ol>
358 \\<li>Item 1.</li>
359 \\<li>Item 2.</li>
360 \\<li>Item 3.</li>
361 \\</ol>
362 \\</li>
363 \\</ul>
364 \\
365 );
366}
367
368test "tables" {
369 try testRender(
370 \\| Operator | Meaning |
371 \\| :------: | ---------------- |
372 \\| `+` | Add |
373 \\| `-` | Subtract |
374 \\| `*` | Multiply |
375 \\| `/` | Divide |
376 \\| `??` | **Not sure yet** |
377 \\
378 \\| Item 1 | Value 1 |
379 \\| Item 2 | Value 2 |
380 \\| Item 3 | Value 3 |
381 \\| Item 4 | Value 4 |
382 \\
383 \\| :--- | :----: | ----: |
384 \\| Left | Center | Right |
385 \\
386 ,
387 \\<table>
388 \\<tr>
389 \\<th style="text-align: center">Operator</th>
390 \\<th>Meaning</th>
391 \\</tr>
392 \\<tr>
393 \\<td style="text-align: center"><code>+</code></td>
394 \\<td>Add</td>
395 \\</tr>
396 \\<tr>
397 \\<td style="text-align: center"><code>-</code></td>
398 \\<td>Subtract</td>
399 \\</tr>
400 \\<tr>
401 \\<td style="text-align: center"><code>*</code></td>
402 \\<td>Multiply</td>
403 \\</tr>
404 \\<tr>
405 \\<td style="text-align: center"><code>/</code></td>
406 \\<td>Divide</td>
407 \\</tr>
408 \\<tr>
409 \\<td style="text-align: center"><code>??</code></td>
410 \\<td><strong>Not sure yet</strong></td>
411 \\</tr>
412 \\</table>
413 \\<table>
414 \\<tr>
415 \\<td>Item 1</td>
416 \\<td>Value 1</td>
417 \\</tr>
418 \\<tr>
419 \\<td>Item 2</td>
420 \\<td>Value 2</td>
421 \\</tr>
422 \\<tr>
423 \\<td>Item 3</td>
424 \\<td>Value 3</td>
425 \\</tr>
426 \\<tr>
427 \\<td>Item 4</td>
428 \\<td>Value 4</td>
429 \\</tr>
430 \\</table>
431 \\<table>
432 \\<tr>
433 \\<td style="text-align: left">Left</td>
434 \\<td style="text-align: center">Center</td>
435 \\<td style="text-align: right">Right</td>
436 \\</tr>
437 \\</table>
438 \\
439 );
440}
441
442test "table with uneven number of columns" {
443 try testRender(
444 \\| One |
445 \\| :-- | :--: |
446 \\| One | Two | Three |
447 \\
448 ,
449 \\<table>
450 \\<tr>
451 \\<th style="text-align: left">One</th>
452 \\</tr>
453 \\<tr>
454 \\<td style="text-align: left">One</td>
455 \\<td style="text-align: center">Two</td>
456 \\<td>Three</td>
457 \\</tr>
458 \\</table>
459 \\
460 );
461}
462
463test "table with escaped pipes" {
464 try testRender(
465 \\| One \| Two |
466 \\| --- | --- |
467 \\| One \| Two |
468 \\
469 ,
470 \\<table>
471 \\<tr>
472 \\<th>One | Two</th>
473 \\</tr>
474 \\<tr>
475 \\<td>One | Two</td>
476 \\</tr>
477 \\</table>
478 \\
479 );
480}
481
482test "table with pipes in code spans" {
483 try testRender(
484 \\| `|` | Bitwise _OR_ |
485 \\| `||` | Combines error sets |
486 \\| `` `||` `` | Escaped version |
487 \\| ` ``||`` ` | Another escaped version |
488 \\| `Oops unterminated code span |
489 \\
490 ,
491 \\<table>
492 \\<tr>
493 \\<td><code>|</code></td>
494 \\<td>Bitwise <em>OR</em></td>
495 \\</tr>
496 \\<tr>
497 \\<td><code>||</code></td>
498 \\<td>Combines error sets</td>
499 \\</tr>
500 \\<tr>
501 \\<td><code>`||`</code></td>
502 \\<td>Escaped version</td>
503 \\</tr>
504 \\<tr>
505 \\<td><code>``||``</code></td>
506 \\<td>Another escaped version</td>
507 \\</tr>
508 \\</table>
509 \\<p>| <code>Oops unterminated code span |</code></p>
510 \\
511 );
512}
513
514test "tables require leading and trailing pipes" {
515 try testRender(
516 \\Not | a | table
517 \\
518 \\| But | this | is |
519 \\
520 ,
521 \\<p>Not | a | table</p>
522 \\<table>
523 \\<tr>
524 \\<td>But</td>
525 \\<td>this</td>
526 \\<td>is</td>
527 \\</tr>
528 \\</table>
529 \\
530 );
531}
532
533test "headings" {
534 try testRender(
535 \\# Level one
536 \\## Level two
537 \\### Level three
538 \\#### Level four
539 \\##### Level five
540 \\###### Level six
541 \\####### Not a heading
542 \\
543 ,
544 \\<h1>Level one</h1>
545 \\<h2>Level two</h2>
546 \\<h3>Level three</h3>
547 \\<h4>Level four</h4>
548 \\<h5>Level five</h5>
549 \\<h6>Level six</h6>
550 \\<p>####### Not a heading</p>
551 \\
552 );
553}
554
555test "headings with inline content" {
556 try testRender(
557 \\# Outline of `std.zig`
558 \\## **Important** notes
559 \\### ***Nested* inline content**
560 \\
561 ,
562 \\<h1>Outline of <code>std.zig</code></h1>
563 \\<h2><strong>Important</strong> notes</h2>
564 \\<h3><strong><em>Nested</em> inline content</strong></h3>
565 \\
566 );
567}
568
569test "code blocks" {
570 try testRender(
571 \\```
572 \\Hello, world!
573 \\This is some code.
574 \\```
575 \\``` zig test
576 \\const std = @import("std");
577 \\
578 \\test {
579 \\ try std.testing.expect(2 + 2 == 4);
580 \\}
581 \\```
582 \\
583 ,
584 \\<pre><code>Hello, world!
585 \\This is some code.
586 \\</code></pre>
587 \\<pre><code class="zig test">const std = @import(&quot;std&quot;);
588 \\
589 \\test {
590 \\ try std.testing.expect(2 + 2 == 4);
591 \\}
592 \\</code></pre>
593 \\
594 );
595}
596
597test "blockquotes" {
598 try testRender(
599 \\> > You miss 100% of the shots you don't take.
600 \\> >
601 \\> > ~ Wayne Gretzky
602 \\>
603 \\> ~ Michael Scott
604 \\
605 ,
606 \\<blockquote>
607 \\<blockquote>
608 \\<p>You miss 100% of the shots you don't take.</p>
609 \\<p>~ Wayne Gretzky</p>
610 \\</blockquote>
611 \\<p>~ Michael Scott</p>
612 \\</blockquote>
613 \\
614 );
615}
616
617test "blockquote lazy continuation lines" {
618 try testRender(
619 \\>>>>Deeply nested blockquote
620 \\>>which continues on another line
621 \\and then yet another one.
622 \\>>
623 \\>> But now two of them have been closed.
624 \\
625 \\And then there were none.
626 \\
627 ,
628 \\<blockquote>
629 \\<blockquote>
630 \\<blockquote>
631 \\<blockquote>
632 \\<p>Deeply nested blockquote
633 \\which continues on another line
634 \\and then yet another one.</p>
635 \\</blockquote>
636 \\</blockquote>
637 \\<p>But now two of them have been closed.</p>
638 \\</blockquote>
639 \\</blockquote>
640 \\<p>And then there were none.</p>
641 \\
642 );
643}
644
645test "paragraphs" {
646 try testRender(
647 \\Paragraph one.
648 \\
649 \\Paragraph two.
650 \\Still in the paragraph.
651 \\ So is this.
652 \\
653 \\
654 \\
655 \\
656 \\ Last paragraph.
657 \\
658 ,
659 \\<p>Paragraph one.</p>
660 \\<p>Paragraph two.
661 \\Still in the paragraph.
662 \\So is this.</p>
663 \\<p>Last paragraph.</p>
664 \\
665 );
666}
667
668test "thematic breaks" {
669 try testRender(
670 \\---
671 \\***
672 \\___
673 \\ ---
674 \\ - - - - - - - - - - -
675 \\
676 ,
677 \\<hr />
678 \\<hr />
679 \\<hr />
680 \\<hr />
681 \\<hr />
682 \\
683 );
684}
685
686test "links" {
687 try testRender(
688 \\[Link](https://example.com)
689 \\[Link *with inlines*](https://example.com)
690 \\[Nested parens](https://example.com/nested(parens(inside)))
691 \\[Escaped parens](https://example.com/\)escaped\()
692 \\[Line break in target](test\
693 \\target)
694 \\
695 ,
696 \\<p><a href="https://example.com">Link</a>
697 \\<a href="https://example.com">Link <em>with inlines</em></a>
698 \\<a href="https://example.com/nested(parens(inside))">Nested parens</a>
699 \\<a href="https://example.com/)escaped(">Escaped parens</a>
700 \\<a href="test\
701 \\target">Line break in target</a></p>
702 \\
703 );
704}
705
706test "images" {
707 try testRender(
708 \\![Alt text](https://example.com/image.png)
709 \\![Alt text *with inlines*](https://example.com/image.png)
710 \\![Nested parens](https://example.com/nested(parens(inside)).png)
711 \\![Escaped parens](https://example.com/\)escaped\(.png)
712 \\![Line break in target](test\
713 \\target)
714 \\
715 ,
716 \\<p><img src="https://example.com/image.png" alt="Alt text" />
717 \\<img src="https://example.com/image.png" alt="Alt text with inlines" />
718 \\<img src="https://example.com/nested(parens(inside)).png" alt="Nested parens" />
719 \\<img src="https://example.com/)escaped(.png" alt="Escaped parens" />
720 \\<img src="test\
721 \\target" alt="Line break in target" /></p>
722 \\
723 );
724}
725
726test "emphasis" {
727 try testRender(
728 \\*Emphasis.*
729 \\**Strong.**
730 \\***Strong emphasis.***
731 \\****More...****
732 \\*****MORE...*****
733 \\******Even more...******
734 \\*******OK, this is enough.*******
735 \\
736 ,
737 \\<p><em>Emphasis.</em>
738 \\<strong>Strong.</strong>
739 \\<em><strong>Strong emphasis.</strong></em>
740 \\<em><strong><em>More...</em></strong></em>
741 \\<em><strong><strong>MORE...</strong></strong></em>
742 \\<em><strong><em><strong>Even more...</strong></em></strong></em>
743 \\<em><strong><em><strong><em>OK, this is enough.</em></strong></em></strong></em></p>
744 \\
745 );
746 try testRender(
747 \\_Emphasis._
748 \\__Strong.__
749 \\___Strong emphasis.___
750 \\____More...____
751 \\_____MORE..._____
752 \\______Even more...______
753 \\_______OK, this is enough._______
754 \\
755 ,
756 \\<p><em>Emphasis.</em>
757 \\<strong>Strong.</strong>
758 \\<em><strong>Strong emphasis.</strong></em>
759 \\<em><strong><em>More...</em></strong></em>
760 \\<em><strong><strong>MORE...</strong></strong></em>
761 \\<em><strong><em><strong>Even more...</strong></em></strong></em>
762 \\<em><strong><em><strong><em>OK, this is enough.</em></strong></em></strong></em></p>
763 \\
764 );
765}
766
767test "nested emphasis" {
768 try testRender(
769 \\**Hello, *world!***
770 \\*Hello, **world!***
771 \\**Hello, _world!_**
772 \\_Hello, **world!**_
773 \\*Hello, **nested** *world!**
774 \\***Hello,* world!**
775 \\__**Hello, world!**__
776 \\****Hello,** world!**
777 \\__Hello,_ world!_
778 \\*Test**123*
779 \\__Test____123__
780 \\
781 ,
782 \\<p><strong>Hello, <em>world!</em></strong>
783 \\<em>Hello, <strong>world!</strong></em>
784 \\<strong>Hello, <em>world!</em></strong>
785 \\<em>Hello, <strong>world!</strong></em>
786 \\<em>Hello, <strong>nested</strong> <em>world!</em></em>
787 \\<strong><em>Hello,</em> world!</strong>
788 \\<strong><strong>Hello, world!</strong></strong>
789 \\<strong><strong>Hello,</strong> world!</strong>
790 \\<em><em>Hello,</em> world!</em>
791 \\<em>Test</em><em>123</em>
792 \\<strong>Test____123</strong></p>
793 \\
794 );
795}
796
797test "emphasis precedence" {
798 try testRender(
799 \\*First one _wins*_.
800 \\_*No other __rule matters.*_
801 \\
802 ,
803 \\<p><em>First one _wins</em>_.
804 \\<em><em>No other __rule matters.</em></em></p>
805 \\
806 );
807}
808
809test "emphasis open and close" {
810 try testRender(
811 \\Cannot open: *
812 \\Cannot open: _
813 \\*Cannot close: *
814 \\_Cannot close: _
815 \\
816 \\foo*bar*baz
817 \\foo_bar_baz
818 \\foo**bar**baz
819 \\foo__bar__baz
820 \\
821 ,
822 \\<p>Cannot open: *
823 \\Cannot open: _
824 \\*Cannot close: *
825 \\_Cannot close: _</p>
826 \\<p>foo<em>bar</em>baz
827 \\foo_bar_baz
828 \\foo<strong>bar</strong>baz
829 \\foo__bar__baz</p>
830 \\
831 );
832}
833
834test "code spans" {
835 try testRender(
836 \\`Hello, world!`
837 \\```Multiple `backticks` can be used.```
838 \\`**This** does not produce emphasis.`
839 \\`` `Backtick enclosed string.` ``
840 \\`Delimiter lengths ```must``` match.`
841 \\
842 \\Unterminated ``code...
843 \\
844 \\Weird empty code span: `
845 \\
846 \\**Very important code: `hi`**
847 \\
848 ,
849 \\<p><code>Hello, world!</code>
850 \\<code>Multiple `backticks` can be used.</code>
851 \\<code>**This** does not produce emphasis.</code>
852 \\<code>`Backtick enclosed string.`</code>
853 \\<code>Delimiter lengths ```must``` match.</code></p>
854 \\<p>Unterminated <code>code...</code></p>
855 \\<p>Weird empty code span: <code></code></p>
856 \\<p><strong>Very important code: <code>hi</code></strong></p>
857 \\
858 );
859}
860
861test "backslash escapes" {
862 try testRender(
863 \\Not \*emphasized\*.
864 \\Literal \\backslashes\\.
865 \\Not code: \`hi\`.
866 \\\# Not a title.
867 \\#\# Also not a title.
868 \\\> Not a blockquote.
869 \\\- Not a list item.
870 \\\| Not a table. |
871 \\| Also not a table. \|
872 \\Any \punctuation\ characte\r can be escaped:
873 \\\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~
874 \\
875 ,
876 \\<p>Not *emphasized*.
877 \\Literal \backslashes\.
878 \\Not code: `hi`.
879 \\# Not a title.
880 \\## Also not a title.
881 \\&gt; Not a blockquote.
882 \\- Not a list item.
883 \\| Not a table. |
884 \\| Also not a table. |
885 \\Any \punctuation\ characte\r can be escaped:
886 \\!&quot;#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</p>
887 \\
888 );
889}
890
891test "hard line breaks" {
892 try testRender(
893 \\The iguana sits\
894 \\Perched atop a short desk chair\
895 \\Writing code in Zig
896 \\
897 ,
898 \\<p>The iguana sits<br />
899 \\Perched atop a short desk chair<br />
900 \\Writing code in Zig</p>
901 \\
902 );
903}
904
905test "Unicode handling" {
906 // Null bytes must be replaced.
907 try testRender("\x00\x00\x00", "<p>\u{FFFD}\u{FFFD}\u{FFFD}</p>\n");
908
909 // Invalid UTF-8 must be replaced.
910 try testRender("\xC0\x80\xE0\x80\x80\xF0\x80\x80\x80", "<p>\u{FFFD}\u{FFFD}\u{FFFD}</p>\n");
911 try testRender("\xED\xA0\x80\xED\xBF\xBF", "<p>\u{FFFD}\u{FFFD}</p>\n");
912
913 // Incomplete UTF-8 must be replaced.
914 try testRender("\xE2\x82", "<p>\u{FFFD}</p>\n");
915}
916
917fn testRender(input: []const u8, expected: []const u8) !void {
918 var parser = try Parser.init(testing.allocator);
919 defer parser.deinit();
920
921 var lines = std.mem.split(u8, input, "\n");
922 while (lines.next()) |line| {
923 try parser.feedLine(line);
924 }
925 var doc = try parser.endInput();
926 defer doc.deinit(testing.allocator);
927
928 var actual = std.ArrayList(u8).init(testing.allocator);
929 defer actual.deinit();
930 try doc.render(actual.writer());
931
932 try testing.expectEqualStrings(expected, actual.items);
933}
lib/docs/wasm/markdown/Document.zig created+192
......@@ -0,0 +1,192 @@
1//! An abstract tree representation of a Markdown document.
2
3const std = @import("std");
4const builtin = @import("builtin");
5const assert = std.debug.assert;
6const Allocator = std.mem.Allocator;
7const Renderer = @import("renderer.zig").Renderer;
8
9nodes: Node.List.Slice,
10extra: []u32,
11string_bytes: []u8,
12
13const Document = @This();
14
15pub const Node = struct {
16 tag: Tag,
17 data: Data,
18
19 pub const Index = enum(u32) {
20 root = 0,
21 _,
22 };
23 pub const List = std.MultiArrayList(Node);
24
25 pub const Tag = enum {
26 /// Data is `container`.
27 root,
28
29 // Blocks
30 /// Data is `list`.
31 list,
32 /// Data is `list_item`.
33 list_item,
34 /// Data is `container`.
35 table,
36 /// Data is `container`.
37 table_row,
38 /// Data is `table_cell`.
39 table_cell,
40 /// Data is `heading`.
41 heading,
42 /// Data is `code_block`.
43 code_block,
44 /// Data is `container`.
45 blockquote,
46 /// Data is `container`.
47 paragraph,
48 /// Data is `none`.
49 thematic_break,
50
51 // Inlines
52 /// Data is `link`.
53 link,
54 /// Data is `link`.
55 image,
56 /// Data is `container`.
57 strong,
58 /// Data is `container`.
59 emphasis,
60 /// Data is `text`.
61 code_span,
62 /// Data is `text`.
63 text,
64 /// Data is `none`.
65 line_break,
66 };
67
68 pub const Data = union {
69 none: void,
70 container: struct {
71 children: ExtraIndex,
72 },
73 text: struct {
74 content: StringIndex,
75 },
76 list: struct {
77 start: ListStart,
78 children: ExtraIndex,
79 },
80 list_item: struct {
81 tight: bool,
82 children: ExtraIndex,
83 },
84 table_cell: struct {
85 info: packed struct {
86 alignment: TableCellAlignment,
87 header: bool,
88 },
89 children: ExtraIndex,
90 },
91 heading: struct {
92 /// Between 1 and 6, inclusive.
93 level: u3,
94 children: ExtraIndex,
95 },
96 code_block: struct {
97 tag: StringIndex,
98 content: StringIndex,
99 },
100 link: struct {
101 target: StringIndex,
102 children: ExtraIndex,
103 },
104
105 comptime {
106 // In Debug and ReleaseSafe builds, there may be hidden extra fields
107 // included for safety checks. Without such safety checks enabled,
108 // we always want this union to be 8 bytes.
109 if (builtin.mode != .Debug and builtin.mode != .ReleaseSafe) {
110 assert(@sizeOf(Data) == 8);
111 }
112 }
113 };
114
115 /// The starting number of a list. This is either a number between 0 and
116 /// 999,999,999, inclusive, or `unordered` to indicate an unordered list.
117 pub const ListStart = enum(u30) {
118 // When https://github.com/ziglang/zig/issues/104 is implemented, this
119 // type can be more naturally expressed as ?u30. As it is, we want
120 // values to fit within 4 bytes, so ?u30 does not yet suffice for
121 // storage.
122 unordered = std.math.maxInt(u30),
123 _,
124
125 pub fn asNumber(start: ListStart) ?u30 {
126 if (start == .unordered) return null;
127 assert(@intFromEnum(start) <= 999_999_999);
128 return @intFromEnum(start);
129 }
130 };
131
132 pub const TableCellAlignment = enum {
133 unset,
134 left,
135 center,
136 right,
137 };
138
139 /// Trailing: `len` times `Node.Index`
140 pub const Children = struct {
141 len: u32,
142 };
143};
144
145pub const ExtraIndex = enum(u32) { _ };
146
147/// The index of a null-terminated string in `string_bytes`.
148pub const StringIndex = enum(u32) {
149 empty = 0,
150 _,
151};
152
153pub fn deinit(doc: *Document, allocator: Allocator) void {
154 doc.nodes.deinit(allocator);
155 allocator.free(doc.extra);
156 allocator.free(doc.string_bytes);
157 doc.* = undefined;
158}
159
160/// Renders a document directly to a writer using the default renderer.
161pub fn render(doc: Document, writer: anytype) @TypeOf(writer).Error!void {
162 const renderer: Renderer(@TypeOf(writer), void) = .{ .context = {} };
163 try renderer.render(doc, writer);
164}
165
166pub fn ExtraData(comptime T: type) type {
167 return struct { data: T, end: usize };
168}
169
170pub fn extraData(doc: Document, comptime T: type, index: ExtraIndex) ExtraData(T) {
171 const fields = @typeInfo(T).Struct.fields;
172 var i: usize = @intFromEnum(index);
173 var result: T = undefined;
174 inline for (fields) |field| {
175 @field(result, field.name) = switch (field.type) {
176 u32 => doc.extra[i],
177 else => @compileError("bad field type"),
178 };
179 i += 1;
180 }
181 return .{ .data = result, .end = i };
182}
183
184pub fn extraChildren(doc: Document, index: ExtraIndex) []const Node.Index {
185 const children = doc.extraData(Node.Children, index);
186 return @ptrCast(doc.extra[children.end..][0..children.data.len]);
187}
188
189pub fn string(doc: Document, index: StringIndex) [:0]const u8 {
190 const start = @intFromEnum(index);
191 return std.mem.span(@as([*:0]u8, @ptrCast(doc.string_bytes[start..].ptr)));
192}
lib/docs/wasm/markdown/Parser.zig created+1500
......@@ -0,0 +1,1500 @@
1//! A Markdown parser producing `Document`s.
2//!
3//! The parser operates at two levels: at the outer level, the parser accepts
4//! the content of an input document line by line and begins building the _block
5//! structure_ of the document. This creates a stack of currently open blocks.
6//!
7//! When the parser detects the end of a block, it closes the block, popping it
8//! from the open block stack and completing any additional parsing of the
9//! block's content. For blocks which contain parseable inline content, this
10//! invokes the inner level of the parser, handling the _inline structure_ of
11//! the block.
12//!
13//! Inline parsing scans through the collected inline content of a block. When
14//! it encounters a character that could indicate the beginning of an inline, it
15//! either handles the inline right away (if possible) or adds it to a pending
16//! inlines stack. When an inline is completed, it is added to a list of
17//! completed inlines, which (along with any surrounding text nodes) will become
18//! the children of the parent inline or the block whose inline content is being
19//! parsed.
20
21const std = @import("std");
22const mem = std.mem;
23const assert = std.debug.assert;
24const isWhitespace = std.ascii.isWhitespace;
25const Allocator = mem.Allocator;
26const expectEqual = std.testing.expectEqual;
27const Document = @import("Document.zig");
28const Node = Document.Node;
29const ExtraIndex = Document.ExtraIndex;
30const ExtraData = Document.ExtraData;
31const StringIndex = Document.StringIndex;
32
33nodes: Node.List = .{},
34extra: std.ArrayListUnmanaged(u32) = .{},
35scratch_extra: std.ArrayListUnmanaged(u32) = .{},
36string_bytes: std.ArrayListUnmanaged(u8) = .{},
37scratch_string: std.ArrayListUnmanaged(u8) = .{},
38pending_blocks: std.ArrayListUnmanaged(Block) = .{},
39allocator: Allocator,
40
41const Parser = @This();
42
43/// An arbitrary limit on the maximum number of columns in a table so that
44/// table-related metadata maintained by the parser does not require dynamic
45/// memory allocation.
46const max_table_columns = 128;
47
48/// A block element which is still receiving children.
49const Block = struct {
50 tag: Tag,
51 data: Data,
52 extra_start: usize,
53 string_start: usize,
54
55 const Tag = enum {
56 /// Data is `list`.
57 list,
58 /// Data is `list_item`.
59 list_item,
60 /// Data is `table`.
61 table,
62 /// Data is `none`.
63 table_row,
64 /// Data is `heading`.
65 heading,
66 /// Data is `code_block`.
67 code_block,
68 /// Data is `none`.
69 blockquote,
70 /// Data is `none`.
71 paragraph,
72 /// Data is `none`.
73 thematic_break,
74 };
75
76 const Data = union {
77 none: void,
78 list: struct {
79 marker: ListMarker,
80 /// Between 0 and 999,999,999, inclusive.
81 start: u30,
82 tight: bool,
83 last_line_blank: bool = false,
84 },
85 list_item: struct {
86 continuation_indent: usize,
87 },
88 table: struct {
89 column_alignments: std.BoundedArray(Node.TableCellAlignment, max_table_columns) = .{},
90 },
91 heading: struct {
92 /// Between 1 and 6, inclusive.
93 level: u3,
94 },
95 code_block: struct {
96 tag: StringIndex,
97 fence_len: usize,
98 indent: usize,
99 },
100
101 const ListMarker = enum {
102 @"-",
103 @"*",
104 @"+",
105 number_dot,
106 number_paren,
107 };
108 };
109
110 const ContentType = enum {
111 blocks,
112 inlines,
113 raw_inlines,
114 nothing,
115 };
116
117 fn canAccept(b: Block) ContentType {
118 return switch (b.tag) {
119 .list,
120 .list_item,
121 .table,
122 .blockquote,
123 => .blocks,
124
125 .heading,
126 .paragraph,
127 => .inlines,
128
129 .code_block,
130 => .raw_inlines,
131
132 .table_row,
133 .thematic_break,
134 => .nothing,
135 };
136 }
137
138 /// Attempts to continue `b` using the contents of `line`. If successful,
139 /// returns the remaining portion of `line` to be considered part of `b`
140 /// (e.g. for a blockquote, this would be everything except the leading
141 /// `>`). If unsuccessful, returns null.
142 fn match(b: Block, line: []const u8) ?[]const u8 {
143 const unindented = mem.trimLeft(u8, line, " \t");
144 const indent = line.len - unindented.len;
145 return switch (b.tag) {
146 .list => line,
147 .list_item => if (indent >= b.data.list_item.continuation_indent)
148 line[b.data.list_item.continuation_indent..]
149 else if (unindented.len == 0)
150 // Blank lines should not close list items, since there may be
151 // more indented contents to follow after the blank line.
152 ""
153 else
154 null,
155 .table => if (unindented.len > 0) unindented else null,
156 .table_row => null,
157 .heading => null,
158 .code_block => code_block: {
159 const trimmed = mem.trimRight(u8, unindented, " \t");
160 if (mem.indexOfNone(u8, trimmed, "`") != null or trimmed.len != b.data.code_block.fence_len) {
161 const effective_indent = @min(indent, b.data.code_block.indent);
162 break :code_block line[effective_indent..];
163 } else {
164 break :code_block null;
165 }
166 },
167 .blockquote => if (mem.startsWith(u8, unindented, ">"))
168 unindented[1..]
169 else
170 null,
171 .paragraph => if (unindented.len > 0) unindented else null,
172 .thematic_break => null,
173 };
174 }
175};
176
177pub fn init(allocator: Allocator) Allocator.Error!Parser {
178 var p: Parser = .{ .allocator = allocator };
179 try p.nodes.append(allocator, .{
180 .tag = .root,
181 .data = undefined,
182 });
183 try p.string_bytes.append(allocator, 0);
184 return p;
185}
186
187pub fn deinit(p: *Parser) void {
188 p.nodes.deinit(p.allocator);
189 p.extra.deinit(p.allocator);
190 p.scratch_extra.deinit(p.allocator);
191 p.string_bytes.deinit(p.allocator);
192 p.scratch_string.deinit(p.allocator);
193 p.pending_blocks.deinit(p.allocator);
194 p.* = undefined;
195}
196
197/// Accepts a single line of content. `line` should not have a trailing line
198/// ending character.
199pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {
200 var rest_line = line;
201 const first_unmatched = for (p.pending_blocks.items, 0..) |b, i| {
202 if (b.match(rest_line)) |rest| {
203 rest_line = rest;
204 } else {
205 break i;
206 }
207 } else p.pending_blocks.items.len;
208
209 const in_code_block = p.pending_blocks.items.len > 0 and
210 p.pending_blocks.getLast().tag == .code_block;
211 const code_block_end = in_code_block and
212 first_unmatched + 1 == p.pending_blocks.items.len;
213 // New blocks cannot be started if we are actively inside a code block or
214 // are just closing one (to avoid interpreting the closing ``` as a new code
215 // block start).
216 var maybe_block_start = if (!in_code_block or first_unmatched + 2 <= p.pending_blocks.items.len)
217 try p.startBlock(rest_line)
218 else
219 null;
220
221 // This is a lazy continuation line if there are no new blocks to open and
222 // the last open block is a paragraph.
223 if (maybe_block_start == null and
224 !isBlank(rest_line) and
225 p.pending_blocks.items.len > 0 and
226 p.pending_blocks.getLast().tag == .paragraph)
227 {
228 try p.addScratchStringLine(rest_line);
229 return;
230 }
231
232 // If a new block needs to be started, any paragraph needs to be closed,
233 // even though this isn't detected as part of the closing condition for
234 // paragraphs.
235 if (maybe_block_start != null and
236 p.pending_blocks.items.len > 0 and
237 p.pending_blocks.getLast().tag == .paragraph)
238 {
239 try p.closeLastBlock();
240 }
241
242 while (p.pending_blocks.items.len > first_unmatched) {
243 try p.closeLastBlock();
244 }
245
246 while (maybe_block_start) |block_start| : (maybe_block_start = try p.startBlock(rest_line)) {
247 try p.appendBlockStart(block_start);
248 // There may be more blocks to start within the same line.
249 rest_line = block_start.rest;
250 // Headings may only contain inline content.
251 if (block_start.tag == .heading) break;
252 // An opening code fence does not contain any additional block or inline
253 // content to process.
254 if (block_start.tag == .code_block) return;
255 }
256
257 // Do not append the end of a code block (```) as textual content.
258 if (code_block_end) return;
259
260 const can_accept = if (p.pending_blocks.getLastOrNull()) |last_pending_block|
261 last_pending_block.canAccept()
262 else
263 .blocks;
264 const rest_line_trimmed = mem.trimLeft(u8, rest_line, " \t");
265 switch (can_accept) {
266 .blocks => {
267 // If we're inside a list item and the rest of the line is blank, it
268 // means that any subsequent child of the list item (or subsequent
269 // item in the list) will cause the containing list to be considered
270 // loose. However, we can't immediately declare that the list is
271 // loose, since we might just be looking at a blank line after the
272 // end of the last item in the list. The final determination will be
273 // made when appending the next child of the list or list item.
274 const maybe_containing_list = if (p.pending_blocks.items.len > 0 and p.pending_blocks.getLast().tag == .list_item)
275 &p.pending_blocks.items[p.pending_blocks.items.len - 2]
276 else
277 null;
278
279 if (rest_line_trimmed.len > 0) {
280 try p.appendBlockStart(.{
281 .tag = .paragraph,
282 .data = .{ .none = {} },
283 .rest = undefined,
284 });
285 try p.addScratchStringLine(rest_line_trimmed);
286 }
287
288 if (maybe_containing_list) |containing_list| {
289 containing_list.data.list.last_line_blank = rest_line_trimmed.len == 0;
290 }
291 },
292 .inlines => try p.addScratchStringLine(rest_line_trimmed),
293 .raw_inlines => try p.addScratchStringLine(rest_line),
294 .nothing => {},
295 }
296}
297
298/// Completes processing of the input and returns the parsed document.
299pub fn endInput(p: *Parser) Allocator.Error!Document {
300 while (p.pending_blocks.items.len > 0) {
301 try p.closeLastBlock();
302 }
303 // There should be no inline content pending after closing the last open
304 // block.
305 assert(p.scratch_string.items.len == 0);
306
307 const children = try p.addExtraChildren(@ptrCast(p.scratch_extra.items));
308 p.nodes.items(.data)[0] = .{ .container = .{ .children = children } };
309 p.scratch_string.items.len = 0;
310 p.scratch_extra.items.len = 0;
311
312 var nodes = p.nodes.toOwnedSlice();
313 errdefer nodes.deinit(p.allocator);
314 const extra = try p.extra.toOwnedSlice(p.allocator);
315 errdefer p.allocator.free(extra);
316 const string_bytes = try p.string_bytes.toOwnedSlice(p.allocator);
317 errdefer p.allocator.free(string_bytes);
318
319 return .{
320 .nodes = nodes,
321 .extra = extra,
322 .string_bytes = string_bytes,
323 };
324}
325
326/// Data describing the start of a new block element.
327const BlockStart = struct {
328 tag: Tag,
329 data: Data,
330 rest: []const u8,
331
332 const Tag = enum {
333 /// Data is `list_item`.
334 list_item,
335 /// Data is `table_row`.
336 table_row,
337 /// Data is `heading`.
338 heading,
339 /// Data is `code_block`.
340 code_block,
341 /// Data is `none`.
342 blockquote,
343 /// Data is `none`.
344 paragraph,
345 /// Data is `none`.
346 thematic_break,
347 };
348
349 const Data = union {
350 none: void,
351 list_item: struct {
352 marker: Block.Data.ListMarker,
353 number: u30,
354 continuation_indent: usize,
355 },
356 table_row: struct {
357 cells: std.BoundedArray([]const u8, max_table_columns),
358 },
359 heading: struct {
360 /// Between 1 and 6, inclusive.
361 level: u3,
362 },
363 code_block: struct {
364 tag: StringIndex,
365 fence_len: usize,
366 indent: usize,
367 },
368 };
369};
370
371fn appendBlockStart(p: *Parser, block_start: BlockStart) !void {
372 if (p.pending_blocks.getLastOrNull()) |last_pending_block| {
373 // Close the last block if it is a list and the new block is not a list item
374 // or not of the same marker type.
375 const should_close_list = last_pending_block.tag == .list and
376 (block_start.tag != .list_item or
377 block_start.data.list_item.marker != last_pending_block.data.list.marker);
378 // The last block should also be closed if the new block is not a table
379 // row, which is the only allowed child of a table.
380 const should_close_table = last_pending_block.tag == .table and
381 block_start.tag != .table_row;
382 if (should_close_list or should_close_table) {
383 try p.closeLastBlock();
384 }
385 }
386
387 if (p.pending_blocks.getLastOrNull()) |last_pending_block| {
388 // If the last block is a list or list item, check for tightness based
389 // on the last line.
390 const maybe_containing_list = switch (last_pending_block.tag) {
391 .list => &p.pending_blocks.items[p.pending_blocks.items.len - 1],
392 .list_item => &p.pending_blocks.items[p.pending_blocks.items.len - 2],
393 else => null,
394 };
395 if (maybe_containing_list) |containing_list| {
396 if (containing_list.data.list.last_line_blank) {
397 containing_list.data.list.tight = false;
398 }
399 }
400 }
401
402 // Start a new list if the new block is a list item and there is no
403 // containing list yet.
404 if (block_start.tag == .list_item and
405 (p.pending_blocks.items.len == 0 or p.pending_blocks.getLast().tag != .list))
406 {
407 try p.pending_blocks.append(p.allocator, .{
408 .tag = .list,
409 .data = .{ .list = .{
410 .marker = block_start.data.list_item.marker,
411 .start = block_start.data.list_item.number,
412 .tight = true,
413 } },
414 .string_start = p.scratch_string.items.len,
415 .extra_start = p.scratch_extra.items.len,
416 });
417 }
418
419 if (block_start.tag == .table_row) {
420 // Likewise, table rows start a table implicitly.
421 if (p.pending_blocks.items.len == 0 or p.pending_blocks.getLast().tag != .table) {
422 try p.pending_blocks.append(p.allocator, .{
423 .tag = .table,
424 .data = .{ .table = .{
425 .column_alignments = .{},
426 } },
427 .string_start = p.scratch_string.items.len,
428 .extra_start = p.scratch_extra.items.len,
429 });
430 }
431
432 const current_row = p.scratch_extra.items.len - p.pending_blocks.getLast().extra_start;
433 if (current_row <= 1) {
434 if (parseTableHeaderDelimiter(block_start.data.table_row.cells)) |alignments| {
435 p.pending_blocks.items[p.pending_blocks.items.len - 1].data.table.column_alignments = alignments;
436 if (current_row == 1) {
437 // We need to go back and mark the header row and its column
438 // alignments.
439 const datas = p.nodes.items(.data);
440 const header_data = datas[p.scratch_extra.getLast()];
441 for (p.extraChildren(header_data.container.children), 0..) |header_cell, i| {
442 const alignment = if (i < alignments.len) alignments.buffer[i] else .unset;
443 const cell_data = &datas[@intFromEnum(header_cell)].table_cell;
444 cell_data.info.alignment = alignment;
445 cell_data.info.header = true;
446 }
447 }
448 return;
449 }
450 }
451 }
452
453 const tag: Block.Tag, const data: Block.Data = switch (block_start.tag) {
454 .list_item => .{ .list_item, .{ .list_item = .{
455 .continuation_indent = block_start.data.list_item.continuation_indent,
456 } } },
457 .table_row => .{ .table_row, .{ .none = {} } },
458 .heading => .{ .heading, .{ .heading = .{
459 .level = block_start.data.heading.level,
460 } } },
461 .code_block => .{ .code_block, .{ .code_block = .{
462 .tag = block_start.data.code_block.tag,
463 .fence_len = block_start.data.code_block.fence_len,
464 .indent = block_start.data.code_block.indent,
465 } } },
466 .blockquote => .{ .blockquote, .{ .none = {} } },
467 .paragraph => .{ .paragraph, .{ .none = {} } },
468 .thematic_break => .{ .thematic_break, .{ .none = {} } },
469 };
470
471 try p.pending_blocks.append(p.allocator, .{
472 .tag = tag,
473 .data = data,
474 .string_start = p.scratch_string.items.len,
475 .extra_start = p.scratch_extra.items.len,
476 });
477
478 if (tag == .table_row) {
479 // Table rows are unique, since we already have all the children
480 // available in the BlockStart. We can immediately parse and append
481 // these children now.
482 const containing_table = p.pending_blocks.items[p.pending_blocks.items.len - 2];
483 const column_alignments = containing_table.data.table.column_alignments.slice();
484 for (block_start.data.table_row.cells.slice(), 0..) |cell_content, i| {
485 const cell_children = try p.parseInlines(cell_content);
486 const alignment = if (i < column_alignments.len) column_alignments[i] else .unset;
487 const cell = try p.addNode(.{
488 .tag = .table_cell,
489 .data = .{ .table_cell = .{
490 .info = .{
491 .alignment = alignment,
492 .header = false,
493 },
494 .children = cell_children,
495 } },
496 });
497 try p.addScratchExtraNode(cell);
498 }
499 }
500}
501
502fn startBlock(p: *Parser, line: []const u8) !?BlockStart {
503 const unindented = mem.trimLeft(u8, line, " \t");
504 const indent = line.len - unindented.len;
505 if (isThematicBreak(line)) {
506 // Thematic breaks take precedence over list items.
507 return .{
508 .tag = .thematic_break,
509 .data = .{ .none = {} },
510 .rest = "",
511 };
512 } else if (startListItem(unindented)) |list_item| {
513 return .{
514 .tag = .list_item,
515 .data = .{ .list_item = .{
516 .marker = list_item.marker,
517 .number = list_item.number,
518 .continuation_indent = list_item.continuation_indent,
519 } },
520 .rest = list_item.rest,
521 };
522 } else if (startTableRow(unindented)) |table_row| {
523 return .{
524 .tag = .table_row,
525 .data = .{ .table_row = .{
526 .cells = table_row.cells,
527 } },
528 .rest = "",
529 };
530 } else if (startHeading(unindented)) |heading| {
531 return .{
532 .tag = .heading,
533 .data = .{ .heading = .{
534 .level = heading.level,
535 } },
536 .rest = heading.rest,
537 };
538 } else if (try p.startCodeBlock(unindented)) |code_block| {
539 return .{
540 .tag = .code_block,
541 .data = .{ .code_block = .{
542 .tag = code_block.tag,
543 .fence_len = code_block.fence_len,
544 .indent = indent,
545 } },
546 .rest = "",
547 };
548 } else if (startBlockquote(unindented)) |rest| {
549 return .{
550 .tag = .blockquote,
551 .data = .{ .none = {} },
552 .rest = rest,
553 };
554 } else {
555 return null;
556 }
557}
558
559const ListItemStart = struct {
560 marker: Block.Data.ListMarker,
561 number: u30,
562 continuation_indent: usize,
563 rest: []const u8,
564};
565
566fn startListItem(unindented_line: []const u8) ?ListItemStart {
567 if (mem.startsWith(u8, unindented_line, "- ")) {
568 return .{
569 .marker = .@"-",
570 .number = undefined,
571 .continuation_indent = 2,
572 .rest = unindented_line[2..],
573 };
574 } else if (mem.startsWith(u8, unindented_line, "* ")) {
575 return .{
576 .marker = .@"*",
577 .number = undefined,
578 .continuation_indent = 2,
579 .rest = unindented_line[2..],
580 };
581 } else if (mem.startsWith(u8, unindented_line, "+ ")) {
582 return .{
583 .marker = .@"+",
584 .number = undefined,
585 .continuation_indent = 2,
586 .rest = unindented_line[2..],
587 };
588 }
589
590 const number_end = mem.indexOfNone(u8, unindented_line, "0123456789") orelse return null;
591 const after_number = unindented_line[number_end..];
592 const marker: Block.Data.ListMarker = if (mem.startsWith(u8, after_number, ". "))
593 .number_dot
594 else if (mem.startsWith(u8, after_number, ") "))
595 .number_paren
596 else
597 return null;
598 const number = std.fmt.parseInt(u30, unindented_line[0..number_end], 10) catch return null;
599 if (number > 999_999_999) return null;
600 return .{
601 .marker = marker,
602 .number = number,
603 .continuation_indent = number_end + 2,
604 .rest = after_number[2..],
605 };
606}
607
608const TableRowStart = struct {
609 cells: std.BoundedArray([]const u8, max_table_columns),
610};
611
612fn startTableRow(unindented_line: []const u8) ?TableRowStart {
613 if (!mem.startsWith(u8, unindented_line, "|") or
614 mem.endsWith(u8, unindented_line, "\\|") or
615 !mem.endsWith(u8, unindented_line, "|")) return null;
616
617 var cells: std.BoundedArray([]const u8, max_table_columns) = .{};
618 const table_row_content = unindented_line[1 .. unindented_line.len - 1];
619 var cell_start: usize = 0;
620 var i: usize = 0;
621 while (i < table_row_content.len) : (i += 1) {
622 switch (table_row_content[i]) {
623 '\\' => i += 1,
624 '|' => {
625 cells.append(table_row_content[cell_start..i]) catch return null;
626 cell_start = i + 1;
627 },
628 '`' => {
629 // Ignoring pipes in code spans allows table cells to contain
630 // code using ||, for example.
631 const open_start = i;
632 i = mem.indexOfNonePos(u8, table_row_content, i, "`") orelse return null;
633 const open_len = i - open_start;
634 while (mem.indexOfScalarPos(u8, table_row_content, i, '`')) |close_start| {
635 i = mem.indexOfNonePos(u8, table_row_content, close_start, "`") orelse return null;
636 const close_len = i - close_start;
637 if (close_len == open_len) break;
638 } else return null;
639 },
640 else => {},
641 }
642 }
643 cells.append(table_row_content[cell_start..]) catch return null;
644
645 return .{ .cells = cells };
646}
647
648fn parseTableHeaderDelimiter(
649 row_cells: std.BoundedArray([]const u8, max_table_columns),
650) ?std.BoundedArray(Node.TableCellAlignment, max_table_columns) {
651 var alignments: std.BoundedArray(Node.TableCellAlignment, max_table_columns) = .{};
652 for (row_cells.slice()) |content| {
653 const alignment = parseTableHeaderDelimiterCell(content) orelse return null;
654 alignments.appendAssumeCapacity(alignment);
655 }
656 return alignments;
657}
658
659fn parseTableHeaderDelimiterCell(content: []const u8) ?Node.TableCellAlignment {
660 var state: enum {
661 before_rule,
662 after_left_anchor,
663 in_rule,
664 after_right_anchor,
665 after_rule,
666 } = .before_rule;
667 var left_anchor = false;
668 var right_anchor = false;
669 for (content) |c| {
670 switch (state) {
671 .before_rule => switch (c) {
672 ' ' => {},
673 ':' => {
674 left_anchor = true;
675 state = .after_left_anchor;
676 },
677 '-' => state = .in_rule,
678 else => return null,
679 },
680 .after_left_anchor => switch (c) {
681 '-' => state = .in_rule,
682 else => return null,
683 },
684 .in_rule => switch (c) {
685 '-' => {},
686 ':' => {
687 right_anchor = true;
688 state = .after_right_anchor;
689 },
690 ' ' => state = .after_rule,
691 else => return null,
692 },
693 .after_right_anchor => switch (c) {
694 ' ' => state = .after_rule,
695 else => return null,
696 },
697 .after_rule => switch (c) {
698 ' ' => {},
699 else => return null,
700 },
701 }
702 }
703
704 switch (state) {
705 .before_rule,
706 .after_left_anchor,
707 => return null,
708
709 .in_rule,
710 .after_right_anchor,
711 .after_rule,
712 => {},
713 }
714
715 return if (left_anchor and right_anchor)
716 .center
717 else if (left_anchor)
718 .left
719 else if (right_anchor)
720 .right
721 else
722 .unset;
723}
724
725test parseTableHeaderDelimiterCell {
726 try expectEqual(null, parseTableHeaderDelimiterCell(""));
727 try expectEqual(null, parseTableHeaderDelimiterCell(" "));
728 try expectEqual(.unset, parseTableHeaderDelimiterCell("-"));
729 try expectEqual(.unset, parseTableHeaderDelimiterCell(" - "));
730 try expectEqual(.unset, parseTableHeaderDelimiterCell("----"));
731 try expectEqual(.unset, parseTableHeaderDelimiterCell(" ---- "));
732 try expectEqual(null, parseTableHeaderDelimiterCell(":"));
733 try expectEqual(null, parseTableHeaderDelimiterCell("::"));
734 try expectEqual(.left, parseTableHeaderDelimiterCell(":-"));
735 try expectEqual(.left, parseTableHeaderDelimiterCell(" :----"));
736 try expectEqual(.center, parseTableHeaderDelimiterCell(":-:"));
737 try expectEqual(.center, parseTableHeaderDelimiterCell(":----:"));
738 try expectEqual(.center, parseTableHeaderDelimiterCell(" :----: "));
739 try expectEqual(.right, parseTableHeaderDelimiterCell("-:"));
740 try expectEqual(.right, parseTableHeaderDelimiterCell("----:"));
741 try expectEqual(.right, parseTableHeaderDelimiterCell(" ----: "));
742}
743
744const HeadingStart = struct {
745 level: u3,
746 rest: []const u8,
747};
748
749fn startHeading(unindented_line: []const u8) ?HeadingStart {
750 var level: u3 = 0;
751 return for (unindented_line, 0..) |c, i| {
752 switch (c) {
753 '#' => {
754 if (level == 6) break null;
755 level += 1;
756 },
757 ' ' => {
758 // We must have seen at least one # by this point, since
759 // unindented_line has no leading spaces.
760 assert(level > 0);
761 break .{
762 .level = level,
763 .rest = unindented_line[i + 1 ..],
764 };
765 },
766 else => break null,
767 }
768 } else null;
769}
770
771const CodeBlockStart = struct {
772 tag: StringIndex,
773 fence_len: usize,
774};
775
776fn startCodeBlock(p: *Parser, unindented_line: []const u8) !?CodeBlockStart {
777 var fence_len: usize = 0;
778 const tag_bytes = for (unindented_line, 0..) |c, i| {
779 switch (c) {
780 '`' => fence_len += 1,
781 else => break unindented_line[i..],
782 }
783 } else "";
784 // Code block tags may not contain backticks, since that would create
785 // potential confusion with inline code spans.
786 if (fence_len < 3 or mem.indexOfScalar(u8, tag_bytes, '`') != null) return null;
787 return .{
788 .tag = try p.addString(mem.trim(u8, tag_bytes, " ")),
789 .fence_len = fence_len,
790 };
791}
792
793fn startBlockquote(unindented_line: []const u8) ?[]const u8 {
794 return if (mem.startsWith(u8, unindented_line, ">"))
795 unindented_line[1..]
796 else
797 null;
798}
799
800fn isThematicBreak(line: []const u8) bool {
801 var char: ?u8 = null;
802 var count: usize = 0;
803 for (line) |c| {
804 switch (c) {
805 ' ' => {},
806 '-', '_', '*' => {
807 if (char != null and c != char.?) return false;
808 char = c;
809 count += 1;
810 },
811 else => return false,
812 }
813 }
814 return count >= 3;
815}
816
817fn closeLastBlock(p: *Parser) !void {
818 const b = p.pending_blocks.pop();
819 const node = switch (b.tag) {
820 .list => list: {
821 assert(b.string_start == p.scratch_string.items.len);
822
823 // Although tightness is parsed as a property of the list, it is
824 // stored at the list item level to make it possible to render each
825 // node without any context from its parents.
826 const list_items = p.scratch_extra.items[b.extra_start..];
827 const node_datas = p.nodes.items(.data);
828 if (!b.data.list.tight) {
829 for (list_items) |list_item| {
830 node_datas[list_item].list_item.tight = false;
831 }
832 }
833
834 const children = try p.addExtraChildren(@ptrCast(list_items));
835 break :list try p.addNode(.{
836 .tag = .list,
837 .data = .{ .list = .{
838 .start = switch (b.data.list.marker) {
839 .number_dot, .number_paren => @enumFromInt(b.data.list.start),
840 .@"-", .@"*", .@"+" => .unordered,
841 },
842 .children = children,
843 } },
844 });
845 },
846 .list_item => list_item: {
847 assert(b.string_start == p.scratch_string.items.len);
848 const children = try p.addExtraChildren(@ptrCast(p.scratch_extra.items[b.extra_start..]));
849 break :list_item try p.addNode(.{
850 .tag = .list_item,
851 .data = .{ .list_item = .{
852 .tight = true,
853 .children = children,
854 } },
855 });
856 },
857 .table => table: {
858 assert(b.string_start == p.scratch_string.items.len);
859 const children = try p.addExtraChildren(@ptrCast(p.scratch_extra.items[b.extra_start..]));
860 break :table try p.addNode(.{
861 .tag = .table,
862 .data = .{ .container = .{
863 .children = children,
864 } },
865 });
866 },
867 .table_row => table_row: {
868 assert(b.string_start == p.scratch_string.items.len);
869 const children = try p.addExtraChildren(@ptrCast(p.scratch_extra.items[b.extra_start..]));
870 break :table_row try p.addNode(.{
871 .tag = .table_row,
872 .data = .{ .container = .{
873 .children = children,
874 } },
875 });
876 },
877 .heading => heading: {
878 const children = try p.parseInlines(p.scratch_string.items[b.string_start..]);
879 break :heading try p.addNode(.{
880 .tag = .heading,
881 .data = .{ .heading = .{
882 .level = b.data.heading.level,
883 .children = children,
884 } },
885 });
886 },
887 .code_block => code_block: {
888 const content = try p.addString(p.scratch_string.items[b.string_start..]);
889 break :code_block try p.addNode(.{
890 .tag = .code_block,
891 .data = .{ .code_block = .{
892 .tag = b.data.code_block.tag,
893 .content = content,
894 } },
895 });
896 },
897 .blockquote => blockquote: {
898 assert(b.string_start == p.scratch_string.items.len);
899 const children = try p.addExtraChildren(@ptrCast(p.scratch_extra.items[b.extra_start..]));
900 break :blockquote try p.addNode(.{
901 .tag = .blockquote,
902 .data = .{ .container = .{
903 .children = children,
904 } },
905 });
906 },
907 .paragraph => paragraph: {
908 const children = try p.parseInlines(p.scratch_string.items[b.string_start..]);
909 break :paragraph try p.addNode(.{
910 .tag = .paragraph,
911 .data = .{ .container = .{
912 .children = children,
913 } },
914 });
915 },
916 .thematic_break => try p.addNode(.{
917 .tag = .thematic_break,
918 .data = .{ .none = {} },
919 }),
920 };
921 p.scratch_string.items.len = b.string_start;
922 p.scratch_extra.items.len = b.extra_start;
923 try p.addScratchExtraNode(node);
924}
925
926const InlineParser = struct {
927 parent: *Parser,
928 content: []const u8,
929 pos: usize = 0,
930 pending_inlines: std.ArrayListUnmanaged(PendingInline) = .{},
931 completed_inlines: std.ArrayListUnmanaged(CompletedInline) = .{},
932
933 const PendingInline = struct {
934 tag: Tag,
935 data: Data,
936 start: usize,
937
938 const Tag = enum {
939 /// Data is `emphasis`.
940 emphasis,
941 /// Data is `none`.
942 link,
943 /// Data is `none`.
944 image,
945 };
946
947 const Data = union {
948 none: void,
949 emphasis: struct {
950 underscore: bool,
951 run_len: usize,
952 },
953 };
954 };
955
956 const CompletedInline = struct {
957 node: Node.Index,
958 start: usize,
959 len: usize,
960 };
961
962 fn deinit(ip: *InlineParser) void {
963 ip.pending_inlines.deinit(ip.parent.allocator);
964 ip.completed_inlines.deinit(ip.parent.allocator);
965 }
966
967 /// Parses all of `ip.content`, returning the children of the node
968 /// containing the inline content.
969 fn parse(ip: *InlineParser) Allocator.Error!ExtraIndex {
970 while (ip.pos < ip.content.len) : (ip.pos += 1) {
971 switch (ip.content[ip.pos]) {
972 '\\' => ip.pos += 1,
973 '[' => try ip.pending_inlines.append(ip.parent.allocator, .{
974 .tag = .link,
975 .data = .{ .none = {} },
976 .start = ip.pos,
977 }),
978 '!' => if (ip.pos + 1 < ip.content.len and ip.content[ip.pos + 1] == '[') {
979 try ip.pending_inlines.append(ip.parent.allocator, .{
980 .tag = .image,
981 .data = .{ .none = {} },
982 .start = ip.pos,
983 });
984 ip.pos += 1;
985 },
986 ']' => try ip.parseLink(),
987 '*', '_' => try ip.parseEmphasis(),
988 '`' => try ip.parseCodeSpan(),
989 else => {},
990 }
991 }
992
993 const children = try ip.encodeChildren(0, ip.content.len);
994 // There may be pending inlines after parsing (e.g. unclosed emphasis
995 // runs), but there must not be any completed inlines, since those
996 // should all be part of `children`.
997 assert(ip.completed_inlines.items.len == 0);
998 return children;
999 }
1000
1001 /// Parses a link, starting at the `]` at the end of the link text. `ip.pos`
1002 /// is left at the closing `)` of the link target or at the closing `]` if
1003 /// there is none.
1004 fn parseLink(ip: *InlineParser) !void {
1005 var i = ip.pending_inlines.items.len;
1006 while (i > 0) {
1007 i -= 1;
1008 if (ip.pending_inlines.items[i].tag == .link or
1009 ip.pending_inlines.items[i].tag == .image) break;
1010 } else return;
1011 const opener = ip.pending_inlines.items[i];
1012 ip.pending_inlines.shrinkRetainingCapacity(i);
1013 const text_start = switch (opener.tag) {
1014 .link => opener.start + 1,
1015 .image => opener.start + 2,
1016 else => unreachable,
1017 };
1018
1019 if (ip.pos + 1 >= ip.content.len or ip.content[ip.pos + 1] != '(') return;
1020 const text_end = ip.pos;
1021
1022 const target_start = text_end + 2;
1023 var target_end = target_start;
1024 var nesting_level: usize = 1;
1025 while (target_end < ip.content.len) : (target_end += 1) {
1026 switch (ip.content[target_end]) {
1027 '\\' => target_end += 1,
1028 '(' => nesting_level += 1,
1029 ')' => {
1030 if (nesting_level == 1) break;
1031 nesting_level -= 1;
1032 },
1033 else => {},
1034 }
1035 } else return;
1036 ip.pos = target_end;
1037
1038 const children = try ip.encodeChildren(text_start, text_end);
1039 const target = try ip.encodeLinkTarget(target_start, target_end);
1040
1041 const link = try ip.parent.addNode(.{
1042 .tag = switch (opener.tag) {
1043 .link => .link,
1044 .image => .image,
1045 else => unreachable,
1046 },
1047 .data = .{ .link = .{
1048 .target = target,
1049 .children = children,
1050 } },
1051 });
1052 try ip.completed_inlines.append(ip.parent.allocator, .{
1053 .node = link,
1054 .start = opener.start,
1055 .len = ip.pos - opener.start + 1,
1056 });
1057 }
1058
1059 fn encodeLinkTarget(ip: *InlineParser, start: usize, end: usize) !StringIndex {
1060 // For efficiency, we can encode directly into string_bytes rather than
1061 // creating a temporary string and then encoding it, since this process
1062 // is entirely linear.
1063 const string_top = ip.parent.string_bytes.items.len;
1064 errdefer ip.parent.string_bytes.shrinkRetainingCapacity(string_top);
1065
1066 var text_iter: TextIterator = .{ .content = ip.content[start..end] };
1067 while (text_iter.next()) |content| {
1068 switch (content) {
1069 .char => |c| try ip.parent.string_bytes.append(ip.parent.allocator, c),
1070 .text => |s| try ip.parent.string_bytes.appendSlice(ip.parent.allocator, s),
1071 .line_break => try ip.parent.string_bytes.appendSlice(ip.parent.allocator, "\\\n"),
1072 }
1073 }
1074 try ip.parent.string_bytes.append(ip.parent.allocator, 0);
1075 return @enumFromInt(string_top);
1076 }
1077
1078 /// Parses emphasis, starting at the beginning of a run of `*` or `_`
1079 /// characters. `ip.pos` is left at the last character in the run after
1080 /// parsing.
1081 fn parseEmphasis(ip: *InlineParser) !void {
1082 const char = ip.content[ip.pos];
1083 var start = ip.pos;
1084 while (ip.pos + 1 < ip.content.len and ip.content[ip.pos + 1] == char) {
1085 ip.pos += 1;
1086 }
1087 var len = ip.pos - start + 1;
1088 const underscore = char == '_';
1089 const space_before = start == 0 or isWhitespace(ip.content[start - 1]);
1090 const space_after = start + len == ip.content.len or isWhitespace(ip.content[start + len]);
1091 const punct_before = start == 0 or isPunctuation(ip.content[start - 1]);
1092 const punct_after = start + len == ip.content.len or isPunctuation(ip.content[start + len]);
1093 // The rules for when emphasis may be closed or opened are stricter for
1094 // underscores to avoid inappropriately interpreting snake_case words as
1095 // containing emphasis markers.
1096 const can_open = if (underscore)
1097 !space_after and (space_before or punct_before)
1098 else
1099 !space_after;
1100 const can_close = if (underscore)
1101 !space_before and (space_after or punct_after)
1102 else
1103 !space_before;
1104
1105 if (can_close and ip.pending_inlines.items.len > 0) {
1106 var i = ip.pending_inlines.items.len;
1107 while (i > 0 and len > 0) {
1108 i -= 1;
1109 const opener = &ip.pending_inlines.items[i];
1110 if (opener.tag != .emphasis or
1111 opener.data.emphasis.underscore != underscore) continue;
1112
1113 const close_len = @min(opener.data.emphasis.run_len, len);
1114 const opener_end = opener.start + opener.data.emphasis.run_len;
1115
1116 const emphasis = try ip.encodeEmphasis(opener_end, start, close_len);
1117 const emphasis_start = opener_end - close_len;
1118 const emphasis_len = start - emphasis_start + close_len;
1119 try ip.completed_inlines.append(ip.parent.allocator, .{
1120 .node = emphasis,
1121 .start = emphasis_start,
1122 .len = emphasis_len,
1123 });
1124
1125 // There may still be other openers further down in the
1126 // stack to close, or part of this run might serve as an
1127 // opener itself.
1128 start += close_len;
1129 len -= close_len;
1130
1131 // Remove any pending inlines above this on the stack, since
1132 // closing this emphasis will prevent them from being closed.
1133 // Additionally, if this opener is completely consumed by
1134 // being closed, it can be removed.
1135 opener.data.emphasis.run_len -= close_len;
1136 if (opener.data.emphasis.run_len == 0) {
1137 ip.pending_inlines.shrinkRetainingCapacity(i);
1138 } else {
1139 ip.pending_inlines.shrinkRetainingCapacity(i + 1);
1140 }
1141 }
1142 }
1143
1144 if (can_open and len > 0) {
1145 try ip.pending_inlines.append(ip.parent.allocator, .{
1146 .tag = .emphasis,
1147 .data = .{ .emphasis = .{
1148 .underscore = underscore,
1149 .run_len = len,
1150 } },
1151 .start = start,
1152 });
1153 }
1154 }
1155
1156 /// Encodes emphasis specified by a run of `run_len` emphasis characters,
1157 /// with `start..end` being the range of content contained within the
1158 /// emphasis.
1159 fn encodeEmphasis(ip: *InlineParser, start: usize, end: usize, run_len: usize) !Node.Index {
1160 const children = try ip.encodeChildren(start, end);
1161 var inner = switch (run_len % 3) {
1162 1 => try ip.parent.addNode(.{
1163 .tag = .emphasis,
1164 .data = .{ .container = .{
1165 .children = children,
1166 } },
1167 }),
1168 2 => try ip.parent.addNode(.{
1169 .tag = .strong,
1170 .data = .{ .container = .{
1171 .children = children,
1172 } },
1173 }),
1174 0 => strong_emphasis: {
1175 const strong = try ip.parent.addNode(.{
1176 .tag = .strong,
1177 .data = .{ .container = .{
1178 .children = children,
1179 } },
1180 });
1181 break :strong_emphasis try ip.parent.addNode(.{
1182 .tag = .emphasis,
1183 .data = .{ .container = .{
1184 .children = try ip.parent.addExtraChildren(&.{strong}),
1185 } },
1186 });
1187 },
1188 else => unreachable,
1189 };
1190
1191 var run_left = run_len;
1192 while (run_left > 3) : (run_left -= 3) {
1193 const strong = try ip.parent.addNode(.{
1194 .tag = .strong,
1195 .data = .{ .container = .{
1196 .children = try ip.parent.addExtraChildren(&.{inner}),
1197 } },
1198 });
1199 inner = try ip.parent.addNode(.{
1200 .tag = .emphasis,
1201 .data = .{ .container = .{
1202 .children = try ip.parent.addExtraChildren(&.{strong}),
1203 } },
1204 });
1205 }
1206
1207 return inner;
1208 }
1209
1210 /// Parses a code span, starting at the beginning of the opening backtick
1211 /// run. `ip.pos` is left at the last character in the closing run after
1212 /// parsing.
1213 fn parseCodeSpan(ip: *InlineParser) !void {
1214 const opener_start = ip.pos;
1215 ip.pos = mem.indexOfNonePos(u8, ip.content, ip.pos, "`") orelse ip.content.len;
1216 const opener_len = ip.pos - opener_start;
1217
1218 const start = ip.pos;
1219 const end = while (mem.indexOfScalarPos(u8, ip.content, ip.pos, '`')) |closer_start| {
1220 ip.pos = mem.indexOfNonePos(u8, ip.content, closer_start, "`") orelse ip.content.len;
1221 const closer_len = ip.pos - closer_start;
1222
1223 if (closer_len == opener_len) break closer_start;
1224 } else unterminated: {
1225 ip.pos = ip.content.len;
1226 break :unterminated ip.content.len;
1227 };
1228
1229 var content = if (start < ip.content.len)
1230 ip.content[start..end]
1231 else
1232 "";
1233 // This single space removal rule allows code spans to be written which
1234 // start or end with backticks.
1235 if (mem.startsWith(u8, content, " `")) content = content[1..];
1236 if (mem.endsWith(u8, content, "` ")) content = content[0 .. content.len - 1];
1237
1238 const text = try ip.parent.addNode(.{
1239 .tag = .code_span,
1240 .data = .{ .text = .{
1241 .content = try ip.parent.addString(content),
1242 } },
1243 });
1244 try ip.completed_inlines.append(ip.parent.allocator, .{
1245 .node = text,
1246 .start = opener_start,
1247 .len = ip.pos - opener_start,
1248 });
1249 // Ensure ip.pos is pointing at the last character of the
1250 // closer, not after it.
1251 ip.pos -= 1;
1252 }
1253
1254 /// Encodes children parsed in the content range `start..end`. The children
1255 /// will be text nodes and any completed inlines within the range.
1256 fn encodeChildren(ip: *InlineParser, start: usize, end: usize) !ExtraIndex {
1257 const scratch_extra_top = ip.parent.scratch_extra.items.len;
1258 defer ip.parent.scratch_extra.shrinkRetainingCapacity(scratch_extra_top);
1259
1260 var child_index = ip.completed_inlines.items.len;
1261 while (child_index > 0 and ip.completed_inlines.items[child_index - 1].start >= start) {
1262 child_index -= 1;
1263 }
1264 const start_child_index = child_index;
1265
1266 var pos = start;
1267 while (child_index < ip.completed_inlines.items.len) : (child_index += 1) {
1268 const child_inline = ip.completed_inlines.items[child_index];
1269 // Completed inlines must be strictly nested within the encodable
1270 // content.
1271 assert(child_inline.start >= pos and child_inline.start + child_inline.len <= end);
1272
1273 if (child_inline.start > pos) {
1274 try ip.encodeTextNode(pos, child_inline.start);
1275 }
1276 try ip.parent.addScratchExtraNode(child_inline.node);
1277
1278 pos = child_inline.start + child_inline.len;
1279 }
1280 ip.completed_inlines.shrinkRetainingCapacity(start_child_index);
1281
1282 if (pos < end) {
1283 try ip.encodeTextNode(pos, end);
1284 }
1285
1286 const children = ip.parent.scratch_extra.items[scratch_extra_top..];
1287 return try ip.parent.addExtraChildren(@ptrCast(children));
1288 }
1289
1290 /// Encodes textual content `ip.content[start..end]` to `scratch_extra`. The
1291 /// encoded content may include both `text` and `line_break` nodes.
1292 fn encodeTextNode(ip: *InlineParser, start: usize, end: usize) !void {
1293 // For efficiency, we can encode directly into string_bytes rather than
1294 // creating a temporary string and then encoding it, since this process
1295 // is entirely linear.
1296 const string_top = ip.parent.string_bytes.items.len;
1297 errdefer ip.parent.string_bytes.shrinkRetainingCapacity(string_top);
1298
1299 var string_start = string_top;
1300 var text_iter: TextIterator = .{ .content = ip.content[start..end] };
1301 while (text_iter.next()) |content| {
1302 switch (content) {
1303 .char => |c| try ip.parent.string_bytes.append(ip.parent.allocator, c),
1304 .text => |s| try ip.parent.string_bytes.appendSlice(ip.parent.allocator, s),
1305 .line_break => {
1306 if (ip.parent.string_bytes.items.len > string_start) {
1307 try ip.parent.string_bytes.append(ip.parent.allocator, 0);
1308 try ip.parent.addScratchExtraNode(try ip.parent.addNode(.{
1309 .tag = .text,
1310 .data = .{ .text = .{
1311 .content = @enumFromInt(string_start),
1312 } },
1313 }));
1314 string_start = ip.parent.string_bytes.items.len;
1315 }
1316 try ip.parent.addScratchExtraNode(try ip.parent.addNode(.{
1317 .tag = .line_break,
1318 .data = .{ .none = {} },
1319 }));
1320 },
1321 }
1322 }
1323 if (ip.parent.string_bytes.items.len > string_start) {
1324 try ip.parent.string_bytes.append(ip.parent.allocator, 0);
1325 try ip.parent.addScratchExtraNode(try ip.parent.addNode(.{
1326 .tag = .text,
1327 .data = .{ .text = .{
1328 .content = @enumFromInt(string_start),
1329 } },
1330 }));
1331 }
1332 }
1333
1334 /// An iterator over parts of textual content, handling unescaping of
1335 /// escaped characters and line breaks.
1336 const TextIterator = struct {
1337 content: []const u8,
1338 pos: usize = 0,
1339
1340 const Content = union(enum) {
1341 char: u8,
1342 text: []const u8,
1343 line_break,
1344 };
1345
1346 const replacement = "\u{FFFD}";
1347
1348 fn next(iter: *TextIterator) ?Content {
1349 if (iter.pos >= iter.content.len) return null;
1350 if (iter.content[iter.pos] == '\\') {
1351 iter.pos += 1;
1352 if (iter.pos == iter.content.len) {
1353 return .{ .char = '\\' };
1354 } else if (iter.content[iter.pos] == '\n') {
1355 iter.pos += 1;
1356 return .line_break;
1357 } else if (isPunctuation(iter.content[iter.pos])) {
1358 const c = iter.content[iter.pos];
1359 iter.pos += 1;
1360 return .{ .char = c };
1361 } else {
1362 return .{ .char = '\\' };
1363 }
1364 }
1365 return iter.nextCodepoint();
1366 }
1367
1368 fn nextCodepoint(iter: *TextIterator) ?Content {
1369 switch (iter.content[iter.pos]) {
1370 0 => {
1371 iter.pos += 1;
1372 return .{ .text = replacement };
1373 },
1374 1...127 => |c| {
1375 iter.pos += 1;
1376 return .{ .char = c };
1377 },
1378 else => |b| {
1379 const cp_len = std.unicode.utf8ByteSequenceLength(b) catch {
1380 iter.pos += 1;
1381 return .{ .text = replacement };
1382 };
1383 const is_valid = iter.pos + cp_len < iter.content.len and
1384 std.unicode.utf8ValidateSlice(iter.content[iter.pos..][0..cp_len]);
1385 const cp_encoded = if (is_valid)
1386 iter.content[iter.pos..][0..cp_len]
1387 else
1388 replacement;
1389 iter.pos += cp_len;
1390 return .{ .text = cp_encoded };
1391 },
1392 }
1393 }
1394 };
1395};
1396
1397fn parseInlines(p: *Parser, content: []const u8) !ExtraIndex {
1398 var ip: InlineParser = .{
1399 .parent = p,
1400 .content = mem.trim(u8, content, " \t\n"),
1401 };
1402 defer ip.deinit();
1403 return try ip.parse();
1404}
1405
1406pub fn extraData(p: Parser, comptime T: type, index: ExtraIndex) ExtraData(T) {
1407 const fields = @typeInfo(T).Struct.fields;
1408 var i: usize = @intFromEnum(index);
1409 var result: T = undefined;
1410 inline for (fields) |field| {
1411 @field(result, field.name) = switch (field.type) {
1412 u32 => p.extra.items[i],
1413 else => @compileError("bad field type"),
1414 };
1415 i += 1;
1416 }
1417 return .{ .data = result, .end = i };
1418}
1419
1420pub fn extraChildren(p: Parser, index: ExtraIndex) []const Node.Index {
1421 const children = p.extraData(Node.Children, index);
1422 return @ptrCast(p.extra.items[children.end..][0..children.data.len]);
1423}
1424
1425fn addNode(p: *Parser, node: Node) !Node.Index {
1426 const index: Node.Index = @enumFromInt(@as(u32, @intCast(p.nodes.len)));
1427 try p.nodes.append(p.allocator, node);
1428 return index;
1429}
1430
1431fn addString(p: *Parser, s: []const u8) !StringIndex {
1432 if (s.len == 0) return .empty;
1433
1434 const index: StringIndex = @enumFromInt(@as(u32, @intCast(p.string_bytes.items.len)));
1435 try p.string_bytes.ensureUnusedCapacity(p.allocator, s.len + 1);
1436 p.string_bytes.appendSliceAssumeCapacity(s);
1437 p.string_bytes.appendAssumeCapacity(0);
1438 return index;
1439}
1440
1441fn addExtraChildren(p: *Parser, nodes: []const Node.Index) !ExtraIndex {
1442 const index: ExtraIndex = @enumFromInt(@as(u32, @intCast(p.extra.items.len)));
1443 try p.extra.ensureUnusedCapacity(p.allocator, nodes.len + 1);
1444 p.extra.appendAssumeCapacity(@intCast(nodes.len));
1445 p.extra.appendSliceAssumeCapacity(@ptrCast(nodes));
1446 return index;
1447}
1448
1449fn addScratchExtraNode(p: *Parser, node: Node.Index) !void {
1450 try p.scratch_extra.append(p.allocator, @intFromEnum(node));
1451}
1452
1453fn addScratchStringLine(p: *Parser, line: []const u8) !void {
1454 try p.scratch_string.ensureUnusedCapacity(p.allocator, line.len + 1);
1455 p.scratch_string.appendSliceAssumeCapacity(line);
1456 p.scratch_string.appendAssumeCapacity('\n');
1457}
1458
1459fn isBlank(line: []const u8) bool {
1460 return mem.indexOfNone(u8, line, " \t") == null;
1461}
1462
1463fn isPunctuation(c: u8) bool {
1464 return switch (c) {
1465 '!',
1466 '"',
1467 '#',
1468 '$',
1469 '%',
1470 '&',
1471 '\'',
1472 '(',
1473 ')',
1474 '*',
1475 '+',
1476 ',',
1477 '-',
1478 '.',
1479 '/',
1480 ':',
1481 ';',
1482 '<',
1483 '=',
1484 '>',
1485 '?',
1486 '@',
1487 '[',
1488 '\\',
1489 ']',
1490 '^',
1491 '_',
1492 '`',
1493 '{',
1494 '|',
1495 '}',
1496 '~',
1497 => true,
1498 else => false,
1499 };
1500}
lib/docs/wasm/markdown/renderer.zig created+254
......@@ -0,0 +1,254 @@
1const std = @import("std");
2const Document = @import("Document.zig");
3const Node = Document.Node;
4
5/// A Markdown document renderer.
6///
7/// Each concrete `Renderer` type has a `renderDefault` function, with the
8/// intention that custom `renderFn` implementations can call `renderDefault`
9/// for node types for which they require no special rendering.
10pub fn Renderer(comptime Writer: type, comptime Context: type) type {
11 return struct {
12 renderFn: *const fn (
13 r: Self,
14 doc: Document,
15 node: Node.Index,
16 writer: Writer,
17 ) Writer.Error!void = renderDefault,
18 context: Context,
19
20 const Self = @This();
21
22 pub fn render(r: Self, doc: Document, writer: Writer) Writer.Error!void {
23 try r.renderFn(r, doc, .root, writer);
24 }
25
26 pub fn renderDefault(
27 r: Self,
28 doc: Document,
29 node: Node.Index,
30 writer: Writer,
31 ) Writer.Error!void {
32 const data = doc.nodes.items(.data)[@intFromEnum(node)];
33 switch (doc.nodes.items(.tag)[@intFromEnum(node)]) {
34 .root => {
35 for (doc.extraChildren(data.container.children)) |child| {
36 try r.renderFn(r, doc, child, writer);
37 }
38 },
39 .list => {
40 if (data.list.start.asNumber()) |start| {
41 if (start == 1) {
42 try writer.writeAll("<ol>\n");
43 } else {
44 try writer.print("<ol start=\"{}\">\n", .{start});
45 }
46 } else {
47 try writer.writeAll("<ul>\n");
48 }
49 for (doc.extraChildren(data.list.children)) |child| {
50 try r.renderFn(r, doc, child, writer);
51 }
52 if (data.list.start.asNumber() != null) {
53 try writer.writeAll("</ol>\n");
54 } else {
55 try writer.writeAll("</ul>\n");
56 }
57 },
58 .list_item => {
59 try writer.writeAll("<li>");
60 for (doc.extraChildren(data.list_item.children)) |child| {
61 if (data.list_item.tight and doc.nodes.items(.tag)[@intFromEnum(child)] == .paragraph) {
62 const para_data = doc.nodes.items(.data)[@intFromEnum(child)];
63 for (doc.extraChildren(para_data.container.children)) |para_child| {
64 try r.renderFn(r, doc, para_child, writer);
65 }
66 } else {
67 try r.renderFn(r, doc, child, writer);
68 }
69 }
70 try writer.writeAll("</li>\n");
71 },
72 .table => {
73 try writer.writeAll("<table>\n");
74 for (doc.extraChildren(data.container.children)) |child| {
75 try r.renderFn(r, doc, child, writer);
76 }
77 try writer.writeAll("</table>\n");
78 },
79 .table_row => {
80 try writer.writeAll("<tr>\n");
81 for (doc.extraChildren(data.container.children)) |child| {
82 try r.renderFn(r, doc, child, writer);
83 }
84 try writer.writeAll("</tr>\n");
85 },
86 .table_cell => {
87 if (data.table_cell.info.header) {
88 try writer.writeAll("<th");
89 } else {
90 try writer.writeAll("<td");
91 }
92 switch (data.table_cell.info.alignment) {
93 .unset => try writer.writeAll(">"),
94 else => |a| try writer.print(" style=\"text-align: {s}\">", .{@tagName(a)}),
95 }
96
97 for (doc.extraChildren(data.table_cell.children)) |child| {
98 try r.renderFn(r, doc, child, writer);
99 }
100
101 if (data.table_cell.info.header) {
102 try writer.writeAll("</th>\n");
103 } else {
104 try writer.writeAll("</td>\n");
105 }
106 },
107 .heading => {
108 try writer.print("<h{}>", .{data.heading.level});
109 for (doc.extraChildren(data.heading.children)) |child| {
110 try r.renderFn(r, doc, child, writer);
111 }
112 try writer.print("</h{}>\n", .{data.heading.level});
113 },
114 .code_block => {
115 const tag = doc.string(data.code_block.tag);
116 const content = doc.string(data.code_block.content);
117 if (tag.len > 0) {
118 try writer.print("<pre><code class=\"{}\">{}</code></pre>\n", .{ fmtHtml(tag), fmtHtml(content) });
119 } else {
120 try writer.print("<pre><code>{}</code></pre>\n", .{fmtHtml(content)});
121 }
122 },
123 .blockquote => {
124 try writer.writeAll("<blockquote>\n");
125 for (doc.extraChildren(data.container.children)) |child| {
126 try r.renderFn(r, doc, child, writer);
127 }
128 try writer.writeAll("</blockquote>\n");
129 },
130 .paragraph => {
131 try writer.writeAll("<p>");
132 for (doc.extraChildren(data.container.children)) |child| {
133 try r.renderFn(r, doc, child, writer);
134 }
135 try writer.writeAll("</p>\n");
136 },
137 .thematic_break => {
138 try writer.writeAll("<hr />\n");
139 },
140 .link => {
141 const target = doc.string(data.link.target);
142 try writer.print("<a href=\"{}\">", .{fmtHtml(target)});
143 for (doc.extraChildren(data.link.children)) |child| {
144 try r.renderFn(r, doc, child, writer);
145 }
146 try writer.writeAll("</a>");
147 },
148 .image => {
149 const target = doc.string(data.link.target);
150 try writer.print("<img src=\"{}\" alt=\"", .{fmtHtml(target)});
151 for (doc.extraChildren(data.link.children)) |child| {
152 try renderInlineNodeText(doc, child, writer);
153 }
154 try writer.writeAll("\" />");
155 },
156 .strong => {
157 try writer.writeAll("<strong>");
158 for (doc.extraChildren(data.container.children)) |child| {
159 try r.renderFn(r, doc, child, writer);
160 }
161 try writer.writeAll("</strong>");
162 },
163 .emphasis => {
164 try writer.writeAll("<em>");
165 for (doc.extraChildren(data.container.children)) |child| {
166 try r.renderFn(r, doc, child, writer);
167 }
168 try writer.writeAll("</em>");
169 },
170 .code_span => {
171 const content = doc.string(data.text.content);
172 try writer.print("<code>{}</code>", .{fmtHtml(content)});
173 },
174 .text => {
175 const content = doc.string(data.text.content);
176 try writer.print("{}", .{fmtHtml(content)});
177 },
178 .line_break => {
179 try writer.writeAll("<br />\n");
180 },
181 }
182 }
183 };
184}
185
186/// Renders an inline node as plain text. Asserts that the node is an inline and
187/// has no non-inline children.
188pub fn renderInlineNodeText(
189 doc: Document,
190 node: Node.Index,
191 writer: anytype,
192) @TypeOf(writer).Error!void {
193 const data = doc.nodes.items(.data)[@intFromEnum(node)];
194 switch (doc.nodes.items(.tag)[@intFromEnum(node)]) {
195 .root,
196 .list,
197 .list_item,
198 .table,
199 .table_row,
200 .table_cell,
201 .heading,
202 .code_block,
203 .blockquote,
204 .paragraph,
205 .thematic_break,
206 => unreachable, // Blocks
207
208 .link, .image => {
209 for (doc.extraChildren(data.link.children)) |child| {
210 try renderInlineNodeText(doc, child, writer);
211 }
212 },
213 .strong => {
214 for (doc.extraChildren(data.container.children)) |child| {
215 try renderInlineNodeText(doc, child, writer);
216 }
217 },
218 .emphasis => {
219 for (doc.extraChildren(data.container.children)) |child| {
220 try renderInlineNodeText(doc, child, writer);
221 }
222 },
223 .code_span, .text => {
224 const content = doc.string(data.text.content);
225 try writer.print("{}", .{fmtHtml(content)});
226 },
227 .line_break => {
228 try writer.writeAll("\n");
229 },
230 }
231}
232
233pub fn fmtHtml(bytes: []const u8) std.fmt.Formatter(formatHtml) {
234 return .{ .data = bytes };
235}
236
237fn formatHtml(
238 bytes: []const u8,
239 comptime fmt: []const u8,
240 options: std.fmt.FormatOptions,
241 writer: anytype,
242) !void {
243 _ = fmt;
244 _ = options;
245 for (bytes) |b| {
246 switch (b) {
247 '<' => try writer.writeAll("&lt;"),
248 '>' => try writer.writeAll("&gt;"),
249 '&' => try writer.writeAll("&amp;"),
250 '"' => try writer.writeAll("&quot;"),
251 else => try writer.writeByte(b),
252 }
253 }
254}