odin_html_docs_main.odin 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144
  1. package odin_html_docs
  2. import doc "core:odin/doc-format"
  3. import "core:fmt"
  4. import "core:io"
  5. import "core:os"
  6. import "core:strings"
  7. import "core:path/slashpath"
  8. import "core:sort"
  9. import "core:slice"
  10. GITHUB_CORE_URL :: "https://github.com/odin-lang/Odin/tree/master/core"
  11. header: ^doc.Header
  12. files: []doc.File
  13. pkgs: []doc.Pkg
  14. entities: []doc.Entity
  15. types: []doc.Type
  16. pkgs_to_use: map[string]^doc.Pkg // trimmed path
  17. pkg_to_path: map[^doc.Pkg]string // trimmed path
  18. array :: proc(a: $A/doc.Array($T)) -> []T {
  19. return doc.from_array(header, a)
  20. }
  21. str :: proc(s: $A/doc.String) -> string {
  22. return doc.from_string(header, s)
  23. }
  24. errorf :: proc(format: string, args: ..any) -> ! {
  25. fmt.eprintf("%s ", os.args[0])
  26. fmt.eprintf(format, ..args)
  27. fmt.eprintln()
  28. os.exit(1)
  29. }
  30. base_type :: proc(t: doc.Type) -> doc.Type {
  31. t := t
  32. for {
  33. if t.kind != .Named {
  34. break
  35. }
  36. t = types[array(t.types)[0]]
  37. }
  38. return t
  39. }
  40. is_type_untyped :: proc(type: doc.Type) -> bool {
  41. if type.kind == .Basic {
  42. flags := transmute(doc.Type_Flags_Basic)type.flags
  43. return .Untyped in flags
  44. }
  45. return false
  46. }
  47. common_prefix :: proc(strs: []string) -> string {
  48. if len(strs) == 0 {
  49. return ""
  50. }
  51. n := max(int)
  52. for str in strs {
  53. n = min(n, len(str))
  54. }
  55. prefix := strs[0][:n]
  56. for str in strs[1:] {
  57. for len(prefix) != 0 && str[:len(prefix)] != prefix {
  58. prefix = prefix[:len(prefix)-1]
  59. }
  60. if len(prefix) == 0 {
  61. break
  62. }
  63. }
  64. return prefix
  65. }
  66. recursive_make_directory :: proc(path: string, prefix := "") {
  67. head, _, tail := strings.partition(path, "/")
  68. path_to_make := head
  69. if prefix != "" {
  70. path_to_make = fmt.tprintf("%s/%s", prefix, head)
  71. }
  72. os.make_directory(path_to_make, 0)
  73. if tail != "" {
  74. recursive_make_directory(tail, path_to_make)
  75. }
  76. }
  77. write_html_header :: proc(w: io.Writer, title: string) {
  78. fmt.wprintf(w, string(#load("header.txt.html")), title)
  79. }
  80. write_html_footer :: proc(w: io.Writer, include_directory_js: bool) {
  81. fmt.wprintf(w, "\n")
  82. io.write(w, #load("footer.txt.html"))
  83. if include_directory_js {
  84. io.write_string(w, `
  85. <script type="text/javascript">
  86. (function (win, doc) {
  87. 'use strict';
  88. if (!doc.querySelectorAll || !win.addEventListener) {
  89. // doesn't cut the mustard.
  90. return;
  91. }
  92. let toggles = doc.querySelectorAll('[aria-controls]');
  93. for (let i = 0; i < toggles.length; i = i + 1) {
  94. let toggleID = toggles[i].getAttribute('aria-controls');
  95. if (doc.getElementById(toggleID)) {
  96. let togglecontent = doc.getElementById(toggleID);
  97. togglecontent.setAttribute('aria-hidden', 'true');
  98. togglecontent.setAttribute('tabindex', '-1');
  99. toggles[i].setAttribute('aria-expanded', 'false');
  100. }
  101. }
  102. function toggle(ev) {
  103. ev = ev || win.event;
  104. var target = ev.target || ev.srcElement;
  105. if (target.hasAttribute('data-aria-owns')) {
  106. let toggleIDs = target.getAttribute('data-aria-owns').match(/[^ ]+/g);
  107. toggleIDs.forEach(toggleID => {
  108. if (doc.getElementById(toggleID)) {
  109. ev.preventDefault();
  110. let togglecontent = doc.getElementById(toggleID);
  111. if (togglecontent.getAttribute('aria-hidden') == 'true') {
  112. togglecontent.setAttribute('aria-hidden', 'false');
  113. target.setAttribute('aria-expanded', 'true');
  114. if (target.tagName == 'A') {
  115. togglecontent.focus();
  116. }
  117. } else {
  118. togglecontent.setAttribute('aria-hidden', 'true');
  119. target.setAttribute('aria-expanded', 'false');
  120. }
  121. }
  122. })
  123. }
  124. }
  125. doc.addEventListener('click', toggle, false);
  126. }(this, this.document));
  127. </script>`)
  128. }
  129. fmt.wprintf(w, "</body>\n</html>\n")
  130. }
  131. main :: proc() {
  132. if len(os.args) != 2 {
  133. errorf("expected 1 .odin-doc file")
  134. }
  135. data, ok := os.read_entire_file(os.args[1])
  136. if !ok {
  137. errorf("unable to read file:", os.args[1])
  138. }
  139. err: doc.Reader_Error
  140. header, err = doc.read_from_bytes(data)
  141. switch err {
  142. case .None:
  143. case .Header_Too_Small:
  144. errorf("file is too small for the file format")
  145. case .Invalid_Magic:
  146. errorf("invalid magic for the file format")
  147. case .Data_Too_Small:
  148. errorf("data is too small for the file format")
  149. case .Invalid_Version:
  150. errorf("invalid file format version")
  151. }
  152. files = array(header.files)
  153. pkgs = array(header.pkgs)
  154. entities = array(header.entities)
  155. types = array(header.types)
  156. {
  157. fullpaths: [dynamic]string
  158. defer delete(fullpaths)
  159. for pkg in pkgs[1:] {
  160. append(&fullpaths, str(pkg.fullpath))
  161. }
  162. path_prefix := common_prefix(fullpaths[:])
  163. pkgs_to_use = make(map[string]^doc.Pkg)
  164. fullpath_loop: for fullpath, i in fullpaths {
  165. path := strings.trim_prefix(fullpath, path_prefix)
  166. if !strings.has_prefix(path, "core/") {
  167. continue fullpath_loop
  168. }
  169. pkg := &pkgs[i+1]
  170. if len(array(pkg.entities)) == 0 {
  171. continue fullpath_loop
  172. }
  173. trimmed_path := strings.trim_prefix(path, "core/")
  174. if strings.has_prefix(trimmed_path, "sys") {
  175. continue fullpath_loop
  176. }
  177. pkgs_to_use[trimmed_path] = pkg
  178. }
  179. sort.map_entries_by_key(&pkgs_to_use)
  180. for path, pkg in pkgs_to_use {
  181. pkg_to_path[pkg] = path
  182. }
  183. }
  184. b := strings.make_builder()
  185. defer strings.destroy_builder(&b)
  186. w := strings.to_writer(&b)
  187. {
  188. strings.reset_builder(&b)
  189. write_html_header(w, "core library - pkg.odin-lang.org")
  190. write_core_directory(w)
  191. write_html_footer(w, true)
  192. os.make_directory("core", 0)
  193. os.write_entire_file("core/index.html", b.buf[:])
  194. }
  195. for path, pkg in pkgs_to_use {
  196. strings.reset_builder(&b)
  197. write_html_header(w, fmt.tprintf("package %s - pkg.odin-lang.org", path))
  198. write_pkg(w, path, pkg)
  199. write_html_footer(w, false)
  200. recursive_make_directory(path, "core")
  201. os.write_entire_file(fmt.tprintf("core/%s/index.html", path), b.buf[:])
  202. }
  203. }
  204. write_core_directory :: proc(w: io.Writer) {
  205. Node :: struct {
  206. dir: string,
  207. path: string,
  208. name: string,
  209. pkg: ^doc.Pkg,
  210. next: ^Node,
  211. first_child: ^Node,
  212. }
  213. add_child :: proc(parent: ^Node, child: ^Node) -> ^Node {
  214. assert(parent != nil)
  215. end := &parent.first_child
  216. for end^ != nil {
  217. end = &end^.next
  218. }
  219. child.next = end^
  220. end^ = child
  221. return child
  222. }
  223. root: Node
  224. for path, pkg in pkgs_to_use {
  225. dir, _, inner := strings.partition(path, "/")
  226. node: ^Node = nil
  227. for node = root.first_child; node != nil; node = node.next {
  228. if node.dir == dir {
  229. break
  230. }
  231. }
  232. if inner == "" {
  233. if node == nil {
  234. add_child(&root, new_clone(Node{
  235. dir = dir,
  236. name = dir,
  237. path = path,
  238. pkg = pkg,
  239. }))
  240. } else {
  241. node.dir = dir
  242. node.name = dir
  243. node.path = path
  244. node.pkg = pkg
  245. }
  246. } else {
  247. if node == nil {
  248. node = add_child(&root, new_clone(Node{
  249. dir = dir,
  250. name = dir,
  251. }))
  252. }
  253. assert(node != nil)
  254. child := add_child(node, new_clone(Node{
  255. dir = dir,
  256. name = inner,
  257. path = path,
  258. pkg = pkg,
  259. }))
  260. }
  261. }
  262. fmt.wprintln(w, `<div class="row odin-main">`)
  263. defer fmt.wprintln(w, `</div>`)
  264. fmt.wprintln(w, `<article class="col-lg-12 p-4">`)
  265. defer fmt.wprintln(w, `</article>`)
  266. fmt.wprintln(w, "<article>")
  267. fmt.wprintln(w, "<header>")
  268. fmt.wprintln(w, "<h1>Core Library Collection</h1>")
  269. fmt.wprintln(w, "</header>")
  270. fmt.wprintln(w, "</article>")
  271. fmt.wprintln(w, "<div>")
  272. fmt.wprintln(w, "\t<table class=\"doc-directory mt-4 mb-4\">")
  273. fmt.wprintln(w, "\t\t<tbody>")
  274. for dir := root.first_child; dir != nil; dir = dir.next {
  275. if dir.first_child != nil {
  276. fmt.wprint(w, `<tr aria-controls="`)
  277. for child := dir.first_child; child != nil; child = child.next {
  278. fmt.wprintf(w, "pkg-%s ", str(child.pkg.name))
  279. }
  280. fmt.wprint(w, `" class="directory-pkg"><td class="pkg-line pkg-name" data-aria-owns="`)
  281. for child := dir.first_child; child != nil; child = child.next {
  282. fmt.wprintf(w, "pkg-%s ", str(child.pkg.name))
  283. }
  284. fmt.wprintf(w, `" id="pkg-%s">`, dir.dir)
  285. } else {
  286. fmt.wprintf(w, `<tr id="pkg-%s" class="directory-pkg"><td class="pkg-name">`, dir.dir)
  287. }
  288. if dir.pkg != nil {
  289. fmt.wprintf(w, `<a href="/core/%s">%s</a>`, dir.path, dir.name)
  290. } else {
  291. fmt.wprintf(w, "%s", dir.name)
  292. }
  293. io.write_string(w, `</td>`)
  294. io.write_string(w, `<td class="pkg-line pkg-line-doc">`)
  295. if dir.pkg != nil {
  296. line_doc, _, _ := strings.partition(str(dir.pkg.docs), "\n")
  297. line_doc = strings.trim_space(line_doc)
  298. if line_doc != "" {
  299. write_doc_line(w, line_doc)
  300. }
  301. }
  302. io.write_string(w, `</td>`)
  303. fmt.wprintf(w, "</tr>\n")
  304. for child := dir.first_child; child != nil; child = child.next {
  305. assert(child.pkg != nil)
  306. fmt.wprintf(w, `<tr id="pkg-%s" class="directory-pkg directory-child"><td class="pkg-line pkg-name">`, str(child.pkg.name))
  307. fmt.wprintf(w, `<a href="/core/%s/">%s</a>`, child.path, child.name)
  308. io.write_string(w, `</td>`)
  309. line_doc, _, _ := strings.partition(str(child.pkg.docs), "\n")
  310. line_doc = strings.trim_space(line_doc)
  311. io.write_string(w, `<td class="pkg-line pkg-line-doc">`)
  312. if line_doc != "" {
  313. write_doc_line(w, line_doc)
  314. }
  315. io.write_string(w, `</td>`)
  316. fmt.wprintf(w, "</td>")
  317. fmt.wprintf(w, "</tr>\n")
  318. }
  319. }
  320. fmt.wprintln(w, "\t\t</tbody>")
  321. fmt.wprintln(w, "\t</table>")
  322. fmt.wprintln(w, "</div>")
  323. }
  324. is_entity_blank :: proc(e: doc.Entity_Index) -> bool {
  325. name := str(entities[e].name)
  326. return name == ""
  327. }
  328. write_where_clauses :: proc(w: io.Writer, where_clauses: []doc.String) {
  329. if len(where_clauses) != 0 {
  330. io.write_string(w, " where ")
  331. for clause, i in where_clauses {
  332. if i > 0 {
  333. io.write_string(w, ", ")
  334. }
  335. io.write_string(w, str(clause))
  336. }
  337. }
  338. }
  339. Write_Type_Flag :: enum {
  340. Is_Results,
  341. Variadic,
  342. Allow_Indent,
  343. Poly_Names,
  344. }
  345. Write_Type_Flags :: distinct bit_set[Write_Type_Flag]
  346. Type_Writer :: struct {
  347. w: io.Writer,
  348. pkg: doc.Pkg_Index,
  349. indent: int,
  350. generic_scope: map[string]bool,
  351. }
  352. write_type :: proc(using writer: ^Type_Writer, type: doc.Type, flags: Write_Type_Flags) {
  353. write_param_entity :: proc(using writer: ^Type_Writer, e: ^doc.Entity, flags: Write_Type_Flags, name_width := 0) {
  354. name := str(e.name)
  355. write_padding :: proc(w: io.Writer, name: string, name_width: int) {
  356. for _ in 0..<name_width-len(name) {
  357. io.write_byte(w, ' ')
  358. }
  359. }
  360. if .Param_Using in e.flags { io.write_string(w, "using ") }
  361. if .Param_Const in e.flags { io.write_string(w, "#const ") }
  362. if .Param_Auto_Cast in e.flags { io.write_string(w, "#auto_cast ") }
  363. if .Param_CVararg in e.flags { io.write_string(w, "#c_vararg ") }
  364. if .Param_No_Alias in e.flags { io.write_string(w, "#no_alias ") }
  365. if .Param_Any_Int in e.flags { io.write_string(w, "#any_int ") }
  366. init_string := str(e.init_string)
  367. switch {
  368. case init_string == "#caller_location":
  369. assert(name != "")
  370. io.write_string(w, name)
  371. io.write_string(w, " := ")
  372. io.write_string(w, `<a href="/core/runtime/#Source_Code_Location">`)
  373. io.write_string(w, init_string)
  374. io.write_string(w, `</a>`)
  375. case strings.has_prefix(init_string, "context."):
  376. io.write_string(w, name)
  377. io.write_string(w, " := ")
  378. io.write_string(w, `<a href="/core/runtime/#Context">`)
  379. io.write_string(w, init_string)
  380. io.write_string(w, `</a>`)
  381. case:
  382. the_type := types[e.type]
  383. type_flags := flags - {.Is_Results}
  384. if .Param_Ellipsis in e.flags {
  385. type_flags += {.Variadic}
  386. }
  387. #partial switch e.kind {
  388. case .Constant:
  389. assert(name != "")
  390. io.write_byte(w, '$')
  391. io.write_string(w, name)
  392. generic_scope[name] = true
  393. if !is_type_untyped(the_type) {
  394. io.write_string(w, ": ")
  395. write_padding(w, name, name_width)
  396. write_type(writer, the_type, type_flags)
  397. io.write_string(w, " = ")
  398. io.write_string(w, init_string)
  399. } else {
  400. io.write_string(w, " := ")
  401. io.write_string(w, init_string)
  402. }
  403. return
  404. case .Variable:
  405. if name != "" {
  406. io.write_string(w, name)
  407. io.write_string(w, ": ")
  408. write_padding(w, name, name_width)
  409. }
  410. write_type(writer, the_type, type_flags)
  411. case .Type_Name:
  412. io.write_byte(w, '$')
  413. io.write_string(w, name)
  414. generic_scope[name] = true
  415. io.write_string(w, ": ")
  416. write_padding(w, name, name_width)
  417. if the_type.kind == .Generic {
  418. io.write_string(w, "typeid")
  419. if ts := array(the_type.types); len(ts) == 1 {
  420. io.write_byte(w, '/')
  421. write_type(writer, types[ts[0]], type_flags)
  422. }
  423. } else {
  424. write_type(writer, the_type, type_flags)
  425. }
  426. }
  427. if init_string != "" {
  428. io.write_string(w, " = ")
  429. io.write_string(w, init_string)
  430. }
  431. }
  432. }
  433. write_poly_params :: proc(using writer: ^Type_Writer, type: doc.Type, flags: Write_Type_Flags) {
  434. if type.polymorphic_params != 0 {
  435. io.write_byte(w, '(')
  436. write_type(writer, types[type.polymorphic_params], flags+{.Poly_Names})
  437. io.write_byte(w, ')')
  438. }
  439. write_where_clauses(w, array(type.where_clauses))
  440. }
  441. do_indent :: proc(using writer: ^Type_Writer, flags: Write_Type_Flags) {
  442. if .Allow_Indent not_in flags {
  443. return
  444. }
  445. for _ in 0..<indent {
  446. io.write_byte(w, '\t')
  447. }
  448. }
  449. do_newline :: proc(using writer: ^Type_Writer, flags: Write_Type_Flags) {
  450. if .Allow_Indent in flags {
  451. io.write_byte(w, '\n')
  452. }
  453. }
  454. calc_name_width :: proc(type_entites: []doc.Entity_Index) -> (name_width: int) {
  455. for entity_index in type_entites {
  456. e := &entities[entity_index]
  457. name := str(e.name)
  458. name_width = max(len(name), name_width)
  459. }
  460. return
  461. }
  462. type_entites := array(type.entities)
  463. type_types := array(type.types)
  464. switch type.kind {
  465. case .Invalid:
  466. // ignore
  467. case .Basic:
  468. type_flags := transmute(doc.Type_Flags_Basic)type.flags
  469. if is_type_untyped(type) {
  470. io.write_string(w, str(type.name))
  471. } else {
  472. fmt.wprintf(w, `<a href="">%s</a>`, str(type.name))
  473. }
  474. case .Named:
  475. e := entities[type_entites[0]]
  476. name := str(type.name)
  477. tn_pkg := files[e.pos.file].pkg
  478. if tn_pkg != pkg {
  479. fmt.wprintf(w, `%s.`, str(pkgs[tn_pkg].name))
  480. }
  481. if n := strings.contains_rune(name, '('); n >= 0 {
  482. fmt.wprintf(w, `<a class="code-typename" href="/core/{0:s}/#{1:s}">{1:s}</a>`, pkg_to_path[&pkgs[tn_pkg]], name[:n])
  483. io.write_string(w, name[n:])
  484. } else {
  485. fmt.wprintf(w, `<a class="code-typename" href="/core/{0:s}/#{1:s}">{1:s}</a>`, pkg_to_path[&pkgs[tn_pkg]], name)
  486. }
  487. case .Generic:
  488. name := str(type.name)
  489. if name not_in generic_scope {
  490. io.write_byte(w, '$')
  491. }
  492. io.write_string(w, name)
  493. if name not_in generic_scope && len(array(type.types)) == 1 {
  494. io.write_byte(w, '/')
  495. write_type(writer, types[type_types[0]], flags)
  496. }
  497. case .Pointer:
  498. io.write_byte(w, '^')
  499. write_type(writer, types[type_types[0]], flags)
  500. case .Array:
  501. assert(type.elem_count_len == 1)
  502. io.write_byte(w, '[')
  503. io.write_uint(w, uint(type.elem_counts[0]))
  504. io.write_byte(w, ']')
  505. write_type(writer, types[type_types[0]], flags)
  506. case .Enumerated_Array:
  507. io.write_byte(w, '[')
  508. write_type(writer, types[type_types[0]], flags)
  509. io.write_byte(w, ']')
  510. write_type(writer, types[type_types[1]], flags)
  511. case .Slice:
  512. if .Variadic in flags {
  513. io.write_string(w, "..")
  514. } else {
  515. io.write_string(w, "[]")
  516. }
  517. write_type(writer, types[type_types[0]], flags - {.Variadic})
  518. case .Dynamic_Array:
  519. io.write_string(w, "[dynamic]")
  520. write_type(writer, types[type_types[0]], flags)
  521. case .Map:
  522. io.write_string(w, "map[")
  523. write_type(writer, types[type_types[0]], flags)
  524. io.write_byte(w, ']')
  525. write_type(writer, types[type_types[1]], flags)
  526. case .Struct:
  527. type_flags := transmute(doc.Type_Flags_Struct)type.flags
  528. io.write_string(w, "struct")
  529. write_poly_params(writer, type, flags)
  530. if .Packed in type_flags { io.write_string(w, " #packed") }
  531. if .Raw_Union in type_flags { io.write_string(w, " #raw_union") }
  532. if custom_align := str(type.custom_align); custom_align != "" {
  533. io.write_string(w, " #align")
  534. io.write_string(w, custom_align)
  535. }
  536. io.write_string(w, " {")
  537. if len(type_entites) != 0 {
  538. do_newline(writer, flags)
  539. indent += 1
  540. name_width := calc_name_width(type_entites)
  541. for entity_index in type_entites {
  542. e := &entities[entity_index]
  543. do_indent(writer, flags)
  544. write_param_entity(writer, e, flags, name_width)
  545. io.write_byte(w, ',')
  546. do_newline(writer, flags)
  547. }
  548. indent -= 1
  549. do_indent(writer, flags)
  550. }
  551. io.write_string(w, "}")
  552. case .Union:
  553. type_flags := transmute(doc.Type_Flags_Union)type.flags
  554. io.write_string(w, "union")
  555. write_poly_params(writer, type, flags)
  556. if .No_Nil in type_flags { io.write_string(w, " #no_nil") }
  557. if .Maybe in type_flags { io.write_string(w, " #maybe") }
  558. if custom_align := str(type.custom_align); custom_align != "" {
  559. io.write_string(w, " #align")
  560. io.write_string(w, custom_align)
  561. }
  562. io.write_string(w, " {")
  563. if len(type_types) > 1 {
  564. do_newline(writer, flags)
  565. indent += 1
  566. for type_index in type_types {
  567. do_indent(writer, flags)
  568. write_type(writer, types[type_index], flags)
  569. io.write_string(w, ", ")
  570. do_newline(writer, flags)
  571. }
  572. indent -= 1
  573. do_indent(writer, flags)
  574. }
  575. io.write_string(w, "}")
  576. case .Enum:
  577. io.write_string(w, "enum")
  578. if len(type_types) != 0 {
  579. io.write_byte(w, ' ')
  580. write_type(writer, types[type_types[0]], flags)
  581. }
  582. io.write_string(w, " {")
  583. do_newline(writer, flags)
  584. indent += 1
  585. name_width := calc_name_width(type_entites)
  586. for entity_index in type_entites {
  587. e := &entities[entity_index]
  588. name := str(e.name)
  589. do_indent(writer, flags)
  590. io.write_string(w, name)
  591. if init_string := str(e.init_string); init_string != "" {
  592. for _ in 0..<name_width-len(name) {
  593. io.write_byte(w, ' ')
  594. }
  595. io.write_string(w, " = ")
  596. io.write_string(w, init_string)
  597. }
  598. io.write_string(w, ", ")
  599. do_newline(writer, flags)
  600. }
  601. indent -= 1
  602. do_indent(writer, flags)
  603. io.write_string(w, "}")
  604. case .Tuple:
  605. if len(type_entites) == 0 {
  606. return
  607. }
  608. require_parens := (.Is_Results in flags) && (len(type_entites) > 1 || !is_entity_blank(type_entites[0]))
  609. if require_parens { io.write_byte(w, '(') }
  610. for entity_index, i in type_entites {
  611. if i > 0 {
  612. io.write_string(w, ", ")
  613. }
  614. write_param_entity(writer, &entities[entity_index], flags)
  615. }
  616. if require_parens { io.write_byte(w, ')') }
  617. case .Proc:
  618. type_flags := transmute(doc.Type_Flags_Proc)type.flags
  619. io.write_string(w, "proc")
  620. cc := str(type.calling_convention)
  621. if cc != "" {
  622. io.write_byte(w, ' ')
  623. io.write_quoted_string(w, cc)
  624. io.write_byte(w, ' ')
  625. }
  626. params := array(type.types)[0]
  627. results := array(type.types)[1]
  628. io.write_byte(w, '(')
  629. write_type(writer, types[params], flags)
  630. io.write_byte(w, ')')
  631. if results != 0 {
  632. assert(.Diverging not_in type_flags)
  633. io.write_string(w, " -> ")
  634. write_type(writer, types[results], flags+{.Is_Results})
  635. }
  636. if .Diverging in type_flags {
  637. io.write_string(w, " -> !")
  638. }
  639. if .Optional_Ok in type_flags {
  640. io.write_string(w, " #optional_ok")
  641. }
  642. case .Bit_Set:
  643. type_flags := transmute(doc.Type_Flags_Bit_Set)type.flags
  644. io.write_string(w, "bit_set[")
  645. if .Op_Lt in type_flags {
  646. io.write_uint(w, uint(type.elem_counts[0]))
  647. io.write_string(w, "..<")
  648. io.write_uint(w, uint(type.elem_counts[1]))
  649. } else if .Op_Lt_Eq in type_flags {
  650. io.write_uint(w, uint(type.elem_counts[0]))
  651. io.write_string(w, "..=")
  652. io.write_uint(w, uint(type.elem_counts[1]))
  653. } else {
  654. write_type(writer, types[type_types[0]], flags)
  655. }
  656. if .Underlying_Type in type_flags {
  657. write_type(writer, types[type_types[1]], flags)
  658. }
  659. io.write_string(w, "]")
  660. case .Simd_Vector:
  661. io.write_string(w, "#simd[")
  662. io.write_uint(w, uint(type.elem_counts[0]))
  663. io.write_byte(w, ']')
  664. case .SOA_Struct_Fixed:
  665. io.write_string(w, "#soa[")
  666. io.write_uint(w, uint(type.elem_counts[0]))
  667. io.write_byte(w, ']')
  668. case .SOA_Struct_Slice:
  669. io.write_string(w, "#soa[]")
  670. case .SOA_Struct_Dynamic:
  671. io.write_string(w, "#soa[dynamic]")
  672. case .Relative_Pointer:
  673. io.write_string(w, "#relative(")
  674. write_type(writer, types[type_types[1]], flags)
  675. io.write_string(w, ") ")
  676. write_type(writer, types[type_types[0]], flags)
  677. case .Relative_Slice:
  678. io.write_string(w, "#relative(")
  679. write_type(writer, types[type_types[1]], flags)
  680. io.write_string(w, ") ")
  681. write_type(writer, types[type_types[0]], flags)
  682. case .Multi_Pointer:
  683. io.write_string(w, "[^]")
  684. write_type(writer, types[type_types[0]], flags)
  685. case .Matrix:
  686. io.write_string(w, "matrix[")
  687. io.write_uint(w, uint(type.elem_counts[0]))
  688. io.write_string(w, ", ")
  689. io.write_uint(w, uint(type.elem_counts[1]))
  690. io.write_string(w, "]")
  691. write_type(writer, types[type_types[0]], flags)
  692. }
  693. }
  694. write_doc_line :: proc(w: io.Writer, text: string) {
  695. text := text
  696. for len(text) != 0 {
  697. if strings.count(text, "`") >= 2 {
  698. n := strings.index_byte(text, '`')
  699. io.write_string(w, text[:n])
  700. io.write_string(w, "<code class=\"code-inline\">")
  701. remaining := text[n+1:]
  702. m := strings.index_byte(remaining, '`')
  703. io.write_string(w, remaining[:m])
  704. io.write_string(w, "</code>")
  705. text = remaining[m+1:]
  706. } else {
  707. io.write_string(w, text)
  708. return
  709. }
  710. }
  711. }
  712. write_docs :: proc(w: io.Writer, pkg: ^doc.Pkg, docs: string) {
  713. if docs == "" {
  714. return
  715. }
  716. it := docs
  717. was_code := true
  718. was_paragraph := true
  719. for line in strings.split_iterator(&it, "\n") {
  720. if strings.has_prefix(line, "\t") {
  721. if !was_code {
  722. was_code = true;
  723. fmt.wprint(w, `<pre class="doc-code"><code>`)
  724. }
  725. fmt.wprintf(w, "%s\n", strings.trim_prefix(line, "\t"))
  726. continue
  727. } else if was_code {
  728. was_code = false
  729. fmt.wprintln(w, "</code></pre>")
  730. continue
  731. }
  732. text := strings.trim_space(line)
  733. if text == "" {
  734. if was_paragraph {
  735. was_paragraph = false
  736. fmt.wprintln(w, "</p>")
  737. }
  738. continue
  739. }
  740. if !was_paragraph {
  741. fmt.wprintln(w, "<p>")
  742. }
  743. assert(!was_code)
  744. was_paragraph = true
  745. write_doc_line(w, text)
  746. io.write_byte(w, '\n')
  747. }
  748. if was_code {
  749. // assert(!was_paragraph, str(pkg.name))
  750. was_code = false
  751. fmt.wprintln(w, "</code></pre>")
  752. }
  753. if was_paragraph {
  754. fmt.wprintln(w, "</p>")
  755. }
  756. }
  757. write_pkg :: proc(w: io.Writer, path: string, pkg: ^doc.Pkg) {
  758. fmt.wprintln(w, `<div class="row odin-main">`)
  759. defer fmt.wprintln(w, `</div>`)
  760. { // breadcrumbs
  761. fmt.wprintln(w, `<nav class="col-lg-2 odin-side-bar-border navbar-light">`)
  762. fmt.wprintln(w, `<div class="sticky-top odin-below-navbar py-3">`)
  763. {
  764. dirs := strings.split(path, "/")
  765. io.write_string(w, "<ul class=\"nav nav-pills d-flex flex-column\">\n")
  766. io.write_string(w, `<li class="nav-item"><a class="nav-link" href="/core">core</a></li>`)
  767. for dir, i in dirs {
  768. url := strings.join(dirs[:i+1], "/")
  769. short_path := strings.join(dirs[1:i+1], "/")
  770. io.write_string(w, `<li class="nav-item">`)
  771. a_class := "nav-link"
  772. if i+1 == len(dirs) {
  773. a_class = "nav-link active"
  774. }
  775. if i == 0 || short_path in pkgs_to_use {
  776. fmt.wprintf(w, `<a class="%s" href="/core/%s">%s</a></li>` + "\n", a_class, url, dir)
  777. } else {
  778. fmt.wprintf(w, "%s</li>\n", dir)
  779. }
  780. }
  781. io.write_string(w, "</ul>\n")
  782. }
  783. fmt.wprintln(w, `</div>`)
  784. fmt.wprintln(w, `</nav>`)
  785. }
  786. fmt.wprintln(w, `<article class="col-lg-8 p-4">`)
  787. fmt.wprintf(w, "<h1>package core:%s</h1>\n", path)
  788. fmt.wprintln(w, "<h2>Documentation</h2>")
  789. docs := strings.trim_space(str(pkg.docs))
  790. if docs != "" {
  791. fmt.wprintln(w, "<h3>Overview</h3>")
  792. fmt.wprintln(w, "<div id=\"pkg-overview\">")
  793. defer fmt.wprintln(w, "</div>")
  794. write_docs(w, pkg, docs)
  795. }
  796. fmt.wprintln(w, "<h3>Index</h3>")
  797. fmt.wprintln(w, `<section class="doc-index">`)
  798. pkg_procs: [dynamic]^doc.Entity
  799. pkg_proc_groups: [dynamic]^doc.Entity
  800. pkg_types: [dynamic]^doc.Entity
  801. pkg_vars: [dynamic]^doc.Entity
  802. pkg_consts: [dynamic]^doc.Entity
  803. for entity_index in array(pkg.entities) {
  804. e := &entities[entity_index]
  805. name := str(e.name)
  806. if name == "" || name[0] == '_' {
  807. continue
  808. }
  809. switch e.kind {
  810. case .Invalid, .Import_Name, .Library_Name:
  811. // ignore
  812. case .Constant: append(&pkg_consts, e)
  813. case .Variable: append(&pkg_vars, e)
  814. case .Type_Name: append(&pkg_types, e)
  815. case .Procedure: append(&pkg_procs, e)
  816. case .Proc_Group: append(&pkg_proc_groups, e)
  817. }
  818. }
  819. entity_key :: proc(e: ^doc.Entity) -> string {
  820. return str(e.name)
  821. }
  822. slice.sort_by_key(pkg_procs[:], entity_key)
  823. slice.sort_by_key(pkg_proc_groups[:], entity_key)
  824. slice.sort_by_key(pkg_types[:], entity_key)
  825. slice.sort_by_key(pkg_vars[:], entity_key)
  826. slice.sort_by_key(pkg_consts[:], entity_key)
  827. write_index :: proc(w: io.Writer, name: string, entities: []^doc.Entity) {
  828. fmt.wprintf(w, "<h4>%s</h4>\n", name)
  829. fmt.wprintln(w, `<section class="doc-index">`)
  830. if len(entities) == 0 {
  831. io.write_string(w, "<p>This section is empty.</p>\n")
  832. } else {
  833. fmt.wprintln(w, "<ul>")
  834. for e in entities {
  835. name := str(e.name)
  836. fmt.wprintf(w, "<li><a href=\"#{0:s}\">{0:s}</a></li>\n", name)
  837. }
  838. fmt.wprintln(w, "</ul>")
  839. }
  840. fmt.wprintln(w, "</section>")
  841. }
  842. write_index(w, "Procedures", pkg_procs[:])
  843. write_index(w, "Procedure Groups", pkg_proc_groups[:])
  844. write_index(w, "Types", pkg_types[:])
  845. write_index(w, "Variables", pkg_vars[:])
  846. write_index(w, "Constants", pkg_consts[:])
  847. fmt.wprintln(w, "</section>")
  848. write_entity :: proc(w: io.Writer, e: ^doc.Entity) {
  849. write_attributes :: proc(w: io.Writer, e: ^doc.Entity) {
  850. for attr in array(e.attributes) {
  851. io.write_string(w, "@(")
  852. name := str(attr.name)
  853. value := str(attr.value)
  854. io.write_string(w, name)
  855. if value != "" {
  856. io.write_string(w, "=")
  857. io.write_string(w, value)
  858. }
  859. io.write_string(w, ")\n")
  860. }
  861. }
  862. pkg_index := files[e.pos.file].pkg
  863. pkg := &pkgs[pkg_index]
  864. writer := &Type_Writer{
  865. w = w,
  866. pkg = pkg_index,
  867. }
  868. defer delete(writer.generic_scope)
  869. name := str(e.name)
  870. path := pkg_to_path[pkg]
  871. filename := slashpath.base(str(files[e.pos.file].name))
  872. fmt.wprintf(w, "<h4 id=\"{0:s}\"><span><a class=\"doc-id-link\" href=\"#{0:s}\">{0:s}", name)
  873. fmt.wprintf(w, "<span class=\"a-hidden\">&nbsp;¶</span></a></span>")
  874. if e.pos.file != 0 && e.pos.line > 0 {
  875. src_url := fmt.tprintf("%s/%s/%s#L%d", GITHUB_CORE_URL, path, filename, e.pos.line)
  876. fmt.wprintf(w, "<div class=\"doc-source\"><a href=\"{0:s}\"><em>Source</em></a></div>", src_url)
  877. }
  878. fmt.wprintf(w, "</h4>\n")
  879. switch e.kind {
  880. case .Invalid, .Import_Name, .Library_Name:
  881. // ignore
  882. case .Constant:
  883. fmt.wprint(w, `<pre class="doc-code">`)
  884. the_type := types[e.type]
  885. init_string := str(e.init_string)
  886. assert(init_string != "")
  887. ignore_type := true
  888. if the_type.kind == .Basic && is_type_untyped(the_type) {
  889. } else {
  890. ignore_type = false
  891. type_name := str(the_type.name)
  892. if type_name != "" && strings.has_prefix(init_string, type_name) {
  893. ignore_type = true
  894. }
  895. }
  896. if ignore_type {
  897. fmt.wprintf(w, "%s :: ", name)
  898. } else {
  899. fmt.wprintf(w, "%s: ", name)
  900. write_type(writer, the_type, {.Allow_Indent})
  901. fmt.wprintf(w, " : ")
  902. }
  903. io.write_string(w, init_string)
  904. fmt.wprintln(w, "</pre>")
  905. case .Variable:
  906. fmt.wprint(w, `<pre class="doc-code">`)
  907. write_attributes(w, e)
  908. fmt.wprintf(w, "%s: ", name)
  909. write_type(writer, types[e.type], {.Allow_Indent})
  910. init_string := str(e.init_string)
  911. if init_string != "" {
  912. io.write_string(w, " = ")
  913. io.write_string(w, init_string)
  914. }
  915. fmt.wprintln(w, "</pre>")
  916. case .Type_Name:
  917. fmt.wprint(w, `<pre class="doc-code">`)
  918. fmt.wprintf(w, "%s :: ", name)
  919. the_type := types[e.type]
  920. type_to_print := the_type
  921. if the_type.kind == .Named && .Type_Alias not_in e.flags {
  922. if e.pos == entities[array(the_type.entities)[0]].pos {
  923. bt := base_type(the_type)
  924. #partial switch bt.kind {
  925. case .Struct, .Union, .Proc, .Enum:
  926. // Okay
  927. case:
  928. io.write_string(w, "distinct ")
  929. }
  930. type_to_print = bt
  931. }
  932. }
  933. write_type(writer, type_to_print, {.Allow_Indent})
  934. fmt.wprintln(w, "</pre>")
  935. case .Procedure:
  936. fmt.wprint(w, `<pre class="doc-code">`)
  937. fmt.wprintf(w, "%s :: ", name)
  938. write_type(writer, types[e.type], nil)
  939. write_where_clauses(w, array(e.where_clauses))
  940. fmt.wprint(w, " {…}")
  941. fmt.wprintln(w, "</pre>")
  942. case .Proc_Group:
  943. fmt.wprint(w, `<pre class="doc-code">`)
  944. fmt.wprintf(w, "%s :: proc{{\n", name)
  945. for entity_index in array(e.grouped_entities) {
  946. this_proc := &entities[entity_index]
  947. this_pkg := files[this_proc.pos.file].pkg
  948. io.write_byte(w, '\t')
  949. if this_pkg != pkg_index {
  950. fmt.wprintf(w, "%s.", str(pkgs[this_pkg].name))
  951. }
  952. name := str(this_proc.name)
  953. fmt.wprintf(w, `<a class="code-procedure" href="/core/{0:s}/#{1:s}">`, pkg_to_path[&pkgs[this_pkg]], name)
  954. io.write_string(w, name)
  955. io.write_string(w, `</a>`)
  956. io.write_byte(w, ',')
  957. io.write_byte(w, '\n')
  958. }
  959. fmt.wprintln(w, "}")
  960. fmt.wprintln(w, "</pre>")
  961. }
  962. write_docs(w, pkg, strings.trim_space(str(e.docs)))
  963. }
  964. write_entities :: proc(w: io.Writer, title: string, entities: []^doc.Entity) {
  965. fmt.wprintf(w, "<h3 id=\"pkg-{0:s}\">{0:s}</h3>\n", title)
  966. fmt.wprintln(w, `<section class="documentation">`)
  967. if len(entities) == 0 {
  968. io.write_string(w, "<p>This section is empty.</p>\n")
  969. } else {
  970. for e in entities {
  971. write_entity(w, e)
  972. }
  973. }
  974. fmt.wprintln(w, "</section>")
  975. }
  976. write_entities(w, "Procedures", pkg_procs[:])
  977. write_entities(w, "Procedure Groups", pkg_proc_groups[:])
  978. write_entities(w, "Types", pkg_types[:])
  979. write_entities(w, "Variables", pkg_vars[:])
  980. write_entities(w, "Constants", pkg_consts[:])
  981. fmt.wprintln(w, `<h3 id="pkg-source-files">Source Files</h3>`)
  982. fmt.wprintln(w, "<ul>")
  983. any_hidden := false
  984. source_file_loop: for file_index in array(pkg.files) {
  985. file := files[file_index]
  986. filename := slashpath.base(str(file.name))
  987. switch {
  988. case
  989. strings.has_suffix(filename, "_windows.odin"),
  990. strings.has_suffix(filename, "_darwin.odin"),
  991. strings.has_suffix(filename, "_essence.odin"),
  992. strings.has_suffix(filename, "_freebsd.odin"),
  993. strings.has_suffix(filename, "_wasi.odin"),
  994. strings.has_suffix(filename, "_js.odin"),
  995. strings.has_suffix(filename, "_freestanding.odin"),
  996. strings.has_suffix(filename, "_amd64.odin"),
  997. strings.has_suffix(filename, "_i386.odin"),
  998. strings.has_suffix(filename, "_arch64.odin"),
  999. strings.has_suffix(filename, "_wasm32.odin"),
  1000. strings.has_suffix(filename, "_wasm64.odin"),
  1001. false:
  1002. any_hidden = true
  1003. continue source_file_loop
  1004. }
  1005. fmt.wprintf(w, `<li><a href="%s/%s/%s">%s</a></li>`, GITHUB_CORE_URL, path, filename, filename)
  1006. fmt.wprintln(w)
  1007. }
  1008. if any_hidden {
  1009. fmt.wprintln(w, "<li><em>(hidden platform specific files)</em></li>")
  1010. }
  1011. fmt.wprintln(w, "</ul>")
  1012. fmt.wprintln(w, `</article>`)
  1013. {
  1014. write_link :: proc(w: io.Writer, id, text: string) {
  1015. fmt.wprintf(w, `<li><a href="#%s">%s</a>`, id, text)
  1016. }
  1017. write_index :: proc(w: io.Writer, name: string, entities: []^doc.Entity) {
  1018. fmt.wprintf(w, `<li><a href="#pkg-{0:s}">{0:s}</a>`, name)
  1019. fmt.wprintln(w, `<ul>`)
  1020. for e in entities {
  1021. name := str(e.name)
  1022. fmt.wprintf(w, "<li><a href=\"#{0:s}\">{0:s}</a></li>\n", name)
  1023. }
  1024. fmt.wprintln(w, "</ul>")
  1025. fmt.wprintln(w, "</li>")
  1026. }
  1027. fmt.wprintln(w, `<div class="col-lg-2 odin-toc-border navbar-light"><div class="sticky-top odin-below-navbar py-3">`)
  1028. fmt.wprintln(w, `<nav id="TableOfContents">`)
  1029. fmt.wprintln(w, `<ul>`)
  1030. write_link(w, "pkg-overview", "Overview")
  1031. write_index(w, "Procedures", pkg_procs[:])
  1032. write_index(w, "Procedure Groups", pkg_proc_groups[:])
  1033. write_index(w, "Types", pkg_types[:])
  1034. write_index(w, "Variables", pkg_vars[:])
  1035. write_index(w, "Constants", pkg_consts[:])
  1036. write_link(w, "pkg-source-files", "Source Files")
  1037. fmt.wprintln(w, `</ul>`)
  1038. fmt.wprintln(w, `</nav>`)
  1039. fmt.wprintln(w, `</div></div>`)
  1040. }
  1041. }