Skip to content

Commit

Permalink
further progress on rst roles & dir-s (fix nim-lang#17646)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-mr committed Apr 6, 2021
1 parent 34c1c63 commit 0b0e6ba
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 33 deletions.
107 changes: 74 additions & 33 deletions lib/packages/docutils/rst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,38 @@ template newLeaf(s: string): PRstNode = newRstLeaf(s)
proc newLeaf(p: var RstParser): PRstNode =
result = newLeaf(currentTok(p).symbol)

proc validRefnamePunct(x: string): bool =
## https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#reference-names
x.len == 1 and x[0] in {'-', '_', '.', ':', '+'}

func getRefnameIdx(p: RstParser, startIdx: int): int =
## Gets last token index of a refname ("word" in RST terminology):
##
## reference names are single words consisting of alphanumerics plus
## isolated (no two adjacent) internal hyphens, underscores, periods,
## colons and plus signs; no whitespace or other characters are allowed.
##
## Refnames are used for:
## - reference names
## - role names
## - directive names
## - footnote labels
##
# TODO: use this func in all other relevant places
var j = startIdx
if p.tok[j].kind == tkWord:
inc j
while p.tok[j].kind == tkPunct and validRefnamePunct(p.tok[j].symbol) and
p.tok[j+1].kind == tkWord:
inc j, 2
result = j - 1

func getRefname(p: RstParser, startIdx: int): (string, int) =
let lastIdx = getRefnameIdx(p, startIdx)
result[1] = lastIdx
for j in startIdx..lastIdx:
result[0].add p.tok[j].symbol

proc getReferenceName(p: var RstParser, endStr: string): PRstNode =
var res = newRstNode(rnInner)
while true:
Expand Down Expand Up @@ -1011,7 +1043,10 @@ proc match(p: RstParser, start: int, expr: string): bool =
var last = expr.len - 1
while i <= last:
case expr[i]
of 'w': result = p.tok[j].kind == tkWord
of 'w':
let lastIdx = getRefnameIdx(p, j)
result = lastIdx >= j
if result: j = lastIdx
of ' ': result = p.tok[j].kind == tkWhite
of 'i': result = p.tok[j].kind == tkIndent
of 'I': result = p.tok[j].kind in {tkIndent, tkEof}
Expand Down Expand Up @@ -1058,7 +1093,7 @@ proc fixupEmbeddedRef(n, a, b: PRstNode) =
proc whichRole(p: RstParser, sym: string): RstNodeKind =
result = whichRoleAux(sym)
if result == rnUnknownRole:
rstMessage(p, mwUnsupportedLanguage, p.s.currRole)
rstMessage(p, mwUnsupportedLanguage, sym)

proc toInlineCode(n: PRstNode, language: string): PRstNode =
## Creates rnInlineCode and attaches `n` contents as code (in 3rd son).
Expand All @@ -1078,6 +1113,11 @@ proc toInlineCode(n: PRstNode, language: string): PRstNode =
lb.add newLeaf(s)
result.add lb

proc toUnknownRole(n: PRstNode, roleName: string): PRstNode =
let newN = newRstNode(rnInner, n.sons)
let newSons = @[newN, newLeaf(roleName)]
result = newRstNode(rnUnknownRole, newSons)

proc parsePostfix(p: var RstParser, n: PRstNode): PRstNode =
var newKind = n.kind
var newSons = n.sons
Expand All @@ -1102,17 +1142,15 @@ proc parsePostfix(p: var RstParser, n: PRstNode): PRstNode =
result = newRstNode(newKind, newSons)
elif match(p, p.idx, ":w:"):
# a role:
let roleName = nextTok(p).symbol
let (roleName, lastIdx) = getRefname(p, p.idx+1)
newKind = whichRole(p, roleName)
if newKind == rnUnknownRole:
let newN = newRstNode(rnInner, n.sons)
newSons = @[newN, newLeaf(roleName)]
result = newRstNode(newKind, newSons)
result = n.toUnknownRole(roleName)
elif newKind == rnInlineCode:
result = n.toInlineCode(language=roleName)
else:
result = newRstNode(newKind, newSons)
inc p.idx, 3
p.idx = lastIdx + 2
else:
if p.s.currRoleKind == rnInlineCode:
result = n.toInlineCode(language=p.s.currRole)
Expand All @@ -1139,10 +1177,6 @@ proc parseSmiley(p: var RstParser): PRstNode =
result.text = val
return

proc validRefnamePunct(x: string): bool =
## https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#reference-names
x.len == 1 and x[0] in {'-', '_', '.', ':', '+'}

proc isUrl(p: RstParser, i: int): bool =
result = p.tok[i+1].symbol == ":" and p.tok[i+2].symbol == "//" and
p.tok[i+3].kind == tkWord and
Expand Down Expand Up @@ -1373,14 +1407,18 @@ proc parseInline(p: var RstParser, father: PRstNode) =
var n = newRstNode(rnInlineLiteral)
parseUntil(p, n, "``", false)
father.add(n)
elif match(p, p.idx, ":w:") and p.tok[p.idx+3].symbol == "`":
let roleName = nextTok(p).symbol
elif match(p, p.idx, ":w:") and
(var lastIdx = getRefnameIdx(p, p.idx + 1);
p.tok[lastIdx+2].symbol == "`"):
let (roleName, _) = getRefname(p, p.idx+1)
let k = whichRole(p, roleName)
var n = newRstNode(k)
inc p.idx, 3
p.idx = lastIdx + 2
if k == rnInlineCode:
n = n.toInlineCode(language=roleName)
parseUntil(p, n, "`", false) # bug #17260
if k == rnUnknownRole:
n = n.toUnknownRole(roleName)
father.add(n)
elif isInlineMarkupStart(p, "`"):
var n = newRstNode(rnInterpretedText)
Expand Down Expand Up @@ -1438,25 +1476,28 @@ proc parseInline(p: var RstParser, father: PRstNode) =
else: discard

proc getDirective(p: var RstParser): string =
if currentTok(p).kind == tkWhite and nextTok(p).kind == tkWord:
var j = p.idx
inc p.idx
result = currentTok(p).symbol
inc p.idx
while currentTok(p).kind in {tkWord, tkPunct, tkAdornment, tkOther}:
if currentTok(p).symbol == "::": break
result.add(currentTok(p).symbol)
inc p.idx
if currentTok(p).kind == tkWhite: inc p.idx
if currentTok(p).symbol == "::":
inc p.idx
if currentTok(p).kind == tkWhite: inc p.idx
else:
p.idx = j # set back
result = "" # error
else:
result = ""
result = result.toLowerAscii()
result = ""
if currentTok(p).kind == tkWhite:
let (name, lastIdx) = getRefname(p, p.idx + 1)
let afterIdx = lastIdx + 1
if name.len > 0:
if p.tok[afterIdx].symbol == "::":
result = name
p.idx = afterIdx + 1
if currentTok(p).kind == tkWhite:
inc p.idx
elif currentTok(p).kind != tkIndent:
rstMessage(p, mwRstStyle,
"whitespace or newline expected after directive " & name)
result = result.toLowerAscii()
elif p.tok[afterIdx].symbol == ":":
rstMessage(p, mwRstStyle,
"double colon :: may be missing at end of '" & name & "'",
p.tok[afterIdx].line, p.tok[afterIdx].col)
elif p.tok[afterIdx].kind == tkPunct and p.tok[afterIdx].symbol[0] == ':':
rstMessage(p, mwRstStyle,
"too many colons for a directive (should be ::)",
p.tok[afterIdx].line, p.tok[afterIdx].col)

proc parseComment(p: var RstParser): PRstNode =
case currentTok(p).kind
Expand Down
32 changes: 32 additions & 0 deletions tests/stdlib/trstgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,21 @@ suite "YAML syntax highlighting":
<span class="StringLit">not numbers</span><span class="Punctuation">:</span> <span class="Punctuation">[</span> <span class="StringLit">42e</span><span class="Punctuation">,</span> <span class="StringLit">0023</span><span class="Punctuation">,</span> <span class="StringLit">+32.37</span><span class="Punctuation">,</span> <span class="StringLit">8 ball</span><span class="Punctuation">]</span>
<span class="Punctuation">}</span></pre>"""

test "Directives: warnings":
let input = dedent"""
.. non-existant-warning: Paragraph.
.. another.wrong:warning::: Paragraph.
"""
var warnings = new seq[string]
let output = input.toHtml(warnings=warnings)
check output == ""
doAssert warnings[].len == 2
check "(1, 24) Warning: RST style:" in warnings[0]
check "double colon :: may be missing at end of 'non-existant-warning'" in warnings[0]
check "(3, 25) Warning: RST style:" in warnings[1]
check "RST style: too many colons for a directive (should be ::)" in warnings[1]

test "Anchors, Aliases, Tags":
let input = """.. code-block:: yaml
--- !!map
Expand Down Expand Up @@ -1403,6 +1418,23 @@ Test1
check """`3`:sup:\ He is an isotope of helium.""".toHtml == expected
check """`3`:superscript:\ He is an isotope of helium.""".toHtml == expected

test "Roles: warnings":
let input = dedent"""
See function :py:func:`spam`.
See also `egg`:py:class:.
"""
var warnings = new seq[string]
let output = input.toHtml(warnings=warnings)
doAssert warnings[].len == 2
check "(1, 14) Warning: " in warnings[0]
check "language 'py:func' not supported" in warnings[0]
check "(3, 15) Warning: " in warnings[1]
check "language 'py:class' not supported" in warnings[1]
check("""<p>See function <span class="py:func">spam</span>.</p>""" & "\n" &
"""<p>See also <span class="py:class">egg</span>. </p>""" & "\n" ==
output)

test "(not) Roles: check escaping 1":
let expected = """See :subscript:<tt class="docutils literal">""" &
"""<span class="pre">""" & id"some" & " " & id"text" &
Expand Down

0 comments on commit 0b0e6ba

Please sign in to comment.