From 361cb403fdce259e87e00eb0840c2eeb70303caf Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 29 Nov 2017 21:01:44 -0800 Subject: [PATCH] RFC: Block String (#327) This RFC adds a new form of `StringValue`, the multi-line string, similar to that found in Coffeescript, Python, and Scala. A block string starts and ends with a triple-quote: ``` """This is a triple-quoted string and it can contain multiple lines""" ``` Block strings are useful for typing literal bodies of text where new lines should be interpreted literally. In fact, the only escape sequence used is `\"""` and `\` is otherwise allowed unescaped. This is beneficial when writing documentation within strings which may reference the back-slash often: ``` """ In a block string \n and C:\ are unescaped. """ ``` The primary value of block strings are to write long-form input directly in query text, in tools like GraphiQL, and as a prerequisite to another pending RFC to allow docstring style documentation in the Schema Definition Language. --- spec/Appendix B -- Grammar Summary.md | 11 ++- spec/Section 2 -- Language.md | 97 ++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 10 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index d3b59f131..c6c2edaab 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -69,8 +69,8 @@ ExponentIndicator :: one of `e` `E` Sign :: one of + - StringValue :: - - `""` - - `"` StringCharacter+ `"` + - `"` StringCharacter* `"` + - `"""` BlockStringCharacter* `"""` StringCharacter :: - SourceCharacter but not `"` or \ or LineTerminator @@ -81,6 +81,13 @@ EscapedUnicode :: /[0-9A-Fa-f]{4}/ EscapedCharacter :: one of `"` \ `/` b f n r t +BlockStringCharacter :: + - SourceCharacter but not `"""` or `\"""` + - `\"""` + +Note: Block string values are interpretted to exclude blank initial and trailing +lines and uniform indentation with {BlockStringValue()}. + ## Query Document diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 05f4f145d..3b6da6d97 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -694,8 +694,8 @@ The two keywords `true` and `false` represent the two boolean values. ### String Value StringValue :: - - `""` - - `"` StringCharacter+ `"` + - `"` StringCharacter* `"` + - `"""` BlockStringCharacter* `"""` StringCharacter :: - SourceCharacter but not `"` or \ or LineTerminator @@ -706,24 +706,61 @@ EscapedUnicode :: /[0-9A-Fa-f]{4}/ EscapedCharacter :: one of `"` \ `/` b f n r t +BlockStringCharacter :: + - SourceCharacter but not `"""` or `\"""` + - `\"""` + Strings are sequences of characters wrapped in double-quotes (`"`). (ex. `"Hello World"`). White space and other otherwise-ignored characters are significant within a string value. Note: Unicode characters are allowed within String value literals, however -GraphQL source must not contain some ASCII control characters so escape +{SourceCharacter} must not contain some ASCII control characters so escape sequences must be used to represent these characters. -**Semantics** +**Block Strings** + +Block strings are sequences of characters wrapped in triple-quotes (`"""`). +White space, line terminators, quote, and backslash characters may all be +used unescaped to enable verbatim text. Characters must all be valid +{SourceCharacter}. + +Since block strings represent freeform text often used in indented +positions, the string value semantics of a block string excludes uniform +indentation and blank initial and trailing lines via {BlockStringValue()}. + +For example, the following operation containing a block string: + +```graphql +mutation { + sendEmail(message: """ + Hello, + World! + + Yours, + GraphQL. + """) +} +``` + +Is identical to the standard quoted string: -StringValue :: `""` +```graphql +mutation { + sendEmail(message: "Hello,\n World!\n\nYours,\n GraphQL.") +} +``` - * Return an empty Unicode character sequence. +Note: If non-printable ASCII characters are needed in a string value, a standard +quoted string with appropriate escape sequences must be used instead of a +block string. -StringValue :: `"` StringCharacter+ `"` +**Semantics** + +StringValue :: `"` StringCharacter* `"` * Return the Unicode character sequence of all {StringCharacter} - Unicode character values. + Unicode character values (which may be an empty sequence). StringCharacter :: SourceCharacter but not `"` or \ or LineTerminator @@ -749,6 +786,50 @@ StringCharacter :: \ EscapedCharacter | `r` | U+000D | carriage return | | `t` | U+0009 | horizontal tab | +StringValue :: `"""` BlockStringCharacter* `"""` + + * Let {rawValue} be the Unicode character sequence of all + {BlockStringCharacter} Unicode character values (which may be an empty + sequence). + * Return the result of {BlockStringValue(rawValue)}. + +BlockStringCharacter :: SourceCharacter but not `"""` or `\"""` + + * Return the character value of {SourceCharacter}. + +BlockStringCharacter :: `\"""` + + * Return the character sequence `"""`. + +BlockStringValue(rawValue): + + * Let {lines} be the result of splitting {rawValue} by {LineTerminator}. + * Let {commonIndent} be {null}. + * For each {line} in {lines}: + * If {line} is the first item in {lines}, continue to the next line. + * Let {length} be the number of characters in {line}. + * Let {indent} be the number of leading consecutive {WhiteSpace} characters + in {line}. + * If {indent} is less than {length}: + * If {commonIndent} is {null} or {indent} is less than {commonIndent}: + * Let {commonIndent} be {indent}. + * If {commonIndent} is not {null}: + * For each {line} in {lines}: + * If {line} is the first item in {lines}, continue to the next line. + * Remove {commonIndent} characters from the beginning of {line}. + * While the first item {line} in {lines} contains only {WhiteSpace}: + * Remove the first item from {lines}. + * While the last item {line} in {lines} contains only {WhiteSpace}: + * Remove the last item from {lines}. + * Let {formatted} be the empty character sequence. + * For each {line} in {lines}: + * If {line} is the first item in {lines}: + * Append {formatted} with {line}. + * Otherwise: + * Append {formatted} with a line feed character (U+000A). + * Append {formatted} with {line}. + * Return {formatted}. + ### Null Value