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 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 092362f..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; @@ -27,16 +31,27 @@ 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 = $titleChar+ +sectionTitle = t:$titleChar+ { + return markdownUnescape(t); +} titleChar = [^\n\r# ] / [# ] titleChar sectionID = start:$sectionIDStart rest:('.' $sectionIDPart)* '.' { @@ -209,7 +224,7 @@ inlineEntity = inlineEdit / inlineCode / reference / bold / italic / link / imag content = inlineEntity / text textChar = escaped - / [^\n\r+\-{`*[!<] + / [^\n\r+\-{`*[!<_] / '++' !'}' / '+' !'+}' / '--' !'}' @@ -221,7 +236,7 @@ textChar = escaped text = value:$textChar+ { return { type: 'Text', - value: value + value: markdownUnescape(value) }; } @@ -246,13 +261,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* '++}' { @@ -276,7 +300,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}.'); } @@ -344,7 +368,7 @@ linkTextChar = escaped linkText = value:$linkTextChar+ { return { type: 'Text', - value: value + value: markdownUnescape(value) }; } @@ -438,9 +462,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 markdownUnescape(text); +} + +globalName = text:$([A-Z] $('\\_' / [_a-zA-Z])*) { + return markdownUnescape(text); +} +paramName = text:$($('\\_' / [_a-zA-Z]) $('\\_' / [_a-zA-Z0-9])*) { + return markdownUnescape(text); +} // Algorithm @@ -453,7 +484,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 +492,9 @@ call = name:(globalName / localName) '(' _ args:callArg* _ ')' { }; } -callArg = value:value [, ]* { +callSep = __ ',' __ / __ + +callArg = value:value callSep { return value; } @@ -473,7 +506,7 @@ stringLiteral = '"' value:$([^"\n\r]/'\\"')* closer:'"'? { } return { type: 'StringLiteral', - value: '"' + value + '"' + value: '"' + markdownUnescape(value) + '"' }; } @@ -494,7 +527,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 +549,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 +562,7 @@ oneOfRHS = 'one of' rows:(_ NL? (_ token)+)+ { }; } -singleRHS = condition:condition? _ tokens:token+ { +singleRHS = condition:condition? _ tokens:tokenListMultiline { return { type: 'RHS', condition: condition, @@ -545,7 +578,7 @@ indentedRHS = INDENT defs:(listItemRHS+)? DEDENT &{ return defs !== null; } { return defs; } -listItemRHS = LINE listBullet _ condition:condition? _ tokens:token+ { +listItemRHS = LINE listBullet _ condition:condition? _ tokens:tokenListMultiline { return { type: 'RHS', condition: condition, @@ -561,13 +594,17 @@ 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', token: token, - isList: quantifier === '+' || quantifier === '*', - isOptional: quantifier === '?' || quantifier === '*' + isList: quantifier === '+' || quantifier === '*' || quantifier === '\\*', + isOptional: quantifier === '?' || quantifier === '*' || quantifier === '\\*' }; } if (constraint) { @@ -580,6 +617,22 @@ token = token:unconstrainedToken quantifier:('+' / '?' / '*')? _ constraint:cons return token; } +tokenAfterSpace = _ token:token { + return token; +} + +tokenAfterSpaceOrNewline = __ !listBullet 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:'"'? { @@ -679,7 +732,7 @@ quotedTerminal = '`' value:$(([^`\n] / ('\\`'))+)? closer:'`' { terminal = value:$(([^ \n"/`] [^ \n"\`,\]\}]*)) { return { type: 'Terminal', - value: value + value: markdownUnescape(value) }; } @@ -711,6 +764,7 @@ DEDENT = &lineStart !{ indentStack.length === 0 } { NL = '\n' / '\r' / '\r\n' NOT_NL = [^\n\r] _ = ' '* +__ = ' '* NL ' '* / ' '* EOF = NL* !. lineStart = NL+ / & { return offset() === 0 } 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]; }