@tsuzuri-lab/sed-language-server 0.1.2 → 0.3.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/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
- }