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 @@...@@ -12,6 +12,9 @@
12 .hidden {12 .hidden {
13 display: none;13 display: none;
14 }14 }
15 table {
16 width: 100%;
17 }
15 a {18 a {
16 color: #2A6286;19 color: #2A6286;
17 }20 }
...@@ -25,25 +28,38 @@...@@ -25,25 +28,38 @@
25 }28 }
26 code {29 code {
27 font-family:"Source Code Pro",monospace;30 font-family:"Source Code Pro",monospace;
28 font-size:1em;31 font-size: 0.9em;
29 }32 }
30 code a {33 code a {
31 color: #000000;34 color: #000000;
32 }35 }
33 #listFields > div {36 #listFields > div, #listParams > div {
34 margin-bottom: 1em;37 margin-bottom: 1em;
35 }38 }
39 #hdrName a {
40 font-size: 0.7em;
41 padding-left: 1em;
42 }
36 .fieldDocs {43 .fieldDocs {
37 border: 1px solid #F5F5F5;44 border: 1px solid #F5F5F5;
38 border-top: 0px;45 border-top: 0px;
39 padding: 1px 1em;46 padding: 1px 1em;
40 }47 }
4148
49 #logo {
50 width: 8em;
51 padding: 0.5em 1em;
52 }
4253
43 #navWrap {54 #navWrap {
44 float: left;55 width: -moz-available;
45 width: 47em;56 width: -webkit-fill-available;
46 margin-left: 1em;57 width: stretch;
58 margin-left: 11em;
59 }
60
61 #search {
62 width: 100%;
47 }63 }
4864
49 nav {65 nav {
...@@ -102,15 +118,6 @@...@@ -102,15 +118,6 @@
102 color: #000;118 color: #000;
103 }119 }
104120
105 #logo {
106 width: 8em;
107 padding: 0.5em 1em;
108 }
109
110 #search {
111 width: 100%;
112 }
113
114 #helpDialog {121 #helpDialog {
115 width: 21em;122 width: 21em;
116 height: 21em;123 height: 21em;
...@@ -154,6 +161,12 @@...@@ -154,6 +161,12 @@
154 font-weight: bold;161 font-weight: bold;
155 }162 }
156163
164 dl > div {
165 padding: 0.5em;
166 border: 1px solid #c0c0c0;
167 margin-top: 0.5em;
168 }
169
157 td {170 td {
158 vertical-align: top;171 vertical-align: top;
159 margin: 0;172 margin: 0;
...@@ -163,6 +176,10 @@...@@ -163,6 +176,10 @@
163 overflow-x: hidden;176 overflow-x: hidden;
164 }177 }
165178
179 ul.columns {
180 column-width: 20em;
181 }
182
166 .tok-kw {183 .tok-kw {
167 color: #333;184 color: #333;
168 font-weight: bold;185 font-weight: bold;
...@@ -193,18 +210,19 @@...@@ -193,18 +210,19 @@
193 }210 }
194211
195 @media (prefers-color-scheme: dark) {212 @media (prefers-color-scheme: dark) {
196 body{213 body {
197 background-color: #111;214 background-color: #111;
198 color: #bbb;215 color: #bbb;
199 }216 }
217 pre {
218 background-color: #222;
219 color: #ccc;
220 }
200 a {221 a {
201 color: #88f;222 color: #88f;
202 }223 }
203 code a {224 code a {
204 color: #bbb;225 color: #ccc;
205 }
206 pre{
207 background-color:#2A2A2A;
208 }226 }
209 .fieldDocs {227 .fieldDocs {
210 border-color:#2A2A2A;228 border-color:#2A2A2A;
...@@ -229,6 +247,9 @@...@@ -229,6 +247,9 @@
229 #listSearchResults li.selected a {247 #listSearchResults li.selected a {
230 color: #fff;248 color: #fff;
231 }249 }
250 dl > div {
251 border-color: #373737;
252 }
232 .tok-kw {253 .tok-kw {
233 color: #eee;254 color: #eee;
234 }255 }
...@@ -242,7 +263,7 @@...@@ -242,7 +263,7 @@
242 color: #aa7;263 color: #aa7;
243 }264 }
244 .tok-fn {265 .tok-fn {
245 color: #e33;266 color: #B1A0F8;
246 }267 }
247 .tok-null {268 .tok-null {
248 color: #ff8080;269 color: #ff8080;
...@@ -258,7 +279,7 @@...@@ -258,7 +279,7 @@
258 </head>279 </head>
259 <body>280 <body>
260 <nav>281 <nav>
261 <div class="logo">282 <a class="logo" href="#">
262 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140">283 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140">
263 <g fill="#F7A41D">284 <g fill="#F7A41D">
264 <g>285 <g>
...@@ -297,7 +318,7 @@...@@ -297,7 +318,7 @@
297 </g>318 </g>
298 </g>319 </g>
299 </svg>320 </svg>
300 </div>321 </a>
301 </nav>322 </nav>
302 <div id="navWrap">323 <div id="navWrap">
303 <input type="search" id="search" autocomplete="off" spellcheck="false" placeholder="`s` to search, `?` to see more options">324 <input type="search" id="search" autocomplete="off" spellcheck="false" placeholder="`s` to search, `?` to see more options">
...@@ -305,11 +326,16 @@...@@ -305,11 +326,16 @@
305 </div>326 </div>
306 <section>327 <section>
307 <p id="status">Loading...</p>328 <p id="status">Loading...</p>
329 <h1 id="hdrName" class="hidden"><span></span><a href="#">[src]</a></h1>
308 <div id="fnProto" class="hidden">330 <div id="fnProto" class="hidden">
309 <pre><code id="fnProtoCode"></code></pre>331 <pre><code id="fnProtoCode"></code></pre>
310 </div>332 </div>
311 <h1 id="hdrName" class="hidden"></h1>
312 <div id="tldDocs" class="hidden"></div>333 <div id="tldDocs" class="hidden"></div>
334 <div id="sectParams" class="hidden">
335 <h2>Parameters</h2>
336 <div id="listParams">
337 </div>
338 </div>
313 <div id="sectFnErrors" class="hidden">339 <div id="sectFnErrors" class="hidden">
314 <h2>Errors</h2>340 <h2>Errors</h2>
315 <div id="fnErrorsAnyError">341 <div id="fnErrorsAnyError">
...@@ -332,12 +358,12 @@...@@ -332,12 +358,12 @@
332 </div>358 </div>
333 <div id="sectTypes" class="hidden">359 <div id="sectTypes" class="hidden">
334 <h2>Types</h2>360 <h2>Types</h2>
335 <ul id="listTypes">361 <ul id="listTypes" class="columns">
336 </ul>362 </ul>
337 </div>363 </div>
338 <div id="sectNamespaces" class="hidden">364 <div id="sectNamespaces" class="hidden">
339 <h2>Namespaces</h2>365 <h2>Namespaces</h2>
340 <ul id="listNamespaces">366 <ul id="listNamespaces" class="columns">
341 </ul>367 </ul>
342 </div>368 </div>
343 <div id="sectGlobalVars" class="hidden">369 <div id="sectGlobalVars" class="hidden">
...@@ -347,11 +373,6 @@...@@ -347,11 +373,6 @@
347 </tbody>373 </tbody>
348 </table>374 </table>
349 </div>375 </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">376 <div id="sectValues" class="hidden">
356 <h2>Values</h2>377 <h2>Values</h2>
357 <table>378 <table>
...@@ -359,9 +380,14 @@...@@ -359,9 +380,14 @@
359 </tbody>380 </tbody>
360 </table>381 </table>
361 </div>382 </div>
383 <div id="sectFns" class="hidden">
384 <h2>Functions</h2>
385 <dl id="listFns">
386 </dl>
387 </div>
362 <div id="sectErrSets" class="hidden">388 <div id="sectErrSets" class="hidden">
363 <h2>Error Sets</h2>389 <h2>Error Sets</h2>
364 <ul id="listErrSets">390 <ul id="listErrSets" class="columns">
365 </ul>391 </ul>
366 </div>392 </div>
367 <div id="sectDocTests" class="hidden">393 <div id="sectDocTests" class="hidden">
lib/docs/main.js+344-208
...@@ -7,6 +7,8 @@...@@ -7,6 +7,8 @@
7 const CAT_global_const = 5;7 const CAT_global_const = 5;
8 const CAT_alias = 6;8 const CAT_alias = 6;
9 const CAT_type = 7;9 const CAT_type = 7;
10 const CAT_type_type = 8;
11 const CAT_type_function = 9;
1012
11 const domDocTestsCode = document.getElementById("docTestsCode");13 const domDocTestsCode = document.getElementById("docTestsCode");
12 const domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");14 const domFnErrorsAnyError = document.getElementById("fnErrorsAnyError");
...@@ -16,6 +18,7 @@...@@ -16,6 +18,7 @@
16 const domHelpModal = document.getElementById("helpDialog");18 const domHelpModal = document.getElementById("helpDialog");
17 const domListErrSets = document.getElementById("listErrSets");19 const domListErrSets = document.getElementById("listErrSets");
18 const domListFields = document.getElementById("listFields");20 const domListFields = document.getElementById("listFields");
21 const domListParams = document.getElementById("listParams");
19 const domListFnErrors = document.getElementById("listFnErrors");22 const domListFnErrors = document.getElementById("listFnErrors");
20 const domListFns = document.getElementById("listFns");23 const domListFns = document.getElementById("listFns");
21 const domListGlobalVars = document.getElementById("listGlobalVars");24 const domListGlobalVars = document.getElementById("listGlobalVars");
...@@ -29,6 +32,7 @@...@@ -29,6 +32,7 @@
29 const domSectDocTests = document.getElementById("sectDocTests");32 const domSectDocTests = document.getElementById("sectDocTests");
30 const domSectErrSets = document.getElementById("sectErrSets");33 const domSectErrSets = document.getElementById("sectErrSets");
31 const domSectFields = document.getElementById("sectFields");34 const domSectFields = document.getElementById("sectFields");
35 const domSectParams = document.getElementById("sectParams");
32 const domSectFnErrors = document.getElementById("sectFnErrors");36 const domSectFnErrors = document.getElementById("sectFnErrors");
33 const domSectFns = document.getElementById("sectFns");37 const domSectFns = document.getElementById("sectFns");
34 const domSectGlobalVars = document.getElementById("sectGlobalVars");38 const domSectGlobalVars = document.getElementById("sectGlobalVars");
...@@ -64,8 +68,8 @@...@@ -64,8 +68,8 @@
64 var curSearchIndex = -1;68 var curSearchIndex = -1;
65 var imFeelingLucky = false;69 var imFeelingLucky = false;
6670
67 // names of packages in the same order as wasm71 // names of modules in the same order as wasm
68 const packageList = [];72 const moduleList = [];
6973
70 let wasm_promise = fetch("main.wasm");74 let wasm_promise = fetch("main.wasm");
71 let sources_promise = fetch("sources.tar").then(function(response) {75 let sources_promise = fetch("sources.tar").then(function(response) {
...@@ -99,13 +103,13 @@...@@ -99,13 +103,13 @@
99 wasm_array.set(js_array);103 wasm_array.set(js_array);
100 wasm_exports.unpack(ptr, js_array.length);104 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);
105 domSearch.addEventListener('keydown', onSearchKeyDown, false);109 domSearch.addEventListener('keydown', onSearchKeyDown, false);
106 domSearch.addEventListener('input', onSearchChange, false);110 domSearch.addEventListener('input', onSearchChange, false);
107 window.addEventListener('keydown', onWindowKeyDown, false);111 window.addEventListener('keydown', onWindowKeyDown, false);
108 onHashChange();112 onHashChange(null);
109 });113 });
110 });114 });
111115
...@@ -118,7 +122,7 @@...@@ -118,7 +122,7 @@
118 } else if (curNav.path != null) {122 } else if (curNav.path != null) {
119 document.title = curNav.path + suffix;123 document.title = curNav.path + suffix;
120 } else {124 } else {
121 document.title = packageList[0] + suffix; // Home125 document.title = moduleList[0] + suffix; // Home
122 }126 }
123 }127 }
124128
...@@ -130,6 +134,7 @@...@@ -130,6 +134,7 @@
130 domSectErrSets.classList.add("hidden");134 domSectErrSets.classList.add("hidden");
131 domSectDocTests.classList.add("hidden");135 domSectDocTests.classList.add("hidden");
132 domSectFields.classList.add("hidden");136 domSectFields.classList.add("hidden");
137 domSectParams.classList.add("hidden");
133 domSectFnErrors.classList.add("hidden");138 domSectFnErrors.classList.add("hidden");
134 domSectFns.classList.add("hidden");139 domSectFns.classList.add("hidden");
135 domSectGlobalVars.classList.add("hidden");140 domSectGlobalVars.classList.add("hidden");
...@@ -152,7 +157,7 @@...@@ -152,7 +157,7 @@
152 case 0: return renderHome();157 case 0: return renderHome();
153 case 1:158 case 1:
154 if (curNav.decl == null) {159 if (curNav.decl == null) {
155 return render404();160 return renderNotFound();
156 } else {161 } else {
157 return renderDecl(curNav.decl);162 return renderDecl(curNav.decl);
158 }163 }
...@@ -162,37 +167,51 @@...@@ -162,37 +167,51 @@
162 }167 }
163168
164 function renderHome() {169 function renderHome() {
165 if (packageList.length == 1) return renderPackage(0);170 if (moduleList.length == 0) {
166171 domStatus.textContent = "sources.tar contains no modules";
167 domStatus.textContent = "TODO implement renderHome for multiple packages";172 domStatus.classList.remove("hidden");
168 domStatus.classList.remove("hidden");173 return;
174 }
175 return renderModule(0);
169 }176 }
170177
171 function renderPackage(pkg_index) {178 function renderModule(pkg_index) {
172 const root_decl = wasm_exports.find_package_root(pkg_index);179 const root_decl = wasm_exports.find_module_root(pkg_index);
173 return renderDecl(root_decl);180 return renderDecl(root_decl);
174 }181 }
175182
176 function renderDecl(decl_index) {183 function renderDecl(decl_index) {
177 const category = wasm_exports.categorize_decl(decl_index, 0);184 const category = wasm_exports.categorize_decl(decl_index, 0);
178 switch (category) {185 switch (category) {
179 case CAT_namespace: return renderNamespace(decl_index);186 case CAT_namespace:
180 case CAT_global_variable: throw new Error("TODO: CAT_GLOBAL_VARIABLE");187 return renderNamespacePage(decl_index);
181 case CAT_function: return renderFunction(decl_index);188 case CAT_global_variable:
182 case CAT_primitive: throw new Error("TODO CAT_primitive");189 case CAT_primitive:
183 case CAT_error_set: throw new Error("TODO CAT_error_set");190 case CAT_global_const:
184 case CAT_global_const: return renderGlobalConst(decl_index);191 case CAT_type:
185 case CAT_alias: return renderDecl(wasm_exports.get_aliasee());192 case CAT_type_type:
186 case CAT_type: throw new Error("TODO CAT_type");193 return renderGlobal(decl_index);
187 default: throw new Error("unrecognized category " + category);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);
188 }204 }
189 }205 }
190206
191 function renderSource(path) {207 function renderSource(path) {
192 const decl_index = findFileRoot(path);208 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
197 domSourceText.innerHTML = declSourceHtml(decl_index);216 domSourceText.innerHTML = declSourceHtml(decl_index);
198217
...@@ -200,7 +219,12 @@...@@ -200,7 +219,12 @@
200 }219 }
201220
202 function renderDeclHeading(decl_index) {221 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);
204 domHdrName.classList.remove("hidden");228 domHdrName.classList.remove("hidden");
205229
206 renderTopLevelDocs(decl_index);230 renderTopLevelDocs(decl_index);
...@@ -214,8 +238,11 @@...@@ -214,8 +238,11 @@
214 }238 }
215 }239 }
216240
217 function renderNav(cur_nav_decl) {241 function renderNav(cur_nav_decl, list) {
218 const list = [];242 return renderNavFancy(cur_nav_decl, []);
243 }
244
245 function renderNavFancy(cur_nav_decl, list) {
219 {246 {
220 // First, walk backwards the decl parents within a file.247 // First, walk backwards the decl parents within a file.
221 let decl_it = cur_nav_decl;248 let decl_it = cur_nav_decl;
...@@ -235,11 +262,12 @@...@@ -235,11 +262,12 @@
235 const parts = file_path.split(".");262 const parts = file_path.split(".");
236 parts.pop(); // skip last263 parts.pop(); // skip last
237 for (;;) {264 for (;;) {
238 let part = parts.pop();265 const href = navLinkFqn(parts.join("."));
266 const part = parts.pop();
239 if (!part) break;267 if (!part) break;
240 list.push({268 list.push({
241 name: part,269 name: part,
242 href: navLinkFqn(parts.join(".")),270 href: href,
243 });271 });
244 }272 }
245 }273 }
...@@ -263,8 +291,8 @@...@@ -263,8 +291,8 @@
263 domSectNav.classList.remove("hidden");291 domSectNav.classList.remove("hidden");
264 }292 }
265293
266 function render404() {294 function renderNotFound() {
267 domStatus.textContent = "404 Not Found";295 domStatus.textContent = "Declaration not found.";
268 domStatus.classList.remove("hidden");296 domStatus.classList.remove("hidden");
269 }297 }
270298
...@@ -288,31 +316,91 @@...@@ -288,31 +316,91 @@
288 }316 }
289 }317 }
290318
291 function setViewSourceDecl(decl_index) {319 function renderErrorSetPage(decl_index) {
292 curNav.viewSourceHash = "#src/" + unwrapString(wasm_exports.decl_file_path(decl_index));320 renderNav(decl_index);
321 renderDeclHeading(decl_index);
322
323 const errorSetList = declErrorSet(decl_index).slice();
324 renderErrorSet(decl_index, errorSetList);
293 }325 }
294326
295 function renderFunction(decl_index) {327 function renderErrorSet(base_decl, errorSetList) {
296 renderNav(decl_index);328 if (errorSetList == null) {
297 setViewSourceDecl(decl_index);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);
300 renderTopLevelDocs(decl_index);358 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) {
303 const doctest_html = declDoctestHtml(decl_index);373 const doctest_html = declDoctestHtml(decl_index);
304 if (doctest_html.length > 0) {374 if (doctest_html.length > 0) {
305 domDocTestsCode.innerHTML = doctest_html;375 domDocTestsCode.innerHTML = doctest_html;
306 domSectDocTests.classList.remove("hidden");376 domSectDocTests.classList.remove("hidden");
307 }377 }
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);
310 domFnProto.classList.remove("hidden");388 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");
311 }399 }
312400
313 function renderGlobalConst(decl_index) {401 function renderGlobal(decl_index) {
314 renderNav(decl_index);402 renderNav(decl_index);
315 setViewSourceDecl(decl_index);403 renderDeclHeading(decl_index);
316404
317 const docs_html = declDocsHtmlShort(decl_index);405 const docs_html = declDocsHtmlShort(decl_index);
318 if (docs_html.length > 0) {406 if (docs_html.length > 0) {
...@@ -324,171 +412,177 @@...@@ -324,171 +412,177 @@
324 domSectSource.classList.remove("hidden");412 domSectSource.classList.remove("hidden");
325 }413 }
326414
327 function renderNamespace(decl_index) {415 function renderNamespace(base_decl, members, fields) {
328 renderNav(decl_index);416 const typesList = [];
329 renderDeclHeading(decl_index);417 const namespacesList = [];
330 setViewSourceDecl(decl_index);418 const errSetsList = [];
331419 const fnsList = [];
332 const typesList = [];420 const varsList = [];
333 const namespacesList = [];421 const valsList = [];
334 const errSetsList = [];422
335 const fnsList = [];423 member_loop: for (let i = 0; i < members.length; i += 1) {
336 const varsList = [];424 let member = members[i];
337 const valsList = [];425 const original = member;
338 const members = namespaceMembers(decl_index, false);426 while (true) {
339427 const member_category = wasm_exports.categorize_decl(member, 0);
340 member_loop: for (let i = 0; i < members.length; i += 1) {428 switch (member_category) {
341 let member = members[i];429 case CAT_namespace:
342 while (true) {430 if (wasm_exports.decl_field_count(member) > 0) {
343 const member_category = wasm_exports.categorize_decl(member, 0);431 typesList.push({original: original, member: member});
344 switch (member_category) {432 } else {
345 case CAT_namespace:433 namespacesList.push({original: original, member: member});
346 namespacesList.push(member);434 }
347 continue member_loop;435 continue member_loop;
348 case CAT_global_variable:436 case CAT_namespace:
349 varsList.push(member);437 namespacesList.push({original: original, member: member});
350 continue member_loop;438 continue member_loop;
351 case CAT_function:439 case CAT_global_variable:
352 fnsList.push(member);440 varsList.push(member);
353 continue member_loop;441 continue member_loop;
354 case CAT_type:442 case CAT_function:
355 typesList.push(member);443 fnsList.push(member);
356 continue member_loop;444 continue member_loop;
357 case CAT_error_set:445 case CAT_type:
358 errSetsList.push(member);446 case CAT_type_type:
359 continue member_loop;447 case CAT_type_function:
360 case CAT_global_const:448 typesList.push({original: original, member: member});
361 case CAT_primitive:449 continue member_loop;
362 valsList.push(member);450 case CAT_error_set:
363 continue member_loop;451 errSetsList.push({original: original, member: member});
364 case CAT_alias:452 continue member_loop;
365 // TODO: handle aliasing loop453 case CAT_global_const:
366 member = wasm_exports.get_aliasee();454 case CAT_primitive:
367 continue;455 valsList.push({original: original, member: member});
368 default:456 continue member_loop;
369 throw new Error("uknown category: " + member_category);457 case CAT_alias:
370 }458 member = wasm_exports.get_aliasee();
459 continue;
460 default:
461 throw new Error("uknown category: " + member_category);
371 }462 }
372 }463 }
464 }
373465
374 typesList.sort(byDeclIndexName);466 typesList.sort(byDeclIndexName2);
375 namespacesList.sort(byDeclIndexName);467 namespacesList.sort(byDeclIndexName2);
376 errSetsList.sort(byDeclIndexName);468 errSetsList.sort(byDeclIndexName2);
377 fnsList.sort(byDeclIndexName);469 fnsList.sort(byDeclIndexName);
378 varsList.sort(byDeclIndexName);470 varsList.sort(byDeclIndexName);
379 valsList.sort(byDeclIndexName);471 valsList.sort(byDeclIndexName2);
380472
381 if (typesList.length !== 0) {473 if (typesList.length !== 0) {
382 resizeDomList(domListTypes, typesList.length, '<li><a href="#"></a></li>');474 resizeDomList(domListTypes, typesList.length, '<li><a href="#"></a></li>');
383 for (let i = 0; i < typesList.length; i += 1) {475 for (let i = 0; i < typesList.length; i += 1) {
384 const liDom = domListTypes.children[i];476 const liDom = domListTypes.children[i];
385 const aDom = liDom.children[0];477 const aDom = liDom.children[0];
386 const decl = typesList[i];478 const original_decl = typesList[i].original;
387 aDom.textContent = declIndexName(decl);479 const decl = typesList[i].member;
388 aDom.setAttribute('href', navLinkDeclIndex(decl));480 aDom.textContent = declIndexName(original_decl);
389 }481 aDom.setAttribute('href', navLinkDeclIndex(decl));
390 domSectTypes.classList.remove("hidden");482 }
391 }483 domSectTypes.classList.remove("hidden");
392 if (namespacesList.length !== 0) {484 }
393 resizeDomList(domListNamespaces, namespacesList.length, '<li><a href="#"></a></li>');485 if (namespacesList.length !== 0) {
394 for (let i = 0; i < namespacesList.length; i += 1) {486 resizeDomList(domListNamespaces, namespacesList.length, '<li><a href="#"></a></li>');
395 const liDom = domListNamespaces.children[i];487 for (let i = 0; i < namespacesList.length; i += 1) {
396 const aDom = liDom.children[0];488 const liDom = domListNamespaces.children[i];
397 const decl = namespacesList[i];489 const aDom = liDom.children[0];
398 aDom.textContent = declIndexName(decl);490 const original_decl = namespacesList[i].original;
399 aDom.setAttribute('href', navLinkDeclIndex(decl));491 const decl = namespacesList[i].member;
400 }492 aDom.textContent = declIndexName(original_decl);
401 domSectNamespaces.classList.remove("hidden");493 aDom.setAttribute('href', navLinkDeclIndex(decl));
402 }494 }
403495 domSectNamespaces.classList.remove("hidden");
404 if (errSetsList.length !== 0) {496 }
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);
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);511 if (fnsList.length !== 0) {
436 }512 resizeDomList(domListFns, fnsList.length,
437 domSectFns.classList.remove("hidden");513 '<div><dt><code></code></dt><dd></dd></div>');
438 }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.518 const dtDom = divDom.children[0];
441 const fields = declFields(decl_index).slice();519 const ddDocs = divDom.children[1];
442 if (fields.length !== 0) {520 const protoCodeDom = dtDom.children[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 }
450521
451 if (varsList.length !== 0) {522 protoCodeDom.innerHTML = fnProtoHtml(decl, true);
452 resizeDomList(domListGlobalVars, varsList.length,523 ddDocs.innerHTML = declDocsHtmlShort(decl);
453 '<tr><td><a href="#"></a></td><td></td><td></td></tr>');524 }
454 for (let i = 0; i < varsList.length; i += 1) {525 domSectFns.classList.remove("hidden");
455 const decl = varsList[i];526 }
456 const trDom = domListGlobalVars.children[i];
457527
458 const tdName = trDom.children[0];528 if (fields.length !== 0) {
459 const tdNameA = tdName.children[0];529 resizeDomList(domListFields, fields.length, '<div></div>');
460 const tdType = trDom.children[1];530 for (let i = 0; i < fields.length; i += 1) {
461 const tdDesc = trDom.children[2];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));537 if (varsList.length !== 0) {
464 tdNameA.textContent = declIndexName(decl);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);544 const tdName = trDom.children[0];
467 tdDesc.innerHTML = declDocsHtmlShort(decl);545 const tdNameA = tdName.children[0];
468 }546 const tdType = trDom.children[1];
469 domSectGlobalVars.classList.remove("hidden");547 const tdDesc = trDom.children[2];
470 }
471548
472 if (valsList.length !== 0) {549 tdNameA.setAttribute('href', navLinkDeclIndex(decl));
473 resizeDomList(domListValues, valsList.length,550 tdNameA.textContent = declIndexName(decl);
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];
478551
479 const tdName = trDom.children[0];552 tdType.innerHTML = declTypeHtml(decl);
480 const tdNameA = tdName.children[0];553 tdDesc.innerHTML = declDocsHtmlShort(decl);
481 const tdType = trDom.children[1];554 }
482 const tdDesc = trDom.children[2];555 domSectGlobalVars.classList.remove("hidden");
556 }
483557
484 tdNameA.setAttribute('href', navLinkDeclIndex(decl));558 if (valsList.length !== 0) {
485 tdNameA.textContent = declIndexName(decl);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);580 function renderNamespacePage(decl_index) {
488 tdDesc.innerHTML = declDocsHtmlShort(decl);581 renderNav(decl_index);
489 }582 renderDeclHeading(decl_index);
490 domSectValues.classList.remove("hidden");583 const members = namespaceMembers(decl_index, false).slice();
491 }584 const fields = declFields(decl_index).slice();
585 renderNamespace(decl_index, members, fields);
492 }586 }
493587
494 function operatorCompare(a, b) {588 function operatorCompare(a, b) {
...@@ -508,7 +602,7 @@...@@ -508,7 +602,7 @@
508 curNav.viewSourceHash = null;602 curNav.viewSourceHash = null;
509 curNavSearch = "";603 curNavSearch = "";
510604
511 if (location_hash[0] === '#' && location_hash.length > 1) {605 if (location_hash.length > 1 && location_hash[0] === '#') {
512 const query = location_hash.substring(1);606 const query = location_hash.substring(1);
513 const qpos = query.indexOf("?");607 const qpos = query.indexOf("?");
514 let nonSearchPart;608 let nonSearchPart;
...@@ -532,8 +626,14 @@...@@ -532,8 +626,14 @@
532 }626 }
533 }627 }
534628
535 function onHashChange() {629 function onHashChange(state) {
630 history.replaceState({}, "");
536 navigate(location.hash);631 navigate(location.hash);
632 if (state == null) window.scrollTo({top: 0});
633 }
634
635 function onPopState(ev) {
636 onHashChange(ev.state);
537 }637 }
538638
539 function navigate(location_hash) {639 function navigate(location_hash) {
...@@ -686,13 +786,19 @@...@@ -686,13 +786,19 @@
686786
687 function startAsyncSearch() {787 function startAsyncSearch() {
688 clearAsyncSearch();788 clearAsyncSearch();
689 searchTimer = setTimeout(startSearch, 100);789 searchTimer = setTimeout(startSearch, 10);
690 }790 }
691 function computeSearchHash() {791 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;
693 const parts = oldHash.split("?");799 const parts = oldHash.split("?");
694 const newPart2 = (domSearch.value === "") ? "" : ("?" + domSearch.value);800 const newPart2 = (domSearch.value === "") ? "" : ("?" + domSearch.value);
695 return (parts.length === 1) ? (oldHash + newPart2) : ("#" + parts[0] + newPart2);801 return parts[0] + newPart2;
696 }802 }
697 function startSearch() {803 function startSearch() {
698 clearAsyncSearch();804 clearAsyncSearch();
...@@ -734,12 +840,12 @@...@@ -734,12 +840,12 @@
734 }840 }
735 }841 }
736842
737 function updatePackageList() {843 function updateModuleList() {
738 packageList.length = 0;844 moduleList.length = 0;
739 for (let i = 0;; i += 1) {845 for (let i = 0;; i += 1) {
740 const name = unwrapString(wasm_exports.package_name(i));846 const name = unwrapString(wasm_exports.module_name(i));
741 if (name.length == 0) break;847 if (name.length == 0) break;
742 packageList.push(name);848 moduleList.push(name);
743 }849 }
744 }850 }
745851
...@@ -749,6 +855,12 @@...@@ -749,6 +855,12 @@
749 return operatorCompare(a_name, b_name);855 return operatorCompare(a_name, b_name);
750 }856 }
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
752 function decodeString(ptr, len) {864 function decodeString(ptr, len) {
753 if (len === 0) return "";865 if (len === 0) return "";
754 return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len));866 return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len));
...@@ -784,8 +896,8 @@...@@ -784,8 +896,8 @@
784 return unwrapString(wasm_exports.decl_doctest_html(decl_index));896 return unwrapString(wasm_exports.decl_doctest_html(decl_index));
785 }897 }
786898
787 function fnProtoHtml(decl_index) {899 function fnProtoHtml(decl_index, linkify_fn_name) {
788 return unwrapString(wasm_exports.decl_fn_proto_html(decl_index));900 return unwrapString(wasm_exports.decl_fn_proto_html(decl_index, linkify_fn_name));
789 }901 }
790902
791 function setQueryString(s) {903 function setQueryString(s) {
...@@ -805,19 +917,37 @@...@@ -805,19 +917,37 @@
805 }917 }
806918
807 function namespaceMembers(decl_index, include_private) {919 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) {
809 const ptr = Number(bigint & 0xffffffffn);940 const ptr = Number(bigint & 0xffffffffn);
810 const len = Number(bigint >> 32n);941 const len = Number(bigint >> 32n);
811 if (len == 0) return [];942 if (len === 0) return [];
812 return new Uint32Array(wasm_exports.memory.buffer, ptr, len);943 return new Uint32Array(wasm_exports.memory.buffer, ptr, len);
813 }944 }
814945
815 function declFields(decl_index) {946 function unwrapSlice64(bigint) {
816 const bigint = wasm_exports.decl_fields(decl_index);
817 const ptr = Number(bigint & 0xffffffffn);947 const ptr = Number(bigint & 0xffffffffn);
818 const len = Number(bigint >> 32n);948 const len = Number(bigint >> 32n);
819 if (len === 0) return [];949 if (len === 0) return [];
820 return new Uint32Array(wasm_exports.memory.buffer, ptr, len);950 return new BigUint64Array(wasm_exports.memory.buffer, ptr, len);
821 }951 }
822952
823 function findDecl(fqn) {953 function findDecl(fqn) {
...@@ -840,6 +970,12 @@...@@ -840,6 +970,12 @@
840 return result;970 return result;
841 }971 }
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
843 function setInputString(s) {979 function setInputString(s) {
844 const jsArray = text_encoder.encode(s);980 const jsArray = text_encoder.encode(s);
845 const len = jsArray.length;981 const len = jsArray.length;
lib/docs/wasm/Decl.zig+50-4
...@@ -111,8 +111,35 @@ pub fn categorize(decl: *const Decl) Walk.Category {...@@ -111,8 +111,35 @@ pub fn categorize(decl: *const Decl) Walk.Category {
111 return decl.file.categorize_decl(decl.ast_node);111 return decl.file.categorize_decl(decl.ast_node);
112}112}
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`.
114pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void {141pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void {
115 try decl.reset_with_path(out);142 try decl.append_path(out);
116 if (decl.parent != .none) {143 if (decl.parent != .none) {
117 try append_parent_ns(out, decl.parent);144 try append_parent_ns(out, decl.parent);
118 try out.appendSlice(gpa, decl.extra_info().name);145 try out.appendSlice(gpa, decl.extra_info().name);
...@@ -123,9 +150,13 @@ pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void {...@@ -123,9 +150,13 @@ pub fn fqn(decl: *const Decl, out: *std.ArrayListUnmanaged(u8)) Oom!void {
123150
124pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void {151pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void {
125 list.clearRetainingCapacity();152 list.clearRetainingCapacity();
153 try append_path(decl, list);
154}
126155
127 // Prefer the package name alias.156pub fn append_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom!void {
128 for (Walk.packages.keys(), Walk.packages.values()) |pkg_name, pkg_file| {157 const start = list.items.len;
158 // Prefer the module name alias.
159 for (Walk.modules.keys(), Walk.modules.values()) |pkg_name, pkg_file| {
129 if (pkg_file == decl.file) {160 if (pkg_file == decl.file) {
130 try list.ensureUnusedCapacity(gpa, pkg_name.len + 1);161 try list.ensureUnusedCapacity(gpa, pkg_name.len + 1);
131 list.appendSliceAssumeCapacity(pkg_name);162 list.appendSliceAssumeCapacity(pkg_name);
...@@ -137,7 +168,7 @@ pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom...@@ -137,7 +168,7 @@ pub fn reset_with_path(decl: *const Decl, list: *std.ArrayListUnmanaged(u8)) Oom
137 const file_path = decl.file.path();168 const file_path = decl.file.path();
138 try list.ensureUnusedCapacity(gpa, file_path.len + 1);169 try list.ensureUnusedCapacity(gpa, file_path.len + 1);
139 list.appendSliceAssumeCapacity(file_path);170 list.appendSliceAssumeCapacity(file_path);
140 for (list.items) |*byte| switch (byte.*) {171 for (list.items[start..]) |*byte| switch (byte.*) {
141 '/' => byte.* = '.',172 '/' => byte.* = '.',
142 else => continue,173 else => continue,
143 };174 };
...@@ -170,6 +201,21 @@ pub fn findFirstDocComment(ast: *const Ast, token: Ast.TokenIndex) Ast.TokenInde...@@ -170,6 +201,21 @@ pub fn findFirstDocComment(ast: *const Ast, token: Ast.TokenIndex) Ast.TokenInde
170 return it;201 return it;
171}202}
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
173const Decl = @This();219const Decl = @This();
174const std = @import("std");220const std = @import("std");
175const Ast = std.zig.Ast;221const Ast = std.zig.Ast;
lib/docs/wasm/Walk.zig+225-31
...@@ -1,21 +1,26 @@...@@ -1,21 +1,26 @@
1//! Find and annotate identifiers with links to their declarations.1//! Find and annotate identifiers with links to their declarations.
2pub var files: std.StringArrayHashMapUnmanaged(File) = .{};2pub var files: std.StringArrayHashMapUnmanaged(File) = .{};
3pub var decls: std.ArrayListUnmanaged(Decl) = .{};3pub var decls: std.ArrayListUnmanaged(Decl) = .{};
4pub var packages: std.StringArrayHashMapUnmanaged(File.Index) = .{};4pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{};
55
6arena: std.mem.Allocator,
7file: File.Index,6file: File.Index,
87
9/// keep in sync with "CAT_" constants in main.js8/// keep in sync with "CAT_" constants in main.js
10pub const Category = union(enum(u8)) {9pub const Category = union(enum(u8)) {
11 namespace: Ast.Node.Index,10 namespace: Ast.Node.Index,
12 global_variable: Ast.Node.Index,11 global_variable: Ast.Node.Index,
12 /// A function that has not been detected as returning a type.
13 function: Ast.Node.Index,13 function: Ast.Node.Index,
14 primitive: Ast.Node.Index,14 primitive: Ast.Node.Index,
15 error_set: Ast.Node.Index,15 error_set: Ast.Node.Index,
16 global_const: Ast.Node.Index,16 global_const: Ast.Node.Index,
17 alias: Decl.Index,17 alias: Decl.Index,
18 /// A primitive identifier that is also a type.
18 type,19 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
20 pub const Tag = @typeInfo(Category).Union.tag_type.?;25 pub const Tag = @typeInfo(Category).Union.tag_type.?;
21};26};
...@@ -30,12 +35,23 @@ pub const File = struct {...@@ -30,12 +35,23 @@ pub const File = struct {
30 node_decls: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Decl.Index) = .{},35 node_decls: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Decl.Index) = .{},
31 /// Maps function declarations to doctests.36 /// Maps function declarations to doctests.
32 doctests: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .{},37 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
34 pub fn lookup_token(file: *File, token: Ast.TokenIndex) Decl.Index {43 pub fn lookup_token(file: *File, token: Ast.TokenIndex) Decl.Index {
35 const decl_node = file.ident_decls.get(token) orelse return .none;44 const decl_node = file.ident_decls.get(token) orelse return .none;
36 return file.node_decls.get(decl_node) orelse return .none;45 return file.node_decls.get(decl_node) orelse return .none;
37 }46 }
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
39 pub const Index = enum(u32) {55 pub const Index = enum(u32) {
40 _,56 _,
4157
...@@ -90,16 +106,41 @@ pub const File = struct {...@@ -90,16 +106,41 @@ pub const File = struct {
90 .fn_proto_one,106 .fn_proto_one,
91 .fn_proto_simple,107 .fn_proto_simple,
92 .fn_decl,108 .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
95 else => unreachable,115 else => unreachable,
96 }116 }
97 }117 }
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
99 pub fn categorize_expr(file_index: File.Index, node: Ast.Node.Index) Category {137 pub fn categorize_expr(file_index: File.Index, node: Ast.Node.Index) Category {
138 const file = file_index.get();
100 const ast = file_index.get_ast();139 const ast = file_index.get_ast();
101 const node_tags = ast.nodes.items(.tag);140 const node_tags = ast.nodes.items(.tag);
102 const node_datas = ast.nodes.items(.data);141 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])});
103 return switch (node_tags[node]) {144 return switch (node_tags[node]) {
104 .container_decl,145 .container_decl,
105 .container_decl_trailing,146 .container_decl_trailing,
...@@ -116,24 +157,43 @@ pub const File = struct {...@@ -116,24 +157,43 @@ pub const File = struct {
116 => .{ .namespace = node },157 => .{ .namespace = node },
117158
118 .error_set_decl,159 .error_set_decl,
160 .merge_error_sets,
119 => .{ .error_set = node },161 => .{ .error_set = node },
120162
121 .identifier => {163 .identifier => {
122 const name_token = ast.nodes.items(.main_token)[node];164 const name_token = ast.nodes.items(.main_token)[node];
123 const ident_name = ast.tokenSlice(name_token);165 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))
125 return .{ .primitive = node };170 return .{ .primitive = node };
126 }
127171
128 const decl_index = file_index.get().lookup_token(name_token);172 if (std.zig.primitives.isPrimitive(ident_name))
129 if (decl_index != .none) return .{ .alias = decl_index };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
131 return .{ .global_const = node };181 return .{ .global_const = node };
132 },182 },
133183
134 .field_access => {184 .field_access => {
135 // TODO:185 const object_node = node_datas[node].lhs;
136 //return .alias;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
137 return .{ .global_const = node };197 return .{ .global_const = node };
138 },198 },
139199
...@@ -154,10 +214,77 @@ pub const File = struct {...@@ -154,10 +214,77 @@ pub const File = struct {
154 return categorize_builtin_call(file_index, node, params);214 return categorize_builtin_call(file_index, node, params);
155 },215 },
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
157 else => .{ .global_const = node },264 else => .{ .global_const = node },
158 };265 };
159 }266 }
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
161 fn categorize_builtin_call(288 fn categorize_builtin_call(
162 file_index: File.Index,289 file_index: File.Index,
163 node: Ast.Node.Index,290 node: Ast.Node.Index,
...@@ -172,6 +299,9 @@ pub const File = struct {...@@ -172,6 +299,9 @@ pub const File = struct {
172 const str_bytes = ast.tokenSlice(str_lit_token);299 const str_bytes = ast.tokenSlice(str_lit_token);
173 const file_path = std.zig.string_literal.parseAlloc(gpa, str_bytes) catch @panic("OOM");300 const file_path = std.zig.string_literal.parseAlloc(gpa, str_bytes) catch @panic("OOM");
174 defer gpa.free(file_path);301 defer gpa.free(file_path);
302 if (modules.get(file_path)) |imported_file_index| {
303 return .{ .alias = File.Index.findRootDecl(imported_file_index) };
304 }
175 const base_path = file_index.path();305 const base_path = file_index.path();
176 const resolved_path = std.fs.path.resolvePosix(gpa, &.{306 const resolved_path = std.fs.path.resolvePosix(gpa, &.{
177 base_path, "..", file_path,307 base_path, "..", file_path,
...@@ -180,8 +310,8 @@ pub const File = struct {...@@ -180,8 +310,8 @@ pub const File = struct {
180 log.debug("from '{s}' @import '{s}' resolved='{s}'", .{310 log.debug("from '{s}' @import '{s}' resolved='{s}'", .{
181 base_path, file_path, resolved_path,311 base_path, file_path, resolved_path,
182 });312 });
183 if (Walk.files.getIndex(resolved_path)) |imported_file_index| {313 if (files.getIndex(resolved_path)) |imported_file_index| {
184 return .{ .alias = Walk.File.Index.findRootDecl(@enumFromInt(imported_file_index)) };314 return .{ .alias = File.Index.findRootDecl(@enumFromInt(imported_file_index)) };
185 } else {315 } else {
186 log.warn("import target '{s}' did not resolve to any file", .{resolved_path});316 log.warn("import target '{s}' did not resolve to any file", .{resolved_path});
187 }317 }
...@@ -195,10 +325,47 @@ pub const File = struct {...@@ -195,10 +325,47 @@ pub const File = struct {
195325
196 return .{ .global_const = node };326 return .{ .global_const = node };
197 }327 }
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 }
198 };365 };
199};366};
200367
201pub const PackageIndex = enum(u32) {368pub const ModuleIndex = enum(u32) {
202 _,369 _,
203};370};
204371
...@@ -208,28 +375,25 @@ pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {...@@ -208,28 +375,25 @@ pub fn add_file(file_name: []const u8, bytes: []u8) !File.Index {
208 try files.put(gpa, file_name, .{ .ast = ast });375 try files.put(gpa, file_name, .{ .ast = ast });
209376
210 if (ast.errors.len > 0) {377 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()});378 log.err("can't index '{s}' because it has syntax errors", .{file_index.path()});
213 return file_index;379 return file_index;
214 }380 }
215381
216 var arena_instance = std.heap.ArenaAllocator.init(gpa);
217 defer arena_instance.deinit();
218
219 var w: Walk = .{382 var w: Walk = .{
220 .arena = arena_instance.allocator(),
221 .file = file_index,383 .file = file_index,
222 };384 };
223 var scope: Scope = .{ .tag = .top };385 const scope = try gpa.create(Scope);
386 scope.* = .{ .tag = .top };
224387
225 const decl_index = try file_index.add_decl(0, .none);388 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
228 const file = file_index.get();391 const file = file_index.get();
229 shrinkToFit(&file.ident_decls);392 shrinkToFit(&file.ident_decls);
230 shrinkToFit(&file.token_parents);393 shrinkToFit(&file.token_parents);
231 shrinkToFit(&file.node_decls);394 shrinkToFit(&file.node_decls);
232 shrinkToFit(&file.doctests);395 shrinkToFit(&file.doctests);
396 shrinkToFit(&file.scopes);
233397
234 return file_index;398 return file_index;
235}399}
...@@ -250,7 +414,7 @@ fn parse(source: []u8) Oom!Ast {...@@ -250,7 +414,7 @@ fn parse(source: []u8) Oom!Ast {
250 return Ast.parse(gpa, adjusted_source, .zig);414 return Ast.parse(gpa, adjusted_source, .zig);
251}415}
252416
253const Scope = struct {417pub const Scope = struct {
254 tag: Tag,418 tag: Tag,
255419
256 const Tag = enum { top, local, namespace };420 const Tag = enum { top, local, namespace };
...@@ -267,6 +431,7 @@ const Scope = struct {...@@ -267,6 +431,7 @@ const Scope = struct {
267 names: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .{},431 names: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .{},
268 doctests: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .{},432 doctests: std.StringArrayHashMapUnmanaged(Ast.Node.Index) = .{},
269 decl_index: Decl.Index,433 decl_index: Decl.Index,
434 field_count: u32,
270 };435 };
271436
272 fn getNamespaceDecl(start_scope: *Scope) Decl.Index {437 fn getNamespaceDecl(start_scope: *Scope) Decl.Index {
...@@ -284,7 +449,17 @@ const Scope = struct {...@@ -284,7 +449,17 @@ const Scope = struct {
284 };449 };
285 }450 }
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 {
288 const main_tokens = ast.nodes.items(.main_token);463 const main_tokens = ast.nodes.items(.main_token);
289 var it: *Scope = start_scope;464 var it: *Scope = start_scope;
290 while (true) switch (it.tag) {465 while (true) switch (it.tag) {
...@@ -314,17 +489,21 @@ fn struct_decl(...@@ -314,17 +489,21 @@ fn struct_decl(
314 w: *Walk,489 w: *Walk,
315 scope: *Scope,490 scope: *Scope,
316 parent_decl: Decl.Index,491 parent_decl: Decl.Index,
492 node: Ast.Node.Index,
317 container_decl: Ast.full.ContainerDecl,493 container_decl: Ast.full.ContainerDecl,
318) Oom!void {494) Oom!void {
319 const ast = w.file.get_ast();495 const ast = w.file.get_ast();
320 const node_tags = ast.nodes.items(.tag);496 const node_tags = ast.nodes.items(.tag);
321 const node_datas = ast.nodes.items(.data);497 const node_datas = ast.nodes.items(.data);
322498
323 var namespace: Scope.Namespace = .{499 const namespace = try gpa.create(Scope.Namespace);
500 namespace.* = .{
324 .parent = scope,501 .parent = scope,
325 .decl_index = parent_decl,502 .decl_index = parent_decl,
503 .field_count = 0,
326 };504 };
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
329 for (container_decl.ast.members) |member| switch (node_tags[member]) {508 for (container_decl.ast.members) |member| switch (node_tags[member]) {
330 .container_field_init,509 .container_field_init,
...@@ -584,7 +763,8 @@ fn expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index)...@@ -584,7 +763,8 @@ fn expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index)
584 => {763 => {
585 const full = ast.fullAsm(node).?;764 const full = ast.fullAsm(node).?;
586 for (full.ast.items) |n| {765 for (full.ast.items) |n| {
587 // TODO handle .asm_input, .asm_output766 // There is a missing call here to expr() for .asm_input and
767 // .asm_output nodes.
588 _ = n;768 _ = n;
589 }769 }
590 try expr(w, scope, parent_decl, full.ast.template);770 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)...@@ -701,7 +881,7 @@ fn expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, node: Ast.Node.Index)
701 .tagged_union_two_trailing,881 .tagged_union_two_trailing,
702 => {882 => {
703 var buf: [2]Ast.Node.Index = undefined;883 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).?);
705 },885 },
706886
707 .array_type_sentinel => {887 .array_type_sentinel => {
...@@ -803,7 +983,6 @@ fn block(...@@ -803,7 +983,6 @@ fn block(
803 statements: []const Ast.Node.Index,983 statements: []const Ast.Node.Index,
804) Oom!void {984) Oom!void {
805 const ast = w.file.get_ast();985 const ast = w.file.get_ast();
806 const arena = w.arena;
807 const node_tags = ast.nodes.items(.tag);986 const node_tags = ast.nodes.items(.tag);
808 const node_datas = ast.nodes.items(.data);987 const node_datas = ast.nodes.items(.data);
809988
...@@ -818,16 +997,17 @@ fn block(...@@ -818,16 +997,17 @@ fn block(
818 => {997 => {
819 const full = ast.fullVarDecl(node).?;998 const full = ast.fullVarDecl(node).?;
820 try global_var_decl(w, scope, parent_decl, full);999 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);
822 local.* = .{1001 local.* = .{
823 .parent = scope,1002 .parent = scope,
824 .var_node = node,1003 .var_node = node,
825 };1004 };
1005 try w.file.get().scopes.putNoClobber(gpa, node, &local.base);
826 scope = &local.base;1006 scope = &local.base;
827 },1007 },
8281008
829 .assign_destructure => {1009 .assign_destructure => {
830 // TODO1010 log.debug("walk assign_destructure not implemented yet", .{});
831 },1011 },
8321012
833 .grouped_expression => try expr(w, scope, parent_decl, node_datas[node].lhs),1013 .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...@@ -849,7 +1029,6 @@ fn while_expr(w: *Walk, scope: *Scope, parent_decl: Decl.Index, full: Ast.full.W
849}1029}
8501030
851fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.Index) Oom!void {1031fn 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();1032 const ast = w.file.get_ast();
854 const node_tags = ast.nodes.items(.tag);1033 const node_tags = ast.nodes.items(.tag);
855 const main_tokens = ast.nodes.items(.main_token);1034 const main_tokens = ast.nodes.items(.main_token);
...@@ -880,19 +1059,34 @@ fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.In...@@ -880,19 +1059,34 @@ fn scanDecls(w: *Walk, namespace: *Scope.Namespace, members: []const Ast.Node.In
880 const is_doctest = token_tags[ident_token] == .identifier;1059 const is_doctest = token_tags[ident_token] == .identifier;
881 if (is_doctest) {1060 if (is_doctest) {
882 const token_bytes = ast.tokenSlice(ident_token);1061 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);
884 }1063 }
885 continue;1064 continue;
886 },1065 },
8871066
1067 .container_field_init,
1068 .container_field_align,
1069 .container_field,
1070 => {
1071 namespace.field_count += 1;
1072 continue;
1073 },
1074
888 else => continue,1075 else => continue,
889 };1076 };
8901077
891 const token_bytes = ast.tokenSlice(name_token);1078 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);
893 }1080 }
894}1081}
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
896//test {1090//test {
897// const gpa = std.testing.allocator;1091// const gpa = std.testing.allocator;
898//1092//
lib/docs/wasm/main.zig+387-48
...@@ -1,3 +1,6 @@...@@ -1,3 +1,6 @@
1/// Delete this to find out where URL escaping needs to be added.
2const missing_feature_url_escape = true;
3
1const gpa = std.heap.wasm_allocator;4const gpa = std.heap.wasm_allocator;
25
3const std = @import("std");6const std = @import("std");
...@@ -80,7 +83,7 @@ export fn query_exec(ignore_case: bool) [*]Decl.Index {...@@ -80,7 +83,7 @@ export fn query_exec(ignore_case: bool) [*]Decl.Index {
80 return query_results.items.ptr;83 return query_results.items.ptr;
81}84}
8285
83const max_matched_items = 2000;86const max_matched_items = 1000;
8487
85fn query_exec_fallible(query: []const u8, ignore_case: bool) !void {88fn query_exec_fallible(query: []const u8, ignore_case: bool) !void {
86 const Score = packed struct(u32) {89 const Score = packed struct(u32) {
...@@ -181,7 +184,7 @@ fn query_exec_fallible(query: []const u8, ignore_case: bool) !void {...@@ -181,7 +184,7 @@ fn query_exec_fallible(query: []const u8, ignore_case: bool) !void {
181 const b_decl = query_results.items[b_index];184 const b_decl = query_results.items[b_index];
182 const a_file_path = a_decl.get().file.path();185 const a_file_path = a_decl.get().file.path();
183 const b_file_path = b_decl.get().file.path();186 const b_file_path = b_decl.get().file.path();
184 // TODO Also check the local namespace inside the file187 // This neglects to check the local namespace inside the file.
185 return std.mem.lessThan(u8, b_file_path, a_file_path);188 return std.mem.lessThan(u8, b_file_path, a_file_path);
186 }189 }
187 }190 }
...@@ -209,12 +212,178 @@ fn Slice(T: type) type {...@@ -209,12 +212,178 @@ fn Slice(T: type) type {
209 };212 };
210}213}
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
212var string_result: std.ArrayListUnmanaged(u8) = .{};256var 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
214export fn decl_fields(decl_index: Decl.Index) Slice(Ast.Node.Index) {379export 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"));380 return Slice(Ast.Node.Index).init(decl_fields_fallible(decl_index) catch @panic("OOM"));
216}381}
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
218fn decl_fields_fallible(decl_index: Decl.Index) ![]Ast.Node.Index {387fn decl_fields_fallible(decl_index: Decl.Index) ![]Ast.Node.Index {
219 const g = struct {388 const g = struct {
220 var result: std.ArrayListUnmanaged(Ast.Node.Index) = .{};389 var result: std.ArrayListUnmanaged(Ast.Node.Index) = .{};
...@@ -237,12 +406,38 @@ fn decl_fields_fallible(decl_index: Decl.Index) ![]Ast.Node.Index {...@@ -237,12 +406,38 @@ fn decl_fields_fallible(decl_index: Decl.Index) ![]Ast.Node.Index {
237 return g.result.items;406 return g.result.items;
238}407}
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
240export fn decl_field_html(decl_index: Decl.Index, field_node: Ast.Node.Index) String {429export fn decl_field_html(decl_index: Decl.Index, field_node: Ast.Node.Index) String {
241 string_result.clearRetainingCapacity();430 string_result.clearRetainingCapacity();
242 decl_field_html_fallible(&string_result, decl_index, field_node) catch @panic("OOM");431 decl_field_html_fallible(&string_result, decl_index, field_node) catch @panic("OOM");
243 return String.init(string_result.items);432 return String.init(string_result.items);
244}433}
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
246fn decl_field_html_fallible(441fn decl_field_html_fallible(
247 out: *std.ArrayListUnmanaged(u8),442 out: *std.ArrayListUnmanaged(u8),
248 decl_index: Decl.Index,443 decl_index: Decl.Index,
...@@ -251,7 +446,7 @@ fn decl_field_html_fallible(...@@ -251,7 +446,7 @@ fn decl_field_html_fallible(
251 const decl = decl_index.get();446 const decl = decl_index.get();
252 const ast = decl.file.get_ast();447 const ast = decl.file.get_ast();
253 try out.appendSlice(gpa, "<pre><code>");448 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, .{});
255 try out.appendSlice(gpa, "</code></pre>");450 try out.appendSlice(gpa, "</code></pre>");
256451
257 const field = ast.fullContainerField(field_node).?;452 const field = ast.fullContainerField(field_node).?;
...@@ -259,12 +454,48 @@ fn decl_field_html_fallible(...@@ -259,12 +454,48 @@ fn decl_field_html_fallible(
259454
260 if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) {455 if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) {
261 try out.appendSlice(gpa, "<div class=\"fieldDocs\">");456 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);
263 try out.appendSlice(gpa, "</div>");494 try out.appendSlice(gpa, "</div>");
264 }495 }
265}496}
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 {
268 const decl = decl_index.get();499 const decl = decl_index.get();
269 const ast = decl.file.get_ast();500 const ast = decl.file.get_ast();
270 const node_tags = ast.nodes.items(.tag);501 const node_tags = ast.nodes.items(.tag);
...@@ -282,7 +513,12 @@ export fn decl_fn_proto_html(decl_index: Decl.Index) String {...@@ -282,7 +513,12 @@ export fn decl_fn_proto_html(decl_index: Decl.Index) String {
282 };513 };
283514
284 string_result.clearRetainingCapacity();515 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| {
286 fatal("unable to render source: {s}", .{@errorName(err)});522 fatal("unable to render source: {s}", .{@errorName(err)});
287 };523 };
288 return String.init(string_result.items);524 return String.init(string_result.items);
...@@ -292,7 +528,7 @@ export fn decl_source_html(decl_index: Decl.Index) String {...@@ -292,7 +528,7 @@ export fn decl_source_html(decl_index: Decl.Index) String {
292 const decl = decl_index.get();528 const decl = decl_index.get();
293529
294 string_result.clearRetainingCapacity();530 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| {
296 fatal("unable to render source: {s}", .{@errorName(err)});532 fatal("unable to render source: {s}", .{@errorName(err)});
297 };533 };
298 return String.init(string_result.items);534 return String.init(string_result.items);
...@@ -304,7 +540,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String {...@@ -304,7 +540,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String {
304 return String.init("");540 return String.init("");
305541
306 string_result.clearRetainingCapacity();542 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| {
308 fatal("unable to render source: {s}", .{@errorName(err)});544 fatal("unable to render source: {s}", .{@errorName(err)});
309 };545 };
310 return String.init(string_result.items);546 return String.init(string_result.items);
...@@ -312,6 +548,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String {...@@ -312,6 +548,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String {
312548
313export fn decl_fqn(decl_index: Decl.Index) String {549export fn decl_fqn(decl_index: Decl.Index) String {
314 const decl = decl_index.get();550 const decl = decl_index.get();
551 string_result.clearRetainingCapacity();
315 decl.fqn(&string_result) catch @panic("OOM");552 decl.fqn(&string_result) catch @panic("OOM");
316 return String.init(string_result.items);553 return String.init(string_result.items);
317}554}
...@@ -321,6 +558,20 @@ export fn decl_parent(decl_index: Decl.Index) Decl.Index {...@@ -321,6 +558,20 @@ export fn decl_parent(decl_index: Decl.Index) Decl.Index {
321 return decl.parent;558 return decl.parent;
322}559}
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
324export fn decl_file_path(decl_index: Decl.Index) String {575export fn decl_file_path(decl_index: Decl.Index) String {
325 string_result.clearRetainingCapacity();576 string_result.clearRetainingCapacity();
326 string_result.appendSlice(gpa, decl_index.get().file.path()) catch @panic("OOM");577 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 {...@@ -350,7 +601,8 @@ export fn decl_category_name(decl_index: Decl.Index) String {
350 },601 },
351 .global_variable => "Global Variable",602 .global_variable => "Global Variable",
352 .function => "Function",603 .function => "Function",
353 .type => "Type",604 .type_function => "Type Function",
605 .type, .type_type => "Type",
354 .error_set => "Error Set",606 .error_set => "Error Set",
355 .global_const => "Constant",607 .global_const => "Constant",
356 .primitive => "Primitive Value",608 .primitive => "Primitive Value",
...@@ -375,9 +627,8 @@ export fn decl_name(decl_index: Decl.Index) String {...@@ -375,9 +627,8 @@ export fn decl_name(decl_index: Decl.Index) String {
375627
376export fn decl_docs_html(decl_index: Decl.Index, short: bool) String {628export fn decl_docs_html(decl_index: Decl.Index, short: bool) String {
377 const decl = decl_index.get();629 const decl = decl_index.get();
378 const ast = decl.file.get_ast();
379 string_result.clearRetainingCapacity();630 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");
381 return String.init(string_result.items);632 return String.init(string_result.items);
382}633}
383634
...@@ -402,10 +653,12 @@ fn collect_docs(...@@ -402,10 +653,12 @@ fn collect_docs(
402653
403fn render_docs(654fn render_docs(
404 out: *std.ArrayListUnmanaged(u8),655 out: *std.ArrayListUnmanaged(u8),
405 ast: *const Ast,656 decl_index: Decl.Index,
406 first_doc_comment: Ast.TokenIndex,657 first_doc_comment: Ast.TokenIndex,
407 short: bool,658 short: bool,
408) Oom!void {659) Oom!void {
660 const decl = decl_index.get();
661 const ast = decl.file.get_ast();
409 const token_tags = ast.tokens.items(.tag);662 const token_tags = ast.tokens.items(.tag);
410663
411 var parser = try markdown.Parser.init(gpa);664 var parser = try markdown.Parser.init(gpa);
...@@ -423,10 +676,14 @@ fn render_docs(...@@ -423,10 +676,14 @@ fn render_docs(
423 var parsed_doc = try parser.endInput();676 var parsed_doc = try parser.endInput();
424 defer parsed_doc.deinit(gpa);677 defer parsed_doc.deinit(gpa);
425678
679 const g = struct {
680 var link_buffer: std.ArrayListUnmanaged(u8) = .{};
681 };
682
426 const Writer = std.ArrayListUnmanaged(u8).Writer;683 const Writer = std.ArrayListUnmanaged(u8).Writer;
427 const Renderer = markdown.Renderer(Writer, void);684 const Renderer = markdown.Renderer(Writer, Decl.Index);
428 const renderer: Renderer = .{685 const renderer: Renderer = .{
429 .context = {},686 .context = decl_index,
430 .renderFn = struct {687 .renderFn = struct {
431 fn render(688 fn render(
432 r: Renderer,689 r: Renderer,
...@@ -436,23 +693,22 @@ fn render_docs(...@@ -436,23 +693,22 @@ fn render_docs(
436 ) !void {693 ) !void {
437 const data = doc.nodes.items(.data)[@intFromEnum(node)];694 const data = doc.nodes.items(.data)[@intFromEnum(node)];
438 switch (doc.nodes.items(.tag)[@intFromEnum(node)]) {695 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 => {696 .code_span => {
697 try writer.writeAll("<code>");
450 const content = doc.string(data.text.content);698 const content = doc.string(data.text.content);
451 try writer.print("<code>{}</code>", .{markdown.fmtHtml(content)});699 if (resolve_decl_path(r.context, content)) |resolved_decl_index| {
452 },700 g.link_buffer.clearRetainingCapacity();
453 .text => {701 try resolve_decl_link(resolved_decl_index, &g.link_buffer);
454 const content = doc.string(data.text.content);702
455 try writer.print("{}", .{markdown.fmtHtml(content)});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>");
456 },712 },
457713
458 else => try Renderer.renderDefault(r, doc, node, writer),714 else => try Renderer.renderDefault(r, doc, node, writer),
...@@ -463,12 +719,39 @@ fn render_docs(...@@ -463,12 +719,39 @@ fn render_docs(
463 try renderer.render(parsed_doc, out.writer(gpa));719 try renderer.render(parsed_doc, out.writer(gpa));
464}720}
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
466export fn decl_type_html(decl_index: Decl.Index) String {735export fn decl_type_html(decl_index: Decl.Index) String {
467 const decl = decl_index.get();736 const decl = decl_index.get();
468 const ast = decl.file.get_ast();737 const ast = decl.file.get_ast();
469 string_result.clearRetainingCapacity();738 string_result.clearRetainingCapacity();
470 _ = ast; // TODO739 t: {
471 string_result.appendSlice(gpa, "TODO_type_here") catch @panic("OOM");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 }
472 return String.init(string_result.items);755 return String.init(string_result.items);
473}756}
474757
...@@ -491,7 +774,7 @@ fn unpack_inner(tar_bytes: []u8) !void {...@@ -491,7 +774,7 @@ fn unpack_inner(tar_bytes: []u8) !void {
491 const file_name = try gpa.dupe(u8, tar_file.name);774 const file_name = try gpa.dupe(u8, tar_file.name);
492 if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| {775 if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| {
493 const pkg_name = file_name[0..pkg_name_end];776 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);
495 const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len);778 const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len);
496 if (!gop.found_existing or779 if (!gop.found_existing or
497 std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or780 std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or
...@@ -527,13 +810,13 @@ fn ascii_lower(bytes: []u8) void {...@@ -527,13 +810,13 @@ fn ascii_lower(bytes: []u8) void {
527 for (bytes) |*b| b.* = std.ascii.toLower(b.*);810 for (bytes) |*b| b.* = std.ascii.toLower(b.*);
528}811}
529812
530export fn package_name(index: u32) String {813export fn module_name(index: u32) String {
531 const names = Walk.packages.keys();814 const names = Walk.modules.keys();
532 return String.init(if (index >= names.len) "" else names[index]);815 return String.init(if (index >= names.len) "" else names[index]);
533}816}
534817
535export fn find_package_root(pkg: Walk.PackageIndex) Decl.Index {818export fn find_module_root(pkg: Walk.ModuleIndex) Decl.Index {
536 const root_file = Walk.packages.values()[@intFromEnum(pkg)];819 const root_file = Walk.modules.values()[@intFromEnum(pkg)];
537 const result = root_file.findRootDecl();820 const result = root_file.findRootDecl();
538 assert(result != .none);821 assert(result != .none);
539 return result;822 return result;
...@@ -555,11 +838,17 @@ export fn find_file_root() Decl.Index {...@@ -555,11 +838,17 @@ export fn find_file_root() Decl.Index {
555}838}
556839
557/// Uses `input_string`.840/// Uses `input_string`.
841/// Tries to look up the Decl component-wise but then falls back to a file path
842/// based scan.
558export fn find_decl() Decl.Index {843export fn find_decl() Decl.Index {
844 const result = Decl.find(input_string.items);
845 if (result != .none) return result;
846
559 const g = struct {847 const g = struct {
560 var match_fqn: std.ArrayListUnmanaged(u8) = .{};848 var match_fqn: std.ArrayListUnmanaged(u8) = .{};
561 };849 };
562 for (Walk.decls.items, 0..) |*decl, decl_index| {850 for (Walk.decls.items, 0..) |*decl, decl_index| {
851 g.match_fqn.clearRetainingCapacity();
563 decl.fqn(&g.match_fqn) catch @panic("OOM");852 decl.fqn(&g.match_fqn) catch @panic("OOM");
564 if (std.mem.eql(u8, g.match_fqn.items, input_string.items)) {853 if (std.mem.eql(u8, g.match_fqn.items, input_string.items)) {
565 //const path = @as(Decl.Index, @enumFromInt(decl_index)).get().file.path();854 //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...@@ -583,7 +872,7 @@ export fn categorize_decl(decl_index: Decl.Index, resolve_alias_count: usize) Wa
583 var decl = decl_index.get();872 var decl = decl_index.get();
584 while (true) {873 while (true) {
585 const result = decl.categorize();874 const result = decl.categorize();
586 switch (decl.categorize()) {875 switch (result) {
587 .alias => |new_index| {876 .alias => |new_index| {
588 assert(new_index != .none);877 assert(new_index != .none);
589 global_aliasee = new_index;878 global_aliasee = new_index;
...@@ -599,6 +888,10 @@ export fn categorize_decl(decl_index: Decl.Index, resolve_alias_count: usize) Wa...@@ -599,6 +888,10 @@ export fn categorize_decl(decl_index: Decl.Index, resolve_alias_count: usize) Wa
599 }888 }
600}889}
601890
891export fn type_fn_members(parent: Decl.Index, include_private: bool) Slice(Decl.Index) {
892 return namespace_members(parent, include_private);
893}
894
602export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Decl.Index) {895export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Decl.Index) {
603 const g = struct {896 const g = struct {
604 var members: std.ArrayListUnmanaged(Decl.Index) = .{};897 var members: std.ArrayListUnmanaged(Decl.Index) = .{};
...@@ -617,10 +910,18 @@ export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Dec...@@ -617,10 +910,18 @@ export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Dec
617 return Slice(Decl.Index).init(g.members.items);910 return Slice(Decl.Index).init(g.members.items);
618}911}
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
620fn file_source_html(920fn file_source_html(
621 file_index: Walk.File.Index,921 file_index: Walk.File.Index,
622 out: *std.ArrayListUnmanaged(u8),922 out: *std.ArrayListUnmanaged(u8),
623 root_node: Ast.Node.Index,923 root_node: Ast.Node.Index,
924 options: RenderSourceOptions,
624) !void {925) !void {
625 const ast = file_index.get_ast();926 const ast = file_index.get_ast();
626 const file = file_index.get();927 const file = file_index.get();
...@@ -631,6 +932,7 @@ fn file_source_html(...@@ -631,6 +932,7 @@ fn file_source_html(
631932
632 const token_tags = ast.tokens.items(.tag);933 const token_tags = ast.tokens.items(.tag);
633 const token_starts = ast.tokens.items(.start);934 const token_starts = ast.tokens.items(.start);
935 const main_tokens = ast.nodes.items(.main_token);
634936
635 const start_token = ast.firstToken(root_node);937 const start_token = ast.firstToken(root_node);
636 const end_token = ast.lastToken(root_node) + 1;938 const end_token = ast.lastToken(root_node) + 1;
...@@ -643,7 +945,20 @@ fn file_source_html(...@@ -643,7 +945,20 @@ fn file_source_html(
643 start_token..,945 start_token..,
644 ) |tag, start, token_index| {946 ) |tag, start, token_index| {
645 const between = ast.source[cursor..start];947 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 }
647 if (tag == .eof) break;962 if (tag == .eof) break;
648 const slice = ast.tokenSlice(token_index);963 const slice = ast.tokenSlice(token_index);
649 cursor = start + slice.len;964 cursor = start + slice.len;
...@@ -723,17 +1038,36 @@ fn file_source_html(...@@ -723,17 +1038,36 @@ fn file_source_html(
723 .doc_comment,1038 .doc_comment,
724 .container_doc_comment,1039 .container_doc_comment,
725 => {1040 => {
726 try out.appendSlice(gpa, "<span class=\"tok-comment\">");1041 if (!options.skip_doc_comments) {
727 try appendEscaped(out, slice);1042 try out.appendSlice(gpa, "<span class=\"tok-comment\">");
728 try out.appendSlice(gpa, "</span>");1043 try appendEscaped(out, slice);
1044 try out.appendSlice(gpa, "</span>");
1045 }
729 },1046 },
7301047
731 .identifier => i: {1048 .identifier => i: {
732 if (std.mem.eql(u8, slice, "undefined") or1049 if (options.fn_link != .none) {
733 std.mem.eql(u8, slice, "null") or1050 const fn_link = options.fn_link.get();
734 std.mem.eql(u8, slice, "true") or1051 const fn_token = main_tokens[fn_link.ast_node];
735 std.mem.eql(u8, slice, "false"))1052 if (token_index == fn_token + 1) {
736 {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)) {
737 try out.appendSlice(gpa, "<span class=\"tok-null\">");1071 try out.appendSlice(gpa, "<span class=\"tok-null\">");
738 try appendEscaped(out, slice);1072 try appendEscaped(out, slice);
739 try out.appendSlice(gpa, "</span>");1073 try out.appendSlice(gpa, "</span>");
...@@ -752,7 +1086,8 @@ fn file_source_html(...@@ -752,7 +1086,8 @@ fn file_source_html(
752 try walk_field_accesses(file_index, &g.field_access_buffer, field_access_node);1086 try walk_field_accesses(file_index, &g.field_access_buffer, field_access_node);
753 if (g.field_access_buffer.items.len > 0) {1087 if (g.field_access_buffer.items.len > 0) {
754 try out.appendSlice(gpa, "<a href=\"#");1088 try out.appendSlice(gpa, "<a href=\"#");
755 try out.appendSlice(gpa, g.field_access_buffer.items); // TODO url escape1089 _ = missing_feature_url_escape;
1090 try out.appendSlice(gpa, g.field_access_buffer.items);
756 try out.appendSlice(gpa, "\">");1091 try out.appendSlice(gpa, "\">");
757 try appendEscaped(out, slice);1092 try appendEscaped(out, slice);
758 try out.appendSlice(gpa, "</a>");1093 try out.appendSlice(gpa, "</a>");
...@@ -767,7 +1102,8 @@ fn file_source_html(...@@ -767,7 +1102,8 @@ fn file_source_html(
767 try resolve_ident_link(file_index, &g.field_access_buffer, token_index);1102 try resolve_ident_link(file_index, &g.field_access_buffer, token_index);
768 if (g.field_access_buffer.items.len > 0) {1103 if (g.field_access_buffer.items.len > 0) {
769 try out.appendSlice(gpa, "<a href=\"#");1104 try out.appendSlice(gpa, "<a href=\"#");
770 try out.appendSlice(gpa, g.field_access_buffer.items); // TODO url escape1105 _ = missing_feature_url_escape;
1106 try out.appendSlice(gpa, g.field_access_buffer.items);
771 try out.appendSlice(gpa, "\">");1107 try out.appendSlice(gpa, "\">");
772 try appendEscaped(out, slice);1108 try appendEscaped(out, slice);
773 try out.appendSlice(gpa, "</a>");1109 try out.appendSlice(gpa, "</a>");
...@@ -860,7 +1196,10 @@ fn resolve_ident_link(...@@ -860,7 +1196,10 @@ fn resolve_ident_link(
860) Oom!void {1196) Oom!void {
861 const decl_index = file_index.get().lookup_token(ident_token);1197 const decl_index = file_index.get().lookup_token(ident_token);
862 if (decl_index == .none) return;1198 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 {
864 const decl = decl_index.get();1203 const decl = decl_index.get();
865 switch (decl.categorize()) {1204 switch (decl.categorize()) {
866 .alias => |alias_decl| try alias_decl.get().fqn(out),1205 .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" {...@@ -517,6 +517,10 @@ test "tables require leading and trailing pipes" {
517 \\517 \\
518 \\| But | this | is |518 \\| But | this | is |
519 \\519 \\
520 \\Also not a table:
521 \\|
522 \\ |
523 \\
520 ,524 ,
521 \\<p>Not | a | table</p>525 \\<p>Not | a | table</p>
522 \\<table>526 \\<table>
...@@ -526,6 +530,9 @@ test "tables require leading and trailing pipes" {...@@ -526,6 +530,9 @@ test "tables require leading and trailing pipes" {
526 \\<td>is</td>530 \\<td>is</td>
527 \\</tr>531 \\</tr>
528 \\</table>532 \\</table>
533 \\<p>Also not a table:
534 \\|
535 \\|</p>
529 \\536 \\
530 );537 );
531}538}
...@@ -584,7 +591,7 @@ test "code blocks" {...@@ -584,7 +591,7 @@ test "code blocks" {
584 \\<pre><code>Hello, world!591 \\<pre><code>Hello, world!
585 \\This is some code.592 \\This is some code.
586 \\</code></pre>593 \\</code></pre>
587 \\<pre><code class="zig test">const std = @import(&quot;std&quot;);594 \\<pre><code>const std = @import(&quot;std&quot;);
588 \\595 \\
589 \\test {596 \\test {
590 \\ try std.testing.expect(2 + 2 == 4);597 \\ try std.testing.expect(2 + 2 == 4);
lib/docs/wasm/markdown/Parser.zig+2-1
...@@ -610,7 +610,8 @@ const TableRowStart = struct {...@@ -610,7 +610,8 @@ const TableRowStart = struct {
610};610};
611611
612fn startTableRow(unindented_line: []const u8) ?TableRowStart {612fn startTableRow(unindented_line: []const u8) ?TableRowStart {
613 if (!mem.startsWith(u8, unindented_line, "|") or613 if (unindented_line.len < 2 or
614 !mem.startsWith(u8, unindented_line, "|") or
614 mem.endsWith(u8, unindented_line, "\\|") or615 mem.endsWith(u8, unindented_line, "\\|") or
615 !mem.endsWith(u8, unindented_line, "|")) return null;616 !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 {...@@ -112,13 +112,8 @@ pub fn Renderer(comptime Writer: type, comptime Context: type) type {
112 try writer.print("</h{}>\n", .{data.heading.level});112 try writer.print("</h{}>\n", .{data.heading.level});
113 },113 },
114 .code_block => {114 .code_block => {
115 const tag = doc.string(data.code_block.tag);
116 const content = doc.string(data.code_block.content);115 const content = doc.string(data.code_block.content);
117 if (tag.len > 0) {116 try writer.print("<pre><code>{}</code></pre>\n", .{fmtHtml(content)});
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 },117 },
123 .blockquote => {118 .blockquote => {
124 try writer.writeAll("<blockquote>\n");119 try writer.writeAll("<blockquote>\n");