odin_html_docs_main.odin 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  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. common_prefix :: proc(strs: []string) -> string {
  41. if len(strs) == 0 {
  42. return ""
  43. }
  44. n := max(int)
  45. for str in strs {
  46. n = min(n, len(str))
  47. }
  48. prefix := strs[0][:n]
  49. for str in strs[1:] {
  50. for len(prefix) != 0 && str[:len(prefix)] != prefix {
  51. prefix = prefix[:len(prefix)-1]
  52. }
  53. if len(prefix) == 0 {
  54. break
  55. }
  56. }
  57. return prefix
  58. }
  59. recursive_make_directory :: proc(path: string, prefix := "") {
  60. head, _, tail := strings.partition(path, "/")
  61. path_to_make := head
  62. if prefix != "" {
  63. path_to_make = fmt.tprintf("%s/%s", prefix, head)
  64. }
  65. os.make_directory(path_to_make, 0)
  66. if tail != "" {
  67. recursive_make_directory(tail, path_to_make)
  68. }
  69. }
  70. write_html_header :: proc(w: io.Writer, title: string) {
  71. fmt.wprintf(w, `<!DOCTYPE html>
  72. <html>
  73. <head>
  74. <meta charset="utf-8">
  75. <meta name="viewport" content="width=device-width, initial-scale=1">
  76. <title>%s</title>
  77. <script type="text/javascript" src="https://livejs.com/live.js"></script>
  78. <link rel="stylesheet" type="text/css" href="/style.css">
  79. </style>
  80. </head>
  81. <body>`, title)
  82. fmt.wprintln(w, "\n<div class=\"container\">")
  83. fmt.wprintln(w, "\n<a href=\"/core\">Core Directory</a>")
  84. }
  85. write_html_footer :: proc(w: io.Writer) {
  86. fmt.wprintf(w, "</div></body>\n</html>\n")
  87. }
  88. main :: proc() {
  89. if len(os.args) != 2 {
  90. errorf("expected 1 .odin-doc file")
  91. }
  92. data, ok := os.read_entire_file(os.args[1])
  93. if !ok {
  94. errorf("unable to read file:", os.args[1])
  95. }
  96. err: doc.Reader_Error
  97. header, err = doc.read_from_bytes(data)
  98. switch err {
  99. case .None:
  100. case .Header_Too_Small:
  101. errorf("file is too small for the file format")
  102. case .Invalid_Magic:
  103. errorf("invalid magic for the file format")
  104. case .Data_Too_Small:
  105. errorf("data is too small for the file format")
  106. case .Invalid_Version:
  107. errorf("invalid file format version")
  108. }
  109. files = array(header.files)
  110. pkgs = array(header.pkgs)
  111. entities = array(header.entities)
  112. types = array(header.types)
  113. {
  114. fullpaths: [dynamic]string
  115. defer delete(fullpaths)
  116. for pkg in pkgs[1:] {
  117. append(&fullpaths, str(pkg.fullpath))
  118. }
  119. path_prefix := common_prefix(fullpaths[:])
  120. pkgs_to_use = make(map[string]^doc.Pkg)
  121. fullpath_loop: for fullpath, i in fullpaths {
  122. path := strings.trim_prefix(fullpath, path_prefix)
  123. if !strings.has_prefix(path, "core/") {
  124. continue fullpath_loop
  125. }
  126. pkg := &pkgs[i+1]
  127. if len(array(pkg.entities)) == 0 {
  128. continue fullpath_loop
  129. }
  130. trimmed_path := strings.trim_prefix(path, "core/")
  131. if strings.has_prefix(trimmed_path, "sys") {
  132. continue fullpath_loop
  133. }
  134. pkgs_to_use[trimmed_path] = pkg
  135. }
  136. sort.map_entries_by_key(&pkgs_to_use)
  137. for path, pkg in pkgs_to_use {
  138. pkg_to_path[pkg] = path
  139. }
  140. }
  141. b := strings.make_builder()
  142. defer strings.destroy_builder(&b)
  143. w := strings.to_writer(&b)
  144. {
  145. strings.reset_builder(&b)
  146. write_html_header(w, "core library - pkg.odin-lang.org")
  147. write_core_directory(w)
  148. write_html_footer(w)
  149. os.make_directory("core", 0)
  150. os.write_entire_file("core/index.html", b.buf[:])
  151. }
  152. for path, pkg in pkgs_to_use {
  153. strings.reset_builder(&b)
  154. write_html_header(w, fmt.tprintf("package %s - pkg.odin-lang.org", path))
  155. write_pkg(w, path, pkg)
  156. write_html_footer(w)
  157. recursive_make_directory(path, "core")
  158. os.write_entire_file(fmt.tprintf("core/%s/index.html", path), b.buf[:])
  159. }
  160. }
  161. write_core_directory :: proc(w: io.Writer) {
  162. Node :: struct {
  163. dir: string,
  164. path: string,
  165. name: string,
  166. pkg: ^doc.Pkg,
  167. next: ^Node,
  168. first_child: ^Node,
  169. }
  170. add_child :: proc(parent: ^Node, child: ^Node) -> ^Node {
  171. assert(parent != nil)
  172. end := &parent.first_child
  173. for end^ != nil {
  174. end = &end^.next
  175. }
  176. child.next = end^
  177. end^ = child
  178. return child
  179. }
  180. root: Node
  181. for path, pkg in pkgs_to_use {
  182. dir, _, inner := strings.partition(path, "/")
  183. node: ^Node = nil
  184. for node = root.first_child; node != nil; node = node.next {
  185. if node.dir == dir {
  186. break
  187. }
  188. }
  189. if inner == "" {
  190. if node == nil {
  191. add_child(&root, new_clone(Node{
  192. dir = dir,
  193. name = dir,
  194. path = path,
  195. pkg = pkg,
  196. }))
  197. } else {
  198. node.dir = dir
  199. node.name = dir
  200. node.path = path
  201. node.pkg = pkg
  202. }
  203. } else {
  204. if node == nil {
  205. node = add_child(&root, new_clone(Node{
  206. dir = dir,
  207. name = dir,
  208. }))
  209. }
  210. assert(node != nil)
  211. child := add_child(node, new_clone(Node{
  212. dir = dir,
  213. name = inner,
  214. path = path,
  215. pkg = pkg,
  216. }))
  217. }
  218. }
  219. fmt.wprintln(w, "<h2>Directories</h2>")
  220. fmt.wprintln(w, "\t<table>")
  221. fmt.wprintln(w, "\t\t<tbody>")
  222. for dir := root.first_child; dir != nil; dir = dir.next {
  223. if dir.first_child != nil {
  224. fmt.wprint(w, `<tr aria-controls="`)
  225. for child := dir.first_child; child != nil; child = child.next {
  226. fmt.wprintf(w, "pkg-%s ", str(child.pkg.name))
  227. }
  228. fmt.wprint(w, `" class="directory-pkg"><td class="pkg-name" data-aria-owns="`)
  229. for child := dir.first_child; child != nil; child = child.next {
  230. fmt.wprintf(w, "pkg-%s ", str(child.pkg.name))
  231. }
  232. fmt.wprintf(w, `" id="pkg-%s">`, dir.dir)
  233. } else {
  234. fmt.wprintf(w, `<tr id="pkg-%s" class="directory-pkg"><td class="pkg-name">`, dir.dir)
  235. }
  236. if dir.pkg != nil {
  237. fmt.wprintf(w, `<a href="/core/%s">%s</a>`, dir.path, dir.name)
  238. } else {
  239. fmt.wprintf(w, "%s", dir.name)
  240. }
  241. fmt.wprintf(w, "</td>")
  242. if dir.pkg != nil {
  243. line_doc, _, _ := strings.partition(str(dir.pkg.docs), "\n")
  244. line_doc = strings.trim_space(line_doc)
  245. if line_doc != "" {
  246. fmt.wprintf(w, `<td class="pkg-line-doc">%s</td>`, line_doc)
  247. }
  248. }
  249. fmt.wprintf(w, "</tr>\n")
  250. for child := dir.first_child; child != nil; child = child.next {
  251. assert(child.pkg != nil)
  252. fmt.wprintf(w, `<tr id="pkg-%s" class="directory-pkg directory-child"><td class="pkg-name">`, str(child.pkg.name))
  253. fmt.wprintf(w, `<a href="/core/%s/">%s</a>`, child.path, child.name)
  254. fmt.wprintf(w, "</td>")
  255. line_doc, _, _ := strings.partition(str(child.pkg.docs), "\n")
  256. line_doc = strings.trim_space(line_doc)
  257. if line_doc != "" {
  258. fmt.wprintf(w, `<td class="pkg-line-doc">%s</td>`, line_doc)
  259. }
  260. fmt.wprintf(w, "</tr>\n")
  261. }
  262. }
  263. fmt.wprintln(w, "\t\t</tbody>")
  264. fmt.wprintln(w, "\t</table>")
  265. }
  266. is_entity_blank :: proc(e: doc.Entity_Index) -> bool {
  267. name := str(entities[e].name)
  268. return name == "" || name == "_"
  269. }
  270. Write_Type_Flag :: enum {
  271. Is_Results,
  272. Variadic,
  273. Allow_Indent,
  274. }
  275. Write_Type_Flags :: distinct bit_set[Write_Type_Flag]
  276. Type_Writer :: struct {
  277. w: io.Writer,
  278. pkg: doc.Pkg_Index,
  279. indent: int,
  280. }
  281. write_type :: proc(using writer: ^Type_Writer, type: doc.Type, flags: Write_Type_Flags) {
  282. write_param_entity :: proc(using writer: ^Type_Writer, e: ^doc.Entity, flags: Write_Type_Flags, name_width := 0) {
  283. name := str(e.name)
  284. if .Param_Using in e.flags { io.write_string(w, "using ") }
  285. if .Param_Const in e.flags { io.write_string(w, "#const ") }
  286. if .Param_Auto_Cast in e.flags { io.write_string(w, "#auto_cast ") }
  287. if .Param_CVararg in e.flags { io.write_string(w, "#c_vararg ") }
  288. if .Param_No_Alias in e.flags { io.write_string(w, "#no_alias ") }
  289. if .Param_Any_Int in e.flags { io.write_string(w, "#any_int ") }
  290. init_string := str(e.init_string)
  291. switch init_string {
  292. case "#caller_location":
  293. assert(name != "")
  294. io.write_string(w, name)
  295. io.write_string(w, " := ")
  296. io.write_string(w, `<a href="/core/runtime/#Source_Code_Location">`)
  297. io.write_string(w, init_string)
  298. io.write_string(w, `</a>`)
  299. case:
  300. if name != "" {
  301. io.write_string(w, name)
  302. io.write_string(w, ": ")
  303. }
  304. padding := max(name_width-len(name), 0)
  305. for _ in 0..<padding {
  306. io.write_byte(w, ' ')
  307. }
  308. param_flags := flags - {.Is_Results}
  309. if .Param_Ellipsis in e.flags {
  310. param_flags += {.Variadic}
  311. }
  312. write_type(writer, types[e.type], param_flags)
  313. if init_string != "" {
  314. io.write_string(w, " = ")
  315. io.write_string(w, init_string)
  316. }
  317. }
  318. }
  319. write_poly_params :: proc(using writer: ^Type_Writer, type: doc.Type, flags: Write_Type_Flags) {
  320. type_entites := array(type.entities)
  321. for entity_index, i in type_entites {
  322. if i > 0 {
  323. io.write_string(w, ", ")
  324. }
  325. write_param_entity(writer, &entities[entity_index], flags)
  326. }
  327. io.write_byte(w, ')')
  328. }
  329. do_indent :: proc(using writer: ^Type_Writer, flags: Write_Type_Flags) {
  330. if .Allow_Indent not_in flags {
  331. return
  332. }
  333. for _ in 0..<indent {
  334. io.write_byte(w, '\t')
  335. }
  336. }
  337. do_newline :: proc(using writer: ^Type_Writer, flags: Write_Type_Flags) {
  338. if .Allow_Indent in flags {
  339. io.write_byte(w, '\n')
  340. }
  341. }
  342. calc_name_width :: proc(type_entites: []doc.Entity_Index) -> (name_width: int) {
  343. for entity_index in type_entites {
  344. e := &entities[entity_index]
  345. name := str(e.name)
  346. name_width = max(len(name), name_width)
  347. }
  348. return
  349. }
  350. type_entites := array(type.entities)
  351. type_types := array(type.types)
  352. switch type.kind {
  353. case .Invalid:
  354. // ignore
  355. case .Basic:
  356. type_flags := transmute(doc.Type_Flags_Basic)type.flags
  357. if .Untyped in type_flags {
  358. io.write_string(w, str(type.name))
  359. } else {
  360. fmt.wprintf(w, `<a href="">%s</a>`, str(type.name))
  361. }
  362. case .Named:
  363. e := entities[type_entites[0]]
  364. name := str(type.name)
  365. fmt.wprintf(w, `<span>`)
  366. tn_pkg := files[e.pos.file].pkg
  367. if tn_pkg != pkg {
  368. fmt.wprintf(w, `%s.`, str(pkgs[tn_pkg].name))
  369. }
  370. fmt.wprintf(w, `<a class="code-typename" href="/core/{0:s}/#{1:s}">{1:s}</a></span>`, pkg_to_path[&pkgs[tn_pkg]], name)
  371. case .Generic:
  372. name := str(type.name)
  373. io.write_byte(w, '$')
  374. io.write_string(w, name)
  375. if len(array(type.types)) == 1 {
  376. io.write_byte(w, '/')
  377. write_type(writer, types[type_types[0]], flags)
  378. }
  379. case .Pointer:
  380. io.write_byte(w, '^')
  381. write_type(writer, types[type_types[0]], flags)
  382. case .Array:
  383. assert(type.elem_count_len == 1)
  384. io.write_byte(w, '[')
  385. io.write_uint(w, uint(type.elem_counts[0]))
  386. io.write_byte(w, ']')
  387. write_type(writer, types[type_types[0]], flags)
  388. case .Enumerated_Array:
  389. io.write_byte(w, '[')
  390. write_type(writer, types[type_types[0]], flags)
  391. io.write_byte(w, ']')
  392. write_type(writer, types[type_types[1]], flags)
  393. case .Slice:
  394. if .Variadic in flags {
  395. io.write_string(w, "..")
  396. } else {
  397. io.write_string(w, "[]")
  398. }
  399. write_type(writer, types[type_types[0]], flags - {.Variadic})
  400. case .Dynamic_Array:
  401. io.write_string(w, "[dynamic]")
  402. write_type(writer, types[type_types[0]], flags)
  403. case .Map:
  404. io.write_string(w, "map[")
  405. write_type(writer, types[type_types[0]], flags)
  406. io.write_byte(w, ']')
  407. write_type(writer, types[type_types[1]], flags)
  408. case .Struct:
  409. type_flags := transmute(doc.Type_Flags_Struct)type.flags
  410. io.write_string(w, "struct")
  411. if .Polymorphic in type_flags {
  412. write_poly_params(writer, type, flags)
  413. }
  414. if .Packed in type_flags { io.write_string(w, " #packed") }
  415. if .Raw_Union in type_flags { io.write_string(w, " #raw_union") }
  416. if custom_align := str(type.custom_align); custom_align != "" {
  417. io.write_string(w, " #align")
  418. io.write_string(w, custom_align)
  419. }
  420. io.write_string(w, " {")
  421. if len(type_entites) != 0 {
  422. do_newline(writer, flags)
  423. indent += 1
  424. name_width := calc_name_width(type_entites)
  425. for entity_index in type_entites {
  426. e := &entities[entity_index]
  427. do_indent(writer, flags)
  428. write_param_entity(writer, e, flags, name_width)
  429. io.write_byte(w, ',')
  430. do_newline(writer, flags)
  431. }
  432. indent -= 1
  433. do_indent(writer, flags)
  434. }
  435. io.write_string(w, "}")
  436. case .Union:
  437. type_flags := transmute(doc.Type_Flags_Union)type.flags
  438. io.write_string(w, "union")
  439. if .Polymorphic in type_flags {
  440. write_poly_params(writer, type, flags)
  441. }
  442. if .No_Nil in type_flags { io.write_string(w, " #no_nil") }
  443. if .Maybe in type_flags { io.write_string(w, " #maybe") }
  444. if custom_align := str(type.custom_align); custom_align != "" {
  445. io.write_string(w, " #align")
  446. io.write_string(w, custom_align)
  447. }
  448. io.write_string(w, " {")
  449. if len(type_types) > 1 {
  450. do_newline(writer, flags)
  451. indent += 1
  452. for type_index in type_types {
  453. do_indent(writer, flags)
  454. write_type(writer, types[type_index], flags)
  455. io.write_string(w, ", ")
  456. do_newline(writer, flags)
  457. }
  458. indent -= 1
  459. do_indent(writer, flags)
  460. }
  461. io.write_string(w, "}")
  462. case .Enum:
  463. io.write_string(w, "enum")
  464. if len(type_types) != 0 {
  465. io.write_byte(w, ' ')
  466. write_type(writer, types[type_types[0]], flags)
  467. }
  468. io.write_string(w, " {")
  469. do_newline(writer, flags)
  470. indent += 1
  471. name_width := calc_name_width(type_entites)
  472. for entity_index in type_entites {
  473. e := &entities[entity_index]
  474. name := str(e.name)
  475. do_indent(writer, flags)
  476. io.write_string(w, name)
  477. if init_string := str(e.init_string); init_string != "" {
  478. for _ in 0..<name_width-len(name) {
  479. io.write_byte(w, ' ')
  480. }
  481. io.write_string(w, " = ")
  482. io.write_string(w, init_string)
  483. }
  484. io.write_string(w, ", ")
  485. do_newline(writer, flags)
  486. }
  487. indent -= 1
  488. do_indent(writer, flags)
  489. io.write_string(w, "}")
  490. case .Tuple:
  491. if len(type_entites) == 0 {
  492. return
  493. }
  494. require_parens := (.Is_Results in flags) && (len(type_entites) > 1 || !is_entity_blank(type_entites[0]))
  495. if require_parens { io.write_byte(w, '(') }
  496. for entity_index, i in type_entites {
  497. if i > 0 {
  498. io.write_string(w, ", ")
  499. }
  500. write_param_entity(writer, &entities[entity_index], flags)
  501. }
  502. if require_parens { io.write_byte(w, ')') }
  503. case .Proc:
  504. type_flags := transmute(doc.Type_Flags_Proc)type.flags
  505. io.write_string(w, "proc")
  506. cc := str(type.calling_convention)
  507. if cc != "" {
  508. io.write_byte(w, ' ')
  509. io.write_quoted_string(w, cc)
  510. io.write_byte(w, ' ')
  511. }
  512. params := array(type.types)[0]
  513. results := array(type.types)[1]
  514. io.write_byte(w, '(')
  515. write_type(writer, types[params], flags)
  516. io.write_byte(w, ')')
  517. if results != 0 {
  518. assert(.Diverging not_in type_flags)
  519. io.write_string(w, " -> ")
  520. write_type(writer, types[results], flags+{.Is_Results})
  521. }
  522. if .Diverging in type_flags {
  523. io.write_string(w, " -> !")
  524. }
  525. if .Optional_Ok in type_flags {
  526. io.write_string(w, " #optional_ok")
  527. }
  528. case .Bit_Set:
  529. type_flags := transmute(doc.Type_Flags_Bit_Set)type.flags
  530. io.write_string(w, "bit_set[")
  531. if .Op_Lt in type_flags {
  532. io.write_uint(w, uint(type.elem_counts[0]))
  533. io.write_string(w, "..<")
  534. io.write_uint(w, uint(type.elem_counts[1]))
  535. } else if .Op_Lt_Eq in type_flags {
  536. io.write_uint(w, uint(type.elem_counts[0]))
  537. io.write_string(w, "..=")
  538. io.write_uint(w, uint(type.elem_counts[1]))
  539. } else {
  540. write_type(writer, types[type_types[0]], flags)
  541. }
  542. if .Underlying_Type in type_flags {
  543. write_type(writer, types[type_types[1]], flags)
  544. }
  545. io.write_string(w, "]")
  546. case .Simd_Vector:
  547. io.write_string(w, "#simd[")
  548. io.write_uint(w, uint(type.elem_counts[0]))
  549. io.write_byte(w, ']')
  550. case .SOA_Struct_Fixed:
  551. io.write_string(w, "#soa[")
  552. io.write_uint(w, uint(type.elem_counts[0]))
  553. io.write_byte(w, ']')
  554. case .SOA_Struct_Slice:
  555. io.write_string(w, "#soa[]")
  556. case .SOA_Struct_Dynamic:
  557. io.write_string(w, "#soa[dynamic]")
  558. case .Relative_Pointer:
  559. io.write_string(w, "#relative(")
  560. write_type(writer, types[type_types[1]], flags)
  561. io.write_string(w, ") ")
  562. write_type(writer, types[type_types[0]], flags)
  563. case .Relative_Slice:
  564. io.write_string(w, "#relative(")
  565. write_type(writer, types[type_types[1]], flags)
  566. io.write_string(w, ") ")
  567. write_type(writer, types[type_types[0]], flags)
  568. case .Multi_Pointer:
  569. io.write_string(w, "[^]")
  570. write_type(writer, types[type_types[0]], flags)
  571. case .Matrix:
  572. io.write_string(w, "matrix[")
  573. io.write_uint(w, uint(type.elem_counts[0]))
  574. io.write_string(w, ", ")
  575. io.write_uint(w, uint(type.elem_counts[1]))
  576. io.write_string(w, "]")
  577. write_type(writer, types[type_types[0]], flags)
  578. }
  579. }
  580. write_docs :: proc(w: io.Writer, pkg: ^doc.Pkg, docs: string) {
  581. if docs == "" {
  582. return
  583. }
  584. it := docs
  585. was_code := true
  586. was_paragraph := true
  587. for line in strings.split_iterator(&it, "\n") {
  588. if strings.has_prefix(line, "\t") {
  589. if !was_code {
  590. was_code = true;
  591. fmt.wprint(w, `<pre class="doc-code"><code>`)
  592. }
  593. fmt.wprintf(w, "%s\n", strings.trim_prefix(line, "\t"))
  594. continue
  595. } else if was_code {
  596. was_code = false
  597. fmt.wprintln(w, "</code></pre>")
  598. continue
  599. }
  600. text := strings.trim_space(line)
  601. if text == "" {
  602. if was_paragraph {
  603. was_paragraph = false
  604. fmt.wprintln(w, "</p>")
  605. }
  606. continue
  607. }
  608. if !was_paragraph {
  609. fmt.wprintln(w, "<p>")
  610. }
  611. assert(!was_code)
  612. was_paragraph = true
  613. fmt.wprintln(w, text)
  614. }
  615. if was_code {
  616. // assert(!was_paragraph, str(pkg.name))
  617. was_code = false
  618. fmt.wprintln(w, "</code></pre>")
  619. }
  620. if was_paragraph {
  621. fmt.wprintln(w, "</p>")
  622. }
  623. }
  624. write_pkg :: proc(w: io.Writer, path: string, pkg: ^doc.Pkg) {
  625. fmt.wprintf(w, "<h1>package core:%s</h1>\n", path)
  626. fmt.wprintln(w, "<h2>Documentation</h2>")
  627. docs := strings.trim_space(str(pkg.docs))
  628. if docs != "" {
  629. fmt.wprintln(w, "<h3>Overview</h3>")
  630. fmt.wprintln(w, "<div id=\"pkg-overview\">")
  631. defer fmt.wprintln(w, "</div>")
  632. write_docs(w, pkg, docs)
  633. }
  634. fmt.wprintln(w, "<h3>Index</h3>")
  635. fmt.wprintln(w, `<section class="documentation-index">`)
  636. pkg_procs: [dynamic]^doc.Entity
  637. pkg_proc_groups: [dynamic]^doc.Entity
  638. pkg_types: [dynamic]^doc.Entity
  639. pkg_vars: [dynamic]^doc.Entity
  640. pkg_consts: [dynamic]^doc.Entity
  641. for entity_index in array(pkg.entities) {
  642. e := &entities[entity_index]
  643. name := str(e.name)
  644. if name == "" || name[0] == '_' {
  645. continue
  646. }
  647. switch e.kind {
  648. case .Invalid, .Import_Name, .Library_Name:
  649. // ignore
  650. case .Constant: append(&pkg_consts, e)
  651. case .Variable: append(&pkg_vars, e)
  652. case .Type_Name: append(&pkg_types, e)
  653. case .Procedure: append(&pkg_procs, e)
  654. case .Proc_Group: append(&pkg_proc_groups, e)
  655. }
  656. }
  657. entity_key :: proc(e: ^doc.Entity) -> string {
  658. return str(e.name)
  659. }
  660. slice.sort_by_key(pkg_procs[:], entity_key)
  661. slice.sort_by_key(pkg_proc_groups[:], entity_key)
  662. slice.sort_by_key(pkg_types[:], entity_key)
  663. slice.sort_by_key(pkg_vars[:], entity_key)
  664. slice.sort_by_key(pkg_consts[:], entity_key)
  665. print_index :: proc(w: io.Writer, name: string, entities: []^doc.Entity) {
  666. fmt.wprintf(w, "<h4>%s</h4>\n", name)
  667. fmt.wprintln(w, `<section class="documentation-index">`)
  668. if len(entities) == 0 {
  669. io.write_string(w, "<p>This section is empty.</p>\n")
  670. } else {
  671. fmt.wprintln(w, "<ul>")
  672. for e in entities {
  673. name := str(e.name)
  674. fmt.wprintf(w, "<li><a href=\"#{0:s}\">{0:s}</a></li>\n", name)
  675. }
  676. fmt.wprintln(w, "</ul>")
  677. }
  678. fmt.wprintln(w, "</section>")
  679. }
  680. print_index(w, "Procedures", pkg_procs[:])
  681. print_index(w, "Procedure Groups", pkg_proc_groups[:])
  682. print_index(w, "Types", pkg_types[:])
  683. print_index(w, "Variables", pkg_vars[:])
  684. print_index(w, "Constants", pkg_consts[:])
  685. fmt.wprintln(w, "</section>")
  686. print_entity :: proc(w: io.Writer, e: ^doc.Entity) {
  687. write_attributes :: proc(w: io.Writer, e: ^doc.Entity) {
  688. for attr in array(e.attributes) {
  689. io.write_string(w, "@(")
  690. name := str(attr.name)
  691. value := str(attr.value)
  692. io.write_string(w, name)
  693. if value != "" {
  694. io.write_string(w, "=")
  695. io.write_string(w, value)
  696. }
  697. io.write_string(w, ")\n")
  698. }
  699. }
  700. pkg_index := files[e.pos.file].pkg
  701. pkg := &pkgs[pkg_index]
  702. writer := &Type_Writer{
  703. w = w,
  704. pkg = pkg_index,
  705. }
  706. name := str(e.name)
  707. path := pkg_to_path[pkg]
  708. filename := slashpath.base(str(files[e.pos.file].name))
  709. fmt.wprintf(w, "<h4 id=\"{0:s}\"><span><a class=\"documentation-id-link\" href=\"#%s\">{0:s}", name)
  710. fmt.wprintf(w, "<span class=\"a-hidden\">&nbsp;¶</span></a></span></h4>\n")
  711. defer if e.pos.file != 0 && e.pos.line > 0 {
  712. src_url := fmt.tprintf("%s/%s/%s#L%d", GITHUB_CORE_URL, path, filename, e.pos.line)
  713. fmt.wprintf(w, "<a class=\"documentation-source\" href=\"{0:s}\"><em>Source:&nbsp;{0:s}</em></a>", src_url)
  714. }
  715. switch e.kind {
  716. case .Invalid, .Import_Name, .Library_Name:
  717. // ignore
  718. case .Constant:
  719. fmt.wprint(w, "<pre>")
  720. the_type := types[e.type]
  721. if the_type.kind == .Basic && .Untyped in (transmute(doc.Type_Flags_Basic)the_type.flags) {
  722. fmt.wprintf(w, "%s :: ", name)
  723. } else {
  724. fmt.wprintf(w, "%s: ", name)
  725. write_type(writer, the_type, {.Allow_Indent})
  726. fmt.wprintf(w, " : ")
  727. }
  728. init_string := str(e.init_string)
  729. assert(init_string != "")
  730. io.write_string(w, init_string)
  731. fmt.wprintln(w, "</pre>")
  732. case .Variable:
  733. fmt.wprint(w, "<pre>")
  734. write_attributes(w, e)
  735. fmt.wprintf(w, "%s: ", name)
  736. write_type(writer, types[e.type], {.Allow_Indent})
  737. init_string := str(e.init_string)
  738. if init_string != "" {
  739. io.write_string(w, " = ")
  740. io.write_string(w, init_string)
  741. }
  742. fmt.wprintln(w, "</pre>")
  743. case .Type_Name:
  744. fmt.wprint(w, "<pre>")
  745. fmt.wprintf(w, "%s :: ", name)
  746. the_type := types[e.type]
  747. type_to_print := the_type
  748. if the_type.kind == .Named && .Type_Alias not_in e.flags {
  749. if e.pos == entities[array(the_type.entities)[0]].pos {
  750. bt := base_type(the_type)
  751. #partial switch bt.kind {
  752. case .Struct, .Union, .Proc, .Enum:
  753. // Okay
  754. case:
  755. io.write_string(w, "distinct ")
  756. }
  757. type_to_print = bt
  758. }
  759. }
  760. write_type(writer, type_to_print, {.Allow_Indent})
  761. fmt.wprintln(w, "</pre>")
  762. case .Procedure:
  763. fmt.wprint(w, "<pre>")
  764. fmt.wprintf(w, "%s :: ", name)
  765. write_type(writer, types[e.type], nil)
  766. where_clauses := array(e.where_clauses)
  767. if len(where_clauses) != 0 {
  768. io.write_string(w, " where ")
  769. for clause, i in where_clauses {
  770. if i > 0 {
  771. io.write_string(w, ", ")
  772. }
  773. io.write_string(w, str(clause))
  774. }
  775. }
  776. fmt.wprint(w, " {…}")
  777. fmt.wprintln(w, "</pre>")
  778. case .Proc_Group:
  779. fmt.wprint(w, "<pre>")
  780. fmt.wprintf(w, "%s :: proc{{\n", name)
  781. for entity_index in array(e.grouped_entities) {
  782. this_proc := &entities[entity_index]
  783. this_pkg := files[this_proc.pos.file].pkg
  784. io.write_byte(w, '\t')
  785. if this_pkg != pkg_index {
  786. fmt.wprintf(w, "%s.", str(pkgs[this_pkg].name))
  787. }
  788. name := str(this_proc.name)
  789. fmt.wprintf(w, `<a class="code-procedure" href="/core/{0:s}/#{1:s}">`, pkg_to_path[&pkgs[this_pkg]], name)
  790. io.write_string(w, name)
  791. io.write_string(w, `</a>`)
  792. io.write_byte(w, ',')
  793. io.write_byte(w, '\n')
  794. }
  795. fmt.wprintln(w, "}")
  796. fmt.wprintln(w, "</pre>")
  797. }
  798. write_docs(w, pkg, strings.trim_space(str(e.docs)))
  799. }
  800. print_entities :: proc(w: io.Writer, title: string, entities: []^doc.Entity) {
  801. fmt.wprintf(w, "<h3>%s</h3>\n", title)
  802. fmt.wprintln(w, `<section class="documentation">`)
  803. if len(entities) == 0 {
  804. io.write_string(w, "<p>This section is empty.</p>\n")
  805. } else {
  806. for e in entities {
  807. print_entity(w, e)
  808. }
  809. }
  810. fmt.wprintln(w, "</section>")
  811. }
  812. print_entities(w, "Procedures", pkg_procs[:])
  813. print_entities(w, "Procedure Groups", pkg_proc_groups[:])
  814. print_entities(w, "Types", pkg_types[:])
  815. print_entities(w, "Variables", pkg_vars[:])
  816. print_entities(w, "Constants", pkg_consts[:])
  817. fmt.wprintln(w, "<h3>Source Files</h3>")
  818. fmt.wprintln(w, "<ul>")
  819. any_hidden := false
  820. source_file_loop: for file_index in array(pkg.files) {
  821. file := files[file_index]
  822. filename := slashpath.base(str(file.name))
  823. switch {
  824. case
  825. strings.has_suffix(filename, "_windows.odin"),
  826. strings.has_suffix(filename, "_darwin.odin"),
  827. strings.has_suffix(filename, "_essence.odin"),
  828. strings.has_suffix(filename, "_freebsd.odin"),
  829. strings.has_suffix(filename, "_wasi.odin"),
  830. strings.has_suffix(filename, "_js.odin"),
  831. strings.has_suffix(filename, "_freestanding.odin"),
  832. strings.has_suffix(filename, "_amd64.odin"),
  833. strings.has_suffix(filename, "_i386.odin"),
  834. strings.has_suffix(filename, "_arch64.odin"),
  835. strings.has_suffix(filename, "_wasm32.odin"),
  836. strings.has_suffix(filename, "_wasm64.odin"),
  837. false:
  838. any_hidden = true
  839. continue source_file_loop
  840. }
  841. fmt.wprintf(w, `<li><a href="%s/%s/%s">%s</a></li>`, GITHUB_CORE_URL, path, filename, filename)
  842. fmt.wprintln(w)
  843. }
  844. if any_hidden {
  845. fmt.wprintln(w, "<li><em>(hidden platform specific files)</em></li>")
  846. }
  847. fmt.wprintln(w, "</ul>")
  848. }