tree-sitter-sed 0.18.0 → 0.19.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,76 +1,5 @@
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
- }
1
+ import { defineIssueRules, issueField, namedExternal } from "./dsl.js";
2
+ import { breRules, ereRules } from "./regex.js";
74
3
 
75
4
  const functionDefinitions = [
76
5
  { rule: "block_function", spelling: "{", form: "block" },
@@ -120,6 +49,12 @@ const functionDefinitions = [
120
49
  lineTerminated: true,
121
50
  },
122
51
  { rule: "substitute_function", spelling: "s", form: "substitute" },
52
+ {
53
+ rule: "_write_substitute_function",
54
+ spelling: "s",
55
+ form: "substitute",
56
+ writeTerminated: true,
57
+ },
123
58
  {
124
59
  rule: "test_function",
125
60
  spelling: "t",
@@ -155,37 +90,160 @@ const functionDefinitions = [
155
90
  },
156
91
  ];
157
92
 
158
- // Matching known verbs here creates GLR branches that compete with valid
159
- // commands.
160
93
  const unknownFunctionCharacter = new RegExp(
161
94
  `[^ \\t0-9$\\/\\\\!;}\\n${functionDefinitions
162
95
  .map(({ spelling }) => spelling)
163
96
  .join("")}]`,
164
97
  );
165
98
 
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
- };
99
+ function missingAtEndOrBoundary($, reason) {
100
+ return choice(
101
+ issueField($, reason),
102
+ issueField($, `nonconforming_${reason}`),
103
+ );
104
+ }
172
105
 
173
- function nonWriteSubstitutionFlags($) {
106
+ function blankSeparatedFileOperand($, name, operand) {
107
+ return seq(
108
+ $._blanks,
109
+ choice(operand, missingAtEndOrBoundary($, `missing_${name}`)),
110
+ );
111
+ }
112
+
113
+ function fileOperandForms($, name, operand) {
174
114
  return [
175
- $.occurrence_flag,
176
- ...Object.entries(letterSubstitutionFlags).map(([rule, spelling]) =>
177
- alias(spelling, $[rule]),
178
- ),
115
+ blankSeparatedFileOperand($, name, operand),
116
+ seq(issueField($, "omitted_file_separator"), operand),
117
+ missingAtEndOrBoundary($, `missing_${name}`),
179
118
  ];
180
119
  }
181
120
 
182
- function postWriteSubstitutionFlagChoice($) {
121
+ function sourcePiece($, external, leaf) {
122
+ return choice(
123
+ namedExternal($, external, leaf),
124
+ issueField($, "nul_character"),
125
+ issueField($, "invalid_encoding"),
126
+ );
127
+ }
128
+
129
+ function lineOperand($, first, rest) {
130
+ return prec.right(
131
+ seq(
132
+ sourcePiece($, first, "file_literal"),
133
+ repeat(sourcePiece($, rest, "file_literal")),
134
+ ),
135
+ );
136
+ }
137
+
138
+ function operandAtoms($, literal, ...atoms) {
139
+ return repeat1(
140
+ choice(
141
+ literal,
142
+ seq($._escape_prefix, issueField($, "invalid_encoding")),
143
+ issueField($, "nul_character"),
144
+ issueField($, "invalid_encoding"),
145
+ ...atoms,
146
+ ),
147
+ );
148
+ }
149
+
150
+ function missingCommandSeparator($) {
183
151
  return choice(
184
- ...nonWriteSubstitutionFlags($),
185
- alias("w", $.substitution_flag),
152
+ issueField($, "missing_command_separator"),
153
+ issueField($, "incomplete_missing_command_separator"),
186
154
  );
187
155
  }
188
156
 
157
+ function functionVerb($, spelling) {
158
+ return field("verb", alias(spelling, $.function_verb));
159
+ }
160
+
161
+ function substitutionFlag($, kind, seen) {
162
+ if ((kind === 1 && seen & 2) || (kind === 2 && seen & 1)) {
163
+ return issueField(
164
+ $,
165
+ kind === 1 ? "conflicting_global_flag" : "conflicting_occurrence_flag",
166
+ );
167
+ }
168
+ return kind === 1 ? alias("g", $.global_flag) : $.occurrence_flag;
169
+ }
170
+
171
+ function substitutionFlagRules() {
172
+ const rules = {};
173
+ for (const ending of ["plain", "write", "postwrite"]) {
174
+ for (const seen of [0, 1, 2, 3]) {
175
+ rules[`_flags_${ending}_${seen}`] = ($) => {
176
+ const steady = [
177
+ alias("i", $.case_insensitive_flag),
178
+ alias("p", $.print_flag),
179
+ ...(ending === "postwrite"
180
+ ? [
181
+ choice(
182
+ alias("w", $.substitution_flag),
183
+ issueField($, "nonfinal_write_flag"),
184
+ ),
185
+ ]
186
+ : [
187
+ issueField($, "invalid_substitution_flag"),
188
+ issueField($, "invalid_encoding"),
189
+ ]),
190
+ ];
191
+ const transitions = [];
192
+ for (const kind of [1, 2]) {
193
+ const flag = substitutionFlag($, kind, seen);
194
+ if (seen & kind) {
195
+ steady.push(flag);
196
+ } else {
197
+ const tail = $[`_flags_${ending}_${seen | kind}`];
198
+ transitions.push(
199
+ seq(flag, ending === "write" ? tail : optional(tail)),
200
+ );
201
+ }
202
+ }
203
+ if (ending === "write") {
204
+ return prec.right(
205
+ seq(
206
+ repeat(choice(...steady)),
207
+ choice(
208
+ alias($[`_write_flag_${seen}`], $.write_flag),
209
+ ...transitions,
210
+ ),
211
+ ),
212
+ );
213
+ }
214
+ return prec.right(
215
+ transitions.length === 0
216
+ ? repeat1(choice(...steady))
217
+ : choice(
218
+ repeat1(choice(...steady)),
219
+ seq(repeat(choice(...steady)), choice(...transitions)),
220
+ ),
221
+ );
222
+ };
223
+ }
224
+ }
225
+ for (const seen of [0, 1, 2, 3]) {
226
+ rules[`_write_flag_${seen}`] = ($) => {
227
+ const wfile = field("wfile", alias($._substitution_wfile, $.wfile));
228
+ return choice(
229
+ seq(
230
+ field("verb", alias("w", $.substitution_flag)),
231
+ choice(
232
+ ...fileOperandForms($, "wfile", wfile),
233
+ seq(issueField($, "incomplete_omitted_file_separator"), wfile),
234
+ ),
235
+ ),
236
+ seq(
237
+ issueField($, "nonfinal_write_flag"),
238
+ $[`_flags_postwrite_${seen}`],
239
+ blankSeparatedFileOperand($, "wfile", wfile),
240
+ ),
241
+ );
242
+ };
243
+ }
244
+ return rules;
245
+ }
246
+
189
247
  function delimiter($, name) {
190
248
  return namedExternal($, $[`_${name}`], "delimiter");
191
249
  }
@@ -193,6 +251,7 @@ function delimiter($, name) {
193
251
  function missingOpeningDelimiter($) {
194
252
  return choice(
195
253
  issueField($, "invalid_delimiter"),
254
+ issueField($, "invalid_encoding"),
196
255
  issueField($, "missing_opening_delimiter"),
197
256
  issueField($, "nonconforming_missing_opening_delimiter"),
198
257
  );
@@ -217,6 +276,21 @@ function semicolonSeparator($) {
217
276
  return alias(";", $.command_separator);
218
277
  }
219
278
 
279
+ function writeCommandUnit($, block = false) {
280
+ return seq(
281
+ editingCommand($, "write_terminated"),
282
+ choice(
283
+ newlineSeparator($),
284
+ alias(
285
+ block ? $._write_block_terminal_separator : $._write_terminal_separator,
286
+ $.command_separator,
287
+ ),
288
+ issueField($, "undefined_write_command_separator"),
289
+ issueField($, "missing_separator_before_unmatched_brace"),
290
+ ),
291
+ );
292
+ }
293
+
220
294
  function commandListRules() {
221
295
  return {
222
296
  script: ($) => optional(alias($._script_command_list, $.command_list)),
@@ -258,8 +332,7 @@ function commandListRules() {
258
332
  repeat(sourcePiece($, $._comment_text, "comment_text")),
259
333
  ),
260
334
 
261
- // Explicit recursion makes cursor traversal quadratic; repeat keeps lists
262
- // balanced.
335
+ // Explicit recursion makes cursor traversal quadratic.
263
336
  _command_list: ($) =>
264
337
  choice(
265
338
  seq(
@@ -272,12 +345,13 @@ function commandListRules() {
272
345
 
273
346
  _command_unit: ($) =>
274
347
  choice(
348
+ writeCommandUnit($),
275
349
  seq(
276
350
  $._separable_command_item,
277
351
  choice(
278
352
  semicolonSeparator($),
279
353
  newlineSeparator($),
280
- issueNode($, "missing_separator_before_unmatched_brace"),
354
+ issueField($, "missing_separator_before_unmatched_brace"),
281
355
  ),
282
356
  ),
283
357
  seq(
@@ -285,15 +359,15 @@ function commandListRules() {
285
359
  choice(
286
360
  semicolonSeparator($),
287
361
  newlineSeparator($),
288
- issueNode($, "missing_separator_before_unmatched_brace"),
289
- issueNode($, "missing_separator_after_unmatched_brace"),
362
+ issueField($, "missing_separator_before_unmatched_brace"),
363
+ issueField($, "missing_separator_after_unmatched_brace"),
290
364
  ),
291
365
  ),
292
366
  seq(
293
367
  editingCommand($, "line_terminated"),
294
368
  choice(
295
369
  newlineSeparator($),
296
- issueNode($, "missing_separator_before_unmatched_brace"),
370
+ issueField($, "missing_separator_before_unmatched_brace"),
297
371
  ),
298
372
  ),
299
373
  editingCommand($, "recovered_line_terminated"),
@@ -304,12 +378,13 @@ function commandListRules() {
304
378
  $._separable_command_item,
305
379
  $._unmatched_closing_brace_item,
306
380
  editingCommand($, "line_terminated"),
381
+ editingCommand($, "write_terminated"),
307
382
  ),
308
383
 
309
384
  _unmatched_closing_brace_item: ($) =>
310
385
  seq(
311
386
  optional($._brace_leading_blanks),
312
- issueNode($, "unmatched_closing_brace"),
387
+ issueField($, "unmatched_closing_brace"),
313
388
  optional($._blanks),
314
389
  ),
315
390
 
@@ -323,15 +398,13 @@ function commandListRules() {
323
398
  _block_commands: ($) =>
324
399
  choice(
325
400
  repeat1($._block_unit),
326
- seq(
327
- repeat($._block_unit),
328
- optional($._block_item),
329
- missingCommandSeparator($),
330
- ),
401
+ seq(repeat($._block_unit), $._block_item, missingCommandSeparator($)),
402
+ missingCommandSeparator($),
331
403
  ),
332
404
 
333
405
  _block_unit: ($) =>
334
406
  choice(
407
+ writeCommandUnit($, true),
335
408
  seq(
336
409
  $._separable_command_item,
337
410
  choice(semicolonSeparator($), newlineSeparator($)),
@@ -341,7 +414,11 @@ function commandListRules() {
341
414
  ),
342
415
 
343
416
  _block_item: ($) =>
344
- choice($._separable_command_item, editingCommand($, "line_terminated")),
417
+ choice(
418
+ $._separable_command_item,
419
+ editingCommand($, "line_terminated"),
420
+ editingCommand($, "write_terminated"),
421
+ ),
345
422
 
346
423
  empty_command: ($) => seq(optional($._blanks), $._empty_command_marker),
347
424
 
@@ -513,13 +590,6 @@ function delimitedFunction($, spelling, name, first, middleReason, rest) {
513
590
  function operandRules(mode) {
514
591
  const expression = mode === "bre" ? "basic_reg_exp" : "extended_reg_exp";
515
592
 
516
- function substitutionFlagChoice($) {
517
- return choice(
518
- ...nonWriteSubstitutionFlags($),
519
- issueField($, "invalid_substitution_flag"),
520
- );
521
- }
522
-
523
593
  return {
524
594
  substitute_function: ($) =>
525
595
  delimitedFunction(
@@ -534,10 +604,7 @@ function operandRules(mode) {
534
604
  seq(
535
605
  field("closing", delimiter($, "substitute_end")),
536
606
  optional(
537
- field(
538
- "flags",
539
- alias($._substitution_flags, $.substitution_flags),
540
- ),
607
+ field("flags", alias($._flags_plain_0, $.substitution_flags)),
541
608
  ),
542
609
  ),
543
610
  unterminatedDelimiter($, "replacement"),
@@ -571,30 +638,20 @@ function operandRules(mode) {
571
638
 
572
639
  escaped_newline: ($) => $._replacement_escaped_newline,
573
640
 
574
- _substitution_flags: ($) =>
575
- prec.right(
576
- choice(
577
- repeat1(substitutionFlagChoice($)),
578
- seq(repeat(substitutionFlagChoice($)), $.write_flag),
579
- ),
641
+ _write_substitute_function: ($) =>
642
+ seq(
643
+ functionVerb($, "s"),
644
+ field("opening", delimiter($, "substitute_start")),
645
+ optional(field("expression", $[expression])),
646
+ field("middle", delimiter($, "substitute_middle")),
647
+ optional(field("replacement", $.replacement)),
648
+ field("closing", delimiter($, "substitute_end")),
649
+ field("flags", alias($._flags_write_0, $.substitution_flags)),
580
650
  ),
581
651
 
582
- occurrence_flag: () => /[0-9]+/,
652
+ ...substitutionFlagRules(),
583
653
 
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
- },
654
+ occurrence_flag: () => /[0-9]+/,
598
655
 
599
656
  translate_function: ($) =>
600
657
  delimitedFunction(
@@ -673,16 +730,31 @@ function functionRules() {
673
730
  $._substitution_wfile_continuation,
674
731
  ),
675
732
 
676
- label: ($) => sourcePieces($, "label_literal", $._line_word),
733
+ label: ($) =>
734
+ choice(
735
+ seq($._label_start, $._label_body),
736
+ issueField($, "nonportable_label_length"),
737
+ ),
738
+
739
+ _label_body: ($) =>
740
+ prec.right(
741
+ repeat1(
742
+ choice(
743
+ sourcePiece($, $._label_literal, "label_literal"),
744
+ issueField($, "nonportable_label_character"),
745
+ ),
746
+ ),
747
+ ),
677
748
 
678
- comment: ($) => sourcePieces($, "comment_text", $._comment_text),
749
+ comment: ($) =>
750
+ prec.right(repeat1(sourcePiece($, $._comment_text, "comment_text"))),
679
751
 
680
752
  comment_function: ($) =>
681
753
  prec.right(
682
754
  seq(functionVerb($, "#"), optional(field("comment", $.comment))),
683
755
  ),
684
756
 
685
- closing_brace: ($) => $._right_brace,
757
+ closing_brace: () => token("}"),
686
758
  };
687
759
 
688
760
  function fileForm(form) {
@@ -747,10 +819,22 @@ function functionRules() {
747
819
  return rules;
748
820
  }
749
821
 
822
+ function commandTail($) {
823
+ return seq(
824
+ repeat(issueField($, "invalid_encoding")),
825
+ optional(
826
+ choice(
827
+ issueField($, "unexpected_command_text"),
828
+ issueField($, "incomplete_command_text"),
829
+ ),
830
+ ),
831
+ );
832
+ }
833
+
750
834
  function unknownFunction($, id) {
751
835
  return seq(
752
- issueField($, id),
753
- optional(issueField($, "unexpected_command_text")),
836
+ choice(issueField($, id), issueField($, "invalid_encoding")),
837
+ commandTail($),
754
838
  );
755
839
  }
756
840
 
@@ -761,18 +845,24 @@ function editingCommandRules() {
761
845
  2: ($) => $.address_clause,
762
846
  };
763
847
 
764
- // Keep address, issue, and function rules separate by address maximum so
765
- // GLR paths cannot merge before the function verb.
848
+ // Separate address limits to prevent GLR merging before the verb.
766
849
  const wrappers = [];
767
850
  for (const [kind, isLineTerminated] of [
768
851
  ["chainable", false],
769
852
  ["line_terminated", true],
853
+ ["write_terminated", false],
770
854
  ]) {
771
855
  for (const maximum of [0, 1, 2]) {
772
856
  const names = functionDefinitions
773
857
  .filter(
774
- ({ lineTerminated = false, maxAddresses = 2 }) =>
775
- lineTerminated === isLineTerminated && maxAddresses === maximum,
858
+ ({
859
+ lineTerminated = false,
860
+ writeTerminated = false,
861
+ maxAddresses = 2,
862
+ }) =>
863
+ writeTerminated === (kind === "write_terminated") &&
864
+ lineTerminated === isLineTerminated &&
865
+ maxAddresses === maximum,
776
866
  )
777
867
  .map(({ rule }) => rule);
778
868
  if (names.length > 0) {
@@ -815,16 +905,13 @@ function editingCommandRules() {
815
905
 
816
906
  const rules = {
817
907
  _chainable_editing_command: ($) =>
818
- seq(
819
- addressedForms($, "chainable"),
820
- optional(issueField($, "unexpected_command_text")),
821
- ),
908
+ seq(addressedForms($, "chainable"), commandTail($)),
909
+
910
+ _write_terminated_editing_command: ($) =>
911
+ seq(addressedForms($, "write_terminated"), commandTail($)),
822
912
 
823
913
  _line_terminated_editing_command: ($) =>
824
- seq(
825
- addressedForms($, "line_terminated"),
826
- optional(issueField($, "unexpected_command_text")),
827
- ),
914
+ seq(addressedForms($, "line_terminated"), commandTail($)),
828
915
 
829
916
  _recovered_line_terminated_editing_command: ($) =>
830
917
  seq(
@@ -870,7 +957,14 @@ function editingCommandRules() {
870
957
  };
871
958
 
872
959
  for (const { name, names } of wrappers) {
873
- rules[name] = ($) => choice(...names.map((name) => $[name]));
960
+ rules[name] = ($) =>
961
+ choice(
962
+ ...names.map((name) =>
963
+ name === "_write_substitute_function"
964
+ ? alias($[name], $.substitute_function)
965
+ : $[name],
966
+ ),
967
+ );
874
968
  }
875
969
 
876
970
  return rules;
@@ -910,14 +1004,13 @@ function missingMarkerNames(mode) {
910
1004
  "omitted_address",
911
1005
  "incomplete_omitted_address",
912
1006
  "omitted_first_address",
913
- "empty_subexpression",
914
- "missing_subexpression",
915
1007
  ...(mode === "ere" ? ["empty_alternative"] : []),
916
1008
  "missing_function",
917
1009
  "missing_label",
918
1010
  "missing_rfile",
919
1011
  "missing_wfile",
920
1012
  "omitted_file_separator",
1013
+ "incomplete_omitted_file_separator",
921
1014
  "missing_text_introducer",
922
1015
  "missing_text",
923
1016
  "missing_command_separator",
@@ -927,7 +1020,6 @@ function missingMarkerNames(mode) {
927
1020
  "missing_separator_before_unmatched_brace",
928
1021
  "missing_separator_after_unmatched_brace",
929
1022
  ...boundaryMissingReasons.map((reason) => `nonconforming_${reason}`),
930
- "missing_subexpression_placeholder",
931
1023
  "incomplete_bracket_list",
932
1024
  "incomplete_bracket_expression",
933
1025
  ...(mode === "ere" ? ["incomplete_alternative"] : []),
@@ -942,21 +1034,49 @@ function issueDefinitions(mode) {
942
1034
 
943
1035
  return [
944
1036
  {
945
- reason: "malformed_bracket_term",
1037
+ reason: "nonportable_label_character",
1038
+ outcome: "unspecified_syntax",
1039
+ rule: ($) => alias($._nonportable_label_character, $.label_literal),
1040
+ },
1041
+ {
1042
+ reason: "nonportable_label_length",
1043
+ outcome: "unspecified_syntax",
1044
+ rule: ($) =>
1045
+ seq($._long_label_start, alias($._label_literal, $.label_literal)),
1046
+ },
1047
+ {
1048
+ reason: "missing_bracket_term_close",
946
1049
  outcome: "undefined_syntax",
947
- rule: ($) => $._regex_malformed_bracket_term,
1050
+ rule: ($) => $._regex_missing_bracket_term_close,
948
1051
  },
949
1052
  {
950
- id: "invalid_character_class_name",
951
- reason: "malformed_bracket_term",
1053
+ reason: "invalid_character_class_name",
952
1054
  outcome: "undefined_syntax",
953
1055
  rule: ($) => $._regex_invalid_class_name,
954
1056
  },
955
1057
  {
956
1058
  id: "equivalence_class_meta_character",
957
- reason: "malformed_bracket_term",
1059
+ reason: "invalid_equivalence_class_element",
1060
+ outcome: "undefined_syntax",
1061
+ rule: ($) => namedExternal($, $._regex_meta_char, "meta_character"),
1062
+ },
1063
+ {
1064
+ id: "incomplete_equivalence_class_meta_character",
1065
+ reason: "invalid_equivalence_class_element",
1066
+ outcome: "incomplete_syntax",
1067
+ rule: ($) =>
1068
+ namedExternal($, $._regex_incomplete_meta_char, "meta_character"),
1069
+ },
1070
+ {
1071
+ reason: "missing_bracket_term_payload",
958
1072
  outcome: "undefined_syntax",
959
- rule: ($) => namedExternal($, $._regex_meta_char, "meta_char"),
1073
+ rule: ($) => $._regex_missing_bracket_term_payload,
1074
+ },
1075
+ {
1076
+ id: "incomplete_bracket_term_payload",
1077
+ reason: "missing_bracket_term_payload",
1078
+ outcome: "incomplete_syntax",
1079
+ rule: ($) => $._regex_incomplete_bracket_term_payload,
960
1080
  },
961
1081
  {
962
1082
  reason: "nul_character",
@@ -968,6 +1088,19 @@ function issueDefinitions(mode) {
968
1088
  outcome: "invalid_syntax",
969
1089
  rule: ($) => $._nul_character,
970
1090
  },
1091
+ ...[
1092
+ ["escaped_nul_character", "nul_character", "nonconforming_syntax"],
1093
+ [
1094
+ "escaped_invalid_regular_expression_character",
1095
+ "invalid_regular_expression_character",
1096
+ "invalid_syntax",
1097
+ ],
1098
+ ].map(([id, reason, outcome]) => ({
1099
+ id,
1100
+ reason,
1101
+ outcome,
1102
+ rule: ($) => $._escaped_nul_character,
1103
+ })),
971
1104
  {
972
1105
  reason: "invalid_encoding",
973
1106
  outcome: "invalid_syntax",
@@ -986,8 +1119,7 @@ function issueDefinitions(mode) {
986
1119
  {
987
1120
  reason: "shared_range_endpoint",
988
1121
  outcome: "undefined_syntax",
989
- rule: ($) =>
990
- namedExternal($, $._regex_shared_range_endpoint, "range_operator"),
1122
+ rule: ($) => alias($._regex_shared_range_endpoint, "-"),
991
1123
  },
992
1124
  {
993
1125
  reason: "omitted_address",
@@ -1017,11 +1149,6 @@ function issueDefinitions(mode) {
1017
1149
  outcome: "undefined_syntax",
1018
1150
  rule: ($) => $._regex_nonportable_escape,
1019
1151
  },
1020
- {
1021
- reason: "empty_subexpression",
1022
- outcome: "undefined_syntax",
1023
- rule: missing("empty_subexpression"),
1024
- },
1025
1152
  {
1026
1153
  reason: "unclosed_subexpression",
1027
1154
  outcome: "undefined_syntax",
@@ -1032,39 +1159,61 @@ function issueDefinitions(mode) {
1032
1159
  {
1033
1160
  reason: "unmatched_subexpression_close",
1034
1161
  outcome: "undefined_syntax",
1035
- rule: ($) =>
1036
- namedExternal(
1037
- $,
1038
- $._regex_unmatched_group_close,
1039
- "back_close_parenthesis",
1040
- ),
1162
+ rule: ($) => $._regex_unmatched_group_close,
1041
1163
  },
1042
1164
  {
1043
1165
  reason: "unmatched_interval_close",
1044
1166
  outcome: "undefined_syntax",
1045
- rule: ($) =>
1046
- namedExternal(
1047
- $,
1048
- $._regex_unmatched_interval_close,
1049
- "back_close_brace",
1050
- ),
1167
+ rule: ($) => $._regex_unmatched_interval_close,
1051
1168
  },
1052
1169
  ]
1053
1170
  : []),
1054
1171
  {
1055
- reason: "malformed_interval",
1172
+ reason: "invalid_interval_character",
1173
+ outcome: "undefined_syntax",
1174
+ rule: ($) => $._regex_invalid_interval,
1175
+ },
1176
+ {
1177
+ reason: "unexpected_interval_separator",
1056
1178
  outcome: "undefined_syntax",
1057
1179
  rule: ($) =>
1058
- prec.right(
1059
- repeat1(
1060
- choice(
1061
- $._regex_invalid_interval,
1062
- issueField($, "invalid_regular_expression_character"),
1063
- issueField($, "invalid_encoding"),
1064
- ),
1180
+ choice(
1181
+ alias($._regex_interval_separator, ","),
1182
+ namedExternal(
1183
+ $,
1184
+ $._regex_interval_escaped_separator,
1185
+ "escaped_delimiter",
1065
1186
  ),
1066
1187
  ),
1067
1188
  },
1189
+ ...["minimum", "close"].flatMap((part) => [
1190
+ {
1191
+ reason: `missing_interval_${part}`,
1192
+ outcome: "undefined_syntax",
1193
+ rule: ($) => $[`_regex_missing_interval_${part}`],
1194
+ },
1195
+ ...(part === "minimum"
1196
+ ? [
1197
+ {
1198
+ id: "incomplete_interval_minimum",
1199
+ reason: "missing_interval_minimum",
1200
+ outcome: "incomplete_syntax",
1201
+ rule: ($) => $._regex_incomplete_interval_minimum,
1202
+ },
1203
+ ]
1204
+ : []),
1205
+ ]),
1206
+ {
1207
+ reason: "invalid_interval_close",
1208
+ outcome: "undefined_syntax",
1209
+ rule: ($) => $._regex_invalid_interval_close,
1210
+ },
1211
+ {
1212
+ id: "incomplete_interval_close",
1213
+ reason: "invalid_interval_close",
1214
+ outcome: "incomplete_syntax",
1215
+ rule: ($) => $._regex_incomplete_interval_close,
1216
+ },
1068
1217
  {
1069
1218
  reason: "leading_duplication_symbol",
1070
1219
  outcome: "undefined_syntax",
@@ -1114,7 +1263,7 @@ function issueDefinitions(mode) {
1114
1263
  rule: ($) =>
1115
1264
  choice(
1116
1265
  $._translate_nonportable_escape,
1117
- seq($._escape_prefix, issueField($, "nul_character")),
1266
+ seq($._escape_prefix, issueField($, "escaped_nul_character")),
1118
1267
  ),
1119
1268
  },
1120
1269
  {
@@ -1143,7 +1292,7 @@ function issueDefinitions(mode) {
1143
1292
  rule: ($) =>
1144
1293
  choice(
1145
1294
  $._replacement_nonportable_escape,
1146
- seq($._escape_prefix, issueField($, "nul_character")),
1295
+ seq($._escape_prefix, issueField($, "escaped_nul_character")),
1147
1296
  ),
1148
1297
  },
1149
1298
  {
@@ -1172,7 +1321,7 @@ function issueDefinitions(mode) {
1172
1321
  rule: ($) =>
1173
1322
  choice(
1174
1323
  $._text_unspecified_escape,
1175
- seq($._escape_prefix, issueField($, "nul_character")),
1324
+ seq($._escape_prefix, issueField($, "escaped_nul_character")),
1176
1325
  ),
1177
1326
  },
1178
1327
  ...(mode === "bre"
@@ -1180,29 +1329,17 @@ function issueDefinitions(mode) {
1180
1329
  {
1181
1330
  reason: "bre_vertical_line_escape",
1182
1331
  outcome: "implementation_defined_syntax",
1183
- rule: ($) =>
1184
- field(
1185
- "token",
1186
- namedExternal($, $._regex_bre_vertical_line_escape, "back_bar"),
1187
- ),
1332
+ rule: ($) => $._regex_bre_vertical_line_escape,
1188
1333
  },
1189
1334
  {
1190
1335
  reason: "bre_question_mark_escape",
1191
1336
  outcome: "implementation_defined_syntax",
1192
- rule: ($) =>
1193
- field(
1194
- "token",
1195
- namedExternal($, $._regex_bre_question_mark_escape, "back_qm"),
1196
- ),
1337
+ rule: ($) => $._regex_bre_question_mark_escape,
1197
1338
  },
1198
1339
  {
1199
1340
  reason: "bre_plus_escape",
1200
1341
  outcome: "implementation_defined_syntax",
1201
- rule: ($) =>
1202
- field(
1203
- "token",
1204
- namedExternal($, $._regex_bre_plus_escape, "back_plus"),
1205
- ),
1342
+ rule: ($) => $._regex_bre_plus_escape,
1206
1343
  },
1207
1344
  {
1208
1345
  reason: "bre_subexpression_left_anchor",
@@ -1221,6 +1358,12 @@ function issueDefinitions(mode) {
1221
1358
  outcome: "nonconforming_syntax",
1222
1359
  rule: missing("omitted_file_separator"),
1223
1360
  },
1361
+ {
1362
+ id: "incomplete_omitted_file_separator",
1363
+ reason: "omitted_file_separator",
1364
+ outcome: "incomplete_syntax",
1365
+ rule: missing("incomplete_omitted_file_separator"),
1366
+ },
1224
1367
  {
1225
1368
  reason: "excess_address",
1226
1369
  outcome: "nonconforming_syntax",
@@ -1243,11 +1386,7 @@ function issueDefinitions(mode) {
1243
1386
  reason: "unknown_function",
1244
1387
  outcome: "nonconforming_syntax",
1245
1388
  rule: ($) =>
1246
- choice(
1247
- token(prec(-2, unknownFunctionCharacter)),
1248
- $._nul_character,
1249
- issueField($, "invalid_encoding"),
1250
- ),
1389
+ choice(token(prec(-2, unknownFunctionCharacter)), $._nul_character),
1251
1390
  },
1252
1391
  {
1253
1392
  id: "reserved_unknown_function",
@@ -1259,11 +1398,14 @@ function issueDefinitions(mode) {
1259
1398
  reason: "unexpected_command_text",
1260
1399
  outcome: "nonconforming_syntax",
1261
1400
  rule: ($) =>
1262
- repeat1(
1263
- choice(
1264
- $._unexpected_command_text,
1265
- issueField($, "nul_character"),
1266
- issueField($, "invalid_encoding"),
1401
+ seq(
1402
+ choice($._unexpected_command_text, issueField($, "nul_character")),
1403
+ repeat(
1404
+ choice(
1405
+ $._unexpected_command_text,
1406
+ issueField($, "nul_character"),
1407
+ issueField($, "invalid_encoding"),
1408
+ ),
1267
1409
  ),
1268
1410
  ),
1269
1411
  },
@@ -1272,6 +1414,12 @@ function issueDefinitions(mode) {
1272
1414
  outcome: "nonconforming_syntax",
1273
1415
  rule: ($) => alias(token.immediate(";"), $.command_separator),
1274
1416
  },
1417
+ {
1418
+ id: "incomplete_command_text",
1419
+ reason: "unexpected_command_text",
1420
+ outcome: "incomplete_syntax",
1421
+ rule: ($) => $._incomplete_command_text,
1422
+ },
1275
1423
  {
1276
1424
  reason: "blanks_around_address_separator",
1277
1425
  outcome: "nonconforming_syntax",
@@ -1310,24 +1458,41 @@ function issueDefinitions(mode) {
1310
1458
  rule: missing("missing_separator_after_unmatched_brace"),
1311
1459
  },
1312
1460
  {
1313
- reason: "invalid_substitution_flag",
1314
- outcome: "nonconforming_syntax",
1461
+ id: "undefined_write_command_separator",
1462
+ reason: "forbidden_command_separator",
1463
+ outcome: "undefined_syntax",
1464
+ rule: ($) => alias($._write_following_separator, $.command_separator),
1465
+ },
1466
+ {
1467
+ reason: "nonfinal_write_flag",
1468
+ outcome: "undefined_syntax",
1315
1469
  rule: ($) =>
1316
- choice(
1317
- $._invalid_substitution_flag,
1318
- $._nul_character,
1319
- issueField($, "invalid_encoding"),
1470
+ seq(
1471
+ field("verb", alias("w", $.substitution_flag)),
1472
+ $._flag_after_write_marker,
1320
1473
  ),
1321
1474
  },
1475
+ {
1476
+ id: "conflicting_global_flag",
1477
+ reason: "conflicting_substitution_flag",
1478
+ outcome: "unspecified_syntax",
1479
+ rule: ($) => alias("g", $.global_flag),
1480
+ },
1481
+ {
1482
+ id: "conflicting_occurrence_flag",
1483
+ reason: "conflicting_substitution_flag",
1484
+ outcome: "unspecified_syntax",
1485
+ rule: ($) => $.occurrence_flag,
1486
+ },
1487
+ {
1488
+ reason: "invalid_substitution_flag",
1489
+ outcome: "nonconforming_syntax",
1490
+ rule: ($) => choice($._invalid_substitution_flag, $._nul_character),
1491
+ },
1322
1492
  {
1323
1493
  reason: "invalid_delimiter",
1324
1494
  outcome: "nonconforming_syntax",
1325
- rule: ($) =>
1326
- choice(
1327
- token(/\\/),
1328
- $._nul_character,
1329
- issueField($, "invalid_encoding"),
1330
- ),
1495
+ rule: ($) => choice(token(/\\/), $._nul_character),
1331
1496
  },
1332
1497
  {
1333
1498
  reason: "unterminated_regular_expression",
@@ -1368,11 +1533,6 @@ function issueDefinitions(mode) {
1368
1533
  outcome: "incomplete_syntax",
1369
1534
  rule: rule ?? missing(reason),
1370
1535
  })),
1371
- {
1372
- reason: "missing_subexpression",
1373
- outcome: "incomplete_syntax",
1374
- rule: missing("missing_subexpression"),
1375
- },
1376
1536
  {
1377
1537
  id: "incomplete_unclosed_subexpression",
1378
1538
  reason: "unclosed_subexpression",
@@ -1380,7 +1540,8 @@ function issueDefinitions(mode) {
1380
1540
  rule: ($) => $._regex_incomplete_group,
1381
1541
  },
1382
1542
  {
1383
- reason: "incomplete_bracket_term",
1543
+ id: "incomplete_bracket_term",
1544
+ reason: "missing_bracket_term_close",
1384
1545
  outcome: "incomplete_syntax",
1385
1546
  rule: ($) => $._regex_incomplete_bracket_term,
1386
1547
  },
@@ -1399,14 +1560,16 @@ function issueDefinitions(mode) {
1399
1560
  ...(mode === "ere"
1400
1561
  ? [
1401
1562
  {
1402
- reason: "incomplete_alternative",
1563
+ id: "incomplete_alternative",
1564
+ reason: "empty_alternative",
1403
1565
  outcome: "incomplete_syntax",
1404
1566
  rule: ($) => $._incomplete_alternative_marker,
1405
1567
  },
1406
1568
  ]
1407
1569
  : []),
1408
1570
  {
1409
- reason: "incomplete_interval",
1571
+ id: "incomplete_interval",
1572
+ reason: "missing_interval_close",
1410
1573
  outcome: "incomplete_syntax",
1411
1574
  rule: ($) => $._regex_incomplete_interval,
1412
1575
  },
@@ -1417,7 +1580,8 @@ function issueDefinitions(mode) {
1417
1580
  rule: ($) => $._incomplete_command_separator_marker,
1418
1581
  },
1419
1582
  {
1420
- reason: "incomplete_regular_expression",
1583
+ id: "incomplete_regular_expression",
1584
+ reason: "unterminated_regular_expression",
1421
1585
  outcome: "incomplete_syntax",
1422
1586
  rule: ($) =>
1423
1587
  choice($._regex_unterminated_address, $._regex_unterminated_substitute),
@@ -1428,7 +1592,8 @@ function issueDefinitions(mode) {
1428
1592
  rule: ($) => $._regex_incomplete_escape,
1429
1593
  },
1430
1594
  {
1431
- reason: "incomplete_replacement",
1595
+ id: "incomplete_replacement",
1596
+ reason: "unterminated_replacement",
1432
1597
  outcome: "incomplete_syntax",
1433
1598
  rule: ($) => $._replacement_unterminated,
1434
1599
  },
@@ -1438,7 +1603,8 @@ function issueDefinitions(mode) {
1438
1603
  rule: ($) => $._replacement_incomplete_escape,
1439
1604
  },
1440
1605
  {
1441
- reason: "incomplete_translation",
1606
+ id: "incomplete_translation",
1607
+ reason: "unterminated_translation",
1442
1608
  outcome: "incomplete_syntax",
1443
1609
  rule: ($) =>
1444
1610
  choice(
@@ -1468,6 +1634,7 @@ function externalTokens($, mode) {
1468
1634
  $._regex_literal,
1469
1635
  $._invalid_character,
1470
1636
  $._nul_character,
1637
+ $._escaped_nul_character,
1471
1638
  $._regex_beginning_anchor,
1472
1639
  $._regex_end_anchor,
1473
1640
  $._regex_period,
@@ -1503,10 +1670,17 @@ function externalTokens($, mode) {
1503
1670
  : []),
1504
1671
  $._regex_interval_open,
1505
1672
  $._regex_dup_count,
1673
+ $._regex_interval_escaped_count,
1674
+ $._regex_interval_escaped_separator,
1506
1675
  $._regex_interval_separator,
1507
1676
  $._regex_interval_close,
1508
1677
  ...(mode === "bre" ? [$._regex_backreference] : []),
1509
1678
  $._regex_invalid_interval,
1679
+ $._regex_missing_interval_minimum,
1680
+ $._regex_incomplete_interval_minimum,
1681
+ $._regex_missing_interval_close,
1682
+ $._regex_invalid_interval_close,
1683
+ $._regex_incomplete_interval_close,
1510
1684
  $._regex_nonportable_escape,
1511
1685
  $._regex_incomplete_escape,
1512
1686
  $._regex_bracket_open,
@@ -1524,10 +1698,13 @@ function externalTokens($, mode) {
1524
1698
  $._regex_coll_elem_single,
1525
1699
  $._regex_coll_elem_multi,
1526
1700
  $._regex_meta_char,
1701
+ $._regex_incomplete_meta_char,
1527
1702
  $._regex_dot_close,
1528
1703
  $._regex_open_equal,
1529
1704
  $._regex_equal_close,
1530
- $._regex_malformed_bracket_term,
1705
+ $._regex_missing_bracket_term_close,
1706
+ $._regex_missing_bracket_term_payload,
1707
+ $._regex_incomplete_bracket_term_payload,
1531
1708
  $._regex_shared_range_endpoint,
1532
1709
  $._ambiguous_bracket_expression_marker,
1533
1710
  $._missing_bracket_list_marker,
@@ -1555,6 +1732,9 @@ function externalTokens($, mode) {
1555
1732
  $._translate_unterminated_destination,
1556
1733
  $._invalid_substitution_flag,
1557
1734
  $._flag_after_write_marker,
1735
+ $._write_terminal_separator,
1736
+ $._write_block_terminal_separator,
1737
+ $._write_following_separator,
1558
1738
  $._text_command_start,
1559
1739
  $._text_incomplete_introducer,
1560
1740
  $._text_literal,
@@ -1569,8 +1749,11 @@ function externalTokens($, mode) {
1569
1749
  $._substitution_wfile_argument,
1570
1750
  $._substitution_wfile_continuation,
1571
1751
  $._line_word,
1752
+ $._label_start,
1753
+ $._long_label_start,
1754
+ $._label_literal,
1755
+ $._nonportable_label_character,
1572
1756
  $._argument_separator,
1573
- $._right_brace,
1574
1757
  $._brace_leading_blanks,
1575
1758
  $._block_trailing_blanks,
1576
1759
  $._empty_command_marker,
@@ -1587,6 +1770,7 @@ function externalTokens($, mode) {
1587
1770
  $._blanks_around_address_separator,
1588
1771
  ...missingMarkerNames(mode).map((name) => $[`_${name}_marker`]),
1589
1772
  $._unexpected_command_text,
1773
+ $._incomplete_command_text,
1590
1774
  $._error_sentinel,
1591
1775
  ];
1592
1776
  }
@@ -1596,7 +1780,7 @@ function defineGrammar(name, mode) {
1596
1780
  throw new Error(`Unsupported regular-expression mode: ${mode}`);
1597
1781
  }
1598
1782
 
1599
- return grammar({
1783
+ const options = {
1600
1784
  name,
1601
1785
 
1602
1786
  externals: ($) => externalTokens($, mode),
@@ -1618,13 +1802,12 @@ function defineGrammar(name, mode) {
1618
1802
  $._omitted_first_address_unit1_reason,
1619
1803
  ],
1620
1804
  ],
1621
-
1622
- rules: {
1623
- ...sedRules(mode),
1624
- ...regularExpressionRules(mode),
1625
- ...defineIssueRules(issueDefinitions(mode)),
1626
- },
1627
- });
1805
+ };
1806
+ const rules = sedRules(mode);
1807
+ const issues = defineIssueRules(issueDefinitions(mode));
1808
+ return mode === "bre"
1809
+ ? grammar({ ...options, rules: { ...rules, ...breRules(), ...issues } })
1810
+ : grammar({ ...options, rules: { ...rules, ...ereRules(), ...issues } });
1628
1811
  }
1629
1812
 
1630
1813
  export default defineGrammar;