@tsuzuri-lab/sed-language-server 0.1.1 → 0.2.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/README.md +49 -39
- package/package.json +8 -4
- package/src/analysis.js +275 -0
- package/src/server.js +49 -46
- package/vendor/tree-sitter-sed-gnu.wasm +0 -0
- package/vendor/tree-sitter-sed-posix.wasm +0 -0
- package/src/completion.js +0 -148
- package/src/definition.js +0 -46
- package/src/diagnostics.js +0 -1865
- package/src/document-structure.js +0 -774
- package/src/sed-syntax.js +0 -2321
- package/src/syntax-profile.js +0 -99
package/src/sed-syntax.js
DELETED
|
@@ -1,2321 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defaultSyntaxProfile,
|
|
3
|
-
requireSyntaxProfile,
|
|
4
|
-
} from "./syntax-profile.js";
|
|
5
|
-
|
|
6
|
-
export const commandSpecifications = Object.freeze([
|
|
7
|
-
{
|
|
8
|
-
command: ":",
|
|
9
|
-
kind: "label",
|
|
10
|
-
maximumAddresses: 0,
|
|
11
|
-
documentation: "Define a label for b and t branches.",
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
command: "#",
|
|
15
|
-
kind: "line",
|
|
16
|
-
maximumAddresses: 0,
|
|
17
|
-
documentation: "Ignore the rest of the line as a comment.",
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
command: "}",
|
|
21
|
-
kind: "block-close",
|
|
22
|
-
maximumAddresses: 0,
|
|
23
|
-
documentation: "End the current command block.",
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
command: "=",
|
|
27
|
-
kind: "simple",
|
|
28
|
-
maximumAddresses: 1,
|
|
29
|
-
documentation: "Write the current input line number.",
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
command: "a",
|
|
33
|
-
kind: "text",
|
|
34
|
-
maximumAddresses: 1,
|
|
35
|
-
documentation: "Queue text for output after the selected pattern space.",
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
command: "i",
|
|
39
|
-
kind: "text",
|
|
40
|
-
maximumAddresses: 1,
|
|
41
|
-
documentation: "Write text before the selected pattern space.",
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
command: "q",
|
|
45
|
-
kind: "simple",
|
|
46
|
-
maximumAddresses: 1,
|
|
47
|
-
documentation: "Quit without starting another cycle.",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
command: "r",
|
|
51
|
-
kind: "file",
|
|
52
|
-
maximumAddresses: 1,
|
|
53
|
-
documentation: "Queue a file's contents for output.",
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
command: "{",
|
|
57
|
-
kind: "block-open",
|
|
58
|
-
maximumAddresses: 2,
|
|
59
|
-
documentation:
|
|
60
|
-
"Execute a command block when its address selects the pattern space.",
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
command: "b",
|
|
64
|
-
kind: "label",
|
|
65
|
-
maximumAddresses: 2,
|
|
66
|
-
documentation:
|
|
67
|
-
"Branch to a label, or to the end of the script when omitted.",
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
command: "c",
|
|
71
|
-
kind: "text",
|
|
72
|
-
maximumAddresses: 2,
|
|
73
|
-
documentation:
|
|
74
|
-
"Delete the selected line or range, write text in its place, and start the next cycle.",
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
command: "d",
|
|
78
|
-
kind: "simple",
|
|
79
|
-
maximumAddresses: 2,
|
|
80
|
-
documentation: "Delete the pattern space and start the next cycle.",
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
command: "D",
|
|
84
|
-
kind: "simple",
|
|
85
|
-
maximumAddresses: 2,
|
|
86
|
-
documentation: "Delete through the first newline and restart the cycle.",
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
command: "g",
|
|
90
|
-
kind: "simple",
|
|
91
|
-
maximumAddresses: 2,
|
|
92
|
-
documentation: "Replace the pattern space with the hold space.",
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
command: "G",
|
|
96
|
-
kind: "simple",
|
|
97
|
-
maximumAddresses: 2,
|
|
98
|
-
documentation: "Append a newline and the hold space to the pattern space.",
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
command: "h",
|
|
102
|
-
kind: "simple",
|
|
103
|
-
maximumAddresses: 2,
|
|
104
|
-
documentation: "Replace the hold space with the pattern space.",
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
command: "H",
|
|
108
|
-
kind: "simple",
|
|
109
|
-
maximumAddresses: 2,
|
|
110
|
-
documentation: "Append a newline and the pattern space to the hold space.",
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
command: "l",
|
|
114
|
-
kind: "simple",
|
|
115
|
-
maximumAddresses: 2,
|
|
116
|
-
documentation: "Write the pattern space in a visually unambiguous form.",
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
command: "n",
|
|
120
|
-
kind: "simple",
|
|
121
|
-
maximumAddresses: 2,
|
|
122
|
-
documentation:
|
|
123
|
-
"Write the pattern space if default output is enabled, then read the next input line.",
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
command: "N",
|
|
127
|
-
kind: "simple",
|
|
128
|
-
maximumAddresses: 2,
|
|
129
|
-
documentation: "Append the next input line to the pattern space.",
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
command: "p",
|
|
133
|
-
kind: "simple",
|
|
134
|
-
maximumAddresses: 2,
|
|
135
|
-
documentation: "Write the pattern space.",
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
command: "P",
|
|
139
|
-
kind: "simple",
|
|
140
|
-
maximumAddresses: 2,
|
|
141
|
-
documentation: "Write the pattern space through its first newline.",
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
command: "s",
|
|
145
|
-
kind: "substitute",
|
|
146
|
-
maximumAddresses: 2,
|
|
147
|
-
documentation: "Replace regular-expression matches in the pattern space.",
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
command: "t",
|
|
151
|
-
kind: "label",
|
|
152
|
-
maximumAddresses: 2,
|
|
153
|
-
documentation:
|
|
154
|
-
"Branch if a substitution has occurred since the latest input read or t command.",
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
command: "w",
|
|
158
|
-
kind: "file",
|
|
159
|
-
maximumAddresses: 2,
|
|
160
|
-
documentation: "Append the pattern space to a file.",
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
command: "x",
|
|
164
|
-
kind: "simple",
|
|
165
|
-
maximumAddresses: 2,
|
|
166
|
-
documentation: "Exchange the pattern and hold spaces.",
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
command: "y",
|
|
170
|
-
kind: "transliterate",
|
|
171
|
-
maximumAddresses: 2,
|
|
172
|
-
documentation: "Transliterate characters from one set to another.",
|
|
173
|
-
},
|
|
174
|
-
]);
|
|
175
|
-
|
|
176
|
-
const gnuCommandSpecificationOverlays = Object.freeze([
|
|
177
|
-
Object.freeze({
|
|
178
|
-
command: ":",
|
|
179
|
-
documentation: "Define a label for b, t, and T branches.",
|
|
180
|
-
}),
|
|
181
|
-
Object.freeze({
|
|
182
|
-
command: "=",
|
|
183
|
-
maximumAddresses: 2,
|
|
184
|
-
}),
|
|
185
|
-
Object.freeze({
|
|
186
|
-
command: "a",
|
|
187
|
-
maximumAddresses: 2,
|
|
188
|
-
}),
|
|
189
|
-
Object.freeze({
|
|
190
|
-
command: "i",
|
|
191
|
-
maximumAddresses: 2,
|
|
192
|
-
}),
|
|
193
|
-
Object.freeze({
|
|
194
|
-
command: "l",
|
|
195
|
-
kind: "numeric",
|
|
196
|
-
}),
|
|
197
|
-
Object.freeze({
|
|
198
|
-
command: "q",
|
|
199
|
-
kind: "numeric",
|
|
200
|
-
}),
|
|
201
|
-
Object.freeze({
|
|
202
|
-
command: "r",
|
|
203
|
-
maximumAddresses: 2,
|
|
204
|
-
}),
|
|
205
|
-
Object.freeze({
|
|
206
|
-
command: "t",
|
|
207
|
-
documentation:
|
|
208
|
-
"Branch if a substitution has occurred since the latest input read or conditional branch.",
|
|
209
|
-
}),
|
|
210
|
-
Object.freeze({
|
|
211
|
-
command: "e",
|
|
212
|
-
kind: "shell",
|
|
213
|
-
maximumAddresses: 2,
|
|
214
|
-
documentation:
|
|
215
|
-
"Execute shell command text, or execute the pattern space when omitted.",
|
|
216
|
-
}),
|
|
217
|
-
Object.freeze({
|
|
218
|
-
command: "F",
|
|
219
|
-
kind: "simple",
|
|
220
|
-
maximumAddresses: 2,
|
|
221
|
-
documentation: "Write the current input filename.",
|
|
222
|
-
}),
|
|
223
|
-
Object.freeze({
|
|
224
|
-
command: "Q",
|
|
225
|
-
kind: "numeric",
|
|
226
|
-
maximumAddresses: 1,
|
|
227
|
-
documentation:
|
|
228
|
-
"Quit without writing the pattern space, optionally returning an exit status.",
|
|
229
|
-
}),
|
|
230
|
-
Object.freeze({
|
|
231
|
-
command: "R",
|
|
232
|
-
kind: "file",
|
|
233
|
-
maximumAddresses: 2,
|
|
234
|
-
documentation: "Queue the next line from a file for output.",
|
|
235
|
-
}),
|
|
236
|
-
Object.freeze({
|
|
237
|
-
command: "T",
|
|
238
|
-
kind: "label",
|
|
239
|
-
maximumAddresses: 2,
|
|
240
|
-
documentation:
|
|
241
|
-
"Branch if no substitution has occurred since the latest input read or conditional branch.",
|
|
242
|
-
}),
|
|
243
|
-
Object.freeze({
|
|
244
|
-
command: "v",
|
|
245
|
-
kind: "version",
|
|
246
|
-
maximumAddresses: 2,
|
|
247
|
-
documentation: "Require GNU sed, optionally at a minimum version.",
|
|
248
|
-
}),
|
|
249
|
-
Object.freeze({
|
|
250
|
-
command: "W",
|
|
251
|
-
kind: "file",
|
|
252
|
-
maximumAddresses: 2,
|
|
253
|
-
documentation:
|
|
254
|
-
"Append the pattern space through its first newline to a file.",
|
|
255
|
-
}),
|
|
256
|
-
Object.freeze({
|
|
257
|
-
command: "z",
|
|
258
|
-
kind: "simple",
|
|
259
|
-
maximumAddresses: 2,
|
|
260
|
-
documentation: "Empty the pattern space.",
|
|
261
|
-
}),
|
|
262
|
-
]);
|
|
263
|
-
|
|
264
|
-
function applySpecificationOverlays(specifications, overlays, key) {
|
|
265
|
-
const merged = new Map(
|
|
266
|
-
specifications.map((specification) => [specification[key], specification]),
|
|
267
|
-
);
|
|
268
|
-
for (const overlay of overlays) {
|
|
269
|
-
const specification = merged.get(overlay[key]);
|
|
270
|
-
merged.set(overlay[key], Object.freeze({ ...specification, ...overlay }));
|
|
271
|
-
}
|
|
272
|
-
return Object.freeze([...merged.values()]);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const gnuCommandSpecifications = applySpecificationOverlays(
|
|
276
|
-
commandSpecifications,
|
|
277
|
-
gnuCommandSpecificationOverlays,
|
|
278
|
-
"command",
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
export const substituteFlagSpecifications = Object.freeze([
|
|
282
|
-
{
|
|
283
|
-
flag: "g",
|
|
284
|
-
documentation: "Replace all non-overlapping matches.",
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
flag: "i",
|
|
288
|
-
documentation: "Match the regular expression case-insensitively.",
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
flag: "p",
|
|
292
|
-
documentation: "Write the pattern space if a replacement was made.",
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
flag: "w",
|
|
296
|
-
documentation:
|
|
297
|
-
"Append the pattern space to a file if a replacement was made.",
|
|
298
|
-
},
|
|
299
|
-
]);
|
|
300
|
-
|
|
301
|
-
const gnuSubstituteFlagSpecificationOverlays = Object.freeze([
|
|
302
|
-
Object.freeze({
|
|
303
|
-
flag: "e",
|
|
304
|
-
documentation:
|
|
305
|
-
"Evaluate the replacement as a shell command after substituting.",
|
|
306
|
-
}),
|
|
307
|
-
Object.freeze({
|
|
308
|
-
flag: "I",
|
|
309
|
-
documentation: "Match the regular expression case-insensitively.",
|
|
310
|
-
}),
|
|
311
|
-
Object.freeze({
|
|
312
|
-
flag: "M",
|
|
313
|
-
documentation:
|
|
314
|
-
"Make `^` and `$` also match around newlines in the pattern space.",
|
|
315
|
-
}),
|
|
316
|
-
Object.freeze({
|
|
317
|
-
flag: "m",
|
|
318
|
-
documentation:
|
|
319
|
-
"Make `^` and `$` also match around newlines in the pattern space.",
|
|
320
|
-
}),
|
|
321
|
-
]);
|
|
322
|
-
const gnuSubstituteFlagSpecifications = applySpecificationOverlays(
|
|
323
|
-
substituteFlagSpecifications,
|
|
324
|
-
gnuSubstituteFlagSpecificationOverlays,
|
|
325
|
-
"flag",
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
export function commandSpecificationsFor(options = defaultSyntaxProfile) {
|
|
329
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
330
|
-
return syntaxProfile.dialect === "gnu"
|
|
331
|
-
? gnuCommandSpecifications
|
|
332
|
-
: commandSpecifications;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export function substituteFlagSpecificationsFor(
|
|
336
|
-
options = defaultSyntaxProfile,
|
|
337
|
-
) {
|
|
338
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
339
|
-
return syntaxProfile.dialect === "gnu"
|
|
340
|
-
? gnuSubstituteFlagSpecifications
|
|
341
|
-
: substituteFlagSpecifications;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const posixSyntaxPolicy = Object.freeze({
|
|
345
|
-
address: Object.freeze({
|
|
346
|
-
backslashRegexpDelimiter: false,
|
|
347
|
-
numericStep: false,
|
|
348
|
-
rangeOffsets: false,
|
|
349
|
-
regexpModifiers: "",
|
|
350
|
-
}),
|
|
351
|
-
commandEnd: Object.freeze({
|
|
352
|
-
closingBraceTerminates: false,
|
|
353
|
-
commentTerminates: false,
|
|
354
|
-
rejectsTrailingBlanksInBlock: true,
|
|
355
|
-
}),
|
|
356
|
-
filename: Object.freeze({
|
|
357
|
-
opaqueUntil: "physical-line",
|
|
358
|
-
separator: "blank-required",
|
|
359
|
-
}),
|
|
360
|
-
label: Object.freeze({
|
|
361
|
-
boundary: "physical-line",
|
|
362
|
-
branchSeparator: "literal-space",
|
|
363
|
-
normalization: "preserve-trailing-blanks",
|
|
364
|
-
}),
|
|
365
|
-
numericArguments: "",
|
|
366
|
-
regexp: Object.freeze({
|
|
367
|
-
escapedPhysicalNewlines: false,
|
|
368
|
-
}),
|
|
369
|
-
shell: Object.freeze({
|
|
370
|
-
inline: false,
|
|
371
|
-
opaqueUntil: null,
|
|
372
|
-
}),
|
|
373
|
-
substituteFlags: Object.freeze({
|
|
374
|
-
separator: "none",
|
|
375
|
-
}),
|
|
376
|
-
text: Object.freeze({
|
|
377
|
-
inline: false,
|
|
378
|
-
opaqueUntil: "physical-line",
|
|
379
|
-
}),
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
const gnuSyntaxPolicy = Object.freeze({
|
|
383
|
-
address: Object.freeze({
|
|
384
|
-
backslashRegexpDelimiter: true,
|
|
385
|
-
numericStep: true,
|
|
386
|
-
rangeOffsets: true,
|
|
387
|
-
regexpModifiers: "IM",
|
|
388
|
-
}),
|
|
389
|
-
commandEnd: Object.freeze({
|
|
390
|
-
closingBraceTerminates: true,
|
|
391
|
-
commentTerminates: true,
|
|
392
|
-
rejectsTrailingBlanksInBlock: false,
|
|
393
|
-
}),
|
|
394
|
-
filename: Object.freeze({
|
|
395
|
-
opaqueUntil: "physical-line",
|
|
396
|
-
separator: "optional-blanks",
|
|
397
|
-
}),
|
|
398
|
-
label: Object.freeze({
|
|
399
|
-
boundary: "gnu-token",
|
|
400
|
-
branchSeparator: "optional-blanks",
|
|
401
|
-
normalization: "trim-syntactic-blanks",
|
|
402
|
-
}),
|
|
403
|
-
numericArguments: "lqQ",
|
|
404
|
-
regexp: Object.freeze({
|
|
405
|
-
escapedPhysicalNewlines: true,
|
|
406
|
-
}),
|
|
407
|
-
shell: Object.freeze({
|
|
408
|
-
inline: true,
|
|
409
|
-
opaqueUntil: "escaped-newline-continuation",
|
|
410
|
-
}),
|
|
411
|
-
substituteFlags: Object.freeze({
|
|
412
|
-
separator: "optional-blanks",
|
|
413
|
-
}),
|
|
414
|
-
text: Object.freeze({
|
|
415
|
-
inline: true,
|
|
416
|
-
opaqueUntil: "physical-line",
|
|
417
|
-
}),
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
const noAddressModifiers = Object.freeze([]);
|
|
421
|
-
|
|
422
|
-
export function syntaxPolicyFor(options = defaultSyntaxProfile) {
|
|
423
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
424
|
-
return syntaxProfile.dialect === "gnu" ? gnuSyntaxPolicy : posixSyntaxPolicy;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export function characterAt(text, offset) {
|
|
428
|
-
const codePoint = text.codePointAt(offset);
|
|
429
|
-
if (codePoint === undefined) {
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const value = String.fromCodePoint(codePoint);
|
|
434
|
-
return { value, width: value.length };
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
export function skipBlanks(text, offset, end) {
|
|
438
|
-
let cursor = offset;
|
|
439
|
-
while (cursor < end && (text[cursor] === " " || text[cursor] === "\t")) {
|
|
440
|
-
cursor += 1;
|
|
441
|
-
}
|
|
442
|
-
return cursor;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function scanDecimalEnd(text, offset, end) {
|
|
446
|
-
let cursor = offset;
|
|
447
|
-
while (cursor < end && text[cursor] >= "0" && text[cursor] <= "9") {
|
|
448
|
-
cursor += 1;
|
|
449
|
-
}
|
|
450
|
-
return cursor;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
export function findLineEnd(text, offset) {
|
|
454
|
-
const newlineOffset = text.indexOf("\n", offset);
|
|
455
|
-
let lineEnd = newlineOffset === -1 ? text.length : newlineOffset;
|
|
456
|
-
if (lineEnd > offset && text[lineEnd - 1] === "\r") {
|
|
457
|
-
lineEnd -= 1;
|
|
458
|
-
}
|
|
459
|
-
return lineEnd;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
export function hasUnescapedTrailingBackslash(text, lineStart, lineEnd) {
|
|
463
|
-
let backslashCount = 0;
|
|
464
|
-
let cursor = lineEnd - 1;
|
|
465
|
-
|
|
466
|
-
while (cursor >= lineStart && text[cursor] === "\\") {
|
|
467
|
-
backslashCount += 1;
|
|
468
|
-
cursor -= 1;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
return backslashCount % 2 === 1;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
export function scanCommandRecoverySyntax(
|
|
475
|
-
text,
|
|
476
|
-
offset,
|
|
477
|
-
lineEnd,
|
|
478
|
-
options = defaultSyntaxProfile,
|
|
479
|
-
state = {},
|
|
480
|
-
) {
|
|
481
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
482
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
483
|
-
const recoverAtClosingBrace =
|
|
484
|
-
(state.recoverAtClosingBrace ?? false) ||
|
|
485
|
-
policy.commandEnd.closingBraceTerminates;
|
|
486
|
-
const separatorOffset = text.indexOf(";", offset);
|
|
487
|
-
const closingBraceOffset = recoverAtClosingBrace
|
|
488
|
-
? text.indexOf("}", offset)
|
|
489
|
-
: -1;
|
|
490
|
-
const commentOffset = policy.commandEnd.commentTerminates
|
|
491
|
-
? text.indexOf("#", offset)
|
|
492
|
-
: -1;
|
|
493
|
-
let recoveryOffset = null;
|
|
494
|
-
for (const candidate of [
|
|
495
|
-
separatorOffset,
|
|
496
|
-
closingBraceOffset,
|
|
497
|
-
commentOffset,
|
|
498
|
-
]) {
|
|
499
|
-
if (
|
|
500
|
-
candidate !== -1 &&
|
|
501
|
-
candidate < lineEnd &&
|
|
502
|
-
(recoveryOffset === null || candidate < recoveryOffset)
|
|
503
|
-
) {
|
|
504
|
-
recoveryOffset = candidate;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (recoveryOffset === null) {
|
|
509
|
-
return {
|
|
510
|
-
nextCommandBoundary: null,
|
|
511
|
-
nextCommandOffset: null,
|
|
512
|
-
recoveryOffset: null,
|
|
513
|
-
terminator: null,
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (text[recoveryOffset] === "#") {
|
|
518
|
-
return {
|
|
519
|
-
nextCommandBoundary: null,
|
|
520
|
-
nextCommandOffset: null,
|
|
521
|
-
recoveryOffset,
|
|
522
|
-
terminator: "comment",
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
if (text[recoveryOffset] === "}") {
|
|
527
|
-
return {
|
|
528
|
-
nextCommandBoundary: "recovered",
|
|
529
|
-
nextCommandOffset: recoveryOffset,
|
|
530
|
-
recoveryOffset,
|
|
531
|
-
terminator: "closing-brace",
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
return {
|
|
536
|
-
nextCommandBoundary: "separated",
|
|
537
|
-
nextCommandOffset: recoveryOffset + 1,
|
|
538
|
-
recoveryOffset,
|
|
539
|
-
terminator: "semicolon",
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
export function scanCommandEndSyntax(
|
|
544
|
-
text,
|
|
545
|
-
commandEnd,
|
|
546
|
-
lineEnd,
|
|
547
|
-
options = defaultSyntaxProfile,
|
|
548
|
-
state = {},
|
|
549
|
-
) {
|
|
550
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
551
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
552
|
-
const insideBlock = state.insideBlock ?? false;
|
|
553
|
-
const recoverAtClosingBrace = state.recoverAtClosingBrace ?? insideBlock;
|
|
554
|
-
const argumentOffset = skipBlanks(text, commandEnd, lineEnd);
|
|
555
|
-
|
|
556
|
-
if (
|
|
557
|
-
insideBlock &&
|
|
558
|
-
policy.commandEnd.rejectsTrailingBlanksInBlock &&
|
|
559
|
-
argumentOffset > commandEnd &&
|
|
560
|
-
(argumentOffset >= lineEnd || text[argumentOffset] === ";")
|
|
561
|
-
) {
|
|
562
|
-
return {
|
|
563
|
-
kind: "unexpected",
|
|
564
|
-
nextCommandBoundary: argumentOffset >= lineEnd ? null : "separated",
|
|
565
|
-
nextCommandOffset: argumentOffset >= lineEnd ? null : argumentOffset + 1,
|
|
566
|
-
startOffset: commandEnd,
|
|
567
|
-
endOffset: argumentOffset,
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (argumentOffset >= lineEnd) {
|
|
572
|
-
return {
|
|
573
|
-
kind: "valid",
|
|
574
|
-
nextCommandBoundary: null,
|
|
575
|
-
nextCommandOffset: null,
|
|
576
|
-
terminator: "physical-line",
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
if (text[argumentOffset] === ";") {
|
|
581
|
-
return {
|
|
582
|
-
kind: "valid",
|
|
583
|
-
nextCommandBoundary: "separated",
|
|
584
|
-
nextCommandOffset: argumentOffset + 1,
|
|
585
|
-
terminator: "semicolon",
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
if (policy.commandEnd.commentTerminates && text[argumentOffset] === "#") {
|
|
590
|
-
return {
|
|
591
|
-
kind: "valid",
|
|
592
|
-
nextCommandBoundary: null,
|
|
593
|
-
nextCommandOffset: null,
|
|
594
|
-
terminator: "comment",
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
(policy.commandEnd.closingBraceTerminates || recoverAtClosingBrace) &&
|
|
600
|
-
text[argumentOffset] === "}"
|
|
601
|
-
) {
|
|
602
|
-
return {
|
|
603
|
-
kind: "valid",
|
|
604
|
-
nextCommandBoundary: "direct",
|
|
605
|
-
nextCommandOffset: argumentOffset,
|
|
606
|
-
terminator: policy.commandEnd.closingBraceTerminates
|
|
607
|
-
? "closing-brace"
|
|
608
|
-
: "recovered-closing-brace",
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const recovery = scanCommandRecoverySyntax(
|
|
613
|
-
text,
|
|
614
|
-
argumentOffset,
|
|
615
|
-
lineEnd,
|
|
616
|
-
syntaxProfile,
|
|
617
|
-
{ recoverAtClosingBrace },
|
|
618
|
-
);
|
|
619
|
-
let unexpectedEnd = recovery.recoveryOffset ?? lineEnd;
|
|
620
|
-
while (
|
|
621
|
-
unexpectedEnd > argumentOffset &&
|
|
622
|
-
(text[unexpectedEnd - 1] === " " || text[unexpectedEnd - 1] === "\t")
|
|
623
|
-
) {
|
|
624
|
-
unexpectedEnd -= 1;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
return {
|
|
628
|
-
kind: "unexpected",
|
|
629
|
-
nextCommandBoundary: recovery.nextCommandBoundary,
|
|
630
|
-
nextCommandOffset: recovery.nextCommandOffset,
|
|
631
|
-
startOffset: argumentOffset,
|
|
632
|
-
endOffset: unexpectedEnd,
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function gnuLabelTerminator(character) {
|
|
637
|
-
if (character === ";") {
|
|
638
|
-
return "semicolon";
|
|
639
|
-
}
|
|
640
|
-
if (character === "}") {
|
|
641
|
-
return "closing-brace";
|
|
642
|
-
}
|
|
643
|
-
if (character === "#") {
|
|
644
|
-
return "comment";
|
|
645
|
-
}
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
export function scanLabelArgumentSyntax(
|
|
650
|
-
text,
|
|
651
|
-
commandOffset,
|
|
652
|
-
lineEnd,
|
|
653
|
-
options = defaultSyntaxProfile,
|
|
654
|
-
) {
|
|
655
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
656
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
657
|
-
const command = text[commandOffset];
|
|
658
|
-
const fieldStartOffset = commandOffset + 1;
|
|
659
|
-
|
|
660
|
-
if (policy.label.boundary === "physical-line") {
|
|
661
|
-
const labelStartOffset = skipBlanks(text, fieldStartOffset, lineEnd);
|
|
662
|
-
const hasLabel = labelStartOffset < lineEnd;
|
|
663
|
-
const hasValidSeparator = command === ":" || text[fieldStartOffset] === " ";
|
|
664
|
-
return {
|
|
665
|
-
branchContextStartOffset:
|
|
666
|
-
command !== ":" && hasValidSeparator ? fieldStartOffset + 1 : null,
|
|
667
|
-
fieldEndOffset: lineEnd,
|
|
668
|
-
fieldStartOffset,
|
|
669
|
-
hasLabel,
|
|
670
|
-
hasValidSeparator,
|
|
671
|
-
labelEndOffset: lineEnd,
|
|
672
|
-
labelStartOffset,
|
|
673
|
-
name: hasLabel ? text.slice(labelStartOffset, lineEnd) : "",
|
|
674
|
-
nextCommandBoundary: null,
|
|
675
|
-
nextCommandOffset: null,
|
|
676
|
-
terminator: "physical-line",
|
|
677
|
-
terminatorOffset: lineEnd,
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
const labelStartOffset = skipBlanks(text, fieldStartOffset, lineEnd);
|
|
682
|
-
let labelEndOffset = labelStartOffset;
|
|
683
|
-
while (labelEndOffset < lineEnd) {
|
|
684
|
-
const character = text[labelEndOffset];
|
|
685
|
-
if (
|
|
686
|
-
character === " " ||
|
|
687
|
-
character === "\t" ||
|
|
688
|
-
gnuLabelTerminator(character) !== null
|
|
689
|
-
) {
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
labelEndOffset += 1;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
let boundaryOffset = labelEndOffset;
|
|
696
|
-
let terminator = gnuLabelTerminator(text[boundaryOffset]);
|
|
697
|
-
if (text[boundaryOffset] === " " || text[boundaryOffset] === "\t") {
|
|
698
|
-
const afterBlanks = skipBlanks(text, boundaryOffset, lineEnd);
|
|
699
|
-
const structuralTerminator = gnuLabelTerminator(text[afterBlanks]);
|
|
700
|
-
if (afterBlanks >= lineEnd) {
|
|
701
|
-
boundaryOffset = lineEnd;
|
|
702
|
-
terminator = "physical-line";
|
|
703
|
-
} else if (structuralTerminator !== null) {
|
|
704
|
-
boundaryOffset = afterBlanks;
|
|
705
|
-
terminator = structuralTerminator;
|
|
706
|
-
} else {
|
|
707
|
-
terminator = "blank";
|
|
708
|
-
boundaryOffset = labelEndOffset;
|
|
709
|
-
}
|
|
710
|
-
} else if (boundaryOffset >= lineEnd) {
|
|
711
|
-
terminator = "physical-line";
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
let nextCommandOffset = null;
|
|
715
|
-
let nextCommandBoundary = null;
|
|
716
|
-
if (terminator === "semicolon") {
|
|
717
|
-
nextCommandOffset = boundaryOffset + 1;
|
|
718
|
-
nextCommandBoundary = "separated";
|
|
719
|
-
} else if (terminator === "closing-brace") {
|
|
720
|
-
nextCommandOffset = boundaryOffset;
|
|
721
|
-
nextCommandBoundary = "direct";
|
|
722
|
-
} else if (terminator === "blank") {
|
|
723
|
-
nextCommandOffset = skipBlanks(text, labelEndOffset, lineEnd);
|
|
724
|
-
nextCommandBoundary = "separated";
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
return {
|
|
728
|
-
branchContextStartOffset: command === ":" ? null : fieldStartOffset,
|
|
729
|
-
fieldEndOffset: boundaryOffset,
|
|
730
|
-
fieldStartOffset,
|
|
731
|
-
hasLabel: labelStartOffset < labelEndOffset,
|
|
732
|
-
hasValidSeparator: true,
|
|
733
|
-
labelEndOffset,
|
|
734
|
-
labelStartOffset,
|
|
735
|
-
name: text.slice(labelStartOffset, labelEndOffset),
|
|
736
|
-
nextCommandBoundary,
|
|
737
|
-
nextCommandOffset,
|
|
738
|
-
terminator,
|
|
739
|
-
terminatorOffset: boundaryOffset,
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
export function scanFilenameArgumentSyntax(
|
|
744
|
-
text,
|
|
745
|
-
commandOffset,
|
|
746
|
-
lineEnd,
|
|
747
|
-
options = defaultSyntaxProfile,
|
|
748
|
-
) {
|
|
749
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
750
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
751
|
-
const fieldStartOffset = commandOffset + 1;
|
|
752
|
-
const hasBlankSeparator =
|
|
753
|
-
text[fieldStartOffset] === " " || text[fieldStartOffset] === "\t";
|
|
754
|
-
const filenameStartOffset = skipBlanks(text, fieldStartOffset, lineEnd);
|
|
755
|
-
|
|
756
|
-
return {
|
|
757
|
-
fieldStartOffset,
|
|
758
|
-
filenameEndOffset: lineEnd,
|
|
759
|
-
filenameStartOffset,
|
|
760
|
-
hasName: filenameStartOffset < lineEnd,
|
|
761
|
-
hasValidSeparator:
|
|
762
|
-
policy.filename.separator === "optional-blanks" || hasBlankSeparator,
|
|
763
|
-
name:
|
|
764
|
-
filenameStartOffset < lineEnd
|
|
765
|
-
? text.slice(filenameStartOffset, lineEnd)
|
|
766
|
-
: "",
|
|
767
|
-
opaqueUntil: policy.filename.opaqueUntil,
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
export function scanTextCommandSyntax(
|
|
772
|
-
text,
|
|
773
|
-
commandOffset,
|
|
774
|
-
lineEnd,
|
|
775
|
-
hasPhysicalNewline,
|
|
776
|
-
options = defaultSyntaxProfile,
|
|
777
|
-
) {
|
|
778
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
779
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
780
|
-
const fieldStartOffset = commandOffset + 1;
|
|
781
|
-
|
|
782
|
-
if (!policy.text.inline) {
|
|
783
|
-
if (text[fieldStartOffset] !== "\\") {
|
|
784
|
-
return {
|
|
785
|
-
kind: "invalid",
|
|
786
|
-
reason: "missing-backslash",
|
|
787
|
-
startOffset: commandOffset,
|
|
788
|
-
endOffset: commandOffset + 1,
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
if (fieldStartOffset + 1 < lineEnd) {
|
|
793
|
-
return {
|
|
794
|
-
kind: "invalid",
|
|
795
|
-
reason: "unexpected-after-backslash",
|
|
796
|
-
startOffset: fieldStartOffset + 1,
|
|
797
|
-
endOffset: lineEnd,
|
|
798
|
-
consumesFollowingTextLine: hasPhysicalNewline,
|
|
799
|
-
allowsMissingFollowingTextLine: false,
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
if (!hasPhysicalNewline) {
|
|
804
|
-
return {
|
|
805
|
-
kind: "invalid",
|
|
806
|
-
reason: "missing-newline",
|
|
807
|
-
startOffset: fieldStartOffset,
|
|
808
|
-
endOffset: fieldStartOffset + 1,
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
return {
|
|
813
|
-
kind: "portable",
|
|
814
|
-
backslashOffset: fieldStartOffset,
|
|
815
|
-
consumesFollowingTextLine: true,
|
|
816
|
-
allowsMissingFollowingTextLine: false,
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const textStartOffset = skipBlanks(text, fieldStartOffset, lineEnd);
|
|
821
|
-
if (textStartOffset >= lineEnd) {
|
|
822
|
-
return hasPhysicalNewline
|
|
823
|
-
? {
|
|
824
|
-
kind: "inline",
|
|
825
|
-
textStartOffset: lineEnd,
|
|
826
|
-
textEndOffset: lineEnd,
|
|
827
|
-
}
|
|
828
|
-
: {
|
|
829
|
-
kind: "invalid",
|
|
830
|
-
reason: "missing-text",
|
|
831
|
-
startOffset: commandOffset,
|
|
832
|
-
endOffset: commandOffset + 1,
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
if (text[textStartOffset] !== "\\") {
|
|
837
|
-
return {
|
|
838
|
-
kind: "inline",
|
|
839
|
-
consumesFollowingTextLine:
|
|
840
|
-
hasPhysicalNewline &&
|
|
841
|
-
hasUnescapedTrailingBackslash(text, textStartOffset, lineEnd),
|
|
842
|
-
allowsMissingFollowingTextLine: true,
|
|
843
|
-
textStartOffset,
|
|
844
|
-
textEndOffset: lineEnd,
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
if (textStartOffset + 1 < lineEnd) {
|
|
849
|
-
const leadingTextCharacter = characterAt(text, textStartOffset + 1);
|
|
850
|
-
return {
|
|
851
|
-
kind: "inline",
|
|
852
|
-
consumesFollowingTextLine:
|
|
853
|
-
hasPhysicalNewline &&
|
|
854
|
-
hasUnescapedTrailingBackslash(
|
|
855
|
-
text,
|
|
856
|
-
textStartOffset + 1 + (leadingTextCharacter?.width ?? 0),
|
|
857
|
-
lineEnd,
|
|
858
|
-
),
|
|
859
|
-
allowsMissingFollowingTextLine: true,
|
|
860
|
-
textStartOffset: textStartOffset + 1,
|
|
861
|
-
textEndOffset: lineEnd,
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
if (!hasPhysicalNewline) {
|
|
866
|
-
return {
|
|
867
|
-
kind: "inline",
|
|
868
|
-
textStartOffset: lineEnd,
|
|
869
|
-
textEndOffset: lineEnd,
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
return {
|
|
874
|
-
kind: "portable",
|
|
875
|
-
backslashOffset: textStartOffset,
|
|
876
|
-
consumesFollowingTextLine: true,
|
|
877
|
-
allowsMissingFollowingTextLine: true,
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
export function scanShellCommandSyntax(
|
|
882
|
-
text,
|
|
883
|
-
commandOffset,
|
|
884
|
-
lineEnd,
|
|
885
|
-
hasPhysicalNewline,
|
|
886
|
-
options = defaultSyntaxProfile,
|
|
887
|
-
) {
|
|
888
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
889
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
890
|
-
const fieldStartOffset = commandOffset + 1;
|
|
891
|
-
|
|
892
|
-
if (!policy.shell.inline) {
|
|
893
|
-
return {
|
|
894
|
-
kind: "unsupported",
|
|
895
|
-
consumesFollowingTextLine: false,
|
|
896
|
-
fieldStartOffset,
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const shellStartOffset = skipBlanks(text, fieldStartOffset, lineEnd);
|
|
901
|
-
if (shellStartOffset >= lineEnd) {
|
|
902
|
-
return {
|
|
903
|
-
kind: "empty",
|
|
904
|
-
consumesFollowingTextLine: false,
|
|
905
|
-
fieldStartOffset,
|
|
906
|
-
shellEndOffset: lineEnd,
|
|
907
|
-
shellStartOffset: lineEnd,
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
if (text[shellStartOffset] !== "\\") {
|
|
912
|
-
return {
|
|
913
|
-
kind: "inline",
|
|
914
|
-
consumesFollowingTextLine:
|
|
915
|
-
hasPhysicalNewline &&
|
|
916
|
-
hasUnescapedTrailingBackslash(text, shellStartOffset, lineEnd),
|
|
917
|
-
fieldStartOffset,
|
|
918
|
-
shellEndOffset: lineEnd,
|
|
919
|
-
shellStartOffset,
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
const bodyStartOffset = shellStartOffset + 1;
|
|
924
|
-
if (bodyStartOffset >= lineEnd) {
|
|
925
|
-
return {
|
|
926
|
-
kind: "inline",
|
|
927
|
-
consumesFollowingTextLine: hasPhysicalNewline,
|
|
928
|
-
fieldStartOffset,
|
|
929
|
-
shellEndOffset: lineEnd,
|
|
930
|
-
shellStartOffset: bodyStartOffset,
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
const leadingShellCharacter = characterAt(text, bodyStartOffset);
|
|
935
|
-
return {
|
|
936
|
-
kind: "inline",
|
|
937
|
-
consumesFollowingTextLine:
|
|
938
|
-
hasPhysicalNewline &&
|
|
939
|
-
hasUnescapedTrailingBackslash(
|
|
940
|
-
text,
|
|
941
|
-
bodyStartOffset + (leadingShellCharacter?.width ?? 0),
|
|
942
|
-
lineEnd,
|
|
943
|
-
),
|
|
944
|
-
fieldStartOffset,
|
|
945
|
-
shellEndOffset: lineEnd,
|
|
946
|
-
shellStartOffset: bodyStartOffset,
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
export function scanOptionalNumericArgumentSyntax(
|
|
951
|
-
text,
|
|
952
|
-
commandOffset,
|
|
953
|
-
lineEnd,
|
|
954
|
-
options = defaultSyntaxProfile,
|
|
955
|
-
) {
|
|
956
|
-
const syntaxProfile = requireSyntaxProfile(options);
|
|
957
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
958
|
-
const command = text[commandOffset];
|
|
959
|
-
const fieldStartOffset = commandOffset + 1;
|
|
960
|
-
const argumentStartOffset = skipBlanks(text, fieldStartOffset, lineEnd);
|
|
961
|
-
const argumentEndOffset = policy.numericArguments.includes(command)
|
|
962
|
-
? scanDecimalEnd(text, argumentStartOffset, lineEnd)
|
|
963
|
-
: argumentStartOffset;
|
|
964
|
-
|
|
965
|
-
return {
|
|
966
|
-
argumentEndOffset,
|
|
967
|
-
argumentStartOffset,
|
|
968
|
-
commandEndOffset:
|
|
969
|
-
argumentEndOffset > argumentStartOffset
|
|
970
|
-
? argumentEndOffset
|
|
971
|
-
: fieldStartOffset,
|
|
972
|
-
hasArgument: argumentEndOffset > argumentStartOffset,
|
|
973
|
-
value:
|
|
974
|
-
argumentEndOffset > argumentStartOffset
|
|
975
|
-
? text.slice(argumentStartOffset, argumentEndOffset)
|
|
976
|
-
: null,
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
function versionCharacterCode(value, offset) {
|
|
981
|
-
return offset < value.length ? value.charCodeAt(offset) : 0;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
function isVersionDigit(code) {
|
|
985
|
-
return code >= 48 && code <= 57;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
function digitRunEnd(value, offset) {
|
|
989
|
-
let endOffset = offset;
|
|
990
|
-
while (isVersionDigit(versionCharacterCode(value, endOffset))) {
|
|
991
|
-
endOffset += 1;
|
|
992
|
-
}
|
|
993
|
-
return endOffset;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
function compareVersionTokenToTarget(version, targetVersion) {
|
|
997
|
-
let leftOffset = 0;
|
|
998
|
-
let rightOffset = 0;
|
|
999
|
-
|
|
1000
|
-
while (true) {
|
|
1001
|
-
const leftCode = versionCharacterCode(version, leftOffset);
|
|
1002
|
-
const rightCode = versionCharacterCode(targetVersion, rightOffset);
|
|
1003
|
-
if (leftCode === 0) {
|
|
1004
|
-
return leftCode - rightCode;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// The fixed target has no digit run beginning with zero. Nonzero runs
|
|
1008
|
-
// compare by magnitude; a leading-zero requirement stays in byte order.
|
|
1009
|
-
if (
|
|
1010
|
-
leftCode !== 48 &&
|
|
1011
|
-
rightCode !== 48 &&
|
|
1012
|
-
isVersionDigit(leftCode) &&
|
|
1013
|
-
isVersionDigit(rightCode)
|
|
1014
|
-
) {
|
|
1015
|
-
const leftEnd = digitRunEnd(version, leftOffset);
|
|
1016
|
-
const rightEnd = digitRunEnd(targetVersion, rightOffset);
|
|
1017
|
-
const lengthDifference = leftEnd - leftOffset - (rightEnd - rightOffset);
|
|
1018
|
-
if (lengthDifference !== 0) {
|
|
1019
|
-
return lengthDifference;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
const leftDigits = version.slice(leftOffset, leftEnd);
|
|
1023
|
-
const rightDigits = targetVersion.slice(rightOffset, rightEnd);
|
|
1024
|
-
if (leftDigits !== rightDigits) {
|
|
1025
|
-
return leftDigits < rightDigits ? -1 : 1;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
leftOffset = leftEnd;
|
|
1029
|
-
rightOffset = rightEnd;
|
|
1030
|
-
continue;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
const difference = leftCode - rightCode;
|
|
1034
|
-
if (difference !== 0) {
|
|
1035
|
-
return difference;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
leftOffset += 1;
|
|
1039
|
-
rightOffset += 1;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
export const gnuSedTargetVersion = "4.10";
|
|
1044
|
-
|
|
1045
|
-
export function isGnuSedVersionSupported(version) {
|
|
1046
|
-
return compareVersionTokenToTarget(version, gnuSedTargetVersion) <= 0;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
export function scanVersionArgumentSyntax(
|
|
1050
|
-
text,
|
|
1051
|
-
commandOffset,
|
|
1052
|
-
lineEnd,
|
|
1053
|
-
options = defaultSyntaxProfile,
|
|
1054
|
-
) {
|
|
1055
|
-
const argument = scanLabelArgumentSyntax(
|
|
1056
|
-
text,
|
|
1057
|
-
commandOffset,
|
|
1058
|
-
lineEnd,
|
|
1059
|
-
options,
|
|
1060
|
-
);
|
|
1061
|
-
const comparedVersion = argument.hasLabel ? argument.name : "4.0";
|
|
1062
|
-
|
|
1063
|
-
return {
|
|
1064
|
-
comparedVersion,
|
|
1065
|
-
endOffset: argument.labelEndOffset,
|
|
1066
|
-
hasArgument: argument.hasLabel,
|
|
1067
|
-
isSupported: isGnuSedVersionSupported(comparedVersion),
|
|
1068
|
-
nextCommandBoundary: argument.nextCommandBoundary,
|
|
1069
|
-
nextCommandOffset: argument.nextCommandOffset,
|
|
1070
|
-
startOffset: argument.labelStartOffset,
|
|
1071
|
-
terminator: argument.terminator,
|
|
1072
|
-
value: argument.hasLabel ? argument.name : null,
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
function physicalNewlineWidthAt(text, offset, end) {
|
|
1077
|
-
if (text[offset] === "\n") {
|
|
1078
|
-
return 1;
|
|
1079
|
-
}
|
|
1080
|
-
if (text[offset] === "\r" && offset + 1 < end && text[offset + 1] === "\n") {
|
|
1081
|
-
return 2;
|
|
1082
|
-
}
|
|
1083
|
-
return 0;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
function scanRegexpSyntax(text, offset, end, delimiter, syntaxProfile) {
|
|
1087
|
-
const policy = syntaxPolicyFor(syntaxProfile);
|
|
1088
|
-
let cursor = offset;
|
|
1089
|
-
let inBracketExpression = false;
|
|
1090
|
-
let atFirstBracketCharacter = false;
|
|
1091
|
-
let canNegateBracketExpression = false;
|
|
1092
|
-
let bracketElementMarker = null;
|
|
1093
|
-
let bracketElementCanClose = false;
|
|
1094
|
-
let recoveryBackslashCount = 0;
|
|
1095
|
-
let possibleClosingOffset = null;
|
|
1096
|
-
|
|
1097
|
-
while (cursor < end) {
|
|
1098
|
-
const character = characterAt(text, cursor);
|
|
1099
|
-
if (character === null) {
|
|
1100
|
-
break;
|
|
1101
|
-
}
|
|
1102
|
-
const physicalNewlineWidth = physicalNewlineWidthAt(text, cursor, end);
|
|
1103
|
-
if (physicalNewlineWidth > 0) {
|
|
1104
|
-
return {
|
|
1105
|
-
closingOffset: null,
|
|
1106
|
-
failureOffset: cursor,
|
|
1107
|
-
hasUnclosedBracketExpression:
|
|
1108
|
-
inBracketExpression || bracketElementMarker !== null,
|
|
1109
|
-
possibleClosingOffset,
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
if (inBracketExpression) {
|
|
1114
|
-
// If the unfinished `[` is treated as ordinary, an odd backslash run
|
|
1115
|
-
// still prevents the following delimiter from ending the RE.
|
|
1116
|
-
const isEscapedOutsideBracket = recoveryBackslashCount % 2 === 1;
|
|
1117
|
-
|
|
1118
|
-
if (bracketElementMarker !== null) {
|
|
1119
|
-
if (character.value === "[") {
|
|
1120
|
-
cursor += character.width;
|
|
1121
|
-
continue;
|
|
1122
|
-
}
|
|
1123
|
-
if (character.value === bracketElementMarker) {
|
|
1124
|
-
bracketElementCanClose = !bracketElementCanClose;
|
|
1125
|
-
cursor += character.width;
|
|
1126
|
-
continue;
|
|
1127
|
-
}
|
|
1128
|
-
if (character.value === "]" && bracketElementCanClose) {
|
|
1129
|
-
bracketElementMarker = null;
|
|
1130
|
-
bracketElementCanClose = false;
|
|
1131
|
-
recoveryBackslashCount = 0;
|
|
1132
|
-
atFirstBracketCharacter = false;
|
|
1133
|
-
canNegateBracketExpression = false;
|
|
1134
|
-
cursor += character.width;
|
|
1135
|
-
continue;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
bracketElementCanClose = false;
|
|
1139
|
-
cursor += character.width;
|
|
1140
|
-
continue;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
if (
|
|
1144
|
-
character.value === "[" &&
|
|
1145
|
-
".:=".includes(text[cursor + character.width])
|
|
1146
|
-
) {
|
|
1147
|
-
bracketElementMarker = text[cursor + character.width];
|
|
1148
|
-
bracketElementCanClose = false;
|
|
1149
|
-
recoveryBackslashCount = 0;
|
|
1150
|
-
cursor += character.width + 1;
|
|
1151
|
-
continue;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
if (canNegateBracketExpression && character.value === "^") {
|
|
1155
|
-
canNegateBracketExpression = false;
|
|
1156
|
-
recoveryBackslashCount = 0;
|
|
1157
|
-
cursor += character.width;
|
|
1158
|
-
continue;
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
if (atFirstBracketCharacter && character.value === "]") {
|
|
1162
|
-
atFirstBracketCharacter = false;
|
|
1163
|
-
canNegateBracketExpression = false;
|
|
1164
|
-
recoveryBackslashCount = 0;
|
|
1165
|
-
cursor += character.width;
|
|
1166
|
-
continue;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
if (
|
|
1170
|
-
delimiter !== null &&
|
|
1171
|
-
!isEscapedOutsideBracket &&
|
|
1172
|
-
possibleClosingOffset === null &&
|
|
1173
|
-
character.value === delimiter.value
|
|
1174
|
-
) {
|
|
1175
|
-
possibleClosingOffset = cursor;
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
if (character.value === "]") {
|
|
1179
|
-
inBracketExpression = false;
|
|
1180
|
-
bracketElementCanClose = false;
|
|
1181
|
-
recoveryBackslashCount = 0;
|
|
1182
|
-
possibleClosingOffset = null;
|
|
1183
|
-
cursor += character.width;
|
|
1184
|
-
continue;
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
atFirstBracketCharacter = false;
|
|
1188
|
-
canNegateBracketExpression = false;
|
|
1189
|
-
recoveryBackslashCount =
|
|
1190
|
-
character.value === "\\" ? recoveryBackslashCount + 1 : 0;
|
|
1191
|
-
cursor += character.width;
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
if (delimiter !== null && character.value === delimiter.value) {
|
|
1196
|
-
return {
|
|
1197
|
-
closingOffset: cursor,
|
|
1198
|
-
hasUnclosedBracketExpression: false,
|
|
1199
|
-
possibleClosingOffset: null,
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
if (character.value === "\\") {
|
|
1204
|
-
const escapedOffset = cursor + character.width;
|
|
1205
|
-
if (escapedOffset >= end) {
|
|
1206
|
-
break;
|
|
1207
|
-
}
|
|
1208
|
-
const escaped = characterAt(text, escapedOffset);
|
|
1209
|
-
if (escaped === null) {
|
|
1210
|
-
break;
|
|
1211
|
-
}
|
|
1212
|
-
const escapedNewlineWidth = escaped.value === "\n" ? 1 : 0;
|
|
1213
|
-
if (escapedNewlineWidth > 0) {
|
|
1214
|
-
if (!policy.regexp.escapedPhysicalNewlines) {
|
|
1215
|
-
return {
|
|
1216
|
-
closingOffset: null,
|
|
1217
|
-
failureOffset: escapedOffset,
|
|
1218
|
-
hasUnclosedBracketExpression: false,
|
|
1219
|
-
possibleClosingOffset: null,
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
const continuationEnd = escapedOffset + escapedNewlineWidth;
|
|
1224
|
-
cursor = continuationEnd;
|
|
1225
|
-
continue;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
if (
|
|
1229
|
-
syntaxProfile.dialect === "posix" &&
|
|
1230
|
-
delimiter?.value === "[" &&
|
|
1231
|
-
escaped.value === delimiter.value
|
|
1232
|
-
) {
|
|
1233
|
-
// POSIX leaves `\[` unspecified when `[` is the delimiter: it may
|
|
1234
|
-
// stay special and begin a bracket expression, changing the boundary.
|
|
1235
|
-
return {
|
|
1236
|
-
closingOffset: null,
|
|
1237
|
-
hasUnclosedBracketExpression: false,
|
|
1238
|
-
isDelimiterInterpretationUnspecified: true,
|
|
1239
|
-
possibleClosingOffset: null,
|
|
1240
|
-
};
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
if (syntaxProfile.dialect === "gnu" && escaped.value === "c") {
|
|
1244
|
-
const payloadOffset = escapedOffset + escaped.width;
|
|
1245
|
-
if (payloadOffset >= end) {
|
|
1246
|
-
cursor = payloadOffset;
|
|
1247
|
-
continue;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
const payload = characterAt(text, payloadOffset);
|
|
1251
|
-
if (payload === null) {
|
|
1252
|
-
break;
|
|
1253
|
-
}
|
|
1254
|
-
const payloadNewlineWidth = payload.value === "\n" ? 1 : 0;
|
|
1255
|
-
const controlEscapeEnd =
|
|
1256
|
-
payloadNewlineWidth > 0
|
|
1257
|
-
? payloadOffset + payloadNewlineWidth
|
|
1258
|
-
: payloadOffset + payload.width;
|
|
1259
|
-
cursor = controlEscapeEnd;
|
|
1260
|
-
continue;
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
const escapeEnd = escapedOffset + escaped.width;
|
|
1264
|
-
cursor = escapeEnd;
|
|
1265
|
-
continue;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
if (character.value === "[") {
|
|
1269
|
-
inBracketExpression = true;
|
|
1270
|
-
atFirstBracketCharacter = true;
|
|
1271
|
-
recoveryBackslashCount = 0;
|
|
1272
|
-
canNegateBracketExpression = true;
|
|
1273
|
-
cursor += character.width;
|
|
1274
|
-
continue;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
cursor += character.width;
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
return {
|
|
1281
|
-
closingOffset: null,
|
|
1282
|
-
hasUnclosedBracketExpression:
|
|
1283
|
-
inBracketExpression || bracketElementMarker !== null,
|
|
1284
|
-
possibleClosingOffset,
|
|
1285
|
-
};
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
export function scanRegexDelimiter(
|
|
1289
|
-
text,
|
|
1290
|
-
offset,
|
|
1291
|
-
end,
|
|
1292
|
-
delimiter,
|
|
1293
|
-
syntaxProfile = defaultSyntaxProfile,
|
|
1294
|
-
) {
|
|
1295
|
-
return scanRegexpSyntax(text, offset, end, delimiter, syntaxProfile);
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
export function scanAddressSyntax(
|
|
1299
|
-
text,
|
|
1300
|
-
offset,
|
|
1301
|
-
end,
|
|
1302
|
-
syntaxProfile = defaultSyntaxProfile,
|
|
1303
|
-
) {
|
|
1304
|
-
const profile = requireSyntaxProfile(syntaxProfile);
|
|
1305
|
-
const policy = syntaxPolicyFor(profile);
|
|
1306
|
-
const first = text[offset];
|
|
1307
|
-
|
|
1308
|
-
if (first >= "0" && first <= "9") {
|
|
1309
|
-
const cursor = scanDecimalEnd(text, offset + 1, end);
|
|
1310
|
-
|
|
1311
|
-
const firstValue = text.slice(offset, cursor);
|
|
1312
|
-
if (policy.address.numericStep) {
|
|
1313
|
-
const operatorOffset = skipBlanks(text, cursor, end);
|
|
1314
|
-
if (text[operatorOffset] === "~") {
|
|
1315
|
-
const stepStartOffset = skipBlanks(text, operatorOffset + 1, end);
|
|
1316
|
-
const stepEndOffset = scanDecimalEnd(text, stepStartOffset, end);
|
|
1317
|
-
|
|
1318
|
-
return {
|
|
1319
|
-
kind: "valid",
|
|
1320
|
-
addressKind: "line-number-step",
|
|
1321
|
-
endOffset:
|
|
1322
|
-
stepEndOffset > stepStartOffset
|
|
1323
|
-
? stepEndOffset
|
|
1324
|
-
: operatorOffset + 1,
|
|
1325
|
-
isUndefined: false,
|
|
1326
|
-
modifiers: noAddressModifiers,
|
|
1327
|
-
operatorOffset,
|
|
1328
|
-
startOffset: offset,
|
|
1329
|
-
stepEndOffset,
|
|
1330
|
-
stepStartOffset,
|
|
1331
|
-
value: {
|
|
1332
|
-
first: firstValue,
|
|
1333
|
-
// GNU sed 4.10 treats an omitted step as zero when a command
|
|
1334
|
-
// follows. Keep null so callers can still distinguish the source.
|
|
1335
|
-
step:
|
|
1336
|
-
stepEndOffset > stepStartOffset
|
|
1337
|
-
? text.slice(stepStartOffset, stepEndOffset)
|
|
1338
|
-
: null,
|
|
1339
|
-
},
|
|
1340
|
-
};
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
return {
|
|
1345
|
-
kind: "valid",
|
|
1346
|
-
addressKind: "line-number",
|
|
1347
|
-
endOffset: cursor,
|
|
1348
|
-
isUndefined: false,
|
|
1349
|
-
modifiers: noAddressModifiers,
|
|
1350
|
-
startOffset: offset,
|
|
1351
|
-
value: firstValue,
|
|
1352
|
-
};
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
if (policy.address.rangeOffsets && (first === "+" || first === "~")) {
|
|
1356
|
-
const valueStartOffset = skipBlanks(text, offset + 1, end);
|
|
1357
|
-
const valueEndOffset = scanDecimalEnd(text, valueStartOffset, end);
|
|
1358
|
-
|
|
1359
|
-
return {
|
|
1360
|
-
kind: "valid",
|
|
1361
|
-
addressKind:
|
|
1362
|
-
first === "+" ? "relative-line-count" : "relative-line-multiple",
|
|
1363
|
-
endOffset:
|
|
1364
|
-
valueEndOffset > valueStartOffset ? valueEndOffset : offset + 1,
|
|
1365
|
-
isUndefined: false,
|
|
1366
|
-
modifiers: noAddressModifiers,
|
|
1367
|
-
operatorOffset: offset,
|
|
1368
|
-
startOffset: offset,
|
|
1369
|
-
// A missing value has GNU sed's null-address behavior. Preserve the
|
|
1370
|
-
// omission instead of manufacturing a source value of "0".
|
|
1371
|
-
value:
|
|
1372
|
-
valueEndOffset > valueStartOffset
|
|
1373
|
-
? text.slice(valueStartOffset, valueEndOffset)
|
|
1374
|
-
: null,
|
|
1375
|
-
valueEndOffset,
|
|
1376
|
-
valueStartOffset,
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
if (first === "$") {
|
|
1381
|
-
return {
|
|
1382
|
-
kind: "valid",
|
|
1383
|
-
addressKind: "last-line",
|
|
1384
|
-
endOffset: offset + 1,
|
|
1385
|
-
isUndefined: false,
|
|
1386
|
-
modifiers: noAddressModifiers,
|
|
1387
|
-
startOffset: offset,
|
|
1388
|
-
value: "$",
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
let delimiterOffset;
|
|
1393
|
-
if (first === "/") {
|
|
1394
|
-
delimiterOffset = offset;
|
|
1395
|
-
} else if (first === "\\") {
|
|
1396
|
-
delimiterOffset = offset + 1;
|
|
1397
|
-
} else {
|
|
1398
|
-
return { kind: "none" };
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
if (delimiterOffset >= end) {
|
|
1402
|
-
return {
|
|
1403
|
-
kind: "invalid",
|
|
1404
|
-
delimiterOffset,
|
|
1405
|
-
reason: "missing-delimiter",
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
const delimiter = characterAt(text, delimiterOffset);
|
|
1410
|
-
if (
|
|
1411
|
-
delimiter === null ||
|
|
1412
|
-
(delimiter.value === "\\" && !policy.address.backslashRegexpDelimiter) ||
|
|
1413
|
-
delimiter.value === "\n"
|
|
1414
|
-
) {
|
|
1415
|
-
return {
|
|
1416
|
-
kind: "invalid",
|
|
1417
|
-
delimiter,
|
|
1418
|
-
delimiterOffset,
|
|
1419
|
-
reason: "invalid-delimiter",
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
const result = scanRegexDelimiter(
|
|
1424
|
-
text,
|
|
1425
|
-
delimiterOffset + delimiter.width,
|
|
1426
|
-
end,
|
|
1427
|
-
delimiter,
|
|
1428
|
-
syntaxProfile,
|
|
1429
|
-
);
|
|
1430
|
-
const closingOffset =
|
|
1431
|
-
result.closingOffset ??
|
|
1432
|
-
(profile.dialect === "posix" && result.hasUnclosedBracketExpression
|
|
1433
|
-
? result.possibleClosingOffset
|
|
1434
|
-
: null);
|
|
1435
|
-
|
|
1436
|
-
if (closingOffset === null) {
|
|
1437
|
-
return profile.dialect === "posix" &&
|
|
1438
|
-
(result.hasUnclosedBracketExpression ||
|
|
1439
|
-
result.isDelimiterInterpretationUnspecified)
|
|
1440
|
-
? {
|
|
1441
|
-
endOffset: result.failureOffset ?? end,
|
|
1442
|
-
kind: "indeterminate",
|
|
1443
|
-
delimiter,
|
|
1444
|
-
delimiterOffset,
|
|
1445
|
-
reason: result.isDelimiterInterpretationUnspecified
|
|
1446
|
-
? "unspecified-escaped-delimiter"
|
|
1447
|
-
: "undefined-bracket-expression",
|
|
1448
|
-
}
|
|
1449
|
-
: {
|
|
1450
|
-
endOffset: result.failureOffset ?? end,
|
|
1451
|
-
kind: "invalid",
|
|
1452
|
-
delimiter,
|
|
1453
|
-
delimiterOffset,
|
|
1454
|
-
reason: "unterminated",
|
|
1455
|
-
};
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
const patternStartOffset = delimiterOffset + delimiter.width;
|
|
1459
|
-
let addressEndOffset = closingOffset + delimiter.width;
|
|
1460
|
-
const modifiers = [];
|
|
1461
|
-
|
|
1462
|
-
if (policy.address.regexpModifiers !== "") {
|
|
1463
|
-
let modifierSearchOffset = addressEndOffset;
|
|
1464
|
-
while (modifierSearchOffset < end) {
|
|
1465
|
-
const modifierOffset = skipBlanks(text, modifierSearchOffset, end);
|
|
1466
|
-
const modifier = text[modifierOffset];
|
|
1467
|
-
if (!policy.address.regexpModifiers.includes(modifier)) {
|
|
1468
|
-
break;
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
modifiers.push({
|
|
1472
|
-
endOffset: modifierOffset + 1,
|
|
1473
|
-
startOffset: modifierOffset,
|
|
1474
|
-
value: modifier,
|
|
1475
|
-
});
|
|
1476
|
-
addressEndOffset = modifierOffset + 1;
|
|
1477
|
-
modifierSearchOffset = addressEndOffset;
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
return {
|
|
1482
|
-
kind: "valid",
|
|
1483
|
-
addressKind: "regular-expression",
|
|
1484
|
-
delimiter,
|
|
1485
|
-
delimiterOffset,
|
|
1486
|
-
endOffset: addressEndOffset,
|
|
1487
|
-
isUndefined: result.closingOffset === null,
|
|
1488
|
-
modifiers:
|
|
1489
|
-
modifiers.length === 0 ? noAddressModifiers : Object.freeze(modifiers),
|
|
1490
|
-
patternEndOffset: closingOffset,
|
|
1491
|
-
patternStartOffset,
|
|
1492
|
-
startOffset: offset,
|
|
1493
|
-
value: text.slice(patternStartOffset, closingOffset),
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
function regexpEscapeKind(value, syntaxProfile) {
|
|
1498
|
-
if (value >= "1" && value <= "9") {
|
|
1499
|
-
return "back-reference";
|
|
1500
|
-
}
|
|
1501
|
-
if (syntaxProfile.regexpMode === "bre" && value === "(") {
|
|
1502
|
-
return "subexpression-open";
|
|
1503
|
-
}
|
|
1504
|
-
if (syntaxProfile.regexpMode === "bre" && value === ")") {
|
|
1505
|
-
return "subexpression-close";
|
|
1506
|
-
}
|
|
1507
|
-
if (
|
|
1508
|
-
syntaxProfile.dialect === "gnu" &&
|
|
1509
|
-
syntaxProfile.regexpMode === "bre" &&
|
|
1510
|
-
["+", "?", "|"].includes(value)
|
|
1511
|
-
) {
|
|
1512
|
-
return "operator";
|
|
1513
|
-
}
|
|
1514
|
-
if (syntaxProfile.dialect === "gnu" && ["`", "'"].includes(value)) {
|
|
1515
|
-
return "anchor";
|
|
1516
|
-
}
|
|
1517
|
-
if (
|
|
1518
|
-
syntaxProfile.dialect === "gnu" &&
|
|
1519
|
-
["b", "B", "<", ">", "w", "W", "s", "S"].includes(value)
|
|
1520
|
-
) {
|
|
1521
|
-
return "word-boundary";
|
|
1522
|
-
}
|
|
1523
|
-
return "escaped-character";
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
function regexpUnit(value, startOffset, endOffset, origin = null) {
|
|
1527
|
-
return { endOffset, origin, startOffset, value };
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
function numericEscapeValue(text, digitOffset, end, base) {
|
|
1531
|
-
const maximumDigits = base === 16 ? 2 : 3;
|
|
1532
|
-
let cursor = digitOffset;
|
|
1533
|
-
let value = 0;
|
|
1534
|
-
let digits = 0;
|
|
1535
|
-
while (cursor < end && digits < maximumDigits) {
|
|
1536
|
-
const code = text.charCodeAt(cursor);
|
|
1537
|
-
const digit =
|
|
1538
|
-
code >= 48 && code <= 57
|
|
1539
|
-
? code - 48
|
|
1540
|
-
: code >= 65 && code <= 70
|
|
1541
|
-
? code - 55
|
|
1542
|
-
: code >= 97 && code <= 102
|
|
1543
|
-
? code - 87
|
|
1544
|
-
: -1;
|
|
1545
|
-
if (digit < 0 || digit >= base) {
|
|
1546
|
-
break;
|
|
1547
|
-
}
|
|
1548
|
-
value = value * base + digit;
|
|
1549
|
-
digits += 1;
|
|
1550
|
-
cursor += 1;
|
|
1551
|
-
}
|
|
1552
|
-
return { cursor, digits, value };
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
const gnuSimpleRegexpEscapes = new Map([
|
|
1556
|
-
["a", "\u0007"],
|
|
1557
|
-
["f", "\f"],
|
|
1558
|
-
["n", "\n"],
|
|
1559
|
-
["r", "\r"],
|
|
1560
|
-
["t", "\t"],
|
|
1561
|
-
["v", "\u000b"],
|
|
1562
|
-
]);
|
|
1563
|
-
|
|
1564
|
-
function gnuNormalizedRegexpEscape(text, escapedOffset, end) {
|
|
1565
|
-
const escaped = characterAt(text, escapedOffset);
|
|
1566
|
-
if (escaped === null || escapedOffset >= end) {
|
|
1567
|
-
return null;
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
const simpleValue = gnuSimpleRegexpEscapes.get(escaped.value);
|
|
1571
|
-
if (simpleValue !== undefined) {
|
|
1572
|
-
return {
|
|
1573
|
-
endOffset: escapedOffset + escaped.width,
|
|
1574
|
-
value: simpleValue,
|
|
1575
|
-
};
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
if (escaped.value === "c") {
|
|
1579
|
-
const payloadOffset = escapedOffset + escaped.width;
|
|
1580
|
-
const payload = characterAt(text, payloadOffset);
|
|
1581
|
-
if (payload !== null && payloadOffset < end) {
|
|
1582
|
-
if (payload.value === "\\") {
|
|
1583
|
-
const recursiveOffset = payloadOffset + payload.width;
|
|
1584
|
-
const recursive = characterAt(text, recursiveOffset);
|
|
1585
|
-
if (
|
|
1586
|
-
recursive === null ||
|
|
1587
|
-
recursiveOffset >= end ||
|
|
1588
|
-
recursive.value !== "\\"
|
|
1589
|
-
) {
|
|
1590
|
-
return {
|
|
1591
|
-
endOffset:
|
|
1592
|
-
recursive === null || recursiveOffset >= end
|
|
1593
|
-
? recursiveOffset
|
|
1594
|
-
: recursiveOffset + recursive.width,
|
|
1595
|
-
problem: "recursive-control-escape",
|
|
1596
|
-
value: String.fromCharCode(
|
|
1597
|
-
payload.value.toUpperCase().charCodeAt(0) ^ 0x40,
|
|
1598
|
-
),
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
return {
|
|
1603
|
-
endOffset: recursiveOffset + recursive.width,
|
|
1604
|
-
value: String.fromCharCode(
|
|
1605
|
-
payload.value.toUpperCase().charCodeAt(0) ^ 0x40,
|
|
1606
|
-
),
|
|
1607
|
-
};
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
return {
|
|
1611
|
-
endOffset: payloadOffset + payload.width,
|
|
1612
|
-
value: String.fromCharCode(
|
|
1613
|
-
payload.value.toUpperCase().charCodeAt(0) ^ 0x40,
|
|
1614
|
-
),
|
|
1615
|
-
};
|
|
1616
|
-
}
|
|
1617
|
-
return null;
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
const base =
|
|
1621
|
-
escaped.value === "d"
|
|
1622
|
-
? 10
|
|
1623
|
-
: escaped.value === "o"
|
|
1624
|
-
? 8
|
|
1625
|
-
: escaped.value === "x"
|
|
1626
|
-
? 16
|
|
1627
|
-
: null;
|
|
1628
|
-
if (base === null) {
|
|
1629
|
-
return null;
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
const numeric = numericEscapeValue(
|
|
1633
|
-
text,
|
|
1634
|
-
escapedOffset + escaped.width,
|
|
1635
|
-
end,
|
|
1636
|
-
base,
|
|
1637
|
-
);
|
|
1638
|
-
return {
|
|
1639
|
-
endOffset:
|
|
1640
|
-
numeric.digits === 0 ? escapedOffset + escaped.width : numeric.cursor,
|
|
1641
|
-
value:
|
|
1642
|
-
numeric.digits === 0
|
|
1643
|
-
? escaped.value
|
|
1644
|
-
: String.fromCharCode(numeric.value & 0xff),
|
|
1645
|
-
};
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
function createRawRegexpBracketState() {
|
|
1649
|
-
return {
|
|
1650
|
-
bracketElementCanClose: false,
|
|
1651
|
-
bracketElementMarker: null,
|
|
1652
|
-
canNegate: false,
|
|
1653
|
-
firstCharacter: false,
|
|
1654
|
-
inBracket: false,
|
|
1655
|
-
skipBracketElementOpener: false,
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
function advanceRawRegexpBracketState(text, offset, end, state) {
|
|
1660
|
-
let cursor = offset;
|
|
1661
|
-
while (cursor < end) {
|
|
1662
|
-
const character = characterAt(text, cursor);
|
|
1663
|
-
if (character === null) {
|
|
1664
|
-
break;
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
if (state.skipBracketElementOpener) {
|
|
1668
|
-
state.skipBracketElementOpener = false;
|
|
1669
|
-
cursor += character.width;
|
|
1670
|
-
continue;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
if (!state.inBracket) {
|
|
1674
|
-
if (character.value === "[") {
|
|
1675
|
-
state.inBracket = true;
|
|
1676
|
-
state.firstCharacter = true;
|
|
1677
|
-
state.canNegate = true;
|
|
1678
|
-
}
|
|
1679
|
-
cursor += character.width;
|
|
1680
|
-
continue;
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
if (state.bracketElementMarker !== null) {
|
|
1684
|
-
if (character.value === "[") {
|
|
1685
|
-
// GNU sed's raw delimiter scanner preserves the "can close" phase
|
|
1686
|
-
// across a nested left bracket inside a bracket element.
|
|
1687
|
-
} else if (character.value === state.bracketElementMarker) {
|
|
1688
|
-
state.bracketElementCanClose = !state.bracketElementCanClose;
|
|
1689
|
-
} else if (character.value === "]" && state.bracketElementCanClose) {
|
|
1690
|
-
state.bracketElementCanClose = false;
|
|
1691
|
-
state.bracketElementMarker = null;
|
|
1692
|
-
state.firstCharacter = false;
|
|
1693
|
-
state.canNegate = false;
|
|
1694
|
-
} else {
|
|
1695
|
-
state.bracketElementCanClose = false;
|
|
1696
|
-
}
|
|
1697
|
-
cursor += character.width;
|
|
1698
|
-
continue;
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
const marker = text[cursor + character.width];
|
|
1702
|
-
if (character.value === "[" && ".:=".includes(marker)) {
|
|
1703
|
-
state.bracketElementCanClose = false;
|
|
1704
|
-
state.bracketElementMarker = marker;
|
|
1705
|
-
state.skipBracketElementOpener = true;
|
|
1706
|
-
state.firstCharacter = false;
|
|
1707
|
-
state.canNegate = false;
|
|
1708
|
-
cursor += character.width;
|
|
1709
|
-
continue;
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
if (state.canNegate && character.value === "^") {
|
|
1713
|
-
state.canNegate = false;
|
|
1714
|
-
} else if (state.firstCharacter && character.value === "]") {
|
|
1715
|
-
state.firstCharacter = false;
|
|
1716
|
-
state.canNegate = false;
|
|
1717
|
-
} else if (character.value === "]") {
|
|
1718
|
-
state.inBracket = false;
|
|
1719
|
-
state.firstCharacter = false;
|
|
1720
|
-
state.canNegate = false;
|
|
1721
|
-
} else {
|
|
1722
|
-
state.firstCharacter = false;
|
|
1723
|
-
state.canNegate = false;
|
|
1724
|
-
}
|
|
1725
|
-
cursor += character.width;
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
function* regexpUnits(text, offset, end, delimiter, syntaxProfile) {
|
|
1730
|
-
const rawBracketState = createRawRegexpBracketState();
|
|
1731
|
-
let cursor = offset;
|
|
1732
|
-
|
|
1733
|
-
while (cursor < end) {
|
|
1734
|
-
const character = characterAt(text, cursor);
|
|
1735
|
-
if (character === null) {
|
|
1736
|
-
break;
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
if (character.value !== "\\") {
|
|
1740
|
-
const characterEnd = cursor + character.width;
|
|
1741
|
-
yield regexpUnit(character.value, cursor, characterEnd);
|
|
1742
|
-
advanceRawRegexpBracketState(text, cursor, characterEnd, rawBracketState);
|
|
1743
|
-
cursor = characterEnd;
|
|
1744
|
-
continue;
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
const wasInRawBracket = rawBracketState.inBracket;
|
|
1748
|
-
const escapedOffset = cursor + character.width;
|
|
1749
|
-
if (escapedOffset >= end) {
|
|
1750
|
-
yield regexpUnit(character.value, cursor, escapedOffset);
|
|
1751
|
-
if (wasInRawBracket) {
|
|
1752
|
-
advanceRawRegexpBracketState(
|
|
1753
|
-
text,
|
|
1754
|
-
cursor,
|
|
1755
|
-
escapedOffset,
|
|
1756
|
-
rawBracketState,
|
|
1757
|
-
);
|
|
1758
|
-
}
|
|
1759
|
-
break;
|
|
1760
|
-
}
|
|
1761
|
-
const escaped = characterAt(text, escapedOffset);
|
|
1762
|
-
if (escaped === null) {
|
|
1763
|
-
break;
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
const escapedEnd = escapedOffset + escaped.width;
|
|
1767
|
-
if (escaped.value === "\n") {
|
|
1768
|
-
yield regexpUnit("\n", cursor, escapedEnd);
|
|
1769
|
-
if (wasInRawBracket) {
|
|
1770
|
-
advanceRawRegexpBracketState(text, cursor, escapedEnd, rawBracketState);
|
|
1771
|
-
}
|
|
1772
|
-
cursor = escapedEnd;
|
|
1773
|
-
continue;
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
if (
|
|
1777
|
-
!wasInRawBracket &&
|
|
1778
|
-
delimiter !== null &&
|
|
1779
|
-
escaped.value === delimiter.value
|
|
1780
|
-
) {
|
|
1781
|
-
yield regexpUnit(escaped.value, cursor, escapedEnd);
|
|
1782
|
-
cursor = escapedEnd;
|
|
1783
|
-
continue;
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
if (syntaxProfile.dialect === "gnu") {
|
|
1787
|
-
const normalized = gnuNormalizedRegexpEscape(text, escapedOffset, end);
|
|
1788
|
-
if (normalized !== null) {
|
|
1789
|
-
yield regexpUnit(
|
|
1790
|
-
normalized.value,
|
|
1791
|
-
cursor,
|
|
1792
|
-
normalized.endOffset,
|
|
1793
|
-
normalized.problem === "recursive-control-escape"
|
|
1794
|
-
? "invalid-control-escape"
|
|
1795
|
-
: "character-escape",
|
|
1796
|
-
);
|
|
1797
|
-
if (wasInRawBracket) {
|
|
1798
|
-
advanceRawRegexpBracketState(
|
|
1799
|
-
text,
|
|
1800
|
-
cursor,
|
|
1801
|
-
normalized.endOffset,
|
|
1802
|
-
rawBracketState,
|
|
1803
|
-
);
|
|
1804
|
-
}
|
|
1805
|
-
cursor = normalized.endOffset;
|
|
1806
|
-
continue;
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
yield regexpUnit("\\", cursor, escapedOffset);
|
|
1811
|
-
yield regexpUnit(escaped.value, escapedOffset, escapedEnd);
|
|
1812
|
-
if (wasInRawBracket) {
|
|
1813
|
-
advanceRawRegexpBracketState(text, cursor, escapedEnd, rawBracketState);
|
|
1814
|
-
}
|
|
1815
|
-
cursor = escapedEnd;
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
function advanceRegexpUnitStream(stream, count = 1) {
|
|
1820
|
-
for (let advanced = 0; advanced < count; advanced += 1) {
|
|
1821
|
-
stream.current = stream.next;
|
|
1822
|
-
stream.next = stream.iterator.next();
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
export function* iterateRegexpTokens(
|
|
1827
|
-
text,
|
|
1828
|
-
offset,
|
|
1829
|
-
end,
|
|
1830
|
-
delimiter = null,
|
|
1831
|
-
syntaxProfile = defaultSyntaxProfile,
|
|
1832
|
-
) {
|
|
1833
|
-
const profile = requireSyntaxProfile(syntaxProfile);
|
|
1834
|
-
const iterator = regexpUnits(text, offset, end, delimiter, profile);
|
|
1835
|
-
const stream = {
|
|
1836
|
-
current: iterator.next(),
|
|
1837
|
-
iterator,
|
|
1838
|
-
next: iterator.next(),
|
|
1839
|
-
};
|
|
1840
|
-
let inBracket = false;
|
|
1841
|
-
let firstBracketCharacter = false;
|
|
1842
|
-
let canNegateBracket = false;
|
|
1843
|
-
let bracketElementMarker = null;
|
|
1844
|
-
let bracketStartOffset = null;
|
|
1845
|
-
|
|
1846
|
-
while (!stream.current.done) {
|
|
1847
|
-
const unit = stream.current.value;
|
|
1848
|
-
const nextUnit = stream.next.done ? null : stream.next.value;
|
|
1849
|
-
|
|
1850
|
-
if (unit.origin === "invalid-control-escape") {
|
|
1851
|
-
yield {
|
|
1852
|
-
endOffset: unit.endOffset,
|
|
1853
|
-
escaped: true,
|
|
1854
|
-
kind: "invalid-control-escape",
|
|
1855
|
-
offset: unit.startOffset,
|
|
1856
|
-
origin: unit.origin,
|
|
1857
|
-
value: "c",
|
|
1858
|
-
width: unit.endOffset - unit.startOffset,
|
|
1859
|
-
};
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
if (inBracket) {
|
|
1863
|
-
if (bracketElementMarker !== null) {
|
|
1864
|
-
if (unit.value === bracketElementMarker && nextUnit?.value === "]") {
|
|
1865
|
-
bracketElementMarker = null;
|
|
1866
|
-
firstBracketCharacter = false;
|
|
1867
|
-
canNegateBracket = false;
|
|
1868
|
-
advanceRegexpUnitStream(stream, 2);
|
|
1869
|
-
} else {
|
|
1870
|
-
advanceRegexpUnitStream(stream);
|
|
1871
|
-
}
|
|
1872
|
-
continue;
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
if (unit.value === "[" && ".:=".includes(nextUnit?.value)) {
|
|
1876
|
-
bracketElementMarker = nextUnit.value;
|
|
1877
|
-
advanceRegexpUnitStream(stream, 2);
|
|
1878
|
-
continue;
|
|
1879
|
-
}
|
|
1880
|
-
if (
|
|
1881
|
-
canNegateBracket &&
|
|
1882
|
-
unit.value === "^" &&
|
|
1883
|
-
bracketElementMarker === null
|
|
1884
|
-
) {
|
|
1885
|
-
canNegateBracket = false;
|
|
1886
|
-
} else if (
|
|
1887
|
-
firstBracketCharacter &&
|
|
1888
|
-
unit.value === "]" &&
|
|
1889
|
-
bracketElementMarker === null
|
|
1890
|
-
) {
|
|
1891
|
-
firstBracketCharacter = false;
|
|
1892
|
-
canNegateBracket = false;
|
|
1893
|
-
} else if (unit.value === "]") {
|
|
1894
|
-
inBracket = false;
|
|
1895
|
-
bracketStartOffset = null;
|
|
1896
|
-
} else {
|
|
1897
|
-
firstBracketCharacter = false;
|
|
1898
|
-
canNegateBracket = false;
|
|
1899
|
-
}
|
|
1900
|
-
advanceRegexpUnitStream(stream);
|
|
1901
|
-
continue;
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
if (unit.value === "[") {
|
|
1905
|
-
inBracket = true;
|
|
1906
|
-
bracketStartOffset = unit.startOffset;
|
|
1907
|
-
firstBracketCharacter = true;
|
|
1908
|
-
canNegateBracket = true;
|
|
1909
|
-
advanceRegexpUnitStream(stream);
|
|
1910
|
-
continue;
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
if (unit.value === "\\" && nextUnit !== null) {
|
|
1914
|
-
yield {
|
|
1915
|
-
endOffset: nextUnit.endOffset,
|
|
1916
|
-
escaped: true,
|
|
1917
|
-
kind: regexpEscapeKind(nextUnit.value, profile),
|
|
1918
|
-
offset: unit.startOffset,
|
|
1919
|
-
origin: unit.origin ?? nextUnit.origin,
|
|
1920
|
-
value: nextUnit.value,
|
|
1921
|
-
width: nextUnit.endOffset - unit.startOffset,
|
|
1922
|
-
};
|
|
1923
|
-
advanceRegexpUnitStream(stream, 2);
|
|
1924
|
-
continue;
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
if (
|
|
1928
|
-
profile.regexpMode === "ere" &&
|
|
1929
|
-
(unit.value === "(" || unit.value === ")")
|
|
1930
|
-
) {
|
|
1931
|
-
yield {
|
|
1932
|
-
endOffset: unit.endOffset,
|
|
1933
|
-
escaped: false,
|
|
1934
|
-
kind: unit.value === "(" ? "subexpression-open" : "subexpression-close",
|
|
1935
|
-
offset: unit.startOffset,
|
|
1936
|
-
origin: unit.origin,
|
|
1937
|
-
value: unit.value,
|
|
1938
|
-
width: unit.endOffset - unit.startOffset,
|
|
1939
|
-
};
|
|
1940
|
-
} else if (unit.origin === "character-escape") {
|
|
1941
|
-
yield {
|
|
1942
|
-
endOffset: unit.endOffset,
|
|
1943
|
-
escaped: true,
|
|
1944
|
-
kind: "character-escape",
|
|
1945
|
-
offset: unit.startOffset,
|
|
1946
|
-
origin: unit.origin,
|
|
1947
|
-
value: unit.value,
|
|
1948
|
-
width: unit.endOffset - unit.startOffset,
|
|
1949
|
-
};
|
|
1950
|
-
}
|
|
1951
|
-
advanceRegexpUnitStream(stream);
|
|
1952
|
-
}
|
|
1953
|
-
|
|
1954
|
-
if (inBracket && bracketStartOffset !== null) {
|
|
1955
|
-
yield {
|
|
1956
|
-
endOffset: end,
|
|
1957
|
-
escaped: false,
|
|
1958
|
-
kind: "unclosed-bracket-expression",
|
|
1959
|
-
offset: bracketStartOffset,
|
|
1960
|
-
origin: null,
|
|
1961
|
-
value: "[",
|
|
1962
|
-
width: end - bracketStartOffset,
|
|
1963
|
-
};
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
export function findRegexpTokens(
|
|
1968
|
-
text,
|
|
1969
|
-
offset,
|
|
1970
|
-
end,
|
|
1971
|
-
delimiter = null,
|
|
1972
|
-
syntaxProfile = defaultSyntaxProfile,
|
|
1973
|
-
) {
|
|
1974
|
-
return Array.from(
|
|
1975
|
-
iterateRegexpTokens(text, offset, end, delimiter, syntaxProfile),
|
|
1976
|
-
);
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
const noReplacementCaseConversionEscapes = Object.freeze([]);
|
|
1980
|
-
|
|
1981
|
-
export function findReplacementCaseConversionEscapes(
|
|
1982
|
-
text,
|
|
1983
|
-
offset,
|
|
1984
|
-
end,
|
|
1985
|
-
delimiter,
|
|
1986
|
-
syntaxProfile = defaultSyntaxProfile,
|
|
1987
|
-
) {
|
|
1988
|
-
const profile = requireSyntaxProfile(syntaxProfile);
|
|
1989
|
-
if (profile.dialect !== "gnu") {
|
|
1990
|
-
return noReplacementCaseConversionEscapes;
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
const caseConversionEscapes = [];
|
|
1994
|
-
let cursor = offset;
|
|
1995
|
-
while (cursor < end) {
|
|
1996
|
-
const character = characterAt(text, cursor);
|
|
1997
|
-
if (character === null) {
|
|
1998
|
-
break;
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
if (character.value !== "\\") {
|
|
2002
|
-
cursor += character.width;
|
|
2003
|
-
continue;
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
const escaped = characterAt(text, cursor + character.width);
|
|
2007
|
-
if (escaped === null || cursor + character.width >= end) {
|
|
2008
|
-
break;
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
if (
|
|
2012
|
-
escaped.value !== delimiter.value &&
|
|
2013
|
-
["L", "l", "U", "u", "E"].includes(escaped.value)
|
|
2014
|
-
) {
|
|
2015
|
-
caseConversionEscapes.push({
|
|
2016
|
-
endOffset: cursor + character.width + escaped.width,
|
|
2017
|
-
startOffset: cursor,
|
|
2018
|
-
value: escaped.value,
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
|
-
cursor += character.width + escaped.width;
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
return caseConversionEscapes.length === 0
|
|
2025
|
-
? noReplacementCaseConversionEscapes
|
|
2026
|
-
: Object.freeze(caseConversionEscapes);
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
export function findInvalidReplacementControlEscape(
|
|
2030
|
-
text,
|
|
2031
|
-
offset,
|
|
2032
|
-
end,
|
|
2033
|
-
delimiter,
|
|
2034
|
-
syntaxProfile = defaultSyntaxProfile,
|
|
2035
|
-
) {
|
|
2036
|
-
const profile = requireSyntaxProfile(syntaxProfile);
|
|
2037
|
-
if (profile.dialect !== "gnu") {
|
|
2038
|
-
return null;
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
let cursor = offset;
|
|
2042
|
-
while (cursor < end) {
|
|
2043
|
-
const character = characterAt(text, cursor);
|
|
2044
|
-
if (character === null) {
|
|
2045
|
-
break;
|
|
2046
|
-
}
|
|
2047
|
-
if (character.value !== "\\") {
|
|
2048
|
-
cursor += character.width;
|
|
2049
|
-
continue;
|
|
2050
|
-
}
|
|
2051
|
-
|
|
2052
|
-
const escapedOffset = cursor + character.width;
|
|
2053
|
-
const escaped = characterAt(text, escapedOffset);
|
|
2054
|
-
if (escaped === null || escapedOffset >= end) {
|
|
2055
|
-
break;
|
|
2056
|
-
}
|
|
2057
|
-
if (escaped.value !== "c" || escaped.value === delimiter.value) {
|
|
2058
|
-
cursor = escapedOffset + escaped.width;
|
|
2059
|
-
continue;
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
const payloadOffset = escapedOffset + escaped.width;
|
|
2063
|
-
const payload = characterAt(text, payloadOffset);
|
|
2064
|
-
if (payload === null || payloadOffset >= end) {
|
|
2065
|
-
break;
|
|
2066
|
-
}
|
|
2067
|
-
if (payload.value !== "\\") {
|
|
2068
|
-
cursor = payloadOffset + payload.width;
|
|
2069
|
-
continue;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
const recursiveOffset = payloadOffset + payload.width;
|
|
2073
|
-
const recursive = characterAt(text, recursiveOffset);
|
|
2074
|
-
if (recursive === null || recursiveOffset >= end) {
|
|
2075
|
-
return {
|
|
2076
|
-
endOffset: recursiveOffset,
|
|
2077
|
-
startOffset: cursor,
|
|
2078
|
-
};
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
const escapedDelimiterRemovesBackslash =
|
|
2082
|
-
recursive.value === delimiter.value && delimiter.value !== "&";
|
|
2083
|
-
if (
|
|
2084
|
-
recursive.value !== "\\" &&
|
|
2085
|
-
recursive.value !== "\n" &&
|
|
2086
|
-
!escapedDelimiterRemovesBackslash
|
|
2087
|
-
) {
|
|
2088
|
-
return {
|
|
2089
|
-
endOffset: recursiveOffset + recursive.width,
|
|
2090
|
-
startOffset: cursor,
|
|
2091
|
-
};
|
|
2092
|
-
}
|
|
2093
|
-
cursor = recursiveOffset + recursive.width;
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
return null;
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
export function findReplacementDelimiter(
|
|
2100
|
-
text,
|
|
2101
|
-
offset,
|
|
2102
|
-
delimiter,
|
|
2103
|
-
syntaxProfile = defaultSyntaxProfile,
|
|
2104
|
-
) {
|
|
2105
|
-
requireSyntaxProfile(syntaxProfile);
|
|
2106
|
-
let cursor = offset;
|
|
2107
|
-
|
|
2108
|
-
while (cursor < text.length) {
|
|
2109
|
-
const character = characterAt(text, cursor);
|
|
2110
|
-
if (character === null) {
|
|
2111
|
-
break;
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
if (character.value === delimiter.value) {
|
|
2115
|
-
return {
|
|
2116
|
-
closingOffset: cursor,
|
|
2117
|
-
failureOffset: null,
|
|
2118
|
-
};
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
if (character.value === "\n") {
|
|
2122
|
-
return {
|
|
2123
|
-
closingOffset: null,
|
|
2124
|
-
failureOffset: cursor,
|
|
2125
|
-
};
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
if (character.value === "\\") {
|
|
2129
|
-
const escaped = characterAt(text, cursor + character.width);
|
|
2130
|
-
if (escaped === null) {
|
|
2131
|
-
return {
|
|
2132
|
-
closingOffset: null,
|
|
2133
|
-
failureOffset: text.length,
|
|
2134
|
-
};
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
cursor += character.width + escaped.width;
|
|
2138
|
-
continue;
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
cursor += character.width;
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
return {
|
|
2145
|
-
closingOffset: null,
|
|
2146
|
-
failureOffset: text.length,
|
|
2147
|
-
};
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
export function findTransliterateDelimiter(
|
|
2151
|
-
text,
|
|
2152
|
-
offset,
|
|
2153
|
-
lineEnd,
|
|
2154
|
-
delimiter,
|
|
2155
|
-
_syntaxProfile = defaultSyntaxProfile,
|
|
2156
|
-
) {
|
|
2157
|
-
let cursor = offset;
|
|
2158
|
-
|
|
2159
|
-
while (cursor < lineEnd) {
|
|
2160
|
-
const character = characterAt(text, cursor);
|
|
2161
|
-
if (character === null) {
|
|
2162
|
-
break;
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
if (character.value === "\\") {
|
|
2166
|
-
const escaped = characterAt(text, cursor + character.width);
|
|
2167
|
-
if (escaped === null) {
|
|
2168
|
-
return null;
|
|
2169
|
-
}
|
|
2170
|
-
cursor += character.width + escaped.width;
|
|
2171
|
-
continue;
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
if (character.value === delimiter.value) {
|
|
2175
|
-
return cursor;
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
cursor += character.width;
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
return null;
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
export function createSubstituteFlagState() {
|
|
2185
|
-
return {
|
|
2186
|
-
evaluation: false,
|
|
2187
|
-
global: false,
|
|
2188
|
-
occurrenceNumber: null,
|
|
2189
|
-
printTiming: null,
|
|
2190
|
-
};
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
function isZeroDecimalToken(value) {
|
|
2194
|
-
for (const digit of value) {
|
|
2195
|
-
if (digit !== "0") {
|
|
2196
|
-
return false;
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
return true;
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
const substituteFlagValuesByProfile = new Map();
|
|
2203
|
-
|
|
2204
|
-
function substituteFlagValuesFor(syntaxProfile) {
|
|
2205
|
-
let values = substituteFlagValuesByProfile.get(syntaxProfile);
|
|
2206
|
-
if (values === undefined) {
|
|
2207
|
-
values = new Set(
|
|
2208
|
-
substituteFlagSpecificationsFor(syntaxProfile).map(({ flag }) => flag),
|
|
2209
|
-
);
|
|
2210
|
-
substituteFlagValuesByProfile.set(syntaxProfile, values);
|
|
2211
|
-
}
|
|
2212
|
-
return values;
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
export function scanSubstituteFlagTokenSyntax(
|
|
2216
|
-
text,
|
|
2217
|
-
offset,
|
|
2218
|
-
end,
|
|
2219
|
-
state = createSubstituteFlagState(),
|
|
2220
|
-
syntaxProfile = defaultSyntaxProfile,
|
|
2221
|
-
) {
|
|
2222
|
-
const profile = requireSyntaxProfile(syntaxProfile);
|
|
2223
|
-
const character = characterAt(text, offset);
|
|
2224
|
-
if (character === null || offset >= end) {
|
|
2225
|
-
return { kind: "none", endOffset: offset, state };
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
if (character.value >= "0" && character.value <= "9") {
|
|
2229
|
-
const endOffset = scanDecimalEnd(text, offset + 1, end);
|
|
2230
|
-
const value = text.slice(offset, endOffset);
|
|
2231
|
-
if (profile.dialect === "gnu" && state.occurrenceNumber !== null) {
|
|
2232
|
-
return {
|
|
2233
|
-
endOffset,
|
|
2234
|
-
kind: "invalid",
|
|
2235
|
-
reason: "repeated-occurrence",
|
|
2236
|
-
startOffset: offset,
|
|
2237
|
-
state,
|
|
2238
|
-
value,
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
const isZero =
|
|
2243
|
-
profile.dialect === "gnu"
|
|
2244
|
-
? isZeroDecimalToken(value)
|
|
2245
|
-
: value.startsWith("0");
|
|
2246
|
-
if (isZero) {
|
|
2247
|
-
return {
|
|
2248
|
-
endOffset,
|
|
2249
|
-
kind: "invalid",
|
|
2250
|
-
reason: "zero-occurrence",
|
|
2251
|
-
startOffset: offset,
|
|
2252
|
-
state,
|
|
2253
|
-
value,
|
|
2254
|
-
};
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
return {
|
|
2258
|
-
endOffset,
|
|
2259
|
-
kind: "occurrence",
|
|
2260
|
-
startOffset: offset,
|
|
2261
|
-
state: { ...state, occurrenceNumber: value },
|
|
2262
|
-
value,
|
|
2263
|
-
};
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
const endOffset = offset + character.width;
|
|
2267
|
-
if (character.value === "w") {
|
|
2268
|
-
return {
|
|
2269
|
-
endOffset,
|
|
2270
|
-
kind: "write",
|
|
2271
|
-
startOffset: offset,
|
|
2272
|
-
state,
|
|
2273
|
-
value: character.value,
|
|
2274
|
-
};
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
if (!substituteFlagValuesFor(profile).has(character.value)) {
|
|
2278
|
-
return {
|
|
2279
|
-
endOffset,
|
|
2280
|
-
kind: "invalid",
|
|
2281
|
-
reason: "unknown",
|
|
2282
|
-
startOffset: offset,
|
|
2283
|
-
state,
|
|
2284
|
-
value: character.value,
|
|
2285
|
-
};
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
if (
|
|
2289
|
-
profile.dialect === "gnu" &&
|
|
2290
|
-
((character.value === "g" && state.global) ||
|
|
2291
|
-
(character.value === "p" && state.printTiming !== null))
|
|
2292
|
-
) {
|
|
2293
|
-
return {
|
|
2294
|
-
endOffset,
|
|
2295
|
-
kind: "invalid",
|
|
2296
|
-
reason: character.value === "g" ? "repeated-global" : "repeated-print",
|
|
2297
|
-
startOffset: offset,
|
|
2298
|
-
state,
|
|
2299
|
-
value: character.value,
|
|
2300
|
-
};
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
const nextState = { ...state };
|
|
2304
|
-
if (character.value === "e") {
|
|
2305
|
-
nextState.evaluation = true;
|
|
2306
|
-
} else if (character.value === "g") {
|
|
2307
|
-
nextState.global = true;
|
|
2308
|
-
} else if (character.value === "p") {
|
|
2309
|
-
nextState.printTiming = state.evaluation
|
|
2310
|
-
? "after-evaluation"
|
|
2311
|
-
: "before-evaluation";
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
return {
|
|
2315
|
-
endOffset,
|
|
2316
|
-
kind: "flag",
|
|
2317
|
-
startOffset: offset,
|
|
2318
|
-
state: nextState,
|
|
2319
|
-
value: character.value,
|
|
2320
|
-
};
|
|
2321
|
-
}
|