/* * Parsing of per-module files. * * Copyright © 2025-2026 Samuel Lidén Borell * * SPDX-License-Identifier: EUPL-1.2+ OR LGPL-2.1-or-later */ #include "compiler.h" #include "token.h" #include #include unsigned num_sources = 0; char *(sources[MAX_SOURCES]) = { 0 }; static struct TreeNode *dependencies = NULL; struct DependencyInfo *dependencies_list = NULL; void parse_source_index(FILE *f) { char line[SOURCELINE_MAX]; size_t len; while (read_source_line(f, line, &len, STRIP_COMMENTS)) { if (num_sources == MAX_SOURCES) { FAIL("Too many sources for bootstrap compiler."); } check_filename(line, "slul"); if (len == 14 && !memcmp(line, "interface.slul", 14)) { is_library = true; } else if (len == 15 && !memcmp(line, "interfaces.slul", 15)) { error("Interface file should be called interface.slul " "(remove the 's')"); } else if ((len>=15 && !memcmp(line+len-15, "/interface.slul", 15)) || (len>=16 && !memcmp(line+len-16, "/interfaces.slul", 16))) { error("interface.slul is only supported in the root directory"); } else { sources[num_sources++] = dupmemz(line, len); } } } static const char *next_word(const char *last_start, size_t *prev_len_out) { const char *s = last_start; bool spaces = false; assert(*s && *s != ' ' && *s != '\t'); while (*s && *s != ' ' && *s != '\t') s++; *prev_len_out = (unsigned)(s - last_start); /* Skip whitespace after */ while (*s == ' ' || *s == '\t') { s++; spaces = true; } if (!*s) { if (spaces) { warning("Trailing whitespace"); } return NULL; } return s; } static void check_modname(const char *name, size_t len) { if (len > 50) { error_len("Module name of dependency too long", name, len); } /* TODO */ /* XXX require that the first letter is lowercase? or all letters? */ } static void check_version(const char *version, size_t len) { if (len > 50) { error_len("Version of dependency too long", version, len); } /* TODO */ } static void add_dependency(const char *apihash, const char *modname, size_t modname_len, const char *apiversion, size_t apiversion_len) { struct DependencyInfo *dep; assert(modname != NULL); check_modname(modname, modname_len); if (apiversion) { assert(apihash != NULL); check_version(apiversion, apiversion_len); } dep = (struct DependencyInfo *)tree_insert_str(&dependencies, modname, modname_len, NULL, sizeof(struct DependencyInfo)); NO_NULL(dep); dep->next = dependencies_list; dependencies_list = dep; if (dep->node.is_new) { dep->use_latest_api = false; dep->apiversions = NULL; dep->apiversions_list = NULL; } else if (dep->use_latest_api && !apihash) { error("Duplicate module dependency"); } else if ((dep->apiversions && !apihash) || (!dep->apiversions && apihash)) { /* This is only a warning, in order to allow adding a unversioned dependency during development. */ warning("Mixing dependencies of the same module " "with and without API hashes."); } if (!apihash) { dep->use_latest_api = true; } else { unsigned char binhash[32]; /* copied by tree_insert/create_node */ struct DepApiVersion *ver; if (!unbase64url(apihash, 43, binhash, 32)) { error("Invalid encoding of API hash (must be Base64url)"); } ver = (struct DepApiVersion *)tree_insert_str(&dep->apiversions, (const char*)binhash, 32, NULL, sizeof(struct DepApiVersion)); NO_NULL(ver); if (!ver->node.is_new) { error("Duplicate API version"); } ver->apiversion = apiversion ? dupmemz(apiversion, apiversion_len) : NULL; ver->apiversion_len = apiversion_len; ver->next = dep->apiversions_list; dep->apiversions_list = ver; } } static void parse_dependency_line(const char *line) { const char *word1, *word2; const char *apihash, *modname, *apiversion; size_t len1; size_t modname_len, apiversion_len; if (!line[0]) { return; /* Empty line */ } else if (line[0] == ' ' || line[0] == '\t') { error("Leading whitespace in dependencies file"); } word1 = line; word2 = next_word(word1, &len1); assert(len1 > 0); if (word2) { const char *junk; /* Full line with [] */ apihash = word1; if (len1 != 43) { error_len("Invalid length of API hash of dependency, " "should be 43 characters (256 bits)", apihash, len1); } modname = word2; apiversion = next_word(word2, &modname_len); if (apiversion) { junk = next_word(apiversion, &apiversion_len); if (junk != NULL) { error_str("Unexpected stuff at end of dependency line", junk); } } else { apiversion_len = 0; } } else { /* Short line with just a module name. This is for prototyping and internal submodules only. */ modname = word1; modname_len = len1; apihash = NULL; apiversion = NULL; apiversion_len = 0; } add_dependency(apihash, modname, modname_len, apiversion, apiversion_len); } void parse_dependencies_list(FILE *f) { char line[SOURCELINE_MAX]; size_t len; while (read_source_line(f, line, &len, STRIP_COMMENTS)) { parse_dependency_line(line); } }