diff --git a/par/md.test.ts b/par/md.test.ts index 0bc6c39..8852521 100644 --- a/par/md.test.ts +++ b/par/md.test.ts @@ -8,6 +8,7 @@ const ren = new HtmlStrRenderer(); Deno.test({ name: "should skip empty line", + only: true, fn: () => { const par = new MarkdownParser(); assertEquals(ren.render(par.parse("\n")), ""); @@ -21,15 +22,30 @@ Deno.test({ Deno.test({ name: "should parse empty ATX header", + only: true, fn: () => { const par = new MarkdownParser(); - const res = par.parse("#"); - assertEquals(ren.render(res), "

"); + assertEquals(ren.render(par.parse("#")), "

"); + assertEquals(ren.render(par.parse("##")), "

"); + }, +}); + +Deno.test({ + name: "should parse ATX header if line contains additional spaces", + only: true, + fn: () => { + const par = new MarkdownParser(); + assertEquals(ren.render(par.parse(" #")), "

"); + assertEquals(ren.render(par.parse(" #")), "

"); + assertEquals(ren.render(par.parse(" ##")), "

"); + assertEquals(ren.render(par.parse(" #")), "

"); + assertEquals(ren.render(par.parse(" ##")), "

"); }, }); Deno.test({ name: "should parse ATX header with text", + only: true, fn: () => { const par = new MarkdownParser(); assertEquals(ren.render(par.parse("# hello")), "

hello

"); @@ -50,16 +66,6 @@ Deno.test({ }, }); -Deno.test({ - name: "should parse ATX header if line contains additional spaces", - fn: () => { - const par = new MarkdownParser(); - assertEquals(ren.render(par.parse(" # hello")), "

hello

"); - assertEquals(ren.render(par.parse(" # hello")), "

hello

"); - assertEquals(ren.render(par.parse(" # hello")), "

hello

"); - }, -}); - Deno.test({ name: "should parse ATX header with closing sequence", fn: () => { diff --git a/par/md.ts b/par/md.ts index 9eadfc8..e30b447 100644 --- a/par/md.ts +++ b/par/md.ts @@ -23,6 +23,8 @@ import { Parser } from "./types.ts"; const RE_EMPTY_LINE = /^[ ]*\r?\n/; +const RE_ATX_HEADING = /^[ ]{0,3}(#{1,6})(?:$|([ ].+)(?:[ ]?#*[ ]*)?$)/; + const RE_OPEN_ATX_HEADING = /^[ ]{0,3}(#{1,6})([ ]|$)/; const RE_CLOSE_ATX_HEADING = /(^|[ ]+)#*[ ]*$/; @@ -35,20 +37,93 @@ export class MarkdownParser implements Parser { parse(input: string): AnyNode { const astDoc: AstDocument = { kind: AstKind.Document, content: [] }; - let readStr = input; - while (readStr.length) { - const newReadStr = skipEmptyLine(readStr) ?? - parseAtxHeading(astDoc, readStr) ?? - parseList(astDoc, readStr) ?? - parseParagraph(astDoc, readStr); - if (isNil(newReadStr)) break; - readStr = newReadStr; + const segments = splitLines(input); + + console.log({ segments }); + + const res = parseHeaderFromSegments(segments); + if (res != null) { + astDoc.content.push(res.ast); } + // let readStr = input; + // while (readStr.length) { + // const newReadStr = skipEmptyLine(readStr) ?? + // parseAtxHeading(astDoc, readStr) ?? + // parseList(astDoc, readStr) ?? + // parseParagraph(astDoc, readStr); + // if (isNil(newReadStr)) break; + // readStr = newReadStr; + // } + return new Fragment(astDoc.content.map(DocChild)); } } +function parseHeaderFromSegments( + lines: Lines, +): ParsedRes | null { + const headingMatch = RE_ATX_HEADING.exec(lines[0]); + if (headingMatch == null) return null; + + const inlineContent = parseInlineContentFromSegments([headingMatch[2]]); + + const ast: AstAtxHeading = { + kind: AstKind.AtxHeading, + content: [], + level: headingMatch[1].length as HeadingLevel, + }; + + return { + ast, + restLines: lines.slice(1), + }; +} + +function parseInlineContentFromSegments( + lines: Lines, +): ParsedRes | null { + if (!lines.length) return null; + + const linkMatch = RE_LINK.exec(readStr); + if (!isNil(linkMatch)) { + const astLink: AstLink = { + kind: AstKind.Link, + destination: encodeURI(linkMatch[3] ?? linkMatch[2]), + title: linkMatch[5], + content: [], + }; + + // 1. parse before link + parseText(ast, readStr.slice(0, linkMatch.index)); + + // 2. create link and parse inner content for link + ast.content.push(astLink); + parseText(astLink, linkMatch[1]); + + // 3. parse rest text + return parseInlineContent( + ast, + readStr.slice(linkMatch.index + linkMatch[0].length), + ); + } else { + return parseText(ast, readStr); + } +} + +interface ParsedRes { + ast: T; + restLines: Lines; +} + +function splitLines(input: string): Lines { + return input.split("\n"); +} + +type Lines = string[]; + +// ---- OLD + function List(ast: AstList): Elem { // switch (ast.kind) return BulletList(ast);