Lex '''-delimited multiline string literals. (#2133)

"""-delimited literals are still lexed for error recovery but produce an error.
This commit is contained in:
Richard Smith
2022-09-01 18:00:37 -07:00
committed by GitHub
parent e489b14de0
commit dcc80f5491
7 changed files with 242 additions and 99 deletions
+81 -45
View File
@@ -39,6 +39,13 @@ class StringLiteralTest : public ::testing::Test {
TEST_F(StringLiteralTest, StringLiteralBounds) {
llvm::StringLiteral valid[] = {
R"("")",
R"('''
''')",
R"('''
"foo"
''')",
// Lex """-delimited block string literals for error recovery.
R"("""
""")",
R"("""
@@ -49,21 +56,21 @@ TEST_F(StringLiteralTest, StringLiteralBounds) {
R"("\"")",
R"("\\")",
R"("\\\"")",
R"("""
\"""
""")",
R"("""
"\""
""")",
R"("""
""\"
""")",
R"("""
""\
""")",
R"(#"""
"""\#n
"""#)",
R"('''
\'''
''')",
R"('''
'\''
''')",
R"('''
''\'
''')",
R"('''
''\
''')",
R"(#'''
'''\#n
'''#)",
// Only a matching number of '#'s terminates the string.
R"(#""#)",
@@ -74,9 +81,9 @@ TEST_F(StringLiteralTest, StringLiteralBounds) {
// Escape sequences likewise require a matching number of '#'s.
R"(#"\#"#"#)",
R"(#"\"#)",
R"(#"""
\#"""#
"""#)",
R"(#'''
\#'''#
'''#)",
// #"""# does not start a multiline string literal.
R"(#"""#)",
@@ -95,16 +102,14 @@ TEST_F(StringLiteralTest, StringLiteralBounds) {
llvm::StringLiteral invalid[] = {
// clang-format off
R"(")",
R"("""
"")",
R"("\)",
R"("\")",
R"("\\)",
R"("\\\")",
R"("""
)",
R"(#"""
""")",
"'''\n",
"'''\n'",
"'''\n''",
"#'''\n'''",
R"(" \
")",
// clang-format on
@@ -126,41 +131,41 @@ TEST_F(StringLiteralTest, StringLiteralContents) {
{R"("")", ""},
{R"(
"""
"""
'''
'''
)",
""},
// Nearly-empty strings.
{R"(
"""
'''
"""
'''
)",
"\n"},
// Lines containing only whitespace are treated as empty even if they
// contain tabs.
{"\"\"\"\n\t \t\n\"\"\"", "\n"},
{"'''\n\t \t\n'''", "\n"},
// Indent removal.
{R"(
"""file type indicator
'''file type indicator
indented contents \
"""
'''
)",
" indented contents "},
// Removal of tabs in indent and suffix.
{"\"\"\"\n \t hello \t \n \t \"\"\"", " hello\n"},
{"'''\n \t hello \t \n \t '''", " hello\n"},
{R"(
"""
'''
hello
world
end of test
"""
'''
)",
" hello\nworld\n\n end of test\n"},
@@ -195,7 +200,7 @@ TEST_F(StringLiteralTest, StringLiteralContents) {
"\\n,\\#n,\n,\\##n,\\###n"},
// Trailing whitespace handling.
{"\"\"\"\n Hello \\\n World \t \n Bye! \\\n \"\"\"",
{"'''\n Hello \\\n World \t \n Bye! \\\n '''",
"Hello World\nBye! "},
};
@@ -207,20 +212,51 @@ TEST_F(StringLiteralTest, StringLiteralContents) {
}
}
TEST_F(StringLiteralTest, DoubleQuotedMultiLineLiteral) {
// For error recovery, """-delimited literals are lexed, but rejected.
std::pair<llvm::StringLiteral, llvm::StringLiteral> testcases[] = {
{R"(
"""
'''
"""
)",
"'''\n"},
{R"(
#"""
\#tx
"""#
)",
"\tx\n"},
{R"(
"""abcxyz
hello\
"""
)",
"hello"},
};
for (auto [test, contents] : testcases) {
error_tracker.Reset();
auto value = Parse(test.trim());
EXPECT_TRUE(error_tracker.seen_error()) << "`" << test << "`";
EXPECT_EQ(value, contents);
}
}
TEST_F(StringLiteralTest, StringLiteralBadIndent) {
std::pair<llvm::StringLiteral, llvm::StringLiteral> testcases[] = {
// Indent doesn't match the last line.
{"\"\"\"\n \tx\n \"\"\"", "x\n"},
{"\"\"\"\n x\n \"\"\"", "x\n"},
{"\"\"\"\n x\n\t\"\"\"", "x\n"},
{"\"\"\"\n ok\n bad\n \"\"\"", "ok\nbad\n"},
{"\"\"\"\n bad\n ok\n \"\"\"", "bad\nok\n"},
{"\"\"\"\n escaped,\\\n bad\n \"\"\"", "escaped,bad\n"},
{"'''\n \tx\n '''", "x\n"},
{"'''\n x\n '''", "x\n"},
{"'''\n x\n\t'''", "x\n"},
{"'''\n ok\n bad\n '''", "ok\nbad\n"},
{"'''\n bad\n ok\n '''", "bad\nok\n"},
{"'''\n escaped,\\\n bad\n '''", "escaped,bad\n"},
// Indent on last line is followed by text.
{"\"\"\"\n x\n x\"\"\"", "x\nx"},
{"\"\"\"\n x\n x\"\"\"", " x\nx"},
{"\"\"\"\n x\n x\"\"\"", "x\nx"},
{"'''\n x\n x'''", "x\nx"},
{"'''\n x\n x'''", " x\nx"},
{"'''\n x\n x'''", "x\nx"},
};
for (auto [test, contents] : testcases) {
@@ -293,7 +329,7 @@ TEST_F(StringLiteralTest, TabAtEndOfString) {
}
TEST_F(StringLiteralTest, TabInBlockString) {
auto value = Parse("\"\"\"\nx\ty\n\"\"\"");
auto value = Parse("'''\nx\ty\n'''");
EXPECT_TRUE(error_tracker.seen_error());
EXPECT_EQ(value, "x\ty\n");
}