codekingpro/portable-devtools
114k
1"""Balance paired characters (*, _, etc) in inline tokens."""2 3from __future__ import annotations4 5from .state_inline import Delimiter, StateInline6 7 8def processDelimiters(state: StateInline, delimiters: list[Delimiter]) -> None:9 """For each opening emphasis-like marker find a matching closing one."""10 if not delimiters:11 return12 13 openersBottom = {}14 maximum = len(delimiters)15 16 # headerIdx is the first delimiter of the current (where closer is) delimiter run17 headerIdx = 018 lastTokenIdx = -2 # needs any value lower than -119 jumps: list[int] = []20 closerIdx = 021 while closerIdx < maximum:22 closer = delimiters[closerIdx]23 24 jumps.append(0)25 26 # markers belong to same delimiter run if:27 # - they have adjacent tokens28 # - AND markers are the same29 #30 if (31 delimiters[headerIdx].marker != closer.marker32 or lastTokenIdx != closer.token - 133 ):34 headerIdx = closerIdx35 lastTokenIdx = closer.token36 37 # Length is only used for emphasis-specific "rule of 3",38 # if it's not defined (in strikethrough or 3rd party plugins),39 # we can default it to 0 to disable those checks.40 #41 closer.length = closer.length or 042 43 if not closer.close:44 closerIdx += 145 continue46 47 # Previously calculated lower bounds (previous fails)48 # for each marker, each delimiter length modulo 3,49 # and for whether this closer can be an opener;50 # https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c7244346051 if closer.marker not in openersBottom:52 openersBottom[closer.marker] = [-1, -1, -1, -1, -1, -1]53 54 minOpenerIdx = openersBottom[closer.marker][55 (3 if closer.open else 0) + (closer.length % 3)56 ]57 58 openerIdx = headerIdx - jumps[headerIdx] - 159 60 newMinOpenerIdx = openerIdx61 62 while openerIdx > minOpenerIdx:63 opener = delimiters[openerIdx]64 65 if opener.marker != closer.marker:66 openerIdx -= jumps[openerIdx] + 167 continue68 69 if opener.open and opener.end < 0:70 isOddMatch = False71 72 # from spec:73 #74 # If one of the delimiters can both open and close emphasis, then the75 # sum of the lengths of the delimiter runs containing the opening and76 # closing delimiters must not be a multiple of 3 unless both lengths77 # are multiples of 3.78 #79 if (80 (opener.close or closer.open)81 and ((opener.length + closer.length) % 3 == 0)82 and (opener.length % 3 != 0 or closer.length % 3 != 0)83 ):84 isOddMatch = True85 86 if not isOddMatch:87 # If previous delimiter cannot be an opener, we can safely skip88 # the entire sequence in future checks. This is required to make89 # sure algorithm has linear complexity (see *_*_*_*_*_... case).90 #91 if openerIdx > 0 and not delimiters[openerIdx - 1].open:92 lastJump = jumps[openerIdx - 1] + 193 else:94 lastJump = 095 96 jumps[closerIdx] = closerIdx - openerIdx + lastJump97 jumps[openerIdx] = lastJump98 99 closer.open = False100 opener.end = closerIdx101 opener.close = False102 newMinOpenerIdx = -1103 104 # treat next token as start of run,105 # it optimizes skips in **<...>**a**<...>** pathological case106 lastTokenIdx = -2107 108 break109 110 openerIdx -= jumps[openerIdx] + 1111 112 if newMinOpenerIdx != -1:113 # If match for this delimiter run failed, we want to set lower bound for114 # future lookups. This is required to make sure algorithm has linear115 # complexity.116 #117 # See details here:118 # https:#github.com/commonmark/cmark/issues/178#issuecomment-270417442119 #120 openersBottom[closer.marker][121 (3 if closer.open else 0) + ((closer.length or 0) % 3)122 ] = newMinOpenerIdx123 124 closerIdx += 1125 126 127def link_pairs(state: StateInline) -> None:128 tokens_meta = state.tokens_meta129 maximum = len(state.tokens_meta)130 131 processDelimiters(state, state.delimiters)132 133 curr = 0134 while curr < maximum:135 curr_meta = tokens_meta[curr]136 if curr_meta and "delimiters" in curr_meta:137 processDelimiters(state, curr_meta["delimiters"])138 curr += 1139 