@keymanapp/kmc-ldml 17.0.299-beta → 17.0.301-beta

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