more strict RST inline markup parsing (#17827)

* more strict RST inline markup parsing
* add test for unexpected bonus
* introduce `roPreferMarkdown`
This commit is contained in:
Andrey Makarov 2021-04-29 18:31:15 +03:00 • committed by GitHub
commit 1640508348
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 207 additions and 13 deletions

View file

@ -11,9 +11,9 @@
## rst
## ==================================
##
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Nim-flavored reStructuredText
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Nim-flavored reStructuredText and Markdown
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
##
## This module implements a `reStructuredText`:idx: (RST) parser.
## A large subset is implemented with some limitations_ and
@ -177,6 +177,8 @@ type
roSupportRawDirective, ## support the ``raw`` directive (don't support
## it for sandboxing)
roSupportMarkdown, ## support additional features of Markdown
roPreferMarkdown, ## parse as Markdown (keeping RST as "extension"
## to Markdown) -- implies `roSupportMarkdown`
roNimFile ## set for Nim files where default interpreted
## text role should be :nim:
@ -277,6 +279,7 @@ type
line*, col*, baseIndent*: int
skipPounds*: bool
adornmentLine*: bool
escapeNext*: bool
proc getThing(L: var Lexer, tok: var Token, s: set[char]) =
tok.kind = tkWord
@ -314,10 +317,18 @@ proc getPunctAdornment(L: var Lexer, tok: var Token) =
tok.col = L.col
var pos = L.bufpos
let c = L.buf[pos]
while true:
if not L.escapeNext and (c != '\\' or L.adornmentLine):
while true:
tok.symbol.add(L.buf[pos])
inc pos
if L.buf[pos] != c: break
elif L.escapeNext:
tok.symbol.add(L.buf[pos])
inc pos
if L.buf[pos] != c: break
else: # not L.escapeNext and c == '\\' and not L.adornmentLine
tok.symbol.add '\\'
inc pos
L.escapeNext = true
inc L.col, pos - L.bufpos
L.bufpos = pos
if tok.symbol == "\\": tok.kind = tkPunct
@ -429,7 +440,9 @@ proc getTokens(buffer: string, skipPounds: bool, tokens: var TokenSeq): int =
while true:
inc length
setLen(tokens, length)
let toEscape = L.escapeNext
rawGetTok(L, tokens[length - 1])
if toEscape: L.escapeNext = false
if tokens[length - 1].kind == tkEof: break
if tokens[0].kind == tkWhite:
# BUGFIX
@ -981,16 +994,24 @@ proc expect(p: var RstParser, tok: string) =
if currentTok(p).symbol == tok: inc p.idx
else: rstMessage(p, meExpected, tok)
proc isInlineMarkupEnd(p: RstParser, markup: string): bool =
proc isInlineMarkupEnd(p: RstParser, markup: string, exact: bool): bool =
# rst rules: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#inline-markup-recognition-rules
result = currentTok(p).symbol == markup
if exact:
result = currentTok(p).symbol == markup
else:
result = currentTok(p).symbol.endsWith markup
if (not result) and markup == "``":
# check that escaping may have splitted `` to 2 tokens ` and `
result = currentTok(p).symbol == "`" and prevTok(p).symbol == "`"
if not result: return
# Rule 2:
result = prevTok(p).kind notin {tkIndent, tkWhite}
if not result: return
# Rule 7:
result = nextTok(p).kind in {tkIndent, tkWhite, tkEof} or
markup in ["``", "`"] and nextTok(p).kind in {tkIndent, tkWhite, tkWord, tkEof} or
(roPreferMarkdown in p.s.options and
markup in ["``", "`"] and
nextTok(p).kind in {tkIndent, tkWhite, tkWord, tkEof}) or
nextTok(p).symbol[0] in
{'\'', '\"', ')', ']', '}', '>', '-', '/', '\\', ':', '.', ',', ';', '!', '?', '_'}
if not result: return
@ -1130,7 +1151,8 @@ proc toOtherRole(n: PRstNode, kind: RstNodeKind, roleName: string): PRstNode =
proc parsePostfix(p: var RstParser, n: PRstNode): PRstNode =
var newKind = n.kind
var newSons = n.sons
if isInlineMarkupEnd(p, "_") or isInlineMarkupEnd(p, "__"):
if isInlineMarkupEnd(p, "_", exact=true) or
isInlineMarkupEnd(p, "__", exact=true):
inc p.idx
if p.tok[p.idx-2].symbol == "`" and p.tok[p.idx-3].symbol == ">":
var a = newRstNode(rnInner)
@ -1215,7 +1237,7 @@ proc parseWordOrRef(p: var RstParser, father: PRstNode) =
inc p.idx
while currentTok(p).kind in {tkWord, tkPunct}:
if currentTok(p).kind == tkPunct:
if isInlineMarkupEnd(p, "_"):
if isInlineMarkupEnd(p, "_", exact=true):
isRef = true
break
if not validRefnamePunct(currentTok(p).symbol):
@ -1253,7 +1275,15 @@ proc parseUntil(p: var RstParser, father: PRstNode, postfix: string,
while true:
case currentTok(p).kind
of tkPunct:
if isInlineMarkupEnd(p, postfix):
if isInlineMarkupEnd(p, postfix, exact=false):
let l = currentTok(p).symbol.len
if l > postfix.len:
# handle cases like *emphasis with stars****. (It's valid RST!)
father.add newLeaf(currentTok(p).symbol[0 ..< l - postfix.len])
elif postfix == "``" and currentTok(p).symbol == "`" and
prevTok(p).symbol == "`":
# handle cases like ``literal\`` - delete ` already added after \
father.sons.setLen(father.sons.len - 1)
inc p.idx
break
else: