diff --git a/include/asm/charmap.hpp b/include/asm/charmap.hpp index 34a578da..b1292ca5 100644 --- a/include/asm/charmap.hpp +++ b/include/asm/charmap.hpp @@ -3,6 +3,7 @@ #ifndef RGBDS_ASM_CHARMAP_HPP #define RGBDS_ASM_CHARMAP_HPP +#include #include #include #include @@ -22,6 +23,7 @@ void charmap_CheckStack(); void charmap_Add(std::string const &mapping, std::vector &&value); bool charmap_HasChar(std::string const &mapping); size_t charmap_CharSize(std::string const &mapping); +std::optional charmap_CharValue(std::string const &mapping, size_t idx); std::vector charmap_Convert(std::string const &input); size_t charmap_ConvertNext(std::string_view &input, std::vector *output); std::string charmap_Reverse(std::vector const &value, bool &unique); diff --git a/man/rgbasm.5 b/man/rgbasm.5 index 0a4e0c95..1ff010fb 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -608,14 +608,21 @@ The following functions operate on string expressions, but return integers. .It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap . .It Fn CHARCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to their charmap entry values with the current charmap. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match. .It Fn CHARSIZE char Ta Returns how many values are in the charmap entry for Ar char No with the current charmap. +.It Fn CHARVAL char idx Ta Returns the value at Ar idx No of the charmap entry for Ar char Ns . .El .Pp -Note that the first character of a string is at index 0, and the last is at index -1. +Note that indexes count starting from 0 at the beginning, or from -1 at the end. +The characters of a string are counted by +.Ql STRLEN ; +the charmap entries of a string are counted by +.Ql CHARLEN ; +and the values of a charmap entry are counted by +.Ql CHARSIZE . .Pp -The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count characters starting from +The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count starting from .Em position 1 , not from index 0! -(Position -1 still counts from the last character.) +(Position -1 still counts from the end.) .Bl -column "STRSUB(str, pos, len)" .It Sy Name Ta Sy Operation .It Fn STRSUB str pos len Ta Returns a substring of Ar str No starting at Ar pos No and Ar len No characters long. If Ar len No is not specified, the substring continues to the end of Ar str No . diff --git a/src/asm/charmap.cpp b/src/asm/charmap.cpp index 4f02194c..0f5d76d8 100644 --- a/src/asm/charmap.cpp +++ b/src/asm/charmap.cpp @@ -189,7 +189,7 @@ bool charmap_HasChar(std::string const &mapping) { return charmap.nodes[nodeIdx].isTerminal(); } -size_t charmap_CharSize(std::string const &mapping) { +static CharmapNode const *charmapEntry(std::string const &mapping) { Charmap const &charmap = *currentCharmap; size_t nodeIdx = 0; @@ -197,12 +197,24 @@ size_t charmap_CharSize(std::string const &mapping) { nodeIdx = charmap.nodes[nodeIdx].next[static_cast(c)]; if (!nodeIdx) { - return 0; + return nullptr; } } - CharmapNode const &node = charmap.nodes[nodeIdx]; - return node.isTerminal() ? node.value.size() : 0; + return &charmap.nodes[nodeIdx]; +} + +size_t charmap_CharSize(std::string const &mapping) { + CharmapNode const *node = charmapEntry(mapping); + return node && node->isTerminal() ? node->value.size() : 0; +} + +std::optional charmap_CharValue(std::string const &mapping, size_t idx) { + if (CharmapNode const *node = charmapEntry(mapping); + node && node->isTerminal() && idx < node->value.size()) { + return node->value[idx]; + } + return std::nullopt; } std::vector charmap_Convert(std::string const &input) { diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 06ee1c59..a86f83f8 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -259,6 +259,7 @@ static std::unordered_map ke {"CHARLEN", T_(OP_CHARLEN) }, {"CHARSIZE", T_(OP_CHARSIZE) }, {"CHARSUB", T_(OP_CHARSUB) }, + {"CHARVAL", T_(OP_CHARVAL) }, {"INCHARMAP", T_(OP_INCHARMAP) }, {"REVCHAR", T_(OP_REVCHAR) }, diff --git a/src/asm/parser.y b/src/asm/parser.y index cbbe3b47..0680b33f 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -287,6 +287,7 @@ %token OP_CHARLEN "CHARLEN" %token OP_CHARSIZE "CHARSIZE" %token OP_CHARSUB "CHARSUB" +%token OP_CHARVAL "CHARVAL" %token OP_COS "COS" %token OP_DEF "DEF" %token OP_FDIV "FDIV" @@ -1580,6 +1581,24 @@ relocexpr_no_str: } $$.makeNumber(charSize); } + | OP_CHARVAL LPAREN string COMMA iconst RPAREN { + if (size_t len = charmap_CharSize($3); len != 0) { + uint32_t idx = adjustNegativeIndex($5, len, "CHARVAL"); + if (std::optional val = charmap_CharValue($3, idx); val.has_value()) { + $$.makeNumber(*val); + } else { + warning( + WARNING_BUILTIN_ARG, + "CHARVAL: Index %" PRIu32 " is past the end of the character mapping\n", + idx + ); + $$.makeNumber(0); + } + } else { + ::error("CHARVAL: No character mapping for \"%s\"\n", $3.c_str()); + $$.makeNumber(0); + } + } | LPAREN relocexpr RPAREN { $$ = std::move($2); } diff --git a/test/asm/charval.asm b/test/asm/charval.asm new file mode 100644 index 00000000..72e5431e --- /dev/null +++ b/test/asm/charval.asm @@ -0,0 +1,23 @@ +charmap "a", 1 +charmap "b", 2, 3 +charmap "cdef", 4 +charmap "ghi", 5, 6, 7, 8, 9 +charmap "jkl", 123, 456, 789 +charmap "mno", 123456789 + +assert charval("a", 0) == 1 +assert charval("a", -1) == 1 +assert charval("b", 0) == 2 +assert charval("b", 1) == 3 +assert charval("b", -1) == 3 +assert charval("b", -2) == 2 +assert charval("cdef", 0) == 4 +assert charval("ghi", 2) == charval("ghi", -3) +assert charval("jkl", -1) == 789 +assert charval("mno", 0) == 123456789 + +assert charval("abc", 0) == 0 +assert charval("cd", 1) == 0 +assert charval("xyz", 2) == 0 +assert charval("ghi", -10) == 5 +assert charval("ghi", 10) == 0 diff --git a/test/asm/charval.err b/test/asm/charval.err new file mode 100644 index 00000000..cf16c9f1 --- /dev/null +++ b/test/asm/charval.err @@ -0,0 +1,11 @@ +error: charval.asm(19): + CHARVAL: No character mapping for "abc" +error: charval.asm(20): + CHARVAL: No character mapping for "cd" +error: charval.asm(21): + CHARVAL: No character mapping for "xyz" +warning: charval.asm(22): [-Wbuiltin-args] + CHARVAL: Index starts at 0 +warning: charval.asm(23): [-Wbuiltin-args] + CHARVAL: Index 10 is past the end of the character mapping +error: Assembly aborted (3 errors)!