99//
1010//===----------------------------------------------------------------------===//
1111
12- // TODO: mock up multi-line soon
13-
1412struct Delimiter : Hashable {
1513 let kind : Kind
1614 let poundCount : Int
@@ -28,13 +26,13 @@ struct Delimiter: Hashable {
2826 kind. closing + String( repeating: " # " , count: poundCount)
2927 }
3028
31- /// The default set of syntax options that the delimiter indicates .
32- var defaultSyntaxOptions : SyntaxOptions {
29+ /// Whether or not multi-line mode is permitted .
30+ var allowsMultiline : Bool {
3331 switch kind {
34- case . forwardSlash, . reSingleQuote :
35- return . traditional
36- case . experimental, . rxSingleQuote:
37- return . experimental
32+ case . forwardSlash:
33+ return poundCount > 0
34+ case . experimental, . reSingleQuote , . rxSingleQuote:
35+ return false
3836 }
3937 }
4038}
@@ -76,6 +74,7 @@ struct DelimiterLexError: Error, CustomStringConvertible {
7674 case invalidUTF8 // TODO: better range reporting
7775 case unknownDelimiter
7876 case unprintableASCII
77+ case multilineClosingNotOnNewline
7978 }
8079
8180 var kind : Kind
@@ -94,6 +93,7 @@ struct DelimiterLexError: Error, CustomStringConvertible {
9493 case . invalidUTF8: return " invalid UTF-8 found in source file "
9594 case . unknownDelimiter: return " unknown regex literal delimiter "
9695 case . unprintableASCII: return " unprintable ASCII character found in source file "
96+ case . multilineClosingNotOnNewline: return " closing delimiter must appear on new line "
9797 }
9898 }
9999}
@@ -103,6 +103,9 @@ fileprivate struct DelimiterLexer {
103103 var cursor : UnsafeRawPointer
104104 let end : UnsafeRawPointer
105105
106+ var firstNewline : UnsafeRawPointer ?
107+ var isMultiline : Bool { firstNewline != nil }
108+
106109 init ( start: UnsafeRawPointer , end: UnsafeRawPointer ) {
107110 precondition ( start <= end)
108111 self . start = start
@@ -262,12 +265,23 @@ fileprivate struct DelimiterLexer {
262265 let contentsEnd = cursor
263266 guard tryEat ( delimiter. closing. utf8) else { return nil }
264267
265- // Form a string from the contents and make sure it's valid UTF-8.
266268 let count = contentsEnd - contentsStart
267269 let contents = UnsafeRawBufferPointer (
268270 start: contentsStart, count: count)
269- let s = String ( decoding: contents, as: UTF8 . self)
270271
272+ // In multi-line mode, we must be on a new line. So scan backwards and make
273+ // sure we only have whitespace until the newline.
274+ if isMultiline {
275+ let idx = contents. lastIndex (
276+ where: { $0 == ascii ( " \n " ) || $0 == ascii ( " \r " ) } ) ! + 1
277+ guard contents [ idx... ] . all ( { $0 == ascii ( " " ) || $0 == ascii ( " \t " ) } )
278+ else {
279+ throw DelimiterLexError ( . multilineClosingNotOnNewline, resumeAt: cursor)
280+ }
281+ }
282+
283+ // Form a string from the contents and make sure it's valid UTF-8.
284+ let s = String ( decoding: contents, as: UTF8 . self)
271285 guard s. utf8. elementsEqual ( contents) else {
272286 throw DelimiterLexError ( . invalidUTF8, resumeAt: cursor)
273287 }
@@ -278,7 +292,10 @@ fileprivate struct DelimiterLexer {
278292 /// the end of the buffer is reached.
279293 mutating func advance( escaped: Bool = false ) throws {
280294 guard let next = load ( ) else {
281- throw DelimiterLexError ( . unterminated, resumeAt: cursor)
295+ // We've hit the end of the buffer. In multi-line mode, we don't want to
296+ // skip over what is likely otherwise valid Swift code, so resume from the
297+ // first newline.
298+ throw DelimiterLexError ( . unterminated, resumeAt: firstNewline ?? cursor)
282299 }
283300 switch UnicodeScalar ( next) {
284301 case let next where !next. isASCII:
@@ -289,7 +306,10 @@ fileprivate struct DelimiterLexer {
289306 advanceCursor ( )
290307
291308 case " \n " , " \r " :
292- throw DelimiterLexError ( . unterminated, resumeAt: cursor)
309+ guard isMultiline else {
310+ throw DelimiterLexError ( . unterminated, resumeAt: cursor)
311+ }
312+ advanceCursor ( )
293313
294314 case " \0 " :
295315 // TODO: Warn to match the behavior of String literal lexer? Or should
@@ -301,8 +321,12 @@ fileprivate struct DelimiterLexer {
301321 advanceCursor ( )
302322 try advance ( escaped: true )
303323
304- case let next where !next. isPrintableASCII:
324+ case let next
325+ where !next. isPrintableASCII && !( isMultiline && next == " \t " ) :
305326 // Diagnose unprintable ASCII.
327+ // Note that tabs are allowed in multi-line literals.
328+ // TODO: This matches the string literal behavior, but should we allow
329+ // tabs for single-line regex literals too?
306330 // TODO: Ideally we would recover and continue to lex until the ending
307331 // delimiter.
308332 throw DelimiterLexError ( . unprintableASCII, resumeAt: cursor. successor ( ) )
@@ -349,6 +373,23 @@ fileprivate struct DelimiterLexer {
349373 throw DelimiterLexError ( . unknownDelimiter, resumeAt: cursor. successor ( ) )
350374 }
351375 let contentsStart = cursor
376+
377+ // If the delimiter allows multi-line, try skipping over any whitespace to a
378+ // newline character. If we can do that, we enter multi-line mode.
379+ if delimiter. allowsMultiline {
380+ while let next = load ( ) {
381+ switch next {
382+ case ascii ( " " ) , ascii ( " \t " ) :
383+ advanceCursor ( )
384+ continue
385+ case ascii ( " \n " ) , ascii ( " \r " ) :
386+ firstNewline = cursor
387+ default :
388+ break
389+ }
390+ break
391+ }
392+ }
352393 while true {
353394 // Check to see if we're at a character that looks like a delimiter, but
354395 // likely isn't. In such a case, we can attempt to skip over it.
0 commit comments