Browse Source

Add `-doc-format` command for the new .odin-doc file format (to be used to generate documentation tools)

gingerBill 4 years ago
parent
commit
2f1c896290
9 changed files with 1550 additions and 18 deletions
  1. 251 0
      core/odin/doc-format/doc_format.odin
  2. 1 0
      src/build_settings.cpp
  3. 0 1
      src/check_type.cpp
  4. 62 15
      src/docs.cpp
  5. 208 0
      src/docs_format.cpp
  6. 1023 0
      src/docs_writer.cpp
  7. 0 1
      src/llvm_backend.cpp
  8. 5 0
      src/main.cpp
  9. 0 1
      src/types.cpp

+ 251 - 0
core/odin/doc-format/doc_format.odin

@@ -0,0 +1,251 @@
+package odin_doc_format
+
+import "core:mem"
+
+Array :: struct($T: typeid) {
+	offset: u32,
+	length: u32,
+}
+
+String :: distinct Array(byte);
+
+Version_Type_Major :: 0;
+Version_Type_Minor :: 1;
+Version_Type_Patch :: 0;
+
+Version_Type :: struct {
+	major, minor, patch: u8,
+	_: u8,
+};
+
+Version_Type_Default :: Version_Type{
+	major=Version_Type_Major,
+	minor=Version_Type_Minor,
+	patch=Version_Type_Patch,
+};
+
+Magic_String :: "odindoc\x00";
+
+Header_Base :: struct {
+	magic: [8]byte,
+	_: u32,
+	version:     Version_Type,
+	total_size:  u32,
+	header_size: u32,
+	hash:        u32,
+}
+
+Header :: struct {
+	using base: Header_Base,
+
+	// NOTE: These arrays reserve the zero element as a sentinel value
+	files:    Array(File),
+	pkgs:     Array(Pkg),
+	entities: Array(Entity),
+	types:    Array(Type),
+}
+
+File_Index   :: distinct u32;
+Pkg_Index    :: distinct u32;
+Entity_Index :: distinct u32;
+Type_Index   :: distinct u32;
+
+
+Position :: struct {
+	file:   File_Index,
+	line:   u32,
+	column: u32,
+	offset: u32,
+};
+
+File :: struct {
+	pkg:  Pkg_Index,
+	name: String,
+}
+
+Pkg :: struct {
+	fullpath: String,
+	name:     String,
+	docs:     String,
+	files:    Array(File_Index),
+	entities: Array(Entity_Index),
+}
+
+Entity_Kind :: enum u32 {
+	Invalid      = 0,
+	Constant     = 1,
+	Variable     = 2,
+	Type_Name    = 3,
+	Procedure    = 4,
+	Proc_Group   = 5,
+	Import_Name  = 6,
+	Library_Name = 7,
+}
+
+Entity_Flag :: enum u32 {
+	Foreign = 0,
+	Export  = 1,
+
+	Param_Using     = 2,
+	Param_Const     = 3,
+	Param_Auto_Cast = 4,
+	Param_Ellipsis  = 5,
+	Param_CVararg   = 6,
+	Param_No_Alias  = 7,
+
+	Type_Alias = 8,
+
+	Var_Thread_Local = 9,
+}
+
+Entity_Flags :: distinct bit_set[Entity_Flag; u32];
+
+Entity :: struct {
+	kind:             Entity_Kind,
+	flags:            Entity_Flags,
+	pos:              Position,
+	name:             String,
+	type:             Type_Index,
+	init_string:      String,
+	_:                u32,
+	comment:          String,
+	docs:             String,
+	foreign_library:  Entity_Index,
+	link_name:        String,
+	attributes:       Array(Attribute),
+	grouped_entities: Array(Entity_Index), // Procedure Groups
+	where_clauses:    Array(String), // Procedures
+}
+
+Attribute :: struct {
+	name:  String,
+	value: String,
+}
+
+Type_Kind :: enum u32 {
+	Invalid            = 0,
+	Basic              = 1,
+	Named              = 2,
+	Generic            = 3,
+	Pointer            = 4,
+	Array              = 5,
+	Enumerated_Array   = 6,
+	Slice              = 7,
+	Dynamic_Array      = 8,
+	Map                = 9,
+	Struct             = 10,
+	Union              = 11,
+	Enum               = 12,
+	Tuple              = 13,
+	Proc               = 14,
+	Bit_Set            = 15,
+	Simd_Vector        = 16,
+	SOA_Struct_Fixed   = 17,
+	SOA_Struct_Slice   = 18,
+	SOA_Struct_Dynamic = 19,
+	Relative_Pointer   = 20,
+	Relative_Slice     = 21,
+}
+
+Type_Elems_Cap :: 4;
+
+Type :: struct {
+	kind:         Type_Kind,
+	flags:        u32, // Type_Kind specific
+	name:         String,
+	custom_align: String,
+
+	// Used by some types
+	elem_count_len: u32,
+	elem_counts:    [Type_Elems_Cap]u64,
+
+	// Each of these is esed by some types, not all
+	types:         Array(Type_Index),
+	entities:      Array(Entity_Index),
+	polymorphic_params: Type_Index, // Struct, Union
+	where_clauses: Array(String), // Struct, Union
+}
+
+Type_Flags_Basic :: distinct bit_set[Type_Flag_Basic; u32];
+Type_Flag_Basic :: enum u32 {
+	Untyped = 1,
+}
+
+Type_Flags_Struct :: distinct bit_set[Type_Flag_Struct; u32];
+Type_Flag_Struct :: enum u32 {
+	Polymorphic = 0,
+	Packed      = 1,
+	Raw_Union   = 2,
+}
+
+Type_Flags_Union :: distinct bit_set[Type_Flag_Union; u32];
+Type_Flag_Union :: enum u32 {
+	Polymorphic = 0,
+	No_Nil      = 1,
+	Maybe       = 2,
+}
+
+Type_Flags_Proc :: distinct bit_set[Type_Flag_Proc; u32];
+Type_Flag_Proc :: enum u32 {
+	Polymorphic = 0,
+	Diverging   = 1,
+	Optional_Ok = 2,
+	Variadic    = 3,
+	C_Vararg    = 4,
+}
+
+Type_Flags_BitSet :: distinct bit_set[Type_Flag_BitSet; u32];
+Type_Flag_BitSet :: enum u32 {
+	Range            = 1,
+	Op_Lt            = 2,
+	Op_Lt_Eq         = 3,
+	Underlying_Type  = 4,
+}
+
+Type_Flags_SimdVector :: distinct bit_set[Type_Flag_SimdVector; u32];
+Type_Flag_SimdVector :: enum u32 {
+	x86_mmx = 1,
+}
+
+from_array :: proc(base: ^Header_Base, a: $A/Array($T)) -> []T {
+	s: mem.Raw_Slice;
+	s.data = rawptr(uintptr(base) + uintptr(a.offset));
+	s.len = int(a.length);
+	return transmute([]T)s;
+}
+from_string :: proc(base: ^Header_Base, s: String) -> string {
+	return string(from_array(base, s));
+}
+
+
+
+
+Reader_Error :: enum {
+	None,
+	Header_Too_Small,
+	Invalid_Magic,
+	Data_Too_Small,
+	Invalid_Version,
+}
+
+read_from_bytes :: proc(data: []byte) -> (h: ^Header, err: Reader_Error) {
+	if len(data) < size_of(Header_Base) {
+		err = .Header_Too_Small;
+		return;
+	}
+	header_base := (^Header_Base)(raw_data(data));
+	if header_base.magic != Magic_String {
+		err = .Invalid_Magic;
+		return;
+	}
+	if len(data) < int(header_base.total_size) {
+		err = .Data_Too_Small;
+		return;
+	}
+	if header_base.version != Version_Type_Default {
+		err = .Invalid_Version;
+		return;
+	}
+	h = (^Header)(header_base);
+	return;
+}

+ 1 - 0
src/build_settings.cpp

@@ -135,6 +135,7 @@ char const *odin_command_strings[32] = {
 enum CmdDocFlag : u32 {
 enum CmdDocFlag : u32 {
 	CmdDocFlag_Short       = 1<<0,
 	CmdDocFlag_Short       = 1<<0,
 	CmdDocFlag_AllPackages = 1<<1,
 	CmdDocFlag_AllPackages = 1<<1,
+	CmdDocFlag_DocFormat   = 1<<2,
 };
 };
 
 
 
 

+ 0 - 1
src/check_type.cpp

@@ -2461,7 +2461,6 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node,
 	type->Proc.specialization_count = specialization_count;
 	type->Proc.specialization_count = specialization_count;
 	type->Proc.diverging            = pt->diverging;
 	type->Proc.diverging            = pt->diverging;
 	type->Proc.optional_ok          = optional_ok;
 	type->Proc.optional_ok          = optional_ok;
-	type->Proc.tags                 = pt->tags;
 
 
 	if (param_count > 0) {
 	if (param_count > 0) {
 		Entity *end = params->Tuple.variables[param_count-1];
 		Entity *end = params->Tuple.variables[param_count-1];

+ 62 - 15
src/docs.cpp

@@ -34,9 +34,17 @@ GB_COMPARE_PROC(cmp_entities_for_printing) {
 	Entity *x = *cast(Entity **)a;
 	Entity *x = *cast(Entity **)a;
 	Entity *y = *cast(Entity **)b;
 	Entity *y = *cast(Entity **)b;
 	int res = 0;
 	int res = 0;
-	res = string_compare(x->pkg->name, y->pkg->name);
-	if (res != 0) {
-		return res;
+	if (x->pkg != y->pkg) {
+		if (x->pkg == nullptr) {
+			return -1;
+		}
+		if (y->pkg == nullptr) {
+			return +1;
+		}
+		res = string_compare(x->pkg->name, y->pkg->name);
+		if (res != 0) {
+			return res;
+		}
 	}
 	}
 	int ox = print_entity_kind_ordering[x->kind];
 	int ox = print_entity_kind_ordering[x->kind];
 	int oy = print_entity_kind_ordering[y->kind];
 	int oy = print_entity_kind_ordering[y->kind];
@@ -56,6 +64,9 @@ GB_COMPARE_PROC(cmp_ast_package_by_name) {
 	return string_compare(x->name, y->name);
 	return string_compare(x->name, y->name);
 }
 }
 
 
+#include "docs_format.cpp"
+#include "docs_writer.cpp"
+
 void print_doc_line(i32 indent, char const *fmt, ...) {
 void print_doc_line(i32 indent, char const *fmt, ...) {
 	while (indent --> 0) {
 	while (indent --> 0) {
 		gb_printf("\t");
 		gb_printf("\t");
@@ -297,23 +308,59 @@ void print_doc_package(CheckerInfo *info, AstPackage *pkg) {
 void generate_documentation(Checker *c) {
 void generate_documentation(Checker *c) {
 	CheckerInfo *info = &c->info;
 	CheckerInfo *info = &c->info;
 
 
-	auto pkgs = array_make<AstPackage *>(permanent_allocator(), 0, info->packages.entries.count);
-	for_array(i, info->packages.entries) {
-		AstPackage *pkg = info->packages.entries[i].value;
-		if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) {
-			array_add(&pkgs, pkg);
+	if (build_context.cmd_doc_flags & CmdDocFlag_DocFormat) {
+		String init_fullpath = c->parser->init_fullpath;
+		String output_name = {};
+		String output_base = {};
+
+		if (build_context.out_filepath.len == 0) {
+			output_name = remove_directory_from_path(init_fullpath);
+			output_name = remove_extension_from_path(output_name);
+			output_name = string_trim_whitespace(output_name);
+			if (output_name.len == 0) {
+				output_name = info->init_scope->pkg->name;
+			}
+			output_base = output_name;
 		} else {
 		} else {
-			if (pkg->kind == Package_Init) {
-				array_add(&pkgs, pkg);
-			} else if (pkg->is_extra) {
+			output_name = build_context.out_filepath;
+			output_name = string_trim_whitespace(output_name);
+			if (output_name.len == 0) {
+				output_name = info->init_scope->pkg->name;
+			}
+			isize pos = string_extension_position(output_name);
+			if (pos < 0) {
+				output_base = output_name;
+			} else {
+				output_base = substring(output_name, 0, pos);
+			}
+		}
+
+		output_base = path_to_full_path(permanent_allocator(), output_base);
+
+		gbString output_file_path = gb_string_make_length(heap_allocator(), output_base.text, output_base.len);
+		output_file_path = gb_string_appendc(output_file_path, ".odin-doc");
+		defer (gb_string_free(output_file_path));
+
+		odin_doc_write(info, output_file_path);
+	} else {
+		auto pkgs = array_make<AstPackage *>(permanent_allocator(), 0, info->packages.entries.count);
+		for_array(i, info->packages.entries) {
+			AstPackage *pkg = info->packages.entries[i].value;
+			if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) {
 				array_add(&pkgs, pkg);
 				array_add(&pkgs, pkg);
+			} else {
+				if (pkg->kind == Package_Init) {
+					array_add(&pkgs, pkg);
+				} else if (pkg->is_extra) {
+					array_add(&pkgs, pkg);
+				}
 			}
 			}
 		}
 		}
-	}
 
 
-	gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name);
+		gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name);
 
 
-	for_array(i, pkgs) {
-		print_doc_package(info, pkgs[i]);
+		for_array(i, pkgs) {
+			print_doc_package(info, pkgs[i]);
+		}
 	}
 	}
 }
 }

+ 208 - 0
src/docs_format.cpp

@@ -0,0 +1,208 @@
+#define OdinDocHeader_MagicString "odindoc\0"
+
+template <typename T>
+struct OdinDocArray {
+	u32 offset;
+	u32 length;
+};
+
+using OdinDocString = OdinDocArray<u8>;
+
+struct OdinDocVersionType {
+	u8 major, minor, patch;
+	u8 pad0;
+};
+
+#define OdinDocVersionType_Major 0
+#define OdinDocVersionType_Minor 1
+#define OdinDocVersionType_Patch 0
+
+struct OdinDocHeaderBase {
+	u8                 magic[8];
+	u32                padding0;
+	OdinDocVersionType version;
+	u32                total_size;
+	u32                header_size;
+	u32                hash; // after header
+};
+
+template <typename T>
+Slice<T> from_array(OdinDocHeaderBase *base, OdinDocArray<T> const &a) {
+	Slice<T> s = {};
+	s.data  = cast(T *)(cast(uintptr)base + cast(uintptr)a.offset);
+	s.count = cast(isize)a.length;
+	return s;
+}
+
+String from_string(OdinDocHeaderBase *base, OdinDocString const &s) {
+	String str = {};
+	str.text = cast(u8 *)(cast(uintptr)base + cast(uintptr)s.offset);
+	str.len  = cast(isize)s.length;
+	return str;
+}
+
+typedef u32 OdinDocFileIndex;
+typedef u32 OdinDocPkgIndex;
+typedef u32 OdinDocEntityIndex;
+typedef u32 OdinDocTypeIndex;
+
+struct OdinDocFile {
+	OdinDocPkgIndex pkg;
+	OdinDocString   name;
+};
+
+struct OdinDocPosition {
+	OdinDocFileIndex file;
+	u32              line;
+	u32              column;
+	u32              offset;
+};
+
+enum OdinDocTypeKind : u32 {
+	OdinDocType_Invalid          = 0,
+	OdinDocType_Basic            = 1,
+	OdinDocType_Named            = 2,
+	OdinDocType_Generic          = 3,
+	OdinDocType_Pointer          = 4,
+	OdinDocType_Array            = 5,
+	OdinDocType_EnumeratedArray  = 6,
+	OdinDocType_Slice            = 7,
+	OdinDocType_DynamicArray     = 8,
+	OdinDocType_Map              = 9,
+	OdinDocType_Struct           = 10,
+	OdinDocType_Union            = 11,
+	OdinDocType_Enum             = 12,
+	OdinDocType_Tuple            = 13,
+	OdinDocType_Proc             = 14,
+	OdinDocType_BitSet           = 15,
+	OdinDocType_SimdVector       = 16,
+	OdinDocType_SOAStructFixed   = 17,
+	OdinDocType_SOAStructSlice   = 18,
+	OdinDocType_SOAStructDynamic = 19,
+	OdinDocType_RelativePointer  = 20,
+	OdinDocType_RelativeSlice    = 21,
+};
+
+enum OdinDocTypeFlag_Basic : u32 {
+	OdinDocTypeFlag_Basic_untyped = 1<<1,
+};
+
+enum OdinDocTypeFlag_Struct : u32 {
+	OdinDocTypeFlag_Struct_polymorphic = 1<<0,
+	OdinDocTypeFlag_Struct_packed      = 1<<1,
+	OdinDocTypeFlag_Struct_raw_union   = 1<<2,
+};
+
+enum OdinDocTypeFlag_Union : u32 {
+	OdinDocTypeFlag_Union_polymorphic = 1<<0,
+	OdinDocTypeFlag_Union_no_nil      = 1<<1,
+	OdinDocTypeFlag_Union_maybe       = 1<<2,
+};
+
+enum OdinDocTypeFlag_Proc : u32 {
+	OdinDocTypeFlag_Proc_polymorphic = 1<<0,
+	OdinDocTypeFlag_Proc_diverging   = 1<<1,
+	OdinDocTypeFlag_Proc_optional_ok = 1<<2,
+	OdinDocTypeFlag_Proc_variadic    = 1<<3,
+	OdinDocTypeFlag_Proc_c_vararg    = 1<<4,
+};
+
+enum OdinDocTypeFlag_BitSet : u32 {
+	OdinDocTypeFlag_BitSet_Range          = 1<<1,
+	OdinDocTypeFlag_BitSet_OpLt           = 1<<2,
+	OdinDocTypeFlag_BitSet_OpLtEq         = 1<<3,
+	OdinDocTypeFlag_BitSet_UnderlyingType = 1<<4,
+};
+
+enum OdinDocTypeFlag_SimdVector : u32 {
+	OdinDocTypeFlag_BitSet_x86_mmx = 1<<1,
+};
+
+enum {
+	// constants
+	OdinDocType_ElemsCap = 4,
+};
+
+struct OdinDocType {
+	OdinDocTypeKind kind;
+	u32             flags;
+	OdinDocString   name;
+	OdinDocString   custom_align;
+
+	// Used by some types
+	u32 elem_count_len;
+	u64 elem_counts[OdinDocType_ElemsCap];
+
+	// Each of these is esed by some types, not all
+	OdinDocArray<OdinDocTypeIndex> types;
+	OdinDocArray<OdinDocEntityIndex> entities;
+	OdinDocTypeIndex polmorphic_params;
+	OdinDocArray<OdinDocString> where_clauses;
+};
+
+struct OdinDocAttribute {
+	OdinDocString name;
+	OdinDocString value;
+};
+
+enum OdinDocEntityKind : u32 {
+	OdinDocEntity_Invalid     = 0,
+	OdinDocEntity_Constant    = 1,
+	OdinDocEntity_Variable    = 2,
+	OdinDocEntity_TypeName    = 3,
+	OdinDocEntity_Procedure   = 4,
+	OdinDocEntity_ProcGroup   = 5,
+	OdinDocEntity_ImportName  = 6,
+	OdinDocEntity_LibraryName = 7,
+};
+
+enum OdinDocEntityFlag : u32 {
+	OdinDocEntityFlag_Foreign = 1<<0,
+	OdinDocEntityFlag_Export  = 1<<1,
+
+	OdinDocEntityFlag_Param_Using    = 1<<2,
+	OdinDocEntityFlag_Param_Const    = 1<<3,
+	OdinDocEntityFlag_Param_AutoCast = 1<<4,
+	OdinDocEntityFlag_Param_Ellipsis = 1<<5,
+	OdinDocEntityFlag_Param_CVararg  = 1<<6,
+	OdinDocEntityFlag_Param_NoAlias  = 1<<7,
+
+	OdinDocEntityFlag_Type_Alias = 1<<8,
+
+	OdinDocEntityFlag_Var_Thread_Local = 1<<9,
+};
+
+struct OdinDocEntity {
+	OdinDocEntityKind  kind;
+	u32                flags;
+	OdinDocPosition    pos;
+	OdinDocString      name;
+	OdinDocTypeIndex   type;
+	OdinDocString      init_string;
+	u32                reserved_for_init;
+	OdinDocString      comment;
+	OdinDocString      docs;
+	OdinDocEntityIndex foreign_library;
+	OdinDocString      link_name;
+	OdinDocArray<OdinDocAttribute> attributes;
+	OdinDocArray<OdinDocEntityIndex> grouped_entities; // Procedure Groups
+	OdinDocArray<OdinDocString>      where_clauses; // Procedures
+};
+
+struct OdinDocPkg {
+	OdinDocString fullpath;
+	OdinDocString name;
+	OdinDocString docs;
+	OdinDocArray<OdinDocFileIndex>   files;
+	OdinDocArray<OdinDocEntityIndex> entities;
+};
+
+struct OdinDocHeader {
+	OdinDocHeaderBase base;
+
+	OdinDocArray<OdinDocFile>   files;
+	OdinDocArray<OdinDocPkg>    pkgs;
+	OdinDocArray<OdinDocEntity> entities;
+	OdinDocArray<OdinDocType>   types;
+};
+

+ 1023 - 0
src/docs_writer.cpp

@@ -0,0 +1,1023 @@
+
+template <typename T>
+struct OdinDocWriterItemTracker {
+	isize len;
+	isize cap;
+	isize offset;
+};
+
+enum OdinDocWriterState {
+	OdinDocWriterState_Preparing,
+	OdinDocWriterState_Writing,
+};
+
+char const* OdinDocWriterState_strings[] {
+	"preparing",
+	"writing  ",
+};
+
+struct OdinDocWriter {
+	CheckerInfo *info;
+	OdinDocWriterState state;
+
+	void *data;
+	isize data_len;
+	OdinDocHeader *header;
+
+	StringMap<OdinDocString> string_cache;
+
+	Map<OdinDocFileIndex>   file_cache;      // Key: AstFile *
+	Map<OdinDocPkgIndex>    pkg_cache;       // Key: AstPackage *
+	Map<OdinDocEntityIndex> entity_cache;    // Key: Entity *
+	Map<Entity *>           entity_id_cache; // Key: OdinDocEntityIndex
+	Map<OdinDocTypeIndex>   type_cache;      // Key: Type *
+	Map<Type *>             type_id_cache;   // Key: OdinDocTypeIndex
+
+	OdinDocWriterItemTracker<OdinDocFile>   files;
+	OdinDocWriterItemTracker<OdinDocPkg>    pkgs;
+	OdinDocWriterItemTracker<OdinDocEntity> entities;
+	OdinDocWriterItemTracker<OdinDocType>   types;
+
+	OdinDocWriterItemTracker<u8> strings;
+	OdinDocWriterItemTracker<u8> blob;
+};
+
+OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e);
+OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type);
+
+template <typename T>
+void odin_doc_writer_item_tracker_init(OdinDocWriterItemTracker<T> *t, isize size) {
+	t->len = size;
+	t->cap = size;
+}
+
+
+void odin_doc_writer_prepare(OdinDocWriter *w) {
+	w->state = OdinDocWriterState_Preparing;
+
+	gbAllocator a = heap_allocator();
+	string_map_init(&w->string_cache, a);
+
+	map_init(&w->file_cache, a);
+	map_init(&w->pkg_cache, a);
+	map_init(&w->entity_cache, a);
+	map_init(&w->entity_id_cache, a);
+	map_init(&w->type_cache, a);
+	map_init(&w->type_id_cache, a);
+
+	odin_doc_writer_item_tracker_init(&w->files,    1);
+	odin_doc_writer_item_tracker_init(&w->pkgs,     1);
+	odin_doc_writer_item_tracker_init(&w->entities, 1);
+	odin_doc_writer_item_tracker_init(&w->types,    1);
+	odin_doc_writer_item_tracker_init(&w->strings, 16);
+	odin_doc_writer_item_tracker_init(&w->blob,    16);
+}
+
+
+void odin_doc_writer_destroy(OdinDocWriter *w) {
+	gb_free(heap_allocator(), w->data);
+
+	string_map_destroy(&w->string_cache);
+	map_destroy(&w->file_cache);
+	map_destroy(&w->pkg_cache);
+	map_destroy(&w->entity_cache);
+	map_destroy(&w->entity_id_cache);
+	map_destroy(&w->type_cache);
+	map_destroy(&w->type_id_cache);
+}
+
+
+
+template <typename T>
+void odin_doc_writer_tracker_size(isize *offset, OdinDocWriterItemTracker<T> *t, isize alignment=1) {
+	isize size = t->cap*gb_size_of(T);
+	isize align = gb_max(gb_align_of(T), alignment);
+	*offset = align_formula_isize(*offset, align);
+	t->offset = *offset;
+	*offset += size;
+}
+
+isize odin_doc_writer_calc_total_size(OdinDocWriter *w) {
+	isize total_size = gb_size_of(OdinDocHeader);
+	odin_doc_writer_tracker_size(&total_size, &w->files);
+	odin_doc_writer_tracker_size(&total_size, &w->pkgs);
+	odin_doc_writer_tracker_size(&total_size, &w->entities);
+	odin_doc_writer_tracker_size(&total_size, &w->types);
+	odin_doc_writer_tracker_size(&total_size, &w->strings, 16);
+	odin_doc_writer_tracker_size(&total_size, &w->blob, 16);
+	return total_size;
+}
+
+void odin_doc_writer_start_writing(OdinDocWriter *w) {
+	w->state = OdinDocWriterState_Writing;
+
+	string_map_clear(&w->string_cache);
+	map_clear(&w->file_cache);
+	map_clear(&w->pkg_cache);
+	map_clear(&w->entity_cache);
+	map_clear(&w->entity_id_cache);
+	map_clear(&w->type_cache);
+	map_clear(&w->type_id_cache);
+
+	isize total_size = odin_doc_writer_calc_total_size(w);
+	total_size = align_formula_isize(total_size, 8);
+	w->data = gb_alloc_align(heap_allocator(), total_size, 8);
+	w->data_len = total_size;
+	w->header = cast(OdinDocHeader *)w->data;
+}
+
+u32 hash_data_after_header(OdinDocHeaderBase *base, void *data, isize data_len) {
+	u8 *start = cast(u8 *)data;
+	u8 *end = start + base->total_size;
+	start += base->header_size;
+
+	u32 h = 0x811c9dc5;
+	for (u8 *b = start; b != end; b++) {
+		h = (h ^ cast(u32)*b) * 0x01000193;
+	}
+	return h;
+}
+
+
+template <typename T>
+void odin_doc_writer_assign_tracker(OdinDocArray<T> *array, OdinDocWriterItemTracker<T> const &t) {
+	array->offset = cast(u32)t.offset;
+	array->length = cast(u32)t.len;
+}
+
+
+void odin_doc_writer_end_writing(OdinDocWriter *w) {
+	OdinDocHeader *h = w->header;
+
+	gb_memmove(h->base.magic, OdinDocHeader_MagicString, gb_strlen(OdinDocHeader_MagicString));
+	h->base.version.major = OdinDocVersionType_Major;
+	h->base.version.minor = OdinDocVersionType_Minor;
+	h->base.version.patch = OdinDocVersionType_Patch;
+	h->base.total_size    = cast(u32)w->data_len;
+	h->base.header_size   = gb_size_of(*h);
+	h->base.hash = hash_data_after_header(&h->base, w->data, w->data_len);
+
+	odin_doc_writer_assign_tracker(&h->files,    w->files);
+	odin_doc_writer_assign_tracker(&h->pkgs,     w->pkgs);
+	odin_doc_writer_assign_tracker(&h->entities, w->entities);
+	odin_doc_writer_assign_tracker(&h->types,    w->types);
+}
+
+template <typename T>
+u32 odin_doc_write_item(OdinDocWriter *w, OdinDocWriterItemTracker<T> *t, T const *item, T **dst=nullptr) {
+	if (w->state == OdinDocWriterState_Preparing) {
+		t->cap += 1;
+		if (dst) *dst = nullptr;
+		return 0;
+	} else {
+		GB_ASSERT_MSG(t->len < t->cap, "%td < %td", t->len, t->cap);
+		isize item_index = t->len++;
+		uintptr data = cast(uintptr)w->data + cast(uintptr)(t->offset + gb_size_of(T)*item_index);
+		if (item) {
+			gb_memmove(cast(T *)data, item, gb_size_of(T));
+		}
+		if (dst) *dst = cast(T *)data;
+
+		return cast(u32)item_index;
+	}
+}
+
+template <typename T>
+T *odin_doc_get_item(OdinDocWriter *w, OdinDocWriterItemTracker<T> *t, u32 index) {
+	if (w->state != OdinDocWriterState_Writing) {
+		return nullptr;
+	}
+	GB_ASSERT(index < t->len);
+	uintptr data = cast(uintptr)w->data + cast(uintptr)(t->offset + gb_size_of(T)*index);
+	return cast(T *)data;
+}
+
+OdinDocString odin_doc_write_string_without_cache(OdinDocWriter *w, String const &str) {
+	OdinDocString res = {};
+
+	if (w->state == OdinDocWriterState_Preparing) {
+		w->strings.cap += str.len+1;
+	} else {
+		GB_ASSERT_MSG(w->strings.len+str.len+1 <= w->strings.cap, "%td <= %td", w->strings.len+str.len, w->strings.cap);
+
+		isize offset = w->strings.offset + w->strings.len;
+		u8 *data = cast(u8 *)w->data + offset;
+		gb_memmove(data, str.text, str.len);
+		data[str.len] = 0;
+		w->strings.len += str.len+1;
+		res.offset = cast(u32)offset;
+		res.length = cast(u32)str.len;
+	}
+
+	return res;
+}
+
+OdinDocString odin_doc_write_string(OdinDocWriter *w, String const &str) {
+	OdinDocString *c = string_map_get(&w->string_cache, str);
+	if (c != nullptr) {
+		if (w->state == OdinDocWriterState_Writing) {
+			GB_ASSERT(from_string(&w->header->base, *c) == str);
+		}
+		return *c;
+	}
+
+	OdinDocString res = odin_doc_write_string_without_cache(w, str);
+
+	string_map_set(&w->string_cache, str, res);
+
+	return res;
+}
+
+
+
+template <typename T>
+OdinDocArray<T> odin_write_slice(OdinDocWriter *w, T *data, isize len) {
+	GB_ASSERT(gb_align_of(T) <= 4);
+	if (len <= 0) {
+		return {0, 0};
+	}
+	isize alignment = 4;
+
+	if (w->state == OdinDocWriterState_Preparing) {
+		w->blob.cap = align_formula_isize(w->blob.cap, alignment);
+		w->blob.cap += len * gb_size_of(T);
+		return {0, 0};
+	}
+
+	w->blob.len = align_formula_isize(w->blob.len, alignment);
+
+	isize offset = w->blob.offset + w->blob.len;
+	u8 *dst = cast(u8 *)w->data + offset;
+	gb_memmove(dst, data, len*gb_size_of(T));
+
+	w->blob.len += len * gb_size_of(T);
+
+	return {cast(u32)offset, cast(u32)len};
+}
+
+
+template <typename T>
+OdinDocArray<T> odin_write_item_as_slice(OdinDocWriter *w, T data) {
+	return odin_write_slice(w, &data, 1);
+}
+
+
+OdinDocPosition odin_doc_token_pos_cast(OdinDocWriter *w, TokenPos const &pos) {
+	OdinDocFileIndex file_index = 0;
+	if (pos.file_id != 0) {
+		String file_path = get_file_path_string(pos.file_id);
+		if (file_path != "") {
+			AstFile **found = string_map_get(&w->info->files, file_path);
+			GB_ASSERT(found != nullptr);
+			AstFile *file = *found;
+			OdinDocFileIndex *file_index_found = map_get(&w->file_cache, hash_pointer(file));
+			GB_ASSERT(file_index_found != nullptr);
+			file_index = *file_index_found;
+		}
+	}
+
+	OdinDocPosition doc_pos = {};
+	doc_pos.file   = file_index;
+	doc_pos.line   = cast(u32)pos.line;
+	doc_pos.column = cast(u32)pos.column;
+	doc_pos.offset = cast(u32)pos.offset;
+	return doc_pos;
+}
+
+bool odin_doc_append_comment_group_string(Array<u8> *buf, CommentGroup *g) {
+	if (g == nullptr) {
+		return false;
+	}
+	isize len = 0;
+	for_array(i, g->list) {
+		String comment = g->list[i].string;
+		len += comment.len;
+		len += 1; // for \n
+	}
+	if (len <= g->list.count) {
+		return false;
+	}
+
+	isize count = 0;
+	for_array(i, g->list) {
+		String comment = g->list[i].string;
+		String original_comment = comment;
+
+		bool slash_slash = comment[1] == '/';
+		bool slash_star = comment[1] == '*';
+		if (comment[1] == '/') {
+			comment.text += 2;
+			comment.len  -= 2;
+		} else if (comment[1] == '*') {
+			comment.text += 2;
+			comment.len  -= 4;
+		}
+
+		// Ignore the first space
+		if (comment.len > 0 && comment[0] == ' ') {
+			comment.text += 1;
+			comment.len  -= 1;
+		}
+
+		if (slash_slash) {
+			if (string_starts_with(comment, str_lit("+"))) {
+				continue;
+			}
+			if (string_starts_with(comment, str_lit("@("))) {
+				continue;
+			}
+		}
+
+		if (slash_slash) {
+			array_add_elems(buf, comment.text, comment.len);
+			array_add(buf, cast(u8)'\n');
+			count += 1;
+		} else {
+			isize pos = 0;
+			for (; pos < comment.len; pos++) {
+				isize end = pos;
+				for (; end < comment.len; end++) {
+					if (comment[end] == '\n') {
+						break;
+					}
+				}
+				String line = substring(comment, pos, end);
+				pos = end+1;
+				String trimmed_line = string_trim_whitespace(line);
+				if (trimmed_line.len == 0) {
+					if (count == 0) {
+						continue;
+					}
+				}
+				/*
+				 * Remove comments with
+				 * styles
+				 * like this
+				 */
+				if (string_starts_with(line, str_lit("* "))) {
+					line = substring(line, 2, line.len);
+				}
+
+				array_add_elems(buf, line.text, line.len);
+				array_add(buf, cast(u8)'\n');
+				count += 1;
+			}
+		}
+	}
+
+	if (count > 0) {
+		array_add(buf, cast(u8)'\n');
+		return true;
+	}
+	return false;
+}
+
+OdinDocString odin_doc_pkg_doc_string(OdinDocWriter *w, AstPackage *pkg) {
+	if (pkg == nullptr) {
+		return {};
+	}
+	auto buf = array_make<u8>(permanent_allocator(), 0, 0); // Minor leak
+
+	for_array(i, pkg->files) {
+		AstFile *f = pkg->files[i];
+		if (f->pkg_decl) {
+			GB_ASSERT(f->pkg_decl->kind == Ast_PackageDecl);
+			odin_doc_append_comment_group_string(&buf, f->pkg_decl->PackageDecl.docs);
+		}
+	}
+
+	return odin_doc_write_string_without_cache(w, make_string(buf.data, buf.count));
+}
+
+OdinDocString odin_doc_comment_group_string(OdinDocWriter *w, CommentGroup *g) {
+	if (g == nullptr) {
+		return {};
+	}
+	auto buf = array_make<u8>(permanent_allocator(), 0, 0); // Minor leak
+
+	odin_doc_append_comment_group_string(&buf, g);
+
+	return odin_doc_write_string_without_cache(w, make_string(buf.data, buf.count));
+}
+
+OdinDocString odin_doc_expr_string(OdinDocWriter *w, Ast *expr) {
+	if (expr == nullptr) {
+		return {};
+	}
+	gbString s = write_expr_to_string( // Minor leak
+		gb_string_make(permanent_allocator(), ""),
+		expr,
+		build_context.cmd_doc_flags & CmdDocFlag_Short
+	);
+
+	return odin_doc_write_string(w, make_string(cast(u8 *)s, gb_string_length(s)));
+}
+
+OdinDocArray<OdinDocAttribute> odin_doc_attributes(OdinDocWriter *w, Array<Ast *> const &attributes) {
+	isize count = 0;
+	for_array(i, attributes) {
+		Ast *attr = attributes[i];
+		if (attr->kind != Ast_Attribute) continue;
+		count += attr->Attribute.elems.count;
+	};
+
+	auto attribs = array_make<OdinDocAttribute>(heap_allocator(), 0, count);
+	defer (array_free(&attribs));
+
+	for_array(i, attributes) {
+		Ast *attr = attributes[i];
+		if (attr->kind != Ast_Attribute) continue;
+		for_array(j, attr->Attribute.elems) {
+			Ast *elem = attr->Attribute.elems[j];
+			String name = {};
+			Ast *value = nullptr;
+			switch (elem->kind) {
+			case_ast_node(i, Ident, elem);
+				name = i->token.string;
+			case_end;
+			case_ast_node(i, Implicit, elem);
+				name = i->string;
+			case_end;
+			case_ast_node(fv, FieldValue, elem);
+				if (fv->field->kind == Ast_Ident) {
+					name = fv->field->Ident.token.string;
+				} else if (fv->field->kind == Ast_Implicit) {
+					name = fv->field->Implicit.string;
+				}
+				value = fv->value;
+			case_end;
+			default:
+				continue;
+			}
+
+			OdinDocAttribute doc_attrib = {};
+			doc_attrib.name = odin_doc_write_string(w, name);
+			doc_attrib.value = odin_doc_expr_string(w, value);
+			array_add(&attribs, doc_attrib);
+		}
+	}
+	return odin_write_slice(w, attribs.data, attribs.count);
+}
+
+OdinDocArray<OdinDocString> odin_doc_where_clauses(OdinDocWriter *w, Slice<Ast *> const &where_clauses) {
+	if (where_clauses.count == 0) {
+		return {};
+	}
+	auto clauses = array_make<OdinDocString>(heap_allocator(), where_clauses.count);
+	defer (array_free(&clauses));
+
+	for_array(i, where_clauses) {
+		clauses[i] = odin_doc_expr_string(w, where_clauses[i]);
+	}
+
+	return odin_write_slice(w, clauses.data, clauses.count);
+}
+
+OdinDocArray<OdinDocTypeIndex> odin_doc_type_as_slice(OdinDocWriter *w, Type *type) {
+	OdinDocTypeIndex index = odin_doc_type(w, type);
+	return odin_write_item_as_slice(w, index);
+}
+
+OdinDocArray<OdinDocEntityIndex> odin_doc_add_entity_as_slice(OdinDocWriter *w, Entity *e) {
+	OdinDocEntityIndex index = odin_doc_add_entity(w, e);
+	return odin_write_item_as_slice(w, index);
+}
+
+OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) {
+	if (type == nullptr) {
+		return 0;
+	}
+	OdinDocTypeIndex *found = map_get(&w->type_cache, hash_pointer(type));
+	if (found) {
+		return *found;
+	}
+	for_array(i, w->type_cache.entries) {
+		// NOTE(bill): THIS IS SLOW
+		Type *other = cast(Type *)cast(uintptr)w->type_cache.entries[i].key.key;
+		if (are_types_identical(type, other)) {
+			OdinDocTypeIndex index = w->type_cache.entries[i].value;
+			map_set(&w->type_cache, hash_pointer(type), index);
+			return index;
+		}
+	}
+
+
+	OdinDocType *dst = nullptr;
+	OdinDocType doc_type = {};
+	OdinDocTypeIndex type_index = 0;
+	type_index = odin_doc_write_item(w, &w->types, &doc_type, &dst);
+	map_set(&w->type_cache, hash_pointer(type), type_index);
+	map_set(&w->type_id_cache, hash_integer(type_index), type);
+
+
+	switch (type->kind) {
+	case Type_Basic:
+		doc_type.kind = OdinDocType_Basic;
+		doc_type.name = odin_doc_write_string(w, type->Basic.name);
+		if (is_type_untyped(type)) {
+			doc_type.flags |= OdinDocTypeFlag_Basic_untyped;
+		}
+		break;
+	case Type_Named:
+		doc_type.kind = OdinDocType_Named;
+		doc_type.name = odin_doc_write_string(w, type->Named.name);
+		doc_type.entities = odin_doc_add_entity_as_slice(w, type->Named.type_name);
+		break;
+	case Type_Generic:
+		doc_type.kind = OdinDocType_Generic;
+		doc_type.name = odin_doc_write_string(w, type->Generic.name);
+		if (type->Generic.specialized) {
+			doc_type.types = odin_doc_type_as_slice(w, type->Generic.specialized);
+		}
+		break;
+	case Type_Pointer:
+		doc_type.kind = OdinDocType_Pointer;
+		doc_type.types = odin_doc_type_as_slice(w, type->Pointer.elem);
+		break;
+	case Type_Array:
+		doc_type.kind = OdinDocType_Array;
+		doc_type.elem_count_len = 1;
+		doc_type.elem_counts[0] = type->Array.count;
+		doc_type.types = odin_doc_type_as_slice(w, type->Array.elem);
+		break;
+	case Type_EnumeratedArray:
+		doc_type.kind = OdinDocType_EnumeratedArray;
+		doc_type.elem_count_len = 1;
+		doc_type.elem_counts[0] = type->EnumeratedArray.count;
+		doc_type.types = odin_doc_type_as_slice(w, type->EnumeratedArray.elem);
+		break;
+	case Type_Slice:
+		doc_type.kind = OdinDocType_Slice;
+		doc_type.types = odin_doc_type_as_slice(w, type->Slice.elem);
+		break;
+	case Type_DynamicArray:
+		doc_type.kind = OdinDocType_DynamicArray;
+		doc_type.types = odin_doc_type_as_slice(w, type->DynamicArray.elem);
+		break;
+	case Type_Map:
+		doc_type.kind = OdinDocType_Map;
+		{
+			OdinDocTypeIndex types[2] = {};
+			types[0] = odin_doc_type(w, type->Map.key);
+			types[1] = odin_doc_type(w, type->Map.value);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+		}
+		break;
+	case Type_Struct:
+		doc_type.kind = OdinDocType_Struct;
+		if (type->Struct.soa_kind != StructSoa_None) {
+			switch (type->Struct.soa_kind) {
+			case StructSoa_Fixed:
+				doc_type.kind = OdinDocType_SOAStructFixed;
+				doc_type.elem_count_len = 1;
+				doc_type.elem_counts[0] = type->Struct.soa_count;
+				break;
+			case StructSoa_Slice:
+				doc_type.kind = OdinDocType_SOAStructSlice;
+				break;
+			case StructSoa_Dynamic:
+				doc_type.kind = OdinDocType_SOAStructDynamic;
+				break;
+			}
+			doc_type.types = odin_doc_type_as_slice(w, type->Struct.soa_elem);
+		} else {
+			if (type->Struct.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Struct_polymorphic; }
+			if (type->Struct.is_packed)      { doc_type.flags |= OdinDocTypeFlag_Struct_packed; }
+			if (type->Struct.is_raw_union)   { doc_type.flags |= OdinDocTypeFlag_Struct_raw_union; }
+
+			auto fields = array_make<OdinDocEntityIndex>(heap_allocator(), type->Struct.fields.count);
+			defer (array_free(&fields));
+
+			for_array(i, type->Struct.fields) {
+				fields[i] = odin_doc_add_entity(w, type->Struct.fields[i]);
+			}
+
+			doc_type.entities = odin_write_slice(w, fields.data, fields.count);
+			doc_type.polmorphic_params = odin_doc_type(w, type->Struct.polymorphic_params);
+
+			if (type->Struct.node) {
+				ast_node(st, StructType, type->Struct.node);
+				if (st->align) {
+					doc_type.custom_align = odin_doc_expr_string(w, st->align);
+				}
+				doc_type.where_clauses = odin_doc_where_clauses(w, st->where_clauses);
+			}
+		}
+		break;
+	case Type_Union:
+		doc_type.kind = OdinDocType_Union;
+		if (type->Union.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Union_polymorphic; }
+		if (type->Union.no_nil)         { doc_type.flags |= OdinDocTypeFlag_Union_no_nil; }
+		if (type->Union.maybe)          { doc_type.flags |= OdinDocTypeFlag_Union_maybe; }
+
+		{
+			auto variants = array_make<OdinDocTypeIndex>(heap_allocator(), type->Union.variants.count);
+			defer (array_free(&variants));
+
+			for_array(i, type->Union.variants) {
+				variants[i] = odin_doc_type(w, type->Union.variants[i]);
+			}
+
+			doc_type.types = odin_write_slice(w, variants.data, variants.count);
+			doc_type.polmorphic_params = odin_doc_type(w, type->Union.polymorphic_params);
+		}
+
+		if (type->Union.node) {
+			ast_node(ut, UnionType, type->Union.node);
+			if (ut->align) {
+				doc_type.custom_align = odin_doc_expr_string(w, ut->align);
+			}
+			doc_type.where_clauses = odin_doc_where_clauses(w, ut->where_clauses);
+		}
+		break;
+	case Type_Enum:
+		doc_type.kind = OdinDocType_Enum;
+		{
+			auto fields = array_make<OdinDocEntityIndex>(heap_allocator(), type->Enum.fields.count);
+			defer (array_free(&fields));
+
+			for_array(i, type->Enum.fields) {
+				fields[i] = odin_doc_add_entity(w, type->Enum.fields[i]);
+			}
+			doc_type.entities = odin_write_slice(w, fields.data, fields.count);
+			if (type->Enum.base_type) {
+				doc_type.types = odin_doc_type_as_slice(w, type->Enum.base_type);
+			}
+		}
+		break;
+	case Type_Tuple:
+		doc_type.kind = OdinDocType_Tuple;
+		{
+			auto variables = array_make<OdinDocEntityIndex>(heap_allocator(), type->Tuple.variables.count);
+			defer (array_free(&variables));
+
+			for_array(i, type->Tuple.variables) {
+				variables[i] = odin_doc_add_entity(w, type->Tuple.variables[i]);
+			}
+
+			doc_type.entities = odin_write_slice(w, variables.data, variables.count);
+		}
+		break;
+	case Type_Proc:
+		doc_type.kind = OdinDocType_Proc;
+		if (type->Proc.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Proc_polymorphic; }
+		if (type->Proc.diverging)      { doc_type.flags |= OdinDocTypeFlag_Proc_diverging; }
+		if (type->Proc.optional_ok)    { doc_type.flags |= OdinDocTypeFlag_Proc_optional_ok; }
+		if (type->Proc.variadic)       { doc_type.flags |= OdinDocTypeFlag_Proc_variadic; }
+		if (type->Proc.c_vararg)       { doc_type.flags |= OdinDocTypeFlag_Proc_c_vararg; }
+		{
+			OdinDocTypeIndex types[2];
+			types[0] = odin_doc_type(w, type->Proc.params);
+			types[1] = odin_doc_type(w, type->Proc.results);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+		}
+		break;
+	case Type_BitSet:
+		doc_type.kind = OdinDocType_BitSet;
+		{
+			isize type_count = 0;
+			OdinDocTypeIndex types[2] = {};
+			if (type->BitSet.elem) {
+				types[type_count++] = odin_doc_type(w, type->BitSet.elem);
+			}
+			if (type->BitSet.underlying) {
+				types[type_count++] = odin_doc_type(w, type->BitSet.underlying);
+				doc_type.flags |= OdinDocTypeFlag_BitSet_UnderlyingType;
+			}
+			doc_type.types = odin_write_slice(w, types, type_count);
+			doc_type.elem_count_len = 2;
+			doc_type.elem_counts[0] = cast(u64)type->BitSet.lower;
+			doc_type.elem_counts[1] = cast(u64)type->BitSet.upper;
+		}
+		break;
+	case Type_SimdVector:
+		doc_type.kind = OdinDocType_SimdVector;
+		if (type->SimdVector.is_x86_mmx) {
+			doc_type.flags |= OdinDocTypeFlag_BitSet_x86_mmx;
+		} else {
+			doc_type.elem_count_len = 1;
+			doc_type.elem_counts[0] = type->SimdVector.count;
+			doc_type.types = odin_doc_type_as_slice(w, type->SimdVector.elem);
+		}
+		// TODO(bill):
+		break;
+	case Type_RelativePointer:
+		doc_type.kind = OdinDocType_RelativePointer;
+		{
+			OdinDocTypeIndex types[2] = {};
+			types[0] = odin_doc_type(w, type->RelativePointer.pointer_type);
+			types[1] = odin_doc_type(w, type->RelativePointer.base_integer);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+		}
+		break;
+	case Type_RelativeSlice:
+		doc_type.kind = OdinDocType_RelativeSlice;
+		{
+			OdinDocTypeIndex types[2] = {};
+			types[0] = odin_doc_type(w, type->RelativeSlice.slice_type);
+			types[1] = odin_doc_type(w, type->RelativeSlice.base_integer);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+		}
+		break;
+	}
+
+	if (dst) {
+		*dst = doc_type;
+	}
+	return type_index;
+}
+OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) {
+	if (e == nullptr) {
+		return 0;
+	}
+
+	OdinDocEntityIndex *prev_index = map_get(&w->entity_cache, hash_pointer(e));
+	if (prev_index) {
+		return *prev_index;
+	}
+
+	if (e->pkg != nullptr && map_get(&w->pkg_cache, hash_pointer(e->pkg)) == nullptr) {
+		return 0;
+	}
+
+	OdinDocEntity doc_entity = {};
+	OdinDocEntity* dst = nullptr;
+
+	OdinDocEntityIndex doc_entity_index = odin_doc_write_item(w, &w->entities, &doc_entity, &dst);
+	map_set(&w->entity_cache, hash_pointer(e), doc_entity_index);
+	map_set(&w->entity_id_cache, hash_integer(doc_entity_index), e);
+
+
+	Ast *type_expr = nullptr;
+	Ast *init_expr = nullptr;
+	Ast *decl_node = nullptr;
+	CommentGroup *comment = nullptr;
+	CommentGroup *docs = nullptr;
+	if (e->decl_info != nullptr) {
+		type_expr = e->decl_info->type_expr;
+		init_expr = e->decl_info->init_expr;
+		decl_node = e->decl_info->decl_node;
+		comment = e->decl_info->comment;
+		docs = e->decl_info->docs;
+	}
+
+	String link_name = {};
+
+	OdinDocEntityKind kind = OdinDocEntity_Invalid;
+	u32 flags = 0;
+
+	switch (e->kind) {
+	case Entity_Invalid:     kind = OdinDocEntity_Invalid;     break;
+	case Entity_Constant:    kind = OdinDocEntity_Constant;    break;
+	case Entity_Variable:    kind = OdinDocEntity_Variable;    break;
+	case Entity_TypeName:    kind = OdinDocEntity_TypeName;    break;
+	case Entity_Procedure:   kind = OdinDocEntity_Procedure;   break;
+	case Entity_ProcGroup:   kind = OdinDocEntity_ProcGroup;   break;
+	case Entity_ImportName:  kind = OdinDocEntity_ImportName;  break;
+	case Entity_LibraryName: kind = OdinDocEntity_LibraryName; break;
+	}
+
+	switch (e->kind) {
+	case Entity_TypeName:
+		if (e->TypeName.is_type_alias) {
+			flags |= OdinDocEntityFlag_Type_Alias;
+		}
+		break;
+	case Entity_Variable:
+		if (e->Variable.is_foreign) { flags |= OdinDocEntityFlag_Foreign; }
+		if (e->Variable.is_export)  { flags |= OdinDocEntityFlag_Export; }
+		if (e->Variable.thread_local_model != "") {
+			flags |= OdinDocEntityFlag_Var_Thread_Local;
+		}
+		link_name = e->Variable.link_name;
+		break;
+	case Entity_Procedure:
+		if (e->Procedure.is_foreign) { flags |= OdinDocEntityFlag_Foreign; }
+		if (e->Procedure.is_export)  { flags |= OdinDocEntityFlag_Export; }
+		link_name = e->Procedure.link_name;
+		break;
+	}
+
+
+	doc_entity.kind = kind;
+	doc_entity.flags = flags;
+	doc_entity.pos = odin_doc_token_pos_cast(w, e->token.pos);
+	doc_entity.name = odin_doc_write_string(w, e->token.string);
+	doc_entity.type = 0; // Set later
+	doc_entity.init_string = odin_doc_expr_string(w, init_expr);
+	doc_entity.comment = odin_doc_comment_group_string(w, comment);
+	doc_entity.docs = odin_doc_comment_group_string(w, docs);
+	doc_entity.foreign_library = 0; // Set later
+	doc_entity.link_name = odin_doc_write_string(w, link_name);
+	if (e->decl_info != nullptr) {
+		doc_entity.attributes = odin_doc_attributes(w, e->decl_info->attributes);
+	}
+	doc_entity.grouped_entities = {}; // Set later
+
+	if (dst) {
+		*dst = doc_entity;
+	}
+
+	return doc_entity_index;
+}
+
+void odin_doc_update_entities(OdinDocWriter *w) {
+	{
+		// NOTE(bill): Double pass, just in case entities are created on odin_doc_type
+		auto entities = array_make<Entity *>(heap_allocator(), w->entity_cache.entries.count);
+		defer (array_free(&entities));
+
+		for_array(i, w->entity_cache.entries) {
+			Entity *e = cast(Entity *)cast(uintptr)w->entity_cache.entries[i].key.key;
+			entities[i] = e;
+		}
+		for_array(i, entities) {
+			Entity *e = entities[i];
+			OdinDocTypeIndex type_index = odin_doc_type(w, e->type);
+		}
+	}
+
+	for_array(i, w->entity_cache.entries) {
+		Entity *e = cast(Entity *)cast(uintptr)w->entity_cache.entries[i].key.key;
+		OdinDocEntityIndex entity_index = w->entity_cache.entries[i].value;
+		OdinDocTypeIndex type_index = odin_doc_type(w, e->type);
+
+		OdinDocEntityIndex foreign_library = 0;
+		OdinDocArray<OdinDocEntityIndex> grouped_entities = {};
+
+		switch (e->kind) {
+		case Entity_Variable:
+			foreign_library = odin_doc_add_entity(w, e->Variable.foreign_library);
+			break;
+		case Entity_Procedure:
+			foreign_library = odin_doc_add_entity(w, e->Procedure.foreign_library);
+			break;
+		case Entity_ProcGroup:
+			{
+				auto pges = array_make<OdinDocEntityIndex>(heap_allocator(), 0, e->ProcGroup.entities.count);
+				defer (array_free(&pges));
+
+				for_array(j, e->ProcGroup.entities) {
+					OdinDocEntityIndex index = odin_doc_add_entity(w, e->ProcGroup.entities[j]);
+					array_add(&pges, index);
+				}
+				grouped_entities = odin_write_slice(w, pges.data, pges.count);
+			}
+			break;
+		}
+
+		OdinDocEntity *dst = odin_doc_get_item(w, &w->entities, entity_index);
+		if (dst) {
+			dst->type = type_index;
+			dst->foreign_library = foreign_library;
+			dst->grouped_entities = grouped_entities;
+		}
+	}
+}
+
+
+
+OdinDocArray<OdinDocEntityIndex> odin_doc_add_pkg_entities(OdinDocWriter *w, AstPackage *pkg) {
+	if (pkg->scope == nullptr) {
+		return {};
+	}
+	if (map_get(&w->pkg_cache, hash_pointer(pkg)) == nullptr) {
+		return {};
+	}
+
+	auto entities = array_make<Entity *>(heap_allocator(), 0, pkg->scope->elements.entries.count);
+	defer (array_free(&entities));
+
+	for_array(i, pkg->scope->elements.entries) {
+		Entity *e = pkg->scope->elements.entries[i].value;
+		switch (e->kind) {
+		case Entity_Invalid:
+		case Entity_Builtin:
+		case Entity_Nil:
+		case Entity_Label:
+			continue;
+		case Entity_Constant:
+		case Entity_Variable:
+		case Entity_TypeName:
+		case Entity_Procedure:
+		case Entity_ProcGroup:
+		case Entity_ImportName:
+		case Entity_LibraryName:
+			// Fine
+			break;
+		}
+		array_add(&entities, e);
+	}
+	gb_sort_array(entities.data, entities.count, cmp_entities_for_printing);
+
+	auto entity_indices = array_make<OdinDocEntityIndex>(heap_allocator(), 0, w->entity_cache.entries.count);
+	defer (array_free(&entity_indices));
+
+	EntityKind curr_entity_kind = Entity_Invalid;
+	for_array(i, entities) {
+		Entity *e = entities[i];
+		if (e->pkg != pkg) {
+			continue;
+		}
+		if (!is_entity_exported(e)) {
+			continue;
+		}
+		if (e->token.string.len == 0) {
+			continue;
+		}
+
+		OdinDocEntityIndex doc_entity_index = 0;
+		doc_entity_index = odin_doc_add_entity(w, e);
+		array_add(&entity_indices, doc_entity_index);
+	}
+
+	return odin_write_slice(w, entity_indices.data, entity_indices.count);
+}
+
+
+void odin_doc_write_docs(OdinDocWriter *w) {
+	auto pkgs = array_make<AstPackage *>(heap_allocator(), 0, w->info->packages.entries.count);
+	defer (array_free(&pkgs));
+	for_array(i, w->info->packages.entries) {
+		AstPackage *pkg = w->info->packages.entries[i].value;
+		if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) {
+			array_add(&pkgs, pkg);
+		} else {
+			if (pkg->kind == Package_Init) {
+				array_add(&pkgs, pkg);
+			} else if (pkg->is_extra) {
+				array_add(&pkgs, pkg);
+			}
+		}
+	}
+
+	gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name);
+
+	for_array(i, pkgs) {
+		gbAllocator allocator = heap_allocator();
+
+		AstPackage *pkg = pkgs[i];
+		OdinDocPkg doc_pkg = {};
+		doc_pkg.fullpath = odin_doc_write_string(w, pkg->fullpath);
+		doc_pkg.name     = odin_doc_write_string(w, pkg->name);
+		doc_pkg.docs     = odin_doc_pkg_doc_string(w, pkg);
+
+		OdinDocPkg *dst = nullptr;
+		OdinDocPkgIndex pkg_index = odin_doc_write_item(w, &w->pkgs, &doc_pkg, &dst);
+		map_set(&w->pkg_cache, hash_pointer(pkg), pkg_index);
+
+		auto file_indices = array_make<OdinDocFileIndex>(heap_allocator(), 0, pkg->files.count);
+		defer (array_free(&file_indices));
+
+		for_array(j, pkg->files) {
+			AstFile *file = pkg->files[j];
+			OdinDocFile doc_file = {};
+			doc_file.pkg = pkg_index;
+			doc_file.name = odin_doc_write_string(w, file->fullpath);
+			OdinDocFileIndex file_index = odin_doc_write_item(w, &w->files, &doc_file);
+			map_set(&w->file_cache, hash_pointer(file), file_index);
+			array_add(&file_indices, file_index);
+		}
+
+		doc_pkg.files = odin_write_slice(w, file_indices.data, file_indices.count);
+		doc_pkg.entities = odin_doc_add_pkg_entities(w, pkg);
+
+		if (dst) {
+			*dst = doc_pkg;
+		}
+	}
+
+	odin_doc_update_entities(w);
+}
+
+
+void odin_doc_write_to_file(OdinDocWriter *w, char const *filename) {
+	gbFile f = {};
+	gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, filename);
+	if (err != gbFileError_None) {
+		gb_printf_err("Failed to write .odin-doc to: %s\n", filename);
+		gb_exit(1);
+		return;
+	}
+	defer (gb_file_close(&f));
+	if (gb_file_write(&f, w->data, w->data_len)) {
+		err = gb_file_truncate(&f, w->data_len);
+		gb_printf("Wrote .odin-doc file to: %s\n", filename);
+	}
+}
+
+void odin_doc_write(CheckerInfo *info, char const *filename) {
+	OdinDocWriter w_ = {};
+	OdinDocWriter *w = &w_;
+	defer (odin_doc_writer_destroy(w));
+	w->info = info;
+
+	odin_doc_writer_prepare(w);
+	odin_doc_write_docs(w);
+
+	odin_doc_writer_start_writing(w);
+	odin_doc_write_docs(w);
+	odin_doc_writer_end_writing(w);
+
+	odin_doc_write_to_file(w, filename);
+}

+ 0 - 1
src/llvm_backend.cpp

@@ -2523,7 +2523,6 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity) {
 	p->type           = entity->type;
 	p->type           = entity->type;
 	p->type_expr      = decl->type_expr;
 	p->type_expr      = decl->type_expr;
 	p->body           = pl->body;
 	p->body           = pl->body;
-	p->tags           = pt->Proc.tags;
 	p->inlining       = ProcInlining_none;
 	p->inlining       = ProcInlining_none;
 	p->is_foreign     = entity->Procedure.is_foreign;
 	p->is_foreign     = entity->Procedure.is_foreign;
 	p->is_export      = entity->Procedure.is_export;
 	p->is_export      = entity->Procedure.is_export;

+ 5 - 0
src/main.cpp

@@ -607,6 +607,7 @@ enum BuildFlagKind {
 
 
 	BuildFlag_Short,
 	BuildFlag_Short,
 	BuildFlag_AllPackages,
 	BuildFlag_AllPackages,
+	BuildFlag_DocFormat,
 
 
 	BuildFlag_IgnoreWarnings,
 	BuildFlag_IgnoreWarnings,
 	BuildFlag_WarningsAsErrors,
 	BuildFlag_WarningsAsErrors,
@@ -721,6 +722,7 @@ bool parse_build_flags(Array<String> args) {
 
 
 	add_flag(&build_flags, BuildFlag_Short,         str_lit("short"),        BuildFlagParam_None, Command_doc);
 	add_flag(&build_flags, BuildFlag_Short,         str_lit("short"),        BuildFlagParam_None, Command_doc);
 	add_flag(&build_flags, BuildFlag_AllPackages,   str_lit("all-packages"), BuildFlagParam_None, Command_doc);
 	add_flag(&build_flags, BuildFlag_AllPackages,   str_lit("all-packages"), BuildFlagParam_None, Command_doc);
+	add_flag(&build_flags, BuildFlag_DocFormat,     str_lit("doc-format"),   BuildFlagParam_None, Command_doc);
 
 
 	add_flag(&build_flags, BuildFlag_IgnoreWarnings,   str_lit("ignore-warnings"),    BuildFlagParam_None, Command_all);
 	add_flag(&build_flags, BuildFlag_IgnoreWarnings,   str_lit("ignore-warnings"),    BuildFlagParam_None, Command_all);
 	add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all);
 	add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all);
@@ -1227,6 +1229,9 @@ bool parse_build_flags(Array<String> args) {
 						case BuildFlag_AllPackages:
 						case BuildFlag_AllPackages:
 							build_context.cmd_doc_flags |= CmdDocFlag_AllPackages;
 							build_context.cmd_doc_flags |= CmdDocFlag_AllPackages;
 							break;
 							break;
+						case BuildFlag_DocFormat:
+							build_context.cmd_doc_flags |= CmdDocFlag_DocFormat;
+							break;
 						case BuildFlag_IgnoreWarnings:
 						case BuildFlag_IgnoreWarnings:
 							if (build_context.warnings_as_errors) {
 							if (build_context.warnings_as_errors) {
 								gb_printf_err("-ignore-warnings cannot be used with -warnings-as-errors\n");
 								gb_printf_err("-ignore-warnings cannot be used with -warnings-as-errors\n");

+ 0 - 1
src/types.cpp

@@ -195,7 +195,6 @@ struct TypeProc {
 	Type *   results; // Type_Tuple
 	Type *   results; // Type_Tuple
 	i32      param_count;
 	i32      param_count;
 	i32      result_count;
 	i32      result_count;
-	u64      tags;
 	isize    specialization_count;
 	isize    specialization_count;
 	ProcCallingConvention calling_convention;
 	ProcCallingConvention calling_convention;
 	i32      variadic_index;
 	i32      variadic_index;