diff --git a/man/rgbasm.5 b/man/rgbasm.5 index ac9ffe6b..0a4e0c95 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -548,7 +548,7 @@ There are a number of escape sequences you can use within a string: .El .Pp Multi-line strings are contained in triple quotes -.Pq Ql \&"\&"\&"for instance\&"\&"\&" . +.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 \er or @@ -560,10 +560,19 @@ Inside them, backslashes and braces are treated like regular characters, so they For example, the raw string .Ql #"\et\e1{s}\e" is equivalent to the regular string -.Ql "\e\et\e\e1\e{s}\e\e" . +.Ql \&"\e\et\e\e1\e{s}\e\e" . (Note that this prevents raw strings from including the double quote character.) Raw strings also may be contained in triple quotes for them to be multi-line, so they can include literal newline or quote characters (although still not three quotes in a row). .Pp +You can use the +.Sq ++ +operator to concatenate two strings. +.Ql \&"str" ++ \&"ing" +is equivalent to +.Ql \&"string" , +or to +.Ql STRCAT("str", \&"ing") . +.Pp The following functions operate on string expressions, and return strings themselves. .Bl -column "STRSLICE(str, start, stop)" .It Sy Name Ta Sy Operation diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index ca001dd3..06ee1c59 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -1774,12 +1774,17 @@ static Token yylex_NORMAL() { // Handle ambiguous 1- or 2-char tokens - case '+': // Either += or ADD - if (peek() == '=') { + case '+': // Either +=, ADD, or CAT + switch (peek()) { + case '=': shiftChar(); return Token(T_(POP_ADDEQ)); + case '+': + shiftChar(); + return Token(T_(OP_CAT)); + default: + return Token(T_(OP_ADD)); } - return Token(T_(OP_ADD)); case '-': // Either -= or SUB if (peek() == '=') { diff --git a/src/asm/parser.y b/src/asm/parser.y index 64ed5f47..cbbe3b47 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -125,6 +125,9 @@ %token OP_MUL "*" OP_DIV "/" OP_MOD "%" %token OP_EXP "**" +// String operators +%token OP_CAT "++" + // Comparison operators %token OP_LOGICEQU "==" OP_LOGICNE "!=" %token OP_LOGICLT "<" OP_LOGICGT ">" @@ -147,6 +150,7 @@ %left OP_AND OP_OR OP_XOR %left OP_SHL OP_SHR OP_USHR %left OP_MUL OP_DIV OP_MOD +%left OP_CAT %precedence NEG // applies to unary OP_LOGICNOT, OP_ADD, OP_SUB, OP_NOT %right OP_EXP @@ -1613,6 +1617,10 @@ string_literal: STRING { $$ = std::move($1); } + | string OP_CAT string { + $$ = std::move($1); + $$.append($3); + } | OP_STRSLICE LPAREN string COMMA iconst COMMA iconst RPAREN { size_t len = strlenUTF8($3, false); uint32_t start = adjustNegativeIndex($5, len, "STRSLICE"); diff --git a/test/asm/equs-newline.err b/test/asm/equs-newline.err index 3208a80a..04ceb7be 100644 --- a/test/asm/equs-newline.err +++ b/test/asm/equs-newline.err @@ -3,6 +3,5 @@ warning: equs-newline.asm(3): [-Wuser] while expanding symbol "ACT" warning: equs-newline.asm(3): [-Wuser] Second -while expanding symbol "ACT" warning: equs-newline.asm(4): [-Wuser] Third diff --git a/test/asm/string-concat.asm b/test/asm/string-concat.asm new file mode 100644 index 00000000..20e386cb --- /dev/null +++ b/test/asm/string-concat.asm @@ -0,0 +1,26 @@ +SECTION "test", ROM0 + +MACRO test + assert !strcmp(\1, \2) +ENDM + +test "a"++"b", "ab" +test "a"++""++"b", "ab" +test "a"++"b", strcat("a", "b") +test "a"++"b"++"c", strcat("a","b","c") +test "" ++ "", "" +test strupr("a") ++ strlwr("B"), "Ab" + +def str equs "hi" +test #str ++ strupr(#str), "hiHI" +test "a" ++ """b""" ++ strupr("c") ++ strslice(#str, 0, 0), "abC" + +charmap "a", 1 +charmap "b", 2 +charmap "ab", 12 +assert "a" + "b" == 3 +assert "a" ++ "b" == 12 + +; errors +assert 2 ++ 2 == 4 +ld a, [hl++] diff --git a/test/asm/string-concat.err b/test/asm/string-concat.err new file mode 100644 index 00000000..cb6b813a --- /dev/null +++ b/test/asm/string-concat.err @@ -0,0 +1,5 @@ +error: string-concat.asm(25): + syntax error, unexpected ++ +error: string-concat.asm(26): + syntax error, unexpected ++, expecting ] or + or - +error: Assembly aborted (2 errors)! diff --git a/test/asm/unique-id.err b/test/asm/unique-id.err index 11392fa6..d0f49d95 100644 --- a/test/asm/unique-id.err +++ b/test/asm/unique-id.err @@ -3,41 +3,30 @@ error: unique-id.asm(11): while expanding symbol "warn_unique" warning: unique-id.asm(11): [-Wuser] ! -while expanding symbol "warn_unique" warning: unique-id.asm(12) -> unique-id.asm::m(4): [-Wuser] _u1! -while expanding symbol "warn_unique" warning: unique-id.asm(12) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~1(6): [-Wuser] _u2! -while expanding symbol "warn_unique" warning: unique-id.asm(12) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~2(6): [-Wuser] _u3! -while expanding symbol "warn_unique" warning: unique-id.asm(12) -> unique-id.asm::m(8): [-Wuser] _u1! -while expanding symbol "warn_unique" error: unique-id.asm(13): '\@' cannot be used outside of a macro or REPT/FOR block while expanding symbol "warn_unique" warning: unique-id.asm(13): [-Wuser] ! -while expanding symbol "warn_unique" warning: unique-id.asm(14) -> unique-id.asm::m(4): [-Wuser] _u4! -while expanding symbol "warn_unique" warning: unique-id.asm(14) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~1(6): [-Wuser] _u5! -while expanding symbol "warn_unique" warning: unique-id.asm(14) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~2(6): [-Wuser] _u6! -while expanding symbol "warn_unique" warning: unique-id.asm(14) -> unique-id.asm::m(8): [-Wuser] _u4! -while expanding symbol "warn_unique" error: unique-id.asm(15): '\@' cannot be used outside of a macro or REPT/FOR block while expanding symbol "warn_unique" warning: unique-id.asm(15): [-Wuser] ! -while expanding symbol "warn_unique" error: Assembly aborted (3 errors)!