From 538e055000ee49e63facfbd12386c6b2d9d3756a Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 1 Jun 2020 17:33:13 +0100 Subject: [PATCH 01/16] Support newlines in production list --- src/grammar.pegjs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index 092362f..3092576 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -494,7 +494,7 @@ variable = name:localName { // Grammar productions -semantic = BLOCK name:nonTerminal _ defType:(':::'/'::'/':') _ tokens:token+ steps:list { +semantic = BLOCK name:nonTerminal _ defType:(':::'/'::'/':') _ tokens:tokenListMultiline steps:list { return { type: 'Semantic', name: name, @@ -516,7 +516,7 @@ production = BLOCK token:nonTerminal _ defType:(':::'/'::'/':') _ rhs:production }; } -productionRHS = oneOfRHS / singleRHS / listRHS +productionRHS = oneOfRHS / listRHS / singleRHS oneOfRHS = 'one of' rows:(_ NL? (_ token)+)+ { return { @@ -529,7 +529,7 @@ oneOfRHS = 'one of' rows:(_ NL? (_ token)+)+ { }; } -singleRHS = condition:condition? _ tokens:token+ { +singleRHS = condition:condition? _ tokens:tokenListMultiline { return { type: 'RHS', condition: condition, @@ -545,7 +545,7 @@ indentedRHS = INDENT defs:(listItemRHS+)? DEDENT &{ return defs !== null; } { return defs; } -listItemRHS = LINE listBullet _ condition:condition? _ tokens:token+ { +listItemRHS = LINE listBullet _ condition:condition? _ tokens:tokenListOneLine { return { type: 'RHS', condition: condition, @@ -561,7 +561,11 @@ condition = '[' condition:$('+' / '~' / 'if' (_ 'not')?) _ param:paramName ']' { }; } -token = token:unconstrainedToken quantifier:('+' / '?' / '*')? _ constraint:constraint? _ { +constraintAfterGap = _ constraint:constraint { + return constraint; +} + +token = token:unconstrainedToken quantifier:('+' / '?' / '*')? constraint:constraintAfterGap? { if (quantifier) { token = { type: 'Quantified', @@ -580,6 +584,22 @@ token = token:unconstrainedToken quantifier:('+' / '?' / '*')? _ constraint:cons return token; } +tokenAfterSpace = _ token:token { + return token; +} + +tokenAfterSpaceOrNewline = __ token:token { + return token; +} + +tokenListOneLine = head:token tail:tokenAfterSpace* { + return [head, ...tail]; +} + +tokenListMultiline = head:token tail:tokenAfterSpaceOrNewline* { + return [head, ...tail]; +} + unconstrainedToken = prose / emptyToken / lookahead / nonTerminal / regexp / quotedTerminal / terminal prose = '"' text:$([^"\n\r]/'\\"')* closer:'"'? { @@ -711,6 +731,7 @@ DEDENT = &lineStart !{ indentStack.length === 0 } { NL = '\n' / '\r' / '\r\n' NOT_NL = [^\n\r] _ = ' '* +__ = ' '* NL ' '* / ' '* EOF = NL* !. lineStart = NL+ / & { return offset() === 0 } From 5678453e5da91b8eb6a0ec79d35b90f3690ec6e5 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 1 Jun 2020 17:39:05 +0100 Subject: [PATCH 02/16] Support newlines in calls --- src/grammar.pegjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index 3092576..edaf03b 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -276,7 +276,7 @@ htmlTag = tag:$('<' '/'? [a-z]+ [^>]* '>') { }; } -reference = '{' !('++'/'--') _ ref:(call / value / token)? _ close:'}'? { +reference = '{' !('++'/'--') __ ref:(call / value / token)? __ close:'}'? { if (ref === null || close === null) { error('Malformed {reference}.'); } @@ -453,7 +453,7 @@ algorithm = BLOCK call:call _ ':' ':'? steps:list { }; } -call = name:(globalName / localName) '(' _ args:callArg* _ ')' { +call = name:(globalName / localName) '(' __ args:callArg* __ ')' { return { type: 'Call', name: name, @@ -461,7 +461,9 @@ call = name:(globalName / localName) '(' _ args:callArg* _ ')' { }; } -callArg = value:value [, ]* { +callSep = __ ',' __ / __ + +callArg = value:value callSep { return value; } From 2c34cdf35366887b46cb355671c8cfb6c51288dc Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 1 Jun 2020 17:42:32 +0100 Subject: [PATCH 03/16] Support multiline in production list --- src/grammar.pegjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index edaf03b..3de4749 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -547,7 +547,7 @@ indentedRHS = INDENT defs:(listItemRHS+)? DEDENT &{ return defs !== null; } { return defs; } -listItemRHS = LINE listBullet _ condition:condition? _ tokens:tokenListOneLine { +listItemRHS = LINE listBullet _ condition:condition? _ tokens:tokenListMultiline { return { type: 'RHS', condition: condition, From efd001d7fae65d771747b620f67ae7a2a0b095fd Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 1 Jun 2020 17:55:20 +0100 Subject: [PATCH 04/16] Handle \_ in titles --- src/grammar.pegjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index 3de4749..ce7073f 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -36,7 +36,9 @@ title = BLOCK !'#' value:$NOT_NL+ NL ('---' '-'* / '===' '='*) &NL { SEC_CLOSE = _ '#'* &NL -sectionTitle = $titleChar+ +sectionTitle = t:$titleChar+ { + return t.replace(/\\_/g, '_'); +} titleChar = [^\n\r# ] / [# ] titleChar sectionID = start:$sectionIDStart rest:('.' $sectionIDPart)* '.' { From a3f9a2f60e91b10f44266c03f6726295deb0586e Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 1 Jun 2020 18:00:50 +0100 Subject: [PATCH 05/16] listBullets are not tokens --- src/grammar.pegjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index ce7073f..78bd223 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -592,7 +592,7 @@ tokenAfterSpace = _ token:token { return token; } -tokenAfterSpaceOrNewline = __ token:token { +tokenAfterSpaceOrNewline = __ !listBullet token:token { return token; } From e897c9eb703bf9b3ef7a2e519465fbb1ecccc109 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 1 Jun 2020 19:56:54 +0100 Subject: [PATCH 06/16] Newlines after tds --- src/print.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/print.js b/src/print.js index 60f809c..9a8ed6a 100644 --- a/src/print.js +++ b/src/print.js @@ -574,7 +574,7 @@ function printAll(list, options) { return ( '\n' + join(row.map(function (def) { - return '' + def + ''; + return '' + def + '\n'; })) + '\n' ); From 8b2a75379cd0646e82b5a65bf9a77b3c8427f219 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 10:29:21 +0100 Subject: [PATCH 07/16] Allow for escaped * in quantifiers --- src/grammar.pegjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index 78bd223..eda1d97 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -569,13 +569,13 @@ constraintAfterGap = _ constraint:constraint { return constraint; } -token = token:unconstrainedToken quantifier:('+' / '?' / '*')? constraint:constraintAfterGap? { +token = token:unconstrainedToken quantifier:('+' / '?' / '*' / '\\*')? constraint:constraintAfterGap? { if (quantifier) { token = { type: 'Quantified', token: token, - isList: quantifier === '+' || quantifier === '*', - isOptional: quantifier === '?' || quantifier === '*' + isList: quantifier === '+' || quantifier === '*' || quantifier === '\\*', + isOptional: quantifier === '?' || quantifier === '*' || quantifier === '\\*' }; } if (constraint) { From 907d6f909cef7d6068b1a1f567a5a454d615e7b9 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 10:29:52 +0100 Subject: [PATCH 08/16] Allow for escaped $/_ in terminals --- src/grammar.pegjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index eda1d97..121e9b4 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -703,7 +703,7 @@ quotedTerminal = '`' value:$(([^`\n] / ('\\`'))+)? closer:'`' { terminal = value:$(([^ \n"/`] [^ \n"\`,\]\}]*)) { return { type: 'Terminal', - value: value + value: value.replace(/\\([$_])/g, '$1') }; } From 24596e4e651eabc04fd13adb3cc20081dcc99658 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 10:30:47 +0100 Subject: [PATCH 09/16] Remove \n after --- src/print.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/print.js b/src/print.js index 9a8ed6a..60f809c 100644 --- a/src/print.js +++ b/src/print.js @@ -574,7 +574,7 @@ function printAll(list, options) { return ( '\n' + join(row.map(function (def) { - return '' + def + '\n'; + return '' + def + ''; })) + '\n' ); From f7f3f20cd392c8d5a1c44f2d534849b903e5feaa Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 10:46:41 +0100 Subject: [PATCH 10/16] Allow for italic with underscores --- src/grammar.pegjs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index 121e9b4..e0e2a7a 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -211,7 +211,7 @@ inlineEntity = inlineEdit / inlineCode / reference / bold / italic / link / imag content = inlineEntity / text textChar = escaped - / [^\n\r+\-{`*[!<] + / [^\n\r+\-{`*[!<_] / '++' !'}' / '+' !'+}' / '--' !'}' @@ -248,13 +248,22 @@ bold = '**' contents:(inlineCode / link / italic / text)+ '**' { }; } -italic = '*' contents:(inlineCode / link / text)+ '*' { +asteriskItalic = '*' contents:(inlineCode / link / text)+ '*' { return { type: 'Italic', contents: contents }; } +underscoreItalic = '_' contents:(inlineCode / link / text)+ '_' { + return { + type: 'Italic', + contents: contents + }; +} + +italic = underscoreItalic / asteriskItalic + inlineEdit = ins / del ins = '{++' contents:content* '++}' { From c479f1a55faa9bd8bc20fdc9259496bc94f96072 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 11:54:34 +0100 Subject: [PATCH 11/16] Unescape more things --- src/grammar.pegjs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index e0e2a7a..26299c0 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -223,7 +223,7 @@ textChar = escaped text = value:$textChar+ { return { type: 'Text', - value: value + value: value.replace(/\\([$_*])/g, '$1') }; } @@ -355,7 +355,7 @@ linkTextChar = escaped linkText = value:$linkTextChar+ { return { type: 'Text', - value: value + value: value.replace(/\\[$_*]/g, '$1') }; } @@ -449,9 +449,16 @@ tableCellText = value:$tableCellTextChar+ { // Names -localName = $([_a-z][_a-zA-Z0-9]*) -globalName = $([A-Z][_a-zA-Z]*) -paramName = $([_a-zA-Z][_a-zA-Z0-9]*) +localName = text:$(('\\_' / [_a-z]) ('\\_' / [_a-zA-Z0-9])*) { + return text.replace(/\\_/g, '_'); +} + +globalName = text:$([A-Z] ('\\_' / [_a-zA-Z])*) { + return text.replace(/\\_/g, '_'); +} +paramName = text:$(('\\_' / [_a-zA-Z]) ('\\_' / [_a-zA-Z0-9])*) { + return text.replace(/\\_/g, '_'); +} // Algorithm @@ -486,7 +493,7 @@ stringLiteral = '"' value:$([^"\n\r]/'\\"')* closer:'"'? { } return { type: 'StringLiteral', - value: '"' + value + '"' + value: '"' + value.replace(/\\([$_*])/g, '$1') + '"' }; } @@ -712,7 +719,7 @@ quotedTerminal = '`' value:$(([^`\n] / ('\\`'))+)? closer:'`' { terminal = value:$(([^ \n"/`] [^ \n"\`,\]\}]*)) { return { type: 'Terminal', - value: value.replace(/\\([$_])/g, '$1') + value: value.replace(/\\([$_*])/g, '$1') }; } From bfa31688e951014c4576e55e265893f791402db4 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 11:55:53 +0100 Subject: [PATCH 12/16] Underscores for emphasis too --- spec/Markdown.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/Markdown.md b/spec/Markdown.md index 53cea6c..e73ee27 100644 --- a/spec/Markdown.md +++ b/spec/Markdown.md @@ -86,8 +86,7 @@ Todo: Links do not yet support a title attribute. ### Emphasis -Wrapping asterisks *(\*)* indicate emphasis. Like Github-flavored -Markdown, Spec Markdown does not treat underscore *(_)* as emphasis. +Wrapping asterisks *(\*)* or underscores _(\_)_ indicate emphasis. ``` Example of **bold** and *italic* and ***bold italic***. @@ -97,6 +96,14 @@ Produces the following: Example of **bold** and *italic* and ***bold italic***. +``` +Example of **bold** and _italic_ and **_bold italic_**. +``` + +Produces the following: + +Example of **bold** and _italic_ and **_bold italic_**. + ### Inline Code From b5f2cc0da83ca85b6e7a32e89f633e701320ea73 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 12:06:54 +0100 Subject: [PATCH 13/16] atx document title --- spec/Spec Additions.md | 10 +++++----- src/grammar.pegjs | 11 ++++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/spec/Spec Additions.md b/spec/Spec Additions.md index 43f1b63..934bb55 100644 --- a/spec/Spec Additions.md +++ b/spec/Spec Additions.md @@ -24,15 +24,15 @@ referencing specific parts of your document easy. Try it here! ## Title and Introduction -A Spec Markdown document should start with one Setext style header which will be -used as the title of the document. Any content before the first atx (`#`) style -header will become the introduction to the document. +A Spec Markdown document should start with one bolded atx style header +(`# **Title**`) which will be used as the title of the document. Any content +before the next atx (`#`) style header will become the introduction to the +document. A Spec Markdown document starts in this form: ``` -Spec Markdown -------------- +# **Spec Markdown** Introductory paragraph. diff --git a/src/grammar.pegjs b/src/grammar.pegjs index 26299c0..b3957a7 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -27,13 +27,22 @@ document = title:title? contents:documentContent* EOF { }; } -title = BLOCK !'#' value:$NOT_NL+ NL ('---' '-'* / '===' '='*) &NL { +oldTitle = BLOCK !'#' value:$NOT_NL+ NL ('---' '-'* / '===' '='*) &NL { return { type: 'DocumentTitle', value: value }; } +newTitle = BLOCK '# **' value:$([^*\r\n]+) '**' &NL { + return { + type: 'DocumentTitle', + value: value + }; +} + +title = newTitle / oldTitle + SEC_CLOSE = _ '#'* &NL sectionTitle = t:$titleChar+ { From 422b38d0247ba255683731d9f81bbd38e5874a36 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 12:07:11 +0100 Subject: [PATCH 14/16] Safer handling of localName, globalName and paramName --- src/grammar.pegjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index b3957a7..e69acf9 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -458,14 +458,14 @@ tableCellText = value:$tableCellTextChar+ { // Names -localName = text:$(('\\_' / [_a-z]) ('\\_' / [_a-zA-Z0-9])*) { +localName = text:$($('\\_' / [_a-z]) $('\\_' / [_a-zA-Z0-9])*) { return text.replace(/\\_/g, '_'); } -globalName = text:$([A-Z] ('\\_' / [_a-zA-Z])*) { +globalName = text:$([A-Z] $('\\_' / [_a-zA-Z])*) { return text.replace(/\\_/g, '_'); } -paramName = text:$(('\\_' / [_a-zA-Z]) ('\\_' / [_a-zA-Z0-9])*) { +paramName = text:$($('\\_' / [_a-zA-Z]) $('\\_' / [_a-zA-Z0-9])*) { return text.replace(/\\_/g, '_'); } From 3831240360871f666db996b32ef13647e88e5f29 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 14:01:00 +0100 Subject: [PATCH 15/16] Support 'raw' languages to avoid prettier formatting --- src/print.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/print.js b/src/print.js index 60f809c..38caafd 100644 --- a/src/print.js +++ b/src/print.js @@ -42,6 +42,13 @@ function getPrismLanguage(lang) { if (!prism.languages[lang]) { loadAllLanguages(); } + if (!prism.languages[lang] && lang.startsWith("raw")) { + // To prevent 'prettier' formatting code in your markdown, you may prefix + // the language with "raw" and we will still format it as you would expect + // in the output. + // e.g. ```graphql -> ```rawgraphql + return prism.languages[lang.substr(3)]; + } return prism.languages[lang]; } From 1a18cd33b4fb4ac6a84cf4d67dd80999b7cf84c0 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 2 Jun 2020 14:01:14 +0100 Subject: [PATCH 16/16] Standardize unescaping --- src/grammar.pegjs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/grammar.pegjs b/src/grammar.pegjs index e69acf9..35e6117 100644 --- a/src/grammar.pegjs +++ b/src/grammar.pegjs @@ -12,6 +12,10 @@ return list; } + function markdownUnescape(text) { + return text.replace(/\\([$_*])/g, '$1'); + } + var htmlBlockName; var BLOCK_TAGS_RX = /^(?:p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)$/i; @@ -46,7 +50,7 @@ title = newTitle / oldTitle SEC_CLOSE = _ '#'* &NL sectionTitle = t:$titleChar+ { - return t.replace(/\\_/g, '_'); + return markdownUnescape(t); } titleChar = [^\n\r# ] / [# ] titleChar @@ -232,7 +236,7 @@ textChar = escaped text = value:$textChar+ { return { type: 'Text', - value: value.replace(/\\([$_*])/g, '$1') + value: markdownUnescape(value) }; } @@ -364,7 +368,7 @@ linkTextChar = escaped linkText = value:$linkTextChar+ { return { type: 'Text', - value: value.replace(/\\[$_*]/g, '$1') + value: markdownUnescape(value) }; } @@ -459,14 +463,14 @@ tableCellText = value:$tableCellTextChar+ { // Names localName = text:$($('\\_' / [_a-z]) $('\\_' / [_a-zA-Z0-9])*) { - return text.replace(/\\_/g, '_'); + return markdownUnescape(text); } globalName = text:$([A-Z] $('\\_' / [_a-zA-Z])*) { - return text.replace(/\\_/g, '_'); + return markdownUnescape(text); } paramName = text:$($('\\_' / [_a-zA-Z]) $('\\_' / [_a-zA-Z0-9])*) { - return text.replace(/\\_/g, '_'); + return markdownUnescape(text); } @@ -502,7 +506,7 @@ stringLiteral = '"' value:$([^"\n\r]/'\\"')* closer:'"'? { } return { type: 'StringLiteral', - value: '"' + value.replace(/\\([$_*])/g, '$1') + '"' + value: '"' + markdownUnescape(value) + '"' }; } @@ -728,7 +732,7 @@ quotedTerminal = '`' value:$(([^`\n] / ('\\`'))+)? closer:'`' { terminal = value:$(([^ \n"/`] [^ \n"\`,\]\}]*)) { return { type: 'Terminal', - value: value.replace(/\\([$_*])/g, '$1') + value: markdownUnescape(value) }; }