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 (""")
+()