diff --git a/contrib/bash_compl/_rgbasm.bash b/contrib/bash_compl/_rgbasm.bash index 2c110706..bf1f7a28 100755 --- a/contrib/bash_compl/_rgbasm.bash +++ b/contrib/bash_compl/_rgbasm.bash @@ -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" ) diff --git a/contrib/bash_compl/_rgbgfx.bash b/contrib/bash_compl/_rgbgfx.bash index 6463ee14..82ee48f5 100755 --- a/contrib/bash_compl/_rgbgfx.bash +++ b/contrib/bash_compl/_rgbgfx.bash @@ -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" diff --git a/contrib/zsh_compl/_rgbasm b/contrib/zsh_compl/_rgbasm index 27915960..36b1edac 100644 --- a/contrib/zsh_compl/_rgbasm +++ b/contrib/zsh_compl/_rgbasm @@ -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:' diff --git a/contrib/zsh_compl/_rgbgfx b/contrib/zsh_compl/_rgbgfx index 408189f3..91ed969a 100644 --- a/contrib/zsh_compl/_rgbgfx +++ b/contrib/zsh_compl/_rgbgfx @@ -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:' diff --git a/include/asm/charmap.hpp b/include/asm/charmap.hpp index 926a8fb6..d76a0e40 100644 --- a/include/asm/charmap.hpp +++ b/include/asm/charmap.hpp @@ -10,6 +10,10 @@ #define DEFAULT_CHARMAP_NAME "main" +bool charmap_ForEach( + void (*mapFunc)(std::string const &), + void (*charFunc)(std::string const &, std::vector) +); void charmap_New(std::string const &name, std::string const *baseName); void charmap_Set(std::string const &name); void charmap_Push(); diff --git a/include/asm/output.hpp b/include/asm/output.hpp index 2e161c87..490f4448 100644 --- a/include/asm/output.hpp +++ b/include/asm/output.hpp @@ -6,12 +6,23 @@ #include #include #include +#include +#include #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 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 const &features); #endif // RGBDS_ASM_OUTPUT_HPP diff --git a/include/asm/symbol.hpp b/include/asm/symbol.hpp index 409e2ba5..19280a35 100644 --- a/include/asm/symbol.hpp +++ b/include/asm/symbol.hpp @@ -44,7 +44,8 @@ struct Symbol { > data; - uint32_t ID; // ID of the symbol in the object file (-1 if none) + 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; } diff --git a/man/rgbasm.1 b/man/rgbasm.1 index 454b5473..6a6d7218 100644 --- a/man/rgbasm.1 +++ b/man/rgbasm.1 @@ -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 . diff --git a/src/asm/charmap.cpp b/src/asm/charmap.cpp index 978820af..b0612fd4 100644 --- a/src/asm/charmap.cpp +++ b/src/asm/charmap.cpp @@ -2,6 +2,7 @@ #include "asm/charmap.hpp" +#include #include #include #include @@ -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 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 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 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 mappings; // keys are indexes of terminal nodes }; -static std::unordered_map charmaps; +static std::deque charmapList; +static std::unordered_map charmapMap; // Indexes into `charmapList` static Charmap *currentCharmap; std::stack charmapStack; +bool charmap_ForEach( + void (*mapFunc)(std::string const &), + void (*charFunc)(std::string const &, std::vector) +) { + 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 &&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(); diff --git a/src/asm/main.cpp b/src/asm/main.cpp index 97586175..73ad4e1c 100644 --- a/src/asm/main.cpp +++ b/src/asm/main.cpp @@ -2,6 +2,7 @@ #include "asm/main.hpp" +#include #include #include #include @@ -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] \n" + " [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n" + " \n" "Useful options:\n" - " -E, --export-all export all labels\n" - " -M, --dependfile set the output dependency file\n" - " -o, --output set the output object file\n" - " -p, --pad-value set the value to use for `ds'\n" - " -V, --version print RGBASM version and exit\n" - " -W, --warning enable or disable warnings\n" + " -E, --export-all export all labels\n" + " -M, --dependfile set the output dependency file\n" + " -o, --output set the output object file\n" + " -p, --pad-value set the value to use for `ds'\n" + " -s, --state : set an output state file\n" + " -V, --version print RGBASM version and exit\n" + " -W, --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> 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 ":" so `musl_optarg` is "" and `name` is "" + char *name = strchr(musl_optarg, ':'); + if (!name) + errx("Invalid argument for option 's'"); + *name++ = '\0'; + + std::vector features; + for (char *feature = musl_optarg; feature;) { + // Split "," so `feature` is "" and `next` is "" + 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; } diff --git a/src/asm/output.cpp b/src/asm/output.cpp index 9f76d909..70a251c2 100644 --- a/src/asm/output.cpp +++ b/src/asm/output.cpp @@ -2,17 +2,18 @@ #include "asm/output.hpp" +#include #include #include #include #include #include #include -#include #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 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(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 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(sym->getOutputValue()); + fprintf(file, "def %s = $%" PRIx32 "\n", sym->name.c_str(), value); + } + + return !variables.empty(); +} + +static bool dumpEqusConstants(FILE *file) { + static std::vector 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 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 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 const &features) { + FILE *file; + if (name != "-") { + file = fopen(name.c_str(), "w"); + } else { + name = ""; + 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"); + } +} diff --git a/src/asm/symbol.cpp b/src/asm/symbol.cpp index 8d803a39..46c12f01 100644 --- a/src/asm/symbol.cpp +++ b/src/asm/symbol.cpp @@ -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; } diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index c87ae592..51de1dd9 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -784,7 +784,7 @@ int main(int argc, char *argv[]) { fputs("#none, ", stderr); } } - fputc('\n', stderr); + putc('\n', stderr); } fputs("\t]\n", stderr); } diff --git a/src/link/symbol.cpp b/src/link/symbol.cpp index b7060035..f061938b 100644 --- a/src/link/symbol.cpp +++ b/src/link/symbol.cpp @@ -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); }