dh_demo

DreamHanks demo project
git clone git://git.lair.cx/dh_demo
Log | Files | Refs | README

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 }