From 6951d3260d0ac21e50f7bd705ca367b91b2c2b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sta=C5=9B=20Ma=C5=82olepszy?= Date: Mon, 15 Oct 2018 17:36:55 +0200 Subject: [PATCH 1/5] Treat backslash as normal char in TextElements --- spec/fluent.ebnf | 34 +++--- syntax/grammar.mjs | 52 ++++----- test/fixtures/escaped_characters.ftl | 23 +++- test/fixtures/escaped_characters.json | 156 ++++++++++++++++++++++++-- 4 files changed, 209 insertions(+), 56 deletions(-) diff --git a/spec/fluent.ebnf b/spec/fluent.ebnf index ad07904..09169e5 100644 --- a/spec/fluent.ebnf +++ b/spec/fluent.ebnf @@ -57,7 +57,7 @@ InlineExpression ::= StringLiteral | inline_placeable /* Literals */ -StringLiteral ::= quote quoted_text_char* quote +StringLiteral ::= "\"" quoted_text_char* "\"" NumberLiteral ::= "-"? digit+ ("." digit+)? /* Inline Expressions */ @@ -84,22 +84,24 @@ Identifier ::= [a-zA-Z] [a-zA-Z0-9_-]* Function ::= [A-Z] [A-Z_?-]* /* Characters */ -backslash ::= "\\" -quote ::= "\"" -/* Any Unicode character from BMP excluding C0 control characters, space, - * surrogate blocks and non-characters (U+FFFE, U+FFFF). - * Cf. https://www.w3.org/TR/REC-xml/#NT-Char - */ -regular_char ::= [\\u{21}-\\u{D7FF}\\u{E000}-\\u{FFFD}\\u{10000}-\\u{10FFFF}] -text_char ::= blank_inline - | "\u0009" - | /\\u[0-9a-fA-F]{4}/ - | (backslash backslash) - | (backslash "{") - | (regular_char - "{" - backslash) +/* Any Unicode character excluding C0 control characters (but including tab), + * space, surrogate blocks and non-characters (U+FFFE, U+FFFF). + * Cf. https://www.w3.org/TR/REC-xml/#NT-Char */ +regular_char ::= [\\u{9}\\u{21}-\\u{D7FF}\\u{E000}-\\u{FFFD}] + | [\\u{10000}-\\u{10FFFF}] +/* The opening brace in text starts a placeable. */ +text_char ::= (regular_char - "{") + | "\u0020" +/* Indented text may not start with characters which mark its end. */ indented_char ::= text_char - "}" - "[" - "*" - "." -quoted_text_char ::= (text_char - quote) - | (backslash quote) +/* Backslash can be used to escape the double quote and the backslash itself. + * The literal opening brace { is allowed because StringLiterals may not have + * placeables. \uXXXX Unicode escape sequences are recognized, too. */ +quoted_text_char ::= (text_char - "\"" - "\\") + | /\\u[0-9a-fA-F]{4}/ + | "{" + | "\\\\" + | "\\\"" digit ::= [0-9] /* Whitespace */ diff --git a/syntax/grammar.mjs b/syntax/grammar.mjs index bfa4e50..406ade9 100644 --- a/syntax/grammar.mjs +++ b/syntax/grammar.mjs @@ -199,9 +199,9 @@ let InlineExpression = defer(() => /* Literals */ let StringLiteral = defer(() => sequence( - quote, + string("\""), repeat(quoted_text_char), - quote) + string("\"")) .map(element_at(1)) .map(join) .chain(into(FTL.StringLiteral))); @@ -373,48 +373,44 @@ let Function = /* ---------- */ /* Characters */ -let backslash = string("\\"); -let quote = string("\""); - -/* Any Unicode character from BMP excluding C0 control characters, space, - * surrogate blocks and non-characters (U+FFFE, U+FFFF). - * Cf. https://www.w3.org/TR/REC-xml/#NT-Char - */ +/* Any Unicode character excluding C0 control characters (but including tab), + * space, surrogate blocks and non-characters (U+FFFE, U+FFFF). + * Cf. https://www.w3.org/TR/REC-xml/#NT-Char */ let regular_char = - charset("\\u{21}-\\u{D7FF}\\u{E000}-\\u{FFFD}\\u{10000}-\\u{10FFFF}"); + either( + charset("\\u{9}\\u{21}-\\u{D7FF}\\u{E000}-\\u{FFFD}"), + charset("\\u{10000}-\\u{10FFFF}")); -let text_char = defer(() => +/* The opening brace in text starts a placeable. */ +let text_char = either( - blank_inline, - string("\u0009"), - regex(/\\u[0-9a-fA-F]{4}/), - sequence( - backslash, - backslash).map(join), - sequence( - backslash, - string("{")).map(join), and( - not(backslash), not(string("{")), - regular_char))); + regular_char), + string("\u0020")); -let indented_char = defer(() => +/* Indented text may not start with characters which mark its end. */ +let indented_char = and( not(string(".")), not(string("*")), not(string("[")), not(string("}")), - text_char)); + text_char); +/* Backslash can be used to escape the double quote and the backslash itself. + * The literal opening brace { is allowed because StringLiterals may not have + * placeables. \uXXXX Unicode escape sequences are recognized, too. */ let quoted_text_char = either( and( - not(quote), + not(string("\\")), + not(string("\"")), text_char), - sequence( - backslash, - quote).map(join)); + regex(/\\u[0-9a-fA-F]{4}/), + string("{"), + string("\\\\"), + string("\\\"")); let digit = charset("0-9"); diff --git a/test/fixtures/escaped_characters.ftl b/test/fixtures/escaped_characters.ftl index d3a5a07..3c64fce 100644 --- a/test/fixtures/escaped_characters.ftl +++ b/test/fixtures/escaped_characters.ftl @@ -1,9 +1,22 @@ -backslash = Value with \\ (an escaped backslash) -closing-brace = Value with \{ (a closing brace) -unicode-escape = \u0041 -escaped-unicode = \\u0041 +## Literal text +text-backslash-one = Value with \ a backslash +text-backslash-two = Value with \\ two backslashes +text-backslash-brace = Value with \{placeable} +text-backslash-u = \u0041 +text-backslash-backslash-u = \\u0041 -## String Expressions +## String literals quote-in-string = {"\""} backslash-in-string = {"\\"} +# ERROR Mismatched quote mismatched-quote = {"\\""} +# ERROR Unknown escape +unknown-escape = {"\x"} + +## Unicode escapes +string-unicode-sequence = {"\u0041"} +string-escaped-unicode = {"\\u0041"} + +## Literal braces +brace-open = An opening {"{"} brace. +brace-close = A closing } brace. diff --git a/test/fixtures/escaped_characters.json b/test/fixtures/escaped_characters.json index 5602775..6c26f82 100644 --- a/test/fixtures/escaped_characters.json +++ b/test/fixtures/escaped_characters.json @@ -1,18 +1,22 @@ { "type": "Resource", "body": [ + { + "type": "GroupComment", + "content": "Literal text" + }, { "type": "Message", "id": { "type": "Identifier", - "name": "backslash" + "name": "text-backslash-one" }, "value": { "type": "Pattern", "elements": [ { "type": "TextElement", - "value": "Value with \\\\ (an escaped backslash)" + "value": "Value with \\ a backslash" } ] }, @@ -23,14 +27,14 @@ "type": "Message", "id": { "type": "Identifier", - "name": "closing-brace" + "name": "text-backslash-two" }, "value": { "type": "Pattern", "elements": [ { "type": "TextElement", - "value": "Value with \\{ (a closing brace)" + "value": "Value with \\\\ two backslashes" } ] }, @@ -41,7 +45,35 @@ "type": "Message", "id": { "type": "Identifier", - "name": "unicode-escape" + "name": "text-backslash-brace" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value with \\" + }, + { + "type": "Placeable", + "expression": { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "placeable" + } + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "text-backslash-u" }, "value": { "type": "Pattern", @@ -59,7 +91,7 @@ "type": "Message", "id": { "type": "Identifier", - "name": "escaped-unicode" + "name": "text-backslash-backslash-u" }, "value": { "type": "Pattern", @@ -75,7 +107,7 @@ }, { "type": "GroupComment", - "content": "String Expressions" + "content": "String literals" }, { "type": "Message", @@ -119,10 +151,120 @@ "attributes": [], "comment": null }, + { + "type": "Comment", + "content": "ERROR Mismatched quote" + }, { "type": "Junk", "annotations": [], "content": "mismatched-quote = {\"\\\\\"\"}\n" + }, + { + "type": "Comment", + "content": "ERROR Unknown escape" + }, + { + "type": "Junk", + "annotations": [], + "content": "unknown-escape = {\"\\x\"}\n" + }, + { + "type": "GroupComment", + "content": "Unicode escapes" + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-unicode-sequence" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "StringLiteral", + "value": "\\u0041" + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "string-escaped-unicode" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "StringLiteral", + "value": "\\\\u0041" + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "GroupComment", + "content": "Literal braces" + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "brace-open" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "An opening " + }, + { + "type": "Placeable", + "expression": { + "type": "StringLiteral", + "value": "{" + } + }, + { + "type": "TextElement", + "value": " brace." + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "brace-close" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "A closing } brace." + } + ] + }, + "attributes": [], + "comment": null } ] } From 5ea35db4a9444dcc1eb773417e7ac28571e1c9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sta=C5=9B=20Ma=C5=82olepszy?= Date: Tue, 23 Oct 2018 17:47:44 +0200 Subject: [PATCH 2/5] Define the escapes lexically --- spec/fluent.ebnf | 29 ++++++++++++++------------ syntax/grammar.mjs | 52 ++++++++++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/spec/fluent.ebnf b/spec/fluent.ebnf index 09169e5..9712fad 100644 --- a/spec/fluent.ebnf +++ b/spec/fluent.ebnf @@ -57,7 +57,7 @@ InlineExpression ::= StringLiteral | inline_placeable /* Literals */ -StringLiteral ::= "\"" quoted_text_char* "\"" +StringLiteral ::= "\"" quoted_char* "\"" NumberLiteral ::= "-"? digit+ ("." digit+)? /* Inline Expressions */ @@ -85,23 +85,26 @@ Function ::= [A-Z] [A-Z_?-]* /* Characters */ /* Any Unicode character excluding C0 control characters (but including tab), - * space, surrogate blocks and non-characters (U+FFFE, U+FFFF). + * surrogate blocks and non-characters (U+FFFE, U+FFFF). * Cf. https://www.w3.org/TR/REC-xml/#NT-Char */ -regular_char ::= [\\u{9}\\u{21}-\\u{D7FF}\\u{E000}-\\u{FFFD}] +regular_char ::= [\\u{9}\\u{20}-\\u{D7FF}\\u{E000}-\\u{FFFD}] | [\\u{10000}-\\u{10FFFF}] /* The opening brace in text starts a placeable. */ -text_char ::= (regular_char - "{") - | "\u0020" +special_text_char ::= "{" +/* Double quote and backslash need to be escaped in string literals. */ +special_quoted_char ::= "\"" + | "\\" +text_char ::= regular_char - special_text_char /* Indented text may not start with characters which mark its end. */ indented_char ::= text_char - "}" - "[" - "*" - "." -/* Backslash can be used to escape the double quote and the backslash itself. - * The literal opening brace { is allowed because StringLiterals may not have - * placeables. \uXXXX Unicode escape sequences are recognized, too. */ -quoted_text_char ::= (text_char - "\"" - "\\") - | /\\u[0-9a-fA-F]{4}/ - | "{" - | "\\\\" - | "\\\"" +literal_escape ::= "\\" special_quoted_char +unicode_escape ::= "\\u" /[0-9a-fA-F]{4}/ +/* The literal opening brace { is allowed in string literals because they may + * not have placeables. */ +quoted_char ::= (text_char - special_quoted_char) + | special_text_char + | literal_escape + | unicode_escape digit ::= [0-9] /* Whitespace */ diff --git a/syntax/grammar.mjs b/syntax/grammar.mjs index 406ade9..c821499 100644 --- a/syntax/grammar.mjs +++ b/syntax/grammar.mjs @@ -200,7 +200,7 @@ let InlineExpression = defer(() => let StringLiteral = defer(() => sequence( string("\""), - repeat(quoted_text_char), + repeat(quoted_char), string("\"")) .map(element_at(1)) .map(join) @@ -374,20 +374,27 @@ let Function = /* Characters */ /* Any Unicode character excluding C0 control characters (but including tab), - * space, surrogate blocks and non-characters (U+FFFE, U+FFFF). + * surrogate blocks and non-characters (U+FFFE, U+FFFF). * Cf. https://www.w3.org/TR/REC-xml/#NT-Char */ let regular_char = either( - charset("\\u{9}\\u{21}-\\u{D7FF}\\u{E000}-\\u{FFFD}"), + charset("\\u{9}\\u{20}-\\u{D7FF}\\u{E000}-\\u{FFFD}"), charset("\\u{10000}-\\u{10FFFF}")); /* The opening brace in text starts a placeable. */ -let text_char = +let special_text_char = + string("{"); + +/* Double quote and backslash need to be escaped in string literals. */ +let special_quoted_char = either( - and( - not(string("{")), - regular_char), - string("\u0020")); + string("\""), + string("\\")); + +let text_char = + and( + not(special_text_char), + regular_char); /* Indented text may not start with characters which mark its end. */ let indented_char = @@ -398,19 +405,28 @@ let indented_char = not(string("}")), text_char); -/* Backslash can be used to escape the double quote and the backslash itself. - * The literal opening brace { is allowed because StringLiterals may not have - * placeables. \uXXXX Unicode escape sequences are recognized, too. */ -let quoted_text_char = +let literal_escape = + sequence( + string("\\"), + special_quoted_char) + .map(join); + +let unicode_escape = + sequence( + string("\\u"), + regex(/[0-9a-fA-F]{4}/)) + .map(join); + +/* The literal opening brace { is allowed in string literals because they may + * not have placeables. */ +let quoted_char = either( and( - not(string("\\")), - not(string("\"")), + not(special_quoted_char), text_char), - regex(/\\u[0-9a-fA-F]{4}/), - string("{"), - string("\\\\"), - string("\\\"")); + special_text_char, + literal_escape, + unicode_escape); let digit = charset("0-9"); From 2356656eadaf9899380f75b23c76c2527c253eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sta=C5=9B=20Ma=C5=82olepszy?= Date: Fri, 26 Oct 2018 15:29:56 +0200 Subject: [PATCH 3/5] Update the Changelog --- spec/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/CHANGELOG.md b/spec/CHANGELOG.md index fbaedad..03fb547 100644 --- a/spec/CHANGELOG.md +++ b/spec/CHANGELOG.md @@ -14,6 +14,16 @@ consistent with how `Identifiers` of variables don't include the `$` sigil either. + - Treat backslash (`\`) as a regular character in `TextElements`. (#123) + + Backslash does no longer have special escaping powers when used in + `TextElements`. It's still recognized as special in `StringLiterals`, + however. `StringLiterals` can be used to insert all special-purpose + characters in text. For instance, `{"{"}` will insert the literal opening + curly brace (`{`), `{"\u00A0"}` will insert the non-breaking space, and + `{" "}` can be used to make a translation start or end with whitespace, + which would otherwise by trimmed by `Pattern.` + ## 0.7.0 (October 15, 2018) - Relax the indentation requirement. (#87) From 57f5e89fc69ed5257a732462f61c7ec181504511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sta=C5=9B=20Ma=C5=82olepszy?= Date: Mon, 29 Oct 2018 14:40:26 +0100 Subject: [PATCH 4/5] Rename regular_char to any_char --- spec/fluent.ebnf | 11 +++++------ syntax/grammar.mjs | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/spec/fluent.ebnf b/spec/fluent.ebnf index 9712fad..ae9f0c9 100644 --- a/spec/fluent.ebnf +++ b/spec/fluent.ebnf @@ -87,23 +87,22 @@ Function ::= [A-Z] [A-Z_?-]* /* Any Unicode character excluding C0 control characters (but including tab), * surrogate blocks and non-characters (U+FFFE, U+FFFF). * Cf. https://www.w3.org/TR/REC-xml/#NT-Char */ -regular_char ::= [\\u{9}\\u{20}-\\u{D7FF}\\u{E000}-\\u{FFFD}] +any_char ::= [\\u{9}\\u{20}-\\u{D7FF}\\u{E000}-\\u{FFFD}] | [\\u{10000}-\\u{10FFFF}] /* The opening brace in text starts a placeable. */ special_text_char ::= "{" /* Double quote and backslash need to be escaped in string literals. */ special_quoted_char ::= "\"" | "\\" -text_char ::= regular_char - special_text_char +text_char ::= any_char - special_text_char /* Indented text may not start with characters which mark its end. */ indented_char ::= text_char - "}" - "[" - "*" - "." -literal_escape ::= "\\" special_quoted_char +special_escape ::= "\\" special_quoted_char unicode_escape ::= "\\u" /[0-9a-fA-F]{4}/ /* The literal opening brace { is allowed in string literals because they may * not have placeables. */ -quoted_char ::= (text_char - special_quoted_char) - | special_text_char - | literal_escape +quoted_char ::= (any_char - special_quoted_char) + | special_escape | unicode_escape digit ::= [0-9] diff --git a/syntax/grammar.mjs b/syntax/grammar.mjs index c821499..2d9bd98 100644 --- a/syntax/grammar.mjs +++ b/syntax/grammar.mjs @@ -376,7 +376,7 @@ let Function = /* Any Unicode character excluding C0 control characters (but including tab), * surrogate blocks and non-characters (U+FFFE, U+FFFF). * Cf. https://www.w3.org/TR/REC-xml/#NT-Char */ -let regular_char = +let any_char = either( charset("\\u{9}\\u{20}-\\u{D7FF}\\u{E000}-\\u{FFFD}"), charset("\\u{10000}-\\u{10FFFF}")); @@ -394,7 +394,7 @@ let special_quoted_char = let text_char = and( not(special_text_char), - regular_char); + any_char); /* Indented text may not start with characters which mark its end. */ let indented_char = @@ -405,7 +405,7 @@ let indented_char = not(string("}")), text_char); -let literal_escape = +let special_escape = sequence( string("\\"), special_quoted_char) @@ -423,9 +423,8 @@ let quoted_char = either( and( not(special_quoted_char), - text_char), - special_text_char, - literal_escape, + any_char), + special_escape, unicode_escape); let digit = charset("0-9"); From 35cb14d0dee274419f7bc6a82e6a22bc3d32da3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sta=C5=9B=20Ma=C5=82olepszy?= Date: Tue, 30 Oct 2018 08:21:17 +0100 Subject: [PATCH 5/5] Better comments about text and quoted characters --- spec/fluent.ebnf | 42 ++++++++++++++++++++++++++---------- syntax/grammar.mjs | 53 +++++++++++++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/spec/fluent.ebnf b/spec/fluent.ebnf index ae9f0c9..55dc725 100644 --- a/spec/fluent.ebnf +++ b/spec/fluent.ebnf @@ -83,27 +83,47 @@ VariantKey ::= "[" blank? (NumberLiteral | Identifier) blank? "]" Identifier ::= [a-zA-Z] [a-zA-Z0-9_-]* Function ::= [A-Z] [A-Z_?-]* -/* Characters */ -/* Any Unicode character excluding C0 control characters (but including tab), - * surrogate blocks and non-characters (U+FFFE, U+FFFF). - * Cf. https://www.w3.org/TR/REC-xml/#NT-Char */ +/* Content Characters + * + * Translation content can be written using most Unicode characters, with the + * exception of C0 control characters (but allowing tab), surrogate blocks and + * non-characters (U+FFFE, U+FFFF). + */ any_char ::= [\\u{9}\\u{20}-\\u{D7FF}\\u{E000}-\\u{FFFD}] | [\\u{10000}-\\u{10FFFF}] -/* The opening brace in text starts a placeable. */ + +/* Text elements + * + * The primary storage for content are text elements. Text elements are not + * delimited with quotes and may span multiple lines as long as all lines are + * indented. The opening brace ({) marks a start of a placeable in the pattern + * and may not be used in text elements verbatim. Due to the indentation + * requirement some text characters may not appear as the first character on a + * new line. + */ special_text_char ::= "{" -/* Double quote and backslash need to be escaped in string literals. */ -special_quoted_char ::= "\"" - | "\\" text_char ::= any_char - special_text_char -/* Indented text may not start with characters which mark its end. */ indented_char ::= text_char - "}" - "[" - "*" - "." + +/* String literals + * + * For special-purpose content, quoted string literals can be used where text + * elements are not a good fit. String literals are delimited with double + * quotes and may not contain line breaks. String literals use the backslash + * (\) as the escape character. The literal double quote can be inserted via + * the \" escape sequence. The literal backslash can be inserted with \\. The + * literal opening brace ({) is allowed in string literals because they may not + * comprise placeables. + */ +special_quoted_char ::= "\"" + | "\\" special_escape ::= "\\" special_quoted_char unicode_escape ::= "\\u" /[0-9a-fA-F]{4}/ -/* The literal opening brace { is allowed in string literals because they may - * not have placeables. */ quoted_char ::= (any_char - special_quoted_char) | special_escape | unicode_escape + +/* Numbers */ digit ::= [0-9] /* Whitespace */ diff --git a/syntax/grammar.mjs b/syntax/grammar.mjs index 2d9bd98..d3d4703 100644 --- a/syntax/grammar.mjs +++ b/syntax/grammar.mjs @@ -370,33 +370,38 @@ let Function = .map(join) .chain(into(FTL.Function)); -/* ---------- */ -/* Characters */ +/* -------------------------------------------------------------------------- */ +/* Content Characters + * + * Translation content can be written using most Unicode characters, with the + * exception of C0 control characters (but allowing tab), surrogate blocks and + * non-characters (U+FFFE, U+FFFF). + */ -/* Any Unicode character excluding C0 control characters (but including tab), - * surrogate blocks and non-characters (U+FFFE, U+FFFF). - * Cf. https://www.w3.org/TR/REC-xml/#NT-Char */ let any_char = either( charset("\\u{9}\\u{20}-\\u{D7FF}\\u{E000}-\\u{FFFD}"), charset("\\u{10000}-\\u{10FFFF}")); -/* The opening brace in text starts a placeable. */ +/* -------------------------------------------------------------------------- */ +/* Text elements + * + * The primary storage for content are text elements. Text elements are not + * delimited with quotes and may span multiple lines as long as all lines are + * indented. The opening brace ({) marks a start of a placeable in the pattern + * and may not be used in text elements verbatim. Due to the indentation + * requirement some text characters may not appear as the first character on a + * new line. + */ + let special_text_char = string("{"); -/* Double quote and backslash need to be escaped in string literals. */ -let special_quoted_char = - either( - string("\""), - string("\\")); - let text_char = and( not(special_text_char), any_char); -/* Indented text may not start with characters which mark its end. */ let indented_char = and( not(string(".")), @@ -405,6 +410,23 @@ let indented_char = not(string("}")), text_char); +/* -------------------------------------------------------------------------- */ +/* String literals + * + * For special-purpose content, quoted string literals can be used where text + * elements are not a good fit. String literals are delimited with double + * quotes and may not contain line breaks. String literals use the backslash + * (\) as the escape character. The literal double quote can be inserted via + * the \" escape sequence. The literal backslash can be inserted with \\. The + * literal opening brace ({) is allowed in string literals because they may not + * comprise placeables. + */ + +let special_quoted_char = + either( + string("\""), + string("\\")); + let special_escape = sequence( string("\\"), @@ -417,8 +439,6 @@ let unicode_escape = regex(/[0-9a-fA-F]{4}/)) .map(join); -/* The literal opening brace { is allowed in string literals because they may - * not have placeables. */ let quoted_char = either( and( @@ -427,6 +447,9 @@ let quoted_char = special_escape, unicode_escape); +/* ------- */ +/* Numbers */ + let digit = charset("0-9"); /* ---------- */