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

@@ -38,6 +38,7 @@ _rgbasm_completions() {
[p]="pad-value:unk"
[Q]="q-precision:unk"
[r]="recursion-depth:unk"
[s]="state:unk"
[W]="warning:warning"
[X]="max-errors:unk"
)

View File

@@ -17,6 +17,7 @@ _rgbgfx_completions() {
[a]="attr-map:glob-*.attrmap"
[A]="auto-attr-map:normal"
[b]="base-tiles:unk"
[c]="colors:unk"
[d]="depth:unk"
[L]="slice:unk"
[N]="nb-tiles:unk"

View File

@@ -44,7 +44,7 @@ local args=(
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
'(-I --include)'{-I,--include}'+[Add an include directory]:include path:_files -/'
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'"
'(-M --dependfile)'{-M,--dependfile}"+[Write deps in make format]:output file:_files -g '*.{d,mk}'"
-MG'[Assume missing files should be generated]'
-MP'[Add phony targets to all deps]'
'*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
@@ -54,6 +54,7 @@ local args=(
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
'(-Q --q-precision)'{-Q,--q-precision}'+[Set fixed-point precision]:precision:'
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
'(-s --state)'{-s,--state}"+[Write features of final state]:state file:_files -g '*.dump.asm'"
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
'(-X --max-errors)'{-X,--max-errors}'+[Set maximum errors before aborting]:maximum errors:'

View File

@@ -26,6 +26,7 @@ local args=(
'(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
'(-L --slice)'{-L,--slice}'+[Only process a portion of the image]:input slice:'
'(-N --nb-tiles)'{-N,--nb-tiles}'+[Limit number of tiles]:tile count:'

View File

@@ -10,6 +10,10 @@
#define DEFAULT_CHARMAP_NAME "main"
bool charmap_ForEach(
void (*mapFunc)(std::string const &),
void (*charFunc)(std::string const &, std::vector<int32_t>)
);
void charmap_New(std::string const &name, std::string const *baseName);
void charmap_Set(std::string const &name);
void charmap_Push();

View File

@@ -6,12 +6,23 @@
#include <memory>
#include <stdint.h>
#include <string>
#include <unordered_map>
#include <vector>
#include "linkdefs.hpp"
struct Expression;
struct FileStackNode;
enum StateFeature {
STATE_EQU,
STATE_VAR,
STATE_EQUS,
STATE_CHAR,
STATE_MACRO,
NB_STATE_FEATURES
};
extern std::string objectFileName;
void out_RegisterNode(std::shared_ptr<FileStackNode> node);
@@ -21,5 +32,6 @@ void out_CreateAssert(
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
);
void out_WriteObject();
void out_WriteState(std::string name, std::vector<StateFeature> const &features);
#endif // RGBDS_ASM_OUTPUT_HPP

View File

@@ -45,6 +45,7 @@ struct Symbol {
data;
uint32_t ID; // ID of the symbol in the object file (-1 if none)
uint32_t defIndex; // Ordering of the symbol in the state file
bool isDefined() const { return type != SYM_REF; }
bool isNumeric() const { return type == SYM_LABEL || type == SYM_EQU || type == SYM_VAR; }

View File

@@ -23,6 +23,7 @@
.Op Fl p Ar pad_value
.Op Fl Q Ar fix_precision
.Op Fl r Ar recursion_depth
.Op Fl s Ar features Ns : Ns Ar state_file
.Op Fl W Ar warning
.Op Fl X Ar max_errors
.Ar asmfile
@@ -143,8 +144,45 @@ The argument may start with a
to match the Q notation, for example,
.Ql Fl Q Ar .16 .
.It Fl r Ar recursion_depth , Fl \-recursion-depth Ar recursion_depth
Specifies the recursion depth past which RGBASM will assume being in an infinite loop.
Specifies the recursion depth past which
.Nm
will assume being in an infinite loop.
The default is 64.
.It Fl s Ar features Ns : Ns Ar state_file , Fl \-state Ar features Ns : Ns Ar state_file
Write the specified
.Ar features
to
.Ar state_file ,
based on the final state of
.Nm
at the end of its input.
The expected
.Ar features
are a comma-separated subset of the following:
.Bl -tag -width Ds
.It Cm equ
Write all numeric constants as
.Ql Ic def Ar name Ic equ Ar value .
.It Cm var
Write all variables as
.Ql Ic def Ar name Ic = Ar value .
.It Cm equs
Write all string constants as
.Ql Ic def Ar name Ic equs Qq Ar value .
.It Cm char
Write all characters as
.Ql Ic charmap Ar name , Ar value .
.It Cm macro
Write all macros as
.Ql Ic macro Ar name No ... Ic endm .
.It Cm all
Acts like
.Cm equ,var,equs,char,macro .
.El
.Pp
This flag may be specified multiple times with different feature subsets to write them to different files (see
.Sx EXAMPLES
below).
.It Fl V , Fl \-version
Print the version of the program and exit.
.It Fl v , Fl \-verbose
@@ -327,6 +365,12 @@ The resulting object file is not yet a usable ROM image\(emit must first be run
.Xr rgblink 1
and then
.Xr rgbfix 1 .
.Pp
Writing the final assembler state to a file:
.Dl $ rgbasm -s all:state.dump.asm foo.asm
.Pp
Or to multiple files:
.Dl $ rgbasm -s equ,var:numbers.dump.asm -s equs:strings.dump.asm foo.asm
.Sh BUGS
Please report bugs on
.Lk https://github.com/gbdev/rgbds/issues GitHub .

View File

@@ -2,6 +2,7 @@
#include "asm/charmap.hpp"
#include <deque>
#include <stack>
#include <stdio.h>
#include <stdlib.h>
@@ -18,7 +19,7 @@
// 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!
// 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;
void charmap_New(std::string const &name, std::string const *baseName) {
Charmap *base = nullptr;
if (baseName != nullptr) {
auto search = charmaps.find(*baseName);
if (search == charmaps.end())
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
else
base = &search->second;
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();
}
if (charmaps.find(name) != charmaps.end()) {
void charmap_New(std::string const &name, std::string const *baseName) {
size_t baseIdx = (size_t)-1;
if (baseName != nullptr) {
if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
error("Base charmap '%s' doesn't exist\n", baseName->c_str());
else
baseIdx = search->second;
}
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,12 +91,14 @@ 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"
" -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"
@@ -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);
}