Skip to content

Commit

Permalink
use contexts instead of toplevellast (#22)
Browse files Browse the repository at this point in the history
* use contexts instead of toplevellast

* update quote behavior
  • Loading branch information
metagn authored Sep 15, 2022
1 parent de8f89a commit 1dbd7cb
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 157 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ representation, which can be converted to HTML. Not stable.
[Try in browser](https://metagn.github.io/marggers/browser/converter.html).
[Docs](https://metagn.github.io/marggers/docs/marggers.html)

Tested for C, JS and NimScript (so also VM).
Tested for C, JS and NimScript (so also VM).

Input file ref.mrg:

Expand Down
6 changes: 2 additions & 4 deletions examples/ref.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ <h5> Heading5</h5>
<h6> Heading6</h6>
<h4 id="heading-id"> Heading (new, makes heading-id the id of this heading)</h4>
<ul><li>Bullet points</li><li>Plus</li><li>Minus</li></ul>
<ol><li> Numbered list</li><li> Can be</li><li> Any number</li><li> or just a dot (new),
<ol><li> Numbered list</li><li>Can be</li><li>Any number</li><li>or just a dot (new),
can also indent</li></ol>
<blockquote><p> Blockquotes</p>

<p> can <em>be</em> <strong>formatted</strong></p></blockquote>
<blockquote><p> Blockquotes</p><p>can <em>be</em> <strong>formatted</strong></p></blockquote>
<pre>Code blocks
Have no formatting

Expand Down
2 changes: 1 addition & 1 deletion marggers.nimble
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "0.3.0"
version = "0.3.1"
author = "metagn"
description = "markdown dialect"
license = "MIT"
Expand Down
306 changes: 161 additions & 145 deletions src/marggers/parser.nim
Original file line number Diff line number Diff line change
Expand Up @@ -522,157 +522,173 @@ proc parseId*(parser; startChar: char): NativeString =
discard parser.nextMatch(idDelim)

proc parseTopLevel*(parser; options): seq[MarggersElement] =
template add(elem: MarggersElement): untyped =
result.add(elem)
parser.topLevelLast = nil
var lastEmptyLine = false
for firstCh in parser.nextChars:
if firstCh in {'\r', '\n'}:
if not parser.topLevelLast.isNil:
add(paragraphIfText(parser.topLevelLast))
elif not parser.topLevelLast.isNil:
assert not parser.topLevelLast.isText
case parser.topLevelLast.tag
of ul:
if parser.nextMatch(Whitespace, offset = 1):
let item = newElem(li)
item.content = parseSingleLine(parser, options)
parser.topLevelLast.add(item)
elif parser.nextMatch(IdStarts, offset = 1):
var item = newElem(li)
item.attr("id", parser.parseId(parser.get(-1)))
item.content = parseSingleLine(parser, options)
parser.topLevelLast.add(item)
else:
parser.topLevelLast[^1].add("\n")
parser.topLevelLast[^1].add(parseSingleLine(parser, options))
of ol:
var i = 0
while parser.peekMatch(Digits, offset = i): inc i
if parser.nextMatch('.', offset = i):
let item = newElem(li)
if (let ch = parser.get(); parser.nextMatch(IdStarts)):
item.attr("id", parser.parseId(ch))
item.content = parseSingleLine(parser, options)
parser.topLevelLast.add(item)
else:
parser.topLevelLast[^1].add("\n ")
parser.topLevelLast[^1].add(parseSingleLine(parser, options))
of blockquote:
if firstCh == '>': inc parser.pos
proc skipWhitespaceUntilNewline(parser: var MarggersParser): bool =
var i = 0
while parser.pos + i < parser.str.len:
let ch = parser.get(offset = i)
case ch
of '\n':
parser.pos += i
return true
of Whitespace - {'\n'}:
discard
else:
return false
inc i
let rem = skipWhitespaceUntilNewline(parser)
if rem:
if firstCh == '>':
parser.topLevelLast[^1] = parser.topLevelLast[^1].paragraphIfText
parser.topLevelLast.add("\n")
if parser.peekPrevMatch(" "):
parser.topLevelLast.add(newElem(br))
var context: MarggersElement
block:
var i = 0
while i < parser.contextStack.len:
let c = parser.contextStack[i]
case c.tag
of ul:
if parser.nextMatch({'*', '-', '+'}):
while parser.nextMatch(InlineWhitespace): discard
else: break
of ol:
let originalPos = parser.pos
while parser.nextMatch(Digits): discard
if parser.nextMatch('.'):
while parser.nextMatch(InlineWhitespace): discard
else:
parser.topLevelLast[^1] = parser.topLevelLast[^1].paragraphIfText
result.add(parser.topLevelLast)
if parser.peekPrevMatch(" "):
result.add(newElem(br))
elif parser.topLevelLast[^1].isText:
parser.topLevelLast.add("\n")
parser.topLevelLast.add(newElem(p, parseSingleLine(parser, options)))
elif firstCh in InlineWhitespace:
parser.topLevelLast[^1].add("\n ")
parser.topLevelLast[^1].add(parseSingleLine(parser, options))
else:
parser.topLevelLast[^1].add("\n")
parser.topLevelLast[^1].add(parseSingleLine(parser, options))
of {low(KnownTags)..high(KnownTags)} - SpecialLineTags:
add(paragraphIfText(parser.topLevelLast))
dec parser.pos
else:
case firstCh
of InlineWhitespace:
if result.len != 0 and not result[^1].isText and
result[^1].tag in SpecialLineTags and
result[^1].content.len != 0:
result[^1][^1].add("\n ")
result[^1][^1].add(parseLine(parser, options))
else:
parser.topLevelLast = newElem(p, parseSingleLine(parser, options))
of '#':
parser.pos = originalPos
break
of blockquote:
if parser.nextMatch('>'):
while parser.nextMatch(InlineWhitespace): discard
else: break
else: discard # unreachable
context = c
inc i
parser.contextStack.setLen(i)

template addElement(elem: MarggersElement): untyped =
let el = elem
if not context.isNil:
context.add(el)
else:
result.add(el)

template addContext(elem: MarggersElement): untyped =
let el = elem
addElement(el)
parser.contextStack.add(el)
context = el

proc addLine(
parser: var MarggersParser;
options: static MarggersOptions;
context: MarggersElement;
result: var seq[MarggersElement];
lastEmptyLine: bool) {.nimcall.} =
if not context.isNil:
case context.tag
of ol, ul:
context.add newElem(li, parseSingleLine(parser, options))
of blockquote:
let c = parseSingleLine(parser, options)
if not lastEmptyLine and context.content.len != 0 and
(let last = context[^1]; not last.isText and last.tag == p):
last.add("\n")
last.add(c)
else:
context.add newElem(p, c)
else: discard # unreachable
else:
result.add newElem(p, parseLine(parser, options))

template addLine() = addLine(parser, options, context, result, lastEmptyLine)

case parser.get()
of '\r', '\n':
discard parser.nextMatch("\r\n") or parser.nextMatch("\n")
dec parser.pos
lastEmptyLine = true
continue
of InlineWhitespace:
let last =
if not context.isNil and context.content.len != 0:
context[^1]
elif result.len != 0:
result[^1]
else: nil
if not last.isText and
last.tag in SpecialLineTags and
(let last2 = last[^1]; last2.content.len != 0):
last2.add("\n ")
last2.add(parseLine(parser, options))
else:
addLine()
of '#':
if context.isNil or context.tag in {blockquote, p}:
while not context.isNil and context.tag == p:
let last = parser.contextStack.len - 1
context = parser.contextStack[last]
parser.contextStack.setLen(last)
if last == 0: break
var level = 1
while level < 6 and parser.peekMatch('#', offset = level): inc level
parser.pos += level
parser.topLevelLast = newElem(KnownTags(static(h1.int - 1) + level))
let header = newElem(KnownTags(static(h1.int - 1) + level))
parser.matchNext:
of '|': style parser.topLevelLast, "text-align:center"
of '<': style parser.topLevelLast, "text-align:left"
of '>': style parser.topLevelLast, "text-align:right"
of '|': style header, "text-align:center"
of '<': style header, "text-align:left"
of '>': style header, "text-align:right"
if (let ch = parser.get(); parser.nextMatch(IdStarts)):
parser.topLevelLast.attr("id", parser.parseId(ch))
parser.topLevelLast.add(parseSingleLine(parser, options))
of '*', '-', '+':
if parser.nextMatch(Whitespace, offset = 1):
parser.topLevelLast = newElem(ul)
let item = newElem(li)
item.add(parseSingleLine(parser, options))
parser.topLevelLast.add(item)
elif parser.nextMatch(IdStarts, offset = 1):
parser.topLevelLast = newElem(ul)
var item = newElem(li)
item.attr("id", parser.parseId(parser.get(-1)))
item.content = parseSingleLine(parser, options)
parser.topLevelLast.add(item)
else:
parser.topLevelLast = newElem(p, parseLine(parser, options))
of Digits:
let originalPos = parser.pos
inc parser.pos
while parser.nextMatch(Digits): discard
if parser.nextMatch('.'):
parser.topLevelLast = newElem(ol)
var item = newElem(li)
if (let ch = parser.get(); parser.nextMatch(IdStarts)):
item.attr("id", parser.parseId(ch))
item.add(parseSingleLine(parser, options))
parser.topLevelLast.add(item)
else:
parser.pos = originalPos
parser.topLevelLast = newElem(p, parseLine(parser, options))
of '>':
parser.topLevelLast = newElem(blockquote)
inc parser.pos
header.attr("id", parser.parseId(ch))
header.add(parseSingleLine(parser, options))
addElement(header)
else:
addLine()
of '*', '-', '+':
if parser.nextMatch(InlineWhitespace, offset = 1):
addContext newElem(ul)
addLine()
elif parser.nextMatch(IdStarts, offset = 1):
let list = newElem(ul)
var item = newElem(li)
item.attr("id", parser.parseId(parser.get(-1)))
item.content = parseSingleLine(parser, options)
list.add(item)
addContext(list)
else:
addLine()
of Digits:
let originalPos = parser.pos
inc parser.pos
while parser.nextMatch(Digits): discard
if parser.nextMatch('.'):
let list = newElem(ol)
var item = newElem(li)
if (let ch = parser.get(); parser.nextMatch(IdStarts)):
parser.topLevelLast.attr("id", parser.parseId(ch))
parser.topLevelLast.add(newElem(p, parseSingleLine(parser, options)))
of '[':
# reference link
let initialPos = parser.pos
inc parser.pos
var refNameFailed = false
let refName = parseReferenceName(parser, options, refNameFailed)
if not refNameFailed and (inc parser.pos; parser.nextMatch(':')) and
(let (correct, link, tip) = parseLink(parser, options, failOnNewline = true);
correct): # smooth
for el in parser.linkReferrers.getOrDefault(refName, @[]):
if tip.len != 0:
el.attrEscaped("title", NativeString(tip))
parser.setLink(options, el, NativeString(link.strip()))
else:
parser.pos = initialPos
parser.topLevelLast = newElem(p, parseLine(parser, options))
elif parser.nextMatch("```"):
add(parseCodeBlock(parser, options, '`'))
elif parser.nextMatch("~~~"):
add(parseCodeBlock(parser, options, '~'))
item.attr("id", parser.parseId(ch))
item.add(parseSingleLine(parser, options))
list.add(item)
addContext(list)
else:
parser.topLevelLast = newElem(p, parseLine(parser, options))
if not parser.topLevelLast.isNil:
add(parser.topLevelLast)
parser.pos = originalPos
addLine()
of '>':
let quote = newElem(blockquote)
inc parser.pos
if (let ch = parser.get(); parser.nextMatch(IdStarts)):
quote.attr("id", parser.parseId(ch))
addContext(quote)
addLine()
of '[':
# reference link
let initialPos = parser.pos
inc parser.pos
var refNameFailed = false
let refName = parseReferenceName(parser, options, refNameFailed)
if not refNameFailed and (inc parser.pos; parser.nextMatch(':')) and
(let (correct, link, tip) = parseLink(parser, options, failOnNewline = true);
correct): # smooth
for el in parser.linkReferrers.getOrDefault(refName, @[]):
if tip.len != 0:
el.attrEscaped("title", NativeString(tip))
parser.setLink(options, el, NativeString(link.strip()))
else:
parser.pos = initialPos
addLine()
elif parser.nextMatch("```"):
addElement(parseCodeBlock(parser, options, '`'))
elif parser.nextMatch("~~~"):
addElement(parseCodeBlock(parser, options, '~'))
else:
addLine()

lastEmptyLine = false

when isMainModule:
import ../marggers
discard parseMarggers("# hello")
7 changes: 3 additions & 4 deletions src/marggers/parser/defs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ type
## Overriden by compile time options.
str*: NativeString # would be openarray[char] if cstring was compatible
pos*: int
topLevelLast*: MarggersElement
## Last element parsed at top level.
##
## Nil if the last element is complete, i.e. 2 newlines were parsed.
contextStack*: seq[MarggersElement]
## Stack of current top level contexts,
## like lists or blockquotes.
linkReferrers*: Table[NativeString, seq[MarggersElement]]
## Table of link references to elements that use the reference.
## During parsing, when a reference link is found, it will modify
Expand Down
4 changes: 2 additions & 2 deletions tests/files/test1.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ <h4> Projects</h4>
</tr>
</tbody></table></p>
<blockquote><p> a
b
c</p></blockquote>
b</p></blockquote>
<p>c</p>
<p>def</p>
<p>inline code block test <pre>ignore lang for now
inline code block still
Expand Down

0 comments on commit 1dbd7cb

Please sign in to comment.