@keymanapp/kmc-model 17.0.85-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/.nyc_output/coverage-10524-1681239236645-0.json +1 -0
  2. package/Makefile +38 -0
  3. package/build/cjs-src/lexical-model-compiler.cjs +152688 -0
  4. package/build/src/build-trie.d.ts +40 -0
  5. package/build/src/build-trie.d.ts.map +1 -0
  6. package/build/src/build-trie.js +362 -0
  7. package/build/src/build-trie.js.map +1 -0
  8. package/build/src/join-word-breaker-decorator.d.ts +10 -0
  9. package/build/src/join-word-breaker-decorator.d.ts.map +1 -0
  10. package/build/src/join-word-breaker-decorator.js +121 -0
  11. package/build/src/join-word-breaker-decorator.js.map +1 -0
  12. package/build/src/lexical-model-compiler.d.ts +19 -0
  13. package/build/src/lexical-model-compiler.d.ts.map +1 -0
  14. package/build/src/lexical-model-compiler.js +155 -0
  15. package/build/src/lexical-model-compiler.js.map +1 -0
  16. package/build/src/lexical-model.d.ts +135 -0
  17. package/build/src/lexical-model.d.ts.map +1 -0
  18. package/build/src/lexical-model.js +6 -0
  19. package/build/src/lexical-model.js.map +1 -0
  20. package/build/src/main.d.ts +15 -0
  21. package/build/src/main.d.ts.map +1 -0
  22. package/build/src/main.js +46 -0
  23. package/build/src/main.js.map +1 -0
  24. package/build/src/model-compiler-errors.d.ts +77 -0
  25. package/build/src/model-compiler-errors.d.ts.map +1 -0
  26. package/build/src/model-compiler-errors.js +156 -0
  27. package/build/src/model-compiler-errors.js.map +1 -0
  28. package/build/src/model-defaults.d.ts +56 -0
  29. package/build/src/model-defaults.d.ts.map +1 -0
  30. package/build/src/model-defaults.js +106 -0
  31. package/build/src/model-defaults.js.map +1 -0
  32. package/build/src/model-definitions.d.ts +71 -0
  33. package/build/src/model-definitions.d.ts.map +1 -0
  34. package/build/src/model-definitions.js +189 -0
  35. package/build/src/model-definitions.js.map +1 -0
  36. package/build/src/script-overrides-decorator.d.ts +4 -0
  37. package/build/src/script-overrides-decorator.d.ts.map +1 -0
  38. package/build/src/script-overrides-decorator.js +63 -0
  39. package/build/src/script-overrides-decorator.js.map +1 -0
  40. package/build/test/helpers/index.d.ts +69 -0
  41. package/build/test/helpers/index.d.ts.map +1 -0
  42. package/build/test/helpers/index.js +160 -0
  43. package/build/test/helpers/index.js.map +1 -0
  44. package/build/test/test-compile-model-with-pseudoclosure.d.ts +2 -0
  45. package/build/test/test-compile-model-with-pseudoclosure.d.ts.map +1 -0
  46. package/build/test/test-compile-model-with-pseudoclosure.js +200 -0
  47. package/build/test/test-compile-model-with-pseudoclosure.js.map +1 -0
  48. package/build/test/test-compile-model.d.ts +2 -0
  49. package/build/test/test-compile-model.d.ts.map +1 -0
  50. package/build/test/test-compile-model.js +30 -0
  51. package/build/test/test-compile-model.js.map +1 -0
  52. package/build/test/test-compile-trie.d.ts +2 -0
  53. package/build/test/test-compile-trie.d.ts.map +1 -0
  54. package/build/test/test-compile-trie.js +125 -0
  55. package/build/test/test-compile-trie.js.map +1 -0
  56. package/build/test/test-default-apply-case.d.ts +2 -0
  57. package/build/test/test-default-apply-case.d.ts.map +1 -0
  58. package/build/test/test-default-apply-case.js +105 -0
  59. package/build/test/test-default-apply-case.js.map +1 -0
  60. package/build/test/test-default-search-term-to-key.d.ts +2 -0
  61. package/build/test/test-default-search-term-to-key.d.ts.map +1 -0
  62. package/build/test/test-default-search-term-to-key.js +148 -0
  63. package/build/test/test-default-search-term-to-key.js.map +1 -0
  64. package/build/test/test-error-logger.d.ts +2 -0
  65. package/build/test/test-error-logger.d.ts.map +1 -0
  66. package/build/test/test-error-logger.js +26 -0
  67. package/build/test/test-error-logger.js.map +1 -0
  68. package/build/test/test-join-word-breaker.d.ts +2 -0
  69. package/build/test/test-join-word-breaker.d.ts.map +1 -0
  70. package/build/test/test-join-word-breaker.js +84 -0
  71. package/build/test/test-join-word-breaker.js.map +1 -0
  72. package/build/test/test-model-definitions.d.ts +2 -0
  73. package/build/test/test-model-definitions.d.ts.map +1 -0
  74. package/build/test/test-model-definitions.js +165 -0
  75. package/build/test/test-model-definitions.js.map +1 -0
  76. package/build/test/test-override-script-defaults.d.ts +2 -0
  77. package/build/test/test-override-script-defaults.d.ts.map +1 -0
  78. package/build/test/test-override-script-defaults.js +28 -0
  79. package/build/test/test-override-script-defaults.js.map +1 -0
  80. package/build/test/test-parse-wordlist.d.ts +2 -0
  81. package/build/test/test-parse-wordlist.d.ts.map +1 -0
  82. package/build/test/test-parse-wordlist.js +110 -0
  83. package/build/test/test-parse-wordlist.js.map +1 -0
  84. package/build/test/test-punctuation.d.ts +2 -0
  85. package/build/test/test-punctuation.d.ts.map +1 -0
  86. package/build/test/test-punctuation.js +31 -0
  87. package/build/test/test-punctuation.js.map +1 -0
  88. package/build/test/tsconfig.tsbuildinfo +1 -0
  89. package/build/test/wordbreakers/data.d.ts +35 -0
  90. package/build/test/wordbreakers/data.d.ts.map +1 -0
  91. package/build/test/wordbreakers/data.js +1778 -0
  92. package/build/test/wordbreakers/data.js.map +1 -0
  93. package/build/test/wordbreakers/default-wordbreaker-esm.d.ts +10 -0
  94. package/build/test/wordbreakers/default-wordbreaker-esm.d.ts.map +1 -0
  95. package/build/test/wordbreakers/default-wordbreaker-esm.js +354 -0
  96. package/build/test/wordbreakers/default-wordbreaker-esm.js.map +1 -0
  97. package/build/tsconfig.tsbuildinfo +1 -0
  98. package/build.sh +73 -0
  99. package/coverage/lcov-report/base.css +224 -0
  100. package/coverage/lcov-report/block-navigation.js +87 -0
  101. package/coverage/lcov-report/favicon.png +0 -0
  102. package/coverage/lcov-report/index.html +161 -0
  103. package/coverage/lcov-report/prettify.css +1 -0
  104. package/coverage/lcov-report/prettify.js +2 -0
  105. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  106. package/coverage/lcov-report/sorter.js +196 -0
  107. package/coverage/lcov-report/src/build-trie.ts.html +1618 -0
  108. package/coverage/lcov-report/src/index.html +221 -0
  109. package/coverage/lcov-report/src/join-word-breaker-decorator.ts.html +487 -0
  110. package/coverage/lcov-report/src/lexical-model-compiler.ts.html +622 -0
  111. package/coverage/lcov-report/src/main.ts.html +271 -0
  112. package/coverage/lcov-report/src/model-compiler-errors.ts.html +691 -0
  113. package/coverage/lcov-report/src/model-defaults.ts.html +415 -0
  114. package/coverage/lcov-report/src/model-definitions.ts.html +748 -0
  115. package/coverage/lcov-report/src/script-overrides-decorator.ts.html +310 -0
  116. package/coverage/lcov-report/test/helpers/index.html +116 -0
  117. package/coverage/lcov-report/test/helpers/index.ts.html +646 -0
  118. package/coverage/lcov-report/test/index.html +266 -0
  119. package/coverage/lcov-report/test/test-compile-model-with-pseudoclosure.ts.html +802 -0
  120. package/coverage/lcov-report/test/test-compile-model.ts.html +187 -0
  121. package/coverage/lcov-report/test/test-compile-trie.ts.html +541 -0
  122. package/coverage/lcov-report/test/test-default-apply-case.ts.html +466 -0
  123. package/coverage/lcov-report/test/test-default-search-term-to-key.ts.html +628 -0
  124. package/coverage/lcov-report/test/test-error-logger.ts.html +196 -0
  125. package/coverage/lcov-report/test/test-join-word-breaker.ts.html +376 -0
  126. package/coverage/lcov-report/test/test-model-definitions.ts.html +676 -0
  127. package/coverage/lcov-report/test/test-override-script-defaults.ts.html +184 -0
  128. package/coverage/lcov-report/test/test-parse-wordlist.ts.html +466 -0
  129. package/coverage/lcov-report/test/test-punctuation.ts.html +190 -0
  130. package/coverage/lcov-report/test/wordbreakers/data.ts.html +5413 -0
  131. package/coverage/lcov-report/test/wordbreakers/default-wordbreaker-esm.ts.html +1234 -0
  132. package/coverage/lcov-report/test/wordbreakers/index.html +131 -0
  133. package/coverage/lcov.info +5969 -0
  134. package/package.json +61 -0
  135. package/src/build-trie.ts +511 -0
  136. package/src/join-word-breaker-decorator.ts +134 -0
  137. package/src/lexical-model-compiler.ts +179 -0
  138. package/src/lexical-model.ts +150 -0
  139. package/src/main.ts +62 -0
  140. package/src/model-compiler-errors.ts +203 -0
  141. package/src/model-defaults.ts +111 -0
  142. package/src/model-definitions.ts +222 -0
  143. package/src/script-overrides-decorator.ts +75 -0
  144. package/test/README.md +15 -0
  145. package/test/fixtures/example.qaa.joinwordbreaker/example.qaa.joinwordbreaker.model.ts +10 -0
  146. package/test/fixtures/example.qaa.joinwordbreaker/wordlist.tsv +3 -0
  147. package/test/fixtures/example.qaa.scriptusesspaces/example.qaa.scriptusesspaces.model.ts +10 -0
  148. package/test/fixtures/example.qaa.scriptusesspaces/wordlist.tsv +8 -0
  149. package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.json +45 -0
  150. package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kps +35 -0
  151. package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.ts +6 -0
  152. package/test/fixtures/example.qaa.sencoten/wordlist.tsv +10 -0
  153. package/test/fixtures/example.qaa.smp/example.qaa.smp.model.ts +6 -0
  154. package/test/fixtures/example.qaa.smp/wordlist.tsv +5 -0
  155. package/test/fixtures/example.qaa.trivial/example.qaa.trivial.model.ts +5 -0
  156. package/test/fixtures/example.qaa.trivial/wordlist.tsv +3 -0
  157. package/test/fixtures/example.qaa.utf16be/example.qaa.utf16be.model.ts +5 -0
  158. package/test/fixtures/example.qaa.utf16be/wordlist.txt +0 -0
  159. package/test/fixtures/example.qaa.utf16le/example.qaa.utf16le.model.ts +5 -0
  160. package/test/fixtures/example.qaa.utf16le/wordlist.txt +0 -0
  161. package/test/fixtures/example.qaa.wordbreaker/example.qaa.wordbreaker.model.ts +9 -0
  162. package/test/fixtures/example.qaa.wordbreaker/wordlist.tsv +3 -0
  163. package/test/helpers/index.ts +187 -0
  164. package/test/test-compile-model-with-pseudoclosure.ts +239 -0
  165. package/test/test-compile-model.ts +34 -0
  166. package/test/test-compile-trie.ts +152 -0
  167. package/test/test-default-apply-case.ts +128 -0
  168. package/test/test-default-search-term-to-key.ts +181 -0
  169. package/test/test-error-logger.ts +38 -0
  170. package/test/test-join-word-breaker.ts +97 -0
  171. package/test/test-model-definitions.ts +198 -0
  172. package/test/test-override-script-defaults.ts +33 -0
  173. package/test/test-parse-wordlist.ts +127 -0
  174. package/test/test-punctuation.ts +35 -0
  175. package/test/tsconfig.json +22 -0
  176. package/test/wordbreakers/README.md +3 -0
  177. package/test/wordbreakers/data.ts +1776 -0
  178. package/test/wordbreakers/default-wordbreaker-esm.ts +383 -0
  179. package/tools/create-override-script-regexp.ts +145 -0
  180. package/tsconfig.json +17 -0
@@ -0,0 +1,383 @@
1
+ // TEMP: esm version of /common/models/wordbreakers/default/index.ts
2
+
3
+ import { I, WORD_BREAK_PROPERTY, WordBreakProperty } from './data.js';
4
+
5
+
6
+ /**
7
+ * Word breaker based on Unicode Standard Annex #29, Section 4.1:
8
+ * Default Word Boundary Specification.
9
+ *
10
+ * @see http://unicode.org/reports/tr29/#Word_Boundaries
11
+ * @see https://github.com/eddieantonio/unicode-default-word-boundary/tree/v12.0.0
12
+ */
13
+ export default function default_(text: string): Span[] {
14
+ let boundaries = findBoundaries(text);
15
+ if (boundaries.length == 0) {
16
+ return [];
17
+ }
18
+
19
+ // All non-empty strings have at least TWO boundaries: at the start and at the end of
20
+ // the string.
21
+ let spans = [];
22
+ for (let i = 0; i < boundaries.length - 1; i++) {
23
+ let start = boundaries[i];
24
+ let end = boundaries[i + 1];
25
+ let span = new LazySpan(text, start, end);
26
+
27
+ if (isNonSpace(span.text)) {
28
+ spans.push(span);
29
+ // Preserve a sequence-final space if it exists. Needed to signal "end of word".
30
+ } else if (i == boundaries.length - 2) { // if "we just checked the final boundary"...
31
+ // We don't want to return the whitespace itself; the correct token is simply ''.
32
+ span = new LazySpan(text, end, end);
33
+ spans.push(span);
34
+ }
35
+ }
36
+ return spans;
37
+ }
38
+
39
+ // Utilities //
40
+ // type WordBreakProperty = data.WordBreakProperty;
41
+ // const WordBreakProperty = data.WordBreakProperty;
42
+ // type I = data.I;
43
+ // const I = data.I;
44
+ // const WORD_BREAK_PROPERTY = data.WORD_BREAK_PROPERTY;
45
+
46
+ /**
47
+ * A span that does not cut out the substring until it absolutely has to!
48
+ */
49
+ class LazySpan implements Span {
50
+ private _source: string;
51
+ readonly start: number;
52
+ readonly end: number;
53
+ constructor(source: string, start: number, end: number) {
54
+ this._source = source;
55
+ this.start = start;
56
+ this.end = end;
57
+ }
58
+
59
+ get text(): string {
60
+ return this._source.substring(this.start, this.end);
61
+ }
62
+
63
+ get length(): number {
64
+ return this.end - this.start;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Returns true when the chunk does not solely consist of whitespace.
70
+ *
71
+ * @param chunk a chunk of text. Starts and ends at word boundaries.
72
+ */
73
+ function isNonSpace(chunk: string): boolean {
74
+ return !Array.from(chunk).map(property).every(wb => (
75
+ wb === WordBreakProperty.CR ||
76
+ wb === WordBreakProperty.LF ||
77
+ wb === WordBreakProperty.Newline ||
78
+ wb === WordBreakProperty.WSegSpace
79
+ ));
80
+ }
81
+
82
+ /**
83
+ * Yields a series of string indices where a word break should
84
+ * occur. That is, there should be a break BEFORE each string
85
+ * index yielded by this generator.
86
+ *
87
+ * @param text Text to find word boundaries in.
88
+ */
89
+ function findBoundaries(text: string): number[] {
90
+ // WB1 and WB2: no boundaries if given an empty string.
91
+ if (text.length === 0) {
92
+ // There are no boundaries in an empty string!
93
+ return [];
94
+ }
95
+
96
+ // This algorithm works by maintaining a sliding window of four SCALAR VALUES.
97
+ //
98
+ // - Scalar values? JavaScript strings are NOT actually a string of
99
+ // Unicode code points; some characters are made up of TWO
100
+ // JavaScript indices. e.g.,
101
+ // "💩".length === 2;
102
+ // "💩"[0] === '\uD83D';
103
+ // "💩"[1] === '\uDCA9';
104
+ //
105
+ // These characters that are represented by TWO indices are
106
+ // called "surrogate pairs". Since we don't want to be in the
107
+ // "middle" of a character, make sure we're always advancing
108
+ // by scalar values, and NOT indices. That means, we sometimes
109
+ // need to advance by TWO indices, not just one.
110
+ // - Four values? Some rules look at what's to the left of
111
+ // left, and some look at what's to the right of right. So
112
+ // keep track of this!
113
+
114
+ let boundaries = [];
115
+
116
+ let rightPos: number;
117
+ let lookaheadPos = 0; // lookahead, one scalar value to the right of right.
118
+ // Before the start of the string is also the start of the string.
119
+ let lookbehind: WordBreakProperty;
120
+ let left = WordBreakProperty.sot;
121
+ let right = WordBreakProperty.sot;
122
+ let lookahead = wordbreakPropertyAt(0);
123
+ // Count RIs to make sure we're not splitting emoji flags:
124
+ let nConsecutiveRegionalIndicators = 0;
125
+
126
+ do {
127
+ // Shift all positions, one scalar value to the right.
128
+ rightPos = lookaheadPos;
129
+ lookaheadPos = positionAfter(lookaheadPos);
130
+ // Shift all properties, one scalar value to the right.
131
+ [lookbehind, left, right, lookahead] =
132
+ [left, right, lookahead, wordbreakPropertyAt(lookaheadPos)];
133
+
134
+ // Break at the start and end of text, unless the text is empty.
135
+ // WB1: Break at start of text...
136
+ if (left === WordBreakProperty.sot) {
137
+ boundaries.push(rightPos);
138
+ continue;
139
+ }
140
+ // WB2: Break at the end of text...
141
+ if (right === WordBreakProperty.eot) {
142
+ boundaries.push(rightPos);
143
+ break; // Reached the end of the string. We're done!
144
+ }
145
+ // WB3: Do not break within CRLF:
146
+ if (left === WordBreakProperty.CR && right === WordBreakProperty.LF)
147
+ continue;
148
+ // WB3b: Otherwise, break after...
149
+ if (left === WordBreakProperty.Newline ||
150
+ left === WordBreakProperty.CR ||
151
+ left === WordBreakProperty.LF) {
152
+ boundaries.push(rightPos);
153
+ continue;
154
+ }
155
+ // WB3a: ...and before newlines
156
+ if (right === WordBreakProperty.Newline ||
157
+ right === WordBreakProperty.CR ||
158
+ right === WordBreakProperty.LF) {
159
+ boundaries.push(rightPos);
160
+ continue;
161
+ }
162
+
163
+ // TODO: WB3c is not implemented, due to its complex, error-prone
164
+ // implementation, requiring a ginormous regexp, and the fact that
165
+ // the only thing it does is prevent big emoji sequences from being
166
+ // split up, like 🧚🏼‍♂️
167
+ // https://www.unicode.org/Public/emoji/12.0/emoji-zwj-sequences.txt
168
+
169
+ // WB3d: Keep horizontal whitespace together
170
+ if (left === WordBreakProperty.WSegSpace && right == WordBreakProperty.WSegSpace)
171
+ continue;
172
+
173
+ // WB4: Ignore format and extend characters
174
+ // This is to keep grapheme clusters together!
175
+ // See: Section 6.2: https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules
176
+ // N.B.: The rule about "except after sot, CR, LF, and
177
+ // Newline" already been by WB1, WB2, WB3a, and WB3b above.
178
+ while (right === WordBreakProperty.Format ||
179
+ right === WordBreakProperty.Extend ||
180
+ right === WordBreakProperty.ZWJ) {
181
+ // Continue advancing in the string, as if these
182
+ // characters do not exist. DO NOT update left and
183
+ // lookbehind however!
184
+ [rightPos, lookaheadPos] = [lookaheadPos, positionAfter(lookaheadPos)];
185
+ [right, lookahead] = [lookahead, wordbreakPropertyAt(lookaheadPos)];
186
+ }
187
+ // In ignoring the characters in the previous loop, we could
188
+ // have fallen off the end of the string, so end the loop
189
+ // prematurely if that happens!
190
+ if (right === WordBreakProperty.eot) {
191
+ boundaries.push(rightPos);
192
+ break;
193
+ }
194
+ // WB4 (continued): Lookahead must ALSO ignore these format,
195
+ // extend, ZWJ characters!
196
+ while (lookahead === WordBreakProperty.Format ||
197
+ lookahead === WordBreakProperty.Extend ||
198
+ lookahead === WordBreakProperty.ZWJ) {
199
+ // Continue advancing in the string, as if these
200
+ // characters do not exist. DO NOT update left and right,
201
+ // however!
202
+ lookaheadPos = positionAfter(lookaheadPos);
203
+ lookahead = wordbreakPropertyAt(lookaheadPos);
204
+ }
205
+
206
+ // WB5: Do not break between most letters.
207
+ if (isAHLetter(left) && isAHLetter(right))
208
+ continue;
209
+ // Do not break across certain punctuation
210
+ // WB6: (Don't break before apostrophes in contractions)
211
+ if (isAHLetter(left) && isAHLetter(lookahead) &&
212
+ (right === WordBreakProperty.MidLetter || isMidNumLetQ(right)))
213
+ continue;
214
+ // WB7: (Don't break after apostrophes in contractions)
215
+ if (isAHLetter(lookbehind) && isAHLetter(right) &&
216
+ (left === WordBreakProperty.MidLetter || isMidNumLetQ(left)))
217
+ continue;
218
+ // WB7a
219
+ if (left === WordBreakProperty.Hebrew_Letter && right === WordBreakProperty.Single_Quote)
220
+ continue;
221
+ // WB7b
222
+ if (left === WordBreakProperty.Hebrew_Letter && right === WordBreakProperty.Double_Quote &&
223
+ lookahead === WordBreakProperty.Hebrew_Letter)
224
+ continue;
225
+ // WB7c
226
+ if (lookbehind === WordBreakProperty.Hebrew_Letter && left === WordBreakProperty.Double_Quote &&
227
+ right === WordBreakProperty.Hebrew_Letter)
228
+ continue;
229
+ // Do not break within sequences of digits, or digits adjacent to letters.
230
+ // e.g., "3a" or "A3"
231
+ // WB8
232
+ if (left === WordBreakProperty.Numeric && right === WordBreakProperty.Numeric)
233
+ continue;
234
+ // WB9
235
+ if (isAHLetter(left) && right === WordBreakProperty.Numeric)
236
+ continue;
237
+ // WB10
238
+ if (left === WordBreakProperty.Numeric && isAHLetter(right))
239
+ continue;
240
+ // Do not break within sequences, such as 3.2, 3,456.789
241
+ // WB11
242
+ if (lookbehind === WordBreakProperty.Numeric && right === WordBreakProperty.Numeric &&
243
+ (left === WordBreakProperty.MidNum || isMidNumLetQ(left)))
244
+ continue;
245
+ // WB12
246
+ if (left === WordBreakProperty.Numeric && lookahead === WordBreakProperty.Numeric &&
247
+ (right === WordBreakProperty.MidNum || isMidNumLetQ(right)))
248
+ continue;
249
+ // WB13: Do not break between Katakana
250
+ if (left === WordBreakProperty.Katakana && right === WordBreakProperty.Katakana)
251
+ continue;
252
+ // Do not break from extenders (e.g., U+202F NARROW NO-BREAK SPACE)
253
+ // WB13a
254
+ if ((isAHLetter(left) ||
255
+ left === WordBreakProperty.Numeric ||
256
+ left === WordBreakProperty.Katakana ||
257
+ left === WordBreakProperty.ExtendNumLet) &&
258
+ right === WordBreakProperty.ExtendNumLet)
259
+ continue;
260
+ // WB13b
261
+ if ((isAHLetter(right) ||
262
+ right === WordBreakProperty.Numeric ||
263
+ right === WordBreakProperty.Katakana) && left === WordBreakProperty.ExtendNumLet)
264
+ continue;
265
+
266
+ // WB15 & WB16:
267
+ // Do not break within emoji flag sequences. That is, do not break between
268
+ // regional indicator (RI) symbols if there is an odd number of RI
269
+ // characters before the break point.
270
+ if (right === WordBreakProperty.Regional_Indicator) {
271
+ // Emoji flags are actually composed of TWO scalar values, each being a
272
+ // "regional indicator". These indicators correspond to Latin letters. Put
273
+ // two of them together, and they spell out an ISO 3166-1-alpha-2 country
274
+ // code. Since these always come in pairs, NEVER split the pairs! So, if
275
+ // we happen to be inside the middle of an odd numbered of
276
+ // Regional_Indicators, DON'T SPLIT IT!
277
+ nConsecutiveRegionalIndicators += 1;
278
+ if ((nConsecutiveRegionalIndicators % 2) == 1) {
279
+ continue;
280
+ }
281
+ } else {
282
+ nConsecutiveRegionalIndicators = 0;
283
+ }
284
+ // WB999: Otherwise, break EVERYWHERE (including around ideographs)
285
+ boundaries.push(rightPos);
286
+ } while (rightPos < text.length);
287
+
288
+ return boundaries;
289
+
290
+ ///// Internal utility functions /////
291
+
292
+ /**
293
+ * Returns the position of the start of the next scalar value. This jumps
294
+ * over surrogate pairs.
295
+ *
296
+ * If asked for the character AFTER the end of the string, this always
297
+ * returns the length of the string.
298
+ */
299
+ function positionAfter(pos: number): number {
300
+ if (pos >= text.length) {
301
+ return text.length;
302
+ } else if (isStartOfSurrogatePair(text[pos])) {
303
+ return pos + 2;
304
+ }
305
+ return pos + 1;
306
+ }
307
+
308
+ /**
309
+ * Return the value of the Word_Break property at the given string index.
310
+ * @param pos position in the text.
311
+ */
312
+ function wordbreakPropertyAt(pos: number) {
313
+ if (pos < 0) {
314
+ return WordBreakProperty.sot; // Always "start of string" before the string starts!
315
+ } else if (pos >= text.length) {
316
+ return WordBreakProperty.eot; // Always "end of string" after the string ends!
317
+ } else if (isStartOfSurrogatePair(text[pos])) {
318
+ // Surrogate pairs the next TWO items from the string!
319
+ return property(text[pos] + text[pos + 1]);
320
+ }
321
+ return property(text[pos]);
322
+ }
323
+
324
+ // Word_Break rule macros
325
+ // See: https://unicode.org/reports/tr29/#WB_Rule_Macros
326
+ function isAHLetter(prop: WordBreakProperty): boolean {
327
+ return prop === WordBreakProperty.ALetter ||
328
+ prop === WordBreakProperty.Hebrew_Letter;
329
+ }
330
+
331
+ function isMidNumLetQ(prop: WordBreakProperty): boolean {
332
+ return prop === WordBreakProperty.MidNumLet ||
333
+ prop === WordBreakProperty.Single_Quote;
334
+ }
335
+ }
336
+
337
+ function isStartOfSurrogatePair(character: string) {
338
+ let codeUnit = character.charCodeAt(0);
339
+ return codeUnit >= 0xD800 && codeUnit <= 0xDBFF;
340
+ }
341
+
342
+ /**
343
+ * Return the Word_Break property value for a character.
344
+ * Note that
345
+ * @param character a scalar value
346
+ */
347
+ function property(character: string): WordBreakProperty {
348
+ // This MUST be a scalar value.
349
+ // TODO: remove dependence on character.codepointAt()?
350
+ let codepoint = character.codePointAt(0) as number;
351
+ return searchForProperty(codepoint, 0, WORD_BREAK_PROPERTY.length - 1);
352
+ }
353
+
354
+ /**
355
+ * Binary search for the word break property of a given CODE POINT.
356
+ *
357
+ * The auto-generated data.ts master array defines a **character range**
358
+ * lookup table. If a character's codepoint is equal to or greater than
359
+ * the I.Start value for an entry and exclusively less than the next entry,
360
+ * it falls in the first entry's range bucket and is classified accordingly
361
+ * by this method.
362
+ */
363
+ function searchForProperty(codePoint: number, left: number, right: number): WordBreakProperty {
364
+ // All items that are not found in the array are assigned the 'Other' property.
365
+ if (right < left) {
366
+ return WordBreakProperty.Other;
367
+ }
368
+
369
+ let midpoint = left + ~~((right - left) / 2);
370
+ let candidate = WORD_BREAK_PROPERTY[midpoint];
371
+
372
+ let nextRange = WORD_BREAK_PROPERTY[midpoint + 1];
373
+ let startOfNextRange = nextRange ? nextRange[I.Start] : Infinity;
374
+
375
+ if (codePoint < candidate[I.Start]) {
376
+ return searchForProperty(codePoint, left, midpoint - 1);
377
+ } else if (codePoint >= startOfNextRange) {
378
+ return searchForProperty(codePoint, midpoint + 1, right);
379
+ }
380
+
381
+ // We found it!
382
+ return candidate[I.Value];
383
+ }
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env npx ts-node
2
+
3
+ /**
4
+ * Prints a JavaScript regular expression suitable for use in the
5
+ * **overrideScriptDefaults** word breaker decorator.
6
+ *
7
+ * This regular expression matches letters or marks in scripts that
8
+ * conventionally use spaces. This way, the word breaker can make sure to keep
9
+ * contiguous spans of these characters together!
10
+ *
11
+ * If you need to add more Unicode blocks, customize SPACELESS_SCRIPT_BLOCKS.
12
+ */
13
+
14
+ import {readFileSync} from "fs";
15
+ import * as path from "path";
16
+
17
+ // Where to find UnicodeData.txt and Blocks.txt
18
+ const UCD_DIR = path.join("..", "..", "..", "..", "resources", "standards-data", "unicode-character-database");
19
+
20
+ const SPACELESS_SCRIPT_BLOCKS = new Set([
21
+ "Myanmar", // a.k.a., Burmese
22
+ "Lao",
23
+ "Thai",
24
+ "Khmer",
25
+ "Katakana",
26
+ "Katakana Phonetic Extensions",
27
+ // Add more scripts here, as necessary!
28
+ ]);
29
+
30
+ let blockIter = blocks();
31
+ let block = nextBlock();
32
+
33
+ let eligibleCharacters: number[] = [];
34
+
35
+ // @ts-ignore: TypeScript complains that it can't compile for..of over a
36
+ // generator without --downlevelIteration, but it works anyway?
37
+ for (let {codePoint, generalCategory} of unicodeData()) {
38
+ if (!block.contains(codePoint)) {
39
+ block = nextBlock();
40
+ }
41
+ console.assert(block.contains(codePoint));
42
+
43
+ if (SPACELESS_SCRIPT_BLOCKS.has(block.name) && isLetterOrMark(generalCategory)) {
44
+ eligibleCharacters.push(codePoint);
45
+ }
46
+ }
47
+
48
+ const ranges = groupCodePointsIntoRanges(eligibleCharacters);
49
+
50
+ const characterClasses = ranges.map(([lower, upper]) => {
51
+ if (lower === upper) {
52
+ return unicodeEscape(lower);
53
+ } else if (lower === upper - 1) {
54
+ return unicodeEscape(lower) + unicodeEscape(upper);
55
+ } else {
56
+ return `${unicodeEscape(lower)}-${unicodeEscape(upper)}`;
57
+ }
58
+ }).join("");
59
+
60
+ console.log(`/**
61
+ * AUTOMATICALLY GENERATED FILE. DO NOT MODIFY
62
+ * See: libexec/create-override-script-regexp.ts for details!
63
+ */
64
+ export const HAS_SOUTHEAST_ASIAN_LETTER = /[${characterClasses}]/;`);
65
+
66
+
67
+ ////////////////////////////////// Helpers ///////////////////////////////////
68
+
69
+ function* unicodeData() {
70
+ let unicodeDataFile = readFileSync(path.join(UCD_DIR, "UnicodeData.txt"), "UTF-8");
71
+ for (let line of unicodeDataFile.split("\n")) {
72
+ if (line.trim() == "") {
73
+ continue;
74
+ }
75
+
76
+ let parts = line.split(";")
77
+ yield {
78
+ codePoint: parseInt(parts[0], 16),
79
+ generalCategory: parts[2],
80
+ }
81
+ }
82
+ }
83
+
84
+ function* blocks() {
85
+ let blocksFile = readFileSync(path.join(UCD_DIR, "Blocks.txt"), "UTF-8");
86
+ for (let line of blocksFile.split("\n")) {
87
+ if (line.trim() === "") {
88
+ continue;
89
+ }
90
+ if (line.startsWith("#")) {
91
+ continue;
92
+ }
93
+
94
+ let [range, name] = line.split("; ");
95
+ let [lower, upper] = range.split("..").map(s => parseInt(s, 16));
96
+
97
+ yield {
98
+ name, lower, upper,
99
+ contains(codePoint: number) {
100
+ return lower <= codePoint && codePoint <= upper;
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ function nextBlock() {
107
+ let {value} = blockIter.next();
108
+ if (!value) {
109
+ throw new Error("ran out of blocks");
110
+ }
111
+ return value;
112
+ }
113
+
114
+ function isLetterOrMark(category: string): boolean {
115
+ return category.startsWith("L") || category.startsWith("M");
116
+ }
117
+
118
+ function groupCodePointsIntoRanges(characters: number[]): [number, number][] {
119
+ let ranges: [number,number][] = [];
120
+
121
+ let previousCharacter = characters[0];
122
+ const candidates = characters.slice(1);
123
+ let currentRange: [number, number] = [previousCharacter, previousCharacter];
124
+ for (let codePoint of candidates) {
125
+ if (codePoint === previousCharacter + 1) {
126
+ currentRange[1] = codePoint;
127
+ } else {
128
+ ranges.push(currentRange);
129
+ currentRange = [codePoint, codePoint];
130
+ }
131
+
132
+ previousCharacter = codePoint;
133
+ }
134
+
135
+ return ranges;
136
+ }
137
+
138
+ function unicodeEscape(codePoint: number) {
139
+ if (codePoint > 0xFFFF) {
140
+ throw new Error("non-BMP code points not supported");
141
+ }
142
+
143
+ let hex = codePoint.toString(16).toUpperCase().padStart(4,'0');
144
+ return `\\u${hex}`;
145
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../kmc/tsconfig.kmc-base.json",
3
+
4
+ "compilerOptions": {
5
+ "outDir": "build/src/",
6
+ "rootDir": "src/",
7
+ "baseUrl": ".",
8
+ "allowSyntheticDefaultImports": true,
9
+ },
10
+ "include": [
11
+ "src/**/*.ts"
12
+ ],
13
+ "references": [
14
+ { "path": "../../../common/web/keyman-version/tsconfig.esm.json" },
15
+ { "path": "../../../common/models/types" },
16
+ ]
17
+ }