mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Implement STRRPL
Fixes #660 STRRPL(str, "", new) does nothing (warn about it with -Wempty-strrpl)
This commit is contained in:
@@ -20,6 +20,7 @@ enum WarningID {
|
||||
WARNING_DIV, /* Division undefined behavior */
|
||||
WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` with no directive in ROM */
|
||||
WARNING_EMPTY_ENTRY, /* Empty entry in `db`, `dw` or `dl` */
|
||||
WARNING_EMPTY_STRRPL, /* Empty second argument in `STRRPL` */
|
||||
WARNING_LARGE_CONSTANT, /* Constants too large */
|
||||
WARNING_LONG_STR, /* String too long for internal buffers */
|
||||
WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */
|
||||
|
||||
@@ -230,7 +230,7 @@ size_t charmap_Convert(char const *input, uint8_t *output)
|
||||
size_t codepointLen = readUTF8Char(output, input);
|
||||
|
||||
if (codepointLen == 0) {
|
||||
error("Input string is not valid UTF-8!");
|
||||
error("Input string is not valid UTF-8!\n");
|
||||
break;
|
||||
}
|
||||
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
|
||||
|
||||
@@ -204,6 +204,7 @@ static struct KeywordMapping {
|
||||
{"STRCAT", T_OP_STRCAT},
|
||||
{"STRUPR", T_OP_STRUPR},
|
||||
{"STRLWR", T_OP_STRLWR},
|
||||
{"STRRPL", T_OP_STRRPL},
|
||||
{"STRFMT", T_OP_STRFMT},
|
||||
|
||||
{"INCLUDE", T_POP_INCLUDE},
|
||||
@@ -493,7 +494,7 @@ struct KeywordDictNode {
|
||||
uint16_t children[0x60 - ' '];
|
||||
struct KeywordMapping const *keyword;
|
||||
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */
|
||||
} keywordDict[353] = {0}; /* Make sure to keep this correct when adding keywords! */
|
||||
} keywordDict[355] = {0}; /* Make sure to keep this correct when adding keywords! */
|
||||
|
||||
/* Convert a char into its index into the dict */
|
||||
static inline uint8_t dictIndex(char c)
|
||||
|
||||
@@ -164,7 +164,46 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len)
|
||||
dest[destIndex] = 0;
|
||||
}
|
||||
|
||||
static void initStrFmtArgList(struct StrFmtArgList *args) {
|
||||
static void strrpl(char *dest, size_t destLen, char const *src, char const *old, char const *new)
|
||||
{
|
||||
size_t oldLen = strlen(old);
|
||||
size_t newLen = strlen(new);
|
||||
size_t i = 0;
|
||||
|
||||
if (!oldLen) {
|
||||
warning(WARNING_EMPTY_STRRPL, "STRRPL: Cannot replace an empty string\n");
|
||||
strcpy(dest, src);
|
||||
return;
|
||||
}
|
||||
|
||||
for (char const *next = strstr(src, old); next && *next; next = strstr(src, old)) {
|
||||
memcpy(dest + i, src, next - src < destLen - i ? next - src : destLen - i);
|
||||
i += next - src;
|
||||
if (i >= destLen)
|
||||
break;
|
||||
|
||||
memcpy(dest + i, new, newLen < destLen - i ? newLen : destLen - i);
|
||||
i += newLen;
|
||||
if (i >= destLen)
|
||||
break;
|
||||
|
||||
src = next + oldLen;
|
||||
}
|
||||
|
||||
size_t srcLen = strlen(src);
|
||||
|
||||
memcpy(dest + i, src, srcLen < destLen - i ? srcLen : destLen - i);
|
||||
i += srcLen;
|
||||
|
||||
if (i >= destLen) {
|
||||
warning(WARNING_LONG_STR, "STRRPL: String too long, got truncated\n");
|
||||
i = destLen - 1;
|
||||
}
|
||||
dest[i] = '\0';
|
||||
}
|
||||
|
||||
static void initStrFmtArgList(struct StrFmtArgList *args)
|
||||
{
|
||||
args->nbArgs = 0;
|
||||
args->capacity = INITIAL_STRFMT_ARG_SIZE;
|
||||
args->args = malloc(args->capacity * sizeof(*args->args));
|
||||
@@ -173,7 +212,8 @@ static void initStrFmtArgList(struct StrFmtArgList *args) {
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
static size_t nextStrFmtArgListIndex(struct StrFmtArgList *args) {
|
||||
static size_t nextStrFmtArgListIndex(struct StrFmtArgList *args)
|
||||
{
|
||||
if (args->nbArgs == args->capacity) {
|
||||
args->capacity = (args->capacity + 1) * 2;
|
||||
args->args = realloc(args->args, args->capacity * sizeof(*args->args));
|
||||
@@ -184,7 +224,8 @@ static size_t nextStrFmtArgListIndex(struct StrFmtArgList *args) {
|
||||
return args->nbArgs++;
|
||||
}
|
||||
|
||||
static void freeStrFmtArgList(struct StrFmtArgList *args) {
|
||||
static void freeStrFmtArgList(struct StrFmtArgList *args)
|
||||
{
|
||||
free(args->format);
|
||||
for (size_t i = 0; i < args->nbArgs; i++)
|
||||
if (!args->args[i].isNumeric)
|
||||
@@ -192,11 +233,12 @@ static void freeStrFmtArgList(struct StrFmtArgList *args) {
|
||||
free(args->args);
|
||||
}
|
||||
|
||||
static void strfmt(char *dest, size_t destLen, char const *fmt, size_t nbArgs, struct StrFmtArg *args) {
|
||||
static void strfmt(char *dest, size_t destLen, char const *fmt, size_t nbArgs, struct StrFmtArg *args)
|
||||
{
|
||||
size_t a = 0;
|
||||
size_t i;
|
||||
size_t i = 0;
|
||||
|
||||
for (i = 0; i < destLen;) {
|
||||
while (i < destLen) {
|
||||
int c = *fmt++;
|
||||
|
||||
if (c == '\0') {
|
||||
@@ -254,14 +296,14 @@ static void strfmt(char *dest, size_t destLen, char const *fmt, size_t nbArgs, s
|
||||
i += snprintf(&dest[i], destLen - i, "%s", buf);
|
||||
}
|
||||
|
||||
if (a < nbArgs)
|
||||
error("STRFMT: %zu unformatted argument(s)\n", nbArgs - a);
|
||||
|
||||
if (i > destLen - 1) {
|
||||
warning(WARNING_LONG_STR, "STRFMT: String too long, got truncated\n");
|
||||
i = destLen - 1;
|
||||
}
|
||||
dest[i] = '\0';
|
||||
|
||||
if (a < nbArgs)
|
||||
error("STRFMT: %zu unformatted argument(s)\n", nbArgs - a);
|
||||
}
|
||||
|
||||
static inline void failAssert(enum AssertionType type)
|
||||
@@ -387,6 +429,7 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
|
||||
%left T_OP_STRCAT
|
||||
%left T_OP_STRUPR
|
||||
%left T_OP_STRLWR
|
||||
%left T_OP_STRRPL
|
||||
%left T_OP_STRFMT
|
||||
|
||||
%token <tzSym> T_LABEL
|
||||
@@ -1293,15 +1336,18 @@ string : T_STRING
|
||||
| T_OP_STRLWR T_LPAREN string T_RPAREN {
|
||||
lowerstring($$, $3);
|
||||
}
|
||||
| T_OP_STRRPL T_LPAREN string T_COMMA string T_COMMA string T_RPAREN {
|
||||
strrpl($$, sizeof($$), $3, $5, $7);
|
||||
}
|
||||
| T_OP_STRFMT T_LPAREN strfmt_args T_RPAREN {
|
||||
strfmt($$, MAXSTRLEN + 1, $3.format, $3.nbArgs, $3.args);
|
||||
strfmt($$, sizeof($$), $3.format, $3.nbArgs, $3.args);
|
||||
freeStrFmtArgList(&$3);
|
||||
}
|
||||
;
|
||||
|
||||
strcat_args : string
|
||||
| strcat_args T_COMMA string {
|
||||
if (snprintf($$, MAXSTRLEN + 1, "%s%s", $1, $3) > MAXSTRLEN)
|
||||
if (snprintf($$, sizeof($$), "%s%s", $1, $3) > MAXSTRLEN)
|
||||
warning(WARNING_LONG_STR, "STRCAT: String too long '%s%s'\n",
|
||||
$1, $3);
|
||||
}
|
||||
|
||||
@@ -215,6 +215,12 @@ Warn when an empty entry is encountered in a
|
||||
list.
|
||||
This warning is enabled by
|
||||
.Fl Wextra .
|
||||
.It Fl Wempty-strrpl
|
||||
Warn when
|
||||
.Fn STRRPL
|
||||
is called with an empty string as its second argument (the substring to replace).
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wlarge-constant
|
||||
Warn when a constant too large to fit in a signed 32-bit integer is encountered.
|
||||
This warning is enabled by
|
||||
|
||||
@@ -374,6 +374,7 @@ Most of them return a string, however some of these functions actually return an
|
||||
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos Po first character is position 1 Pc and Ar len No characters long.
|
||||
.It Fn STRUPR str Ta Returns Ar str No with all letters in uppercase.
|
||||
.It Fn STRLWR str Ta Returns Ar str No with all letters in lowercase.
|
||||
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
|
||||
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||
.Ql %spec
|
||||
pattern replaced by interpolating the format
|
||||
|
||||
@@ -34,6 +34,7 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
|
||||
[WARNING_DIV] = WARNING_DISABLED,
|
||||
[WARNING_EMPTY_DATA_DIRECTIVE] = WARNING_DISABLED,
|
||||
[WARNING_EMPTY_ENTRY] = WARNING_DISABLED,
|
||||
[WARNING_EMPTY_STRRPL] = WARNING_DISABLED,
|
||||
[WARNING_LARGE_CONSTANT] = WARNING_DISABLED,
|
||||
[WARNING_LONG_STR] = WARNING_DISABLED,
|
||||
[WARNING_NESTED_COMMENT] = WARNING_ENABLED,
|
||||
@@ -74,6 +75,7 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
|
||||
"div",
|
||||
"empty-data-directive",
|
||||
"empty-entry",
|
||||
"empty-strrpl",
|
||||
"large-constant",
|
||||
"long-string",
|
||||
"nested-comment",
|
||||
@@ -98,6 +100,7 @@ static uint8_t const _wallCommands[] = {
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_CHARMAP_REDEF,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_LONG_STR,
|
||||
META_WARNING_DONE
|
||||
@@ -116,6 +119,7 @@ static uint8_t const _weverythingCommands[] = {
|
||||
WARNING_DIV,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_ENTRY,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_LONG_STR,
|
||||
WARNING_NESTED_COMMENT,
|
||||
|
||||
6
test/asm/strrpl.asm
Normal file
6
test/asm/strrpl.asm
Normal file
@@ -0,0 +1,6 @@
|
||||
println strrpl("\tld [hli], a", "[hli]", "[hl+]")
|
||||
println strrpl("lolololol", "lol", "hah")
|
||||
println strrpl("h e ll o", " ", "")
|
||||
println strrpl("world", "", "x")
|
||||
println strrpl("", "a", "b")
|
||||
println strrpl("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "a", "[bbbbbbbb]")
|
||||
4
test/asm/strrpl.err
Normal file
4
test/asm/strrpl.err
Normal file
@@ -0,0 +1,4 @@
|
||||
warning: strrpl.asm(4): [-Wempty-strrpl]
|
||||
STRRPL: Cannot replace an empty string
|
||||
warning: strrpl.asm(6): [-Wlong-string]
|
||||
STRRPL: String too long, got truncated
|
||||
6
test/asm/strrpl.out
Normal file
6
test/asm/strrpl.out
Normal file
@@ -0,0 +1,6 @@
|
||||
ld [hl+], a
|
||||
hahohahol
|
||||
hello
|
||||
world
|
||||
|
||||
[bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbbbbbb][bbbb
|
||||
Reference in New Issue
Block a user