authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-10 17:53:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-10 18:13:25-07:00
logb13a55db97b14fac0462d2a1cb83be8dcb2eb335
tree393f86a0582e10931b19b7f68d2f06b0d9e19449
parent6b8c7540a876b000e5c53fdbf9336c9fdc8d7556

update autodocs web application to latest

upstream commit 1f921d540e1a8bb40839be30239019c820eb663d after this branch is merged, ziglang/zig becomes the new repository for this code.

8 files changed, 1074 insertions(+), 330 deletions(-)

lib/docs/index.html+57-31
......@@ -12,6 +12,9 @@
1212 .hidden {
1313 display: none;
1414 }
15 table {
16 width: 100%;
17 }
1518 a {
1619 color: #2A6286;
1720 }
......@@ -25,25 +28,38 @@
2528 }
2629 code {
2730 font-family:"Source Code Pro",monospace;
28 font-size:1em;
31 font-size: 0.9em;
2932 }
3033 code a {
3134 color: #000000;
3235 }
33 #listFields > div {
36 #listFields > div, #listParams > div {
3437 margin-bottom: 1em;
3538 }
39 #hdrName a {
40 font-size: 0.7em;
41 padding-left: 1em;
42 }
3643 .fieldDocs {
3744 border: 1px solid #F5F5F5;
3845 border-top: 0px;
3946 padding: 1px 1em;
4047 }
4148
49 #logo {
50 width: 8em;
51 padding: 0.5em 1em;
52 }
4253
4354 #navWrap {
44 float: left;
45 width: 47em;
46 margin-left: 1em;
55 width: -moz-available;
56 width: -webkit-fill-available;
57 width: stretch;
58 margin-left: 11em;
59 }
60
61 #search {
62 width: 100%;
4763 }
4864
4965 nav {
......@@ -102,15 +118,6 @@
102118 color: #000;
103119 }
104120
105 #logo {
106 width: 8em;
107 padding: 0.5em 1em;
108 }
109
110 #search {
111 width: 100%;
112 }
113
114121 #helpDialog {
115122 width: 21em;
116123 height: 21em;
......@@ -154,6 +161,12 @@
154161 font-weight: bold;
155162 }
156163
164 dl > div {
165 padding: 0.5em;
166 border: 1px solid #c0c0c0;
167 margin-top: 0.5em;
168 }
169
157170 td {
158171 vertical-align: top;
159172 margin: 0;
......@@ -163,6 +176,10 @@
163176 overflow-x: hidden;
164177 }
165178
179 ul.columns {
180 column-width: 20em;
181 }
182
166183 .tok-kw {
167184 color: #333;
168185 font-weight: bold;
......@@ -193,18 +210,19 @@
193210 }
194211
195212 @media (prefers-color-scheme: dark) {
196 body{
213 body {
197214 background-color: #111;
198215 color: #bbb;
199216 }
217 pre {
218 background-color: #222;
219 color: #ccc;
220 }
200221 a {
201222 color: #88f;
202223 }
203224 code a {
204 color: #bbb;
205 }
206 pre{
207 background-color:#2A2A2A;
225 color: #ccc;
208226 }
209227 .fieldDocs {
210228 border-color:#2A2A2A;
......@@ -229,6 +247,9 @@
229247 #listSearchResults li.selected a {
230248 color: #fff;
231249 }
250 dl > div {
251 border-color: #373737;
252 }
232253 .tok-kw {
233254 color: #eee;
234255 }
......@@ -242,7 +263,7 @@
242263 color: #aa7;
243264 }
244265 .tok-fn {
245 color: #e33;
266 color: #B1A0F8;
246267 }
247268 .tok-null {
248269 color: #ff8080;
......@@ -258,7 +279,7 @@
258279 </head>
259280 <body>
260281 <nav>
261 <div class="logo">
282 <a class="logo" href="#">
262283 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140">
263284 <g fill="#F7A41D">
264285 <g>
......@@ -297,7 +318,7 @@
297318 </g>
298319 </g>
299320 </svg>
300 </div>
321 </a>
301322 </nav>
302323 <div id="navWrap">
303324 <input type="search" id="search" autocomplete="off" spellcheck="false" placeholder="`s` to search, `?` to see more options">
......@@ -305,11 +326,16 @@
305326 </div>
306327 <section>
307328 <p id="status">Loading...</p>
329 <h1 id="hdrName" class="hidden"><span></span><a href="#">[src]</a></h1>
308330 <div id="fnProto" class="hidden">
309331 <pre><code id="fnProtoCode"></code></pre>
310332 </div>
311 <h1 id="hdrName" class="hidden"></h1>
312333 <div id="tldDocs" class="hidden"></div>
334 <div id="sectParams" class="hidden">
335 <h2>Parameters</h2>
336 <div id="listParams">
337 </div>
338 </div>
313339 <div id="sectFnErrors" class="hidden">
314340 <h2>Errors</h2>
315341 <div id="fnErrorsAnyError">
......@@ -332,12 +358,12 @@
332358 </div>
333359 <div id="sectTypes" class="hidden">
334360 <h2>Types</h2>
335 <ul id="listTypes">
361 <ul id="listTypes" class="columns">
336362 </ul>
337363 </div>
338364 <div id="sectNamespaces" class="hidden">
339365 <h2>Namespaces</h2>
340 <ul id="listNamespaces">
366 <ul id="listNamespaces" class="columns">
341367 </ul>
342368 </div>
343369 <div id="sectGlobalVars" class="hidden">
......@@ -347,11 +373,6 @@
347373 </tbody>
348374 </table>
349375 </div>
350 <div id="sectFns" class="hidden">
351 <h2>Functions</h2>
352 <dl id="listFns">
353 </dl>
354 </div>
355376 <div id="sectValues" class="hidden">
356377 <h2>Values</h2>
357378 <table>
......@@ -359,9 +380,14 @@
359380 </tbody>
360381 </table>
361382 </div>
383 <div id="sectFns" class="hidden">
384 <h2>Functions</h2>
385 <dl id="listFns">
386 </dl>
387 </div>
362388 <div id="sectErrSets" class="hidden">
363389 <h2>Error Sets</h2>
364 <ul id="listErrSets">
390 <ul id="listErrSets" class="columns">
365391 </ul>
366392 </div>
367393 <div id="sectDocTests" class="hidden">
lib/docs/main.js+344-208
......@@ -7,6 +7,8 @@
77 const CAT_global_const = 5;
88 const CAT_alias = 6;
99 const CAT_type = 7;
10 const CAT_type_type = 8;
11 const CAT_type_function = 9;
1012
1113 const domDocTestsCode = document.getElementById("docTestsCode");
1214 const domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");
......@@ -16,6 +18,7 @@
1618 const domHelpModal = document.getElementById("helpDialog");
1719 const domListErrSets = document.getElementById("listErrSets");
1820 const domListFields = document.getElementById("listFields");
21 const domListParams = document.getElementById("listParams");
1922 const domListFnErrors = document.getElementById("listFnErrors");
2023 const domListFns = document.getElementById("listFns");
2124 const domListGlobalVars = document.getElementById("listGlobalVars");
......@@ -29,6 +32,7 @@
2932 const domSectDocTests = document.getElementById("sectDocTests");
3033 const domSectErrSets = document.getElementById("sectErrSets");
3134 const domSectFields = document.getElementById("sectFields");
35 const domSectParams = document.getElementById("sectParams");
3236 const domSectFnErrors = document.getElementById("sectFnErrors");
3337 const domSectFns = document.getElementById("sectFns");
3438 const domSectGlobalVars = document.getElementById("sectGlobalVars");
......@@ -64,8 +68,8 @@
6468 var curSearchIndex = -1;
6569 var imFeelingLucky = false;
6670
67 // names of packages in the same order as wasm
68 const packageList = [];
71 // names of modules in the same order as wasm
72 const moduleList = [];
6973
7074 let wasm_promise = fetch("main.wasm");
7175 let sources_promise = fetch("sources.tar").then(function(response) {
......@@ -99,13 +103,13 @@
99103 wasm_array.set(js_array);
100104 wasm_exports.unpack(ptr, js_array.length);
101105
102 updatePackageList();
106 updateModuleList();
103107
104 window.addEventListener('hashchange', onHashChange, false);
108 window.addEventListener('popstate', onPopState, false);
105109 domSearch.addEventListener('keydown', onSearchKeyDown, false);
106110 domSearch.addEventListener('input', onSearchChange, false);
107111 window.addEventListener('keydown', onWindowKeyDown, false);
108 onHashChange();
112 onHashChange(null);
109113 });
110114 });
111115
......@@ -118,7 +122,7 @@
118122 } else if (curNav.path != null) {
119123 document.title = curNav.path + suffix;
120124 } else {
121 document.title = packageList[0] + suffix; // Home
125 document.title = moduleList[0] + suffix; // Home
122126 }
123127 }
124128
......@@ -130,6 +134,7 @@
130134 domSectErrSets.classList.add("hidden");
131135 domSectDocTests.classList.add("hidden");
132136 domSectFields.classList.add("hidden");
137 domSectParams.classList.add("hidden");
133138 domSectFnErrors.classList.add("hidden");
134139 domSectFns.classList.add("hidden");
135140 domSectGlobalVars.classList.add("hidden");
......@@ -152,7 +157,7 @@
152157 case 0: return renderHome();
153158 case 1:
154159 if (curNav.decl == null) {
155 return render404();
160 return renderNotFound();
156161 } else {
157162 return renderDecl(curNav.decl);
158163 }
......@@ -162,37 +167,51 @@
162167 }
163168
164169 function renderHome() {
165 if (packageList.length == 1) return renderPackage(0);
166
167 domStatus.textContent = "TODO implement renderHome for multiple packages";
168 domStatus.classList.remove("hidden");
170 if (moduleList.length == 0) {
171 domStatus.textContent = "sources.tar contains no modules";
172 domStatus.classList.remove("hidden");
173 return;
174 }
175 return renderModule(0);
169176 }
170177
171 function renderPackage(pkg_index) {
172 const root_decl = wasm_exports.find_package_root(pkg_index);
178 function renderModule(pkg_index) {
179 const root_decl = wasm_exports.find_module_root(pkg_index);
173180 return renderDecl(root_decl);
174181 }
175182
176183 function renderDecl(decl_index) {
177184 const category = wasm_exports.categorize_decl(decl_index, 0);
178185 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);
186 case CAT_namespace:
187 return renderNamespacePage(decl_index);
188 case CAT_global_variable:
189 case CAT_primitive:
190 case CAT_global_const:
191 case CAT_type:
192 case CAT_type_type:
193 return renderGlobal(decl_index);
194 case CAT_function:
195 return renderFunction(decl_index);
196 case CAT_type_function:
197 return renderTypeFunction(decl_index);
198 case CAT_error_set:
199 return renderErrorSetPage(decl_index);
200 case CAT_alias:
201 return renderDecl(wasm_exports.get_aliasee());
202 default:
203 throw new Error("unrecognized category " + category);
188204 }
189205 }
190206
191207 function renderSource(path) {
192208 const decl_index = findFileRoot(path);
193 if (decl_index == null) return render404();
209 if (decl_index == null) return renderNotFound();
194210
195 renderNav(decl_index);
211 renderNavFancy(decl_index, [{
212 name: "[src]",
213 href: location.hash,
214 }]);
196215
197216 domSourceText.innerHTML = declSourceHtml(decl_index);
198217
......@@ -200,7 +219,12 @@
200219 }
201220
202221 function renderDeclHeading(decl_index) {
203 domHdrName.innerText = unwrapString(wasm_exports.decl_category_name(decl_index));
222 curNav.viewSourceHash = "#src/" + unwrapString(wasm_exports.decl_file_path(decl_index));
223
224 const hdrNameSpan = domHdrName.children[0];
225 const srcLink = domHdrName.children[1];
226 hdrNameSpan.innerText = unwrapString(wasm_exports.decl_category_name(decl_index));
227 srcLink.setAttribute('href', curNav.viewSourceHash);
204228 domHdrName.classList.remove("hidden");
205229
206230 renderTopLevelDocs(decl_index);
......@@ -214,8 +238,11 @@
214238 }
215239 }
216240
217 function renderNav(cur_nav_decl) {
218 const list = [];
241 function renderNav(cur_nav_decl, list) {
242 return renderNavFancy(cur_nav_decl, []);
243 }
244
245 function renderNavFancy(cur_nav_decl, list) {
219246 {
220247 // First, walk backwards the decl parents within a file.
221248 let decl_it = cur_nav_decl;
......@@ -235,11 +262,12 @@
235262 const parts = file_path.split(".");
236263 parts.pop(); // skip last
237264 for (;;) {
238 let part = parts.pop();
265 const href = navLinkFqn(parts.join("."));
266 const part = parts.pop();
239267 if (!part) break;
240268 list.push({
241269 name: part,
242 href: navLinkFqn(parts.join(".")),
270 href: href,
243271 });
244272 }
245273 }
......@@ -263,8 +291,8 @@
263291 domSectNav.classList.remove("hidden");
264292 }
265293
266 function render404() {
267 domStatus.textContent = "404 Not Found";
294 function renderNotFound() {
295 domStatus.textContent = "Declaration not found.";
268296 domStatus.classList.remove("hidden");
269297 }
270298
......@@ -288,31 +316,91 @@
288316 }
289317 }
290318
291 function setViewSourceDecl(decl_index) {
292 curNav.viewSourceHash = "#src/" + unwrapString(wasm_exports.decl_file_path(decl_index));
319 function renderErrorSetPage(decl_index) {
320 renderNav(decl_index);
321 renderDeclHeading(decl_index);
322
323 const errorSetList = declErrorSet(decl_index).slice();
324 renderErrorSet(decl_index, errorSetList);
293325 }
294326
295 function renderFunction(decl_index) {
296 renderNav(decl_index);
297 setViewSourceDecl(decl_index);
327 function renderErrorSet(base_decl, errorSetList) {
328 if (errorSetList == null) {
329 domFnErrorsAnyError.classList.remove("hidden");
330 } else {
331 resizeDomList(domListFnErrors, errorSetList.length, '<div></div>');
332 for (let i = 0; i < errorSetList.length; i += 1) {
333 const divDom = domListFnErrors.children[i];
334 const html = unwrapString(wasm_exports.error_html(base_decl, errorSetList[i]));
335 divDom.innerHTML = html;
336 }
337 domTableFnErrors.classList.remove("hidden");
338 }
339 domSectFnErrors.classList.remove("hidden");
340 }
341
342 function renderParams(decl_index) {
343 // Prevent params from being emptied next time wasm calls memory.grow.
344 const params = declParams(decl_index).slice();
345 if (params.length !== 0) {
346 resizeDomList(domListParams, params.length, '<div></div>');
347 for (let i = 0; i < params.length; i += 1) {
348 const divDom = domListParams.children[i];
349 divDom.innerHTML = unwrapString(wasm_exports.decl_param_html(decl_index, params[i]));
350 }
351 domSectParams.classList.remove("hidden");
352 }
353 }
298354
299 domFnProtoCode.innerHTML = fnProtoHtml(decl_index);
355 function renderTypeFunction(decl_index) {
356 renderNav(decl_index);
357 renderDeclHeading(decl_index);
300358 renderTopLevelDocs(decl_index);
301 domSourceText.innerHTML = declSourceHtml(decl_index);
359 renderParams(decl_index);
360 renderDocTests(decl_index);
361
362 const members = unwrapSlice32(wasm_exports.type_fn_members(decl_index, false)).slice();
363 const fields = unwrapSlice32(wasm_exports.type_fn_fields(decl_index)).slice();
364 if (members.length !== 0 || fields.length !== 0) {
365 renderNamespace(decl_index, members, fields);
366 } else {
367 domSourceText.innerHTML = declSourceHtml(decl_index);
368 domSectSource.classList.remove("hidden");
369 }
370 }
302371
372 function renderDocTests(decl_index) {
303373 const doctest_html = declDoctestHtml(decl_index);
304374 if (doctest_html.length > 0) {
305375 domDocTestsCode.innerHTML = doctest_html;
306376 domSectDocTests.classList.remove("hidden");
307377 }
378 }
308379
309 domSectSource.classList.remove("hidden");
380 function renderFunction(decl_index) {
381 renderNav(decl_index);
382 renderDeclHeading(decl_index);
383 renderTopLevelDocs(decl_index);
384 renderParams(decl_index);
385 renderDocTests(decl_index);
386
387 domFnProtoCode.innerHTML = fnProtoHtml(decl_index, false);
310388 domFnProto.classList.remove("hidden");
389
390
391 const errorSetNode = fnErrorSet(decl_index);
392 if (errorSetNode != null) {
393 const base_decl = wasm_exports.fn_error_set_decl(decl_index, errorSetNode);
394 renderErrorSet(base_decl, errorSetNodeList(decl_index, errorSetNode));
395 }
396
397 domSourceText.innerHTML = declSourceHtml(decl_index);
398 domSectSource.classList.remove("hidden");
311399 }
312400
313 function renderGlobalConst(decl_index) {
401 function renderGlobal(decl_index) {
314402 renderNav(decl_index);
315 setViewSourceDecl(decl_index);
403 renderDeclHeading(decl_index);
316404
317405 const docs_html = declDocsHtmlShort(decl_index);
318406 if (docs_html.length > 0) {
......@@ -324,171 +412,177 @@
324412 domSectSource.classList.remove("hidden");
325413 }
326414
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 }
415 function renderNamespace(base_decl, members, fields) {
416 const typesList = [];
417 const namespacesList = [];
418 const errSetsList = [];
419 const fnsList = [];
420 const varsList = [];
421 const valsList = [];
422
423 member_loop: for (let i = 0; i < members.length; i += 1) {
424 let member = members[i];
425 const original = member;
426 while (true) {
427 const member_category = wasm_exports.categorize_decl(member, 0);
428 switch (member_category) {
429 case CAT_namespace:
430 if (wasm_exports.decl_field_count(member) > 0) {
431 typesList.push({original: original, member: member});
432 } else {
433 namespacesList.push({original: original, member: member});
434 }
435 continue member_loop;
436 case CAT_namespace:
437 namespacesList.push({original: original, member: member});
438 continue member_loop;
439 case CAT_global_variable:
440 varsList.push(member);
441 continue member_loop;
442 case CAT_function:
443 fnsList.push(member);
444 continue member_loop;
445 case CAT_type:
446 case CAT_type_type:
447 case CAT_type_function:
448 typesList.push({original: original, member: member});
449 continue member_loop;
450 case CAT_error_set:
451 errSetsList.push({original: original, member: member});
452 continue member_loop;
453 case CAT_global_const:
454 case CAT_primitive:
455 valsList.push({original: original, member: member});
456 continue member_loop;
457 case CAT_alias:
458 member = wasm_exports.get_aliasee();
459 continue;
460 default:
461 throw new Error("uknown category: " + member_category);
371462 }
372463 }
464 }
373465
374 typesList.sort(byDeclIndexName);
375 namespacesList.sort(byDeclIndexName);
376 errSetsList.sort(byDeclIndexName);
377 fnsList.sort(byDeclIndexName);
378 varsList.sort(byDeclIndexName);
379 valsList.sort(byDeclIndexName);
380
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");
391 }
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");
402 }
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");
414 }
415
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];
422
423 const dtName = divDom.children[0];
424 const ddDocs = divDom.children[1];
425 const codeDom = divDom.children[2].children[1].children[0];
426
427 const nameLinkDom = dtName.children[0];
428 const expandSourceDom = dtName.children[1];
429
430 nameLinkDom.setAttribute('href', navLinkDeclIndex(decl));
431 nameLinkDom.textContent = declIndexName(decl);
466 typesList.sort(byDeclIndexName2);
467 namespacesList.sort(byDeclIndexName2);
468 errSetsList.sort(byDeclIndexName2);
469 fnsList.sort(byDeclIndexName);
470 varsList.sort(byDeclIndexName);
471 valsList.sort(byDeclIndexName2);
472
473 if (typesList.length !== 0) {
474 resizeDomList(domListTypes, typesList.length, '<li><a href="#"></a></li>');
475 for (let i = 0; i < typesList.length; i += 1) {
476 const liDom = domListTypes.children[i];
477 const aDom = liDom.children[0];
478 const original_decl = typesList[i].original;
479 const decl = typesList[i].member;
480 aDom.textContent = declIndexName(original_decl);
481 aDom.setAttribute('href', navLinkDeclIndex(decl));
482 }
483 domSectTypes.classList.remove("hidden");
484 }
485 if (namespacesList.length !== 0) {
486 resizeDomList(domListNamespaces, namespacesList.length, '<li><a href="#"></a></li>');
487 for (let i = 0; i < namespacesList.length; i += 1) {
488 const liDom = domListNamespaces.children[i];
489 const aDom = liDom.children[0];
490 const original_decl = namespacesList[i].original;
491 const decl = namespacesList[i].member;
492 aDom.textContent = declIndexName(original_decl);
493 aDom.setAttribute('href', navLinkDeclIndex(decl));
494 }
495 domSectNamespaces.classList.remove("hidden");
496 }
432497
433 ddDocs.innerHTML = declDocsHtmlShort(decl);
498 if (errSetsList.length !== 0) {
499 resizeDomList(domListErrSets, errSetsList.length, '<li><a href="#"></a></li>');
500 for (let i = 0; i < errSetsList.length; i += 1) {
501 const liDom = domListErrSets.children[i];
502 const aDom = liDom.children[0];
503 const original_decl = errSetsList[i].original;
504 const decl = errSetsList[i].member;
505 aDom.textContent = declIndexName(original_decl);
506 aDom.setAttribute('href', navLinkDeclIndex(decl));
507 }
508 domSectErrSets.classList.remove("hidden");
509 }
434510
435 codeDom.innerHTML = declSourceHtml(decl);
436 }
437 domSectFns.classList.remove("hidden");
438 }
511 if (fnsList.length !== 0) {
512 resizeDomList(domListFns, fnsList.length,
513 '<div><dt><code></code></dt><dd></dd></div>');
514 for (let i = 0; i < fnsList.length; i += 1) {
515 const decl = fnsList[i];
516 const divDom = domListFns.children[i];
439517
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 }
518 const dtDom = divDom.children[0];
519 const ddDocs = divDom.children[1];
520 const protoCodeDom = dtDom.children[0];
450521
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];
522 protoCodeDom.innerHTML = fnProtoHtml(decl, true);
523 ddDocs.innerHTML = declDocsHtmlShort(decl);
524 }
525 domSectFns.classList.remove("hidden");
526 }
457527
458 const tdName = trDom.children[0];
459 const tdNameA = tdName.children[0];
460 const tdType = trDom.children[1];
461 const tdDesc = trDom.children[2];
528 if (fields.length !== 0) {
529 resizeDomList(domListFields, fields.length, '<div></div>');
530 for (let i = 0; i < fields.length; i += 1) {
531 const divDom = domListFields.children[i];
532 divDom.innerHTML = unwrapString(wasm_exports.decl_field_html(base_decl, fields[i]));
533 }
534 domSectFields.classList.remove("hidden");
535 }
462536
463 tdNameA.setAttribute('href', navLinkDeclIndex(decl));
464 tdNameA.textContent = declIndexName(decl);
537 if (varsList.length !== 0) {
538 resizeDomList(domListGlobalVars, varsList.length,
539 '<tr><td><a href="#"></a></td><td></td><td></td></tr>');
540 for (let i = 0; i < varsList.length; i += 1) {
541 const decl = varsList[i];
542 const trDom = domListGlobalVars.children[i];
465543
466 tdType.innerHTML = declTypeHtml(decl);
467 tdDesc.innerHTML = declDocsHtmlShort(decl);
468 }
469 domSectGlobalVars.classList.remove("hidden");
470 }
544 const tdName = trDom.children[0];
545 const tdNameA = tdName.children[0];
546 const tdType = trDom.children[1];
547 const tdDesc = trDom.children[2];
471548
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];
549 tdNameA.setAttribute('href', navLinkDeclIndex(decl));
550 tdNameA.textContent = declIndexName(decl);
478551
479 const tdName = trDom.children[0];
480 const tdNameA = tdName.children[0];
481 const tdType = trDom.children[1];
482 const tdDesc = trDom.children[2];
552 tdType.innerHTML = declTypeHtml(decl);
553 tdDesc.innerHTML = declDocsHtmlShort(decl);
554 }
555 domSectGlobalVars.classList.remove("hidden");
556 }
483557
484 tdNameA.setAttribute('href', navLinkDeclIndex(decl));
485 tdNameA.textContent = declIndexName(decl);
558 if (valsList.length !== 0) {
559 resizeDomList(domListValues, valsList.length,
560 '<tr><td><a href="#"></a></td><td></td><td></td></tr>');
561 for (let i = 0; i < valsList.length; i += 1) {
562 const trDom = domListValues.children[i];
563 const tdName = trDom.children[0];
564 const tdNameA = tdName.children[0];
565 const tdType = trDom.children[1];
566 const tdDesc = trDom.children[2];
567
568 const original_decl = valsList[i].original;
569 const decl = valsList[i].member;
570 tdNameA.setAttribute('href', navLinkDeclIndex(decl));
571 tdNameA.textContent = declIndexName(original_decl);
572
573 tdType.innerHTML = declTypeHtml(decl);
574 tdDesc.innerHTML = declDocsHtmlShort(decl);
575 }
576 domSectValues.classList.remove("hidden");
577 }
578 }
486579
487 tdType.innerHTML = declTypeHtml(decl);
488 tdDesc.innerHTML = declDocsHtmlShort(decl);
489 }
490 domSectValues.classList.remove("hidden");
491 }
580 function renderNamespacePage(decl_index) {
581 renderNav(decl_index);
582 renderDeclHeading(decl_index);
583 const members = namespaceMembers(decl_index, false).slice();
584 const fields = declFields(decl_index).slice();
585 renderNamespace(decl_index, members, fields);
492586 }
493587
494588 function operatorCompare(a, b) {
......@@ -508,7 +602,7 @@
508602 curNav.viewSourceHash = null;
509603 curNavSearch = "";
510604
511 if (location_hash[0] === '#' && location_hash.length > 1) {
605 if (location_hash.length > 1 && location_hash[0] === '#') {
512606 const query = location_hash.substring(1);
513607 const qpos = query.indexOf("?");
514608 let nonSearchPart;
......@@ -532,8 +626,14 @@
532626 }
533627 }
534628
535 function onHashChange() {
629 function onHashChange(state) {
630 history.replaceState({}, "");
536631 navigate(location.hash);
632 if (state == null) window.scrollTo({top: 0});
633 }
634
635 function onPopState(ev) {
636 onHashChange(ev.state);
537637 }
538638
539639 function navigate(location_hash) {
......@@ -686,13 +786,19 @@
686786
687787 function startAsyncSearch() {
688788 clearAsyncSearch();
689 searchTimer = setTimeout(startSearch, 100);
789 searchTimer = setTimeout(startSearch, 10);
690790 }
691791 function computeSearchHash() {
692 const oldHash = location.hash;
792 // How location.hash works:
793 // 1. http://example.com/ => ""
794 // 2. http://example.com/# => ""
795 // 3. http://example.com/#foo => "#foo"
796 // wat
797 const oldWatHash = location.hash;
798 const oldHash = oldWatHash.startsWith("#") ? oldWatHash : "#" + oldWatHash;
693799 const parts = oldHash.split("?");
694800 const newPart2 = (domSearch.value === "") ? "" : ("?" + domSearch.value);
695 return (parts.length === 1) ? (oldHash + newPart2) : ("#" + parts[0] + newPart2);
801 return parts[0] + newPart2;
696802 }
697803 function startSearch() {
698804 clearAsyncSearch();
......@@ -734,12 +840,12 @@
734840 }
735841 }
736842
737 function updatePackageList() {
738 packageList.length = 0;
843 function updateModuleList() {
844 moduleList.length = 0;
739845 for (let i = 0;; i += 1) {
740 const name = unwrapString(wasm_exports.package_name(i));
846 const name = unwrapString(wasm_exports.module_name(i));
741847 if (name.length == 0) break;
742 packageList.push(name);
848 moduleList.push(name);
743849 }
744850 }
745851
......@@ -749,6 +855,12 @@
749855 return operatorCompare(a_name, b_name);
750856 }
751857
858 function byDeclIndexName2(a, b) {
859 const a_name = declIndexName(a.original);
860 const b_name = declIndexName(b.original);
861 return operatorCompare(a_name, b_name);
862 }
863
752864 function decodeString(ptr, len) {
753865 if (len === 0) return "";
754866 return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len));
......@@ -784,8 +896,8 @@
784896 return unwrapString(wasm_exports.decl_doctest_html(decl_index));
785897 }
786898
787 function fnProtoHtml(decl_index) {
788 return unwrapString(wasm_exports.decl_fn_proto_html(decl_index));
899 function fnProtoHtml(decl_index, linkify_fn_name) {
900 return unwrapString(wasm_exports.decl_fn_proto_html(decl_index, linkify_fn_name));
789901 }
790902
791903 function setQueryString(s) {
......@@ -805,19 +917,37 @@
805917 }
806918
807919 function namespaceMembers(decl_index, include_private) {
808 const bigint = wasm_exports.namespace_members(decl_index, include_private);
920 return unwrapSlice32(wasm_exports.namespace_members(decl_index, include_private));
921 }
922
923 function declFields(decl_index) {
924 return unwrapSlice32(wasm_exports.decl_fields(decl_index));
925 }
926
927 function declParams(decl_index) {
928 return unwrapSlice32(wasm_exports.decl_params(decl_index));
929 }
930
931 function declErrorSet(decl_index) {
932 return unwrapSlice64(wasm_exports.decl_error_set(decl_index));
933 }
934
935 function errorSetNodeList(base_decl, err_set_node) {
936 return unwrapSlice64(wasm_exports.error_set_node_list(base_decl, err_set_node));
937 }
938
939 function unwrapSlice32(bigint) {
809940 const ptr = Number(bigint & 0xffffffffn);
810941 const len = Number(bigint >> 32n);
811 if (len == 0) return [];
942 if (len === 0) return [];
812943 return new Uint32Array(wasm_exports.memory.buffer, ptr, len);
813944 }
814945
815 function declFields(decl_index) {
816 const bigint = wasm_exports.decl_fields(decl_index);
946 function unwrapSlice64(bigint) {
817947 const ptr = Number(bigint & 0xffffffffn);
818948 const len = Number(bigint >> 32n);
819949 if (len === 0) return [];
820 return new Uint32Array(wasm_exports.memory.buffer, ptr, len);
950 return new BigUint64Array(wasm_exports.memory.buffer, ptr, len);
821951 }
822952
823953 function findDecl(fqn) {
......@@ -840,6 +970,12 @@
840970 return result;
841971 }
842972
973 function fnErrorSet(decl_index) {
974 const result = wasm_exports.fn_error_set(decl_index);
975 if (result === 0) return null;
976 return result;
977 }
978
843979 function setInputString(s) {
844980 const jsArray = text_encoder.encode(s);
845981 const len = jsArray.length;
lib/docs/wasm/Decl.zig+50-4
......@@ -111,8 +111,35 @@ pub fn categorize(decl: *const Decl) Walk.Category {
111111 return decl.file.categorize_decl(decl.ast_node);
112112}
113113
114/// Looks up a direct child of `decl` by name.
115pub fn get_child(decl: *const Decl, name: []const u8) ?Decl.Index {
116 switch (decl.categorize()) {
117 .alias => |aliasee| return aliasee.get().get_child(name),
118 .namespace => |node| {
119 const file = decl.file.get();
120 const scope = file.scopes.get(node) orelse return null;
121 const child_node = scope.get_child(name) orelse return null;
122 return file.node_decls.get(child_node);
123 },
124 else => return null,
125 }
126}
127
128/// Looks up a decl by name accessible in `decl`'s namespace.
129pub fn lookup(decl: *const Decl, name: []const u8) ?Decl.Index {
130 const namespace_node = switch (decl.categorize()) {
131 .namespace => |node| node,
132 else => decl.parent.get().ast_node,
133 };
134 const file = decl.file.get();
135 const scope = file.scopes.get(namespace_node) orelse return null;
136 const resolved_node = scope.lookup(&file.ast, name) orelse return null;
137 return file.node_decls.get(resolved_node);
138}
139
140/// Appends the fully qualified name to `out`.
114141pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void {
115 try decl.reset_with_path(out);
142 try decl.append_path(out);
116143 if (decl.parent != .none) {
117144 try append_parent_ns(out, decl.parent);
118145 try out.appendSlice(gpa, decl.extra_info().name);
......@@ -123,9 +150,13 @@ pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void {
123150
124151pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void {
125152 list.clearRetainingCapacity();
153 try append_path(decl, list);
154}
126155
127 // Prefer the package name alias.
128 for (Walk.packages.keys(), Walk.packages.values()) |pkg_name, pkg_file| {
156pub fn append_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void {
157 const start = list.items.len;
158 // Prefer the module name alias.
159 for (Walk.modules.keys(), Walk.modules.values()) |pkg_name, pkg_file| {
129160 if (pkg_file == decl.file) {
130161 try list.ensureUnusedCapacity(gpa, pkg_name.len + 1);
131162 list.appendSliceAssumeCapacity(pkg_name);
......@@ -137,7 +168,7 @@ pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom
137168 const file_path = decl.file.path();
138169 try list.ensureUnusedCapacity(gpa, file_path.len + 1);
139170 list.appendSliceAssumeCapacity(file_path);
140 for (list.items) |*byte| switch (byte.*) {
171 for (list.items[start..]) |*byte| switch (byte.*) {
141172 '/' => byte.* = '.',
142173 else => continue,
143174 };
......@@ -170,6 +201,21 @@ pub fn findFirstDocComment(ast: *const Ast, token: Ast.TokenIndex) Ast.TokenInde
170201 return it;
171202}
172203
204/// Successively looks up each component.
205pub fn find(search_string: []const u8) Decl.Index {
206 var path_components = std.mem.splitScalar(u8, search_string, '.');
207 const file = Walk.modules.get(path_components.first()) orelse return .none;
208 var current_decl_index = file.findRootDecl();
209 while (path_components.next()) |component| {
210 while (true) switch (current_decl_index.get().categorize()) {
211 .alias => |aliasee| current_decl_index = aliasee,
212 else => break,
213 };
214 current_decl_index = current_decl_index.get().get_child(component) orelse return .none;
215 }
216 return current_decl_index;
217}
218
173219const Decl = @This();
174220const std = @import("std");
175221const Ast = std.zig.Ast;
lib/docs/wasm/Walk.zig+225-31
......@@ -1,21 +1,26 @@
11//! Find and annotate identifiers with links to their declarations.
22pub var files: std.StringArrayHashMapUnmanaged(File) = .{};
33pub var decls: std.ArrayListUnmanaged(Decl) = .{};
4pub var packages: std.StringArrayHashMapUnmanaged(File.Index) = .{};
4pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{};
55
6arena: std.mem.Allocator,
76file: File.Index,
87
98/// keep in sync with "CAT_" constants in main.js
109pub const Category = union(enum(u8)) {
1110 namespace: Ast.Node.Index,
1211 global_variable: Ast.Node.Index,
12 /// A function that has not been detected as returning a type.
1313 function: Ast.Node.Index,
1414 primitive: Ast.Node.Index,
1515 error_set: Ast.Node.Index,
1616 global_const: Ast.Node.Index,
1717 alias: Decl.Index,
18 /// A primitive identifier that is also a type.
1819 type,
20 /// Specifically it is the literal `type`.
21 type_type,
22 /// A function that returns a type.
23 type_function: Ast.Node.Index,
1924
2025 pub const Tag = @typeInfo(Category).Union.tag_type.?;
2126};
......@@ -30,12 +35,23 @@ pub const File = struct {
3035 node_decls: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Decl.Index) = .{},
3136 /// Maps function declarations to doctests.
3237 doctests: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .{},
38 /// root node => its namespace scope
39 /// struct/union/enum/opaque decl node => its namespace scope
40 /// local var decl node => its local variable scope
41 scopes: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, *Scope) = .{},
3342
3443 pub fn lookup_token(file: *File, token: Ast.TokenIndex) Decl.Index {
3544 const decl_node = file.ident_decls.get(token) orelse return .none;
3645 return file.node_decls.get(decl_node) orelse return .none;
3746 }
3847
48 pub fn field_count(file: *const File, node: Ast.Node.Index) u32 {
49 const scope = file.scopes.get(node) orelse return 0;
50 if (scope.tag != .namespace) return 0;
51 const namespace = @fieldParentPtr(Scope.Namespace, "base", scope);
52 return namespace.field_count;
53 }
54
3955 pub const Index = enum(u32) {
4056 _,
4157
......@@ -90,16 +106,41 @@ pub const File = struct {
90106 .fn_proto_one,
91107 .fn_proto_simple,
92108 .fn_decl,
93 => return .{ .function = node },
109 => {
110 var buf: [1]Ast.Node.Index = undefined;
111 const full = ast.fullFnProto(&buf, node).?;
112 return categorize_func(file_index, node, full);
113 },
94114
95115 else => unreachable,
96116 }
97117 }
98118
119 pub fn categorize_func(
120 file_index: File.Index,
121 node: Ast.Node.Index,
122 full: Ast.full.FnProto,
123 ) Category {
124 return switch (categorize_expr(file_index, full.ast.return_type)) {
125 .namespace, .error_set, .type_type => .{ .type_function = node },
126 else => .{ .function = node },
127 };
128 }
129
130 pub fn categorize_expr_deep(file_index: File.Index, node: Ast.Node.Index) Category {
131 return switch (categorize_expr(file_index, node)) {
132 .alias => |aliasee| aliasee.get().categorize(),
133 else => |result| result,
134 };
135 }
136
99137 pub fn categorize_expr(file_index: File.Index, node: Ast.Node.Index) Category {
138 const file = file_index.get();
100139 const ast = file_index.get_ast();
101140 const node_tags = ast.nodes.items(.tag);
102141 const node_datas = ast.nodes.items(.data);
142 const main_tokens = ast.nodes.items(.main_token);
143 //log.debug("categorize_expr tag {s}", .{@tagName(node_tags[node])});
103144 return switch (node_tags[node]) {
104145 .container_decl,
105146 .container_decl_trailing,
......@@ -116,24 +157,43 @@ pub const File = struct {
116157 => .{ .namespace = node },
117158
118159 .error_set_decl,
160 .merge_error_sets,
119161 => .{ .error_set = node },
120162
121163 .identifier => {
122164 const name_token = ast.nodes.items(.main_token)[node];
123165 const ident_name = ast.tokenSlice(name_token);
124 if (std.zig.primitives.isPrimitive(ident_name)) {
166 if (std.mem.eql(u8, ident_name, "type"))
167 return .type_type;
168
169 if (isPrimitiveNonType(ident_name))
125170 return .{ .primitive = node };
126 }
127171
128 const decl_index = file_index.get().lookup_token(name_token);
129 if (decl_index != .none) return .{ .alias = decl_index };
172 if (std.zig.primitives.isPrimitive(ident_name))
173 return .type;
174
175 if (file.ident_decls.get(name_token)) |decl_node| {
176 const decl_index = file.node_decls.get(decl_node) orelse .none;
177 if (decl_index != .none) return .{ .alias = decl_index };
178 return categorize_decl(file_index, decl_node);
179 }
130180
131181 return .{ .global_const = node };
132182 },
133183
134184 .field_access => {
135 // TODO:
136 //return .alias;
185 const object_node = node_datas[node].lhs;
186 const dot_token = main_tokens[node];
187 const field_ident = dot_token + 1;
188 const field_name = ast.tokenSlice(field_ident);
189
190 switch (categorize_expr(file_index, object_node)) {
191 .alias => |aliasee| if (aliasee.get().get_child(field_name)) |decl_index| {
192 return .{ .alias = decl_index };
193 },
194 else => {},
195 }
196
137197 return .{ .global_const = node };
138198 },
139199
......@@ -154,10 +214,77 @@ pub const File = struct {
154214 return categorize_builtin_call(file_index, node, params);
155215 },
156216
217 .call_one,
218 .call_one_comma,
219 .async_call_one,
220 .async_call_one_comma,
221 .call,
222 .call_comma,
223 .async_call,
224 .async_call_comma,
225 => {
226 var buf: [1]Ast.Node.Index = undefined;
227 return categorize_call(file_index, node, ast.fullCall(&buf, node).?);
228 },
229
230 .if_simple,
231 .@"if",
232 => {
233 const if_full = ast.fullIf(node).?;
234 if (if_full.ast.else_expr != 0) {
235 const then_cat = categorize_expr_deep(file_index, if_full.ast.then_expr);
236 const else_cat = categorize_expr_deep(file_index, if_full.ast.else_expr);
237 if (then_cat == .type_type and else_cat == .type_type) {
238 return .type_type;
239 } else if (then_cat == .error_set and else_cat == .error_set) {
240 return .{ .error_set = node };
241 } else if (then_cat == .type or else_cat == .type or
242 then_cat == .namespace or else_cat == .namespace or
243 then_cat == .error_set or else_cat == .error_set or
244 then_cat == .type_function or else_cat == .type_function)
245 {
246 return .type;
247 }
248 }
249 return .{ .global_const = node };
250 },
251
252 .@"switch", .switch_comma => return categorize_switch(file_index, node),
253
254 .optional_type,
255 .array_type,
256 .array_type_sentinel,
257 .ptr_type_aligned,
258 .ptr_type_sentinel,
259 .ptr_type,
260 .ptr_type_bit_range,
261 .anyframe_type,
262 => .type,
263
157264 else => .{ .global_const = node },
158265 };
159266 }
160267
268 fn categorize_call(
269 file_index: File.Index,
270 node: Ast.Node.Index,
271 call: Ast.full.Call,
272 ) Category {
273 return switch (categorize_expr(file_index, call.ast.fn_expr)) {
274 .type_function => .type,
275 .alias => |aliasee| categorize_decl_as_callee(aliasee, node),
276 else => .{ .global_const = node },
277 };
278 }
279
280 fn categorize_decl_as_callee(decl_index: Decl.Index, call_node: Ast.Node.Index) Category {
281 return switch (decl_index.get().categorize()) {
282 .type_function => .type,
283 .alias => |aliasee| categorize_decl_as_callee(aliasee, call_node),
284 else => .{ .global_const = call_node },
285 };
286 }
287
161288 fn categorize_builtin_call(
162289 file_index: File.Index,
163290 node: Ast.Node.Index,
......@@ -172,6 +299,9 @@ pub const File = struct {
172299 const str_bytes = ast.tokenSlice(str_lit_token);
173300 const file_path = std.zig.string_literal.parseAlloc(gpa, str_bytes) catch @panic("OOM");
174301 defer gpa.free(file_path);
302 if (modules.get(file_path)) |imported_file_index| {
303 return .{ .alias = File.Index.findRootDecl(imported_file_index) };
304 }
175305 const base_path = file_index.path();
176306 const resolved_path = std.fs.path.resolvePosix(gpa, &.{
177307 base_path, "..", file_path,
......@@ -180,8 +310,8 @@ pub const File = struct {
180310 log.debug("from '{s}' @import '{s}' resolved='{s}'", .{
181311 base_path, file_path, resolved_path,
182312 });
183 if (Walk.files.getIndex(resolved_path)) |imported_file_index| {
184 return .{ .alias = Walk.File.Index.findRootDecl(@enumFromInt(imported_file_index)) };
313 if (files.getIndex(resolved_path)) |imported_file_index| {
314 return .{ .alias = File.Index.findRootDecl(@enumFromInt(imported_file_index)) };
185315 } else {
186316 log.warn("import target '{s}' did not resolve to any file", .{resolved_path});
187317 }
......@@ -195,10 +325,47 @@ pub const File = struct {
195325
196326 return .{ .global_const = node };
197327 }
328
329 fn categorize_switch(file_index: File.Index, node: Ast.Node.Index) Category {
330 const ast = file_index.get_ast();
331 const node_datas = ast.nodes.items(.data);
332 const extra = ast.extraData(node_datas[node].rhs, Ast.Node.SubRange);
333 const case_nodes = ast.extra_data[extra.start..extra.end];
334 var all_type_type = true;
335 var all_error_set = true;
336 var any_type = false;
337 if (case_nodes.len == 0) return .{ .global_const = node };
338 for (case_nodes) |case_node| {
339 const case = ast.fullSwitchCase(case_node).?;
340 switch (categorize_expr_deep(file_index, case.ast.target_expr)) {
341 .type_type => {
342 any_type = true;
343 all_error_set = false;
344 },
345 .error_set => {
346 any_type = true;
347 all_type_type = false;
348 },
349 .type, .namespace, .type_function => {
350 any_type = true;
351 all_error_set = false;
352 all_type_type = false;
353 },
354 else => {
355 all_error_set = false;
356 all_type_type = false;
357 },
358 }
359 }
360 if (all_type_type) return .type_type;
361 if (all_error_set) return .{ .error_set = node };
362 if (any_type) return .type;
363 return .{ .global_const = node };
364 }
198365 };
199366};
200367
201pub const PackageIndex = enum(u32) {
368pub const ModuleIndex = enum(u32) {
202369 _,
203370};
204371
......@@ -208,28 +375,25 @@ pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {
208375 try files.put(gpa, file_name, .{ .ast = ast });
209376
210377 if (ast.errors.len > 0) {
211 // TODO: expose this in the UI
212378 log.err("can't index '{s}' because it has syntax errors", .{file_index.path()});
213379 return file_index;
214380 }
215381
216 var arena_instance = std.heap.ArenaAllocator.init(gpa);
217 defer arena_instance.deinit();
218
219382 var w: Walk = .{
220 .arena = arena_instance.allocator(),
221383 .file = file_index,
222384 };
223 var scope: Scope = .{ .tag = .top };
385 const scope = try gpa.create(Scope);
386 scope.* = .{ .tag = .top };
224387
225388 const decl_index = try file_index.add_decl(0, .none);
226 try struct_decl(&w, &scope, decl_index, ast.containerDeclRoot());
389 try struct_decl(&w, scope, decl_index, 0, ast.containerDeclRoot());
227390
228391 const file = file_index.get();
229392 shrinkToFit(&file.ident_decls);
230393 shrinkToFit(&file.token_parents);
231394 shrinkToFit(&file.node_decls);
232395 shrinkToFit(&file.doctests);
396 shrinkToFit(&file.scopes);
233397
234398 return file_index;
235399}
......@@ -250,7 +414,7 @@ fn parse(source: []u8) Oom!Ast {
250414 return Ast.parse(gpa, adjusted_source, .zig);
251415}
252416
253const Scope = struct {
417pub const Scope = struct {
254418 tag: Tag,
255419
256420 const Tag = enum { top, local, namespace };
......@@ -267,6 +431,7 @@ const Scope = struct {
267431 names: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .{},
268432 doctests: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .{},
269433 decl_index: Decl.Index,
434 field_count: u32,
270435 };
271436
272437 fn getNamespaceDecl(start_scope: *Scope) Decl.Index {
......@@ -284,7 +449,17 @@ const Scope = struct {
284449 };
285450 }
286451
287 fn lookup(start_scope: *Scope, ast: *const Ast, name: []const u8) ?Ast.Node.Index {
452 pub fn get_child(scope: *Scope, name: []const u8) ?Ast.Node.Index {
453 switch (scope.tag) {
454 .top, .local => return null,
455 .namespace => {
456 const namespace = @fieldParentPtr(Namespace, "base", scope);
457 return namespace.names.get(name);
458 },
459 }
460 }
461
462 pub fn lookup(start_scope: *Scope, ast: *const Ast, name: []const u8) ?Ast.Node.Index {
288463 const main_tokens = ast.nodes.items(.main_token);
289464 var it: *Scope = start_scope;
290465 while (true) switch (it.tag) {
......@@ -314,17 +489,21 @@ fn struct_decl(
314489 w: *Walk,
315490 scope: *Scope,
316491 parent_decl: Decl.Index,
492 node: Ast.Node.Index,
317493 container_decl: Ast.full.ContainerDecl,
318494) Oom!void {
319495 const ast = w.file.get_ast();
320496 const node_tags = ast.nodes.items(.tag);
321497 const node_datas = ast.nodes.items(.data);
322498
323 var namespace: Scope.Namespace = .{
499 const namespace = try gpa.create(Scope.Namespace);
500 namespace.* = .{
324501 .parent = scope,
325502 .decl_index = parent_decl,
503 .field_count = 0,
326504 };
327 try w.scanDecls(&namespace, container_decl.ast.members);
505 try w.file.get().scopes.putNoClobber(gpa, node, &namespace.base);
506 try w.scanDecls(namespace, container_decl.ast.members);
328507
329508 for (container_decl.ast.members) |member| switch (node_tags[member]) {
330509 .container_field_init,
......@@ -584,7 +763,8 @@ fn expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index)
584763 => {
585764 const full = ast.fullAsm(node).?;
586765 for (full.ast.items) |n| {
587 // TODO handle .asm_input, .asm_output
766 // There is a missing call here to expr() for .asm_input and
767 // .asm_output nodes.
588768 _ = n;
589769 }
590770 try expr(w, scope, parent_decl, full.ast.template);
......@@ -701,7 +881,7 @@ fn expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index)
701881 .tagged_union_two_trailing,
702882 => {
703883 var buf: [2]Ast.Node.Index = undefined;
704 return struct_decl(w, scope, parent_decl, ast.fullContainerDecl(&buf, node).?);
884 return struct_decl(w, scope, parent_decl, node, ast.fullContainerDecl(&buf, node).?);
705885 },
706886
707887 .array_type_sentinel => {
......@@ -803,7 +983,6 @@ fn block(
803983 statements: []const Ast.Node.Index,
804984) Oom!void {
805985 const ast = w.file.get_ast();
806 const arena = w.arena;
807986 const node_tags = ast.nodes.items(.tag);
808987 const node_datas = ast.nodes.items(.data);
809988
......@@ -818,16 +997,17 @@ fn block(
818997 => {
819998 const full = ast.fullVarDecl(node).?;
820999 try global_var_decl(w, scope, parent_decl, full);
821 const local = try arena.create(Scope.Local);
1000 const local = try gpa.create(Scope.Local);
8221001 local.* = .{
8231002 .parent = scope,
8241003 .var_node = node,
8251004 };
1005 try w.file.get().scopes.putNoClobber(gpa, node, &local.base);
8261006 scope = &local.base;
8271007 },
8281008
8291009 .assign_destructure => {
830 // TODO
1010 log.debug("walk assign_destructure not implemented yet", .{});
8311011 },
8321012
8331013 .grouped_expression => try expr(w, scope, parent_decl, node_datas[node].lhs),
......@@ -849,7 +1029,6 @@ fn while_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.W
8491029}
8501030
8511031fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.Index) Oom!void {
852 const arena = w.arena;
8531032 const ast = w.file.get_ast();
8541033 const node_tags = ast.nodes.items(.tag);
8551034 const main_tokens = ast.nodes.items(.main_token);
......@@ -880,19 +1059,34 @@ fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.In
8801059 const is_doctest = token_tags[ident_token] == .identifier;
8811060 if (is_doctest) {
8821061 const token_bytes = ast.tokenSlice(ident_token);
883 try namespace.doctests.put(arena, token_bytes, member_node);
1062 try namespace.doctests.put(gpa, token_bytes, member_node);
8841063 }
8851064 continue;
8861065 },
8871066
1067 .container_field_init,
1068 .container_field_align,
1069 .container_field,
1070 => {
1071 namespace.field_count += 1;
1072 continue;
1073 },
1074
8881075 else => continue,
8891076 };
8901077
8911078 const token_bytes = ast.tokenSlice(name_token);
892 try namespace.names.put(arena, token_bytes, member_node);
1079 try namespace.names.put(gpa, token_bytes, member_node);
8931080 }
8941081}
8951082
1083pub fn isPrimitiveNonType(name: []const u8) bool {
1084 return std.mem.eql(u8, name, "undefined") or
1085 std.mem.eql(u8, name, "null") or
1086 std.mem.eql(u8, name, "true") or
1087 std.mem.eql(u8, name, "false");
1088}
1089
8961090//test {
8971091// const gpa = std.testing.allocator;
8981092//
lib/docs/wasm/main.zig+387-48
......@@ -1,3 +1,6 @@
1/// Delete this to find out where URL escaping needs to be added.
2const missing_feature_url_escape = true;
3
14const gpa = std.heap.wasm_allocator;
25
36const std = @import("std");
......@@ -80,7 +83,7 @@ export fn query_exec(ignore_case: bool) [*]Decl.Index {
8083 return query_results.items.ptr;
8184}
8285
83const max_matched_items = 2000;
86const max_matched_items = 1000;
8487
8588fn query_exec_fallible(query: []const u8, ignore_case: bool) !void {
8689 const Score = packed struct(u32) {
......@@ -181,7 +184,7 @@ fn query_exec_fallible(query: []const u8, ignore_case: bool) !void {
181184 const b_decl = query_results.items[b_index];
182185 const a_file_path = a_decl.get().file.path();
183186 const b_file_path = b_decl.get().file.path();
184 // TODO Also check the local namespace inside the file
187 // This neglects to check the local namespace inside the file.
185188 return std.mem.lessThan(u8, b_file_path, a_file_path);
186189 }
187190 }
......@@ -209,12 +212,178 @@ fn Slice(T: type) type {
209212 };
210213}
211214
215const ErrorIdentifier = packed struct(u64) {
216 token_index: Ast.TokenIndex,
217 decl_index: Decl.Index,
218
219 fn hasDocs(ei: ErrorIdentifier) bool {
220 const decl_index = ei.decl_index;
221 const ast = decl_index.get().file.get_ast();
222 const token_tags = ast.tokens.items(.tag);
223 const token_index = ei.token_index;
224 if (token_index == 0) return false;
225 return token_tags[token_index - 1] == .doc_comment;
226 }
227
228 fn html(ei: ErrorIdentifier, base_decl: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void {
229 const decl_index = ei.decl_index;
230 const ast = decl_index.get().file.get_ast();
231 const name = ast.tokenSlice(ei.token_index);
232 const first_doc_comment = Decl.findFirstDocComment(ast, ei.token_index);
233 const has_docs = ast.tokens.items(.tag)[first_doc_comment] == .doc_comment;
234 const has_link = base_decl != decl_index;
235
236 try out.appendSlice(gpa, "<dt>");
237 try out.appendSlice(gpa, name);
238 if (has_link) {
239 try out.appendSlice(gpa, " <a href=\"#");
240 _ = missing_feature_url_escape;
241 try decl_index.get().fqn(out);
242 try out.appendSlice(gpa, "\">");
243 try out.appendSlice(gpa, decl_index.get().extra_info().name);
244 try out.appendSlice(gpa, "</a>");
245 }
246 try out.appendSlice(gpa, "</dt>");
247
248 if (has_docs) {
249 try out.appendSlice(gpa, "<dd>");
250 try render_docs(out, decl_index, first_doc_comment, false);
251 try out.appendSlice(gpa, "</dd>");
252 }
253 }
254};
255
212256var string_result: std.ArrayListUnmanaged(u8) = .{};
257var error_set_result: std.StringArrayHashMapUnmanaged(ErrorIdentifier) = .{};
258
259export fn decl_error_set(decl_index: Decl.Index) Slice(ErrorIdentifier) {
260 return Slice(ErrorIdentifier).init(decl_error_set_fallible(decl_index) catch @panic("OOM"));
261}
262
263export fn error_set_node_list(base_decl: Decl.Index, node: Ast.Node.Index) Slice(ErrorIdentifier) {
264 error_set_result.clearRetainingCapacity();
265 addErrorsFromExpr(base_decl, &error_set_result, node) catch @panic("OOM");
266 sort_error_set_result();
267 return Slice(ErrorIdentifier).init(error_set_result.values());
268}
269
270export fn fn_error_set_decl(decl_index: Decl.Index, node: Ast.Node.Index) Decl.Index {
271 return switch (decl_index.get().file.categorize_expr(node)) {
272 .alias => |aliasee| fn_error_set_decl(aliasee, aliasee.get().ast_node),
273 else => decl_index,
274 };
275}
276
277export fn decl_field_count(decl_index: Decl.Index) u32 {
278 switch (decl_index.get().categorize()) {
279 .namespace => |node| return decl_index.get().file.get().field_count(node),
280 else => return 0,
281 }
282}
283
284fn decl_error_set_fallible(decl_index: Decl.Index) Oom![]ErrorIdentifier {
285 error_set_result.clearRetainingCapacity();
286 try addErrorsFromDecl(decl_index, &error_set_result);
287 sort_error_set_result();
288 return error_set_result.values();
289}
290
291fn sort_error_set_result() void {
292 const sort_context: struct {
293 pub fn lessThan(sc: @This(), a_index: usize, b_index: usize) bool {
294 _ = sc;
295 const a_name = error_set_result.keys()[a_index];
296 const b_name = error_set_result.keys()[b_index];
297 return std.mem.lessThan(u8, a_name, b_name);
298 }
299 } = .{};
300 error_set_result.sortUnstable(sort_context);
301}
302
303fn addErrorsFromDecl(
304 decl_index: Decl.Index,
305 out: *std.StringArrayHashMapUnmanaged(ErrorIdentifier),
306) Oom!void {
307 switch (decl_index.get().categorize()) {
308 .error_set => |node| try addErrorsFromExpr(decl_index, out, node),
309 .alias => |aliasee| try addErrorsFromDecl(aliasee, out),
310 else => |cat| log.debug("unable to addErrorsFromDecl: {any}", .{cat}),
311 }
312}
313
314fn addErrorsFromExpr(
315 decl_index: Decl.Index,
316 out: *std.StringArrayHashMapUnmanaged(ErrorIdentifier),
317 node: Ast.Node.Index,
318) Oom!void {
319 const decl = decl_index.get();
320 const ast = decl.file.get_ast();
321 const node_tags = ast.nodes.items(.tag);
322 const node_datas = ast.nodes.items(.data);
323
324 switch (decl.file.categorize_expr(node)) {
325 .error_set => |n| switch (node_tags[n]) {
326 .error_set_decl => {
327 try addErrorsFromNode(decl_index, out, node);
328 },
329 .merge_error_sets => {
330 try addErrorsFromExpr(decl_index, out, node_datas[node].lhs);
331 try addErrorsFromExpr(decl_index, out, node_datas[node].rhs);
332 },
333 else => unreachable,
334 },
335 .alias => |aliasee| {
336 try addErrorsFromDecl(aliasee, out);
337 },
338 else => return,
339 }
340}
341
342fn addErrorsFromNode(
343 decl_index: Decl.Index,
344 out: *std.StringArrayHashMapUnmanaged(ErrorIdentifier),
345 node: Ast.Node.Index,
346) Oom!void {
347 const decl = decl_index.get();
348 const ast = decl.file.get_ast();
349 const main_tokens = ast.nodes.items(.main_token);
350 const token_tags = ast.tokens.items(.tag);
351 const error_token = main_tokens[node];
352 var tok_i = error_token + 2;
353 while (true) : (tok_i += 1) switch (token_tags[tok_i]) {
354 .doc_comment, .comma => {},
355 .identifier => {
356 const name = ast.tokenSlice(tok_i);
357 const gop = try out.getOrPut(gpa, name);
358 // If there are more than one, take the one with doc comments.
359 // If they both have doc comments, prefer the existing one.
360 const new: ErrorIdentifier = .{
361 .token_index = tok_i,
362 .decl_index = decl_index,
363 };
364 if (!gop.found_existing or
365 (!gop.value_ptr.hasDocs() and new.hasDocs()))
366 {
367 gop.value_ptr.* = new;
368 }
369 },
370 .r_brace => break,
371 else => unreachable,
372 };
373}
374
375export fn type_fn_fields(decl_index: Decl.Index) Slice(Ast.Node.Index) {
376 return decl_fields(decl_index);
377}
213378
214379export fn decl_fields(decl_index: Decl.Index) Slice(Ast.Node.Index) {
215380 return Slice(Ast.Node.Index).init(decl_fields_fallible(decl_index) catch @panic("OOM"));
216381}
217382
383export fn decl_params(decl_index: Decl.Index) Slice(Ast.Node.Index) {
384 return Slice(Ast.Node.Index).init(decl_params_fallible(decl_index) catch @panic("OOM"));
385}
386
218387fn decl_fields_fallible(decl_index: Decl.Index) ![]Ast.Node.Index {
219388 const g = struct {
220389 var result: std.ArrayListUnmanaged(Ast.Node.Index) = .{};
......@@ -237,12 +406,38 @@ fn decl_fields_fallible(decl_index: Decl.Index) ![]Ast.Node.Index {
237406 return g.result.items;
238407}
239408
409fn decl_params_fallible(decl_index: Decl.Index) ![]Ast.Node.Index {
410 const g = struct {
411 var result: std.ArrayListUnmanaged(Ast.Node.Index) = .{};
412 };
413 g.result.clearRetainingCapacity();
414 const decl = decl_index.get();
415 const ast = decl.file.get_ast();
416 const value_node = decl.value_node() orelse return &.{};
417 var buf: [1]Ast.Node.Index = undefined;
418 const fn_proto = ast.fullFnProto(&buf, value_node) orelse return &.{};
419 try g.result.appendSlice(gpa, fn_proto.ast.params);
420 return g.result.items;
421}
422
423export fn error_html(base_decl: Decl.Index, error_identifier: ErrorIdentifier) String {
424 string_result.clearRetainingCapacity();
425 error_identifier.html(base_decl, &string_result) catch @panic("OOM");
426 return String.init(string_result.items);
427}
428
240429export fn decl_field_html(decl_index: Decl.Index, field_node: Ast.Node.Index) String {
241430 string_result.clearRetainingCapacity();
242431 decl_field_html_fallible(&string_result, decl_index, field_node) catch @panic("OOM");
243432 return String.init(string_result.items);
244433}
245434
435export fn decl_param_html(decl_index: Decl.Index, param_node: Ast.Node.Index) String {
436 string_result.clearRetainingCapacity();
437 decl_param_html_fallible(&string_result, decl_index, param_node) catch @panic("OOM");
438 return String.init(string_result.items);
439}
440
246441fn decl_field_html_fallible(
247442 out: *std.ArrayListUnmanaged(u8),
248443 decl_index: Decl.Index,
......@@ -251,7 +446,7 @@ fn decl_field_html_fallible(
251446 const decl = decl_index.get();
252447 const ast = decl.file.get_ast();
253448 try out.appendSlice(gpa, "<pre><code>");
254 try file_source_html(decl.file, out, field_node);
449 try file_source_html(decl.file, out, field_node, .{});
255450 try out.appendSlice(gpa, "</code></pre>");
256451
257452 const field = ast.fullContainerField(field_node).?;
......@@ -259,12 +454,48 @@ fn decl_field_html_fallible(
259454
260455 if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) {
261456 try out.appendSlice(gpa, "<div class=\"fieldDocs\">");
262 try render_docs(out, ast, first_doc_comment, false);
457 try render_docs(out, decl_index, first_doc_comment, false);
458 try out.appendSlice(gpa, "</div>");
459 }
460}
461
462fn decl_param_html_fallible(
463 out: *std.ArrayListUnmanaged(u8),
464 decl_index: Decl.Index,
465 param_node: Ast.Node.Index,
466) !void {
467 const decl = decl_index.get();
468 const ast = decl.file.get_ast();
469 const token_tags = ast.tokens.items(.tag);
470 const colon = ast.firstToken(param_node) - 1;
471 const name_token = colon - 1;
472 const first_doc_comment = f: {
473 var it = ast.firstToken(param_node);
474 while (it > 0) {
475 it -= 1;
476 switch (token_tags[it]) {
477 .doc_comment, .colon, .identifier, .keyword_comptime, .keyword_noalias => {},
478 else => break,
479 }
480 }
481 break :f it + 1;
482 };
483 const name = ast.tokenSlice(name_token);
484
485 try out.appendSlice(gpa, "<pre><code>");
486 try appendEscaped(out, name);
487 try out.appendSlice(gpa, ": ");
488 try file_source_html(decl.file, out, param_node, .{});
489 try out.appendSlice(gpa, "</code></pre>");
490
491 if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) {
492 try out.appendSlice(gpa, "<div class=\"fieldDocs\">");
493 try render_docs(out, decl_index, first_doc_comment, false);
263494 try out.appendSlice(gpa, "</div>");
264495 }
265496}
266497
267export fn decl_fn_proto_html(decl_index: Decl.Index) String {
498export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) String {
268499 const decl = decl_index.get();
269500 const ast = decl.file.get_ast();
270501 const node_tags = ast.nodes.items(.tag);
......@@ -282,7 +513,12 @@ export fn decl_fn_proto_html(decl_index: Decl.Index) String {
282513 };
283514
284515 string_result.clearRetainingCapacity();
285 file_source_html(decl.file, &string_result, proto_node) catch |err| {
516 file_source_html(decl.file, &string_result, proto_node, .{
517 .skip_doc_comments = true,
518 .skip_comments = true,
519 .collapse_whitespace = true,
520 .fn_link = if (linkify_fn_name) decl_index else .none,
521 }) catch |err| {
286522 fatal("unable to render source: {s}", .{@errorName(err)});
287523 };
288524 return String.init(string_result.items);
......@@ -292,7 +528,7 @@ export fn decl_source_html(decl_index: Decl.Index) String {
292528 const decl = decl_index.get();
293529
294530 string_result.clearRetainingCapacity();
295 file_source_html(decl.file, &string_result, decl.ast_node) catch |err| {
531 file_source_html(decl.file, &string_result, decl.ast_node, .{}) catch |err| {
296532 fatal("unable to render source: {s}", .{@errorName(err)});
297533 };
298534 return String.init(string_result.items);
......@@ -304,7 +540,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String {
304540 return String.init("");
305541
306542 string_result.clearRetainingCapacity();
307 file_source_html(decl.file, &string_result, doctest_ast_node) catch |err| {
543 file_source_html(decl.file, &string_result, doctest_ast_node, .{}) catch |err| {
308544 fatal("unable to render source: {s}", .{@errorName(err)});
309545 };
310546 return String.init(string_result.items);
......@@ -312,6 +548,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String {
312548
313549export fn decl_fqn(decl_index: Decl.Index) String {
314550 const decl = decl_index.get();
551 string_result.clearRetainingCapacity();
315552 decl.fqn(&string_result) catch @panic("OOM");
316553 return String.init(string_result.items);
317554}
......@@ -321,6 +558,20 @@ export fn decl_parent(decl_index: Decl.Index) Decl.Index {
321558 return decl.parent;
322559}
323560
561export fn fn_error_set(decl_index: Decl.Index) Ast.Node.Index {
562 const decl = decl_index.get();
563 const ast = decl.file.get_ast();
564 var buf: [1]Ast.Node.Index = undefined;
565 const full = ast.fullFnProto(&buf, decl.ast_node).?;
566 const node_tags = ast.nodes.items(.tag);
567 const node_datas = ast.nodes.items(.data);
568 return switch (node_tags[full.ast.return_type]) {
569 .error_set_decl => full.ast.return_type,
570 .error_union => node_datas[full.ast.return_type].lhs,
571 else => 0,
572 };
573}
574
324575export fn decl_file_path(decl_index: Decl.Index) String {
325576 string_result.clearRetainingCapacity();
326577 string_result.appendSlice(gpa, decl_index.get().file.path()) catch @panic("OOM");
......@@ -350,7 +601,8 @@ export fn decl_category_name(decl_index: Decl.Index) String {
350601 },
351602 .global_variable => "Global Variable",
352603 .function => "Function",
353 .type => "Type",
604 .type_function => "Type Function",
605 .type, .type_type => "Type",
354606 .error_set => "Error Set",
355607 .global_const => "Constant",
356608 .primitive => "Primitive Value",
......@@ -375,9 +627,8 @@ export fn decl_name(decl_index: Decl.Index) String {
375627
376628export fn decl_docs_html(decl_index: Decl.Index, short: bool) String {
377629 const decl = decl_index.get();
378 const ast = decl.file.get_ast();
379630 string_result.clearRetainingCapacity();
380 render_docs(&string_result, ast, decl.extra_info().first_doc_comment, short) catch @panic("OOM");
631 render_docs(&string_result, decl_index, decl.extra_info().first_doc_comment, short) catch @panic("OOM");
381632 return String.init(string_result.items);
382633}
383634
......@@ -402,10 +653,12 @@ fn collect_docs(
402653
403654fn render_docs(
404655 out: *std.ArrayListUnmanaged(u8),
405 ast: *const Ast,
656 decl_index: Decl.Index,
406657 first_doc_comment: Ast.TokenIndex,
407658 short: bool,
408659) Oom!void {
660 const decl = decl_index.get();
661 const ast = decl.file.get_ast();
409662 const token_tags = ast.tokens.items(.tag);
410663
411664 var parser = try markdown.Parser.init(gpa);
......@@ -423,10 +676,14 @@ fn render_docs(
423676 var parsed_doc = try parser.endInput();
424677 defer parsed_doc.deinit(gpa);
425678
679 const g = struct {
680 var link_buffer: std.ArrayListUnmanaged(u8) = .{};
681 };
682
426683 const Writer = std.ArrayListUnmanaged(u8).Writer;
427 const Renderer = markdown.Renderer(Writer, void);
684 const Renderer = markdown.Renderer(Writer, Decl.Index);
428685 const renderer: Renderer = .{
429 .context = {},
686 .context = decl_index,
430687 .renderFn = struct {
431688 fn render(
432689 r: Renderer,
......@@ -436,23 +693,22 @@ fn render_docs(
436693 ) !void {
437694 const data = doc.nodes.items(.data)[@intFromEnum(node)];
438695 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 },
449696 .code_span => {
697 try writer.writeAll("<code>");
450698 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)});
699 if (resolve_decl_path(r.context, content)) |resolved_decl_index| {
700 g.link_buffer.clearRetainingCapacity();
701 try resolve_decl_link(resolved_decl_index, &g.link_buffer);
702
703 try writer.writeAll("<a href=\"#");
704 _ = missing_feature_url_escape;
705 try writer.writeAll(g.link_buffer.items);
706 try writer.print("\">{}</a>", .{markdown.fmtHtml(content)});
707 } else {
708 try writer.print("{}", .{markdown.fmtHtml(content)});
709 }
710
711 try writer.writeAll("</code>");
456712 },
457713
458714 else => try Renderer.renderDefault(r, doc, node, writer),
......@@ -463,12 +719,39 @@ fn render_docs(
463719 try renderer.render(parsed_doc, out.writer(gpa));
464720}
465721
722fn resolve_decl_path(decl_index: Decl.Index, path: []const u8) ?Decl.Index {
723 var path_components = std.mem.splitScalar(u8, path, '.');
724 var current_decl_index = decl_index.get().lookup(path_components.first()) orelse return null;
725 while (path_components.next()) |component| {
726 switch (current_decl_index.get().categorize()) {
727 .alias => |aliasee| current_decl_index = aliasee,
728 else => {},
729 }
730 current_decl_index = current_decl_index.get().get_child(component) orelse return null;
731 }
732 return current_decl_index;
733}
734
466735export fn decl_type_html(decl_index: Decl.Index) String {
467736 const decl = decl_index.get();
468737 const ast = decl.file.get_ast();
469738 string_result.clearRetainingCapacity();
470 _ = ast; // TODO
471 string_result.appendSlice(gpa, "TODO_type_here") catch @panic("OOM");
739 t: {
740 // If there is an explicit type, use it.
741 if (ast.fullVarDecl(decl.ast_node)) |var_decl| {
742 if (var_decl.ast.type_node != 0) {
743 string_result.appendSlice(gpa, "<code>") catch @panic("OOM");
744 file_source_html(decl.file, &string_result, var_decl.ast.type_node, .{
745 .skip_comments = true,
746 .collapse_whitespace = true,
747 }) catch |e| {
748 fatal("unable to render html: {s}", .{@errorName(e)});
749 };
750 string_result.appendSlice(gpa, "</code>") catch @panic("OOM");
751 break :t;
752 }
753 }
754 }
472755 return String.init(string_result.items);
473756}
474757
......@@ -491,7 +774,7 @@ fn unpack_inner(tar_bytes: []u8) !void {
491774 const file_name = try gpa.dupe(u8, tar_file.name);
492775 if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| {
493776 const pkg_name = file_name[0..pkg_name_end];
494 const gop = try Walk.packages.getOrPut(gpa, pkg_name);
777 const gop = try Walk.modules.getOrPut(gpa, pkg_name);
495778 const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len);
496779 if (!gop.found_existing or
497780 std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or
......@@ -527,13 +810,13 @@ fn ascii_lower(bytes: []u8) void {
527810 for (bytes) |*b| b.* = std.ascii.toLower(b.*);
528811}
529812
530export fn package_name(index: u32) String {
531 const names = Walk.packages.keys();
813export fn module_name(index: u32) String {
814 const names = Walk.modules.keys();
532815 return String.init(if (index >= names.len) "" else names[index]);
533816}
534817
535export fn find_package_root(pkg: Walk.PackageIndex) Decl.Index {
536 const root_file = Walk.packages.values()[@intFromEnum(pkg)];
818export fn find_module_root(pkg: Walk.ModuleIndex) Decl.Index {
819 const root_file = Walk.modules.values()[@intFromEnum(pkg)];
537820 const result = root_file.findRootDecl();
538821 assert(result != .none);
539822 return result;
......@@ -555,11 +838,17 @@ export fn find_file_root() Decl.Index {
555838}
556839
557840/// Uses `input_string`.
841/// Tries to look up the Decl component-wise but then falls back to a file path
842/// based scan.
558843export fn find_decl() Decl.Index {
844 const result = Decl.find(input_string.items);
845 if (result != .none) return result;
846
559847 const g = struct {
560848 var match_fqn: std.ArrayListUnmanaged(u8) = .{};
561849 };
562850 for (Walk.decls.items, 0..) |*decl, decl_index| {
851 g.match_fqn.clearRetainingCapacity();
563852 decl.fqn(&g.match_fqn) catch @panic("OOM");
564853 if (std.mem.eql(u8, g.match_fqn.items, input_string.items)) {
565854 //const path = @as(Decl.Index, @enumFromInt(decl_index)).get().file.path();
......@@ -583,7 +872,7 @@ export fn categorize_decl(decl_index: Decl.Index, resolve_alias_count: usize) Wa
583872 var decl = decl_index.get();
584873 while (true) {
585874 const result = decl.categorize();
586 switch (decl.categorize()) {
875 switch (result) {
587876 .alias => |new_index| {
588877 assert(new_index != .none);
589878 global_aliasee = new_index;
......@@ -599,6 +888,10 @@ export fn categorize_decl(decl_index: Decl.Index, resolve_alias_count: usize) Wa
599888 }
600889}
601890
891export fn type_fn_members(parent: Decl.Index, include_private: bool) Slice(Decl.Index) {
892 return namespace_members(parent, include_private);
893}
894
602895export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Decl.Index) {
603896 const g = struct {
604897 var members: std.ArrayListUnmanaged(Decl.Index) = .{};
......@@ -617,10 +910,18 @@ export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Dec
617910 return Slice(Decl.Index).init(g.members.items);
618911}
619912
913const RenderSourceOptions = struct {
914 skip_doc_comments: bool = false,
915 skip_comments: bool = false,
916 collapse_whitespace: bool = false,
917 fn_link: Decl.Index = .none,
918};
919
620920fn file_source_html(
621921 file_index: Walk.File.Index,
622922 out: *std.ArrayListUnmanaged(u8),
623923 root_node: Ast.Node.Index,
924 options: RenderSourceOptions,
624925) !void {
625926 const ast = file_index.get_ast();
626927 const file = file_index.get();
......@@ -631,6 +932,7 @@ fn file_source_html(
631932
632933 const token_tags = ast.tokens.items(.tag);
633934 const token_starts = ast.tokens.items(.start);
935 const main_tokens = ast.nodes.items(.main_token);
634936
635937 const start_token = ast.firstToken(root_node);
636938 const end_token = ast.lastToken(root_node) + 1;
......@@ -643,7 +945,20 @@ fn file_source_html(
643945 start_token..,
644946 ) |tag, start, token_index| {
645947 const between = ast.source[cursor..start];
646 try appendEscaped(out, between);
948 if (std.mem.trim(u8, between, " \t\r\n").len > 0) {
949 if (!options.skip_comments) {
950 try out.appendSlice(gpa, "<span class=\"tok-comment\">");
951 try appendEscaped(out, between);
952 try out.appendSlice(gpa, "</span>");
953 }
954 } else if (between.len > 0) {
955 if (options.collapse_whitespace) {
956 if (out.items.len > 0 and out.items[out.items.len - 1] != ' ')
957 try out.append(gpa, ' ');
958 } else {
959 try out.appendSlice(gpa, between);
960 }
961 }
647962 if (tag == .eof) break;
648963 const slice = ast.tokenSlice(token_index);
649964 cursor = start + slice.len;
......@@ -723,17 +1038,36 @@ fn file_source_html(
7231038 .doc_comment,
7241039 .container_doc_comment,
7251040 => {
726 try out.appendSlice(gpa, "<span class=\"tok-comment\">");
727 try appendEscaped(out, slice);
728 try out.appendSlice(gpa, "</span>");
1041 if (!options.skip_doc_comments) {
1042 try out.appendSlice(gpa, "<span class=\"tok-comment\">");
1043 try appendEscaped(out, slice);
1044 try out.appendSlice(gpa, "</span>");
1045 }
7291046 },
7301047
7311048 .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 {
1049 if (options.fn_link != .none) {
1050 const fn_link = options.fn_link.get();
1051 const fn_token = main_tokens[fn_link.ast_node];
1052 if (token_index == fn_token + 1) {
1053 try out.appendSlice(gpa, "<a class=\"tok-fn\" href=\"#");
1054 _ = missing_feature_url_escape;
1055 try fn_link.fqn(out);
1056 try out.appendSlice(gpa, "\">");
1057 try appendEscaped(out, slice);
1058 try out.appendSlice(gpa, "</a>");
1059 break :i;
1060 }
1061 }
1062
1063 if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) {
1064 try out.appendSlice(gpa, "<span class=\"tok-fn\">");
1065 try appendEscaped(out, slice);
1066 try out.appendSlice(gpa, "</span>");
1067 break :i;
1068 }
1069
1070 if (Walk.isPrimitiveNonType(slice)) {
7371071 try out.appendSlice(gpa, "<span class=\"tok-null\">");
7381072 try appendEscaped(out, slice);
7391073 try out.appendSlice(gpa, "</span>");
......@@ -752,7 +1086,8 @@ fn file_source_html(
7521086 try walk_field_accesses(file_index, &g.field_access_buffer, field_access_node);
7531087 if (g.field_access_buffer.items.len > 0) {
7541088 try out.appendSlice(gpa, "<a href=\"#");
755 try out.appendSlice(gpa, g.field_access_buffer.items); // TODO url escape
1089 _ = missing_feature_url_escape;
1090 try out.appendSlice(gpa, g.field_access_buffer.items);
7561091 try out.appendSlice(gpa, "\">");
7571092 try appendEscaped(out, slice);
7581093 try out.appendSlice(gpa, "</a>");
......@@ -767,7 +1102,8 @@ fn file_source_html(
7671102 try resolve_ident_link(file_index, &g.field_access_buffer, token_index);
7681103 if (g.field_access_buffer.items.len > 0) {
7691104 try out.appendSlice(gpa, "<a href=\"#");
770 try out.appendSlice(gpa, g.field_access_buffer.items); // TODO url escape
1105 _ = missing_feature_url_escape;
1106 try out.appendSlice(gpa, g.field_access_buffer.items);
7711107 try out.appendSlice(gpa, "\">");
7721108 try appendEscaped(out, slice);
7731109 try out.appendSlice(gpa, "</a>");
......@@ -860,7 +1196,10 @@ fn resolve_ident_link(
8601196) Oom!void {
8611197 const decl_index = file_index.get().lookup_token(ident_token);
8621198 if (decl_index == .none) return;
1199 try resolve_decl_link(decl_index, out);
1200}
8631201
1202fn resolve_decl_link(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void {
8641203 const decl = decl_index.get();
8651204 switch (decl.categorize()) {
8661205 .alias => |alias_decl| try alias_decl.get().fqn(out),
lib/docs/wasm/markdown.zig+8-1
......@@ -517,6 +517,10 @@ test "tables require leading and trailing pipes" {
517517 \\
518518 \\| But | this | is |
519519 \\
520 \\Also not a table:
521 \\|
522 \\ |
523 \\
520524 ,
521525 \\<p>Not | a | table</p>
522526 \\<table>
......@@ -526,6 +530,9 @@ test "tables require leading and trailing pipes" {
526530 \\<td>is</td>
527531 \\</tr>
528532 \\</table>
533 \\<p>Also not a table:
534 \\|
535 \\|</p>
529536 \\
530537 );
531538}
......@@ -584,7 +591,7 @@ test "code blocks" {
584591 \\<pre><code>Hello, world!
585592 \\This is some code.
586593 \\</code></pre>
587 \\<pre><code class="zig test">const std = @import(&quot;std&quot;);
594 \\<pre><code>const std = @import(&quot;std&quot;);
588595 \\
589596 \\test {
590597 \\ try std.testing.expect(2 + 2 == 4);
lib/docs/wasm/markdown/Parser.zig+2-1
......@@ -610,7 +610,8 @@ const TableRowStart = struct {
610610};
611611
612612fn startTableRow(unindented_line: []const u8) ?TableRowStart {
613 if (!mem.startsWith(u8, unindented_line, "|") or
613 if (unindented_line.len < 2 or
614 !mem.startsWith(u8, unindented_line, "|") or
614615 mem.endsWith(u8, unindented_line, "\\|") or
615616 !mem.endsWith(u8, unindented_line, "|")) return null;
616617
lib/docs/wasm/markdown/renderer.zig+1-6
......@@ -112,13 +112,8 @@ pub fn Renderer(comptime Writer: type, comptime Context: type) type {
112112 try writer.print("</h{}>\n", .{data.heading.level});
113113 },
114114 .code_block => {
115 const tag = doc.string(data.code_block.tag);
116115 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 }
116 try writer.print("<pre><code>{}</code></pre>\n", .{fmtHtml(content)});
122117 },
123118 .blockquote => {
124119 try writer.writeAll("<blockquote>\n");