Skip to content

Commit 3442a82

Browse files
authored
Merge pull request #16 from BelfrySCAD/feat/unicode-line-separators
Lexer: Unicode line separators are whitespace that ends a // comment
2 parents bd07ef7 + 4c6e609 commit 3442a82

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

‎src/grammar/lexer.l‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,17 @@ NUMLIT 0[xX][0-9A-Fa-f]+|[0-9]+([.][0-9]*)?([eE][+-]?[0-9]+)?|[.][0-9]+([eE][+-
8888
// U+FEFF (EF BB BF, a byte-order mark, anywhere in the file) and a bare
8989
// A0, Latin-1's no-break space.
9090
}
91-
"//"[^\n]* { /* skip line comment */ }
91+
\xe2\x80\xa8|\xe2\x80\xa9|\xc2\x85 {
92+
// U+2028 LINE SEPARATOR, U+2029 PARAGRAPH SEPARATOR and U+0085 NEXT
93+
// LINE: line breaks to look at, so whitespace that also ends a `//`
94+
// comment (below). Qt's editor types U+2028 for Shift+Enter, and it
95+
// shows as a new line (BelfrySCAD #949). It does NOT count as a line:
96+
// the editor numbers its lines by block and a U+2028 sits inside one,
97+
// so every line and column after it still matches the editor's.
98+
// OpenSCAD's lexer rejects all three.
99+
}
100+
/* A line comment ends at a newline or at any separator above. */
101+
"//"([^\n\xe2\xc2]|\xe2[^\x80\n]|\xe2\x80[^\xa8\xa9\n]|\xc2[^\x85\n])* { /* skip line comment */ }
92102
"/*"([^*]|\*+[^*/])*\*+"/" { /* skip block comment */ }
93103

94104
{IDENT} {

‎tests/test_lexical.cpp‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,22 @@ TEST(LexicalWhitespace, NoBreakSpaceByteOrderMarkAndLatin1A0) {
483483
}
484484
}
485485

486+
// U+2028/U+2029/U+0085 are whitespace that ends a line comment, without
487+
// counting as a line (BelfrySCAD's editor numbers lines by block, and a
488+
// U+2028 sits inside one).
489+
TEST(LexicalWhitespace, UnicodeLineSeparatorsEndACommentButNotALine) {
490+
for (const char* sep : {"\xe2\x80\xa8", "\xe2\x80\xa9", "\xc2\x85"}) {
491+
const std::string src = std::string("a = 1;") + sep + "b = 2; // note" + sep + "c = 3;\nd = 4;";
492+
auto ast = parseSrc(src);
493+
ASSERT_EQ(ast.size(), 4u) << "the comment swallowed c = 3";
494+
EXPECT_EQ(ast[2]->position().line, 1);
495+
EXPECT_EQ(size_t(ast[2]->position().start_offset), src.find("c = 3"));
496+
EXPECT_EQ(ast[3]->position().line, 2);
497+
}
498+
// Other multi-byte characters stay inside the comment.
499+
EXPECT_EQ(parseSrc("a = 1; // \xe2\x80\x94 \xc2\xa0 \xe2\x82\xac c = 3;\nb = 2;").size(), 2u);
500+
}
501+
486502
// The message quotes a whole character, and names a byte that starts none,
487503
// so it is always valid UTF-8.
488504
TEST(LexicalWhitespace, UnexpectedCharacterMessageIsValidUtf8) {

0 commit comments

Comments
 (0)