Implement ++ operator for string concatenation (#1698)

This commit is contained in:
Rangi
2025-06-12 16:52:00 -04:00
committed by GitHub
parent fa3d83a3d1
commit fa9e29e4ce
7 changed files with 58 additions and 17 deletions

View File

@@ -548,7 +548,7 @@ There are a number of escape sequences you can use within a string:
.El .El
.Pp .Pp
Multi-line strings are contained in triple quotes 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 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 .Ql \er
or or
@@ -560,10 +560,19 @@ Inside them, backslashes and braces are treated like regular characters, so they
For example, the raw string For example, the raw string
.Ql #"\et\e1{s}\e" .Ql #"\et\e1{s}\e"
is equivalent to the regular string 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.) (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). 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 .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. The following functions operate on string expressions, and return strings themselves.
.Bl -column "STRSLICE(str, start, stop)" .Bl -column "STRSLICE(str, start, stop)"
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation

View File

@@ -1774,12 +1774,17 @@ static Token yylex_NORMAL() {
// Handle ambiguous 1- or 2-char tokens // Handle ambiguous 1- or 2-char tokens
case '+': // Either += or ADD case '+': // Either +=, ADD, or CAT
if (peek() == '=') { switch (peek()) {
case '=':
shiftChar(); shiftChar();
return Token(T_(POP_ADDEQ)); 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 case '-': // Either -= or SUB
if (peek() == '=') { if (peek() == '=') {

View File

@@ -125,6 +125,9 @@
%token OP_MUL "*" OP_DIV "/" OP_MOD "%" %token OP_MUL "*" OP_DIV "/" OP_MOD "%"
%token OP_EXP "**" %token OP_EXP "**"
// String operators
%token OP_CAT "++"
// Comparison operators // Comparison operators
%token OP_LOGICEQU "==" OP_LOGICNE "!=" %token OP_LOGICEQU "==" OP_LOGICNE "!="
%token OP_LOGICLT "<" OP_LOGICGT ">" %token OP_LOGICLT "<" OP_LOGICGT ">"
@@ -147,6 +150,7 @@
%left OP_AND OP_OR OP_XOR %left OP_AND OP_OR OP_XOR
%left OP_SHL OP_SHR OP_USHR %left OP_SHL OP_SHR OP_USHR
%left OP_MUL OP_DIV OP_MOD %left OP_MUL OP_DIV OP_MOD
%left OP_CAT
%precedence NEG // applies to unary OP_LOGICNOT, OP_ADD, OP_SUB, OP_NOT %precedence NEG // applies to unary OP_LOGICNOT, OP_ADD, OP_SUB, OP_NOT
%right OP_EXP %right OP_EXP
@@ -1613,6 +1617,10 @@ string_literal:
STRING { STRING {
$$ = std::move($1); $$ = std::move($1);
} }
| string OP_CAT string {
$$ = std::move($1);
$$.append($3);
}
| OP_STRSLICE LPAREN string COMMA iconst COMMA iconst RPAREN { | OP_STRSLICE LPAREN string COMMA iconst COMMA iconst RPAREN {
size_t len = strlenUTF8($3, false); size_t len = strlenUTF8($3, false);
uint32_t start = adjustNegativeIndex($5, len, "STRSLICE"); uint32_t start = adjustNegativeIndex($5, len, "STRSLICE");

View File

@@ -3,6 +3,5 @@ warning: equs-newline.asm(3): [-Wuser]
while expanding symbol "ACT" while expanding symbol "ACT"
warning: equs-newline.asm(3): [-Wuser] warning: equs-newline.asm(3): [-Wuser]
Second Second
while expanding symbol "ACT"
warning: equs-newline.asm(4): [-Wuser] warning: equs-newline.asm(4): [-Wuser]
Third Third

View File

@@ -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++]

View File

@@ -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)!

View File

@@ -3,41 +3,30 @@ error: unique-id.asm(11):
while expanding symbol "warn_unique" while expanding symbol "warn_unique"
warning: unique-id.asm(11): [-Wuser] warning: unique-id.asm(11): [-Wuser]
! !
while expanding symbol "warn_unique"
warning: unique-id.asm(12) -> unique-id.asm::m(4): [-Wuser] warning: unique-id.asm(12) -> unique-id.asm::m(4): [-Wuser]
_u1! _u1!
while expanding symbol "warn_unique"
warning: unique-id.asm(12) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~1(6): [-Wuser] warning: unique-id.asm(12) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~1(6): [-Wuser]
_u2! _u2!
while expanding symbol "warn_unique"
warning: unique-id.asm(12) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~2(6): [-Wuser] warning: unique-id.asm(12) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~2(6): [-Wuser]
_u3! _u3!
while expanding symbol "warn_unique"
warning: unique-id.asm(12) -> unique-id.asm::m(8): [-Wuser] warning: unique-id.asm(12) -> unique-id.asm::m(8): [-Wuser]
_u1! _u1!
while expanding symbol "warn_unique"
error: unique-id.asm(13): error: unique-id.asm(13):
'\@' cannot be used outside of a macro or REPT/FOR block '\@' cannot be used outside of a macro or REPT/FOR block
while expanding symbol "warn_unique" while expanding symbol "warn_unique"
warning: unique-id.asm(13): [-Wuser] warning: unique-id.asm(13): [-Wuser]
! !
while expanding symbol "warn_unique"
warning: unique-id.asm(14) -> unique-id.asm::m(4): [-Wuser] warning: unique-id.asm(14) -> unique-id.asm::m(4): [-Wuser]
_u4! _u4!
while expanding symbol "warn_unique"
warning: unique-id.asm(14) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~1(6): [-Wuser] warning: unique-id.asm(14) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~1(6): [-Wuser]
_u5! _u5!
while expanding symbol "warn_unique"
warning: unique-id.asm(14) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~2(6): [-Wuser] warning: unique-id.asm(14) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~2(6): [-Wuser]
_u6! _u6!
while expanding symbol "warn_unique"
warning: unique-id.asm(14) -> unique-id.asm::m(8): [-Wuser] warning: unique-id.asm(14) -> unique-id.asm::m(8): [-Wuser]
_u4! _u4!
while expanding symbol "warn_unique"
error: unique-id.asm(15): error: unique-id.asm(15):
'\@' cannot be used outside of a macro or REPT/FOR block '\@' cannot be used outside of a macro or REPT/FOR block
while expanding symbol "warn_unique" while expanding symbol "warn_unique"
warning: unique-id.asm(15): [-Wuser] warning: unique-id.asm(15): [-Wuser]
! !
while expanding symbol "warn_unique"
error: Assembly aborted (3 errors)! error: Assembly aborted (3 errors)!