[swift-evolution] [Review] SE-0168: Multi-Line String Literals

Adrian Zubarev adrian.zubarev at devandartist.com
Thu Apr 13 11:27:08 CDT 2017


Btw. shouldn’t we let the core return the current proposal officially for revision, or otherwise it will need to stay ‘long enough’ in it’s revisioned pitch state before we can kick off a second review process!

Just saying.



-- 
Adrian Zubarev
Sent with Airmail

Am 13. April 2017 um 18:21:29, Adrian Zubarev (adrian.zubarev at devandartist.com) schrieb:

I’ve update my gist to reflect that now: https://gist.github.com/DevAndArtist/dae76d4e3d4e49b1fab22ef7e86a87a9

Simple ‘multi-line string literal’ model

Core features:

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).
Consequences of #1:

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
Consequences of #2:

To fully support this feature, we need to compromise the design for simplicity and intuitivity.

This feature needs precision for leading and trailing whitespaces.

Alternatively one would need a way to disable new line injection to also support code formatting.

Two ways of writing a multi-line string literal:

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 DS and DE (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"""   
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 DE and apply stripping to lines s0 to sn.

Right (or trailing) precision of each line from s0 to sn (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 s0 to s(n - 1) (4. and last compromise of the design). The last line sn (or s2 above) 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
foo
Important:

Because whitespace is important to these examples, it is explicitly indicated: · is a space, ⇥ is a tab, and ↵ is a newline.

Leading/trailing precision and indent (1. and 2. compromise):

// 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   // => true
Warning:

warning: missing indentation in multi-line string literal
  ··foo
    ^   
  Fix-it: Insert "··"
The stripping algorithm calculates the prefix indent from the closing delimiter line DE and tries to strip it in lines s0 to sn if possible, otherwise each line, which could not be handled correctly will emit an individual warning and a fix-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 a fix-it will be provided to add \n\ after the last whitespace character for explicit precision, or a \ if the warning happens in the last content line sn. 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↵
"""
Disabling new line injection (3. compromise):

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 // => true
To 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" // => true
Remember that the last content line sn does not automatically inject a new line into the final string!

New line injection except for the last line (4. compromise):

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 // => true
At 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↵
····↵
····"""


-- 
Adrian Zubarev
Sent with Airmail

Am 13. April 2017 um 16:39:05, Ricardo Parada (rparada at mac.com) schrieb:

That would be good, I think because it would force everyone to be precise in regards to trailing whitespace.  And I don't see that as a bad thing.


On Apr 13, 2017, at 9:54 AM, Adrian Zubarev via swift-evolution <swift-evolution at swift.org> wrote:

I was really confused by your last reply. Actually I’ve got a better idea for a fix-it. :-)

let str_8 = """↵    
····foo··········↵
····"""
warning: line # includes trailing space characters in multi-line string literal
  ····foo··········
         ~~~~~~~~~~    
  Fix-it: Insert "\n\" (after these space characters)
The fix-it will inset \n\ after all your space characters, so the developer is kinda forced to strip them manually to ····foo or let the IDE add \n\ if he really needs that precision.

That would work. :)




-- 
Adrian Zubarev
Sent with Airmail

Am 13. April 2017 um 15:46:52, John Holdsworth (mac at johnholdsworth.com) schrieb:

\n\
would work

On 13 Apr 2017, at 14:44, John Holdsworth <mac at johnholdsworth.com> wrote:


I’ve never understood how you mean "explicit backslash". backslash has specific roles
and at the moment it is assigned to meaning standard escapes or "don’t include this next
newline in the literal". I can’t see how it could be reassigned to mean “include whitespace”
without loosing the option to concatenate lines.

fix-its are beyond my pay grade so that’ll have to wait until Apple looks at the implementation!


On 13 Apr 2017, at 14:32, Adrian Zubarev <adrian.zubarev at devandartist.com> wrote:

A warning that will popup when you included any trailing spaces in a ‘content line’ without the explicit backslash could also provide a fix-it to the user to remove these spaces. However if you can emit that warning and calculate the spaces to remove, than the compiler should be able to swallow these characters by default right?





_______________________________________________
swift-evolution mailing list
swift-evolution at swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170413/bb1afde5/attachment.html>


More information about the swift-evolution mailing list