Skip to content
This repository has been archived by the owner on May 2, 2023. It is now read-only.

Support multiline string literals #58

Merged
merged 1 commit into from
Jan 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion Sources/SwiftPowerAssertCore/Formatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Formatter {
case plain
case token
case string
case multilineString
case stringEscape
case newline
case indent
Expand All @@ -33,6 +34,8 @@ class Formatter {
var tokens = [Token]()
var storage = ""
var input: String
var openingDelimiterCount = 0
var closingDelimiterCount = 0

init(input: String) {
self.input = input
Expand All @@ -41,11 +44,20 @@ class Formatter {

func tokenize(source: String) -> [Token] {
let state = State(input: source)
for character in state.input {
let input = state.input
for (index, character) in input.enumerated() {
switch state.mode {
case .plain:
switch character {
case "\"":
if index + 2 < input.count {
let startIndex = input.index(input.startIndex, offsetBy: index)
if input[startIndex...input.index(startIndex, offsetBy: 2)] == "\"\"\"" {
state.mode = .multilineString
state.openingDelimiterCount = 1
break
}
}
state.mode = .string
case "\n":
state.tokens.append(Token(type: .newline, value: String(character)))
Expand Down Expand Up @@ -93,6 +105,27 @@ class Formatter {
default:
state.storage += String(character)
}
case .multilineString:
switch character {
case "\"":
if state.openingDelimiterCount == 3 {
if state.closingDelimiterCount == 2 {
state.tokens.append(Token(type: .string, value: normalizeMultilineLiteral(state.storage)))
state.mode = .plain
state.storage = ""
state.openingDelimiterCount = 0
state.closingDelimiterCount = 0
} else {
state.closingDelimiterCount += 1
}
} else {
state.openingDelimiterCount += 1
}
case "\\":
state.mode = .stringEscape
default:
state.storage += String(character)
}
case .stringEscape:
switch character {
case "\"", "\\", "'", "t", "n", "r":
Expand Down Expand Up @@ -155,6 +188,26 @@ class Formatter {
return state.tokens
}

func normalizeMultilineLiteral(_ literal: String) -> String {
var lines = [String]()
var isFirstLine = true
var indentCount = 0
literal.trimmingCharacters(in: .newlines).enumerateLines { (line, stop) in
if isFirstLine {
for character in line {
if character == " " {
indentCount += 1
} else {
break
}
}
isFirstLine = false
}
lines.append(String(line.suffix(line.count - indentCount)))
}
return lines.dropLast().joined(separator: "\\n")
}

func format(tokens: [Token]) -> String {
var formatted = ""
for token in tokens {
Expand Down
86 changes: 50 additions & 36 deletions Sources/SwiftPowerAssertCore/Transformer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -514,18 +514,25 @@ class Transformer {
private func stringLiteralExpression(_ child: Expression, _ parent: Expression) -> String {
var source = sourceFile[child.range]
let rest = restOfExpression(child, parent)
var previous = ""
for character in rest {
switch character {
case "\"" where previous != "\\":
source += String(character)
return source
default:
previous = String(character)
source += previous
if rest.hasPrefix("\"\"") {
// Multiline String Literal
if let range = rest.range(of: "\"\"\"") {
return source + rest[..<range.upperBound]
}
} else {
var previous = ""
for character in rest {
switch character {
case "\"" where previous != "\\":
source += String(character)
return source
default:
previous = String(character)
source += previous
}
}
}
return source
return source + rest
}

func findFirst(_ expression: Expression, where closure: (_ expression: Expression) -> Bool) -> Expression? {
Expand Down Expand Up @@ -630,32 +637,39 @@ class Transformer {
}

var result = source
for character in rest {
switch mode {
case .plain:
switch character {
case ".", ",", " ", "\"", "\t", "\n", "(", "[", "{", ")", "]", "}", ":", ";", "?":
return result
default:
result += String(character)
}
case .string:
switch character {
case "\"":
result += String(character)
return result
case "\\":
mode = .stringEscape
default:
result += String(character)
}
case .stringEscape:
switch character {
case "\"", "\\", "'", "t", "n", "r":
mode = .string
result += "\\" + String(character)
default:
fatalError("unexpected '\(character)' in string escape")
if rest.hasPrefix("\"\"") {
// Multiline String Literal
if let range = rest.range(of: "\"\"\"") {
return source + rest[..<range.upperBound]
}
} else {
for character in rest {
switch mode {
case .plain:
switch character {
case ".", ",", " ", "\"", "\t", "\n", "(", "[", "{", ")", "]", "}", ":", ";", "?":
return result
default:
result += String(character)
}
case .string:
switch character {
case "\"":
result += String(character)
return result
case "\\":
mode = .stringEscape
default:
result += String(character)
}
case .stringEscape:
switch character {
case "\"", "\\", "'", "t", "n", "r":
mode = .string
result += "\\" + String(character)
default:
fatalError("unexpected '\(character)' in string escape")
}
}
}
}
Expand Down
38 changes: 38 additions & 0 deletions Tests/SwiftPowerAssertTests/AssertTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1674,4 +1674,42 @@ class AssertTests: XCTestCase {
let result = TestRunner().run(source: source)
XCTAssertEqual(expected, result)
}

func testMultilineStringLiterals() throws {
let source = """
import XCTest

class Tests: XCTestCase {
func testMethod() {
let multilineLiteral = \"\"\"
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
\"\"\"
assert(multilineLiteral != \"\"\"
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
\"\"\")
assert(multilineLiteral != multilineLiteral)
}
}

"""

let expected = """
assert(multilineLiteral != "Lorem ipsum dolor sit amet, consectetur adipiscing elit,\\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
| | |
| | "Lorem ipsum dolor sit amet, consectetur adipiscing elit,\\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
| false
"Lorem ipsum dolor sit amet, consectetur adipiscing elit,\\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
assert(multilineLiteral != multilineLiteral)
| | |
| | "Lorem ipsum dolor sit amet, consectetur adipiscing elit,\\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
| false
"Lorem ipsum dolor sit amet, consectetur adipiscing elit,\\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua."

"""

let result = TestRunner().run(source: source)
XCTAssertEqual(expected, result)
}
}