Newer
Older
Tardis / lang / module.c
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 John Watts and the LuminaSensum contributors

#define MODULE_INTERNAL_API
#include "module.h"
#include "error.h"
#include "object.h"
#include "string.h"
#include "vm.h"
#include <stdio.h>

// Useful macro for getting array size
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

// Use X macros to declare external module variables
#define X(id) extern ModuleInfo module_info_##id;
#include <module_ids.x>
#undef X

// Use X macros to build a list of modules
#define X(id) &module_info_##id,
static ModuleInfo *modules_list[] = {
#include <module_ids.x>
};
#undef X

static const ModuleInfo *info_by_name(const char *name) {
	for (unsigned int i = 0; i < ARRAY_SIZE(modules_list); ++i) {
		ModuleInfo *info = modules_list[i];
		if (strcmp(info->name, name) == 0)
			return info;
	}
	return NULL;
}

struct module_stack {
	ModuleInfo *elems[64];
	int top;
};

// Checks if an info is in a stack
static int in_stack(const ModuleInfo *info, struct module_stack *stack) {
	for (int i = 0; i <= stack->top; ++i) {
		ModuleInfo *elem = stack->elems[i];
		if (elem == info)
			return 1;
	}
	return 0;
}

// Traverses a module use dependency tree, checking for circular dependencies
// This uses a depth-first search with a stack for traversing
// The resulting visited list is the reverse load order
static void traverse_module_uses(
	const ModuleInfo *start, struct module_stack *visited) {
	static struct module_stack traversing = {0};
	if (sizeof(modules_list) > sizeof(traversing.elems))
		abort_print("Modules too big to traverse");
	traversing.top = 0;
	traversing.elems[traversing.top] = start;
	do {
		ModuleInfo *cur = traversing.elems[traversing.top];
		const char **use = cur->uses;
		while (*use) {
			const ModuleInfo *info = info_by_name(*use);
			if (info == NULL)
				abort_print("Couldn't find module");
			if (in_stack(info, &traversing))
				abort_print("Cyclic dependency found");
			if (!in_stack(info, visited)) {
				traversing.elems[++traversing.top] = info;
				break;
			}
			use++;
		}
		if (*use == NULL) {
			traversing.elems[traversing.top--] = NULL;
			visited->elems[++visited->top] = cur;
		}
	} while (traversing.top != -1);
}

Object module_find(VmState state, const char *name) {
	const ModuleInfo *info = info_by_name(name);
	vm_abort_if(state, !info, "Unable to find module!");
	static struct module_stack visited = {0};
	if (sizeof(modules_list) > sizeof(visited.elems))
		abort_print("Modules too big to track");
	traverse_module_uses(info, &visited); // TODO: Does nothing
	traverse_module_uses(info, &visited); // TODO: Does nothing
	return info->create(state);
}