import { AnyNode, Elem, Fragment, TextNode } from "../core/node.ts"; import { isNil } from "../core/utils.ts"; import { Parser } from "./types.ts"; const RE_OPEN_ATX_HEADING = /^\s{0,3}(#{1,6})(\s|$)/; const RE_CLOSE_ATX_HEADING = /(^|\s+)#*\s*$/; export class MarkdownParser implements Parser { parse(input: string): AnyNode { const ast: AstDocument = { kind: AstKind.Document, content: [] }; let readStr = input; const match = RE_OPEN_ATX_HEADING.exec(readStr); if (!isNil(match)) { readStr = readStr.slice(match[0].length); const atxHeading: AstAtxHeading = { kind: AstKind.AtxHeading, level: match[1].length as HeadingLevel, content: [], }; ast.content.push(atxHeading); if (match[2].length > 0) { const endMatch = RE_CLOSE_ATX_HEADING.exec(readStr); const headingContent = isNil(endMatch) ? readStr.split("\n", 1)[0] : readStr.slice(0, endMatch.index); readStr = readStr.slice(headingContent.length); if (headingContent.trim().length) { const text: AstText = { kind: AstKind.Text, content: headingContent.trim(), }; atxHeading.content.push(text); } } } return new Fragment(ast.content.map(Heading)); } } function Heading(ast: AstAtxHeading): Elem { return new Elem(`h${ast.level}`, {}, ast.content.map(Text)); } function Text(ast: AstText): TextNode { return new TextNode(ast.content); } // AST type AstDocument = BaseAstItem; type AstDocumentChild = AstAtxHeading; interface AstAtxHeading extends BaseAstItem { level: HeadingLevel; } type AstText = BaseAstItem; type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6; interface BaseAstItem { kind: K; content: Cont; } enum AstKind { Document, AtxHeading, Text, }