Implement state file output for RGBASM (#1435)

This commit is contained in:
Sylvie
2024-08-05 12:41:40 -04:00
committed by GitHub
parent c5e6a815fa
commit f304e1dd7f
14 changed files with 346 additions and 31 deletions

View File

@@ -2,6 +2,7 @@
#include "asm/charmap.hpp"
#include <deque>
#include <stack>
#include <stdio.h>
#include <stdlib.h>
@@ -17,9 +18,9 @@
// Essentially a tree, where each nodes stores a single character's worth of info:
// whether there exists a mapping that ends at the current character,
struct CharmapNode {
std::vector<int32_t> value; // The mapped value, if there exists a mapping that ends here
// This MUST be indexes and not pointers, because pointers get invalidated by reallocation!
size_t next[256]; // Indexes of where to go next, 0 = nowhere
std::vector<int32_t> value; // The mapped value, if there exists a mapping that ends here
// These MUST be indexes and not pointers, because pointers get invalidated by reallocation!
size_t next[256]; // Indexes of where to go next, 0 = nowhere
bool isTerminal() const { return !value.empty(); }
};
@@ -27,49 +28,64 @@ struct CharmapNode {
struct Charmap {
std::string name;
std::vector<CharmapNode> nodes; // first node is reserved for the root node
// FIXME: strictly speaking, this is redundant, we could walk the trie to get mappings instead
std::unordered_map<size_t, std::string> mappings; // keys are indexes of terminal nodes
};
static std::unordered_map<std::string, Charmap> charmaps;
static std::deque<Charmap> charmapList;
static std::unordered_map<std::string, size_t> charmapMap; // Indexes into `charmapList`
static Charmap *currentCharmap;
std::stack<Charmap *> charmapStack;
bool charmap_ForEach(
void (*mapFunc)(std::string const &),
void (*charFunc)(std::string const &, std::vector<int32_t>)
) {
for (Charmap &charmap : charmapList) {
mapFunc(charmap.name);
for (size_t i = 0; i < charmap.nodes.size(); ++i) {
if (CharmapNode const &node = charmap.nodes[i]; node.isTerminal())
charFunc(charmap.mappings[i], node.value);
}
}
return !charmapList.empty();
}
void charmap_New(std::string const &name, std::string const *baseName) {
Charmap *base = nullptr;
size_t baseIdx = (size_t)-1;
if (baseName != nullptr) {
auto search = charmaps.find(*baseName);
if (search == charmaps.end())
if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
else
base = &search->second;
baseIdx = search->second;
}
if (charmaps.find(name) != charmaps.end()) {
if (charmapMap.find(name) != charmapMap.end()) {
error("Charmap '%s' already exists\n", name.c_str());
return;
}
// Init the new charmap's fields
Charmap &charmap = charmaps[name];
charmapMap[name] = charmapList.size();
Charmap &charmap = charmapList.emplace_back();
if (base)
charmap.nodes = base->nodes; // Copies `base->nodes`
if (baseIdx != (size_t)-1)
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
else
charmap.nodes.emplace_back(); // Zero-init the root node
charmap.name = name;
currentCharmap = &charmap;
}
void charmap_Set(std::string const &name) {
auto search = charmaps.find(name);
if (search == charmaps.end())
if (auto search = charmapMap.find(name); search == charmapMap.end())
error("Charmap '%s' doesn't exist\n", name.c_str());
else
currentCharmap = &search->second;
currentCharmap = &charmapList[search->second];
}
void charmap_Push() {
@@ -98,6 +114,8 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
// Switch to and zero-init the new node
nextIdxRef = charmap.nodes.size();
nextIdx = nextIdxRef;
// Save the mapping of this node
charmap.mappings[charmap.nodes.size()] = mapping;
// This may reallocate `charmap.nodes` and invalidate `nextIdxRef`,
// which is why we keep the actual value in `nextIdx`
charmap.nodes.emplace_back();

View File

@@ -2,6 +2,7 @@
#include "asm/main.hpp"
#include <algorithm>
#include <limits.h>
#include <memory>
#include <stdlib.h>
@@ -47,7 +48,7 @@ static std::string make_escape(std::string &str) {
}
// Short options
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:VvW:wX:";
static char const *optstring = "b:D:Eg:I:M:o:P:p:Q:r:s:VvW:wX:";
// Variables for the long-only options
static int depType; // Variants of `-M`
@@ -77,6 +78,7 @@ static option const longopts[] = {
{"pad-value", required_argument, nullptr, 'p'},
{"q-precision", required_argument, nullptr, 'Q'},
{"recursion-depth", required_argument, nullptr, 'r'},
{"state", required_argument, nullptr, 's'},
{"version", no_argument, nullptr, 'V'},
{"verbose", no_argument, nullptr, 'v'},
{"warning", required_argument, nullptr, 'W'},
@@ -89,14 +91,16 @@ static void printUsage() {
"Usage: rgbasm [-EVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
" [-r depth] [-W warning] [-X max_errors] <file>\n"
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
" <file>\n"
"Useful options:\n"
" -E, --export-all export all labels\n"
" -M, --dependfile <path> set the output dependency file\n"
" -o, --output <path> set the output object file\n"
" -p, --pad-value <value> set the value to use for `ds'\n"
" -V, --version print RGBASM version and exit\n"
" -W, --warning <warning> enable or disable warnings\n"
" -E, --export-all export all labels\n"
" -M, --dependfile <path> set the output dependency file\n"
" -o, --output <path> set the output object file\n"
" -p, --pad-value <value> set the value to use for `ds'\n"
" -s, --state <features>:<path> set an output state file\n"
" -V, --version print RGBASM version and exit\n"
" -W, --warning <warning> enable or disable warnings\n"
"\n"
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n",
stderr
@@ -126,6 +130,7 @@ int main(int argc, char *argv[]) {
sym_SetExportAll(false);
uint32_t maxDepth = DEFAULT_MAX_DEPTH;
char const *dependFileName = nullptr;
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs;
std::string newTarget;
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal.
if (isatty(STDERR_FILENO))
@@ -227,6 +232,58 @@ int main(int argc, char *argv[]) {
errx("Invalid argument for option 'r'");
break;
case 's': {
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
char *name = strchr(musl_optarg, ':');
if (!name)
errx("Invalid argument for option 's'");
*name++ = '\0';
std::vector<StateFeature> features;
for (char *feature = musl_optarg; feature;) {
// Split "<feature>,<rest>" so `feature` is "<feature>" and `next` is "<rest>"
char *next = strchr(feature, ',');
if (next)
*next++ = '\0';
// Trim whitespace from the beginning of `feature`...
feature += strspn(feature, " \t");
// ...and from the end
if (char *end = strpbrk(feature, " \t"); end)
*end = '\0';
// A feature must be specified
if (*feature == '\0')
errx("Empty feature for option 's'");
// Parse the `feature` and update the `features` list
if (!strcasecmp(feature, "all")) {
if (!features.empty())
warnx("Redundant feature before \"%s\" for option 's'", feature);
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
} else {
StateFeature value = !strcasecmp(feature, "equ") ? STATE_EQU
: !strcasecmp(feature, "var") ? STATE_VAR
: !strcasecmp(feature, "equs") ? STATE_EQUS
: !strcasecmp(feature, "char") ? STATE_CHAR
: !strcasecmp(feature, "macro") ? STATE_MACRO
: NB_STATE_FEATURES;
if (value == NB_STATE_FEATURES) {
errx("Invalid feature for option 's': \"%s\"", feature);
} else if (std::find(RANGE(features), value) != features.end()) {
warnx("Ignoring duplicate feature for option 's': \"%s\"", feature);
} else {
features.push_back(value);
}
}
feature = next;
}
if (stateFileSpecs.find(name) != stateFileSpecs.end())
warnx("Overriding state filename %s", name);
if (verbose)
printf("State filename %s\n", name);
stateFileSpecs.emplace(name, std::move(features));
break;
}
case 'V':
printf("rgbasm %s\n", get_package_version_string());
exit(0);
@@ -334,5 +391,8 @@ int main(int argc, char *argv[]) {
out_WriteObject();
for (auto [name, features] : stateFileSpecs)
out_WriteState(name, features);
return 0;
}

View File

@@ -2,17 +2,18 @@
#include "asm/output.hpp"
#include <algorithm>
#include <deque>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <vector>
#include "error.hpp"
#include "helpers.hpp" // assume, Defer
#include "asm/charmap.hpp"
#include "asm/fstack.hpp"
#include "asm/lexer.hpp"
#include "asm/main.hpp"
@@ -354,3 +355,171 @@ void out_SetFileName(std::string const &name) {
if (verbose)
printf("Output filename %s\n", objectFileName.c_str());
}
static void dumpString(std::string const &escape, FILE *file) {
for (char c : escape) {
// Escape characters that need escaping
switch (c) {
case '\\':
case '"':
case '{':
putc('\\', file);
[[fallthrough]];
default:
putc(c, file);
break;
case '\n':
fputs("\\n", file);
break;
case '\r':
fputs("\\r", file);
break;
case '\t':
fputs("\\t", file);
break;
case '\0':
fputs("\\0", file);
break;
}
}
}
static bool dumpEquConstants(FILE *file) {
static std::vector<Symbol *> equConstants; // `static` so `sym_ForEach` callback can see it
equConstants.clear();
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_EQU)
equConstants.push_back(&sym);
});
// Constants are ordered by file, then by definition order
std::sort(RANGE(equConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
return sym1->defIndex < sym2->defIndex;
});
for (Symbol const *sym : equConstants) {
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
fprintf(file, "def %s equ $%" PRIx32 "\n", sym->name.c_str(), value);
}
return !equConstants.empty();
}
static bool dumpVariables(FILE *file) {
static std::vector<Symbol *> variables; // `static` so `sym_ForEach` callback can see it
variables.clear();
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_VAR)
variables.push_back(&sym);
});
// Variables are ordered by file, then by definition order
std::sort(RANGE(variables), [](Symbol *sym1, Symbol *sym2) -> bool {
return sym1->defIndex < sym2->defIndex;
});
for (Symbol const *sym : variables) {
uint32_t value = static_cast<uint32_t>(sym->getOutputValue());
fprintf(file, "def %s = $%" PRIx32 "\n", sym->name.c_str(), value);
}
return !variables.empty();
}
static bool dumpEqusConstants(FILE *file) {
static std::vector<Symbol *> equsConstants; // `static` so `sym_ForEach` callback can see it
equsConstants.clear();
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_EQUS)
equsConstants.push_back(&sym);
});
// Constants are ordered by file, then by definition order
std::sort(RANGE(equsConstants), [](Symbol *sym1, Symbol *sym2) -> bool {
return sym1->defIndex < sym2->defIndex;
});
for (Symbol const *sym : equsConstants) {
fprintf(file, "def %s equs \"", sym->name.c_str());
dumpString(*sym->getEqus(), file);
fputs("\"\n", file);
}
return !equsConstants.empty();
}
static bool dumpCharmaps(FILE *file) {
static FILE *charmapFile; // `static` so `charmap_ForEach` callbacks can see it
charmapFile = file;
// Characters are ordered by charmap, then by definition order
return charmap_ForEach(
[](std::string const &name) { fprintf(charmapFile, "newcharmap %s\n", name.c_str()); },
[](std::string const &mapping, std::vector<int32_t> value) {
fputs("charmap \"", charmapFile);
dumpString(mapping, charmapFile);
putc('"', charmapFile);
for (int32_t v : value)
fprintf(charmapFile, ", $%" PRIx32, v);
putc('\n', charmapFile);
}
);
}
static bool dumpMacros(FILE *file) {
static std::vector<Symbol *> macros; // `static` so `sym_ForEach` callback can see it
macros.clear();
sym_ForEach([](Symbol &sym) {
if (!sym.isBuiltin && sym.type == SYM_MACRO)
macros.push_back(&sym);
});
// Macros are ordered by file, then by definition order
std::sort(RANGE(macros), [](Symbol *sym1, Symbol *sym2) -> bool {
return sym1->defIndex < sym2->defIndex;
});
for (Symbol const *sym : macros) {
auto const &body = sym->getMacro();
fprintf(file, "macro %s\n", sym->name.c_str());
fwrite(body.ptr.get(), 1, body.size, file);
fputs("endm\n", file);
}
return !macros.empty();
}
void out_WriteState(std::string name, std::vector<StateFeature> const &features) {
FILE *file;
if (name != "-") {
file = fopen(name.c_str(), "w");
} else {
name = "<stdout>";
file = fdopen(STDOUT_FILENO, "w");
}
if (!file)
err("Failed to open state file '%s'", name.c_str());
Defer closeFile{[&] { fclose(file); }};
static char const *dumpHeadings[NB_STATE_FEATURES] = {
"Numeric constants",
"Variables",
"String constants",
"Character maps",
"Macros",
};
static bool (* const dumpFuncs[NB_STATE_FEATURES])(FILE *) = {
dumpEquConstants,
dumpVariables,
dumpEqusConstants,
dumpCharmaps,
dumpMacros,
};
fputs("; File generated by rgbasm\n", file);
for (StateFeature feature : features) {
fprintf(file, "\n; %s\n", dumpHeadings[feature]);
if (!dumpFuncs[feature](file))
fprintf(file, "; No values\n");
}
}

View File

@@ -106,6 +106,8 @@ static void updateSymbolFilename(Symbol &sym) {
// Create a new symbol by name
static Symbol &createSymbol(std::string const &symName) {
static uint32_t nextDefIndex = 0;
Symbol &sym = symbols[symName];
sym.name = symName;
@@ -115,6 +117,7 @@ static Symbol &createSymbol(std::string const &symName) {
sym.src = fstk_GetFileStack();
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
sym.ID = -1;
sym.defIndex = nextDefIndex++;
return sym;
}

View File

@@ -784,7 +784,7 @@ int main(int argc, char *argv[]) {
fputs("#none, ", stderr);
}
}
fputc('\n', stderr);
putc('\n', stderr);
}
fputs("\t]\n", stderr);
}

View File

@@ -33,7 +33,7 @@ void sym_AddSymbol(Symbol &symbol) {
symbol.src->dump(symbol.lineNo);
fprintf(stderr, " and in %s from ", other->objFileName);
other->src->dump(other->lineNo);
fputc('\n', stderr);
putc('\n', stderr);
exit(1);
}