shithub: rgbds

Download patch

ref: 77279984a53054436450ea251343515bfdbf25b1
parent: 669a392fcd02d7544b2ab70ad6d835c15301c458
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Wed Dec 30 06:00:44 EST 2020

Implement `STRRPL`

Fixes #660

STRRPL(str, "", new) does nothing
(warn about it with -Wempty-strrpl)

--- a/include/asm/warning.h
+++ b/include/asm/warning.h
@@ -20,6 +20,7 @@
 	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 */
--- a/src/asm/charmap.c
+++ b/src/asm/charmap.c
@@ -230,7 +230,7 @@
 				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 */
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -204,6 +204,7 @@
 	{"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 @@
 	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)
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -164,7 +164,46 @@
 	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 @@
 			   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 @@
 	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 @@
 	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 @@
 		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 @@
 %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,8 +1336,11 @@
 		| 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);
 		}
 ;
@@ -1301,7 +1347,7 @@
 
 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);
 		}
--- a/src/asm/rgbasm.1
+++ b/src/asm/rgbasm.1
@@ -215,6 +215,12 @@
 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
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -374,6 +374,7 @@
 .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
--- a/src/asm/warning.c
+++ b/src/asm/warning.c
@@ -34,6 +34,7 @@
 	[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 @@
 	"div",
 	"empty-data-directive",
 	"empty-entry",
+	"empty-strrpl",
 	"large-constant",
 	"long-string",
 	"nested-comment",
@@ -98,6 +100,7 @@
 	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 @@
 	WARNING_DIV,
 	WARNING_EMPTY_DATA_DIRECTIVE,
 	WARNING_EMPTY_ENTRY,
+	WARNING_EMPTY_STRRPL,
 	WARNING_LARGE_CONSTANT,
 	WARNING_LONG_STR,
 	WARNING_NESTED_COMMENT,
--- /dev/null
+++ b/test/asm/strrpl.asm
@@ -1,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]")
--- /dev/null
+++ b/test/asm/strrpl.err
@@ -1,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
--- /dev/null
+++ b/test/asm/strrpl.out
@@ -1,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