Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol diffdown.com
1import {Tree} from "@lezer/common" 2import {MarkdownParser} from ".." 3 4const abbrev: {[abbr: string]: string} = { 5 __proto__: null as any, 6 CB: "CodeBlock", 7 FC: "FencedCode", 8 Q: "Blockquote", 9 HR: "HorizontalRule", 10 BL: "BulletList", 11 OL: "OrderedList", 12 LI: "ListItem", 13 H1: "ATXHeading1", 14 H2: "ATXHeading2", 15 H3: "ATXHeading3", 16 H4: "ATXHeading4", 17 H5: "ATXHeading5", 18 H6: "ATXHeading6", 19 SH1: "SetextHeading1", 20 SH2: "SetextHeading2", 21 HB: "HTMLBlock", 22 PI: "ProcessingInstructionBlock", 23 CMB: "CommentBlock", 24 LR: "LinkReference", 25 P: "Paragraph", 26 Esc: "Escape", 27 Ent: "Entity", 28 BR: "HardBreak", 29 Em: "Emphasis", 30 St: "StrongEmphasis", 31 Ln: "Link", 32 Al: "Autolink", 33 Im: "Image", 34 C: "InlineCode", 35 HT: "HTMLTag", 36 CM: "Comment", 37 Pi: "ProcessingInstruction", 38 h: "HeaderMark", 39 q: "QuoteMark", 40 l: "ListMark", 41 L: "LinkMark", 42 e: "EmphasisMark", 43 c: "CodeMark", 44 cI: "CodeInfo", 45 cT: "CodeText", 46 LT: "LinkTitle", 47 LL: "LinkLabel" 48} 49 50export class SpecParser { 51 constructor(readonly parser: MarkdownParser, readonly localAbbrev?: {[name: string]: string}) {} 52 53 type(name: string) { 54 name = (this.localAbbrev && this.localAbbrev[name]) || abbrev[name] || name 55 return this.parser.nodeSet.types.find(t => t.name == name)?.id 56 } 57 58 parse(spec: string, specName: string) { 59 let doc = "", buffer = [], stack: number[] = [] 60 for (let pos = 0; pos < spec.length; pos++) { 61 let ch = spec[pos] 62 if (ch == "{") { 63 let name = /^(\w+):/.exec(spec.slice(pos + 1)), tag = name && this.type(name[1]) 64 if (tag == null) throw new Error(`Invalid node opening mark at ${pos} in ${specName}`) 65 pos += name![0].length 66 stack.push(tag, doc.length, buffer.length) 67 } else if (ch == "}") { 68 if (!stack.length) throw new Error(`Mismatched node close mark at ${pos} in ${specName}`) 69 let bufStart = stack.pop()!, from = stack.pop()!, type = stack.pop()! 70 buffer.push(type, from, doc.length, 4 + buffer.length - bufStart) 71 } else { 72 doc += ch 73 } 74 } 75 if (stack.length) throw new Error(`Unclosed node in ${specName}`) 76 return {tree: Tree.build({buffer, nodeSet: this.parser.nodeSet, topID: this.type("Document")!, length: doc.length}), doc} 77 } 78} 79