aboutsummaryrefslogtreecommitdiff
path: root/src/brussel.codegen.comp/CodegenModel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/brussel.codegen.comp/CodegenModel.cpp')
-rw-r--r--src/brussel.codegen.comp/CodegenModel.cpp732
1 files changed, 732 insertions, 0 deletions
diff --git a/src/brussel.codegen.comp/CodegenModel.cpp b/src/brussel.codegen.comp/CodegenModel.cpp
new file mode 100644
index 0000000..303ad4e
--- /dev/null
+++ b/src/brussel.codegen.comp/CodegenModel.cpp
@@ -0,0 +1,732 @@
+#include "CodegenModel.hpp"
+
+#include "CodegenUtils.hpp"
+#include "SQLiteHelper.hpp"
+
+#include <Macros.hpp>
+#include <ScopeGuard.hpp>
+#include <Utils.hpp>
+
+#include <robin_hood.h>
+#include <cassert>
+#include <cstdint>
+#include <stdexcept>
+#include <string>
+#include <variant>
+
+using namespace std::literals;
+
+// TODO only delete unused records from model instead of regenerating all records every time
+
+struct SomeDecl {
+ std::variant<DeclStruct, DeclFunction, DeclEnum> v;
+};
+
+class CodegenRuntimeModel::Private {
+ friend class CodegenArchiveModel;
+
+public:
+ // We want address stability for everything
+ robin_hood::unordered_node_map<std::string, SomeDecl, StringHash, StringEqual> decls;
+ robin_hood::unordered_node_map<std::string, DeclNamespace, StringHash, StringEqual> namespaces;
+};
+
+// A number for `PRAGMA user_vesrion`, representing the current database version. Increment when the table format changes.
+#define CURRENT_DATABASE_VERSION 1
+constexpr int64_t kGlobalNamespaceId = 1;
+
+namespace {
+void PrintErrMsgIfPresent(char*& errMsg) {
+ if (errMsg) {
+ printf("SQLite error: %s\n", errMsg);
+ sqlite3_free(errMsg);
+ }
+}
+} // namespace
+
+class CodegenArchiveModel::Private {
+ friend class CodegenRuntimeModel;
+
+public:
+ // NOTE: this must be the first field, because we want it to destruct after all other statement fields
+ SQLiteDatabase database;
+ /* Core Statements */
+ SQLiteStatement beginTransactionStmt;
+ SQLiteStatement commitTransactionStmt;
+ SQLiteStatement rollbackTransactionStmt;
+ SQLiteStatement findFileStmt;
+ SQLiteStatement storeFileStmt;
+ SQLiteStatement findNamespaceStmt;
+ SQLiteStatement getNamespaceStmt;
+ SQLiteStatement storeNamespaceStmt;
+ /* Component Statements, initalized on demand */
+ SQLiteStatement storeStructStmt;
+ SQLiteStatement storeStructBaseClassStmt;
+ SQLiteStatement storeStructPropertyStmt;
+ // TODO store method
+ SQLiteStatement storeEnumStmt;
+ SQLiteStatement storeEnumElmStmt;
+ SQLiteStatement deleteFunctionDeclByFilenameStmt;
+ SQLiteStatement deleteStructDeclByFilenameStmt;
+ SQLiteStatement deleteEnumDeclByFilenameStmt;
+ // TODO
+ // SQLiteStatement getRootClassStmt;
+
+ void InitializeDatabase() {
+ char* errMsg = nullptr;
+
+ int result = sqlite3_exec(database, "PRAGMA user_version = " STRINGIFY(CURRENT_DATABASE_VERSION), nullptr, nullptr, &errMsg);
+ PrintErrMsgIfPresent(errMsg);
+ assert(result == SQLITE_OK);
+
+ // TODO unique with overloading, and container structs
+ result = sqlite3_exec(database, R"""(
+BEGIN TRANSACTION;
+CREATE TABLE Files(
+ -- NOTE: SQLite forbids foreign keys referencing the implicit `rowid` column, we have to create an alias for it
+ Id INTEGER PRIMARY KEY,
+ FileName TEXT,
+ UNIQUE (FileName)
+);
+
+CREATE TABLE Namespaces(
+ Id INTEGER PRIMARY KEY,
+ ParentNamespaceId INTEGER REFERENCES Namespaces(Id),
+ Name TEXT,
+ UNIQUE (ParentNamespaceId, Name)
+);
+
+CREATE TABLE DeclFunctions(
+ Id INTEGER PRIMARY KEY,
+ FileId INTEGER REFERENCES Files(Id) ON DELETE CASCADE,
+ NamespaceId INTEGER REFERENCES Namespaces(Id),
+ Name TEXT
+);
+CREATE TABLE DeclFunctionParameters(
+ FunctionId INTEGER REFERENCES DeclFunctions(Id) ON DELETE CASCADE,
+ Name TEXT,
+ Type TEXT,
+ UNIQUE (FunctionId, Name)
+);
+
+CREATE TABLE DeclStructs(
+ Id INTEGER PRIMARY KEY,
+ FileId INTEGER REFERENCES Files(Id) ON DELETE CASCADE,
+ NamespaceId INTEGER REFERENCES Namespaces(Id),
+ Name TEXT,
+ IsMetadataMarked INTEGER
+);
+CREATE TABLE DeclStructBaseClassRelations(
+ StructId INTEGER REFERENCES DeclStructs(Id) ON DELETE CASCADE,
+ -- NOTE: intentionally not foreign keys, because we want relations to still exist even if the base class is deleted
+ -- we do validation after a complete regeneration pass, on reads
+ ParentStructNamespaceId INTEGER,
+ ParentStructName TEXT,
+ UNIQUE (StructId, ParentStructNamespaceId, ParentStructName)
+);
+CREATE TABLE DeclStructProperties(
+ StructId INTEGER REFERENCES DeclStructs(Id) ON DELETE CASCADE,
+ Name TEXT,
+ Type TEXT,
+ -- NOTE: getter and setter may or may not be methods; search the DeclStructMethods table if needed
+ GetterName TEXT,
+ SetterName TEXT,
+ IsPlainField INTEGER GENERATED ALWAYS AS (GetterName = '' AND SetterName = '') VIRTUAL,
+ IsMetadataMarked INTEGER
+);
+CREATE TABLE DeclStructMethods(
+ Id INTEGER PRIMARY KEY,
+ StructId INTEGER REFERENCES DeclStructs(Id) ON DELETE CASCADE,
+ Name TEXT,
+ Type TEXT,
+ IsConst INTEGER,
+ IsMetadataMarked INTEGER
+);
+CREATE TABLE DeclStructMethodParameters(
+ MethodId INTEGER REFERENCES DeclStructMethods(Id) ON DELETE CASCADE,
+ Name TEXT,
+ Type TEXT,
+ UNIQUE (MethodId, Name)
+);
+
+CREATE TABLE DeclEnums(
+ Id INTEGER PRIMARY KEY,
+FileId INTEGER REFERENCES Files(Id) ON DELETE CASCADE,
+ NamespaceId INTEGER REFERENCES Namespaces(Id),
+ Name TEXT,
+ UnderlyingType TEXT
+);
+CREATE TABLE DeclEnumElements(
+ EnumId INTEGER REFERENCES DeclEnums(Id) ON DELETE CASCADE,
+ Name TEXT,
+ Value INTEGER,
+ UNIQUE (EnumId, Name)
+);
+
+CREATE INDEX Index_DeclFunctions_FileId ON DeclFunctions(FileId);
+CREATE INDEX Index_DeclStructs_FileId ON DeclStructs(FileId);
+CREATE INDEX Index_DeclEnums_FileId ON DeclEnums(FileId);
+
+CREATE UNIQUE INDEX Index_DeclFunctions_Identity ON DeclFunctions(NamespaceId, Name);
+
+CREATE UNIQUE INDEX Index_DeclStruct_Identity ON DeclStructs(NamespaceId, Name);
+CREATE UNIQUE INDEX Index_DeclStructProperties_Identity ON DeclStructProperties(StructId, Name);
+CREATE UNIQUE INDEX Index_DeclStructMethods_Identity ON DeclStructMethods(StructId, Name);
+
+CREATE UNIQUE INDEX Index_DeclEnums_Identity ON DeclEnums(NamespaceId, Name);
+
+-- Special global namespace that has no parent, and Id should always be 1
+INSERT INTO Namespaces(Id, ParentNamespaceId, Name)
+VALUES (1, NULL, '<global namespace>');
+
+COMMIT TRANSACTION;
+)""",
+ nullptr,
+ nullptr,
+ &errMsg);
+ PrintErrMsgIfPresent(errMsg);
+ assert(result == SQLITE_OK);
+ }
+
+ void BeginTransaction() {
+ int result = sqlite3_step(beginTransactionStmt);
+ assert(result == SQLITE_DONE);
+ sqlite3_reset(beginTransactionStmt);
+ }
+
+ void CommitTransaction() {
+ int result = sqlite3_step(commitTransactionStmt);
+ assert(result == SQLITE_DONE);
+ sqlite3_reset(commitTransactionStmt);
+ }
+
+ void RollbackTransaction() {
+ int result = sqlite3_step(rollbackTransactionStmt);
+ assert(result == SQLITE_DONE);
+ sqlite3_reset(rollbackTransactionStmt);
+ }
+
+ /// \return Row ID of the namespace, or 0 if it currently doesn't exist.
+ int64_t FindNamespace(const DeclNamespace* ns) {
+ if (!ns) {
+ return kGlobalNamespaceId;
+ }
+
+ return FindNamespaceImpl(*ns);
+ }
+
+ /// \return Row ID of the namespace.
+ int64_t FindOrStoreNamespace(const DeclNamespace* ns) {
+ if (!ns) {
+ return kGlobalNamespaceId;
+ }
+
+ if (auto rowId = FindNamespaceImpl(*ns); rowId != 0) {
+ return rowId;
+ }
+
+ SQLiteRunningStatement rt(storeNamespaceStmt);
+ rt.BindArguments(FindOrStoreNamespace(ns->container), ns->name);
+
+ rt.StepAndCheck(SQLITE_ROW);
+
+ auto [nsId] = rt.ResultColumns<int64_t>();
+ return nsId;
+ }
+
+ std::string GetNamespaceFullName(int64_t nsId) const {
+ return GetNamespaceFullNameImpl(nsId, nullptr, 0);
+ }
+
+ std::string GetDeclFullName(int64_t nsId, std::string_view declName) const {
+ return GetNamespaceFullNameImpl(nsId, declName.data(), declName.size());
+ }
+
+ /// \return Row ID of the file, or 0 if it currently doesn't exist.
+ int64_t FindFile(std::string_view filename) {
+ SQLiteRunningStatement rt(findFileStmt);
+ rt.BindArguments(filename);
+
+ int result = rt.Step();
+
+ if (result == SQLITE_ROW) {
+ auto [fileId] = rt.ResultColumns<int64_t>();
+ return fileId;
+ } else {
+ return 0;
+ }
+ }
+
+ /// \return Row ID of the file
+ int64_t FindOrStoreFile(std::string_view filename) {
+ if (auto id = FindFile(filename); id != 0) {
+ return id;
+ }
+
+ SQLiteRunningStatement rt(storeFileStmt);
+ rt.BindArguments(filename);
+
+ rt.StepAndCheck(SQLITE_ROW);
+
+ auto [fileId] = rt.ResultColumns<int64_t>();
+ return fileId;
+ }
+
+ /// \return Row ID of the file, or 0 if not found.
+ int64_t FindOrStoreFile(/*nullable*/ const SourceFile* file) {
+ if (!file) {
+ return 0;
+ }
+ return FindOrStoreFile(file->filename);
+ }
+
+private:
+ // TODO maybe merge with Utils::MakeFullName?
+ std::string GetNamespaceFullNameImpl(int64_t nsId, const char* append, size_t appendLength) const {
+ std::vector<std::string> namespaceNames;
+ size_t fullnameLength = 0;
+
+ sqlite3_stmt* stmt = getNamespaceStmt;
+ int64_t currentNsId = nsId;
+ while (true) {
+ SQLiteRunningStatement rt(getNamespaceStmt);
+ rt.BindArguments(currentNsId);
+
+ rt.StepAndCheck(SQLITE_ROW);
+
+ auto [id, parentNamespaceId, name] = rt.ResultColumns<int64_t, int64_t, std::string>();
+ currentNsId = parentNamespaceId;
+ fullnameLength += name.size() + 2;
+ namespaceNames.push_back(std::move(name));
+
+ if (parentNamespaceId == kGlobalNamespaceId) {
+ break;
+ }
+ }
+ if (append) {
+ // Already has the '::' at the end
+ fullnameLength += appendLength;
+ } else {
+ fullnameLength -= 2;
+ }
+
+ std::string fullname;
+ fullname.reserve(fullnameLength);
+
+ for (auto it = namespaceNames.rbegin(); it != namespaceNames.rend(); ++it) {
+ fullname.append(*it);
+ if (append || std::next(it) != namespaceNames.rend()) {
+ fullname.append("::");
+ }
+ }
+ if (append) {
+ fullname += std::string_view(append, appendLength);
+ }
+
+ return fullname;
+ }
+
+ int64_t FindNamespaceImpl(const DeclNamespace& ns) {
+ int64_t parentNsRowId;
+ if (ns.container) {
+ parentNsRowId = FindNamespaceImpl(*ns.container);
+ if (parentNsRowId == 0) {
+ // Parent namespace doesn't exist in database, shortcircuit
+ return 0;
+ }
+ } else {
+ parentNsRowId = kGlobalNamespaceId;
+ }
+
+ return FindNamespaceImpl(ns, parentNsRowId);
+ }
+
+ int64_t FindNamespaceImpl(const DeclNamespace& ns, int64_t parentNsRowId) {
+ sqlite3_stmt* stmt = findNamespaceStmt;
+ SQLiteRunningStatement rt(findNamespaceStmt);
+ rt.BindArguments(parentNsRowId, ns.name);
+
+ int result = rt.Step();
+ if (result == SQLITE_ROW) {
+ auto [nsId] = rt.ResultColumns<int64_t>();
+ return nsId;
+ } else {
+ return 0;
+ }
+ }
+};
+
+CodegenRuntimeModel::CodegenRuntimeModel()
+ : m{ new Private() } //
+{
+}
+
+CodegenRuntimeModel::~CodegenRuntimeModel() {
+ delete m;
+}
+
+#define STORE_DECL_OF_TYPE(DeclType, fullname, decl) \
+ auto [iter, success] = m->decls.try_emplace(std::move(fullname), SomeDecl{ .v = std::move(decl) }); \
+ auto& key = iter->first; \
+ auto& val = iter->second; \
+ auto& declRef = std::get<DeclType>(val.v); \
+ declRef.fullname = &key; \
+ return &declRef
+
+DeclEnum* CodegenRuntimeModel::AddEnum(std::string fullname, DeclEnum decl) {
+#if CODEGEN_DEBUG_PRINT
+ printf("Committed enum '%s'\n", decl.name.c_str());
+ for (auto& elm : decl.elements) {
+ printf(" - element %s = %" PRId64 "\n", elm.name.c_str(), elm.value);
+ }
+#endif
+
+ STORE_DECL_OF_TYPE(DeclEnum, fullname, decl);
+}
+
+DeclStruct* CodegenRuntimeModel::AddStruct(std::string fullname, DeclStruct decl) {
+#if CODEGEN_DEBUG_PRINT
+ printf("Committed struct '%s'\n", decl.name.c_str());
+ printf(" Base classes:\n");
+ for (auto& base : decl.baseClasses) {
+ printf(" - %.*s\n", PRINTF_STRING_VIEW(base->name));
+ }
+#endif
+
+ STORE_DECL_OF_TYPE(DeclStruct, fullname, decl);
+}
+
+#define FIND_DECL_OF_TYPE(DeclType) \
+ auto iter = m->decls.find(name); \
+ if (iter != m->decls.end()) { \
+ auto& some = iter->second.v; \
+ if (auto decl = std::get_if<DeclType>(&some)) { \
+ return decl; \
+ } \
+ } \
+ return nullptr
+
+const DeclEnum* CodegenRuntimeModel::FindEnum(std::string_view name) const {
+ FIND_DECL_OF_TYPE(DeclEnum);
+}
+
+const DeclStruct* CodegenRuntimeModel::FindStruct(std::string_view name) const {
+ FIND_DECL_OF_TYPE(DeclStruct);
+}
+
+DeclNamespace* CodegenRuntimeModel::AddNamespace(DeclNamespace ns) {
+ auto path = Utils::MakeFullName(""sv, &ns);
+ auto [iter, success] = m->namespaces.try_emplace(std::move(path), std::move(ns));
+ auto& nsRef = iter->second;
+ if (success) {
+ nsRef.fullname = &iter->first;
+ }
+ return &nsRef;
+}
+
+const DeclNamespace* CodegenRuntimeModel::FindNamespace(std::string_view fullname) const {
+ auto iter = m->namespaces.find(fullname);
+ if (iter != m->namespaces.end()) {
+ return &iter->second;
+ } else {
+ return nullptr;
+ }
+}
+
+DeclNamespace* CodegenRuntimeModel::FindNamespace(std::string_view name) {
+ return const_cast<DeclNamespace*>(const_cast<const CodegenRuntimeModel*>(this)->FindNamespace(name));
+}
+
+CodegenArchiveModel::CodegenArchiveModel(std::string_view dbPath)
+ : m{ new Private() } //
+{
+ std::string zstrPath(dbPath);
+ int reuslt = sqlite3_open(zstrPath.c_str(), &m->database);
+ if (reuslt != SQLITE_OK) {
+ std::string msg;
+ msg += "Failed to open SQLite3 database, error message:\n";
+ msg += sqlite3_errmsg(m->database);
+ throw std::runtime_error(msg);
+ }
+
+ // NOTE: These pragmas are not persistent, so we need to set them every time
+ // As of SQLite3 3.38.5, it defaults to foreign_keys = OFF, so we need this to be on for ON DELETE CASCADE and etc. to work
+ sqlite3_exec(m->database, "PRAGMA foreign_keys = ON", nullptr, nullptr, nullptr);
+ // This database is used for a buildsystem and can be regenerated at any time. We don't care for the slightest about data integrity, we just want fast updates
+ sqlite3_exec(m->database, "PRAGMA synchronous = OFF", nullptr, nullptr, nullptr);
+ sqlite3_exec(m->database, "PRAGMA journal_mode = MEMORY", nullptr, nullptr, nullptr);
+
+ {
+ SQLiteStatement readVersionStmt;
+ readVersionStmt.InitializeLazily(m->database, "PRAGMA user_version"sv);
+
+ int result = sqlite3_step(readVersionStmt);
+ assert(result == SQLITE_ROW);
+ int currentDatabaseVersion = sqlite3_column_int(readVersionStmt, 0);
+
+ result = sqlite3_step(readVersionStmt);
+ assert(result == SQLITE_DONE);
+
+ if (currentDatabaseVersion == 0) {
+ // Newly created database, initialize it
+ m->InitializeDatabase();
+ } else if (currentDatabaseVersion == CURRENT_DATABASE_VERSION) {
+ // Same version, no need to do anything
+ } else {
+ INPLACE_FMT(msg, "Incompatbile database versions %d (in file) vs %d (expected).", currentDatabaseVersion, CURRENT_DATABASE_VERSION);
+ throw std::runtime_error(msg);
+ }
+ }
+
+ // Initialize core statements
+ m->beginTransactionStmt.Initialize(m->database, "BEGIN TRANSACTION");
+ m->commitTransactionStmt.Initialize(m->database, "COMMIT TRANSACTION");
+ m->rollbackTransactionStmt.Initialize(m->database, "ROLLBACK TRANSACTION");
+ m->findFileStmt.Initialize(m->database, "SELECT Id FROM Files WHERE FileName = ?1");
+ m->storeFileStmt.Initialize(m->database, "INSERT INTO Files(FileName) VALUES (?1) RETURNING Id");
+ m->findNamespaceStmt.Initialize(m->database, "SELECT Id FROM Namespaces WHERE ParentNamespaceId = ?1 AND Name = ?2");
+ m->getNamespaceStmt.Initialize(m->database, "SELECT * FROM Namespaces WHERE Id = ?1"sv);
+ m->storeNamespaceStmt.Initialize(m->database, "INSERT INTO Namespaces(ParentNamespaceId, Name) VALUES (?1, ?2) RETURNING Id");
+}
+
+CodegenArchiveModel::~CodegenArchiveModel() {
+ delete m;
+}
+
+void CodegenArchiveModel::DeleteDeclsRelatedToFile(std::string_view filename) {
+ // -Argument- -Description-
+ // ?1 The filename to delete
+ m->deleteFunctionDeclByFilenameStmt.InitializeLazily(m->database, "DELETE FROM DeclFunctions WHERE FileId = (SELECT Id FROM Files WHERE FileName = ?1)"sv);
+ m->deleteStructDeclByFilenameStmt.InitializeLazily(m->database, "DELETE FROM DeclStructs WHERE FileId = (SELECT Id FROM Files WHERE FileName = ?1);"sv);
+ m->deleteEnumDeclByFilenameStmt.InitializeLazily(m->database, "DELETE FROM DeclEnums WHERE FileId = (SELECT Id FROM Files WHERE FileName = ?1);"sv);
+
+ m->BeginTransaction();
+ auto stmtList = {
+ m->deleteFunctionDeclByFilenameStmt.stmt,
+ m->deleteStructDeclByFilenameStmt.stmt,
+ m->deleteEnumDeclByFilenameStmt.stmt,
+ };
+ for (auto& stmt : stmtList) {
+ SQLiteRunningStatement rt(stmt);
+ rt.BindArguments(filename);
+ rt.StepUntilDone();
+ }
+ m->CommitTransaction();
+}
+
+void CodegenArchiveModel::Store(const CodegenRuntimeModel& cgModel) {
+ auto& cgm = cgModel.GetPimpl();
+
+ struct Visiter {
+ CodegenArchiveModel* self;
+
+ void operator()(const DeclStruct& decl) const {
+ self->StoreStruct(decl);
+ }
+ void operator()(const DeclFunction& decl) const {
+ self->StoreFunction(decl);
+ }
+ void operator()(const DeclEnum& decl) const {
+ self->StoreEnum(decl);
+ }
+ } visiter;
+ visiter.self = this;
+
+ m->BeginTransaction();
+
+ for (auto&& [DISCARD, ns] : cgm.namespaces) {
+ // This will insert the namespace if it doesn't exist, or no-op (fetches data) if it already exists
+ m->FindOrStoreNamespace(&ns);
+ }
+ for (auto&& [DISCARD, value] : cgm.decls) {
+ std::visit(visiter, value.v);
+ }
+
+ m->CommitTransaction();
+}
+
+void CodegenArchiveModel::LoadInto(CodegenRuntimeModel& model) const {
+ // TODO
+}
+
+CodegenRuntimeModel CodegenArchiveModel::Load() const {
+ CodegenRuntimeModel cgModel;
+
+ // TODO files
+ // TODO namespaces
+
+ robin_hood::unordered_map<int64_t, DeclStruct*> structsById;
+ robin_hood::unordered_map<int64_t, DeclMemberVariable*> propertiesById;
+ robin_hood::unordered_map<int64_t, DeclMemberFunction*> methodsById;
+
+ { // Load structs
+ SQLiteStatement stmt(m->database, "SELECT * FROM DeclStructs"sv);
+ SQLiteRunningStatement rt(stmt);
+ while (true) {
+ int result = rt.StepAndCheckError();
+ if (result == SQLITE_DONE) break;
+ assert(result == SQLITE_ROW);
+
+ auto [id, fileId, nsId, name] = rt.ResultColumns<int64_t, int64_t, int64_t, std::string_view>();
+
+ auto decl = cgModel.AddStruct(m->GetDeclFullName(nsId, name), DeclStruct{});
+ structsById.try_emplace(id, decl);
+ }
+ }
+ { // Load struct's base classes
+ SQLiteStatement stmt(m->database, "SELECT * FROM DeclStructBaseClassRelations");
+ SQLiteRunningStatement rt(stmt);
+ while (true) {
+ int result = rt.StepAndCheckError();
+ if (result == SQLITE_DONE) break;
+ assert(result == SQLITE_ROW);
+
+ auto [structId, parentStructNsId, parentStructName] = rt.ResultColumns<int64_t, int64_t, std::string_view>();
+
+ auto declThis = structsById.at(structId);
+ auto declParent = cgModel.FindStruct(parentStructName); // TODO namespace
+ declThis->baseClasses.push_back(declParent);
+ }
+ }
+ { // Load struct properties
+ SQLiteStatement stmt(m->database, "SELECT * FROM DeclStructProperties"sv);
+ SQLiteRunningStatement rt(stmt);
+ while (true) {
+ int result = rt.StepAndCheckError();
+ if (result == SQLITE_DONE) break;
+ assert(result == SQLITE_ROW);
+
+ // TODO
+ }
+ }
+ { // Load struct methods
+ SQLiteStatement stmt(m->database, "SELECT * FROM DeclStructMethods"sv);
+ SQLiteRunningStatement rt(stmt);
+ while (true) {
+ int result = rt.StepAndCheckError();
+ if (result == SQLITE_DONE) break;
+ assert(result == SQLITE_ROW);
+
+ // TODO
+ }
+ }
+ { // Load method params
+ SQLiteStatement stmt(m->database, "SELECT * FROM DeclStructMethodParameters"sv);
+ SQLiteRunningStatement rt(stmt);
+ while (true) {
+ int result = rt.StepAndCheckError();
+ if (result == SQLITE_DONE) break;
+ assert(result == SQLITE_ROW);
+
+ // TODO
+ }
+ }
+
+ return cgModel;
+}
+
+void CodegenArchiveModel::StoreStruct(const DeclStruct& decl) {
+ // -Argument- -Description-
+ // ?1 Namespace ID
+ // ?2 Struct name
+ // ?3 File ID containing the struct
+ // ?4 Is this struct marked for metadata generation?
+ m->storeStructStmt.InitializeLazily(m->database, R"""(
+INSERT INTO DeclStructs(NamespaceId, Name, FileId, IsMetadataMarked)
+VALUES (?1, ?2, ?3, ?4)
+ON CONFLICT DO UPDATE SET
+ FileId = ?3,
+ IsMetadataMarked = ?4
+RETURNING Id
+)"""sv);
+
+ // -Argument- -Description-
+ // ?1 Struct ID
+ // ?2 Parent struct's namespace ID
+ // ?3 Parent struct's name
+ m->storeStructBaseClassStmt.InitializeLazily(m->database, R"""(
+INSERT INTO DeclStructBaseClassRelations(StructId, ParentStructNamespaceId, ParentStructName)
+VALUES (?1, ?2, ?3)
+)"""sv);
+
+ // -Argument- -Description-
+ // ?1 Struct ID
+ // ?2 Property name
+ // ?3 Property type
+ // ?4 Getter name (optional)
+ // ?5 Setter name (optional)
+ // ?6 Is this property marked for metadata generation?
+ m->storeStructPropertyStmt.InitializeLazily(m->database, R"""(
+INSERT INTO DeclStructProperties(StructId, Name, Type, GetterName, SetterName, IsMetadataMarked)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
+)"""sv);
+
+ SQLiteRunningStatement rt(m->storeStructStmt);
+ rt.BindArguments(m->FindOrStoreNamespace(decl.container), decl.name, m->FindOrStoreFile(decl.sourceFile), decl.generating);
+ rt.StepAndCheck(SQLITE_ROW);
+ auto [structId] = rt.ResultColumns<int64_t>();
+
+ for (auto& baseClass : decl.baseClasses) {
+ SQLiteRunningStatement rt(m->storeStructBaseClassStmt);
+ rt.BindArguments(structId, m->FindOrStoreNamespace(baseClass->container), baseClass->name);
+ rt.StepUntilDone();
+ }
+
+ for (auto& property : decl.memberVariables) {
+ SQLiteRunningStatement rt(m->storeStructPropertyStmt);
+ rt.BindArguments(
+ structId,
+ property.name,
+ property.type,
+ property.getterName,
+ property.setterName,
+ // Since DeclMemberVariable entries currently only exist if it's marked BRUSSEL_PROPERTY
+ true);
+ rt.StepUntilDone();
+ }
+
+ for (auto& method : decl.memberFunctions) {
+ // TODO
+ }
+}
+
+void CodegenArchiveModel::StoreFunction(const DeclFunction& decl) {
+ // TODO
+}
+
+void CodegenArchiveModel::StoreEnum(const DeclEnum& decl) {
+ // -Argument- -Description-
+ // ?1 Namespace ID
+ // ?2 Enum name
+ // ?3 Enum underlying type
+ // ?4 File ID containing the enum
+ m->storeEnumStmt.InitializeLazily(m->database, R"""(
+INSERT INTO DeclEnums(NamespaceId, Name, UnderlyingType, FileId)
+VALUES (?1, ?2, ?3, ?4)
+ON CONFLICT DO UPDATE SET
+ UnderlyingType = ?3,
+ FileId = ?4
+RETURNING Id
+)"""sv);
+
+ // -Argument- -Description-
+ // ?1 Container enum's id
+ // ?2 Enum element name
+ // ?3 Enum element value
+ m->storeEnumElmStmt.InitializeLazily(m->database, R"""(
+INSERT INTO DeclEnumElements(EnumId, Name, Value)
+VALUES (?1, ?2, ?3)
+ON CONFLICT DO UPDATE SET Value=?3
+)"""sv);
+
+ SQLiteRunningStatement rt(m->storeEnumStmt);
+ rt.BindArguments(m->FindOrStoreNamespace(decl.container), decl.name, decl.GetUnderlyingTypeName(), m->FindOrStoreFile(decl.sourceFile));
+ rt.StepAndCheck(SQLITE_ROW);
+ auto [enumId] = rt.ResultColumns<int64_t>();
+
+ for (auto& elm : decl.elements) {
+ SQLiteRunningStatement rt(m->storeEnumElmStmt);
+ rt.BindArguments(enumId, elm.name, elm.value);
+ rt.StepUntilDone();
+ }
+}