tree-sitter-sed 0.18.0 → 0.20.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/common/grammar.js CHANGED
@@ -1,77 +1,3 @@
1
- import {
2
- defineIssueRules,
3
- issueField,
4
- issueNode,
5
- namedExternal,
6
- } from "./dsl.js";
7
- import regularExpressionRules from "./regex.js";
8
-
9
- function missingAtEndOrBoundary($, reason) {
10
- return choice(
11
- issueField($, reason),
12
- issueField($, `nonconforming_${reason}`),
13
- );
14
- }
15
-
16
- function blankSeparatedFileOperand($, name, operand) {
17
- return seq(
18
- $._blanks,
19
- choice(operand, missingAtEndOrBoundary($, `missing_${name}`)),
20
- );
21
- }
22
-
23
- function fileOperandForms($, name, operand) {
24
- return [
25
- blankSeparatedFileOperand($, name, operand),
26
- seq(issueField($, "omitted_file_separator"), operand),
27
- missingAtEndOrBoundary($, `missing_${name}`),
28
- ];
29
- }
30
-
31
- function sourcePiece($, external, leaf) {
32
- return choice(
33
- namedExternal($, external, leaf),
34
- issueField($, "nul_character"),
35
- issueField($, "invalid_encoding"),
36
- );
37
- }
38
-
39
- function sourcePieces($, leaf, external) {
40
- return prec.right(repeat1(sourcePiece($, external, leaf)));
41
- }
42
-
43
- function lineOperand($, first, rest) {
44
- return prec.right(
45
- seq(
46
- sourcePiece($, first, "file_literal"),
47
- repeat(sourcePiece($, rest, "file_literal")),
48
- ),
49
- );
50
- }
51
-
52
- function operandAtoms($, literal, ...atoms) {
53
- return repeat1(
54
- choice(
55
- literal,
56
- seq($._escape_prefix, issueField($, "invalid_encoding")),
57
- issueField($, "nul_character"),
58
- issueField($, "invalid_encoding"),
59
- ...atoms,
60
- ),
61
- );
62
- }
63
-
64
- function missingCommandSeparator($) {
65
- return choice(
66
- issueNode($, "missing_command_separator"),
67
- issueNode($, "incomplete_missing_command_separator"),
68
- );
69
- }
70
-
71
- function functionVerb($, spelling) {
72
- return field("verb", alias(spelling, $.function_verb));
73
- }
74
-
75
1
  const functionDefinitions = [
76
2
  { rule: "block_function", spelling: "{", form: "block" },
77
3
  {
@@ -120,6 +46,12 @@ const functionDefinitions = [
120
46
  lineTerminated: true,
121
47
  },
122
48
  { rule: "substitute_function", spelling: "s", form: "substitute" },
49
+ {
50
+ rule: "_write_substitute_function",
51
+ spelling: "s",
52
+ form: "substitute",
53
+ writeTerminated: true,
54
+ },
123
55
  {
124
56
  rule: "test_function",
125
57
  spelling: "t",
@@ -155,44 +87,185 @@ const functionDefinitions = [
155
87
  },
156
88
  ];
157
89
 
158
- // Matching known verbs here creates GLR branches that compete with valid
159
- // commands.
160
90
  const unknownFunctionCharacter = new RegExp(
161
91
  `[^ \\t0-9$\\/\\\\!;}\\n${functionDefinitions
162
92
  .map(({ spelling }) => spelling)
163
93
  .join("")}]`,
164
94
  );
165
95
 
166
- // String-only flag rules share verb tokens and would wrap anonymous leaves.
167
- const letterSubstitutionFlags = {
168
- global_flag: "g",
169
- case_insensitive_flag: "i",
170
- print_flag: "p",
171
- };
96
+ function defineIssueRules(definitions) {
97
+ const rules = {};
98
+ for (const { reason, id = reason, outcome, rule } of definitions) {
99
+ rules[`_${id}_reason`] = rule;
100
+ rules[`_${id}_outcome`] = ($) => alias($[`_${id}_reason`], $[reason]);
101
+ rules[`_${id}_issue`] = ($) => alias($[`_${id}_outcome`], $[outcome]);
102
+ }
103
+ return rules;
104
+ }
105
+
106
+ function issueField($, id) {
107
+ return field("issue", alias($[`_${id}_issue`], $.syntax_issue));
108
+ }
109
+
110
+ function missingAtEndOrBoundary($, reason) {
111
+ return choice(
112
+ issueField($, reason),
113
+ issueField($, `nonconforming_${reason}`),
114
+ );
115
+ }
116
+
117
+ function blankSeparatedFileOperand($, name, operand) {
118
+ return seq(
119
+ $._blanks,
120
+ choice(operand, missingAtEndOrBoundary($, `missing_${name}`)),
121
+ );
122
+ }
172
123
 
173
- function nonWriteSubstitutionFlags($) {
124
+ function fileOperandForms($, name, operand) {
174
125
  return [
175
- $.occurrence_flag,
176
- ...Object.entries(letterSubstitutionFlags).map(([rule, spelling]) =>
177
- alias(spelling, $[rule]),
178
- ),
126
+ blankSeparatedFileOperand($, name, operand),
127
+ seq(issueField($, "omitted_file_separator"), operand),
128
+ missingAtEndOrBoundary($, `missing_${name}`),
179
129
  ];
180
130
  }
181
131
 
182
- function postWriteSubstitutionFlagChoice($) {
132
+ function sourcePiece($, external, leaf) {
133
+ return choice(
134
+ alias(external, $[leaf]),
135
+ issueField($, "nul_character"),
136
+ issueField($, "invalid_encoding"),
137
+ );
138
+ }
139
+
140
+ function lineOperand($, first, rest) {
141
+ return prec.right(
142
+ seq(
143
+ sourcePiece($, first, "file_literal"),
144
+ repeat(sourcePiece($, rest, "file_literal")),
145
+ ),
146
+ );
147
+ }
148
+
149
+ function operandAtoms($, literal, ...atoms) {
150
+ return repeat1(
151
+ choice(
152
+ literal,
153
+ seq($._escape_prefix, issueField($, "invalid_encoding")),
154
+ issueField($, "nul_character"),
155
+ issueField($, "invalid_encoding"),
156
+ ...atoms,
157
+ ),
158
+ );
159
+ }
160
+
161
+ function missingCommandSeparator($) {
183
162
  return choice(
184
- ...nonWriteSubstitutionFlags($),
185
- alias("w", $.substitution_flag),
163
+ issueField($, "missing_command_separator"),
164
+ issueField($, "incomplete_missing_command_separator"),
186
165
  );
187
166
  }
188
167
 
168
+ function functionVerb($, spelling) {
169
+ return field("verb", alias(spelling, $.function_verb));
170
+ }
171
+
172
+ const exclusiveSubstitutionFlags = [
173
+ {
174
+ bit: 1,
175
+ flag: ($) => alias("g", $.global_flag),
176
+ conflict: "conflicting_global_flag",
177
+ },
178
+ {
179
+ bit: 2,
180
+ flag: ($) => $.occurrence_flag,
181
+ conflict: "conflicting_occurrence_flag",
182
+ },
183
+ ];
184
+
185
+ function substitutionFlagRules() {
186
+ const rules = {};
187
+ for (const ending of ["plain", "write", "postwrite"]) {
188
+ for (const seen of [0, 1, 2, 3]) {
189
+ rules[`_flags_${ending}_${seen}`] = ($) => {
190
+ const steady = [
191
+ alias("i", $.case_insensitive_flag),
192
+ alias("p", $.print_flag),
193
+ ...(ending === "postwrite"
194
+ ? [
195
+ choice(
196
+ alias("w", $.substitution_flag),
197
+ issueField($, "nonfinal_write_flag"),
198
+ ),
199
+ ]
200
+ : [
201
+ issueField($, "invalid_substitution_flag"),
202
+ issueField($, "invalid_encoding"),
203
+ ]),
204
+ ];
205
+ const transitions = [];
206
+ for (const { bit, flag, conflict } of exclusiveSubstitutionFlags) {
207
+ const symbol = seen & ~bit ? issueField($, conflict) : flag($);
208
+ if (seen & bit) {
209
+ steady.push(symbol);
210
+ } else {
211
+ const tail = $[`_flags_${ending}_${seen | bit}`];
212
+ transitions.push(
213
+ seq(symbol, ending === "write" ? tail : optional(tail)),
214
+ );
215
+ }
216
+ }
217
+ if (ending === "write") {
218
+ return prec.right(
219
+ seq(
220
+ repeat(choice(...steady)),
221
+ choice(
222
+ alias($[`_write_flag_${seen}`], $.write_flag),
223
+ ...transitions,
224
+ ),
225
+ ),
226
+ );
227
+ }
228
+ return prec.right(
229
+ transitions.length === 0
230
+ ? repeat1(choice(...steady))
231
+ : choice(
232
+ repeat1(choice(...steady)),
233
+ seq(repeat(choice(...steady)), choice(...transitions)),
234
+ ),
235
+ );
236
+ };
237
+ }
238
+ }
239
+ for (const seen of [0, 1, 2, 3]) {
240
+ rules[`_write_flag_${seen}`] = ($) => {
241
+ const wfile = field("wfile", alias($._substitution_wfile, $.wfile));
242
+ return choice(
243
+ seq(
244
+ field("verb", alias("w", $.substitution_flag)),
245
+ choice(
246
+ ...fileOperandForms($, "wfile", wfile),
247
+ seq(issueField($, "incomplete_omitted_file_separator"), wfile),
248
+ ),
249
+ ),
250
+ seq(
251
+ issueField($, "nonfinal_write_flag"),
252
+ $[`_flags_postwrite_${seen}`],
253
+ blankSeparatedFileOperand($, "wfile", wfile),
254
+ ),
255
+ );
256
+ };
257
+ }
258
+ return rules;
259
+ }
260
+
189
261
  function delimiter($, name) {
190
- return namedExternal($, $[`_${name}`], "delimiter");
262
+ return alias($[`_${name}`], $.delimiter);
191
263
  }
192
264
 
193
265
  function missingOpeningDelimiter($) {
194
266
  return choice(
195
267
  issueField($, "invalid_delimiter"),
268
+ issueField($, "invalid_encoding"),
196
269
  issueField($, "missing_opening_delimiter"),
197
270
  issueField($, "nonconforming_missing_opening_delimiter"),
198
271
  );
@@ -217,6 +290,23 @@ function semicolonSeparator($) {
217
290
  return alias(";", $.command_separator);
218
291
  }
219
292
 
293
+ function writeCommandUnit($, block = false) {
294
+ return seq(
295
+ editingCommand($, "write_terminated"),
296
+ choice(
297
+ newlineSeparator($),
298
+ alias(
299
+ block ? $._write_block_terminal_separator : $._write_terminal_separator,
300
+ $.command_separator,
301
+ ),
302
+ issueField($, "undefined_write_command_separator"),
303
+ ...(block
304
+ ? []
305
+ : [issueField($, "missing_separator_before_unmatched_brace")]),
306
+ ),
307
+ );
308
+ }
309
+
220
310
  function commandListRules() {
221
311
  return {
222
312
  script: ($) => optional(alias($._script_command_list, $.command_list)),
@@ -249,17 +339,12 @@ function commandListRules() {
249
339
  seq(
250
340
  field(
251
341
  "suppression",
252
- namedExternal(
253
- $,
254
- $._default_output_suppression,
255
- "default_output_suppression",
256
- ),
342
+ alias($._default_output_suppression, $.default_output_suppression),
257
343
  ),
258
344
  repeat(sourcePiece($, $._comment_text, "comment_text")),
259
345
  ),
260
346
 
261
- // Explicit recursion makes cursor traversal quadratic; repeat keeps lists
262
- // balanced.
347
+ // Explicit recursion makes cursor traversal quadratic.
263
348
  _command_list: ($) =>
264
349
  choice(
265
350
  seq(
@@ -272,12 +357,13 @@ function commandListRules() {
272
357
 
273
358
  _command_unit: ($) =>
274
359
  choice(
360
+ writeCommandUnit($),
275
361
  seq(
276
362
  $._separable_command_item,
277
363
  choice(
278
364
  semicolonSeparator($),
279
365
  newlineSeparator($),
280
- issueNode($, "missing_separator_before_unmatched_brace"),
366
+ issueField($, "missing_separator_before_unmatched_brace"),
281
367
  ),
282
368
  ),
283
369
  seq(
@@ -285,15 +371,15 @@ function commandListRules() {
285
371
  choice(
286
372
  semicolonSeparator($),
287
373
  newlineSeparator($),
288
- issueNode($, "missing_separator_before_unmatched_brace"),
289
- issueNode($, "missing_separator_after_unmatched_brace"),
374
+ issueField($, "missing_separator_before_unmatched_brace"),
375
+ issueField($, "missing_separator_after_unmatched_brace"),
290
376
  ),
291
377
  ),
292
378
  seq(
293
379
  editingCommand($, "line_terminated"),
294
380
  choice(
295
381
  newlineSeparator($),
296
- issueNode($, "missing_separator_before_unmatched_brace"),
382
+ issueField($, "missing_separator_before_unmatched_brace"),
297
383
  ),
298
384
  ),
299
385
  editingCommand($, "recovered_line_terminated"),
@@ -304,12 +390,13 @@ function commandListRules() {
304
390
  $._separable_command_item,
305
391
  $._unmatched_closing_brace_item,
306
392
  editingCommand($, "line_terminated"),
393
+ editingCommand($, "write_terminated"),
307
394
  ),
308
395
 
309
396
  _unmatched_closing_brace_item: ($) =>
310
397
  seq(
311
398
  optional($._brace_leading_blanks),
312
- issueNode($, "unmatched_closing_brace"),
399
+ issueField($, "unmatched_closing_brace"),
313
400
  optional($._blanks),
314
401
  ),
315
402
 
@@ -323,15 +410,13 @@ function commandListRules() {
323
410
  _block_commands: ($) =>
324
411
  choice(
325
412
  repeat1($._block_unit),
326
- seq(
327
- repeat($._block_unit),
328
- optional($._block_item),
329
- missingCommandSeparator($),
330
- ),
413
+ seq(repeat($._block_unit), $._block_item, missingCommandSeparator($)),
414
+ missingCommandSeparator($),
331
415
  ),
332
416
 
333
417
  _block_unit: ($) =>
334
418
  choice(
419
+ writeCommandUnit($, true),
335
420
  seq(
336
421
  $._separable_command_item,
337
422
  choice(semicolonSeparator($), newlineSeparator($)),
@@ -341,7 +426,11 @@ function commandListRules() {
341
426
  ),
342
427
 
343
428
  _block_item: ($) =>
344
- choice($._separable_command_item, editingCommand($, "line_terminated")),
429
+ choice(
430
+ $._separable_command_item,
431
+ editingCommand($, "line_terminated"),
432
+ editingCommand($, "write_terminated"),
433
+ ),
345
434
 
346
435
  empty_command: ($) => seq(optional($._blanks), $._empty_command_marker),
347
436
 
@@ -356,8 +445,13 @@ function omittedAddress($) {
356
445
  );
357
446
  }
358
447
 
448
+ const regularExpressionRules = {
449
+ bre: { expression: "basic_reg_exp", duplication: "bre_dupl_symbol" },
450
+ ere: { expression: "extended_reg_exp", duplication: "ere_dupl_symbol" },
451
+ };
452
+
359
453
  function addressRules(mode) {
360
- const expression = mode === "bre" ? "basic_reg_exp" : "extended_reg_exp";
454
+ const { expression } = regularExpressionRules[mode];
361
455
 
362
456
  function addressChoice($) {
363
457
  return choice(
@@ -511,14 +605,7 @@ function delimitedFunction($, spelling, name, first, middleReason, rest) {
511
605
  }
512
606
 
513
607
  function operandRules(mode) {
514
- const expression = mode === "bre" ? "basic_reg_exp" : "extended_reg_exp";
515
-
516
- function substitutionFlagChoice($) {
517
- return choice(
518
- ...nonWriteSubstitutionFlags($),
519
- issueField($, "invalid_substitution_flag"),
520
- );
521
- }
608
+ const { expression } = regularExpressionRules[mode];
522
609
 
523
610
  return {
524
611
  substitute_function: ($) =>
@@ -534,10 +621,7 @@ function operandRules(mode) {
534
621
  seq(
535
622
  field("closing", delimiter($, "substitute_end")),
536
623
  optional(
537
- field(
538
- "flags",
539
- alias($._substitution_flags, $.substitution_flags),
540
- ),
624
+ field("flags", alias($._flags_plain_0, $.substitution_flags)),
541
625
  ),
542
626
  ),
543
627
  unterminatedDelimiter($, "replacement"),
@@ -571,30 +655,20 @@ function operandRules(mode) {
571
655
 
572
656
  escaped_newline: ($) => $._replacement_escaped_newline,
573
657
 
574
- _substitution_flags: ($) =>
575
- prec.right(
576
- choice(
577
- repeat1(substitutionFlagChoice($)),
578
- seq(repeat(substitutionFlagChoice($)), $.write_flag),
579
- ),
658
+ _write_substitute_function: ($) =>
659
+ seq(
660
+ functionVerb($, "s"),
661
+ field("opening", delimiter($, "substitute_start")),
662
+ optional(field("expression", $[expression])),
663
+ field("middle", delimiter($, "substitute_middle")),
664
+ optional(field("replacement", $.replacement)),
665
+ field("closing", delimiter($, "substitute_end")),
666
+ field("flags", alias($._flags_write_0, $.substitution_flags)),
580
667
  ),
581
668
 
582
- occurrence_flag: () => /[0-9]+/,
669
+ ...substitutionFlagRules(),
583
670
 
584
- write_flag: ($) => {
585
- const wfile = field("wfile", alias($._substitution_wfile, $.wfile));
586
- return seq(
587
- field("verb", alias("w", $.substitution_flag)),
588
- choice(
589
- seq(
590
- $._flag_after_write_marker,
591
- repeat1(postWriteSubstitutionFlagChoice($)),
592
- blankSeparatedFileOperand($, "wfile", wfile),
593
- ),
594
- ...fileOperandForms($, "wfile", wfile),
595
- ),
596
- );
597
- },
671
+ occurrence_flag: () => /[0-9]+/,
598
672
 
599
673
  translate_function: ($) =>
600
674
  delimitedFunction(
@@ -673,16 +747,31 @@ function functionRules() {
673
747
  $._substitution_wfile_continuation,
674
748
  ),
675
749
 
676
- label: ($) => sourcePieces($, "label_literal", $._line_word),
750
+ label: ($) =>
751
+ choice(
752
+ seq($._label_start, $._label_body),
753
+ issueField($, "nonportable_label_length"),
754
+ ),
755
+
756
+ _label_body: ($) =>
757
+ prec.right(
758
+ repeat1(
759
+ choice(
760
+ sourcePiece($, $._label_literal, "label_literal"),
761
+ issueField($, "nonportable_label_character"),
762
+ ),
763
+ ),
764
+ ),
677
765
 
678
- comment: ($) => sourcePieces($, "comment_text", $._comment_text),
766
+ comment: ($) =>
767
+ prec.right(repeat1(sourcePiece($, $._comment_text, "comment_text"))),
679
768
 
680
769
  comment_function: ($) =>
681
770
  prec.right(
682
771
  seq(functionVerb($, "#"), optional(field("comment", $.comment))),
683
772
  ),
684
773
 
685
- closing_brace: ($) => $._right_brace,
774
+ closing_brace: () => token("}"),
686
775
  };
687
776
 
688
777
  function fileForm(form) {
@@ -715,7 +804,7 @@ function functionRules() {
715
804
  seq(
716
805
  field(
717
806
  "separator",
718
- namedExternal($, $._argument_separator, "argument_separator"),
807
+ alias($._argument_separator, $.argument_separator),
719
808
  ),
720
809
  optional(field("label", $.label)),
721
810
  ),
@@ -747,10 +836,22 @@ function functionRules() {
747
836
  return rules;
748
837
  }
749
838
 
839
+ function commandTail($) {
840
+ return seq(
841
+ repeat(issueField($, "invalid_encoding")),
842
+ optional(
843
+ choice(
844
+ issueField($, "unexpected_command_text"),
845
+ issueField($, "incomplete_command_text"),
846
+ ),
847
+ ),
848
+ );
849
+ }
850
+
750
851
  function unknownFunction($, id) {
751
852
  return seq(
752
- issueField($, id),
753
- optional(issueField($, "unexpected_command_text")),
853
+ choice(issueField($, id), issueField($, "invalid_encoding")),
854
+ commandTail($),
754
855
  );
755
856
  }
756
857
 
@@ -761,18 +862,24 @@ function editingCommandRules() {
761
862
  2: ($) => $.address_clause,
762
863
  };
763
864
 
764
- // Keep address, issue, and function rules separate by address maximum so
765
- // GLR paths cannot merge before the function verb.
865
+ // Separate address limits to prevent GLR merging before the verb.
766
866
  const wrappers = [];
767
867
  for (const [kind, isLineTerminated] of [
768
868
  ["chainable", false],
769
869
  ["line_terminated", true],
870
+ ["write_terminated", false],
770
871
  ]) {
771
872
  for (const maximum of [0, 1, 2]) {
772
873
  const names = functionDefinitions
773
874
  .filter(
774
- ({ lineTerminated = false, maxAddresses = 2 }) =>
775
- lineTerminated === isLineTerminated && maxAddresses === maximum,
875
+ ({
876
+ lineTerminated = false,
877
+ writeTerminated = false,
878
+ maxAddresses = 2,
879
+ }) =>
880
+ writeTerminated === (kind === "write_terminated") &&
881
+ lineTerminated === isLineTerminated &&
882
+ maxAddresses === maximum,
776
883
  )
777
884
  .map(({ rule }) => rule);
778
885
  if (names.length > 0) {
@@ -815,16 +922,13 @@ function editingCommandRules() {
815
922
 
816
923
  const rules = {
817
924
  _chainable_editing_command: ($) =>
818
- seq(
819
- addressedForms($, "chainable"),
820
- optional(issueField($, "unexpected_command_text")),
821
- ),
925
+ seq(addressedForms($, "chainable"), commandTail($)),
926
+
927
+ _write_terminated_editing_command: ($) =>
928
+ seq(addressedForms($, "write_terminated"), commandTail($)),
822
929
 
823
930
  _line_terminated_editing_command: ($) =>
824
- seq(
825
- addressedForms($, "line_terminated"),
826
- optional(issueField($, "unexpected_command_text")),
827
- ),
931
+ seq(addressedForms($, "line_terminated"), commandTail($)),
828
932
 
829
933
  _recovered_line_terminated_editing_command: ($) =>
830
934
  seq(
@@ -870,7 +974,14 @@ function editingCommandRules() {
870
974
  };
871
975
 
872
976
  for (const { name, names } of wrappers) {
873
- rules[name] = ($) => choice(...names.map((name) => $[name]));
977
+ rules[name] = ($) =>
978
+ choice(
979
+ ...names.map((name) =>
980
+ name === "_write_substitute_function"
981
+ ? alias($[name], $.substitute_function)
982
+ : $[name],
983
+ ),
984
+ );
874
985
  }
875
986
 
876
987
  return rules;
@@ -886,38 +997,460 @@ function sedRules(mode) {
886
997
  };
887
998
  }
888
999
 
889
- const missingCommandDefinitions = [
890
- { reason: "missing_function", atBoundary: true },
891
- { reason: "missing_label", atBoundary: true },
892
- { reason: "missing_rfile", atBoundary: true },
893
- { reason: "missing_wfile", atBoundary: true },
894
- { reason: "missing_text_introducer", atBoundary: true },
895
- {
896
- reason: "incomplete_text_introducer",
897
- rule: ($) => $._text_incomplete_introducer,
898
- },
899
- { reason: "missing_text" },
900
- { reason: "missing_closing_brace" },
901
- { reason: "missing_opening_delimiter", atBoundary: true },
902
- ];
1000
+ const nonportableRangeClasses = ["character_class", "equivalence_class"];
903
1001
 
904
- const boundaryMissingReasons = missingCommandDefinitions
905
- .filter(({ atBoundary }) => atBoundary)
906
- .map(({ reason }) => reason);
1002
+ function intervalExpression($, opening, closing) {
1003
+ const piece = (name) =>
1004
+ choice(
1005
+ field(name, $.duplication_count),
1006
+ field(name, alias($._regex_interval_escaped_count, $.escaped_delimiter)),
1007
+ issueField($, "invalid_interval_character"),
1008
+ ...(opening === "{"
1009
+ ? [issueField($, "incomplete_regular_expression_escape")]
1010
+ : []),
1011
+ issueField($, "invalid_regular_expression_character"),
1012
+ issueField($, "invalid_encoding"),
1013
+ seq(
1014
+ $._escape_prefix,
1015
+ choice(
1016
+ issueField($, "escaped_invalid_regular_expression_character"),
1017
+ issueField($, "invalid_encoding"),
1018
+ ),
1019
+ ),
1020
+ );
1021
+ return seq(
1022
+ alias($._regex_interval_open, opening),
1023
+ choice(
1024
+ repeat1(piece("minimum")),
1025
+ issueField($, "missing_interval_minimum"),
1026
+ issueField($, "incomplete_interval_minimum"),
1027
+ ),
1028
+ optional(
1029
+ seq(
1030
+ choice(
1031
+ alias($._regex_interval_separator, ","),
1032
+ alias($._regex_interval_escaped_separator, $.escaped_delimiter),
1033
+ ),
1034
+ repeat(
1035
+ choice(
1036
+ piece("maximum"),
1037
+ issueField($, "unexpected_interval_separator"),
1038
+ ),
1039
+ ),
1040
+ ),
1041
+ ),
1042
+ choice(
1043
+ alias($._regex_interval_close, closing),
1044
+ issueField($, "missing_interval_close"),
1045
+ issueField($, "incomplete_interval"),
1046
+ ...(opening === "\\{"
1047
+ ? [
1048
+ issueField($, "invalid_interval_close"),
1049
+ issueField($, "incomplete_interval_close"),
1050
+ ]
1051
+ : []),
1052
+ ),
1053
+ );
1054
+ }
907
1055
 
908
- function missingMarkerNames(mode) {
909
- return [
910
- "omitted_address",
911
- "incomplete_omitted_address",
912
- "omitted_first_address",
913
- "empty_subexpression",
914
- "missing_subexpression",
915
- ...(mode === "ere" ? ["empty_alternative"] : []),
916
- "missing_function",
917
- "missing_label",
1056
+ function compoundBracketExpression(
1057
+ $,
1058
+ openingExternal,
1059
+ marker,
1060
+ contentField,
1061
+ content,
1062
+ closingExternal,
1063
+ ...payloadIssues
1064
+ ) {
1065
+ const closingIssues = [
1066
+ issueField($, "missing_bracket_term_close"),
1067
+ issueField($, "incomplete_bracket_term"),
1068
+ ];
1069
+ const closing = seq(
1070
+ alias(closingExternal, marker),
1071
+ choice("]", ...closingIssues),
1072
+ );
1073
+ const payload = repeat1(
1074
+ choice(
1075
+ field(contentField, content),
1076
+ issueField($, "invalid_regular_expression_character"),
1077
+ issueField($, "invalid_encoding"),
1078
+ ...payloadIssues.map((reason) => issueField($, reason)),
1079
+ ),
1080
+ );
1081
+ return seq(
1082
+ alias(openingExternal, "["),
1083
+ marker,
1084
+ choice(
1085
+ payload,
1086
+ issueField($, "missing_bracket_term_payload"),
1087
+ issueField($, "incomplete_bracket_term_payload"),
1088
+ ),
1089
+ choice(closing, ...closingIssues),
1090
+ );
1091
+ }
1092
+
1093
+ function bracketListBody($, list) {
1094
+ return choice(
1095
+ list,
1096
+ issueField($, "missing_bracket_list"),
1097
+ issueField($, "incomplete_missing_bracket_list"),
1098
+ );
1099
+ }
1100
+
1101
+ function subexpressionClose($, closing) {
1102
+ return choice(
1103
+ field("closing", alias($._regex_group_close, closing)),
1104
+ issueField($, "unclosed_subexpression"),
1105
+ issueField($, "incomplete_unclosed_subexpression"),
1106
+ );
1107
+ }
1108
+
1109
+ function bracketExpression($, ambiguous) {
1110
+ return seq(
1111
+ alias($._regex_bracket_open, "["),
1112
+ bracketListBody($, choice($.matching_list, $.nonmatching_list)),
1113
+ ...(ambiguous ? [$._ambiguous_bracket_expression_marker] : []),
1114
+ choice(
1115
+ alias($._regex_bracket_close, "]"),
1116
+ issueField($, "unclosed_bracket_expression"),
1117
+ issueField($, "incomplete_unclosed_bracket_expression"),
1118
+ ),
1119
+ );
1120
+ }
1121
+
1122
+ function rangeOperator($) {
1123
+ return alias($._regex_bracket_hyphen, "-");
1124
+ }
1125
+
1126
+ function nonportableRangeRules() {
1127
+ const rules = {};
1128
+ for (const kind of nonportableRangeClasses) {
1129
+ rules[`_${kind}_start_range`] = ($) =>
1130
+ seq(issueField($, `${kind}_range_start`), rangeOperator($));
1131
+ }
1132
+ for (const kind of nonportableRangeClasses) {
1133
+ rules[`_${kind}_end_range`] = ($) => issueField($, `${kind}_range_end`);
1134
+ }
1135
+ return rules;
1136
+ }
1137
+
1138
+ function bracketRules() {
1139
+ return {
1140
+ bracket_expression: ($) => bracketExpression($, false),
1141
+
1142
+ _ambiguous_bracket_expression: ($) => bracketExpression($, true),
1143
+
1144
+ _bracket_expression: ($) =>
1145
+ choice(
1146
+ $.bracket_expression,
1147
+ issueField($, "ambiguous_bracket_expression"),
1148
+ ),
1149
+
1150
+ matching_list: ($) => $.bracket_list,
1151
+
1152
+ nonmatching_list: ($) =>
1153
+ seq(
1154
+ alias($._regex_bracket_negation, "^"),
1155
+ bracketListBody($, $.bracket_list),
1156
+ ),
1157
+
1158
+ bracket_list: ($) =>
1159
+ seq(
1160
+ $.follow_list,
1161
+ optional(alias($._regex_bracket_trailing_hyphen, "-")),
1162
+ ),
1163
+
1164
+ follow_list: ($) => repeat1($.expression_term),
1165
+
1166
+ expression_term: ($) =>
1167
+ choice(
1168
+ $.single_expression,
1169
+ $.range_expression,
1170
+ issueField($, "shared_range_endpoint"),
1171
+ ),
1172
+
1173
+ single_expression: ($) =>
1174
+ choice($.end_range, $.character_class, $.equivalence_class),
1175
+
1176
+ range_expression: ($) =>
1177
+ seq(
1178
+ choice(
1179
+ $.start_range,
1180
+ ...nonportableRangeClasses.map((kind) =>
1181
+ alias($[`_${kind}_start_range`], $.start_range),
1182
+ ),
1183
+ ),
1184
+ choice(
1185
+ $.end_range,
1186
+ ...nonportableRangeClasses.map((kind) =>
1187
+ alias($[`_${kind}_end_range`], $.end_range),
1188
+ ),
1189
+ alias($._regex_bracket_range_end_hyphen, "-"),
1190
+ ),
1191
+ ),
1192
+
1193
+ ...nonportableRangeRules(),
1194
+
1195
+ start_range: ($) => seq($.end_range, rangeOperator($)),
1196
+
1197
+ end_range: ($) =>
1198
+ choice(
1199
+ alias($._regex_bracket_literal, $.collating_element_single),
1200
+ $.collating_symbol,
1201
+ issueField($, "invalid_regular_expression_character"),
1202
+ issueField($, "invalid_encoding"),
1203
+ ),
1204
+
1205
+ collating_symbol: ($) =>
1206
+ compoundBracketExpression(
1207
+ $,
1208
+ $._regex_open_dot,
1209
+ ".",
1210
+ "element",
1211
+ choice(
1212
+ alias($._regex_coll_elem_single, $.collating_element_single),
1213
+ alias($._regex_coll_elem_multi, $.collating_element_multi),
1214
+ alias($._regex_meta_char, $.meta_character),
1215
+ ),
1216
+ $._regex_dot_close,
1217
+ ),
1218
+
1219
+ equivalence_class: ($) =>
1220
+ compoundBracketExpression(
1221
+ $,
1222
+ $._regex_open_equal,
1223
+ "=",
1224
+ "element",
1225
+ choice(
1226
+ alias($._regex_coll_elem_single, $.collating_element_single),
1227
+ alias($._regex_coll_elem_multi, $.collating_element_multi),
1228
+ ),
1229
+ $._regex_equal_close,
1230
+ "equivalence_class_meta_character",
1231
+ "incomplete_equivalence_class_meta_character",
1232
+ ),
1233
+
1234
+ character_class: ($) =>
1235
+ compoundBracketExpression(
1236
+ $,
1237
+ $._regex_open_colon,
1238
+ ":",
1239
+ "name",
1240
+ alias($._regex_class_name, $.class_name),
1241
+ $._regex_colon_close,
1242
+ "invalid_character_class_name",
1243
+ ),
1244
+ };
1245
+ }
1246
+
1247
+ function regularExpressionEscape($) {
1248
+ return choice(
1249
+ $.quoted_character,
1250
+ seq(
1251
+ $._escape_prefix,
1252
+ choice(
1253
+ issueField($, "escaped_invalid_regular_expression_character"),
1254
+ issueField($, "invalid_encoding"),
1255
+ ),
1256
+ ),
1257
+ $.escaped_delimiter,
1258
+ issueField($, "ordinary_character_escape"),
1259
+ issueField($, "incomplete_regular_expression_escape"),
1260
+ issueField($, "forbidden_regular_expression_newline"),
1261
+ );
1262
+ }
1263
+
1264
+ function oneCharOrCollElem($, ...additional) {
1265
+ return choice(
1266
+ issueField($, "invalid_regular_expression_character"),
1267
+ issueField($, "invalid_encoding"),
1268
+ $.ordinary_character,
1269
+ regularExpressionEscape($),
1270
+ $.sed_newline_escape,
1271
+ alias($._regex_period, "."),
1272
+ $._bracket_expression,
1273
+ issueField($, "special_delimiter_escape"),
1274
+ ...additional,
1275
+ );
1276
+ }
1277
+
1278
+ function commonRegularExpressionRules() {
1279
+ return {
1280
+ duplication_count: ($) => $._regex_dup_count,
1281
+
1282
+ ordinary_character: ($) => $._regex_literal,
1283
+
1284
+ quoted_character: ($) => $._regex_quoted_escape,
1285
+
1286
+ escaped_delimiter: ($) => $._regex_escaped_delimiter,
1287
+
1288
+ sed_newline_escape: ($) => $._regex_newline_escape,
1289
+
1290
+ ...bracketRules(),
1291
+ };
1292
+ }
1293
+
1294
+ function breRules() {
1295
+ return {
1296
+ ...commonRegularExpressionRules(),
1297
+ basic_reg_exp: ($) => $.bre_branch,
1298
+
1299
+ bre_branch: ($) => repeat1($.bre_expression),
1300
+
1301
+ bre_expression: ($) =>
1302
+ prec.right(
1303
+ choice(
1304
+ seq(
1305
+ alias($._regex_beginning_anchor, "^"),
1306
+ optional($.simple_bre),
1307
+ optional(alias($._regex_end_anchor, "$")),
1308
+ ),
1309
+ seq($.simple_bre, optional(alias($._regex_end_anchor, "$"))),
1310
+ alias($._regex_end_anchor, "$"),
1311
+ ),
1312
+ ),
1313
+
1314
+ simple_bre: ($) =>
1315
+ seq(
1316
+ choice(
1317
+ seq($.nondupl_bre, optional($.bre_dupl_symbol)),
1318
+ issueField($, "leading_duplication_symbol"),
1319
+ ),
1320
+ repeat(issueField($, "adjacent_duplication_symbol")),
1321
+ ),
1322
+
1323
+ nondupl_bre: ($) =>
1324
+ choice(
1325
+ $.one_char_or_coll_elem_bre,
1326
+ seq(
1327
+ field("opening", alias($._regex_group_open, "\\(")),
1328
+ optional(field("expression", $.basic_reg_exp)),
1329
+ subexpressionClose($, "\\)"),
1330
+ ),
1331
+ $.backreference,
1332
+ issueField($, "unmatched_subexpression_close"),
1333
+ issueField($, "unmatched_interval_close"),
1334
+ ),
1335
+
1336
+ backreference: ($) => $._regex_backreference,
1337
+
1338
+ one_char_or_coll_elem_bre: ($) =>
1339
+ oneCharOrCollElem(
1340
+ $,
1341
+ issueField($, "bre_vertical_line_escape"),
1342
+ issueField($, "bre_question_mark_escape"),
1343
+ issueField($, "bre_plus_escape"),
1344
+ issueField($, "bre_subexpression_right_anchor"),
1345
+ issueField($, "bre_subexpression_left_anchor"),
1346
+ ),
1347
+
1348
+ bre_dupl_symbol: ($) =>
1349
+ choice(alias($._regex_zero_or_more, "*"), $._bre_interval),
1350
+
1351
+ _bre_interval: ($) => intervalExpression($, "\\{", "\\}"),
1352
+ };
1353
+ }
1354
+
1355
+ function ereRules() {
1356
+ return {
1357
+ ...commonRegularExpressionRules(),
1358
+ extended_reg_exp: ($) =>
1359
+ choice(
1360
+ $.ere_branch,
1361
+ seq(
1362
+ choice($.ere_branch, $._empty_ere_branch),
1363
+ repeat1(
1364
+ seq(
1365
+ alias($._regex_alternation_operator, "|"),
1366
+ choice($.ere_branch, $._empty_ere_branch),
1367
+ ),
1368
+ ),
1369
+ ),
1370
+ ),
1371
+
1372
+ _empty_ere_branch: ($) =>
1373
+ choice(
1374
+ issueField($, "empty_alternative"),
1375
+ issueField($, "incomplete_alternative"),
1376
+ ),
1377
+
1378
+ ere_branch: ($) => repeat1($.ere_expression),
1379
+
1380
+ ere_expression: ($) =>
1381
+ prec.left(
1382
+ 1,
1383
+ seq(
1384
+ choice(
1385
+ $.one_char_or_coll_elem_ere,
1386
+ alias($._regex_beginning_anchor, "^"),
1387
+ alias($._regex_end_anchor, "$"),
1388
+ seq(
1389
+ field("opening", alias($._regex_group_open, "(")),
1390
+ optional(field("expression", $.extended_reg_exp)),
1391
+ subexpressionClose($, ")"),
1392
+ ),
1393
+ issueField($, "leading_duplication_symbol"),
1394
+ ),
1395
+ repeat(
1396
+ choice(
1397
+ $.ere_dupl_symbol,
1398
+ issueField($, "adjacent_duplication_symbol"),
1399
+ ),
1400
+ ),
1401
+ ),
1402
+ ),
1403
+
1404
+ one_char_or_coll_elem_ere: ($) => oneCharOrCollElem($),
1405
+
1406
+ ere_dupl_symbol: ($) =>
1407
+ seq(
1408
+ choice(
1409
+ alias($._regex_zero_or_more, "*"),
1410
+ alias($._regex_one_or_more, "+"),
1411
+ alias($._regex_zero_or_one, "?"),
1412
+ $._ere_interval,
1413
+ ),
1414
+ optional(field("modifier", $.repetition_modifier)),
1415
+ ),
1416
+
1417
+ _ere_interval: ($) => intervalExpression($, "{", "}"),
1418
+
1419
+ repetition_modifier: ($) => $._regex_repetition_modifier,
1420
+ };
1421
+ }
1422
+
1423
+ const missingCommandDefinitions = [
1424
+ { reason: "missing_function", atBoundary: true },
1425
+ { reason: "missing_label", atBoundary: true },
1426
+ { reason: "missing_rfile", atBoundary: true },
1427
+ { reason: "missing_wfile", atBoundary: true },
1428
+ { reason: "missing_text_introducer", atBoundary: true },
1429
+ {
1430
+ reason: "incomplete_text_introducer",
1431
+ rule: ($) => $._text_incomplete_introducer,
1432
+ },
1433
+ { reason: "missing_text" },
1434
+ { reason: "missing_closing_brace" },
1435
+ { reason: "missing_opening_delimiter", atBoundary: true },
1436
+ ];
1437
+
1438
+ const boundaryMissingReasons = missingCommandDefinitions
1439
+ .filter(({ atBoundary }) => atBoundary)
1440
+ .map(({ reason }) => reason);
1441
+
1442
+ function missingMarkerNames(mode) {
1443
+ return [
1444
+ "omitted_address",
1445
+ "incomplete_omitted_address",
1446
+ "omitted_first_address",
1447
+ ...(mode === "ere" ? ["empty_alternative"] : []),
1448
+ "missing_function",
1449
+ "missing_label",
918
1450
  "missing_rfile",
919
1451
  "missing_wfile",
920
1452
  "omitted_file_separator",
1453
+ "incomplete_omitted_file_separator",
921
1454
  "missing_text_introducer",
922
1455
  "missing_text",
923
1456
  "missing_command_separator",
@@ -927,7 +1460,6 @@ function missingMarkerNames(mode) {
927
1460
  "missing_separator_before_unmatched_brace",
928
1461
  "missing_separator_after_unmatched_brace",
929
1462
  ...boundaryMissingReasons.map((reason) => `nonconforming_${reason}`),
930
- "missing_subexpression_placeholder",
931
1463
  "incomplete_bracket_list",
932
1464
  "incomplete_bracket_expression",
933
1465
  ...(mode === "ere" ? ["incomplete_alternative"] : []),
@@ -942,21 +1474,48 @@ function issueDefinitions(mode) {
942
1474
 
943
1475
  return [
944
1476
  {
945
- reason: "malformed_bracket_term",
1477
+ reason: "nonportable_label_character",
1478
+ outcome: "unspecified_syntax",
1479
+ rule: ($) => alias($._nonportable_label_character, $.label_literal),
1480
+ },
1481
+ {
1482
+ reason: "nonportable_label_length",
1483
+ outcome: "unspecified_syntax",
1484
+ rule: ($) =>
1485
+ seq($._long_label_start, alias($._label_literal, $.label_literal)),
1486
+ },
1487
+ {
1488
+ reason: "missing_bracket_term_close",
946
1489
  outcome: "undefined_syntax",
947
- rule: ($) => $._regex_malformed_bracket_term,
1490
+ rule: ($) => $._regex_missing_bracket_term_close,
948
1491
  },
949
1492
  {
950
- id: "invalid_character_class_name",
951
- reason: "malformed_bracket_term",
1493
+ reason: "invalid_character_class_name",
952
1494
  outcome: "undefined_syntax",
953
1495
  rule: ($) => $._regex_invalid_class_name,
954
1496
  },
955
1497
  {
956
1498
  id: "equivalence_class_meta_character",
957
- reason: "malformed_bracket_term",
1499
+ reason: "invalid_equivalence_class_element",
1500
+ outcome: "undefined_syntax",
1501
+ rule: ($) => alias($._regex_meta_char, $.meta_character),
1502
+ },
1503
+ {
1504
+ id: "incomplete_equivalence_class_meta_character",
1505
+ reason: "invalid_equivalence_class_element",
1506
+ outcome: "incomplete_syntax",
1507
+ rule: ($) => alias($._regex_incomplete_meta_char, $.meta_character),
1508
+ },
1509
+ {
1510
+ reason: "missing_bracket_term_payload",
958
1511
  outcome: "undefined_syntax",
959
- rule: ($) => namedExternal($, $._regex_meta_char, "meta_char"),
1512
+ rule: ($) => $._regex_missing_bracket_term_payload,
1513
+ },
1514
+ {
1515
+ id: "incomplete_bracket_term_payload",
1516
+ reason: "missing_bracket_term_payload",
1517
+ outcome: "incomplete_syntax",
1518
+ rule: ($) => $._regex_incomplete_bracket_term_payload,
960
1519
  },
961
1520
  {
962
1521
  reason: "nul_character",
@@ -968,6 +1527,18 @@ function issueDefinitions(mode) {
968
1527
  outcome: "invalid_syntax",
969
1528
  rule: ($) => $._nul_character,
970
1529
  },
1530
+ {
1531
+ id: "escaped_nul_character",
1532
+ reason: "nul_character",
1533
+ outcome: "nonconforming_syntax",
1534
+ rule: ($) => $._single_nul_character,
1535
+ },
1536
+ {
1537
+ id: "escaped_invalid_regular_expression_character",
1538
+ reason: "invalid_regular_expression_character",
1539
+ outcome: "invalid_syntax",
1540
+ rule: ($) => $._single_nul_character,
1541
+ },
971
1542
  {
972
1543
  reason: "invalid_encoding",
973
1544
  outcome: "invalid_syntax",
@@ -986,8 +1557,7 @@ function issueDefinitions(mode) {
986
1557
  {
987
1558
  reason: "shared_range_endpoint",
988
1559
  outcome: "undefined_syntax",
989
- rule: ($) =>
990
- namedExternal($, $._regex_shared_range_endpoint, "range_operator"),
1560
+ rule: ($) => alias($._regex_shared_range_endpoint, "-"),
991
1561
  },
992
1562
  {
993
1563
  reason: "omitted_address",
@@ -1017,11 +1587,6 @@ function issueDefinitions(mode) {
1017
1587
  outcome: "undefined_syntax",
1018
1588
  rule: ($) => $._regex_nonportable_escape,
1019
1589
  },
1020
- {
1021
- reason: "empty_subexpression",
1022
- outcome: "undefined_syntax",
1023
- rule: missing("empty_subexpression"),
1024
- },
1025
1590
  {
1026
1591
  reason: "unclosed_subexpression",
1027
1592
  outcome: "undefined_syntax",
@@ -1032,49 +1597,67 @@ function issueDefinitions(mode) {
1032
1597
  {
1033
1598
  reason: "unmatched_subexpression_close",
1034
1599
  outcome: "undefined_syntax",
1035
- rule: ($) =>
1036
- namedExternal(
1037
- $,
1038
- $._regex_unmatched_group_close,
1039
- "back_close_parenthesis",
1040
- ),
1600
+ rule: ($) => $._regex_unmatched_group_close,
1041
1601
  },
1042
1602
  {
1043
1603
  reason: "unmatched_interval_close",
1044
1604
  outcome: "undefined_syntax",
1045
- rule: ($) =>
1046
- namedExternal(
1047
- $,
1048
- $._regex_unmatched_interval_close,
1049
- "back_close_brace",
1050
- ),
1605
+ rule: ($) => $._regex_unmatched_interval_close,
1051
1606
  },
1052
1607
  ]
1053
1608
  : []),
1054
1609
  {
1055
- reason: "malformed_interval",
1610
+ reason: "invalid_interval_character",
1611
+ outcome: "undefined_syntax",
1612
+ rule: ($) => $._regex_invalid_interval,
1613
+ },
1614
+ {
1615
+ reason: "unexpected_interval_separator",
1056
1616
  outcome: "undefined_syntax",
1057
1617
  rule: ($) =>
1058
- prec.right(
1059
- repeat1(
1060
- choice(
1061
- $._regex_invalid_interval,
1062
- issueField($, "invalid_regular_expression_character"),
1063
- issueField($, "invalid_encoding"),
1064
- ),
1065
- ),
1618
+ choice(
1619
+ alias($._regex_interval_separator, ","),
1620
+ alias($._regex_interval_escaped_separator, $.escaped_delimiter),
1066
1621
  ),
1067
1622
  },
1623
+ {
1624
+ reason: "missing_interval_minimum",
1625
+ outcome: "undefined_syntax",
1626
+ rule: ($) => $._regex_missing_interval_minimum,
1627
+ },
1628
+ {
1629
+ id: "incomplete_interval_minimum",
1630
+ reason: "missing_interval_minimum",
1631
+ outcome: "incomplete_syntax",
1632
+ rule: ($) => $._regex_incomplete_interval_minimum,
1633
+ },
1634
+ {
1635
+ reason: "missing_interval_close",
1636
+ outcome: "undefined_syntax",
1637
+ rule: ($) => $._regex_missing_interval_close,
1638
+ },
1639
+ ...(mode === "bre"
1640
+ ? [
1641
+ {
1642
+ reason: "invalid_interval_close",
1643
+ outcome: "undefined_syntax",
1644
+ rule: ($) => $._regex_invalid_interval_close,
1645
+ },
1646
+ {
1647
+ id: "incomplete_interval_close",
1648
+ reason: "invalid_interval_close",
1649
+ outcome: "incomplete_syntax",
1650
+ rule: ($) => $._regex_incomplete_interval_close,
1651
+ },
1652
+ ]
1653
+ : []),
1068
1654
  {
1069
1655
  reason: "leading_duplication_symbol",
1070
1656
  outcome: "undefined_syntax",
1071
1657
  rule: ($) =>
1072
1658
  seq(
1073
1659
  $._regex_leading_duplication_marker,
1074
- field(
1075
- "operator",
1076
- mode === "bre" ? $.bre_dupl_symbol : $.ere_dupl_symbol,
1077
- ),
1660
+ field("operator", $[regularExpressionRules[mode].duplication]),
1078
1661
  ),
1079
1662
  },
1080
1663
  {
@@ -1083,10 +1666,7 @@ function issueDefinitions(mode) {
1083
1666
  rule: ($) =>
1084
1667
  seq(
1085
1668
  $._regex_adjacent_duplication_marker,
1086
- field(
1087
- "operator",
1088
- mode === "bre" ? $.bre_dupl_symbol : $.ere_dupl_symbol,
1089
- ),
1669
+ field("operator", $[regularExpressionRules[mode].duplication]),
1090
1670
  ),
1091
1671
  },
1092
1672
  ...(mode === "ere"
@@ -1114,7 +1694,7 @@ function issueDefinitions(mode) {
1114
1694
  rule: ($) =>
1115
1695
  choice(
1116
1696
  $._translate_nonportable_escape,
1117
- seq($._escape_prefix, issueField($, "nul_character")),
1697
+ seq($._escape_prefix, issueField($, "escaped_nul_character")),
1118
1698
  ),
1119
1699
  },
1120
1700
  {
@@ -1143,27 +1723,22 @@ function issueDefinitions(mode) {
1143
1723
  rule: ($) =>
1144
1724
  choice(
1145
1725
  $._replacement_nonportable_escape,
1146
- seq($._escape_prefix, issueField($, "nul_character")),
1726
+ seq($._escape_prefix, issueField($, "escaped_nul_character")),
1147
1727
  ),
1148
1728
  },
1149
1729
  {
1150
1730
  reason: "special_delimiter_escape",
1151
1731
  outcome: "unspecified_syntax",
1152
1732
  rule: ($) =>
1153
- namedExternal(
1154
- $,
1155
- $._regex_special_escaped_delimiter,
1156
- "escaped_delimiter",
1157
- ),
1733
+ alias($._regex_special_escaped_delimiter, $.escaped_delimiter),
1158
1734
  },
1159
1735
  {
1160
1736
  reason: "replacement_ampersand_delimiter_escape",
1161
1737
  outcome: "unspecified_syntax",
1162
1738
  rule: ($) =>
1163
- namedExternal(
1164
- $,
1739
+ alias(
1165
1740
  $._replacement_ampersand_escaped_delimiter,
1166
- "replacement_escaped_delimiter",
1741
+ $.replacement_escaped_delimiter,
1167
1742
  ),
1168
1743
  },
1169
1744
  {
@@ -1172,7 +1747,7 @@ function issueDefinitions(mode) {
1172
1747
  rule: ($) =>
1173
1748
  choice(
1174
1749
  $._text_unspecified_escape,
1175
- seq($._escape_prefix, issueField($, "nul_character")),
1750
+ seq($._escape_prefix, issueField($, "escaped_nul_character")),
1176
1751
  ),
1177
1752
  },
1178
1753
  ...(mode === "bre"
@@ -1180,29 +1755,17 @@ function issueDefinitions(mode) {
1180
1755
  {
1181
1756
  reason: "bre_vertical_line_escape",
1182
1757
  outcome: "implementation_defined_syntax",
1183
- rule: ($) =>
1184
- field(
1185
- "token",
1186
- namedExternal($, $._regex_bre_vertical_line_escape, "back_bar"),
1187
- ),
1758
+ rule: ($) => $._regex_bre_vertical_line_escape,
1188
1759
  },
1189
1760
  {
1190
1761
  reason: "bre_question_mark_escape",
1191
1762
  outcome: "implementation_defined_syntax",
1192
- rule: ($) =>
1193
- field(
1194
- "token",
1195
- namedExternal($, $._regex_bre_question_mark_escape, "back_qm"),
1196
- ),
1763
+ rule: ($) => $._regex_bre_question_mark_escape,
1197
1764
  },
1198
1765
  {
1199
1766
  reason: "bre_plus_escape",
1200
1767
  outcome: "implementation_defined_syntax",
1201
- rule: ($) =>
1202
- field(
1203
- "token",
1204
- namedExternal($, $._regex_bre_plus_escape, "back_plus"),
1205
- ),
1768
+ rule: ($) => $._regex_bre_plus_escape,
1206
1769
  },
1207
1770
  {
1208
1771
  reason: "bre_subexpression_left_anchor",
@@ -1221,6 +1784,12 @@ function issueDefinitions(mode) {
1221
1784
  outcome: "nonconforming_syntax",
1222
1785
  rule: missing("omitted_file_separator"),
1223
1786
  },
1787
+ {
1788
+ id: "incomplete_omitted_file_separator",
1789
+ reason: "omitted_file_separator",
1790
+ outcome: "incomplete_syntax",
1791
+ rule: missing("incomplete_omitted_file_separator"),
1792
+ },
1224
1793
  {
1225
1794
  reason: "excess_address",
1226
1795
  outcome: "nonconforming_syntax",
@@ -1245,8 +1814,7 @@ function issueDefinitions(mode) {
1245
1814
  rule: ($) =>
1246
1815
  choice(
1247
1816
  token(prec(-2, unknownFunctionCharacter)),
1248
- $._nul_character,
1249
- issueField($, "invalid_encoding"),
1817
+ $._single_nul_character,
1250
1818
  ),
1251
1819
  },
1252
1820
  {
@@ -1259,11 +1827,14 @@ function issueDefinitions(mode) {
1259
1827
  reason: "unexpected_command_text",
1260
1828
  outcome: "nonconforming_syntax",
1261
1829
  rule: ($) =>
1262
- repeat1(
1263
- choice(
1264
- $._unexpected_command_text,
1265
- issueField($, "nul_character"),
1266
- issueField($, "invalid_encoding"),
1830
+ seq(
1831
+ choice($._unexpected_command_text, issueField($, "nul_character")),
1832
+ repeat(
1833
+ choice(
1834
+ $._unexpected_command_text,
1835
+ issueField($, "nul_character"),
1836
+ issueField($, "invalid_encoding"),
1837
+ ),
1267
1838
  ),
1268
1839
  ),
1269
1840
  },
@@ -1272,6 +1843,12 @@ function issueDefinitions(mode) {
1272
1843
  outcome: "nonconforming_syntax",
1273
1844
  rule: ($) => alias(token.immediate(";"), $.command_separator),
1274
1845
  },
1846
+ {
1847
+ id: "incomplete_command_text",
1848
+ reason: "unexpected_command_text",
1849
+ outcome: "incomplete_syntax",
1850
+ rule: ($) => $._incomplete_command_text,
1851
+ },
1275
1852
  {
1276
1853
  reason: "blanks_around_address_separator",
1277
1854
  outcome: "nonconforming_syntax",
@@ -1310,24 +1887,41 @@ function issueDefinitions(mode) {
1310
1887
  rule: missing("missing_separator_after_unmatched_brace"),
1311
1888
  },
1312
1889
  {
1313
- reason: "invalid_substitution_flag",
1314
- outcome: "nonconforming_syntax",
1890
+ id: "undefined_write_command_separator",
1891
+ reason: "forbidden_command_separator",
1892
+ outcome: "undefined_syntax",
1893
+ rule: ($) => alias($._write_following_separator, $.command_separator),
1894
+ },
1895
+ {
1896
+ reason: "nonfinal_write_flag",
1897
+ outcome: "undefined_syntax",
1315
1898
  rule: ($) =>
1316
- choice(
1317
- $._invalid_substitution_flag,
1318
- $._nul_character,
1319
- issueField($, "invalid_encoding"),
1899
+ seq(
1900
+ field("verb", alias("w", $.substitution_flag)),
1901
+ $._flag_after_write_marker,
1320
1902
  ),
1321
1903
  },
1904
+ {
1905
+ id: "conflicting_global_flag",
1906
+ reason: "conflicting_substitution_flag",
1907
+ outcome: "unspecified_syntax",
1908
+ rule: ($) => alias("g", $.global_flag),
1909
+ },
1910
+ {
1911
+ id: "conflicting_occurrence_flag",
1912
+ reason: "conflicting_substitution_flag",
1913
+ outcome: "unspecified_syntax",
1914
+ rule: ($) => $.occurrence_flag,
1915
+ },
1916
+ {
1917
+ reason: "invalid_substitution_flag",
1918
+ outcome: "nonconforming_syntax",
1919
+ rule: ($) => $._invalid_substitution_flag,
1920
+ },
1322
1921
  {
1323
1922
  reason: "invalid_delimiter",
1324
1923
  outcome: "nonconforming_syntax",
1325
- rule: ($) =>
1326
- choice(
1327
- token(/\\/),
1328
- $._nul_character,
1329
- issueField($, "invalid_encoding"),
1330
- ),
1924
+ rule: ($) => choice(token(/\\/), $._single_nul_character),
1331
1925
  },
1332
1926
  {
1333
1927
  reason: "unterminated_regular_expression",
@@ -1368,11 +1962,6 @@ function issueDefinitions(mode) {
1368
1962
  outcome: "incomplete_syntax",
1369
1963
  rule: rule ?? missing(reason),
1370
1964
  })),
1371
- {
1372
- reason: "missing_subexpression",
1373
- outcome: "incomplete_syntax",
1374
- rule: missing("missing_subexpression"),
1375
- },
1376
1965
  {
1377
1966
  id: "incomplete_unclosed_subexpression",
1378
1967
  reason: "unclosed_subexpression",
@@ -1380,7 +1969,8 @@ function issueDefinitions(mode) {
1380
1969
  rule: ($) => $._regex_incomplete_group,
1381
1970
  },
1382
1971
  {
1383
- reason: "incomplete_bracket_term",
1972
+ id: "incomplete_bracket_term",
1973
+ reason: "missing_bracket_term_close",
1384
1974
  outcome: "incomplete_syntax",
1385
1975
  rule: ($) => $._regex_incomplete_bracket_term,
1386
1976
  },
@@ -1399,14 +1989,16 @@ function issueDefinitions(mode) {
1399
1989
  ...(mode === "ere"
1400
1990
  ? [
1401
1991
  {
1402
- reason: "incomplete_alternative",
1992
+ id: "incomplete_alternative",
1993
+ reason: "empty_alternative",
1403
1994
  outcome: "incomplete_syntax",
1404
1995
  rule: ($) => $._incomplete_alternative_marker,
1405
1996
  },
1406
1997
  ]
1407
1998
  : []),
1408
1999
  {
1409
- reason: "incomplete_interval",
2000
+ id: "incomplete_interval",
2001
+ reason: "missing_interval_close",
1410
2002
  outcome: "incomplete_syntax",
1411
2003
  rule: ($) => $._regex_incomplete_interval,
1412
2004
  },
@@ -1417,7 +2009,8 @@ function issueDefinitions(mode) {
1417
2009
  rule: ($) => $._incomplete_command_separator_marker,
1418
2010
  },
1419
2011
  {
1420
- reason: "incomplete_regular_expression",
2012
+ id: "incomplete_regular_expression",
2013
+ reason: "unterminated_regular_expression",
1421
2014
  outcome: "incomplete_syntax",
1422
2015
  rule: ($) =>
1423
2016
  choice($._regex_unterminated_address, $._regex_unterminated_substitute),
@@ -1428,7 +2021,8 @@ function issueDefinitions(mode) {
1428
2021
  rule: ($) => $._regex_incomplete_escape,
1429
2022
  },
1430
2023
  {
1431
- reason: "incomplete_replacement",
2024
+ id: "incomplete_replacement",
2025
+ reason: "unterminated_replacement",
1432
2026
  outcome: "incomplete_syntax",
1433
2027
  rule: ($) => $._replacement_unterminated,
1434
2028
  },
@@ -1438,7 +2032,8 @@ function issueDefinitions(mode) {
1438
2032
  rule: ($) => $._replacement_incomplete_escape,
1439
2033
  },
1440
2034
  {
1441
- reason: "incomplete_translation",
2035
+ id: "incomplete_translation",
2036
+ reason: "unterminated_translation",
1442
2037
  outcome: "incomplete_syntax",
1443
2038
  rule: ($) =>
1444
2039
  choice(
@@ -1454,140 +2049,155 @@ function issueDefinitions(mode) {
1454
2049
  ];
1455
2050
  }
1456
2051
 
1457
- function externalTokens($, mode) {
2052
+ export function externalTokens(mode) {
1458
2053
  return [
1459
- $._regex_address_start,
1460
- $._escaped_regex_address_start,
1461
- $._regex_address_end,
1462
- $._substitute_start,
1463
- $._substitute_middle,
1464
- $._substitute_end,
1465
- $._translate_start,
1466
- $._translate_middle,
1467
- $._translate_end,
1468
- $._regex_literal,
1469
- $._invalid_character,
1470
- $._nul_character,
1471
- $._regex_beginning_anchor,
1472
- $._regex_end_anchor,
1473
- $._regex_period,
1474
- $._regex_quoted_escape,
1475
- $._escape_prefix,
1476
- $._regex_newline_escape,
1477
- $._regex_escaped_delimiter,
1478
- $._regex_special_escaped_delimiter,
1479
- $._regex_group_open,
1480
- $._regex_group_close,
1481
- $._regex_unclosed_group,
2054
+ "regex_address_start",
2055
+ "escaped_regex_address_start",
2056
+ "regex_address_end",
2057
+ "substitute_start",
2058
+ "substitute_middle",
2059
+ "substitute_end",
2060
+ "translate_start",
2061
+ "translate_middle",
2062
+ "translate_end",
2063
+ "regex_literal",
2064
+ "invalid_character",
2065
+ "nul_character",
2066
+ "single_nul_character",
2067
+ "regex_beginning_anchor",
2068
+ "regex_end_anchor",
2069
+ "regex_period",
2070
+ "regex_quoted_escape",
2071
+ "escape_prefix",
2072
+ "regex_newline_escape",
2073
+ "regex_escaped_delimiter",
2074
+ "regex_special_escaped_delimiter",
2075
+ "regex_group_open",
2076
+ "regex_group_close",
2077
+ "regex_unclosed_group",
1482
2078
  ...(mode === "bre"
1483
2079
  ? [
1484
- $._regex_unmatched_group_close,
1485
- $._regex_bre_vertical_line_escape,
1486
- $._regex_bre_question_mark_escape,
1487
- $._regex_bre_plus_escape,
1488
- $._regex_bre_subexpression_caret,
1489
- $._regex_bre_subexpression_dollar,
1490
- $._regex_unmatched_interval_close,
2080
+ "regex_unmatched_group_close",
2081
+ "regex_bre_vertical_line_escape",
2082
+ "regex_bre_question_mark_escape",
2083
+ "regex_bre_plus_escape",
2084
+ "regex_bre_subexpression_caret",
2085
+ "regex_bre_subexpression_dollar",
2086
+ "regex_unmatched_interval_close",
1491
2087
  ]
1492
2088
  : []),
1493
- ...(mode === "ere" ? [$._regex_alternation_operator] : []),
1494
- $._regex_leading_duplication_marker,
1495
- $._regex_adjacent_duplication_marker,
1496
- $._regex_zero_or_more,
2089
+ ...(mode === "ere" ? ["regex_alternation_operator"] : []),
2090
+ "regex_leading_duplication_marker",
2091
+ "regex_adjacent_duplication_marker",
2092
+ "regex_zero_or_more",
1497
2093
  ...(mode === "ere"
1498
- ? [
1499
- $._regex_one_or_more,
1500
- $._regex_zero_or_one,
1501
- $._regex_repetition_modifier,
1502
- ]
2094
+ ? ["regex_one_or_more", "regex_zero_or_one", "regex_repetition_modifier"]
1503
2095
  : []),
1504
- $._regex_interval_open,
1505
- $._regex_dup_count,
1506
- $._regex_interval_separator,
1507
- $._regex_interval_close,
1508
- ...(mode === "bre" ? [$._regex_backreference] : []),
1509
- $._regex_invalid_interval,
1510
- $._regex_nonportable_escape,
1511
- $._regex_incomplete_escape,
1512
- $._regex_bracket_open,
1513
- $._regex_bracket_close,
1514
- $._regex_bracket_literal,
1515
- $._regex_bracket_negation,
1516
- $._regex_bracket_hyphen,
1517
- $._regex_bracket_range_end_hyphen,
1518
- $._regex_bracket_trailing_hyphen,
1519
- $._regex_open_colon,
1520
- $._regex_class_name,
1521
- $._regex_invalid_class_name,
1522
- $._regex_colon_close,
1523
- $._regex_open_dot,
1524
- $._regex_coll_elem_single,
1525
- $._regex_coll_elem_multi,
1526
- $._regex_meta_char,
1527
- $._regex_dot_close,
1528
- $._regex_open_equal,
1529
- $._regex_equal_close,
1530
- $._regex_malformed_bracket_term,
1531
- $._regex_shared_range_endpoint,
1532
- $._ambiguous_bracket_expression_marker,
1533
- $._missing_bracket_list_marker,
1534
- $._unclosed_bracket_expression_marker,
1535
- $._nonportable_range_start_marker,
1536
- $._nonportable_range_end_marker,
1537
- $._regex_unterminated_address,
1538
- $._regex_unterminated_substitute,
1539
- $._replacement_literal,
1540
- $._replacement_match_reference,
1541
- $._replacement_backreference,
1542
- $._replacement_escaped_delimiter,
1543
- $._replacement_ampersand_escaped_delimiter,
1544
- $._replacement_escape_sequence,
1545
- $._replacement_nonportable_escape,
1546
- $._replacement_escaped_newline,
1547
- $._replacement_incomplete_escape,
1548
- $._replacement_unterminated,
1549
- $._translate_literal,
1550
- $._translate_escaped_delimiter,
1551
- $._translate_escape,
1552
- $._translate_nonportable_escape,
1553
- $._translate_incomplete_escape,
1554
- $._translate_unterminated_source,
1555
- $._translate_unterminated_destination,
1556
- $._invalid_substitution_flag,
1557
- $._flag_after_write_marker,
1558
- $._text_command_start,
1559
- $._text_incomplete_introducer,
1560
- $._text_literal,
1561
- $._text_backslash_escape,
1562
- $._text_escaped_newline,
1563
- $._text_unspecified_escape,
1564
- $._text_line_end,
1565
- $._text_eof,
1566
- $._default_output_suppression,
1567
- $._comment_text,
1568
- $._file_argument,
1569
- $._substitution_wfile_argument,
1570
- $._substitution_wfile_continuation,
1571
- $._line_word,
1572
- $._argument_separator,
1573
- $._right_brace,
1574
- $._brace_leading_blanks,
1575
- $._block_trailing_blanks,
1576
- $._empty_command_marker,
1577
- $._reserved_unknown_function_token,
1578
- $._regex_incomplete_group,
1579
- $._regex_incomplete_bracket_term,
1580
- $._regex_incomplete_interval,
1581
- $._regex_forbidden_newline_escape,
1582
- $._regex_line_unterminated_address,
1583
- $._regex_line_unterminated_substitute,
1584
- $._replacement_line_unterminated,
1585
- $._translate_line_unterminated_source,
1586
- $._translate_line_unterminated_destination,
1587
- $._blanks_around_address_separator,
1588
- ...missingMarkerNames(mode).map((name) => $[`_${name}_marker`]),
1589
- $._unexpected_command_text,
1590
- $._error_sentinel,
2096
+ "regex_interval_open",
2097
+ "regex_dup_count",
2098
+ "regex_interval_escaped_count",
2099
+ "regex_interval_escaped_separator",
2100
+ "regex_interval_separator",
2101
+ "regex_interval_close",
2102
+ ...(mode === "bre" ? ["regex_backreference"] : []),
2103
+ "regex_invalid_interval",
2104
+ "regex_missing_interval_minimum",
2105
+ "regex_incomplete_interval_minimum",
2106
+ "regex_missing_interval_close",
2107
+ ...(mode === "bre"
2108
+ ? ["regex_invalid_interval_close", "regex_incomplete_interval_close"]
2109
+ : []),
2110
+ "regex_nonportable_escape",
2111
+ "regex_incomplete_escape",
2112
+ "regex_bracket_open",
2113
+ "regex_bracket_close",
2114
+ "regex_bracket_literal",
2115
+ "regex_bracket_negation",
2116
+ "regex_bracket_hyphen",
2117
+ "regex_bracket_range_end_hyphen",
2118
+ "regex_bracket_trailing_hyphen",
2119
+ "regex_open_colon",
2120
+ "regex_class_name",
2121
+ "regex_invalid_class_name",
2122
+ "regex_colon_close",
2123
+ "regex_open_dot",
2124
+ "regex_coll_elem_single",
2125
+ "regex_coll_elem_multi",
2126
+ "regex_meta_char",
2127
+ "regex_incomplete_meta_char",
2128
+ "regex_dot_close",
2129
+ "regex_open_equal",
2130
+ "regex_equal_close",
2131
+ "regex_missing_bracket_term_close",
2132
+ "regex_missing_bracket_term_payload",
2133
+ "regex_incomplete_bracket_term_payload",
2134
+ "regex_shared_range_endpoint",
2135
+ "ambiguous_bracket_expression_marker",
2136
+ "missing_bracket_list_marker",
2137
+ "unclosed_bracket_expression_marker",
2138
+ "nonportable_range_start_marker",
2139
+ "nonportable_range_end_marker",
2140
+ "regex_unterminated_address",
2141
+ "regex_unterminated_substitute",
2142
+ "replacement_literal",
2143
+ "replacement_match_reference",
2144
+ "replacement_backreference",
2145
+ "replacement_escaped_delimiter",
2146
+ "replacement_ampersand_escaped_delimiter",
2147
+ "replacement_escape_sequence",
2148
+ "replacement_nonportable_escape",
2149
+ "replacement_escaped_newline",
2150
+ "replacement_incomplete_escape",
2151
+ "replacement_unterminated",
2152
+ "translate_literal",
2153
+ "translate_escaped_delimiter",
2154
+ "translate_escape",
2155
+ "translate_nonportable_escape",
2156
+ "translate_incomplete_escape",
2157
+ "translate_unterminated_source",
2158
+ "translate_unterminated_destination",
2159
+ "invalid_substitution_flag",
2160
+ "flag_after_write_marker",
2161
+ "write_terminal_separator",
2162
+ "write_block_terminal_separator",
2163
+ "write_following_separator",
2164
+ "text_command_start",
2165
+ "text_incomplete_introducer",
2166
+ "text_literal",
2167
+ "text_backslash_escape",
2168
+ "text_escaped_newline",
2169
+ "text_unspecified_escape",
2170
+ "text_line_end",
2171
+ "text_eof",
2172
+ "default_output_suppression",
2173
+ "comment_text",
2174
+ "file_argument",
2175
+ "substitution_wfile_argument",
2176
+ "substitution_wfile_continuation",
2177
+ "line_word",
2178
+ "label_start",
2179
+ "long_label_start",
2180
+ "label_literal",
2181
+ "nonportable_label_character",
2182
+ "argument_separator",
2183
+ "brace_leading_blanks",
2184
+ "block_trailing_blanks",
2185
+ "empty_command_marker",
2186
+ "reserved_unknown_function_token",
2187
+ "regex_incomplete_group",
2188
+ "regex_incomplete_bracket_term",
2189
+ "regex_incomplete_interval",
2190
+ "regex_forbidden_newline_escape",
2191
+ "regex_line_unterminated_address",
2192
+ "regex_line_unterminated_substitute",
2193
+ "replacement_line_unterminated",
2194
+ "translate_line_unterminated_source",
2195
+ "translate_line_unterminated_destination",
2196
+ "blanks_around_address_separator",
2197
+ ...missingMarkerNames(mode).map((name) => `${name}_marker`),
2198
+ "unexpected_command_text",
2199
+ "incomplete_command_text",
2200
+ "error_sentinel",
1591
2201
  ];
1592
2202
  }
1593
2203
 
@@ -1596,10 +2206,10 @@ function defineGrammar(name, mode) {
1596
2206
  throw new Error(`Unsupported regular-expression mode: ${mode}`);
1597
2207
  }
1598
2208
 
1599
- return grammar({
2209
+ const options = {
1600
2210
  name,
1601
2211
 
1602
- externals: ($) => externalTokens($, mode),
2212
+ externals: ($) => externalTokens(mode).map((name) => $[`_${name}`]),
1603
2213
 
1604
2214
  extras: () => [],
1605
2215
 
@@ -1618,13 +2228,12 @@ function defineGrammar(name, mode) {
1618
2228
  $._omitted_first_address_unit1_reason,
1619
2229
  ],
1620
2230
  ],
1621
-
1622
- rules: {
1623
- ...sedRules(mode),
1624
- ...regularExpressionRules(mode),
1625
- ...defineIssueRules(issueDefinitions(mode)),
1626
- },
1627
- });
2231
+ };
2232
+ const rules = sedRules(mode);
2233
+ const issues = defineIssueRules(issueDefinitions(mode));
2234
+ return mode === "bre"
2235
+ ? grammar({ ...options, rules: { ...rules, ...breRules(), ...issues } })
2236
+ : grammar({ ...options, rules: { ...rules, ...ereRules(), ...issues } });
1628
2237
  }
1629
2238
 
1630
2239
  export default defineGrammar;