From ba72e6123d3b396e27c7079aaaa35f29160f44d0 Mon Sep 17 00:00:00 2001 From: ike709 Date: Tue, 26 Nov 2024 20:05:36 -0600 Subject: [PATCH] Support `@(XYZ)` raw strings --- .../Expression/String/raw2.dm | 0 .../DMPreprocessor/DMPreprocessorLexer.cs | 70 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) rename Content.Tests/DMProject/{Broken Tests => Tests}/Expression/String/raw2.dm (100%) diff --git a/Content.Tests/DMProject/Broken Tests/Expression/String/raw2.dm b/Content.Tests/DMProject/Tests/Expression/String/raw2.dm similarity index 100% rename from Content.Tests/DMProject/Broken Tests/Expression/String/raw2.dm rename to Content.Tests/DMProject/Tests/Expression/String/raw2.dm diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs index 83a3a3a403..154b9cb3b1 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs @@ -301,12 +301,31 @@ public Token NextToken(bool ignoreWhitespace = false) { } case '@': { //Raw string char delimiter = Advance(); + var startLoc = CurrentLocation(); + + // @(XYZ) where XYZ is the delimiter + string complexDelimiter = string.Empty; + if (delimiter == '(') { + Advance(); + while (GetCurrent() != ')') { + if (AtEndOfSource()) { + _compiler.Emit(WarningCode.BadExpression, startLoc, + "Unterminated string delimiter"); + break; + } + + complexDelimiter += GetCurrent(); + Advance(); + } + } TokenTextBuilder.Clear(); TokenTextBuilder.Append('@'); TokenTextBuilder.Append(delimiter); + bool isComplex = complexDelimiter != string.Empty; bool isLong = false; + c = Advance(); if (delimiter == '{') { TokenTextBuilder.Append(c); @@ -314,7 +333,33 @@ public Token NextToken(bool ignoreWhitespace = false) { if (c == '"') isLong = true; } - if (isLong) { + if (isComplex) { + TokenTextBuilder.Append(complexDelimiter); + TokenTextBuilder.Append(')'); + + // Ignore a newline immediately after @(complexDelimiter) + if (c == '\r') c = Advance(); + if (c == '\n') c = Advance(); + + var delimIdx = 0; + do { + TokenTextBuilder.Append(c); + if (GetCurrent() == complexDelimiter[delimIdx]) delimIdx++; + if (delimIdx + 1 == complexDelimiter.Length) { + TokenTextBuilder.Remove(TokenTextBuilder.Length - delimIdx, complexDelimiter.Length - 1); + break; + } + + c = Advance(); + } while (!AtEndOfSource()); + + if (AtEndOfSource()) { + _compiler.Emit(WarningCode.BadExpression, startLoc, + "Unterminated string delimiter"); + } + + TokenTextBuilder.Append(complexDelimiter); + } else if (isLong) { bool nextCharCanTerm = false; Advance(); @@ -335,6 +380,11 @@ public Token NextToken(bool ignoreWhitespace = false) { if (c == '"') nextCharCanTerm = true; } while (!AtEndOfSource()); + + if (AtEndOfSource()) { + _compiler.Emit(WarningCode.BadExpression, startLoc, + "Unterminated string delimiter"); + } } else { while (c != delimiter && !AtLineEnd() && !AtEndOfSource()) { TokenTextBuilder.Append(c); @@ -342,14 +392,24 @@ public Token NextToken(bool ignoreWhitespace = false) { } } - TokenTextBuilder.Append(c); + if (isComplex) Advance(); + else TokenTextBuilder.Append(c); + if (!HandleLineEnd()) Advance(); string text = TokenTextBuilder.ToString(); string value; - if (isLong) { + if (isComplex) { + // Complex strings need to strip @(complexDelimiter) and a potential final newline. Newline after @(complexDelimiter) is already handled + var trimEnd = complexDelimiter.Length; + if (TokenTextBuilder[^(complexDelimiter.Length + 1)] == '\n') trimEnd += 1; + if (TokenTextBuilder[^(complexDelimiter.Length + 2)] == '\r') trimEnd += 1; + var trimStart = 3 + complexDelimiter.Length; + value = TokenTextBuilder.ToString(trimStart, TokenTextBuilder.Length - (trimStart + trimEnd)); + } + else if (isLong) { // Long strings ignore a newline immediately after the @{" and before the "} if (TokenTextBuilder[3] == '\n') TokenTextBuilder.Remove(3, 1); @@ -639,6 +699,10 @@ private char GetCurrent() { return _current; } + private Location CurrentLocation() { + return new Location(File, _previousLine, _previousColumn, _isDMStandard); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private char Advance() { int value = _source.Read();