Skip to content

Commit

Permalink
handle “almost thematic break” pathological input
Browse files Browse the repository at this point in the history
  • Loading branch information
loiclec committed Oct 30, 2016
1 parent 49044fc commit 4c11766
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 32 deletions.
63 changes: 40 additions & 23 deletions Sources/Apodimark/BlockParsing/BlockParsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//

enum ListState {
case normal, followedByEmptyLine, closed
case normal, followedByEmptyLine, closed, lastLeafIsCodeBlock
}

enum AddLineResult {
Expand All @@ -17,7 +17,7 @@ extension MarkdownParser {
func parseBlocks() {
var scanner = Scanner(data: view)
while case .some = scanner.peek() {
_ = add(line: MarkdownParser.parseLine(&scanner))
_ = add(line: MarkdownParser.parseLine(&scanner, context: .default))
// TODO: handle different line endings than LF
scanner.popUntil(Codec.linefeed)
_ = scanner.pop(Codec.linefeed)
Expand Down Expand Up @@ -168,13 +168,13 @@ extension MarkdownParser {
extension MarkdownParser {

fileprivate func preparedLine(from initialLine: Line<View>, for list: ListNode<View>) -> Line<View>? {
guard list.state != .closed else {
return nil
}
var line = initialLine
guard !initialLine.kind.isEmpty() else {
return line
}
guard list.state != .closed else {
return nil
}
guard !(initialLine.indent.level >= list.minimumIndent + TAB_INDENT && list._allowsLazyContinuations) else {
line.indent.level -= list.minimumIndent
return line
Expand Down Expand Up @@ -208,27 +208,44 @@ extension MarkdownParser {
switch preparedLine.kind {

case .empty:

guard case .normal = list.state else {
guard case let lastLeaf = blockTree.buffer.last!.data,
case let .fence(fenceNode) = lastLeaf
else {
list.state = .closed
list._allowsLazyContinuations = false
return
list._allowsLazyContinuations = false

switch list.state {

case .lastLeafIsCodeBlock:
// optimization to avoid deeply nested list + fence + empty lines worst case scenario
let lastLeaf = blockTree.buffer.last!.data
switch lastLeaf {
case .code(let c) : _ = add(line: preparedLine, to: c)
case .fence(let f): _ = add(line: preparedLine, to: f)
default:
fatalError()
}
_ = add(line: preparedLine, to: fenceNode)
return
}

let itemContentLevel = listLevel.incremented().incremented()
guard let lastItemContent = blockTree.last(depthLevel: itemContentLevel) else {
return
case .normal:
let itemContentLevel = listLevel.incremented().incremented()
let lastItemContent = blockTree.last(depthLevel: itemContentLevel)

let result = lastItemContent.map { add(line: preparedLine, to: $0, depthLevel: itemContentLevel) } ?? .failure
list._allowsLazyContinuations = false

let lastLeaf = blockTree.buffer.last!.data
switch lastLeaf {
case .fence, .code:
list.state = .lastLeafIsCodeBlock
default:
switch result {
case .success: list.state = list.state == .normal ? .followedByEmptyLine : .closed
case .failure: list.state = .closed
}
}

case .followedByEmptyLine:
list.state = .closed
case .closed:
break
}
list.state = .followedByEmptyLine
_ = add(line: preparedLine, to: lastItemContent, depthLevel: itemContentLevel)
list._allowsLazyContinuations = lastItemContent.allowsLazyContinuation()


case .list(let kind, let rest) where preparedLine.indent.level < 0:
list.state = .normal
list.minimumIndent += preparedLine.indent.level + kind.width + 1
Expand Down
27 changes: 20 additions & 7 deletions Sources/Apodimark/BlockParsing/LineLexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ extension MarkdownParser {
/// - parameter scanner: a scanner whose `startIndex` points to the start of potential List line
/// - parameter indent: the indent of the line being parsed
/// - return: the parsed Line
fileprivate static func parseList(_ scanner: inout Scanner<View>, indent: Indent) -> Line<View> {
fileprivate static func parseList(_ scanner: inout Scanner<View>, indent: Indent, context: LineLexerContext<Codec>) -> Line<View> {

let indexBeforeList = scanner.startIndex
// let initialSubView = scanner
Expand All @@ -109,7 +109,7 @@ extension MarkdownParser {
// 1234)
// |_<---

let rest = parseLine(&scanner)
let rest = parseLine(&scanner, context: context)
return Line(.list(kind, rest), indent, indexBeforeList ..< scanner.startIndex)
}
catch ListParsingError.notAListMarker {
Expand Down Expand Up @@ -496,8 +496,19 @@ extension MarkdownParser {

return Line(.reference(title, definition), indent, indexBeforeRefDef ..< scanner.startIndex)
}
}


static func parseLine(_ scanner: inout Scanner<View>) -> Line<View> {
struct LineLexerContext <C: MarkdownParserCodec> {
var listKindBeingRead: C.CodeUnit?

static var `default`: LineLexerContext {
return .init(listKindBeingRead: nil)
}
}

extension MarkdownParser {
static func parseLine(_ scanner: inout Scanner<View>, context: LineLexerContext<Codec>) -> Line<View> {
// xxxxx
// |_<--- (start of line)

Expand Down Expand Up @@ -527,7 +538,7 @@ extension MarkdownParser {

case Codec.quote:
_ = scanner.pop()!
let rest = parseLine(&scanner)
let rest = parseLine(&scanner, context: .default)
return Line(.quote(rest), indent, indexAfterIndent ..< scanner.startIndex)


Expand All @@ -540,15 +551,17 @@ extension MarkdownParser {


case Codec.hyphen, Codec.asterisk:
if case .some = try? readThematicBreak(&scanner, firstToken: firstToken) {
if firstToken != context.listKindBeingRead, case .some = try? readThematicBreak(&scanner, firstToken: firstToken) {
return Line(.thematicBreak, indent, indexAfterIndent ..< scanner.startIndex)
} else {
return parseList(&scanner, indent: indent)
var context = context
context.listKindBeingRead = firstToken
return parseList(&scanner, indent: indent, context: context)
}


case Codec.plus, Codec.zero...Codec.nine:
return parseList(&scanner, indent: indent)
return parseList(&scanner, indent: indent, context: context)


case Codec.hash:
Expand Down
2 changes: 1 addition & 1 deletion Tests/PerformanceTests/PerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ private func baseStringForTest(_ name: String, result: Bool = false) -> String {
}

private func testString(size: Int) -> String {
let base = baseStringForTest("mixed1")
let base = baseStringForTest("deeply-nested-lists")
var s = ""
while s.utf16.count < size {
s += base
Expand Down
5 changes: 4 additions & 1 deletion test-files/commonmark-conformance/222-result.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
Document { List[Bullet] { Item { Paragraph(Foo), Code[bar], }, }, Code[ baz], }
Document { List[Bullet] { Item { Paragraph(Foo), Code[bar


baz], }, }, }
1 change: 1 addition & 0 deletions test-files/performance/almost-thematic-break.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - hello

0 comments on commit 4c11766

Please sign in to comment.