Elia Battiston | be9edc1 | 2022-03-09 11:35:58 +0100 | [diff] [blame] | 1 | // Copyright 2015 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | package bidi |
| 6 | |
| 7 | import ( |
| 8 | "container/list" |
| 9 | "fmt" |
| 10 | "sort" |
| 11 | ) |
| 12 | |
| 13 | // This file contains a port of the reference implementation of the |
| 14 | // Bidi Parentheses Algorithm: |
| 15 | // https://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/BidiPBAReference.java |
| 16 | // |
| 17 | // The implementation in this file covers definitions BD14-BD16 and rule N0 |
| 18 | // of UAX#9. |
| 19 | // |
| 20 | // Some preprocessing is done for each rune before data is passed to this |
| 21 | // algorithm: |
| 22 | // - opening and closing brackets are identified |
| 23 | // - a bracket pair type, like '(' and ')' is assigned a unique identifier that |
| 24 | // is identical for the opening and closing bracket. It is left to do these |
| 25 | // mappings. |
| 26 | // - The BPA algorithm requires that bracket characters that are canonical |
| 27 | // equivalents of each other be able to be substituted for each other. |
| 28 | // It is the responsibility of the caller to do this canonicalization. |
| 29 | // |
| 30 | // In implementing BD16, this implementation departs slightly from the "logical" |
| 31 | // algorithm defined in UAX#9. In particular, the stack referenced there |
| 32 | // supports operations that go beyond a "basic" stack. An equivalent |
| 33 | // implementation based on a linked list is used here. |
| 34 | |
| 35 | // Bidi_Paired_Bracket_Type |
| 36 | // BD14. An opening paired bracket is a character whose |
| 37 | // Bidi_Paired_Bracket_Type property value is Open. |
| 38 | // |
| 39 | // BD15. A closing paired bracket is a character whose |
| 40 | // Bidi_Paired_Bracket_Type property value is Close. |
| 41 | type bracketType byte |
| 42 | |
| 43 | const ( |
| 44 | bpNone bracketType = iota |
| 45 | bpOpen |
| 46 | bpClose |
| 47 | ) |
| 48 | |
| 49 | // bracketPair holds a pair of index values for opening and closing bracket |
| 50 | // location of a bracket pair. |
| 51 | type bracketPair struct { |
| 52 | opener int |
| 53 | closer int |
| 54 | } |
| 55 | |
| 56 | func (b *bracketPair) String() string { |
| 57 | return fmt.Sprintf("(%v, %v)", b.opener, b.closer) |
| 58 | } |
| 59 | |
| 60 | // bracketPairs is a slice of bracketPairs with a sort.Interface implementation. |
| 61 | type bracketPairs []bracketPair |
| 62 | |
| 63 | func (b bracketPairs) Len() int { return len(b) } |
| 64 | func (b bracketPairs) Swap(i, j int) { b[i], b[j] = b[j], b[i] } |
| 65 | func (b bracketPairs) Less(i, j int) bool { return b[i].opener < b[j].opener } |
| 66 | |
| 67 | // resolvePairedBrackets runs the paired bracket part of the UBA algorithm. |
| 68 | // |
| 69 | // For each rune, it takes the indexes into the original string, the class the |
| 70 | // bracket type (in pairTypes) and the bracket identifier (pairValues). It also |
| 71 | // takes the direction type for the start-of-sentence and the embedding level. |
| 72 | // |
| 73 | // The identifiers for bracket types are the rune of the canonicalized opening |
| 74 | // bracket for brackets (open or close) or 0 for runes that are not brackets. |
| 75 | func resolvePairedBrackets(s *isolatingRunSequence) { |
| 76 | p := bracketPairer{ |
| 77 | sos: s.sos, |
| 78 | openers: list.New(), |
| 79 | codesIsolatedRun: s.types, |
| 80 | indexes: s.indexes, |
| 81 | } |
| 82 | dirEmbed := L |
| 83 | if s.level&1 != 0 { |
| 84 | dirEmbed = R |
| 85 | } |
| 86 | p.locateBrackets(s.p.pairTypes, s.p.pairValues) |
| 87 | p.resolveBrackets(dirEmbed, s.p.initialTypes) |
| 88 | } |
| 89 | |
| 90 | type bracketPairer struct { |
| 91 | sos Class // direction corresponding to start of sequence |
| 92 | |
| 93 | // The following is a restatement of BD 16 using non-algorithmic language. |
| 94 | // |
| 95 | // A bracket pair is a pair of characters consisting of an opening |
| 96 | // paired bracket and a closing paired bracket such that the |
| 97 | // Bidi_Paired_Bracket property value of the former equals the latter, |
| 98 | // subject to the following constraints. |
| 99 | // - both characters of a pair occur in the same isolating run sequence |
| 100 | // - the closing character of a pair follows the opening character |
| 101 | // - any bracket character can belong at most to one pair, the earliest possible one |
| 102 | // - any bracket character not part of a pair is treated like an ordinary character |
| 103 | // - pairs may nest properly, but their spans may not overlap otherwise |
| 104 | |
| 105 | // Bracket characters with canonical decompositions are supposed to be |
| 106 | // treated as if they had been normalized, to allow normalized and non- |
| 107 | // normalized text to give the same result. In this implementation that step |
| 108 | // is pushed out to the caller. The caller has to ensure that the pairValue |
| 109 | // slices contain the rune of the opening bracket after normalization for |
| 110 | // any opening or closing bracket. |
| 111 | |
| 112 | openers *list.List // list of positions for opening brackets |
| 113 | |
| 114 | // bracket pair positions sorted by location of opening bracket |
| 115 | pairPositions bracketPairs |
| 116 | |
| 117 | codesIsolatedRun []Class // directional bidi codes for an isolated run |
| 118 | indexes []int // array of index values into the original string |
| 119 | |
| 120 | } |
| 121 | |
| 122 | // matchOpener reports whether characters at given positions form a matching |
| 123 | // bracket pair. |
| 124 | func (p *bracketPairer) matchOpener(pairValues []rune, opener, closer int) bool { |
| 125 | return pairValues[p.indexes[opener]] == pairValues[p.indexes[closer]] |
| 126 | } |
| 127 | |
| 128 | const maxPairingDepth = 63 |
| 129 | |
| 130 | // locateBrackets locates matching bracket pairs according to BD16. |
| 131 | // |
| 132 | // This implementation uses a linked list instead of a stack, because, while |
| 133 | // elements are added at the front (like a push) they are not generally removed |
| 134 | // in atomic 'pop' operations, reducing the benefit of the stack archetype. |
| 135 | func (p *bracketPairer) locateBrackets(pairTypes []bracketType, pairValues []rune) { |
| 136 | // traverse the run |
| 137 | // do that explicitly (not in a for-each) so we can record position |
| 138 | for i, index := range p.indexes { |
| 139 | |
| 140 | // look at the bracket type for each character |
| 141 | if pairTypes[index] == bpNone || p.codesIsolatedRun[i] != ON { |
| 142 | // continue scanning |
| 143 | continue |
| 144 | } |
| 145 | switch pairTypes[index] { |
| 146 | case bpOpen: |
| 147 | // check if maximum pairing depth reached |
| 148 | if p.openers.Len() == maxPairingDepth { |
| 149 | p.openers.Init() |
| 150 | return |
| 151 | } |
| 152 | // remember opener location, most recent first |
| 153 | p.openers.PushFront(i) |
| 154 | |
| 155 | case bpClose: |
| 156 | // see if there is a match |
| 157 | count := 0 |
| 158 | for elem := p.openers.Front(); elem != nil; elem = elem.Next() { |
| 159 | count++ |
| 160 | opener := elem.Value.(int) |
| 161 | if p.matchOpener(pairValues, opener, i) { |
| 162 | // if the opener matches, add nested pair to the ordered list |
| 163 | p.pairPositions = append(p.pairPositions, bracketPair{opener, i}) |
| 164 | // remove up to and including matched opener |
| 165 | for ; count > 0; count-- { |
| 166 | p.openers.Remove(p.openers.Front()) |
| 167 | } |
| 168 | break |
| 169 | } |
| 170 | } |
| 171 | sort.Sort(p.pairPositions) |
| 172 | // if we get here, the closing bracket matched no openers |
| 173 | // and gets ignored |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | // Bracket pairs within an isolating run sequence are processed as units so |
| 179 | // that both the opening and the closing paired bracket in a pair resolve to |
| 180 | // the same direction. |
| 181 | // |
| 182 | // N0. Process bracket pairs in an isolating run sequence sequentially in |
| 183 | // the logical order of the text positions of the opening paired brackets |
| 184 | // using the logic given below. Within this scope, bidirectional types EN |
| 185 | // and AN are treated as R. |
| 186 | // |
| 187 | // Identify the bracket pairs in the current isolating run sequence |
| 188 | // according to BD16. For each bracket-pair element in the list of pairs of |
| 189 | // text positions: |
| 190 | // |
| 191 | // a Inspect the bidirectional types of the characters enclosed within the |
| 192 | // bracket pair. |
| 193 | // |
| 194 | // b If any strong type (either L or R) matching the embedding direction is |
| 195 | // found, set the type for both brackets in the pair to match the embedding |
| 196 | // direction. |
| 197 | // |
| 198 | // o [ e ] o -> o e e e o |
| 199 | // |
| 200 | // o [ o e ] -> o e o e e |
| 201 | // |
| 202 | // o [ NI e ] -> o e NI e e |
| 203 | // |
| 204 | // c Otherwise, if a strong type (opposite the embedding direction) is |
| 205 | // found, test for adjacent strong types as follows: 1 First, check |
| 206 | // backwards before the opening paired bracket until the first strong type |
| 207 | // (L, R, or sos) is found. If that first preceding strong type is opposite |
| 208 | // the embedding direction, then set the type for both brackets in the pair |
| 209 | // to that type. 2 Otherwise, set the type for both brackets in the pair to |
| 210 | // the embedding direction. |
| 211 | // |
| 212 | // o [ o ] e -> o o o o e |
| 213 | // |
| 214 | // o [ o NI ] o -> o o o NI o o |
| 215 | // |
| 216 | // e [ o ] o -> e e o e o |
| 217 | // |
| 218 | // e [ o ] e -> e e o e e |
| 219 | // |
| 220 | // e ( o [ o ] NI ) e -> e e o o o o NI e e |
| 221 | // |
| 222 | // d Otherwise, do not set the type for the current bracket pair. Note that |
| 223 | // if the enclosed text contains no strong types the paired brackets will |
| 224 | // both resolve to the same level when resolved individually using rules N1 |
| 225 | // and N2. |
| 226 | // |
| 227 | // e ( NI ) o -> e ( NI ) o |
| 228 | |
| 229 | // getStrongTypeN0 maps character's directional code to strong type as required |
| 230 | // by rule N0. |
| 231 | // |
| 232 | // TODO: have separate type for "strong" directionality. |
| 233 | func (p *bracketPairer) getStrongTypeN0(index int) Class { |
| 234 | switch p.codesIsolatedRun[index] { |
| 235 | // in the scope of N0, number types are treated as R |
| 236 | case EN, AN, AL, R: |
| 237 | return R |
| 238 | case L: |
| 239 | return L |
| 240 | default: |
| 241 | return ON |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | // classifyPairContent reports the strong types contained inside a Bracket Pair, |
| 246 | // assuming the given embedding direction. |
| 247 | // |
| 248 | // It returns ON if no strong type is found. If a single strong type is found, |
| 249 | // it returns this type. Otherwise it returns the embedding direction. |
| 250 | // |
| 251 | // TODO: use separate type for "strong" directionality. |
| 252 | func (p *bracketPairer) classifyPairContent(loc bracketPair, dirEmbed Class) Class { |
| 253 | dirOpposite := ON |
| 254 | for i := loc.opener + 1; i < loc.closer; i++ { |
| 255 | dir := p.getStrongTypeN0(i) |
| 256 | if dir == ON { |
| 257 | continue |
| 258 | } |
| 259 | if dir == dirEmbed { |
| 260 | return dir // type matching embedding direction found |
| 261 | } |
| 262 | dirOpposite = dir |
| 263 | } |
| 264 | // return ON if no strong type found, or class opposite to dirEmbed |
| 265 | return dirOpposite |
| 266 | } |
| 267 | |
| 268 | // classBeforePair determines which strong types are present before a Bracket |
| 269 | // Pair. Return R or L if strong type found, otherwise ON. |
| 270 | func (p *bracketPairer) classBeforePair(loc bracketPair) Class { |
| 271 | for i := loc.opener - 1; i >= 0; i-- { |
| 272 | if dir := p.getStrongTypeN0(i); dir != ON { |
| 273 | return dir |
| 274 | } |
| 275 | } |
| 276 | // no strong types found, return sos |
| 277 | return p.sos |
| 278 | } |
| 279 | |
| 280 | // assignBracketType implements rule N0 for a single bracket pair. |
| 281 | func (p *bracketPairer) assignBracketType(loc bracketPair, dirEmbed Class, initialTypes []Class) { |
| 282 | // rule "N0, a", inspect contents of pair |
| 283 | dirPair := p.classifyPairContent(loc, dirEmbed) |
| 284 | |
| 285 | // dirPair is now L, R, or N (no strong type found) |
| 286 | |
| 287 | // the following logical tests are performed out of order compared to |
| 288 | // the statement of the rules but yield the same results |
| 289 | if dirPair == ON { |
| 290 | return // case "d" - nothing to do |
| 291 | } |
| 292 | |
| 293 | if dirPair != dirEmbed { |
| 294 | // case "c": strong type found, opposite - check before (c.1) |
| 295 | dirPair = p.classBeforePair(loc) |
| 296 | if dirPair == dirEmbed || dirPair == ON { |
| 297 | // no strong opposite type found before - use embedding (c.2) |
| 298 | dirPair = dirEmbed |
| 299 | } |
| 300 | } |
| 301 | // else: case "b", strong type found matching embedding, |
| 302 | // no explicit action needed, as dirPair is already set to embedding |
| 303 | // direction |
| 304 | |
| 305 | // set the bracket types to the type found |
| 306 | p.setBracketsToType(loc, dirPair, initialTypes) |
| 307 | } |
| 308 | |
| 309 | func (p *bracketPairer) setBracketsToType(loc bracketPair, dirPair Class, initialTypes []Class) { |
| 310 | p.codesIsolatedRun[loc.opener] = dirPair |
| 311 | p.codesIsolatedRun[loc.closer] = dirPair |
| 312 | |
| 313 | for i := loc.opener + 1; i < loc.closer; i++ { |
| 314 | index := p.indexes[i] |
| 315 | if initialTypes[index] != NSM { |
| 316 | break |
| 317 | } |
| 318 | p.codesIsolatedRun[i] = dirPair |
| 319 | } |
| 320 | |
| 321 | for i := loc.closer + 1; i < len(p.indexes); i++ { |
| 322 | index := p.indexes[i] |
| 323 | if initialTypes[index] != NSM { |
| 324 | break |
| 325 | } |
| 326 | p.codesIsolatedRun[i] = dirPair |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | // resolveBrackets implements rule N0 for a list of pairs. |
| 331 | func (p *bracketPairer) resolveBrackets(dirEmbed Class, initialTypes []Class) { |
| 332 | for _, loc := range p.pairPositions { |
| 333 | p.assignBracketType(loc, dirEmbed, initialTypes) |
| 334 | } |
| 335 | } |