- Omitting of (most) backslashes for
". - Altering the string with implicit new line injection at the end of the line.
- Sting interpolation (trivial in terms of consistency and functionality).
-
To omit escaping the quote character, the delimiter characters for the multi-line string literal will be tripled quotes
""", also similar to other programming languages. -
When a standard string literal contains at least 5 quotes, then the usage of a multi-line string literal can be shorter.
"<a href=\"\(url)\" id=\"link\(i)\" class=\"link\">" // With escapes """<a href="\(url)" id="link\(i)" class="link">""" // With tripled literals
-
To fully support this feature, we need to compromise the design for simplicity and intuitivity.
i. This feature needs precision for leading and trailing whitespaces.
j. Alternatively one would need a way to disable new line injection to also support code formatting.
-
Single line version
"""abc"""is trivial and already was shown above. -
The multi-line version comes with a few compromises for simplicity of rules:
""" // DS (delimiter start)
foo // s0
foo // s1
foo // s2
""" // DE (delimiter end)- The string content is always written between the lines
DSandDE(delimiter lines).
That means that the following snippets are not allowed:
// Banned option 1:
"""foo
foo
"""
// Banned option 2:
"""foo
foo"""
// Banned option 3:
"""
foo
foo"""
// Banned option 4:
"""\n\
foo
foo
"""
// Banned option 5:
"""\
foo
foo
""" -
To not to go the continuation quotes path, the left (or leading) precision is handled by the closing delimiter (1. compromise). The closing delimiter is also responsible for the indent algorithm, which will calculate the stripping prefix in line
DEand apply stripping to liness0tosn. -
Right (or trailing) precision of each line from
s0tosn(notice n equals 2 in the example above) is handled by a backslash character (2. compromise). -
The right precision comes at a price of losing the implicit new line injection, however this was also a requested feature (3. compromise). That means that the backslash can serve two jobs simultaneously.
-
New line injection happens only on lines
s0tos(n - 1)(4. and last compromise of the design). The last linesn(ors2above) does not inject a new line into the final string. This implies that in this line a backslash character handles only right precision, or one could say it's reduced for one functionality.
The following multi-line string literal
print("""
foo
foo
""")will produce the string "foo\nfoo" and only print:
foo
fooBecause whitespace is important to these examples, it is explicitly indicated: · is a space, ⇥ is a tab, and ↵ is a newline.
// Nothing to strip in this example (no indent).
let str_1 = """↵
foo↵
"""
// No right precision (no backslash), but presence of whitespace
// characters will emit a warning.
let str_2 = """↵
foo··↵
"""Warning:
warning: line # includes trailing whitespace characters in multi-line string literal without the precision `\` character at the end
foo··↵
~~
Fix-it: Insert "\n\" after the last space character (or only `\` if it's the `sn` line)
More examples:
// Simmilar to `str_2`
let str_3 = """↵
foo····↵
"""
// Line `DE` of the closing delimiter calculates the indent prefix
// `··` and strips it from `s0` (left precision).
let str_4 = """↵
··foo↵
··"""
// Line `DE` of the closing delimiter calculates the indent prefix
// `····` and strips it from `s0` (left precision).
//
// No right precision (no backslash), but presence of whitespace
// characters will emit a warning.
let str_5 = """↵
····foo··↵
····"""
// Line `DE` of the closing delimiter calculates the indent prefix
// `⇥ ⇥ ` and strips it from `s0` (left precision).
//
// Right precision is applied (backslash). In this case the literal
// contains only a single line of content, which happens to be
// also the last line before `DE` -> backslash only serves precision.
let str_6 = """↵
⇥ ⇥ foo\↵
⇥ ⇥ """
// Line `DE` of the closing delimiter calculates the indent prefix
// `·⇥ ·⇥ ` and strips it from `s0` (left precision).
//
// No right precision (no backslash), but presence of whitespace
// characters will emit a warning.
let str_7 = """↵
·⇥ ·⇥ foo··↵
·⇥ ·⇥ """
let string_1 = "foo"
str_1 == string_1 // => true
str_2 == string_1 // => false
str_3 == string_1 // => false
str_4 == string_1 // => true
str_5 == string_1 // => false
str_6 == string_1 // => true
str_7 == string_1 // => false
A wrong multi-line string literal, which compiles but emits a warning with a fix-it:
let str_8 = """↵
··foo↵
····"""
str_8 == string_1 // => trueWarning:
warning: missing indentation in multi-line string literal
··foo
^
Fix-it: Insert "··"
-
The stripping algorithm calculates the prefix indent from the closing delimiter line
DEand tries to strip it in liness0tosnif possible, otherwise each line, which could not be handled correctly will emit an individual warning and afix-it. -
To align precision of a multi-line string literal with the standard string literal, we have to check every content line for trailing whitespace characters. If we found any, but there is no
\at the end, a warning will be emitted and afix-itwill be provided to add\n\after the last whitespace character for explicit precision, or a\if the warning happens in the last content linesn. This automatically forces the developer to either remove any unwanted trailing whitespace characters or be explicit about the trailing precision.
An example which will raise such a warning:
"""↵
foo··↵
"""To fix the issue there are two options:
- Use the
fix-it:
"""↵
foo··\↵
"""
- Remove the whitespace characters manually:
"""↵
foo↵
"""The examples we've used so far had only a single content line, so we couldn't showcase this behavior yet. New lines are only injected into a multi-line string if it has at least two content lines.
let str_9 = """↵
····foo↵
····bar↵
····"""
let str_10 = """↵
····foo↵
····bar↵
····baz↵
····"""
let string_2 = "foo\nbar"
let string_3 = "foo\nbar\nbaz"
str_9 == string_2 // => true
str_10 == string_3 // => trueTo disable new line injection one would need to use the backslash for right precision.
let str_11 = """↵
····foo\↵
····bar↵
····"""
let str_12 = """↵
····foo\↵
····bar\↵
····baz↵
····"""
str_11 == string_2 // => false
str_12 == string_3 // => false
str_11 == "foorbar" // => true
str_12 == "foobarbaz" // => trueRemember that the last content line sn does not automatically inject a new line into the final string!
The standard string literal like "foo" only contains its string content from the starting delimiter to the closing delimiter. The discussion on the mailing list suggests that the multi-line string literal should also go that route and not inject a new line for the last content line sn. str_9 is a good example for that behavior.
Now if one would want a new line at the end of the string, there are a few options to achieve this:
// Natural way:
let str_13 = """↵
····foo↵
····bar↵
····↵
····"""
// Remember the last content line does not inject a `\n` character by default
// so there is no need for `\n\` here (but it's possible as well)!
let str_14 = """↵
····foo↵
····bar\n↵
····"""
let string_4 = "foo\nbar\n"
str_13 == string_4 // => true
str_14 == string_4 // => trueAt first glance the behavior in str_13 seems odd and inconsistent, however it actually mimics perfectly the natural way of writing text paragraphs and is consistent to the standard string literal, which represents only its content between the delimiters.
[here is a blank line]↵
text text text text text↵
text text text text text↵
[here is a blank line]
This is easily expressed with the literal model explained above:
let myParagraph = """↵
····↵
····text text text text text↵
····text text text text text↵
····↵
····"""