parse.ts (3227B)
1 import { parseInline } from '@/lib/markup/parse_inline' 2 import { ListType, Token } from '@/lib/markup/token' 3 4 const regexHeading = /^(#{1,6})\s+([^\n]+)\s*(?:$|\n)/ 5 const regexUnorderedListItem = /^([*-]\s[^\n]+(?:\n {2}[^\n]*)*)(?:$|\n)/ 6 const regexOrderedListItem = /^(1\.\s[^\n]+(?:\n {3}[^\n]*)*)(?:$|\n)/ 7 const regexHorizontalRule = /^-{3,}\s*(?:$|\n)/ 8 const regexBlockquote = /^((?:(?:^|\n)>\s[^\n]+)+)\s*(?:$|\n)/ 9 const regexParagraph = /^((?:(?:^|\n[ \t]*)\S[^\n]*)+)\s*(?:$|\n)/ 10 const regexText = /^([^\n]+)(?:$|\n)/ 11 const regexSpaces = /^(?:\s+(?:$|\n)|\n)/g 12 13 function stripPrefix (text: string, prefix: string) { 14 return text.replace(new RegExp(`^${prefix}`, 'gm'), '') 15 } 16 17 function parseBlock (src: string, useParagraph: boolean): Token[] { 18 const out: Token[] = [] 19 20 let caps: RegExpExecArray | null 21 for (; src.length > 0;) { 22 caps = regexSpaces.exec(src) 23 if (caps != null) { 24 src = src.slice(caps[0].length) 25 continue 26 } 27 28 caps = regexHeading.exec(src) 29 if (caps != null) { 30 out.push({ 31 key: 'heading', 32 level: caps[1].length, 33 children: parseInline(caps[2]), 34 }) 35 36 src = src.slice(caps[0].length) 37 continue 38 } 39 40 caps = regexHorizontalRule.exec(src) 41 if (caps != null) { 42 out.push({ key: 'horizontal-rule' }) 43 src = src.slice(caps[0].length) 44 continue 45 } 46 47 caps = regexUnorderedListItem.exec(src) 48 if (caps != null) { 49 src = parseList(src, 'unordered', out) 50 continue 51 } 52 53 caps = regexOrderedListItem.exec(src) 54 if (caps != null) { 55 src = parseList(src, 'ordered', out) 56 continue 57 } 58 59 caps = regexBlockquote.exec(src) 60 if (caps != null) { 61 const innerSrc = stripPrefix(caps[1], '>(?: |(?=\n))') 62 63 out.push({ 64 key: 'blockquote', 65 children: parseBlock(innerSrc, true), 66 }) 67 68 src = src.slice(caps[0].length) 69 continue 70 } 71 72 if (useParagraph) { 73 caps = regexParagraph.exec(src) 74 if (caps != null) { 75 out.push({ key: 'paragraph', children: parseInline(caps[1]) }) 76 77 src = src.slice(caps[0].length) 78 continue 79 } 80 } else { 81 caps = regexText.exec(src) 82 if (caps != null) { 83 out.push({ key: 'text', children: parseInline(caps[1]) }) 84 85 src = src.slice(caps[0].length) 86 continue 87 } 88 } 89 90 throw new Error(`unreachable`) 91 } 92 93 return out 94 } 95 96 function parseList (src: string, listType: ListType, out: Token[]): string { 97 const regexListItem = listType === 'ordered' 98 ? regexOrderedListItem 99 : regexUnorderedListItem 100 101 const listItems: Token[] = [] 102 103 let caps: RegExpExecArray | null 104 for (; src.length > 0;) { 105 caps = regexListItem.exec(src) 106 if (caps != null) { 107 const listSrc = stripPrefix( 108 caps[1], 109 listType === 'ordered' 110 ? '.{3}' 111 : '.{2}', 112 ) 113 114 listItems.push({ 115 key: 'list-item', 116 children: parseBlock(listSrc, false), 117 }) 118 119 src = src.slice(caps[0].length) 120 continue 121 } 122 123 break 124 } 125 126 out.push({ 127 key: 'list', 128 listType, 129 children: listItems, 130 }) 131 132 return src 133 } 134 135 export function parse (src: string): Token[] { 136 return parseBlock(src, true) 137 }