shithub: rgbds

Download patch

ref: 255b8bf9ba04a4621cd41daadf498156c4bc3d80
parent: ad6f17cd93fb5d505240802d6cf53a285086594f
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Wed Dec 16 07:31:44 EST 2020

Implement """triple-quoted""" multi-line strings

Fixes #589

--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -1448,30 +1448,57 @@
 	dbgPrint("Reading string\n");
 	lexerState->disableMacroArgs = true;
 	lexerState->disableInterpolation = true;
+
+	bool multiline = false;
+
+	if (peek(0) == '"') {
+		shiftChars(1);
+		if (peek(0) == '"') {
+			/* """ begins a multi-line string */
+			shiftChars(1);
+			multiline = true;
+		} else {
+			/* "" is an empty string */
+			goto finish;
+		}
+	}
+
 	for (;;) {
 		int c = peek(0);
 
+		if (c == '\r' || c == '\n') {
+			if (!multiline) {
+				/* '\r' or '\n' ends a single-line string early */
+				c = EOF;
+			} else if (c == '\r' && peek(1) == '\n') {
+				/* '\r\n' becomes '\n' in multi-line strings */
+				shiftChars(1);
+				c = '\n';
+			}
+		}
+
 		switch (c) {
 		case '"':
-			shiftChars(1);
+			if (multiline) {
+				/* """ ends a multi-line string */
+				if (peek(1) != '"' || peek(2) != '"')
+					break;
+				shiftChars(3);
+			} else {
+				shiftChars(1);
+			}
 			if (i == sizeof(yylval.tzString)) {
 				i--;
 				warning(WARNING_LONG_STR, "String constant too long\n");
 			}
-			yylval.tzString[i] = '\0';
-			dbgPrint("Read string \"%s\"\n", yylval.tzString);
 			goto finish;
 
-		case '\r':
-		case '\n': /* Do not shift these! */
 		case EOF:
 			if (i == sizeof(yylval.tzString)) {
 				i--;
 				warning(WARNING_LONG_STR, "String constant too long\n");
 			}
-			yylval.tzString[i] = '\0';
 			error("Unterminated string\n");
-			dbgPrint("Read string \"%s\"\n", yylval.tzString);
 			goto finish;
 
 		case '\\': /* Character escape or macro arg */
@@ -1553,6 +1580,9 @@
 	}
 
 finish:
+	yylval.tzString[i] = '\0';
+
+	dbgPrint("Read string \"%s\"\n", yylval.tzString);
 	lexerState->disableMacroArgs = false;
 	lexerState->disableInterpolation = false;
 }
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -240,6 +240,14 @@
 .El
 (Note that some of those can be used outside of strings, when noted further in this document.)
 .Pp
+Multi-line strings are contained in triple quotes
+.Pq Ql \&"\&"\&"for instance\&"\&"\&" .
+Escape sequences work the same way in multi-line strings; however, literal newline
+characters will be included as-is, without needing to escape them with
+.Ql \[rs]r
+or
+.Ql \[rs]n .
+.Pp
 A funky feature is
 .Ql {symbol}
 within a string, called
--- /dev/null
+++ b/test/asm/multi-line-strings.asm
@@ -1,0 +1,32 @@
+S EQUS "Hello"
+
+PRINTT "\"\"\"\n"
+
+PRINTT """{S}
+world
+"""
+
+PRINTT """The multi-line string \ ; line continuations work
+can contain:
+- "single quotes"
+- ""double quotes""
+- even escaped \"""triple"\"" ""\"quotes\"\"\"
+!"""
+
+PRINTT """\n"""
+
+printarg: MACRO
+	PRINTT "arg <\1>\n"
+	PRINTT """arg (\1)\n"""
+ENDM
+
+	printarg "
+	printarg """
+
+EMPTY1 EQUS ""
+EMPTY2 EQUS "\ ; comment
+"
+EMPTY3 EQUS """"""
+EMPTY4 EQUS """\ ; comment
+"""
+	PRINTT STRCAT("(", "{EMPTY1}", "{EMPTY2}", "{EMPTY3}", "{EMPTY4}", ")\n")
--- /dev/null
+++ b/test/asm/multi-line-strings.out
@@ -1,0 +1,13 @@
+"""
+Hello
+world
+The multi-line string can contain:
+- "single quotes"
+- ""double quotes""
+- even escaped """triple""" """quotes"""
+!
+arg <">
+arg (")
+arg <""">
+arg (""")
+()