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