Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multi line text interpolation support #131

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
91 changes: 90 additions & 1 deletion Sources/Splash/Grammar/SwiftGrammar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ private extension SwiftGrammar {
return false
}

return !segment.isWithinStringInterpolation
return !segment.isWithinMultiLineStringInterpolation
}
}

Expand Down Expand Up @@ -605,6 +605,95 @@ private extension Segment {

return markerCounts.start != markerCounts.end
}

var isWithinMultiLineStringInterpolation: Bool {
let delimiter = "\\("

if tokens.current == delimiter || tokens.previous == delimiter {
return true
}


/*
Loop back through tokens (not just same line)
counting closing ) and opening ( and to see if a \\(
can be found before the start of the string.

if the number of closed braces is < the number of opening braces + 1
then we are inside a multi line string interpolation.
*/

var unbalancedClosedParenthesis = 0

/* Note the order of `(` and `)` matters.

for example \(
this is an interpolation
)(but non of this is)
*/

for token in tokens.all.lazy.reversed() {
var previousChar: Character? = nil
// only need to search to the start of this multi line string.
// multi line string must have new line after """ so will always be a suffix of a token.
if token.hasSuffix("\"\"\"") {
// We are before the first interpolation.
return false
}

// The order of ( and ) is important>
// () does note close the interpolation
// )( does close the interpolation
for char in token.lazy.reversed() {
// we consume unbalancedClosedParenthesis
// only once we are sure we are not dealing with the start of
// an interpolation
if previousChar == "(" {
if char != "\\" {
if unbalancedClosedParenthesis > 0 {
unbalancedClosedParenthesis -= 1
}
// we do not want to put unbalancedClosedParenthesis
// into negative as the order of ( and ) is very important.
} else {
// keeping ( in the previousChar
// so that if the token is \\( it still ends up consuming the open brane.
continue
}
}

previousChar = char

switch char {
case ")":
unbalancedClosedParenthesis += 1
default:
previousChar = char
}
}



if token.hasPrefix(delimiter) {
// there is a closing parenthesis that closes the scope
if unbalancedClosedParenthesis > 0 {
return false
}
// all the closing parenthesis have matching opening parenthesis.
return true
}

// If the last char in the token was (
if previousChar == "(" {
if unbalancedClosedParenthesis > 0 {
unbalancedClosedParenthesis -= 1
}
}
}

// not inside a multi line string
return false
}

var isWithinStringInterpolation: Bool {
let delimiter = "\\("
Expand Down
72 changes: 72 additions & 0 deletions Tests/SplashTests/Tests/LiteralTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,78 @@ final class LiteralTests: SyntaxHighlighterTestCase {
.token("\"\"\"", .string)
])
}

func testMultiLineStringLiteralWithMultiLineInterpolated() {
let components = highlighter.highlight("""
let string = \"\"\"
Hello\\(
variable,
format: .value
)(not-interpolated)
\"\"\"
""")

XCTAssertEqual(components, [
.token("let", .keyword),
.whitespace(" "),
.plainText("string"),
.whitespace(" "),
.plainText("="),
.whitespace(" "),
.token("\"\"\"", .string),
.whitespace("\n"),
.token("Hello", .string),
.plainText("\\("),
.whitespace("\n "),
.plainText("variable,"),
.whitespace("\n "),
.plainText("format:"),
.whitespace(" "),
.plainText("."),
.token("value", Splash.TokenType.dotAccess),
.whitespace("\n"),
.plainText(")"),
.token("(not-interpolated)", .string),
.whitespace("\n"),
.token("\"\"\"", .string)
])
}

func testMultiLineStringLiteralWithInterpolatedString() {
let components = highlighter.highlight("""
let string = \"\"\"
Hello \\(
value ? "Bob"
) Welcome.
\"\"\"
""")

XCTAssertEqual(components, [
.token("let", .keyword),
.whitespace(" "),
.plainText("string"),
.whitespace(" "),
.plainText("="),
.whitespace(" "),
.token("\"\"\"", .string),
.whitespace("\n"),
.token("Hello", .string),
.whitespace(" "),
.plainText("\\("),
.whitespace("\n "),
.plainText("value"),
.whitespace(" "),
.plainText("?"),
.whitespace(" "),
.token("\"Bob\"", .string),
.whitespace("\n"),
.plainText(")"),
.whitespace(" "),
.token("Welcome.", .string),
.whitespace("\n"),
.token("\"\"\"", .string)
])
}

func testSingleLineRawStringLiteral() {
let components = highlighter.highlight("""
Expand Down