@keymanapp/kmc-ldml 18.0.41-alpha → 18.0.46-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 (58) hide show
  1. package/build/src/compiler/compiler.d.ts +123 -123
  2. package/build/src/compiler/compiler.js +307 -310
  3. package/build/src/compiler/compiler.js.map +1 -1
  4. package/build/src/compiler/disp.d.ts +11 -11
  5. package/build/src/compiler/disp.js +82 -85
  6. package/build/src/compiler/disp.js.map +1 -1
  7. package/build/src/compiler/empty-compiler.d.ts +37 -37
  8. package/build/src/compiler/empty-compiler.js +114 -117
  9. package/build/src/compiler/empty-compiler.js.map +1 -1
  10. package/build/src/compiler/keymanweb-compiler.d.ts +13 -13
  11. package/build/src/compiler/keymanweb-compiler.js +95 -98
  12. package/build/src/compiler/keymanweb-compiler.js.map +1 -1
  13. package/build/src/compiler/keys.d.ts +53 -53
  14. package/build/src/compiler/keys.js +417 -420
  15. package/build/src/compiler/keys.js.map +1 -1
  16. package/build/src/compiler/layr.d.ts +9 -9
  17. package/build/src/compiler/layr.js +81 -84
  18. package/build/src/compiler/layr.js.map +1 -1
  19. package/build/src/compiler/ldml-compiler-options.d.ts +11 -11
  20. package/build/src/compiler/ldml-compiler-options.js +3 -6
  21. package/build/src/compiler/ldml-compiler-options.js.map +1 -1
  22. package/build/src/compiler/loca.d.ts +15 -15
  23. package/build/src/compiler/loca.js +59 -62
  24. package/build/src/compiler/loca.js.map +1 -1
  25. package/build/src/compiler/messages.d.ts +186 -186
  26. package/build/src/compiler/messages.js +122 -125
  27. package/build/src/compiler/messages.js.map +1 -1
  28. package/build/src/compiler/meta.d.ts +13 -13
  29. package/build/src/compiler/meta.js +55 -58
  30. package/build/src/compiler/meta.js.map +1 -1
  31. package/build/src/compiler/metadata-compiler.d.ts +12 -12
  32. package/build/src/compiler/metadata-compiler.js +47 -50
  33. package/build/src/compiler/metadata-compiler.js.map +1 -1
  34. package/build/src/compiler/section-compiler.d.ts +35 -35
  35. package/build/src/compiler/section-compiler.js +40 -43
  36. package/build/src/compiler/section-compiler.js.map +1 -1
  37. package/build/src/compiler/substitution-tracker.d.ts +47 -47
  38. package/build/src/compiler/substitution-tracker.js +103 -106
  39. package/build/src/compiler/substitution-tracker.js.map +1 -1
  40. package/build/src/compiler/touch-layout-compiler.d.ts +7 -7
  41. package/build/src/compiler/touch-layout-compiler.js +91 -94
  42. package/build/src/compiler/touch-layout-compiler.js.map +1 -1
  43. package/build/src/compiler/tran.d.ts +57 -57
  44. package/build/src/compiler/tran.js +388 -391
  45. package/build/src/compiler/tran.js.map +1 -1
  46. package/build/src/compiler/vars.d.ts +21 -21
  47. package/build/src/compiler/vars.js +234 -237
  48. package/build/src/compiler/vars.js.map +1 -1
  49. package/build/src/compiler/visual-keyboard-compiler.d.ts +8 -8
  50. package/build/src/compiler/visual-keyboard-compiler.js +68 -71
  51. package/build/src/compiler/visual-keyboard-compiler.js.map +1 -1
  52. package/build/src/main.d.ts +3 -3
  53. package/build/src/main.js +3 -6
  54. package/build/src/main.js.map +1 -1
  55. package/build/src/util/util.d.ts +49 -49
  56. package/build/src/util/util.js +183 -186
  57. package/build/src/util/util.js.map +1 -1
  58. package/package.json +6 -6
@@ -1,391 +1,388 @@
1
-
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="25bcdbe4-16f8-5d94-b6ef-d181bc95feed")}catch(e){}}();
3
- import { constants } from "@keymanapp/ldml-keyboard-constants";
4
- import { KMXPlus, VariableParser, MarkerParser, util } from '@keymanapp/common-types';
5
- import { SectionCompiler } from "./section-compiler.js";
6
- var Bksp = KMXPlus.Bksp;
7
- var Tran = KMXPlus.Tran;
8
- var TranReorder = KMXPlus.TranReorder;
9
- var TranTransform = KMXPlus.TranTransform;
10
- import { verifyValidAndUnique } from "../util/util.js";
11
- import { CompilerMessages } from "./messages.js";
12
- import { SubstitutionUse } from "./substitution-tracker.js";
13
- export class TransformCompiler extends SectionCompiler {
14
- static validateSubstitutions(keyboard, st) {
15
- keyboard?.transforms?.forEach(transforms => transforms.transformGroup.forEach(transformGroup => {
16
- transformGroup.transform?.forEach(({ to, from }) => {
17
- st.addSetAndStringSubtitution(SubstitutionUse.consume, from);
18
- st.addSetAndStringSubtitution(SubstitutionUse.emit, to);
19
- const mapFrom = VariableParser.CAPTURE_SET_REFERENCE.exec(from);
20
- const mapTo = VariableParser.MAPPED_SET_REFERENCE.exec(to || '');
21
- if (mapFrom) {
22
- // add the 'from' as a match
23
- st.set.add(SubstitutionUse.consume, [mapFrom[1]]);
24
- }
25
- if (mapTo) {
26
- // add the 'from' as a match
27
- st.set.add(SubstitutionUse.emit, [mapTo[1]]);
28
- }
29
- });
30
- transformGroup.reorder?.forEach(({ before }) => {
31
- st.addStringSubstitution(SubstitutionUse.consume, before);
32
- });
33
- }));
34
- return true;
35
- }
36
- type;
37
- constructor(source, callbacks) {
38
- super(source, callbacks);
39
- }
40
- validate() {
41
- const reportMessage = this.callbacks.reportMessage.bind(this.callbacks);
42
- let valid = true;
43
- const transforms = this?.keyboard3?.transforms;
44
- if (transforms) {
45
- const types = transforms.map(({ type }) => type);
46
- if (!verifyValidAndUnique(types, types => reportMessage(CompilerMessages.Error_DuplicateTransformsType({ types })), new Set(['simple', 'backspace']), types => reportMessage(CompilerMessages.Error_InvalidTransformsType({ types })))) {
47
- valid = false;
48
- }
49
- // check for mixed groups
50
- let mixed = false;
51
- let empty = false;
52
- transforms.forEach(({ transformGroup }) => transformGroup.forEach((transformGroup) => {
53
- if (transformGroup.reorder?.length && transformGroup.transform?.length) {
54
- mixed = true;
55
- }
56
- if (!transformGroup.reorder?.length && !transformGroup.transform?.length) {
57
- empty = true;
58
- }
59
- }));
60
- if (mixed) {
61
- valid = false;
62
- reportMessage(CompilerMessages.Error_MixedTransformGroup()); // report this once
63
- }
64
- if (empty) {
65
- valid = false;
66
- reportMessage(CompilerMessages.Error_EmptyTransformGroup()); // report this once
67
- }
68
- // TODO-LDML: linting here should check for identical from, but this involves a double-parse which is ugly
69
- // TODO-LDML: unicodesets means that either we fully parse them and verify conflicting rules or the linting is imperfect
70
- }
71
- return valid;
72
- }
73
- /* c8 ignore next 4 */
74
- /** allocate a new TranBase subclass */
75
- newTran() {
76
- throw Error(`Internal Error: newTran() not implemented`);
77
- }
78
- compileTransforms(sections, transforms) {
79
- let result = this.newTran();
80
- if (transforms?.transformGroup) {
81
- for (let transformGroup of transforms.transformGroup) {
82
- const tg = this.compileTransformGroup(sections, transformGroup);
83
- if (!tg)
84
- return null; //error
85
- result.groups.push(tg);
86
- }
87
- }
88
- return result;
89
- }
90
- compileTransformGroup(sections, transformGroup) {
91
- if (transformGroup.reorder.length && transformGroup.transform.length) {
92
- /* c8 ignore next 2 */
93
- // should have been caught by validate
94
- throw Error(`Internal error: transformGroup has both reorder and transform elements.`);
95
- }
96
- else if (transformGroup.reorder.length) {
97
- return this.compileReorderTranGroup(sections, transformGroup.reorder);
98
- }
99
- else if (transformGroup.transform.length) {
100
- return this.compileTransformTranGroup(sections, transformGroup.transform);
101
- }
102
- else {
103
- /* c8 ignore next */
104
- throw Error(`Internal error: transformGroup has neither reorder nor transform elements.`);
105
- }
106
- }
107
- compileTransformTranGroup(sections, transforms) {
108
- const result = {
109
- type: constants.tran_group_type_transform,
110
- transforms: transforms.map(transform => this.compileTransform(sections, transform)),
111
- reorders: [],
112
- };
113
- if (result.transforms.includes(null))
114
- return null;
115
- return result;
116
- }
117
- compileTransform(sections, transform) {
118
- let result = new TranTransform();
119
- let cookedFrom = transform.from;
120
- // check for incorrect \uXXXX escapes. Do this before substituting markers or sets.
121
- cookedFrom = this.checkEscapes(cookedFrom); // check for \uXXXX escapes before normalizing
122
- cookedFrom = sections.vars.substituteStrings(cookedFrom, sections, true);
123
- const mapFrom = VariableParser.CAPTURE_SET_REFERENCE.exec(cookedFrom);
124
- const mapTo = VariableParser.MAPPED_SET_REFERENCE.exec(transform.to || '');
125
- if (mapFrom && mapTo) { // TODO-LDML: error cases
126
- result.mapFrom = sections.strs.allocString(mapFrom[1]); // var name
127
- result.mapTo = sections.strs.allocString(mapTo[1]); // var name
128
- }
129
- else {
130
- result.mapFrom = sections.strs.allocString('');
131
- result.mapTo = sections.strs.allocString('');
132
- }
133
- if (cookedFrom === null)
134
- return null; // error
135
- // the set substution will not produce raw markers `\m{...}` but they will already be in sentinel form.
136
- cookedFrom = sections.vars.substituteSetRegex(cookedFrom, sections);
137
- // add in markers. idempotent if no markers.
138
- cookedFrom = sections.vars.substituteMarkerString(cookedFrom, true);
139
- // unescape from \u{} form to plain, or in some cases \uXXXX / \UXXXXXXXX for core
140
- cookedFrom = util.unescapeStringToRegex(cookedFrom);
141
- // check for denormalized ranges
142
- cookedFrom = this.checkRanges(cookedFrom); // check before normalizing
143
- if (!sections?.meta?.normalizationDisabled) {
144
- // nfd here.
145
- cookedFrom = MarkerParser.nfd_markers(cookedFrom, true);
146
- }
147
- // perform regex validation
148
- if (!this.isValidRegex(cookedFrom, transform.from)) {
149
- return null;
150
- }
151
- // cookedFrom is cooked above, since there's some special treatment
152
- result.from = sections.strs.allocString(cookedFrom, {
153
- unescape: false,
154
- }, sections);
155
- // 'to' is handled via allocString
156
- result.to = sections.strs.allocString(transform.to, {
157
- stringVariables: true,
158
- markers: true,
159
- unescape: true,
160
- nfd: true,
161
- }, sections);
162
- return result;
163
- }
164
- /**
165
- * Validate the final regex
166
- * @param cookedFrom the regex to use, missing the trailing '$'
167
- * @param from the original from - for error reporting
168
- * @returns true if OK
169
- */
170
- isValidRegex(cookedFrom, from) {
171
- // check for any unescaped dollar sign here
172
- if (/(?<!\\)(?:\\\\)*\$/.test(cookedFrom)) {
173
- this.callbacks.reportMessage(CompilerMessages.Error_IllegalTransformDollarsign({ from }));
174
- return false;
175
- }
176
- if (/(?<!\\)(?:\\\\)*\*/.test(cookedFrom)) {
177
- this.callbacks.reportMessage(CompilerMessages.Error_IllegalTransformAsterisk({ from }));
178
- return false;
179
- }
180
- if (/(?<!\\)(?:\\\\)*\+/.test(cookedFrom)) {
181
- this.callbacks.reportMessage(CompilerMessages.Error_IllegalTransformPlus({ from }));
182
- return false;
183
- }
184
- // Verify that the regex is syntactically valid
185
- try {
186
- const rg = new RegExp(cookedFrom + '$', 'ug');
187
- // Tests against the regex:
188
- // does it match an empty string?
189
- if (rg.test('')) {
190
- this.callbacks.reportMessage(CompilerMessages.Error_TransformFromMatchesNothing({ from }));
191
- return false;
192
- }
193
- }
194
- catch (e) {
195
- // We're exposing the internal regex error message here.
196
- // In the future, CLDR plans to expose the EBNF for the transform,
197
- // at which point we would have more precise validation prior to getting to this point.
198
- this.callbacks.reportMessage(CompilerMessages.Error_UnparseableTransformFrom({ from, message: e.message }));
199
- return false;
200
- }
201
- return true;
202
- }
203
- compileReorderTranGroup(sections, reorders) {
204
- const result = {
205
- type: constants.tran_group_type_reorder,
206
- transforms: [],
207
- reorders: reorders.map(reorder => this.compileReorder(sections, reorder)),
208
- };
209
- if (result.reorders.includes(null))
210
- return null; // if any of the reorders returned null, fail the entire group.
211
- return result;
212
- }
213
- compileReorder(sections, reorder) {
214
- let result = new TranReorder();
215
- if (reorder.from && this.checkEscapes(reorder.from) === null) {
216
- return null; // error'ed
217
- }
218
- if (reorder.before && this.checkEscapes(reorder.before) === null) {
219
- return null; // error'ed
220
- }
221
- result.elements = sections.elem.allocElementString(sections, reorder.from, reorder.order, reorder.tertiary, reorder.tertiaryBase, reorder.preBase);
222
- result.before = sections.elem.allocElementString(sections, reorder.before);
223
- if (!result.elements || !result.before) {
224
- return null; // already error'ed
225
- }
226
- else {
227
- return result;
228
- }
229
- }
230
- compile(sections) {
231
- for (let t of this.keyboard3.transforms) {
232
- if (t.type == this.type) {
233
- // compile only the transforms of the correct type
234
- return this.compileTransforms(sections, t);
235
- }
236
- }
237
- return this.newTran(); // empty: nothing of this type found.
238
- }
239
- get dependencies() {
240
- const defaults = new Set([
241
- constants.section.elem,
242
- constants.section.list,
243
- constants.section.meta,
244
- constants.section.strs,
245
- constants.section.uset,
246
- constants.section.vars,
247
- ]);
248
- defaults.delete(this.id);
249
- return defaults;
250
- }
251
- /**
252
- * Analyze reorders and regexes for \uXXXX escapes.
253
- * The LDML spec requires \u{XXXX} format.
254
- * @param cookedFrom the original string
255
- * @returns the original string, or null if an error was reported
256
- */
257
- checkEscapes(cookedFrom) {
258
- if (!cookedFrom)
259
- return cookedFrom;
260
- // should not follow marker prefix, nor marker prefix with range
261
- const anyQuad = /(?<!\\uffff\\u0008(?:\[[0-9a-fA-F\\u-]*)?)\\u([0-9a-fA-F]{4})/g;
262
- for (const [, sub] of cookedFrom.matchAll(anyQuad)) {
263
- const s = util.unescapeOne(sub);
264
- if (s !== '\uffff' && s !== '\u0008') { // markers
265
- this.callbacks.reportMessage(CompilerMessages.Error_InvalidQuadEscape({ cp: s.codePointAt(0) }));
266
- return null; // exit on the first error
267
- }
268
- }
269
- return cookedFrom;
270
- }
271
- /**
272
- * Analyze character classes such as '[a-z]' for denormalized characters.
273
- * Escapes non-NFD characters as hex escapes.
274
- * @param cookedFrom input regex string
275
- * @returns updated 'from' string
276
- */
277
- checkRanges(cookedFrom) {
278
- if (!cookedFrom)
279
- return cookedFrom;
280
- // extract all of the potential ranges - but don't match any-markers!
281
- const anyRange = /(?<!\\uffff\\u0008)\[([^\]]+)\]/g;
282
- const ranges = cookedFrom.matchAll(anyRange);
283
- if (!ranges)
284
- return cookedFrom;
285
- // extract inner members of a range (inside the [])
286
- const rangeRegex = /(\\u\{[0-9a-fA-F]\}{1,6}|.)-(\\u\{[0-9a-fA-F]\}{1,6}|.)|./g;
287
- const rangeExplicit = new util.NFDAnalyzer();
288
- const rangeImplicit = new util.NFDAnalyzer();
289
- /** process an explicit entry */
290
- function processExplicit(s) {
291
- if (s.startsWith('\\u{')) {
292
- s = util.unescapeString(s);
293
- }
294
- rangeExplicit.add(s);
295
- return s;
296
- }
297
- for (const [, sub] of ranges) {
298
- const subRanges = sub.matchAll(rangeRegex);
299
- for (const [all, start, end] of subRanges) {
300
- if (!start && !end) {
301
- // explicit single char
302
- processExplicit(all); // matched one char
303
- }
304
- else {
305
- // start-end range - get explicit start and end chars
306
- const s = processExplicit(start);
307
- const sch = s.codePointAt(0);
308
- const e = processExplicit(end);
309
- const ech = e.codePointAt(0);
310
- // now, process the inner chars, not including explicit
311
- for (let n = sch; n < ech; n++) {
312
- // add inner text
313
- rangeImplicit.add(String.fromCodePoint(n));
314
- }
315
- }
316
- }
317
- }
318
- // analyze ranges
319
- let needCooking = false;
320
- const explicitSet = rangeExplicit.analyze()?.get(util.BadStringType.denormalized);
321
- if (explicitSet) {
322
- this.callbacks.reportMessage(CompilerMessages.Warn_CharClassExplicitDenorm({ lowestCh: explicitSet.values().next().value }));
323
- needCooking = true;
324
- }
325
- else {
326
- // don't analyze the implicit set of THIS range, if explicit is already problematic
327
- const implicitSet = rangeImplicit.analyze()?.get(util.BadStringType.denormalized);
328
- if (implicitSet) {
329
- this.callbacks.reportMessage(CompilerMessages.Hint_CharClassImplicitDenorm({ lowestCh: implicitSet.values().next().value }));
330
- needCooking = true;
331
- }
332
- }
333
- // do we need to fixup the ranges?
334
- // don't do this unless we flagged issues above
335
- if (needCooking) {
336
- // if we get here, there are some ranges with troublesome chars.
337
- // we work around this by escaping all chars
338
- function cookOne(s) {
339
- if (s === '^') {
340
- return s; // syntax
341
- }
342
- else if (s.startsWith('\\u{') || s.startsWith('\\u')) {
343
- return s; // already escaped
344
- }
345
- else {
346
- return util.escapeRegexChar(s);
347
- }
348
- }
349
- return cookedFrom.replaceAll(anyRange, (ignored1, sub) => {
350
- return '[' + sub.replaceAll(rangeRegex, (all, start, end) => {
351
- if (!start && !end) {
352
- // explicit single char
353
- return cookOne(all); // matched one char
354
- }
355
- else {
356
- return cookOne(start) + '-' + cookOne(end);
357
- }
358
- }) + ']';
359
- });
360
- }
361
- return cookedFrom; // no change
362
- }
363
- }
364
- export class TranCompiler extends TransformCompiler {
365
- constructor(source, callbacks) {
366
- super(source, callbacks);
367
- this.type = 'simple';
368
- }
369
- newTran() {
370
- return new Tran();
371
- }
372
- get id() {
373
- return constants.section.tran;
374
- }
375
- }
376
- ;
377
- export class BkspCompiler extends TransformCompiler {
378
- constructor(source, callbacks) {
379
- super(source, callbacks);
380
- this.type = 'backspace';
381
- }
382
- newTran() {
383
- return new Bksp();
384
- }
385
- get id() {
386
- return constants.section.bksp;
387
- }
388
- }
389
- ;
390
- //# sourceMappingURL=tran.js.map
391
- //# debugId=25bcdbe4-16f8-5d94-b6ef-d181bc95feed
1
+ import { constants } from "@keymanapp/ldml-keyboard-constants";
2
+ import { KMXPlus, VariableParser, MarkerParser, util } from '@keymanapp/common-types';
3
+ import { SectionCompiler } from "./section-compiler.js";
4
+ var Bksp = KMXPlus.Bksp;
5
+ var Tran = KMXPlus.Tran;
6
+ var TranReorder = KMXPlus.TranReorder;
7
+ var TranTransform = KMXPlus.TranTransform;
8
+ import { verifyValidAndUnique } from "../util/util.js";
9
+ import { CompilerMessages } from "./messages.js";
10
+ import { SubstitutionUse } from "./substitution-tracker.js";
11
+ export class TransformCompiler extends SectionCompiler {
12
+ static validateSubstitutions(keyboard, st) {
13
+ keyboard?.transforms?.forEach(transforms => transforms.transformGroup.forEach(transformGroup => {
14
+ transformGroup.transform?.forEach(({ to, from }) => {
15
+ st.addSetAndStringSubtitution(SubstitutionUse.consume, from);
16
+ st.addSetAndStringSubtitution(SubstitutionUse.emit, to);
17
+ const mapFrom = VariableParser.CAPTURE_SET_REFERENCE.exec(from);
18
+ const mapTo = VariableParser.MAPPED_SET_REFERENCE.exec(to || '');
19
+ if (mapFrom) {
20
+ // add the 'from' as a match
21
+ st.set.add(SubstitutionUse.consume, [mapFrom[1]]);
22
+ }
23
+ if (mapTo) {
24
+ // add the 'from' as a match
25
+ st.set.add(SubstitutionUse.emit, [mapTo[1]]);
26
+ }
27
+ });
28
+ transformGroup.reorder?.forEach(({ before }) => {
29
+ st.addStringSubstitution(SubstitutionUse.consume, before);
30
+ });
31
+ }));
32
+ return true;
33
+ }
34
+ type;
35
+ constructor(source, callbacks) {
36
+ super(source, callbacks);
37
+ }
38
+ validate() {
39
+ const reportMessage = this.callbacks.reportMessage.bind(this.callbacks);
40
+ let valid = true;
41
+ const transforms = this?.keyboard3?.transforms;
42
+ if (transforms) {
43
+ const types = transforms.map(({ type }) => type);
44
+ if (!verifyValidAndUnique(types, types => reportMessage(CompilerMessages.Error_DuplicateTransformsType({ types })), new Set(['simple', 'backspace']), types => reportMessage(CompilerMessages.Error_InvalidTransformsType({ types })))) {
45
+ valid = false;
46
+ }
47
+ // check for mixed groups
48
+ let mixed = false;
49
+ let empty = false;
50
+ transforms.forEach(({ transformGroup }) => transformGroup.forEach((transformGroup) => {
51
+ if (transformGroup.reorder?.length && transformGroup.transform?.length) {
52
+ mixed = true;
53
+ }
54
+ if (!transformGroup.reorder?.length && !transformGroup.transform?.length) {
55
+ empty = true;
56
+ }
57
+ }));
58
+ if (mixed) {
59
+ valid = false;
60
+ reportMessage(CompilerMessages.Error_MixedTransformGroup()); // report this once
61
+ }
62
+ if (empty) {
63
+ valid = false;
64
+ reportMessage(CompilerMessages.Error_EmptyTransformGroup()); // report this once
65
+ }
66
+ // TODO-LDML: linting here should check for identical from, but this involves a double-parse which is ugly
67
+ // TODO-LDML: unicodesets means that either we fully parse them and verify conflicting rules or the linting is imperfect
68
+ }
69
+ return valid;
70
+ }
71
+ /* c8 ignore next 4 */
72
+ /** allocate a new TranBase subclass */
73
+ newTran() {
74
+ throw Error(`Internal Error: newTran() not implemented`);
75
+ }
76
+ compileTransforms(sections, transforms) {
77
+ let result = this.newTran();
78
+ if (transforms?.transformGroup) {
79
+ for (let transformGroup of transforms.transformGroup) {
80
+ const tg = this.compileTransformGroup(sections, transformGroup);
81
+ if (!tg)
82
+ return null; //error
83
+ result.groups.push(tg);
84
+ }
85
+ }
86
+ return result;
87
+ }
88
+ compileTransformGroup(sections, transformGroup) {
89
+ if (transformGroup.reorder.length && transformGroup.transform.length) {
90
+ /* c8 ignore next 2 */
91
+ // should have been caught by validate
92
+ throw Error(`Internal error: transformGroup has both reorder and transform elements.`);
93
+ }
94
+ else if (transformGroup.reorder.length) {
95
+ return this.compileReorderTranGroup(sections, transformGroup.reorder);
96
+ }
97
+ else if (transformGroup.transform.length) {
98
+ return this.compileTransformTranGroup(sections, transformGroup.transform);
99
+ }
100
+ else {
101
+ /* c8 ignore next */
102
+ throw Error(`Internal error: transformGroup has neither reorder nor transform elements.`);
103
+ }
104
+ }
105
+ compileTransformTranGroup(sections, transforms) {
106
+ const result = {
107
+ type: constants.tran_group_type_transform,
108
+ transforms: transforms.map(transform => this.compileTransform(sections, transform)),
109
+ reorders: [],
110
+ };
111
+ if (result.transforms.includes(null))
112
+ return null;
113
+ return result;
114
+ }
115
+ compileTransform(sections, transform) {
116
+ let result = new TranTransform();
117
+ let cookedFrom = transform.from;
118
+ // check for incorrect \uXXXX escapes. Do this before substituting markers or sets.
119
+ cookedFrom = this.checkEscapes(cookedFrom); // check for \uXXXX escapes before normalizing
120
+ cookedFrom = sections.vars.substituteStrings(cookedFrom, sections, true);
121
+ const mapFrom = VariableParser.CAPTURE_SET_REFERENCE.exec(cookedFrom);
122
+ const mapTo = VariableParser.MAPPED_SET_REFERENCE.exec(transform.to || '');
123
+ if (mapFrom && mapTo) { // TODO-LDML: error cases
124
+ result.mapFrom = sections.strs.allocString(mapFrom[1]); // var name
125
+ result.mapTo = sections.strs.allocString(mapTo[1]); // var name
126
+ }
127
+ else {
128
+ result.mapFrom = sections.strs.allocString('');
129
+ result.mapTo = sections.strs.allocString('');
130
+ }
131
+ if (cookedFrom === null)
132
+ return null; // error
133
+ // the set substution will not produce raw markers `\m{...}` but they will already be in sentinel form.
134
+ cookedFrom = sections.vars.substituteSetRegex(cookedFrom, sections);
135
+ // add in markers. idempotent if no markers.
136
+ cookedFrom = sections.vars.substituteMarkerString(cookedFrom, true);
137
+ // unescape from \u{} form to plain, or in some cases \uXXXX / \UXXXXXXXX for core
138
+ cookedFrom = util.unescapeStringToRegex(cookedFrom);
139
+ // check for denormalized ranges
140
+ cookedFrom = this.checkRanges(cookedFrom); // check before normalizing
141
+ if (!sections?.meta?.normalizationDisabled) {
142
+ // nfd here.
143
+ cookedFrom = MarkerParser.nfd_markers(cookedFrom, true);
144
+ }
145
+ // perform regex validation
146
+ if (!this.isValidRegex(cookedFrom, transform.from)) {
147
+ return null;
148
+ }
149
+ // cookedFrom is cooked above, since there's some special treatment
150
+ result.from = sections.strs.allocString(cookedFrom, {
151
+ unescape: false,
152
+ }, sections);
153
+ // 'to' is handled via allocString
154
+ result.to = sections.strs.allocString(transform.to, {
155
+ stringVariables: true,
156
+ markers: true,
157
+ unescape: true,
158
+ nfd: true,
159
+ }, sections);
160
+ return result;
161
+ }
162
+ /**
163
+ * Validate the final regex
164
+ * @param cookedFrom the regex to use, missing the trailing '$'
165
+ * @param from the original from - for error reporting
166
+ * @returns true if OK
167
+ */
168
+ isValidRegex(cookedFrom, from) {
169
+ // check for any unescaped dollar sign here
170
+ if (/(?<!\\)(?:\\\\)*\$/.test(cookedFrom)) {
171
+ this.callbacks.reportMessage(CompilerMessages.Error_IllegalTransformDollarsign({ from }));
172
+ return false;
173
+ }
174
+ if (/(?<!\\)(?:\\\\)*\*/.test(cookedFrom)) {
175
+ this.callbacks.reportMessage(CompilerMessages.Error_IllegalTransformAsterisk({ from }));
176
+ return false;
177
+ }
178
+ if (/(?<!\\)(?:\\\\)*\+/.test(cookedFrom)) {
179
+ this.callbacks.reportMessage(CompilerMessages.Error_IllegalTransformPlus({ from }));
180
+ return false;
181
+ }
182
+ // Verify that the regex is syntactically valid
183
+ try {
184
+ const rg = new RegExp(cookedFrom + '$', 'ug');
185
+ // Tests against the regex:
186
+ // does it match an empty string?
187
+ if (rg.test('')) {
188
+ this.callbacks.reportMessage(CompilerMessages.Error_TransformFromMatchesNothing({ from }));
189
+ return false;
190
+ }
191
+ }
192
+ catch (e) {
193
+ // We're exposing the internal regex error message here.
194
+ // In the future, CLDR plans to expose the EBNF for the transform,
195
+ // at which point we would have more precise validation prior to getting to this point.
196
+ this.callbacks.reportMessage(CompilerMessages.Error_UnparseableTransformFrom({ from, message: e.message }));
197
+ return false;
198
+ }
199
+ return true;
200
+ }
201
+ compileReorderTranGroup(sections, reorders) {
202
+ const result = {
203
+ type: constants.tran_group_type_reorder,
204
+ transforms: [],
205
+ reorders: reorders.map(reorder => this.compileReorder(sections, reorder)),
206
+ };
207
+ if (result.reorders.includes(null))
208
+ return null; // if any of the reorders returned null, fail the entire group.
209
+ return result;
210
+ }
211
+ compileReorder(sections, reorder) {
212
+ let result = new TranReorder();
213
+ if (reorder.from && this.checkEscapes(reorder.from) === null) {
214
+ return null; // error'ed
215
+ }
216
+ if (reorder.before && this.checkEscapes(reorder.before) === null) {
217
+ return null; // error'ed
218
+ }
219
+ result.elements = sections.elem.allocElementString(sections, reorder.from, reorder.order, reorder.tertiary, reorder.tertiaryBase, reorder.preBase);
220
+ result.before = sections.elem.allocElementString(sections, reorder.before);
221
+ if (!result.elements || !result.before) {
222
+ return null; // already error'ed
223
+ }
224
+ else {
225
+ return result;
226
+ }
227
+ }
228
+ compile(sections) {
229
+ for (let t of this.keyboard3.transforms) {
230
+ if (t.type == this.type) {
231
+ // compile only the transforms of the correct type
232
+ return this.compileTransforms(sections, t);
233
+ }
234
+ }
235
+ return this.newTran(); // empty: nothing of this type found.
236
+ }
237
+ get dependencies() {
238
+ const defaults = new Set([
239
+ constants.section.elem,
240
+ constants.section.list,
241
+ constants.section.meta,
242
+ constants.section.strs,
243
+ constants.section.uset,
244
+ constants.section.vars,
245
+ ]);
246
+ defaults.delete(this.id);
247
+ return defaults;
248
+ }
249
+ /**
250
+ * Analyze reorders and regexes for \uXXXX escapes.
251
+ * The LDML spec requires \u{XXXX} format.
252
+ * @param cookedFrom the original string
253
+ * @returns the original string, or null if an error was reported
254
+ */
255
+ checkEscapes(cookedFrom) {
256
+ if (!cookedFrom)
257
+ return cookedFrom;
258
+ // should not follow marker prefix, nor marker prefix with range
259
+ const anyQuad = /(?<!\\uffff\\u0008(?:\[[0-9a-fA-F\\u-]*)?)\\u([0-9a-fA-F]{4})/g;
260
+ for (const [, sub] of cookedFrom.matchAll(anyQuad)) {
261
+ const s = util.unescapeOne(sub);
262
+ if (s !== '\uffff' && s !== '\u0008') { // markers
263
+ this.callbacks.reportMessage(CompilerMessages.Error_InvalidQuadEscape({ cp: s.codePointAt(0) }));
264
+ return null; // exit on the first error
265
+ }
266
+ }
267
+ return cookedFrom;
268
+ }
269
+ /**
270
+ * Analyze character classes such as '[a-z]' for denormalized characters.
271
+ * Escapes non-NFD characters as hex escapes.
272
+ * @param cookedFrom input regex string
273
+ * @returns updated 'from' string
274
+ */
275
+ checkRanges(cookedFrom) {
276
+ if (!cookedFrom)
277
+ return cookedFrom;
278
+ // extract all of the potential ranges - but don't match any-markers!
279
+ const anyRange = /(?<!\\uffff\\u0008)\[([^\]]+)\]/g;
280
+ const ranges = cookedFrom.matchAll(anyRange);
281
+ if (!ranges)
282
+ return cookedFrom;
283
+ // extract inner members of a range (inside the [])
284
+ const rangeRegex = /(\\u\{[0-9a-fA-F]\}{1,6}|.)-(\\u\{[0-9a-fA-F]\}{1,6}|.)|./g;
285
+ const rangeExplicit = new util.NFDAnalyzer();
286
+ const rangeImplicit = new util.NFDAnalyzer();
287
+ /** process an explicit entry */
288
+ function processExplicit(s) {
289
+ if (s.startsWith('\\u{')) {
290
+ s = util.unescapeString(s);
291
+ }
292
+ rangeExplicit.add(s);
293
+ return s;
294
+ }
295
+ for (const [, sub] of ranges) {
296
+ const subRanges = sub.matchAll(rangeRegex);
297
+ for (const [all, start, end] of subRanges) {
298
+ if (!start && !end) {
299
+ // explicit single char
300
+ processExplicit(all); // matched one char
301
+ }
302
+ else {
303
+ // start-end range - get explicit start and end chars
304
+ const s = processExplicit(start);
305
+ const sch = s.codePointAt(0);
306
+ const e = processExplicit(end);
307
+ const ech = e.codePointAt(0);
308
+ // now, process the inner chars, not including explicit
309
+ for (let n = sch; n < ech; n++) {
310
+ // add inner text
311
+ rangeImplicit.add(String.fromCodePoint(n));
312
+ }
313
+ }
314
+ }
315
+ }
316
+ // analyze ranges
317
+ let needCooking = false;
318
+ const explicitSet = rangeExplicit.analyze()?.get(util.BadStringType.denormalized);
319
+ if (explicitSet) {
320
+ this.callbacks.reportMessage(CompilerMessages.Warn_CharClassExplicitDenorm({ lowestCh: explicitSet.values().next().value }));
321
+ needCooking = true;
322
+ }
323
+ else {
324
+ // don't analyze the implicit set of THIS range, if explicit is already problematic
325
+ const implicitSet = rangeImplicit.analyze()?.get(util.BadStringType.denormalized);
326
+ if (implicitSet) {
327
+ this.callbacks.reportMessage(CompilerMessages.Hint_CharClassImplicitDenorm({ lowestCh: implicitSet.values().next().value }));
328
+ needCooking = true;
329
+ }
330
+ }
331
+ // do we need to fixup the ranges?
332
+ // don't do this unless we flagged issues above
333
+ if (needCooking) {
334
+ // if we get here, there are some ranges with troublesome chars.
335
+ // we work around this by escaping all chars
336
+ function cookOne(s) {
337
+ if (s === '^') {
338
+ return s; // syntax
339
+ }
340
+ else if (s.startsWith('\\u{') || s.startsWith('\\u')) {
341
+ return s; // already escaped
342
+ }
343
+ else {
344
+ return util.escapeRegexChar(s);
345
+ }
346
+ }
347
+ return cookedFrom.replaceAll(anyRange, (ignored1, sub) => {
348
+ return '[' + sub.replaceAll(rangeRegex, (all, start, end) => {
349
+ if (!start && !end) {
350
+ // explicit single char
351
+ return cookOne(all); // matched one char
352
+ }
353
+ else {
354
+ return cookOne(start) + '-' + cookOne(end);
355
+ }
356
+ }) + ']';
357
+ });
358
+ }
359
+ return cookedFrom; // no change
360
+ }
361
+ }
362
+ export class TranCompiler extends TransformCompiler {
363
+ constructor(source, callbacks) {
364
+ super(source, callbacks);
365
+ this.type = 'simple';
366
+ }
367
+ newTran() {
368
+ return new Tran();
369
+ }
370
+ get id() {
371
+ return constants.section.tran;
372
+ }
373
+ }
374
+ ;
375
+ export class BkspCompiler extends TransformCompiler {
376
+ constructor(source, callbacks) {
377
+ super(source, callbacks);
378
+ this.type = 'backspace';
379
+ }
380
+ newTran() {
381
+ return new Bksp();
382
+ }
383
+ get id() {
384
+ return constants.section.bksp;
385
+ }
386
+ }
387
+ ;
388
+ //# sourceMappingURL=tran.js.map