Features: * Struct, function (no inline), variables * The only definition we need to handle exporting here is struct. typedef, traits, forall, inline functions, we'll look into those later * Also no `export import` (ie. modules that export other module's contents automatically) * int, int*, const int * No extra built-in types considered here * No `#include` * No manual control over headers demonstrated in example, everything handled in `module`, `import`, `export` * No forward decls * All definitions within modules, with declarations automatically hoisted to the top * Note: this requires top-level declarations to be context-free Stylistic choices: * `module;` doesn't specify a namespace (prefix with file path) * folder files like `graph.cfa` are outside their folders (simplifies the import file discovery algorithm) * `import` uses no quotes/angle brackets (unlike `#include`) * `import` automatically opens the namespace (keeping it closed can be tricky with transitive dependencies) * `stdio` and other standard C libraries have `import` versions * `main.cfa` is not a module (avoid module namespacing interfering with `main()` symbol discovery) Step-by-step: * `0_initial` is the code that is written by the developer * Note how modules seem to have circular dependencies (eg. `graph/node.c` imports `graph/edge.c` and vice versa) * Top-level declarations within modules can also be declared out-of-order (eg. using `a` before `const int a = 4;`) * `1_size_analysis` describes a mechanism for figuring out sizes and alignments of types * There could also be an attribute system for indicating a desired size and alignment for a given type (not shown here) * `2_tshell` generates the type shell headers * These allow importers to avoid unnecessary dependencies to other modules * eg. if module `A` imports `B`, and `B` imports `C`, then a change to a function in `C` should not cause `A` to recompile * `3_export` generates the headers that are imported * Type shells are used in the functions, variables, type definitions * The actual type comes with an implicit zero-cost conversion between it and its type shell * Also note an expansion of the constant `max_edges_per_node` in `graph/node__export.h` * `4_impl` generates the implementations * Insert conversions whenever fields of a type are accessed (read or write, though constructors are tricky) * Note that type-punning (eg. `(*(struct S*)&a)`) may not be safe due to [strict aliasing](https://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html) and size/alignment rules not being enforced by the spec * I also don't think I caught every cast (it's enough to compile) * We also move constants and type definitions not already in `__export.h` to the top, as well as adding function declarations * This is what allows top-level declarations to be out-of-order * `5_tweaking` alters the library imports to make it compile * ie. `#include ` instead of `#include ` Advanced considerations: * No private fields, though opaque types can be made * Hoisting occuring at top-level, what about within functions? * How does update logic work? * No module interface logic here (ie. strong module split) * How much of compilation is centrally controlled (ie. by a file that keeps track of the entire project's dependencies)? * `__tshell.h` changes every time a new type is added, but a central file can check if importing files actually need recompilation * Extra details: How to perform a unity build? Static/dynamic library generation and management? * Aside: Uniform function call syntax