@spintax/core 0.7.0 → 0.9.0
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.
- package/CHANGELOG.md +435 -0
- package/README.md +7 -2
- package/dist/index.cjs +915 -309
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -5
- package/dist/index.d.ts +10 -5
- package/dist/index.js +915 -309
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/internal/ast.ts
|
|
4
|
-
var AST_VERSION =
|
|
4
|
+
var AST_VERSION = 4;
|
|
5
5
|
function walk(nodes, visit) {
|
|
6
6
|
const stack = [{ list: nodes, i: 0 }];
|
|
7
7
|
while (stack.length > 0) {
|
|
@@ -36,6 +36,15 @@ function isParsedAst(value) {
|
|
|
36
36
|
return typeof value === "object" && value !== null && value.astVersion === AST_VERSION && typeof value.source === "string" && Array.isArray(value.nodes);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// src/internal/name-map.ts
|
|
40
|
+
function nameMap(...sources) {
|
|
41
|
+
const map = /* @__PURE__ */ Object.create(null);
|
|
42
|
+
for (const source of sources) {
|
|
43
|
+
for (const name of Object.keys(source)) map[name] = source[name];
|
|
44
|
+
}
|
|
45
|
+
return map;
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
// src/internal/neutralize.ts
|
|
40
49
|
var STRUCTURAL = ["{", "}", "[", "]", "%", "#"];
|
|
41
50
|
var SENTINEL_BASE = 57344;
|
|
@@ -61,18 +70,300 @@ function stripSentinels(text) {
|
|
|
61
70
|
return text.replace(RESTORE_RE, "");
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
// src/internal/
|
|
65
|
-
var
|
|
66
|
-
var
|
|
73
|
+
// src/internal/pairs.ts
|
|
74
|
+
var BRACE_OPEN = 123;
|
|
75
|
+
var BRACE_CLOSE = 125;
|
|
76
|
+
var BRACKET_OPEN = 91;
|
|
77
|
+
var BRACKET_CLOSE = 93;
|
|
78
|
+
function matchPairs(text, open, close) {
|
|
79
|
+
const pairs = new Int32Array(text.length).fill(-1);
|
|
80
|
+
const stack = [];
|
|
81
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
82
|
+
const code = text.charCodeAt(i);
|
|
83
|
+
if (code === open) {
|
|
84
|
+
stack.push(i);
|
|
85
|
+
} else if (code === close) {
|
|
86
|
+
const opener = stack.pop();
|
|
87
|
+
if (opener !== void 0) pairs[opener] = i;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return pairs;
|
|
91
|
+
}
|
|
92
|
+
function matchAnyPairs(text) {
|
|
93
|
+
const pairs = new Int32Array(text.length).fill(-1);
|
|
94
|
+
const stack = [];
|
|
95
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
96
|
+
const code = text.charCodeAt(i);
|
|
97
|
+
if (code === BRACE_OPEN || code === BRACKET_OPEN) {
|
|
98
|
+
stack.push(i);
|
|
99
|
+
} else if (code === BRACE_CLOSE || code === BRACKET_CLOSE) {
|
|
100
|
+
const opener = stack.pop();
|
|
101
|
+
if (opener !== void 0) pairs[opener] = i;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return pairs;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/internal/text-index.ts
|
|
108
|
+
var PIPE = 124;
|
|
109
|
+
var COLON = 58;
|
|
110
|
+
var QUOTE = 34;
|
|
111
|
+
var LT = 60;
|
|
112
|
+
var GT = 62;
|
|
113
|
+
var BANG = 33;
|
|
114
|
+
var QUESTION = 63;
|
|
67
115
|
var CONDITIONAL_NAME_RE = /[A-Za-z_]\w*/y;
|
|
116
|
+
var REFERENCE_SCAN_RE = /%(\w+)%/gu;
|
|
117
|
+
var CLOSING_TAG_RE = /<\/([a-z][a-z0-9-]*)[ \t\n\x0B\f\r]*>/giu;
|
|
118
|
+
function foldTagName(name) {
|
|
119
|
+
return name.toLowerCase().split(LONG_S).join("s");
|
|
120
|
+
}
|
|
121
|
+
var LONG_S = String.fromCharCode(383);
|
|
122
|
+
var rereadSpans = /* @__PURE__ */ new WeakMap();
|
|
123
|
+
function lowerBound(list, x) {
|
|
124
|
+
let lo = 0;
|
|
125
|
+
let hi = list.length;
|
|
126
|
+
while (lo < hi) {
|
|
127
|
+
const mid = lo + hi >>> 1;
|
|
128
|
+
if (list[mid] < x) lo = mid + 1;
|
|
129
|
+
else hi = mid;
|
|
130
|
+
}
|
|
131
|
+
return lo;
|
|
132
|
+
}
|
|
133
|
+
var TextIndex = class {
|
|
134
|
+
text;
|
|
135
|
+
braces = null;
|
|
136
|
+
brackets = null;
|
|
137
|
+
any = null;
|
|
138
|
+
pipeBrace = null;
|
|
139
|
+
pipeBracket = null;
|
|
140
|
+
pipesByDepth = null;
|
|
141
|
+
colons = null;
|
|
142
|
+
quotes = null;
|
|
143
|
+
gtByQuoteParity = null;
|
|
144
|
+
closingTags = null;
|
|
145
|
+
angles = null;
|
|
146
|
+
heads = null;
|
|
147
|
+
headCloseMin = null;
|
|
148
|
+
headTreeSize = 0;
|
|
149
|
+
definedReferences = /* @__PURE__ */ new WeakMap();
|
|
150
|
+
constructor(text) {
|
|
151
|
+
this.text = text;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The closer matching the `{` or `[` at `i` within a span ending at `end`, or -1.
|
|
155
|
+
*
|
|
156
|
+
* The table pairs the whole text, and a span agrees with it: an opener inside a span is closed there
|
|
157
|
+
* exactly when the closer the whole text pairs it with lies inside too, because every opener pushed
|
|
158
|
+
* after it is pushed after the span begins.
|
|
159
|
+
*/
|
|
160
|
+
closerWithin(i, end) {
|
|
161
|
+
const table = this.text.charCodeAt(i) === BRACE_OPEN ? this.braces ??= matchPairs(this.text, BRACE_OPEN, BRACE_CLOSE) : this.brackets ??= matchPairs(this.text, BRACKET_OPEN, BRACKET_CLOSE);
|
|
162
|
+
const close = table[i];
|
|
163
|
+
return close !== -1 && close < end ? close : -1;
|
|
164
|
+
}
|
|
165
|
+
/** {@link matchAnyPairs} of the text: one stack for both bracket kinds, as a clamped depth counts. */
|
|
166
|
+
anyPairs() {
|
|
167
|
+
return this.any ??= matchAnyPairs(this.text);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The pipes of `[start, end)` that `splitTopLevel` splits at, in order.
|
|
171
|
+
*
|
|
172
|
+
* Its two counters are signed and start at zero, so a pipe at `p` splits exactly when the text before
|
|
173
|
+
* it holds as many `{` as `}` and as many `[` as `]` counted from `start` — when both running totals
|
|
174
|
+
* over the whole text read the same at `p` as at `start`. The pipes are grouped by those two totals
|
|
175
|
+
* once, and a span takes the group of its start.
|
|
176
|
+
*/
|
|
177
|
+
topLevelPipes(start, end) {
|
|
178
|
+
if (this.pipesByDepth === null) this.indexPipes();
|
|
179
|
+
const braceDepth = this.pipeBrace;
|
|
180
|
+
const bracketDepth = this.pipeBracket;
|
|
181
|
+
const group = this.pipesByDepth.get(braceDepth[start])?.get(bracketDepth[start]);
|
|
182
|
+
if (group === void 0) return [];
|
|
183
|
+
const out = [];
|
|
184
|
+
for (let k = lowerBound(group, start); k < group.length && group[k] < end; k += 1) {
|
|
185
|
+
out.push(group[k]);
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
indexPipes() {
|
|
190
|
+
const n = this.text.length;
|
|
191
|
+
const braceDepth = new Int32Array(n + 1);
|
|
192
|
+
const bracketDepth = new Int32Array(n + 1);
|
|
193
|
+
const groups = /* @__PURE__ */ new Map();
|
|
194
|
+
let brace = 0;
|
|
195
|
+
let bracket = 0;
|
|
196
|
+
for (let i = 0; i < n; i += 1) {
|
|
197
|
+
braceDepth[i] = brace;
|
|
198
|
+
bracketDepth[i] = bracket;
|
|
199
|
+
const code = this.text.charCodeAt(i);
|
|
200
|
+
if (code === BRACE_OPEN) brace += 1;
|
|
201
|
+
else if (code === BRACE_CLOSE) brace -= 1;
|
|
202
|
+
else if (code === BRACKET_OPEN) bracket += 1;
|
|
203
|
+
else if (code === BRACKET_CLOSE) bracket -= 1;
|
|
204
|
+
else if (code === PIPE) {
|
|
205
|
+
let byBracket = groups.get(brace);
|
|
206
|
+
if (byBracket === void 0) groups.set(brace, byBracket = /* @__PURE__ */ new Map());
|
|
207
|
+
const list = byBracket.get(bracket);
|
|
208
|
+
if (list === void 0) byBracket.set(bracket, [i]);
|
|
209
|
+
else list.push(i);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
braceDepth[n] = brace;
|
|
213
|
+
bracketDepth[n] = bracket;
|
|
214
|
+
this.pipeBrace = braceDepth;
|
|
215
|
+
this.pipeBracket = bracketDepth;
|
|
216
|
+
this.pipesByDepth = groups;
|
|
217
|
+
}
|
|
218
|
+
/** The first `:` in `[from, end)`, or -1. */
|
|
219
|
+
colonWithin(from, end) {
|
|
220
|
+
const colons = this.colons ??= this.positionsOf(COLON);
|
|
221
|
+
const k = lowerBound(colons, from);
|
|
222
|
+
return k < colons.length && colons[k] < end ? colons[k] : -1;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The `>` that ends a `<config>` whose `<` is at `lt`, before `end`, or -1: the first one with an even
|
|
226
|
+
* number of `"` between the `<` and it — the quote toggle `findConfigEnd` keeps, as a parity.
|
|
227
|
+
*/
|
|
228
|
+
configEnd(lt, end) {
|
|
229
|
+
const quotes = this.quotes ??= this.positionsOf(QUOTE);
|
|
230
|
+
if (this.gtByQuoteParity === null) {
|
|
231
|
+
const even = [];
|
|
232
|
+
const odd = [];
|
|
233
|
+
let seen = 0;
|
|
234
|
+
for (let i = 0; i < this.text.length; i += 1) {
|
|
235
|
+
const code = this.text.charCodeAt(i);
|
|
236
|
+
if (code === QUOTE) seen += 1;
|
|
237
|
+
else if (code === GT) (seen % 2 === 0 ? even : odd).push(i);
|
|
238
|
+
}
|
|
239
|
+
this.gtByQuoteParity = [even, odd];
|
|
240
|
+
}
|
|
241
|
+
const parity = lowerBound(quotes, lt + 1) % 2;
|
|
242
|
+
const candidates = this.gtByQuoteParity[parity];
|
|
243
|
+
const k = lowerBound(candidates, lt + 1);
|
|
244
|
+
return k < candidates.length && candidates[k] < end ? candidates[k] : -1;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Does a closing tag of `tagName` lie wholly inside `[from, end)`? Closing tags cannot overlap — each
|
|
248
|
+
* starts with the only `<` it holds — so one scan of the whole text finds every one a scan of the span
|
|
249
|
+
* would, and the first one starting at or after `from` is the one that ends soonest.
|
|
250
|
+
*/
|
|
251
|
+
hasClosingTag(tagName, from, end) {
|
|
252
|
+
if (this.closingTags === null) {
|
|
253
|
+
const tags = /* @__PURE__ */ new Map();
|
|
254
|
+
for (const m of this.text.matchAll(CLOSING_TAG_RE)) {
|
|
255
|
+
const name = foldTagName(m[1] ?? "");
|
|
256
|
+
let entry2 = tags.get(name);
|
|
257
|
+
if (entry2 === void 0) tags.set(name, entry2 = { starts: [], ends: [] });
|
|
258
|
+
entry2.starts.push(m.index ?? 0);
|
|
259
|
+
entry2.ends.push((m.index ?? 0) + m[0].length);
|
|
260
|
+
}
|
|
261
|
+
this.closingTags = tags;
|
|
262
|
+
}
|
|
263
|
+
const entry = this.closingTags.get(tagName);
|
|
264
|
+
if (entry === void 0) return false;
|
|
265
|
+
const k = lowerBound(entry.starts, from);
|
|
266
|
+
return k < entry.starts.length && entry.ends[k] <= end;
|
|
267
|
+
}
|
|
268
|
+
/** The last `<` or `>` in `[floor, at]`, or -1. */
|
|
269
|
+
lastAngleWithin(floor, at) {
|
|
270
|
+
if (this.angles === null) {
|
|
271
|
+
const angles = [];
|
|
272
|
+
for (let i = 0; i < this.text.length; i += 1) {
|
|
273
|
+
const code = this.text.charCodeAt(i);
|
|
274
|
+
if (code === LT || code === GT) angles.push(i);
|
|
275
|
+
}
|
|
276
|
+
this.angles = angles;
|
|
277
|
+
}
|
|
278
|
+
const k = lowerBound(this.angles, at + 1) - 1;
|
|
279
|
+
return k >= 0 && this.angles[k] >= floor ? this.angles[k] : -1;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Would the renderer's conditional pass over `[start, end)` resolve anything? Exactly when a `{?` in
|
|
283
|
+
* the span opens a well-formed head and its brace closes inside the span: the pass reaches the first
|
|
284
|
+
* such one in order, because every `{?` before it is one it steps over.
|
|
285
|
+
*/
|
|
286
|
+
hasConditionalWithin(start, end) {
|
|
287
|
+
if (this.heads === null) this.indexHeads();
|
|
288
|
+
const heads = this.heads;
|
|
289
|
+
let lo = lowerBound(heads, start) + this.headTreeSize;
|
|
290
|
+
let hi = lowerBound(heads, end) + this.headTreeSize;
|
|
291
|
+
const tree = this.headCloseMin;
|
|
292
|
+
let min = Infinity;
|
|
293
|
+
while (lo < hi) {
|
|
294
|
+
if (lo & 1) min = Math.min(min, tree[lo++]);
|
|
295
|
+
if (hi & 1) min = Math.min(min, tree[--hi]);
|
|
296
|
+
lo >>= 1;
|
|
297
|
+
hi >>= 1;
|
|
298
|
+
}
|
|
299
|
+
return min < end;
|
|
300
|
+
}
|
|
301
|
+
indexHeads() {
|
|
302
|
+
const heads = [];
|
|
303
|
+
const closes = [];
|
|
304
|
+
const braces = this.braces ??= matchPairs(this.text, BRACE_OPEN, BRACE_CLOSE);
|
|
305
|
+
for (let at = this.text.indexOf("{?"); at !== -1; at = this.text.indexOf("{?", at + 1)) {
|
|
306
|
+
let p = at + 2;
|
|
307
|
+
if (this.text.charCodeAt(p) === BANG) p += 1;
|
|
308
|
+
CONDITIONAL_NAME_RE.lastIndex = p;
|
|
309
|
+
const name = CONDITIONAL_NAME_RE.exec(this.text);
|
|
310
|
+
if (name === null || this.text.charCodeAt(p + name[0].length) !== QUESTION) continue;
|
|
311
|
+
heads.push(at);
|
|
312
|
+
closes.push(braces[at]);
|
|
313
|
+
}
|
|
314
|
+
let size = 1;
|
|
315
|
+
while (size < heads.length) size *= 2;
|
|
316
|
+
const tree = new Float64Array(2 * size).fill(Infinity);
|
|
317
|
+
closes.forEach((close, k) => {
|
|
318
|
+
tree[size + k] = close === -1 ? Infinity : close;
|
|
319
|
+
});
|
|
320
|
+
for (let k = size - 1; k >= 1; k -= 1) tree[k] = Math.min(tree[2 * k], tree[2 * k + 1]);
|
|
321
|
+
this.heads = heads;
|
|
322
|
+
this.headCloseMin = tree;
|
|
323
|
+
this.headTreeSize = size;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Would expansion substitute anything in `[start, end)` — is a reference there to a name `vars`
|
|
327
|
+
* defines? A reference cannot straddle a span of a construct's content (a bracket bounds it on both
|
|
328
|
+
* sides), so the references one scan of the whole text finds are the ones a scan of the span would.
|
|
329
|
+
*/
|
|
330
|
+
hasDefinedReferenceWithin(start, end, vars) {
|
|
331
|
+
let positions = this.definedReferences.get(vars);
|
|
332
|
+
if (positions === void 0) {
|
|
333
|
+
positions = [];
|
|
334
|
+
for (const m of this.text.matchAll(REFERENCE_SCAN_RE)) {
|
|
335
|
+
if (vars[(m[1] ?? "").toLowerCase()] !== void 0) positions.push(m.index ?? 0);
|
|
336
|
+
}
|
|
337
|
+
this.definedReferences.set(vars, positions);
|
|
338
|
+
}
|
|
339
|
+
const k = lowerBound(positions, start);
|
|
340
|
+
return k < positions.length && positions[k] < end;
|
|
341
|
+
}
|
|
342
|
+
positionsOf(code) {
|
|
343
|
+
const ch = String.fromCharCode(code);
|
|
344
|
+
const out = [];
|
|
345
|
+
for (let i = this.text.indexOf(ch); i !== -1; i = this.text.indexOf(ch, i + 1)) out.push(i);
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// src/internal/parser.ts
|
|
351
|
+
var VARIABLE_RE = /%(\w+)%/y;
|
|
352
|
+
var PERCENT = 37;
|
|
353
|
+
var QUESTION2 = 63;
|
|
354
|
+
var PIPE2 = 124;
|
|
355
|
+
var LT2 = 60;
|
|
356
|
+
var GT2 = 62;
|
|
357
|
+
var DIRECTIVE_RE = /^[ \t]*#(set|def)[ \t]+%(\w+)%[ \t]*=[ \t]*((?:.*[^ \t\n\r\u2028\u2029])?)[ \t]*\r?$/gmu;
|
|
358
|
+
var CONDITIONAL_NAME_RE2 = /[A-Za-z_]\w*/y;
|
|
68
359
|
var PLURAL_PREFIX = "plural ";
|
|
69
360
|
function parseTemplate(src) {
|
|
70
361
|
const { body, setDefs, defDefs } = extractDirectives(stripComments(stripSentinels(src)));
|
|
71
362
|
return { astVersion: AST_VERSION, source: src, setDefs, defDefs, nodes: parseSequence(body) };
|
|
72
363
|
}
|
|
73
364
|
function extractDirectives(text) {
|
|
74
|
-
const setDefs =
|
|
75
|
-
const defDefs =
|
|
365
|
+
const setDefs = nameMap();
|
|
366
|
+
const defDefs = nameMap();
|
|
76
367
|
const occurrences = [];
|
|
77
368
|
let cursorOffset = 0;
|
|
78
369
|
let cursorLine = 1;
|
|
@@ -102,16 +393,36 @@ function extractDirectives(text) {
|
|
|
102
393
|
return { body: stripped.replace(/\n{3,}/gu, "\n\n"), setDefs, defDefs, occurrences };
|
|
103
394
|
}
|
|
104
395
|
function stripComments(text) {
|
|
105
|
-
|
|
396
|
+
let out = "";
|
|
397
|
+
let from = 0;
|
|
398
|
+
for (; ; ) {
|
|
399
|
+
const open = text.indexOf("/#", from);
|
|
400
|
+
if (open === -1) break;
|
|
401
|
+
const close = text.indexOf("#/", open + 2);
|
|
402
|
+
if (close === -1) break;
|
|
403
|
+
out += text.slice(from, open);
|
|
404
|
+
from = close + 2;
|
|
405
|
+
}
|
|
406
|
+
return from === 0 ? text : out + text.slice(from);
|
|
106
407
|
}
|
|
107
408
|
function parseSequence(text) {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
409
|
+
const index = new TextIndex(text);
|
|
410
|
+
const frame = (start, end) => ({
|
|
411
|
+
start,
|
|
412
|
+
end,
|
|
413
|
+
i: start,
|
|
414
|
+
literal: start,
|
|
415
|
+
nodes: [],
|
|
416
|
+
plan: null,
|
|
417
|
+
parts: []
|
|
418
|
+
});
|
|
419
|
+
const stack = [frame(0, text.length)];
|
|
110
420
|
while (stack.length > 0) {
|
|
111
421
|
const f = stack[stack.length - 1];
|
|
112
422
|
if (f.plan !== null) {
|
|
113
|
-
if (f.parts.length < f.plan.
|
|
114
|
-
|
|
423
|
+
if (f.parts.length < f.plan.spans.length) {
|
|
424
|
+
const span2 = f.plan.spans[f.parts.length];
|
|
425
|
+
stack.push(frame(span2[0], span2[1]));
|
|
115
426
|
continue;
|
|
116
427
|
}
|
|
117
428
|
f.nodes.push(f.plan.build(f.parts));
|
|
@@ -119,39 +430,35 @@ function parseSequence(text) {
|
|
|
119
430
|
f.parts = [];
|
|
120
431
|
continue;
|
|
121
432
|
}
|
|
122
|
-
const flushLiteral = () => {
|
|
123
|
-
if (f.literal
|
|
124
|
-
f.nodes.push({ type: "literal", value: f.literal });
|
|
125
|
-
f.literal = "";
|
|
126
|
-
}
|
|
433
|
+
const flushLiteral = (to) => {
|
|
434
|
+
if (to > f.literal) f.nodes.push({ type: "literal", value: text.slice(f.literal, to) });
|
|
127
435
|
};
|
|
128
436
|
let planned = null;
|
|
129
|
-
while (f.i < f.
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
const close =
|
|
133
|
-
|
|
134
|
-
if (end === -1) {
|
|
135
|
-
f.literal += ch;
|
|
437
|
+
while (f.i < f.end && planned === null) {
|
|
438
|
+
const code = text.charCodeAt(f.i);
|
|
439
|
+
if (code === BRACE_OPEN || code === BRACKET_OPEN) {
|
|
440
|
+
const close = index.closerWithin(f.i, f.end);
|
|
441
|
+
if (close === -1) {
|
|
136
442
|
f.i += 1;
|
|
137
443
|
continue;
|
|
138
444
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
f.
|
|
445
|
+
flushLiteral(f.i);
|
|
446
|
+
planned = code === BRACE_OPEN ? planBraceConstruct(index, f.i + 1, close) : planPermutation(index, f.i + 1, close);
|
|
447
|
+
f.i = close + 1;
|
|
448
|
+
f.literal = f.i;
|
|
143
449
|
continue;
|
|
144
450
|
}
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
f.
|
|
150
|
-
f.
|
|
451
|
+
if (code === PERCENT) {
|
|
452
|
+
VARIABLE_RE.lastIndex = f.i;
|
|
453
|
+
const m = VARIABLE_RE.exec(text);
|
|
454
|
+
if (m !== null && VARIABLE_RE.lastIndex <= f.end) {
|
|
455
|
+
flushLiteral(f.i);
|
|
456
|
+
f.nodes.push({ type: "variable", name: m[1] });
|
|
457
|
+
f.i = VARIABLE_RE.lastIndex;
|
|
458
|
+
f.literal = f.i;
|
|
151
459
|
continue;
|
|
152
460
|
}
|
|
153
461
|
}
|
|
154
|
-
f.literal += ch;
|
|
155
462
|
f.i += 1;
|
|
156
463
|
}
|
|
157
464
|
if (planned !== null) {
|
|
@@ -159,7 +466,7 @@ function parseSequence(text) {
|
|
|
159
466
|
else f.plan = planned;
|
|
160
467
|
continue;
|
|
161
468
|
}
|
|
162
|
-
flushLiteral();
|
|
469
|
+
flushLiteral(f.end);
|
|
163
470
|
stack.pop();
|
|
164
471
|
const parent = stack[stack.length - 1];
|
|
165
472
|
if (parent !== void 0) parent.parts.push(f.nodes);
|
|
@@ -167,116 +474,130 @@ function parseSequence(text) {
|
|
|
167
474
|
}
|
|
168
475
|
return [];
|
|
169
476
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
477
|
+
function spansBetween(start, end, pipes) {
|
|
478
|
+
const spans = [];
|
|
479
|
+
let from = start;
|
|
480
|
+
for (const pipe of pipes) {
|
|
481
|
+
spans.push([from, pipe]);
|
|
482
|
+
from = pipe + 1;
|
|
483
|
+
}
|
|
484
|
+
spans.push([from, end]);
|
|
485
|
+
return spans;
|
|
486
|
+
}
|
|
487
|
+
function planBraceConstruct(index, start, end) {
|
|
488
|
+
const { text } = index;
|
|
489
|
+
if (start < end && text.charCodeAt(start) === QUESTION2) {
|
|
490
|
+
const head = recognizeConditional(text, start, end, index.anyPairs());
|
|
491
|
+
if (head !== null) {
|
|
492
|
+
const then = [head.bodyStart, head.sepIndex < 0 ? end : head.sepIndex];
|
|
493
|
+
const otherwise = head.sepIndex < 0 ? [end, end] : [head.sepIndex + 1, end];
|
|
174
494
|
return {
|
|
175
|
-
|
|
495
|
+
spans: [then, otherwise],
|
|
176
496
|
build: (children) => ({
|
|
177
497
|
type: "conditional",
|
|
178
|
-
name:
|
|
179
|
-
inverted:
|
|
498
|
+
name: head.name,
|
|
499
|
+
inverted: head.inverted,
|
|
180
500
|
then: children[0] ?? [],
|
|
181
501
|
else: children[1] ?? []
|
|
182
502
|
})
|
|
183
503
|
};
|
|
184
504
|
}
|
|
185
|
-
} else if (
|
|
186
|
-
|
|
505
|
+
} else if (end - start >= PLURAL_PREFIX.length && text.startsWith(PLURAL_PREFIX, start)) {
|
|
506
|
+
const colon = index.colonWithin(start + PLURAL_PREFIX.length, end);
|
|
507
|
+
if (colon !== -1) {
|
|
508
|
+
return {
|
|
509
|
+
node: {
|
|
510
|
+
type: "plural",
|
|
511
|
+
countRaw: text.slice(start + PLURAL_PREFIX.length, colon),
|
|
512
|
+
formsRaw: text.slice(colon + 1, end)
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
187
516
|
}
|
|
188
517
|
return {
|
|
189
|
-
|
|
190
|
-
build: (children) =>
|
|
518
|
+
spans: spansBetween(start, end, index.topLevelPipes(start, end)),
|
|
519
|
+
build: (children) => {
|
|
520
|
+
if (!needsTextualReread(children)) return { type: "enumeration", options: children };
|
|
521
|
+
const node = { type: "enumeration", options: children, raw: text.slice(start, end) };
|
|
522
|
+
rereadSpans.set(node, { index, start, end });
|
|
523
|
+
return node;
|
|
524
|
+
}
|
|
191
525
|
};
|
|
192
526
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
527
|
+
function needsTextualReread(lists) {
|
|
528
|
+
return lists.some((list) => list.some((node) => node.type === "variable" || node.type === "conditional"));
|
|
529
|
+
}
|
|
530
|
+
var REFERENCE_RE = /%\w+%/u;
|
|
531
|
+
function holdsConditional(text) {
|
|
532
|
+
if (!text.includes("{?")) return false;
|
|
533
|
+
const opens = [];
|
|
534
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
535
|
+
const ch = text.charAt(i);
|
|
536
|
+
if (ch === "{") {
|
|
537
|
+
opens.push(i);
|
|
538
|
+
} else if (ch === "}") {
|
|
539
|
+
const open = opens.pop();
|
|
540
|
+
if (open !== void 0 && text.charAt(open + 1) === "?" && recognizeConditional(text, open + 1, i) !== null) {
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
200
543
|
}
|
|
201
544
|
}
|
|
202
545
|
return false;
|
|
203
546
|
}
|
|
204
|
-
var
|
|
205
|
-
function planPermutation(
|
|
206
|
-
const {
|
|
207
|
-
const {
|
|
208
|
-
const
|
|
547
|
+
var holdsTextForReread = (text) => REFERENCE_RE.test(text) || holdsConditional(text);
|
|
548
|
+
function planPermutation(index, start, end) {
|
|
549
|
+
const { text } = index;
|
|
550
|
+
const { config, contentStart } = extractPermutationConfig(index, start, end);
|
|
551
|
+
const { spans, separators } = permutationElements(index, spansBetween(contentStart, end, index.topLevelPipes(contentStart, end)));
|
|
552
|
+
const header = text.slice(start, contentStart);
|
|
553
|
+
const textNeedsReread = holdsTextForReread(header) || separators.some((sep) => sep !== null && holdsTextForReread(sep));
|
|
209
554
|
return {
|
|
210
|
-
|
|
555
|
+
spans,
|
|
211
556
|
build: (children) => {
|
|
212
557
|
const options = children.map((nodes, i) => ({ nodes, separator: separators[i] ?? null }));
|
|
213
|
-
|
|
558
|
+
if (!textNeedsReread && !needsTextualReread(children)) return { type: "permutation", config, options };
|
|
559
|
+
const node = { type: "permutation", config, options, raw: text.slice(start, end) };
|
|
560
|
+
rereadSpans.set(node, { index, start, end });
|
|
561
|
+
return node;
|
|
214
562
|
}
|
|
215
563
|
};
|
|
216
564
|
}
|
|
217
|
-
function recognizeConditional(text, contentStart, contentEnd) {
|
|
565
|
+
function recognizeConditional(text, contentStart, contentEnd, anyPairs) {
|
|
218
566
|
let p = contentStart + 1;
|
|
219
567
|
let inverted = false;
|
|
220
568
|
if (text.charAt(p) === "!") {
|
|
221
569
|
inverted = true;
|
|
222
570
|
p += 1;
|
|
223
571
|
}
|
|
224
|
-
|
|
225
|
-
const name =
|
|
572
|
+
CONDITIONAL_NAME_RE2.lastIndex = p;
|
|
573
|
+
const name = CONDITIONAL_NAME_RE2.exec(text)?.[0];
|
|
226
574
|
if (name === void 0 || p + name.length > contentEnd) return null;
|
|
227
575
|
p += name.length;
|
|
228
576
|
if (text.charAt(p) !== "?") return null;
|
|
229
577
|
p += 1;
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
thenRaw: sep < 0 ? body : body.slice(0, sep),
|
|
241
|
-
elseRaw: sep < 0 ? "" : body.slice(sep + 1)
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
function parsePlural(afterPrefix) {
|
|
245
|
-
const colon = afterPrefix.indexOf(":");
|
|
246
|
-
return { type: "plural", countRaw: afterPrefix.slice(0, colon), formsRaw: afterPrefix.slice(colon + 1) };
|
|
247
|
-
}
|
|
248
|
-
var CONFIG_KEY_RE = /\b(?:minsize|maxsize|sep|lastsep)\s*=/i;
|
|
249
|
-
var MINSIZE_RE = /minsize\s*=\s*(\d+)/i;
|
|
250
|
-
var MAXSIZE_RE = /maxsize\s*=\s*(\d+)/i;
|
|
251
|
-
var SEP_RE = /(?<!last)sep\s*=\s*"([^"]*)"/i;
|
|
252
|
-
var LASTSEP_RE = /lastsep\s*=\s*"([^"]*)"/i;
|
|
253
|
-
var HTML_TAG_RE = /^([a-zA-Z][a-zA-Z0-9-]*)(?:\s+[^>]*)?\/?$/;
|
|
254
|
-
var PER_ELEM_HTML_RE = /^[a-zA-Z][a-zA-Z0-9]*\s/;
|
|
578
|
+
const sepIndex = anyPairs === void 0 ? firstTopLevelPipe(text, p, contentEnd) : firstTopLevelPipeByPairs(text, p, contentEnd, anyPairs);
|
|
579
|
+
return { name, inverted, bodyStart: p, sepIndex };
|
|
580
|
+
}
|
|
581
|
+
var CONFIG_KEY_RE = /\b(?:minsize|maxsize|sep|lastsep)[ \t\n\x0B\f\r]*=/i;
|
|
582
|
+
var MINSIZE_RE = /minsize[ \t\n\x0B\f\r]*=[ \t\n\x0B\f\r]*(\d+)/i;
|
|
583
|
+
var MAXSIZE_RE = /maxsize[ \t\n\x0B\f\r]*=[ \t\n\x0B\f\r]*(\d+)/i;
|
|
584
|
+
var SEP_RE = /(?<!last)sep[ \t\n\x0B\f\r]*=[ \t\n\x0B\f\r]*"([^"]*)"/i;
|
|
585
|
+
var LASTSEP_RE = /lastsep[ \t\n\x0B\f\r]*=[ \t\n\x0B\f\r]*"([^"]*)"/i;
|
|
586
|
+
var HTML_TAG_RE = /^([a-zA-Z][a-zA-Z0-9-]*)(?:[ \t\n\x0B\f\r][^>]*)?\/?$/;
|
|
587
|
+
var PER_ELEM_HTML_RE = /^[a-zA-Z][a-zA-Z0-9]*[ \t\n\x0B\f\r]/;
|
|
255
588
|
function defaultPermConfig() {
|
|
256
589
|
return { minsize: null, maxsize: null, sep: " ", lastsep: null };
|
|
257
590
|
}
|
|
258
|
-
function extractPermutationConfig(
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
const
|
|
264
|
-
if (
|
|
265
|
-
const configStr =
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return { config: defaultPermConfig(), content };
|
|
269
|
-
}
|
|
270
|
-
return { config: parseConfigString(configStr), content: remaining };
|
|
271
|
-
}
|
|
272
|
-
function findConfigEnd(text) {
|
|
273
|
-
let inQuote = false;
|
|
274
|
-
for (let i = 1; i < text.length; i += 1) {
|
|
275
|
-
const ch = text.charAt(i);
|
|
276
|
-
if (ch === '"') inQuote = !inQuote;
|
|
277
|
-
if (ch === ">" && !inQuote) return i;
|
|
278
|
-
}
|
|
279
|
-
return -1;
|
|
591
|
+
function extractPermutationConfig(index, start, end) {
|
|
592
|
+
const { text } = index;
|
|
593
|
+
let lt = start;
|
|
594
|
+
while (lt < end && isPhpTrimChar(text.charCodeAt(lt))) lt += 1;
|
|
595
|
+
if (lt === end || text.charCodeAt(lt) !== LT2) return { config: defaultPermConfig(), contentStart: start };
|
|
596
|
+
const gt = index.configEnd(lt, end);
|
|
597
|
+
if (gt === -1) return { config: defaultPermConfig(), contentStart: start };
|
|
598
|
+
const configStr = text.slice(lt + 1, gt);
|
|
599
|
+
if (looksLikeHtmlStartTag(index, configStr, gt + 1, end)) return { config: defaultPermConfig(), contentStart: start };
|
|
600
|
+
return { config: parseConfigString(configStr), contentStart: gt + 1 };
|
|
280
601
|
}
|
|
281
602
|
function parseConfigString(str) {
|
|
282
603
|
if (!CONFIG_KEY_RE.test(str)) {
|
|
@@ -289,58 +610,54 @@ function parseConfigString(str) {
|
|
|
289
610
|
lastsep: strGroup(LASTSEP_RE.exec(str))
|
|
290
611
|
};
|
|
291
612
|
}
|
|
292
|
-
function looksLikeHtmlStartTag(tagText,
|
|
613
|
+
function looksLikeHtmlStartTag(index, tagText, from, end) {
|
|
293
614
|
const trimmed = phpTrim(tagText);
|
|
294
615
|
if (trimmed === "") return false;
|
|
295
616
|
const m = HTML_TAG_RE.exec(trimmed);
|
|
296
617
|
if (!m) return false;
|
|
297
618
|
if (trimmed.endsWith("/")) return true;
|
|
298
|
-
|
|
299
|
-
return new RegExp(`</${escapeRegExp(tagName)}\\s*>`, "iu").test(remaining);
|
|
619
|
+
return index.hasClosingTag((m[1] ?? "").toLowerCase(), from, end);
|
|
300
620
|
}
|
|
301
|
-
function permutationElements(
|
|
302
|
-
const
|
|
621
|
+
function permutationElements(index, parts) {
|
|
622
|
+
const { text } = index;
|
|
623
|
+
const spans = [];
|
|
303
624
|
const separators = [];
|
|
304
625
|
let pendingSep = null;
|
|
305
|
-
|
|
306
|
-
let
|
|
626
|
+
parts.forEach(([partStart, partEnd], i) => {
|
|
627
|
+
let textEnd = partEnd;
|
|
307
628
|
let trailingSep = null;
|
|
308
|
-
if (i <
|
|
309
|
-
const extracted = extractTrailingSep(
|
|
629
|
+
if (i < parts.length - 1) {
|
|
630
|
+
const extracted = extractTrailingSep(index, partStart, partEnd);
|
|
310
631
|
if (extracted) {
|
|
311
|
-
|
|
632
|
+
textEnd = extracted.textEnd;
|
|
312
633
|
trailingSep = extracted.sep;
|
|
313
634
|
}
|
|
314
635
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
636
|
+
let a = partStart;
|
|
637
|
+
let b = textEnd;
|
|
638
|
+
while (a < b && isPhpTrimChar(text.charCodeAt(a))) a += 1;
|
|
639
|
+
while (b > a && isPhpTrimChar(text.charCodeAt(b - 1))) b -= 1;
|
|
640
|
+
if (b > a) {
|
|
641
|
+
spans.push([a, b]);
|
|
318
642
|
separators.push(pendingSep);
|
|
319
643
|
}
|
|
320
644
|
pendingSep = trailingSep;
|
|
321
645
|
});
|
|
322
|
-
return {
|
|
323
|
-
}
|
|
324
|
-
function extractTrailingSep(
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
openPos = i;
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
if (ch === ">") return null;
|
|
336
|
-
}
|
|
337
|
-
if (openPos === -1) return null;
|
|
338
|
-
const inner = trimmed.slice(openPos + 1, len - 1);
|
|
646
|
+
return { spans, separators };
|
|
647
|
+
}
|
|
648
|
+
function extractTrailingSep(index, start, end) {
|
|
649
|
+
const { text } = index;
|
|
650
|
+
let len = end;
|
|
651
|
+
while (len > start && isPhpTrimChar(text.charCodeAt(len - 1))) len -= 1;
|
|
652
|
+
if (len === start || text.charCodeAt(len - 1) !== GT2) return null;
|
|
653
|
+
const openPos = index.lastAngleWithin(start, len - 2);
|
|
654
|
+
if (openPos === -1 || text.charCodeAt(openPos) === GT2) return null;
|
|
655
|
+
const inner = text.slice(openPos + 1, len - 1);
|
|
339
656
|
const innerTrimmed = phpTrim(inner);
|
|
340
657
|
if (innerTrimmed.startsWith("/") || innerTrimmed.endsWith("/") || PER_ELEM_HTML_RE.test(innerTrimmed)) {
|
|
341
658
|
return null;
|
|
342
659
|
}
|
|
343
|
-
return {
|
|
660
|
+
return { textEnd: openPos, sep: inner };
|
|
344
661
|
}
|
|
345
662
|
function intGroup(m) {
|
|
346
663
|
return m && m[1] !== void 0 ? Number.parseInt(m[1], 10) : null;
|
|
@@ -348,52 +665,21 @@ function intGroup(m) {
|
|
|
348
665
|
function strGroup(m) {
|
|
349
666
|
return m && m[1] !== void 0 ? m[1] : null;
|
|
350
667
|
}
|
|
351
|
-
function
|
|
352
|
-
return
|
|
668
|
+
function isPhpTrimChar(code) {
|
|
669
|
+
return code === 32 || code === 9 || code === 10 || code === 13 || code === 0 || code === 11;
|
|
353
670
|
}
|
|
354
|
-
var PHP_LTRIM_RE = /^[ \t\n\r\0\x0B]+/u;
|
|
355
|
-
var PHP_RTRIM_RE = /[ \t\n\r\0\x0B]+$/u;
|
|
356
671
|
function phpTrim(s) {
|
|
357
|
-
return
|
|
672
|
+
return phpRtrim(phpLtrim(s));
|
|
358
673
|
}
|
|
359
674
|
function phpLtrim(s) {
|
|
360
|
-
|
|
675
|
+
let start = 0;
|
|
676
|
+
while (start < s.length && isPhpTrimChar(s.charCodeAt(start))) start += 1;
|
|
677
|
+
return start === 0 ? s : s.slice(start);
|
|
361
678
|
}
|
|
362
679
|
function phpRtrim(s) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
let depth = 0;
|
|
367
|
-
for (let i = openPos; i < text.length; i += 1) {
|
|
368
|
-
const ch = text.charAt(i);
|
|
369
|
-
if (ch === open) {
|
|
370
|
-
depth += 1;
|
|
371
|
-
} else if (ch === close) {
|
|
372
|
-
depth -= 1;
|
|
373
|
-
if (depth === 0) return i;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return -1;
|
|
377
|
-
}
|
|
378
|
-
function splitTopLevel(inner) {
|
|
379
|
-
const parts = [];
|
|
380
|
-
let brace = 0;
|
|
381
|
-
let bracket = 0;
|
|
382
|
-
let cur = "";
|
|
383
|
-
for (const ch of inner) {
|
|
384
|
-
if (ch === "{") brace += 1;
|
|
385
|
-
else if (ch === "}") brace -= 1;
|
|
386
|
-
else if (ch === "[") bracket += 1;
|
|
387
|
-
else if (ch === "]") bracket -= 1;
|
|
388
|
-
if (ch === "|" && brace === 0 && bracket === 0) {
|
|
389
|
-
parts.push(cur);
|
|
390
|
-
cur = "";
|
|
391
|
-
} else {
|
|
392
|
-
cur += ch;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
parts.push(cur);
|
|
396
|
-
return parts;
|
|
680
|
+
let end = s.length;
|
|
681
|
+
while (end > 0 && isPhpTrimChar(s.charCodeAt(end - 1))) end -= 1;
|
|
682
|
+
return end === s.length ? s : s.slice(0, end);
|
|
397
683
|
}
|
|
398
684
|
function firstTopLevelPipe(body, from = 0, to = body.length) {
|
|
399
685
|
let depth = 0;
|
|
@@ -409,27 +695,35 @@ function firstTopLevelPipe(body, from = 0, to = body.length) {
|
|
|
409
695
|
}
|
|
410
696
|
return -1;
|
|
411
697
|
}
|
|
698
|
+
function firstTopLevelPipeByPairs(body, from, to, anyPairs) {
|
|
699
|
+
let j = from;
|
|
700
|
+
while (j < to) {
|
|
701
|
+
const code = body.charCodeAt(j);
|
|
702
|
+
if (code === BRACE_OPEN || code === BRACKET_OPEN) {
|
|
703
|
+
const close = anyPairs[j];
|
|
704
|
+
if (close === -1 || close >= to) return -1;
|
|
705
|
+
j = close + 1;
|
|
706
|
+
} else if (code === PIPE2) {
|
|
707
|
+
return j;
|
|
708
|
+
} else {
|
|
709
|
+
j += 1;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return -1;
|
|
713
|
+
}
|
|
412
714
|
|
|
413
715
|
// src/internal/plurals.ts
|
|
414
716
|
var PLURAL_PREFIX2 = "{plural ";
|
|
415
717
|
function findPluralBlocks(text) {
|
|
416
718
|
const blocks = [];
|
|
719
|
+
let close = null;
|
|
417
720
|
let i = 0;
|
|
418
721
|
while (i < text.length) {
|
|
419
722
|
const start = text.indexOf(PLURAL_PREFIX2, i);
|
|
420
723
|
if (start === -1) break;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const ch = text.charAt(j);
|
|
425
|
-
if (ch === "{") depth += 1;
|
|
426
|
-
else if (ch === "}") {
|
|
427
|
-
depth -= 1;
|
|
428
|
-
if (depth === 0) break;
|
|
429
|
-
}
|
|
430
|
-
j += 1;
|
|
431
|
-
}
|
|
432
|
-
if (depth !== 0) {
|
|
724
|
+
close ??= matchPairs(text, BRACE_OPEN, BRACE_CLOSE);
|
|
725
|
+
const j = close[start];
|
|
726
|
+
if (j === -1) {
|
|
433
727
|
i = start + PLURAL_PREFIX2.length;
|
|
434
728
|
continue;
|
|
435
729
|
}
|
|
@@ -592,12 +886,19 @@ function checkDirectives(text, out) {
|
|
|
592
886
|
}
|
|
593
887
|
}
|
|
594
888
|
}
|
|
889
|
+
var CONFIG_KEY_PRESENT_RE = /(?<!\w)\w+[ \t\n\x0B\f\r]*=/;
|
|
890
|
+
var CONFIG_KEYS_RE = /(?<!\w)(\w+)[ \t\n\x0B\f\r]*=/gu;
|
|
595
891
|
function checkPermutationConfigs(text, idx, out) {
|
|
596
|
-
for (
|
|
597
|
-
const
|
|
598
|
-
if (
|
|
599
|
-
const configBase =
|
|
600
|
-
|
|
892
|
+
for (let from = 0; ; ) {
|
|
893
|
+
const open = text.indexOf("[<", from);
|
|
894
|
+
if (open === -1) break;
|
|
895
|
+
const configBase = open + 2;
|
|
896
|
+
const gt = text.indexOf(">", configBase);
|
|
897
|
+
if (gt === -1) break;
|
|
898
|
+
from = gt + 1;
|
|
899
|
+
const configStr = text.slice(configBase, gt);
|
|
900
|
+
if (!CONFIG_KEY_PRESENT_RE.test(configStr)) continue;
|
|
901
|
+
for (const km of configStr.matchAll(CONFIG_KEYS_RE)) {
|
|
601
902
|
const key = (km[1] ?? "").toLowerCase();
|
|
602
903
|
if (!KNOWN_CONFIG_KEYS.has(key)) {
|
|
603
904
|
out.push(err(
|
|
@@ -607,7 +908,7 @@ function checkPermutationConfigs(text, idx, out) {
|
|
|
607
908
|
));
|
|
608
909
|
}
|
|
609
910
|
}
|
|
610
|
-
const min = /minsize\
|
|
911
|
+
const min = /minsize[ \t\n\x0B\f\r]*=[ \t\n\x0B\f\r]*([^;> \t\n\x0B\f\r]+)/i.exec(configStr);
|
|
611
912
|
if (min && !/^\d+$/.test(min[1] ?? "")) {
|
|
612
913
|
out.push(err(
|
|
613
914
|
"permutation.minsize-not-integer",
|
|
@@ -615,7 +916,7 @@ function checkPermutationConfigs(text, idx, out) {
|
|
|
615
916
|
{ ...span(idx, configBase + min.index, min[0].length), data: { value: min[1] } }
|
|
616
917
|
));
|
|
617
918
|
}
|
|
618
|
-
const max = /maxsize\
|
|
919
|
+
const max = /maxsize[ \t\n\x0B\f\r]*=[ \t\n\x0B\f\r]*([^;> \t\n\x0B\f\r]+)/i.exec(configStr);
|
|
619
920
|
if (max && !/^\d+$/.test(max[1] ?? "")) {
|
|
620
921
|
out.push(err(
|
|
621
922
|
"permutation.maxsize-not-integer",
|
|
@@ -799,11 +1100,12 @@ function checkVariableReferences(text, idx, known, out) {
|
|
|
799
1100
|
refsOf.set(name, refs);
|
|
800
1101
|
}
|
|
801
1102
|
const { reaches: reachesCycle, via } = namesThatReachACycle(defs, refsOf);
|
|
1103
|
+
const lengths = routeLengths(via);
|
|
802
1104
|
for (const name of defs.keys()) {
|
|
803
1105
|
if (!reachesCycle.has(name)) continue;
|
|
804
1106
|
out.push(err(
|
|
805
1107
|
"variable.circular-reference",
|
|
806
|
-
`Circular variable reference: ${cyclePath(name, via)}.`,
|
|
1108
|
+
`Circular variable reference: ${cyclePath(name, via, lengths)}.`,
|
|
807
1109
|
{ ...defPos.get(name) ?? somewhere, data: { name } }
|
|
808
1110
|
));
|
|
809
1111
|
}
|
|
@@ -863,7 +1165,7 @@ function namesThatReachACycle(defs, refsOf) {
|
|
|
863
1165
|
return { reaches, via };
|
|
864
1166
|
}
|
|
865
1167
|
var CYCLE_PATH_LIMIT = 8;
|
|
866
|
-
function cyclePath(name, via) {
|
|
1168
|
+
function cyclePath(name, via, lengths) {
|
|
867
1169
|
const seen = /* @__PURE__ */ new Set([name]);
|
|
868
1170
|
const shown = [name];
|
|
869
1171
|
let current = name;
|
|
@@ -875,16 +1177,7 @@ function cyclePath(name, via) {
|
|
|
875
1177
|
return shown.join(" \u2192 ");
|
|
876
1178
|
}
|
|
877
1179
|
if (shown.length >= CYCLE_PATH_LIMIT) {
|
|
878
|
-
|
|
879
|
-
let walk2 = next;
|
|
880
|
-
while (!seen.has(walk2)) {
|
|
881
|
-
seen.add(walk2);
|
|
882
|
-
more += 1;
|
|
883
|
-
const step = via.get(walk2);
|
|
884
|
-
if (step === void 0) break;
|
|
885
|
-
walk2 = step;
|
|
886
|
-
}
|
|
887
|
-
return `${shown.join(" \u2192 ")} \u2192 \u2026 (${more} more)`;
|
|
1180
|
+
return `${shown.join(" \u2192 ")} \u2192 \u2026 (${lengths.get(name) - shown.length} more)`;
|
|
888
1181
|
}
|
|
889
1182
|
seen.add(next);
|
|
890
1183
|
shown.push(next);
|
|
@@ -892,6 +1185,33 @@ function cyclePath(name, via) {
|
|
|
892
1185
|
}
|
|
893
1186
|
return shown.join(" \u2192 ");
|
|
894
1187
|
}
|
|
1188
|
+
function routeLengths(via) {
|
|
1189
|
+
const lengths = /* @__PURE__ */ new Map();
|
|
1190
|
+
for (const start of via.keys()) {
|
|
1191
|
+
if (lengths.has(start)) continue;
|
|
1192
|
+
const walk2 = [];
|
|
1193
|
+
const onWalk = /* @__PURE__ */ new Map();
|
|
1194
|
+
let node = start;
|
|
1195
|
+
while (node !== void 0 && !lengths.has(node) && !onWalk.has(node)) {
|
|
1196
|
+
onWalk.set(node, walk2.length);
|
|
1197
|
+
walk2.push(node);
|
|
1198
|
+
node = via.get(node);
|
|
1199
|
+
}
|
|
1200
|
+
let tailEnd = walk2.length;
|
|
1201
|
+
let length = node === void 0 ? 0 : lengths.get(node) ?? 0;
|
|
1202
|
+
if (node !== void 0 && onWalk.has(node)) {
|
|
1203
|
+
const from = onWalk.get(node);
|
|
1204
|
+
length = walk2.length - from;
|
|
1205
|
+
for (let k = from; k < walk2.length; k += 1) lengths.set(walk2[k], length);
|
|
1206
|
+
tailEnd = from;
|
|
1207
|
+
}
|
|
1208
|
+
for (let k = tailEnd - 1; k >= 0; k -= 1) {
|
|
1209
|
+
length += 1;
|
|
1210
|
+
lengths.set(walk2[k], length);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
return lengths;
|
|
1214
|
+
}
|
|
895
1215
|
function checkIncludeTargets(text, idx, known, out) {
|
|
896
1216
|
const set = new Set(known);
|
|
897
1217
|
INCLUDE_RE.lastIndex = 0;
|
|
@@ -938,6 +1258,13 @@ function addAll(target, text, re, fold) {
|
|
|
938
1258
|
}
|
|
939
1259
|
}
|
|
940
1260
|
|
|
1261
|
+
// src/internal/charclass.ts
|
|
1262
|
+
var UCP_SPACE = "\\t\\n\\x0B\\f\\r \\x85\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000";
|
|
1263
|
+
function isUcpSpace(code) {
|
|
1264
|
+
return code >= 9 && code <= 13 || code === 32 || code === 133 || code === 160 || code === 5760 || code === 6158 || code >= 8192 && code <= 8202 || code === 8232 || code === 8233 || code === 8239 || code === 8287 || code === 12288;
|
|
1265
|
+
}
|
|
1266
|
+
var UCP_WORD = "\\p{L}\\p{N}\\p{Mn}\\p{Pc}";
|
|
1267
|
+
|
|
941
1268
|
// src/internal/errors.ts
|
|
942
1269
|
var SpintaxError = class extends Error {
|
|
943
1270
|
constructor(message) {
|
|
@@ -967,6 +1294,120 @@ var NotImplementedError = class extends SpintaxError {
|
|
|
967
1294
|
}
|
|
968
1295
|
};
|
|
969
1296
|
|
|
1297
|
+
// src/internal/fragment.ts
|
|
1298
|
+
var FLAT_BELOW = 128;
|
|
1299
|
+
var Join = class {
|
|
1300
|
+
constructor(pieces) {
|
|
1301
|
+
this.pieces = pieces;
|
|
1302
|
+
let length = 0;
|
|
1303
|
+
for (const piece of pieces) length += lengthOf(piece);
|
|
1304
|
+
this.length = length;
|
|
1305
|
+
let lead = 0;
|
|
1306
|
+
for (const piece of pieces) {
|
|
1307
|
+
const pieceLead = leadOf(piece);
|
|
1308
|
+
lead += pieceLead;
|
|
1309
|
+
if (pieceLead < lengthOf(piece)) break;
|
|
1310
|
+
}
|
|
1311
|
+
this.lead = lead;
|
|
1312
|
+
let trail = 0;
|
|
1313
|
+
if (lead < length) {
|
|
1314
|
+
for (let k = pieces.length - 1; k >= 0; k -= 1) {
|
|
1315
|
+
const piece = pieces[k];
|
|
1316
|
+
const pieceTrail = trailOf(piece);
|
|
1317
|
+
trail += pieceTrail;
|
|
1318
|
+
if (pieceTrail < lengthOf(piece)) break;
|
|
1319
|
+
}
|
|
1320
|
+
} else {
|
|
1321
|
+
trail = length;
|
|
1322
|
+
}
|
|
1323
|
+
this.trail = trail;
|
|
1324
|
+
}
|
|
1325
|
+
pieces;
|
|
1326
|
+
length;
|
|
1327
|
+
lead;
|
|
1328
|
+
trail;
|
|
1329
|
+
};
|
|
1330
|
+
var Window = class {
|
|
1331
|
+
constructor(base, from, to) {
|
|
1332
|
+
this.base = base;
|
|
1333
|
+
this.from = from;
|
|
1334
|
+
this.to = to;
|
|
1335
|
+
this.length = to - from;
|
|
1336
|
+
}
|
|
1337
|
+
base;
|
|
1338
|
+
from;
|
|
1339
|
+
to;
|
|
1340
|
+
length;
|
|
1341
|
+
lead = 0;
|
|
1342
|
+
trail = 0;
|
|
1343
|
+
};
|
|
1344
|
+
function lengthOf(fragment) {
|
|
1345
|
+
return fragment.length;
|
|
1346
|
+
}
|
|
1347
|
+
function leadOf(fragment) {
|
|
1348
|
+
if (typeof fragment !== "string") return fragment.lead;
|
|
1349
|
+
let k = 0;
|
|
1350
|
+
while (k < fragment.length && isPhpTrimChar(fragment.charCodeAt(k))) k += 1;
|
|
1351
|
+
return k;
|
|
1352
|
+
}
|
|
1353
|
+
function trailOf(fragment) {
|
|
1354
|
+
if (typeof fragment !== "string") return fragment.trail;
|
|
1355
|
+
let k = fragment.length;
|
|
1356
|
+
while (k > 0 && isPhpTrimChar(fragment.charCodeAt(k - 1))) k -= 1;
|
|
1357
|
+
return fragment.length - k;
|
|
1358
|
+
}
|
|
1359
|
+
function joinFragments(pieces) {
|
|
1360
|
+
if (pieces.length === 0) return "";
|
|
1361
|
+
if (pieces.length === 1) return pieces[0];
|
|
1362
|
+
let total = 0;
|
|
1363
|
+
let flat = true;
|
|
1364
|
+
for (const piece of pieces) {
|
|
1365
|
+
total += lengthOf(piece);
|
|
1366
|
+
if (typeof piece !== "string") flat = false;
|
|
1367
|
+
}
|
|
1368
|
+
if (flat && total < FLAT_BELOW) return pieces.join("");
|
|
1369
|
+
return new Join(pieces);
|
|
1370
|
+
}
|
|
1371
|
+
function trimFragment(fragment) {
|
|
1372
|
+
if (typeof fragment === "string") {
|
|
1373
|
+
let start = 0;
|
|
1374
|
+
while (start < fragment.length && isPhpTrimChar(fragment.charCodeAt(start))) start += 1;
|
|
1375
|
+
let end = fragment.length;
|
|
1376
|
+
while (end > start && isPhpTrimChar(fragment.charCodeAt(end - 1))) end -= 1;
|
|
1377
|
+
return start === 0 && end === fragment.length ? fragment : fragment.slice(start, end);
|
|
1378
|
+
}
|
|
1379
|
+
if (fragment.lead === fragment.length) return "";
|
|
1380
|
+
if (fragment.lead === 0 && fragment.trail === 0) return fragment;
|
|
1381
|
+
return new Window(fragment, fragment.lead, fragment.length - fragment.trail);
|
|
1382
|
+
}
|
|
1383
|
+
function flatten(fragment) {
|
|
1384
|
+
if (typeof fragment === "string") return fragment;
|
|
1385
|
+
const out = [];
|
|
1386
|
+
const stack = [[fragment, 0, fragment.length]];
|
|
1387
|
+
while (stack.length > 0) {
|
|
1388
|
+
const [piece, from, to] = stack.pop();
|
|
1389
|
+
if (from >= to) continue;
|
|
1390
|
+
if (typeof piece === "string") {
|
|
1391
|
+
out.push(from === 0 && to === piece.length ? piece : piece.slice(from, to));
|
|
1392
|
+
} else if (piece instanceof Window) {
|
|
1393
|
+
stack.push([piece.base, piece.from + from, piece.from + to]);
|
|
1394
|
+
} else {
|
|
1395
|
+
const wanted = [];
|
|
1396
|
+
let offset = 0;
|
|
1397
|
+
for (const child of piece.pieces) {
|
|
1398
|
+
const length = lengthOf(child);
|
|
1399
|
+
const a = Math.max(from, offset);
|
|
1400
|
+
const b = Math.min(to, offset + length);
|
|
1401
|
+
if (a < b) wanted.push([child, a - offset, b - offset]);
|
|
1402
|
+
offset += length;
|
|
1403
|
+
if (offset >= to) break;
|
|
1404
|
+
}
|
|
1405
|
+
for (let k = wanted.length - 1; k >= 0; k -= 1) stack.push(wanted[k]);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return out.join("");
|
|
1409
|
+
}
|
|
1410
|
+
|
|
970
1411
|
// src/internal/render.ts
|
|
971
1412
|
var MAX_VARIABLE_DEPTH = 50;
|
|
972
1413
|
var MAX_EXPANSION_CHARS = 1024 * 1024;
|
|
@@ -980,7 +1421,7 @@ function renderAst(ast, ctx) {
|
|
|
980
1421
|
onPluralError: ctx.onPluralError,
|
|
981
1422
|
budget: ctx.budget
|
|
982
1423
|
};
|
|
983
|
-
const vars = Object.keys(ast.defDefs).length > 0 ?
|
|
1424
|
+
const vars = Object.keys(ast.defDefs).length > 0 ? nameMap(base, rollDefinitions(ast.defDefs, base, ctx.runtimeContext, walkOpts)) : base;
|
|
984
1425
|
const text = renderNodes(ast.nodes, { ...walkOpts, vars });
|
|
985
1426
|
return ctx.resolver ? resolveIncludes(text, ctx) : text;
|
|
986
1427
|
}
|
|
@@ -1004,7 +1445,7 @@ function resolveIncludes(text, ctx) {
|
|
|
1004
1445
|
});
|
|
1005
1446
|
}
|
|
1006
1447
|
function buildVars(setDefs, context) {
|
|
1007
|
-
const vars =
|
|
1448
|
+
const vars = nameMap();
|
|
1008
1449
|
for (const [name, value] of Object.entries(setDefs)) {
|
|
1009
1450
|
vars[name] = value;
|
|
1010
1451
|
}
|
|
@@ -1015,48 +1456,68 @@ function buildVars(setDefs, context) {
|
|
|
1015
1456
|
}
|
|
1016
1457
|
function rollDefinitions(defDefs, vars, context, opts) {
|
|
1017
1458
|
const outranked = new Set(Object.keys(context).map((key) => key.toLowerCase()));
|
|
1018
|
-
const rolled =
|
|
1019
|
-
const
|
|
1459
|
+
const rolled = nameMap();
|
|
1460
|
+
const definitions = nameMap(defDefs);
|
|
1461
|
+
const aliases = nameMap();
|
|
1020
1462
|
for (const [name, value] of Object.entries(vars)) {
|
|
1021
|
-
if (name in
|
|
1463
|
+
if (name in definitions && !outranked.has(name)) continue;
|
|
1022
1464
|
aliases[name] = value;
|
|
1023
1465
|
}
|
|
1024
|
-
|
|
1466
|
+
const visible = nameMap(vars);
|
|
1467
|
+
for (const name of orderDefinitions(definitions, aliases)) {
|
|
1025
1468
|
if (outranked.has(name)) continue;
|
|
1026
|
-
const value =
|
|
1027
|
-
rolled[name] =
|
|
1469
|
+
const value = renderNodes(parseSequence(definitions[name] ?? ""), { ...opts, vars: visible });
|
|
1470
|
+
rolled[name] = value;
|
|
1471
|
+
visible[name] = value;
|
|
1472
|
+
truthinessByVars.delete(visible);
|
|
1028
1473
|
}
|
|
1029
1474
|
return rolled;
|
|
1030
1475
|
}
|
|
1031
1476
|
function orderDefinitions(defDefs, aliases) {
|
|
1032
1477
|
const names = Object.keys(defDefs);
|
|
1033
|
-
const
|
|
1478
|
+
const position = new Map(names.map((name, i) => [name, i]));
|
|
1479
|
+
const unplaced = /* @__PURE__ */ new Map();
|
|
1480
|
+
const waitingOn = /* @__PURE__ */ new Map();
|
|
1034
1481
|
for (const name of names) {
|
|
1035
|
-
|
|
1036
|
-
|
|
1482
|
+
let count = 0;
|
|
1483
|
+
for (const dep of referencedNames(defDefs[name] ?? "", aliases)) {
|
|
1484
|
+
if (dep === name || !position.has(dep)) continue;
|
|
1485
|
+
count += 1;
|
|
1486
|
+
const waiting = waitingOn.get(dep);
|
|
1487
|
+
if (waiting === void 0) waitingOn.set(dep, [name]);
|
|
1488
|
+
else waiting.push(name);
|
|
1489
|
+
}
|
|
1490
|
+
unplaced.set(name, count);
|
|
1037
1491
|
}
|
|
1038
1492
|
const ordered = [];
|
|
1039
|
-
let
|
|
1040
|
-
while (
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1493
|
+
let round = names.filter((name) => unplaced.get(name) === 0);
|
|
1494
|
+
while (round.length > 0) {
|
|
1495
|
+
const next = [];
|
|
1496
|
+
for (const name of round) {
|
|
1497
|
+
ordered.push(name);
|
|
1498
|
+
for (const waiting of waitingOn.get(name) ?? []) {
|
|
1499
|
+
const left = unplaced.get(waiting) - 1;
|
|
1500
|
+
unplaced.set(waiting, left);
|
|
1501
|
+
if (left === 0) next.push(waiting);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
round = next.sort((a, b) => position.get(a) - position.get(b));
|
|
1505
|
+
}
|
|
1506
|
+
if (ordered.length < names.length) {
|
|
1507
|
+
const placed = new Set(ordered);
|
|
1508
|
+
for (const name of names) if (!placed.has(name)) ordered.push(name);
|
|
1048
1509
|
}
|
|
1049
1510
|
return ordered;
|
|
1050
1511
|
}
|
|
1051
1512
|
function referencedNames(value, aliases) {
|
|
1052
1513
|
const seen = /* @__PURE__ */ new Set();
|
|
1053
1514
|
const queue = directReferences(value);
|
|
1054
|
-
|
|
1055
|
-
const name = queue
|
|
1515
|
+
for (let i = 0; i < queue.length; i += 1) {
|
|
1516
|
+
const name = queue[i];
|
|
1056
1517
|
if (seen.has(name)) continue;
|
|
1057
1518
|
seen.add(name);
|
|
1058
1519
|
const alias = aliases[name];
|
|
1059
|
-
if (alias !== void 0)
|
|
1520
|
+
if (alias !== void 0) for (const ref of directReferences(alias)) queue.push(ref);
|
|
1060
1521
|
}
|
|
1061
1522
|
return seen;
|
|
1062
1523
|
}
|
|
@@ -1064,6 +1525,9 @@ function directReferences(text) {
|
|
|
1064
1525
|
return [...text.matchAll(/%(\w+)%/gu)].map((match) => (match[1] ?? "").toLowerCase());
|
|
1065
1526
|
}
|
|
1066
1527
|
function renderNodes(nodes, opts) {
|
|
1528
|
+
return flatten(renderFragment(nodes, opts));
|
|
1529
|
+
}
|
|
1530
|
+
function renderFragment(nodes, opts) {
|
|
1067
1531
|
const frame = (list) => ({ nodes: list, i: 0, out: [], pending: null });
|
|
1068
1532
|
const stack = [frame(nodes)];
|
|
1069
1533
|
while (stack.length > 0) {
|
|
@@ -1078,7 +1542,7 @@ function renderNodes(nodes, opts) {
|
|
|
1078
1542
|
continue;
|
|
1079
1543
|
}
|
|
1080
1544
|
if (f.i >= f.nodes.length) {
|
|
1081
|
-
const text = f.out
|
|
1545
|
+
const text = joinFragments(f.out);
|
|
1082
1546
|
stack.pop();
|
|
1083
1547
|
const parent = stack[stack.length - 1];
|
|
1084
1548
|
if (parent === void 0) return text;
|
|
@@ -1088,11 +1552,12 @@ function renderNodes(nodes, opts) {
|
|
|
1088
1552
|
const node = f.nodes[f.i];
|
|
1089
1553
|
f.i += 1;
|
|
1090
1554
|
const step = renderNode(node, opts);
|
|
1091
|
-
if (typeof step === "string") f.out.push(step);
|
|
1555
|
+
if (typeof step === "string" || !("lists" in step)) f.out.push(step);
|
|
1092
1556
|
else f.pending = step;
|
|
1093
1557
|
}
|
|
1094
1558
|
return "";
|
|
1095
1559
|
}
|
|
1560
|
+
var single = (list) => ({ lists: [list], done: [], assemble: (parts) => parts[0] ?? "" });
|
|
1096
1561
|
function renderNode(node, opts) {
|
|
1097
1562
|
switch (node.type) {
|
|
1098
1563
|
case "literal":
|
|
@@ -1118,7 +1583,7 @@ function resolveVariable(name, opts) {
|
|
|
1118
1583
|
if (opts.budget.left <= 0) return `%${name}%`;
|
|
1119
1584
|
opts.budget.left -= value.length;
|
|
1120
1585
|
if (opts.depth >= MAX_VARIABLE_DEPTH || !/[{[%]/u.test(value)) return value;
|
|
1121
|
-
return
|
|
1586
|
+
return renderFragment(parseSequence(value), { ...opts, depth: opts.depth + 1 });
|
|
1122
1587
|
}
|
|
1123
1588
|
function passesLeft(opts) {
|
|
1124
1589
|
return Math.max(1, MAX_VARIABLE_DEPTH - opts.depth + 1);
|
|
@@ -1142,18 +1607,29 @@ function expandVarsFixpoint(text, opts, passes) {
|
|
|
1142
1607
|
}
|
|
1143
1608
|
return { text: out, changed: changedAny, converged: false };
|
|
1144
1609
|
}
|
|
1610
|
+
var NON_SPACE_RE = new RegExp(`[^${UCP_SPACE}]`, "u");
|
|
1611
|
+
var truthinessByVars = /* @__PURE__ */ new WeakMap();
|
|
1145
1612
|
function conditionalTakesThen(name, inverted, opts) {
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1613
|
+
const key = name.toLowerCase();
|
|
1614
|
+
let known = truthinessByVars.get(opts.vars);
|
|
1615
|
+
if (known === void 0) truthinessByVars.set(opts.vars, known = /* @__PURE__ */ new Map());
|
|
1616
|
+
let baseTruthy = known.get(key);
|
|
1617
|
+
if (baseTruthy === void 0) {
|
|
1618
|
+
const value = opts.vars[key];
|
|
1619
|
+
baseTruthy = value !== void 0 && NON_SPACE_RE.test(value);
|
|
1620
|
+
known.set(key, baseTruthy);
|
|
1621
|
+
}
|
|
1148
1622
|
return inverted ? !baseTruthy : baseTruthy;
|
|
1149
1623
|
}
|
|
1150
1624
|
function renderConditional(node, opts) {
|
|
1151
|
-
|
|
1152
|
-
return { lists: [truthy ? node.then : node.else], done: [], assemble: (parts) => parts[0] ?? "" };
|
|
1625
|
+
return single(conditionalTakesThen(node.name, node.inverted, opts) ? node.then : node.else);
|
|
1153
1626
|
}
|
|
1154
1627
|
function resolveConditionalsInText(text, opts) {
|
|
1155
1628
|
if (!text.includes("{?")) return text;
|
|
1156
|
-
const close =
|
|
1629
|
+
const close = matchPairs(text, BRACE_OPEN, BRACE_CLOSE);
|
|
1630
|
+
const any = matchAnyPairs(text);
|
|
1631
|
+
const heads = [];
|
|
1632
|
+
for (let at = text.indexOf("{?"); at !== -1; at = text.indexOf("{?", at + 1)) heads.push(at);
|
|
1157
1633
|
const out = [];
|
|
1158
1634
|
const pending = [[0, text.length]];
|
|
1159
1635
|
while (pending.length > 0) {
|
|
@@ -1161,13 +1637,14 @@ function resolveConditionalsInText(text, opts) {
|
|
|
1161
1637
|
const segEnd = segment[1];
|
|
1162
1638
|
let i = segment[0];
|
|
1163
1639
|
while (i < segEnd) {
|
|
1164
|
-
const
|
|
1640
|
+
const next = lowerBound(heads, i);
|
|
1641
|
+
const open = next < heads.length ? heads[next] : -1;
|
|
1165
1642
|
if (open === -1 || open + 1 >= segEnd) {
|
|
1166
1643
|
out.push(text.slice(i, segEnd));
|
|
1167
1644
|
break;
|
|
1168
1645
|
}
|
|
1169
1646
|
const shut = close[open] ?? -1;
|
|
1170
|
-
const head = shut === -1 || shut >= segEnd ? null : recognizeConditional(text, open + 1, shut);
|
|
1647
|
+
const head = shut === -1 || shut >= segEnd ? null : recognizeConditional(text, open + 1, shut, any);
|
|
1171
1648
|
if (head === null) {
|
|
1172
1649
|
out.push(text.slice(i, Math.min(open + 2, segEnd)));
|
|
1173
1650
|
i = open + 2;
|
|
@@ -1183,20 +1660,6 @@ function resolveConditionalsInText(text, opts) {
|
|
|
1183
1660
|
}
|
|
1184
1661
|
return out.join("");
|
|
1185
1662
|
}
|
|
1186
|
-
function matchBraces(text) {
|
|
1187
|
-
const close = new Int32Array(text.length).fill(-1);
|
|
1188
|
-
const opens = [];
|
|
1189
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
1190
|
-
const ch = text.charAt(i);
|
|
1191
|
-
if (ch === "{") {
|
|
1192
|
-
opens.push(i);
|
|
1193
|
-
} else if (ch === "}") {
|
|
1194
|
-
const open = opens.pop();
|
|
1195
|
-
if (open !== void 0) close[open] = i;
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
return close;
|
|
1199
|
-
}
|
|
1200
1663
|
function renderPlural(node, opts) {
|
|
1201
1664
|
const passes = passesLeft(opts);
|
|
1202
1665
|
const countPass = expandVarsFixpoint(node.countRaw, opts, passes);
|
|
@@ -1214,7 +1677,7 @@ function renderPlural(node, opts) {
|
|
|
1214
1677
|
});
|
|
1215
1678
|
return fullwidthVerbatim(countRaw, formsRaw);
|
|
1216
1679
|
}
|
|
1217
|
-
const count =
|
|
1680
|
+
const count = phpTrim(countRaw);
|
|
1218
1681
|
if (!/^-?\d+$/u.test(count)) {
|
|
1219
1682
|
report({
|
|
1220
1683
|
code: "plural.count",
|
|
@@ -1224,7 +1687,7 @@ function renderPlural(node, opts) {
|
|
|
1224
1687
|
});
|
|
1225
1688
|
return "";
|
|
1226
1689
|
}
|
|
1227
|
-
const forms = formsRaw.split("|").map((f) =>
|
|
1690
|
+
const forms = formsRaw.split("|").map((f) => phpTrim(f));
|
|
1228
1691
|
if (forms.length !== pluralArity(base)) {
|
|
1229
1692
|
report({
|
|
1230
1693
|
code: "plural.arity",
|
|
@@ -1237,8 +1700,8 @@ function renderPlural(node, opts) {
|
|
|
1237
1700
|
return fullwidthVerbatim(countRaw, formsRaw);
|
|
1238
1701
|
}
|
|
1239
1702
|
const picked = pluralFor(base, Number.parseInt(count, 10), forms);
|
|
1240
|
-
if (!formsPass.converged) return
|
|
1241
|
-
return
|
|
1703
|
+
if (!formsPass.converged) return renderFragment(parseSequence(picked), { ...opts, frozen: true });
|
|
1704
|
+
return single(parseSequence(picked));
|
|
1242
1705
|
}
|
|
1243
1706
|
function rawConstruct(countRaw, formsRaw) {
|
|
1244
1707
|
return `{plural ${countRaw}:${formsRaw}}`;
|
|
@@ -1247,25 +1710,31 @@ function fullwidthVerbatim(countRaw, formsRaw) {
|
|
|
1247
1710
|
return rawConstruct(countRaw, formsRaw).replace(/\{/gu, "\uFF5B").replace(/\}/gu, "\uFF5D");
|
|
1248
1711
|
}
|
|
1249
1712
|
function renderEnumeration(node, opts) {
|
|
1250
|
-
const spliced = node.raw === void 0 ? null : spliceConstruct(node.raw, "{", "}", opts);
|
|
1713
|
+
const spliced = node.raw === void 0 ? null : spliceConstruct(node, node.raw, "{", "}", opts);
|
|
1251
1714
|
if (spliced !== null) return spliced;
|
|
1252
1715
|
const { options } = node;
|
|
1253
1716
|
if (options.length === 0) return "";
|
|
1254
1717
|
const picked = options[randomInt(opts.rng, 0, options.length - 1)];
|
|
1255
1718
|
if (!picked) return "";
|
|
1256
|
-
return
|
|
1719
|
+
return single(picked);
|
|
1257
1720
|
}
|
|
1258
|
-
function spliceConstruct(raw, open, close, opts) {
|
|
1721
|
+
function spliceConstruct(node, raw, open, close, opts) {
|
|
1259
1722
|
if (opts.frozen === true) return null;
|
|
1723
|
+
const span2 = rereadSpans.get(node);
|
|
1724
|
+
if (span2 !== void 0 && !rereadCouldChange(span2, opts)) return null;
|
|
1260
1725
|
const expanded = expandVarsFixpoint(resolveConditionalsInText(raw, opts), opts, passesLeft(opts));
|
|
1261
1726
|
const body = resolveConditionalsInText(expanded.text, opts);
|
|
1262
1727
|
if (body === raw) return null;
|
|
1263
1728
|
const nodes = parseSequence(open + body + close);
|
|
1264
|
-
if (!expanded.converged) return
|
|
1265
|
-
return
|
|
1729
|
+
if (!expanded.converged) return renderFragment(nodes, { ...opts, frozen: true });
|
|
1730
|
+
return single(nodes);
|
|
1731
|
+
}
|
|
1732
|
+
function rereadCouldChange({ index, start, end }, opts) {
|
|
1733
|
+
if (index.hasConditionalWithin(start, end)) return true;
|
|
1734
|
+
return !(opts.budget.left <= 0) && index.hasDefinedReferenceWithin(start, end, opts.vars);
|
|
1266
1735
|
}
|
|
1267
1736
|
function renderPermutation(node, opts) {
|
|
1268
|
-
const spliced = node.raw === void 0 ? null : spliceConstruct(node.raw, "[", "]", opts);
|
|
1737
|
+
const spliced = node.raw === void 0 ? null : spliceConstruct(node, node.raw, "[", "]", opts);
|
|
1269
1738
|
if (spliced !== null) return spliced;
|
|
1270
1739
|
if (node.options.length === 0) return "";
|
|
1271
1740
|
return {
|
|
@@ -1275,7 +1744,11 @@ function renderPermutation(node, opts) {
|
|
|
1275
1744
|
};
|
|
1276
1745
|
}
|
|
1277
1746
|
function assemblePermutation(node, rendered, opts) {
|
|
1278
|
-
const elements =
|
|
1747
|
+
const elements = [];
|
|
1748
|
+
node.options.forEach((o, i) => {
|
|
1749
|
+
const text = trimFragment(rendered[i] ?? "");
|
|
1750
|
+
if (lengthOf(text) !== 0) elements.push({ text, sep: o.separator });
|
|
1751
|
+
});
|
|
1279
1752
|
const total = elements.length;
|
|
1280
1753
|
if (total === 0) return "";
|
|
1281
1754
|
const { config } = node;
|
|
@@ -1314,25 +1787,20 @@ function joinWithSeparators(elements, globalSep, globalLastsep) {
|
|
|
1314
1787
|
const count = elements.length;
|
|
1315
1788
|
if (count === 0) return "";
|
|
1316
1789
|
if (count === 1) return elements[0].text;
|
|
1317
|
-
|
|
1790
|
+
const pieces = [elements[0].text];
|
|
1318
1791
|
for (let i = 1; i < count; i += 1) {
|
|
1319
1792
|
const el = elements[i];
|
|
1320
1793
|
const sep = el.sep ?? (i === count - 1 ? globalLastsep : globalSep);
|
|
1321
|
-
|
|
1794
|
+
pieces.push(padSeparator(sep), el.text);
|
|
1322
1795
|
}
|
|
1323
|
-
return
|
|
1796
|
+
return joinFragments(pieces);
|
|
1324
1797
|
}
|
|
1325
1798
|
function padSeparator(sep) {
|
|
1326
|
-
const trimmed =
|
|
1799
|
+
const trimmed = phpTrim(sep);
|
|
1327
1800
|
if (trimmed === "") return sep;
|
|
1328
1801
|
if (/^\p{L}+$/u.test(trimmed)) return ` ${trimmed} `;
|
|
1329
1802
|
return sep;
|
|
1330
1803
|
}
|
|
1331
|
-
var PHP_LTRIM_RE2 = /^[ \t\n\r\0\x0B]+/u;
|
|
1332
|
-
var PHP_RTRIM_RE2 = /[ \t\n\r\0\x0B]+$/u;
|
|
1333
|
-
function phpTrim2(s) {
|
|
1334
|
-
return s.replace(PHP_LTRIM_RE2, "").replace(PHP_RTRIM_RE2, "");
|
|
1335
|
-
}
|
|
1336
1804
|
|
|
1337
1805
|
// src/internal/postprocess.ts
|
|
1338
1806
|
var SINGLE_ABBREVS = [
|
|
@@ -1385,41 +1853,182 @@ var SINGLE_ABBREVS = [
|
|
|
1385
1853
|
"Ave",
|
|
1386
1854
|
"Blvd"
|
|
1387
1855
|
];
|
|
1388
|
-
var WS =
|
|
1856
|
+
var WS = UCP_SPACE;
|
|
1389
1857
|
var S = `[${WS}]`;
|
|
1390
|
-
var
|
|
1858
|
+
var AFTER_NON_WORD = `(?<![${UCP_WORD}])`;
|
|
1859
|
+
var WORD_BOUNDARY = `(?:(?<=[${UCP_WORD}])(?![${UCP_WORD}])|(?<![${UCP_WORD}])(?=[${UCP_WORD}]))`;
|
|
1860
|
+
var XN = "[xX][nN]--";
|
|
1861
|
+
var LABEL = `(?:${XN})?[\\p{L}\\p{N}]+(?:-[\\p{L}\\p{N}]+)*`;
|
|
1862
|
+
var TLD_LOWER = "\\p{Ll}\\p{Lm}\\p{Lo}";
|
|
1863
|
+
var TLD_UPPER = "\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}";
|
|
1864
|
+
var TLD = `(?:${XN}[a-zA-Z0-9\\-\\u017F\\u212A]{2,59}|[${TLD_LOWER}][${TLD_LOWER}\\p{N}-]{1,62}|[${TLD_UPPER}][${TLD_UPPER}\\p{N}-]{1,62})`;
|
|
1865
|
+
var DOMAIN_PART = `(?:${LABEL}\\.)+${TLD}`;
|
|
1391
1866
|
var URI_BODY = `[^\\x00${WS}<>"')\\]]`;
|
|
1392
1867
|
var URI_RE = new RegExp(`(?:(?:https?|ftp):\\/\\/|(?:mailto|tel):)${URI_BODY}+`, "giu");
|
|
1393
1868
|
var MAILTEL_PREFIX_RE = /^(?:mailto|tel):/iu;
|
|
1394
|
-
var
|
|
1395
|
-
var
|
|
1869
|
+
var DOMAIN_AT_RE = new RegExp(`${DOMAIN_PART}${WORD_BOUNDARY}`, "uy");
|
|
1870
|
+
var DOMAIN_SCAN_RE = new RegExp(`${AFTER_NON_WORD}(?:(${DOMAIN_PART}${WORD_BOUNDARY})|(?:${LABEL}\\.)*${LABEL})`, "gu");
|
|
1396
1871
|
var DECIMAL_RE = /\b\d+\.\d+\b/gu;
|
|
1397
|
-
var MULTI_ABBR_RE = new RegExp(
|
|
1872
|
+
var MULTI_ABBR_RE = new RegExp(`${AFTER_NON_WORD}(?:\\p{L}{1,2}\\.${S}*){2,}`, "gu");
|
|
1398
1873
|
var SINGLE_ABBR_RE = new RegExp(`(?<![\\p{L}\\p{N}])(?:${SINGLE_ABBREVS.join("|")})\\.(?=${S}|$|<)`, "giu");
|
|
1399
|
-
|
|
1874
|
+
function isEmailLocalChar(code) {
|
|
1875
|
+
return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 46 || code === 95 || code === 37 || code === 43 || code === 45 || code === 383 || code === 8490;
|
|
1876
|
+
}
|
|
1877
|
+
function shieldEmails(text, shield) {
|
|
1878
|
+
let out = "";
|
|
1879
|
+
let emitted = 0;
|
|
1880
|
+
let pos = 0;
|
|
1881
|
+
for (let at = text.indexOf("@"); at !== -1; at = text.indexOf("@", Math.max(at + 1, pos))) {
|
|
1882
|
+
let start = at;
|
|
1883
|
+
while (start > pos && isEmailLocalChar(text.charCodeAt(start - 1))) start -= 1;
|
|
1884
|
+
if (start === at) continue;
|
|
1885
|
+
DOMAIN_AT_RE.lastIndex = at + 1;
|
|
1886
|
+
if (DOMAIN_AT_RE.exec(text) !== null) {
|
|
1887
|
+
out += text.slice(emitted, start) + shield(text.slice(start, DOMAIN_AT_RE.lastIndex));
|
|
1888
|
+
emitted = pos = DOMAIN_AT_RE.lastIndex;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return emitted === 0 ? text : out + text.slice(emitted);
|
|
1892
|
+
}
|
|
1893
|
+
var DOMAIN_DOT_RE = /\.[\p{L}\p{N}]/u;
|
|
1894
|
+
function shieldDomains(text, shield) {
|
|
1895
|
+
if (!DOMAIN_DOT_RE.test(text)) return text;
|
|
1896
|
+
let out = "";
|
|
1897
|
+
let emitted = 0;
|
|
1898
|
+
DOMAIN_SCAN_RE.lastIndex = 0;
|
|
1899
|
+
for (let m = DOMAIN_SCAN_RE.exec(text); m !== null; m = DOMAIN_SCAN_RE.exec(text)) {
|
|
1900
|
+
if (m[1] !== void 0) {
|
|
1901
|
+
out += text.slice(emitted, m.index) + shield(m[1]);
|
|
1902
|
+
emitted = m.index + m[1].length;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
return emitted === 0 ? text : out + text.slice(emitted);
|
|
1906
|
+
}
|
|
1907
|
+
function trailingPunctuationStart(value) {
|
|
1908
|
+
let cut = value.length;
|
|
1909
|
+
while (cut > 0 && ".,;:!".includes(value.charAt(cut - 1))) cut -= 1;
|
|
1910
|
+
return cut;
|
|
1911
|
+
}
|
|
1400
1912
|
var SENTENCE_OPENERS = "\xBF\xA1";
|
|
1401
1913
|
var LEAD = `(?:<[^>]+>|[${SENTENCE_OPENERS}]|${S})*`;
|
|
1402
|
-
var SPACE_BEFORE_PUNCT_RE = new RegExp(
|
|
1403
|
-
var SPACE_AFTER_COMMA_RE = new RegExp(`([,;:])(?!\\
|
|
1404
|
-
var SPACE_AFTER_SENTENCE_RE = new RegExp(`([.!?]+)(?![.!?])(?!\\
|
|
1914
|
+
var SPACE_BEFORE_PUNCT_RE = new RegExp(`(?<!${S})${S}+([,;:!?.])`, "gu");
|
|
1915
|
+
var SPACE_AFTER_COMMA_RE = new RegExp(`([,;:])(?!\\p{Nd})(?!${S}|$|<)`, "gu");
|
|
1916
|
+
var SPACE_AFTER_SENTENCE_RE = new RegExp(`(?<![.!?])([.!?]+)(?![.!?])(?!\\p{Nd})(?!${S}|$|<)`, "gu");
|
|
1405
1917
|
var SPACE_AFTER_OPENER_RE = new RegExp(`([${SENTENCE_OPENERS}])${S}+`, "gu");
|
|
1406
1918
|
var CAP_FIRST_RE = new RegExp(`^(${LEAD})(\\p{Ll})`, "u");
|
|
1407
|
-
var CAP_AFTER_SENTENCE_RE = new RegExp(`([.!?\u2026])(${LEAD})(\\p{Ll})`, "gu");
|
|
1408
|
-
var CAP_AFTER_BLOCK_RE = new RegExp(
|
|
1409
|
-
`(<\\/?(?:p|h[1-6]|li|blockquote|div|td|th)[^>]*>${LEAD})(\\p{Ll})`,
|
|
1410
|
-
"giu"
|
|
1411
|
-
);
|
|
1412
|
-
var CAP_AFTER_BREAK_RE = new RegExp(`(\\n${LEAD})(\\p{Ll})`, "gu");
|
|
1413
1919
|
var up = (ch) => ch.toUpperCase();
|
|
1920
|
+
function indexLeads(text) {
|
|
1921
|
+
const n = text.length;
|
|
1922
|
+
const leadEnd = new Int32Array(n + 1);
|
|
1923
|
+
leadEnd[n] = n;
|
|
1924
|
+
let gt = -1;
|
|
1925
|
+
for (let i = n - 1; i >= 0; i -= 1) {
|
|
1926
|
+
const code = text.charCodeAt(i);
|
|
1927
|
+
let tokenEnd = -1;
|
|
1928
|
+
if (code === 191 || code === 161 || isUcpSpace(code)) {
|
|
1929
|
+
tokenEnd = i + 1;
|
|
1930
|
+
} else if (code === 60 && gt > i + 1) {
|
|
1931
|
+
tokenEnd = gt + 1;
|
|
1932
|
+
}
|
|
1933
|
+
leadEnd[i] = tokenEnd === -1 ? i : leadEnd[tokenEnd];
|
|
1934
|
+
if (code === 62) gt = i;
|
|
1935
|
+
}
|
|
1936
|
+
return { leadEnd };
|
|
1937
|
+
}
|
|
1938
|
+
var LOWER_RE = /^\p{Ll}/u;
|
|
1939
|
+
var BLOCK_TAG_NAME_RE = /<\/?(?:p|h[1-6]|li|blockquote|div|td|th)/iuy;
|
|
1940
|
+
var SENTENCE_END_SCAN_RE = new RegExp(`[.!?${String.fromCharCode(8230)}]`, "g");
|
|
1941
|
+
var LEAD_WALK = 32;
|
|
1942
|
+
function leadEndFrom(text, i, leads) {
|
|
1943
|
+
let j = i;
|
|
1944
|
+
for (let steps = 0; steps < LEAD_WALK && j < text.length; steps += 1) {
|
|
1945
|
+
const code = text.charCodeAt(j);
|
|
1946
|
+
if (code === 60) return leads().leadEnd[j];
|
|
1947
|
+
if (code !== 191 && code !== 161 && !isUcpSpace(code)) return j;
|
|
1948
|
+
j += 1;
|
|
1949
|
+
}
|
|
1950
|
+
return j < text.length ? leads().leadEnd[j] : j;
|
|
1951
|
+
}
|
|
1952
|
+
function capitalizeAfter(text, lower, next, context) {
|
|
1953
|
+
const leads = () => {
|
|
1954
|
+
if (context.leads === null || context.leads.leadEnd.length !== text.length + 1) context.leads = indexLeads(text);
|
|
1955
|
+
return context.leads;
|
|
1956
|
+
};
|
|
1957
|
+
let out = "";
|
|
1958
|
+
let emitted = 0;
|
|
1959
|
+
let from = 0;
|
|
1960
|
+
for (let boundary = next(text, from); boundary !== null; boundary = next(text, from)) {
|
|
1961
|
+
from = boundary.resume;
|
|
1962
|
+
const at = leadEndFrom(text, boundary.leadStart, leads);
|
|
1963
|
+
const cp = text.codePointAt(at);
|
|
1964
|
+
if (cp === void 0) continue;
|
|
1965
|
+
const ch = String.fromCodePoint(cp);
|
|
1966
|
+
if (!lower.test(ch)) continue;
|
|
1967
|
+
out += text.slice(emitted, at) + up(ch);
|
|
1968
|
+
emitted = at + ch.length;
|
|
1969
|
+
from = emitted;
|
|
1970
|
+
}
|
|
1971
|
+
return emitted === 0 ? text : out + text.slice(emitted);
|
|
1972
|
+
}
|
|
1973
|
+
var afterSentenceEnd = (text, from) => {
|
|
1974
|
+
SENTENCE_END_SCAN_RE.lastIndex = from;
|
|
1975
|
+
const m = SENTENCE_END_SCAN_RE.exec(text);
|
|
1976
|
+
return m === null ? null : { leadStart: m.index + 1, resume: m.index + 1 };
|
|
1977
|
+
};
|
|
1978
|
+
function afterBlockTag() {
|
|
1979
|
+
let gtFrom = -1;
|
|
1980
|
+
let gt = -1;
|
|
1981
|
+
return (text, from) => {
|
|
1982
|
+
for (let lt = text.indexOf("<", from); lt !== -1; lt = text.indexOf("<", lt + 1)) {
|
|
1983
|
+
BLOCK_TAG_NAME_RE.lastIndex = lt;
|
|
1984
|
+
if (!BLOCK_TAG_NAME_RE.test(text)) continue;
|
|
1985
|
+
const nameEnd = BLOCK_TAG_NAME_RE.lastIndex;
|
|
1986
|
+
if (gtFrom === -1 || nameEnd < gtFrom || gt !== -1 && nameEnd > gt) {
|
|
1987
|
+
gtFrom = nameEnd;
|
|
1988
|
+
gt = text.indexOf(">", nameEnd);
|
|
1989
|
+
}
|
|
1990
|
+
if (gt === -1) continue;
|
|
1991
|
+
return { leadStart: gt + 1, resume: lt + 1 };
|
|
1992
|
+
}
|
|
1993
|
+
return null;
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
var afterLineBreak = (text, from) => {
|
|
1997
|
+
const at = text.indexOf("\n", from);
|
|
1998
|
+
return at === -1 ? null : { leadStart: at + 1, resume: at + 1 };
|
|
1999
|
+
};
|
|
1414
2000
|
var SHIELD_PREFIXES = ["URL", "URI", "EMAIL", "DOM", "NUM", "ABBR"];
|
|
1415
2001
|
var RESTORE_RE2 = new RegExp(`\\x00(?:${SHIELD_PREFIXES.join("|")})_\\d+\\x00`, "gu");
|
|
1416
2002
|
function restore(text, input, placeholders) {
|
|
1417
2003
|
if (!input.includes("\0")) {
|
|
1418
2004
|
return text.replace(RESTORE_RE2, (key) => placeholders.get(key) ?? key);
|
|
1419
2005
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
2006
|
+
return restoreAsTheLoopDoes(text, placeholders);
|
|
2007
|
+
}
|
|
2008
|
+
function restoreAsTheLoopDoes(text, placeholders) {
|
|
2009
|
+
const gaps = text.split("\0");
|
|
2010
|
+
const rank = /* @__PURE__ */ new Map();
|
|
2011
|
+
for (const key of placeholders.keys()) rank.set(key, rank.size);
|
|
2012
|
+
const byRank = [];
|
|
2013
|
+
for (let j = 1; j < gaps.length - 1; j += 1) {
|
|
2014
|
+
const r = rank.get(`\0${gaps[j]}\0`);
|
|
2015
|
+
if (r !== void 0) (byRank[r] ??= []).push(j);
|
|
2016
|
+
}
|
|
2017
|
+
const taken = new Uint8Array(gaps.length);
|
|
2018
|
+
const replaced = new Uint8Array(gaps.length);
|
|
2019
|
+
for (const candidates of byRank) {
|
|
2020
|
+
for (const j of candidates ?? []) {
|
|
2021
|
+
if (taken[j - 1] === 1 || taken[j] === 1) continue;
|
|
2022
|
+
taken[j - 1] = 1;
|
|
2023
|
+
taken[j] = 1;
|
|
2024
|
+
replaced[j] = 1;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
let out = gaps[0];
|
|
2028
|
+
for (let j = 1; j < gaps.length; j += 1) {
|
|
2029
|
+
if (taken[j - 1] !== 1) out += "\0";
|
|
2030
|
+
const gap = gaps[j];
|
|
2031
|
+
out += replaced[j] === 1 ? placeholders.get(`\0${gap}\0`) : gap;
|
|
1423
2032
|
}
|
|
1424
2033
|
return out;
|
|
1425
2034
|
}
|
|
@@ -1433,21 +2042,17 @@ function postProcess(input) {
|
|
|
1433
2042
|
return key;
|
|
1434
2043
|
};
|
|
1435
2044
|
const storeWithTrailingPunct = (value, prefix) => {
|
|
1436
|
-
const
|
|
1437
|
-
if (
|
|
1438
|
-
|
|
1439
|
-
const body = value.slice(0, value.length - suffix.length);
|
|
1440
|
-
return body === "" ? suffix : store(body, prefix) + suffix;
|
|
1441
|
-
}
|
|
1442
|
-
return store(value, prefix);
|
|
2045
|
+
const cut = trailingPunctuationStart(value);
|
|
2046
|
+
if (cut === value.length) return store(value, prefix);
|
|
2047
|
+
return cut === 0 ? value : store(value.slice(0, cut), prefix) + value.slice(cut);
|
|
1443
2048
|
};
|
|
1444
2049
|
let text = input;
|
|
1445
2050
|
text = text.replace(
|
|
1446
2051
|
URI_RE,
|
|
1447
2052
|
(m) => storeWithTrailingPunct(m, MAILTEL_PREFIX_RE.test(m) ? "URI" : "URL")
|
|
1448
2053
|
);
|
|
1449
|
-
text = text
|
|
1450
|
-
text = text
|
|
2054
|
+
text = shieldEmails(text, (m) => store(m, "EMAIL"));
|
|
2055
|
+
text = shieldDomains(text, (m) => store(m, "DOM"));
|
|
1451
2056
|
text = text.replace(DECIMAL_RE, (m) => store(m, "NUM"));
|
|
1452
2057
|
text = text.replace(MULTI_ABBR_RE, (m) => store(m, "ABBR"));
|
|
1453
2058
|
text = text.replace(SINGLE_ABBR_RE, (m) => store(m, "ABBR"));
|
|
@@ -1457,9 +2062,10 @@ function postProcess(input) {
|
|
|
1457
2062
|
text = text.replace(SPACE_AFTER_SENTENCE_RE, "$1 ");
|
|
1458
2063
|
text = text.replace(SPACE_AFTER_OPENER_RE, "$1");
|
|
1459
2064
|
text = text.replace(CAP_FIRST_RE, (_m, lead, ch) => lead + up(ch));
|
|
1460
|
-
|
|
1461
|
-
text = text
|
|
1462
|
-
text = text
|
|
2065
|
+
const leadContext = { leads: null };
|
|
2066
|
+
text = capitalizeAfter(text, LOWER_RE, afterSentenceEnd, leadContext);
|
|
2067
|
+
text = capitalizeAfter(text, LOWER_RE, afterBlockTag(), leadContext);
|
|
2068
|
+
text = capitalizeAfter(text, LOWER_RE, afterLineBreak, leadContext);
|
|
1463
2069
|
return restore(text, input, placeholders).trim();
|
|
1464
2070
|
}
|
|
1465
2071
|
|