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_DIV, /* Division undefined behavior */
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` with no directive in ROM */
|
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_ENTRY, /* Empty entry in `db`, `dw` or `dl` */
|
||||||
|
WARNING_EMPTY_STRRPL, /* Empty second argument in `STRRPL` */
|
||||||
WARNING_LARGE_CONSTANT, /* Constants too large */
|
WARNING_LARGE_CONSTANT, /* Constants too large */
|
||||||
WARNING_LONG_STR, /* String too long for internal buffers */
|
WARNING_LONG_STR, /* String too long for internal buffers */
|
||||||
WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */
|
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);
|
size_t codepointLen = readUTF8Char(output, input);
|
||||||
|
|
||||||
if (codepointLen == 0) {
|
if (codepointLen == 0) {
|
||||||
error("Input string is not valid UTF-8!");
|
error("Input string is not valid UTF-8!\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
|
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ static struct KeywordMapping {
|
|||||||
{"STRCAT", T_OP_STRCAT},
|
{"STRCAT", T_OP_STRCAT},
|
||||||
{"STRUPR", T_OP_STRUPR},
|
{"STRUPR", T_OP_STRUPR},
|
||||||
{"STRLWR", T_OP_STRLWR},
|
{"STRLWR", T_OP_STRLWR},
|
||||||
|
{"STRRPL", T_OP_STRRPL},
|
||||||
{"STRFMT", T_OP_STRFMT},
|
{"STRFMT", T_OP_STRFMT},
|
||||||
|
|
||||||
{"INCLUDE", T_POP_INCLUDE},
|
{"INCLUDE", T_POP_INCLUDE},
|
||||||
@@ -493,7 +494,7 @@ struct KeywordDictNode {
|
|||||||
uint16_t children[0x60 - ' '];
|
uint16_t children[0x60 - ' '];
|
||||||
struct KeywordMapping const *keyword;
|
struct KeywordMapping const *keyword;
|
||||||
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */
|
/* 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 */
|
/* Convert a char into its index into the dict */
|
||||||
static inline uint8_t dictIndex(char c)
|
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;
|
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->nbArgs = 0;
|
||||||
args->capacity = INITIAL_STRFMT_ARG_SIZE;
|
args->capacity = INITIAL_STRFMT_ARG_SIZE;
|
||||||
args->args = malloc(args->capacity * sizeof(*args->args));
|
args->args = malloc(args->capacity * sizeof(*args->args));
|
||||||
@@ -173,7 +212,8 @@ static void initStrFmtArgList(struct StrFmtArgList *args) {
|
|||||||
strerror(errno));
|
strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t nextStrFmtArgListIndex(struct StrFmtArgList *args) {
|
static size_t nextStrFmtArgListIndex(struct StrFmtArgList *args)
|
||||||
|
{
|
||||||
if (args->nbArgs == args->capacity) {
|
if (args->nbArgs == args->capacity) {
|
||||||
args->capacity = (args->capacity + 1) * 2;
|
args->capacity = (args->capacity + 1) * 2;
|
||||||
args->args = realloc(args->args, args->capacity * sizeof(*args->args));
|
args->args = realloc(args->args, args->capacity * sizeof(*args->args));
|
||||||
@@ -184,7 +224,8 @@ static size_t nextStrFmtArgListIndex(struct StrFmtArgList *args) {
|
|||||||
return args->nbArgs++;
|
return args->nbArgs++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void freeStrFmtArgList(struct StrFmtArgList *args) {
|
static void freeStrFmtArgList(struct StrFmtArgList *args)
|
||||||
|
{
|
||||||
free(args->format);
|
free(args->format);
|
||||||
for (size_t i = 0; i < args->nbArgs; i++)
|
for (size_t i = 0; i < args->nbArgs; i++)
|
||||||
if (!args->args[i].isNumeric)
|
if (!args->args[i].isNumeric)
|
||||||
@@ -192,11 +233,12 @@ static void freeStrFmtArgList(struct StrFmtArgList *args) {
|
|||||||
free(args->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 a = 0;
|
||||||
size_t i;
|
size_t i = 0;
|
||||||
|
|
||||||
for (i = 0; i < destLen;) {
|
while (i < destLen) {
|
||||||
int c = *fmt++;
|
int c = *fmt++;
|
||||||
|
|
||||||
if (c == '\0') {
|
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);
|
i += snprintf(&dest[i], destLen - i, "%s", buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (a < nbArgs)
|
||||||
|
error("STRFMT: %zu unformatted argument(s)\n", nbArgs - a);
|
||||||
|
|
||||||
if (i > destLen - 1) {
|
if (i > destLen - 1) {
|
||||||
warning(WARNING_LONG_STR, "STRFMT: String too long, got truncated\n");
|
warning(WARNING_LONG_STR, "STRFMT: String too long, got truncated\n");
|
||||||
i = destLen - 1;
|
i = destLen - 1;
|
||||||
}
|
}
|
||||||
dest[i] = '\0';
|
dest[i] = '\0';
|
||||||
|
|
||||||
if (a < nbArgs)
|
|
||||||
error("STRFMT: %zu unformatted argument(s)\n", nbArgs - a);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void failAssert(enum AssertionType type)
|
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_STRCAT
|
||||||
%left T_OP_STRUPR
|
%left T_OP_STRUPR
|
||||||
%left T_OP_STRLWR
|
%left T_OP_STRLWR
|
||||||
|
%left T_OP_STRRPL
|
||||||
%left T_OP_STRFMT
|
%left T_OP_STRFMT
|
||||||
|
|
||||||
%token <tzSym> T_LABEL
|
%token <tzSym> T_LABEL
|
||||||
@@ -1293,15 +1336,18 @@ string : T_STRING
|
|||||||
| T_OP_STRLWR T_LPAREN string T_RPAREN {
|
| T_OP_STRLWR T_LPAREN string T_RPAREN {
|
||||||
lowerstring($$, $3);
|
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 {
|
| 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);
|
freeStrFmtArgList(&$3);
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
strcat_args : string
|
strcat_args : string
|
||||||
| strcat_args T_COMMA 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",
|
warning(WARNING_LONG_STR, "STRCAT: String too long '%s%s'\n",
|
||||||
$1, $3);
|
$1, $3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,6 +215,12 @@ Warn when an empty entry is encountered in a
|
|||||||
list.
|
list.
|
||||||
This warning is enabled by
|
This warning is enabled by
|
||||||
.Fl Wextra .
|
.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
|
.It Fl Wlarge-constant
|
||||||
Warn when a constant too large to fit in a signed 32-bit integer is encountered.
|
Warn when a constant too large to fit in a signed 32-bit integer is encountered.
|
||||||
This warning is enabled by
|
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 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 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 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
|
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||||
.Ql %spec
|
.Ql %spec
|
||||||
pattern replaced by interpolating the format
|
pattern replaced by interpolating the format
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
|
|||||||
[WARNING_DIV] = WARNING_DISABLED,
|
[WARNING_DIV] = WARNING_DISABLED,
|
||||||
[WARNING_EMPTY_DATA_DIRECTIVE] = WARNING_DISABLED,
|
[WARNING_EMPTY_DATA_DIRECTIVE] = WARNING_DISABLED,
|
||||||
[WARNING_EMPTY_ENTRY] = WARNING_DISABLED,
|
[WARNING_EMPTY_ENTRY] = WARNING_DISABLED,
|
||||||
|
[WARNING_EMPTY_STRRPL] = WARNING_DISABLED,
|
||||||
[WARNING_LARGE_CONSTANT] = WARNING_DISABLED,
|
[WARNING_LARGE_CONSTANT] = WARNING_DISABLED,
|
||||||
[WARNING_LONG_STR] = WARNING_DISABLED,
|
[WARNING_LONG_STR] = WARNING_DISABLED,
|
||||||
[WARNING_NESTED_COMMENT] = WARNING_ENABLED,
|
[WARNING_NESTED_COMMENT] = WARNING_ENABLED,
|
||||||
@@ -74,6 +75,7 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
|
|||||||
"div",
|
"div",
|
||||||
"empty-data-directive",
|
"empty-data-directive",
|
||||||
"empty-entry",
|
"empty-entry",
|
||||||
|
"empty-strrpl",
|
||||||
"large-constant",
|
"large-constant",
|
||||||
"long-string",
|
"long-string",
|
||||||
"nested-comment",
|
"nested-comment",
|
||||||
@@ -98,6 +100,7 @@ static uint8_t const _wallCommands[] = {
|
|||||||
WARNING_BUILTIN_ARG,
|
WARNING_BUILTIN_ARG,
|
||||||
WARNING_CHARMAP_REDEF,
|
WARNING_CHARMAP_REDEF,
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||||
|
WARNING_EMPTY_STRRPL,
|
||||||
WARNING_LARGE_CONSTANT,
|
WARNING_LARGE_CONSTANT,
|
||||||
WARNING_LONG_STR,
|
WARNING_LONG_STR,
|
||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
@@ -116,6 +119,7 @@ static uint8_t const _weverythingCommands[] = {
|
|||||||
WARNING_DIV,
|
WARNING_DIV,
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||||
WARNING_EMPTY_ENTRY,
|
WARNING_EMPTY_ENTRY,
|
||||||
|
WARNING_EMPTY_STRRPL,
|
||||||
WARNING_LARGE_CONSTANT,
|
WARNING_LARGE_CONSTANT,
|
||||||
WARNING_LONG_STR,
|
WARNING_LONG_STR,
|
||||||
WARNING_NESTED_COMMENT,
|
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