tree-sitter-sed 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/common/grammar.js CHANGED
@@ -6,15 +6,6 @@ const {
6
6
  } = require("./dsl");
7
7
  const regularExpressionRules = require("./regex");
8
8
 
9
- const outcomes = [
10
- "undefined_syntax",
11
- "unspecified_syntax",
12
- "implementation_defined_syntax",
13
- "implementation_option_syntax",
14
- "nonconforming_syntax",
15
- "incomplete_syntax",
16
- ];
17
-
18
9
  function outcomeRuleName(id) {
19
10
  return `_${id}_outcome`;
20
11
  }
@@ -24,37 +15,7 @@ function reasonRuleName(id) {
24
15
  }
25
16
 
26
17
  function defineIssueRules(definitions) {
27
- const activeOutcomes = outcomes.filter((outcome) =>
28
- definitions.some((definition) => definition.outcome === outcome),
29
- );
30
- const rules = {
31
- syntax_issue: ($) => choice(...activeOutcomes.map((outcome) => $[outcome])),
32
- };
33
-
34
- for (const outcome of activeOutcomes) {
35
- rules[outcome] = ($) => {
36
- const reasons = [
37
- ...new Set(
38
- definitions
39
- .filter((definition) => definition.outcome === outcome)
40
- .map(({ reason }) => reason),
41
- ),
42
- ].map((reason) => $[reason]);
43
- return reasons.length === 1 ? reasons[0] : choice(...reasons);
44
- };
45
- }
46
-
47
- for (const reason of new Set(definitions.map(({ reason }) => reason))) {
48
- rules[reason] = ($) => {
49
- const variants = definitions
50
- .filter((definition) => definition.reason === reason)
51
- .map((definition) => {
52
- const id = definition.id ?? reason;
53
- return $[reasonRuleName(id)];
54
- });
55
- return variants.length === 1 ? variants[0] : choice(...variants);
56
- };
57
- }
18
+ const rules = {};
58
19
 
59
20
  for (const definition of definitions) {
60
21
  const { outcome, reason, rule } = definition;
@@ -207,6 +168,13 @@ const nonWriteSubstitutionFlagRules = [
207
168
  "print_flag",
208
169
  ];
209
170
 
171
+ function postWriteSubstitutionFlagChoice($) {
172
+ return choice(
173
+ ...nonWriteSubstitutionFlagRules.map((rule) => $[rule]),
174
+ alias("w", $.substitution_flag),
175
+ );
176
+ }
177
+
210
178
  function delimiter($, name) {
211
179
  return alias($[`_${name}_delimiter`], $.delimiter);
212
180
  }
@@ -250,8 +218,6 @@ function commandListRules() {
250
218
  return {
251
219
  script: ($) => optional(alias($._script_command_list, $.command_list)),
252
220
 
253
- command_list: ($) => $._command_list,
254
-
255
221
  _script_command_list: ($) =>
256
222
  choice($._initial_suppressing_comment_command_list, $._command_list),
257
223
 
@@ -438,6 +404,14 @@ function commandListRules() {
438
404
  function addressRules(mode) {
439
405
  const expression = mode === "bre" ? "basic_reg_exp" : "extended_reg_exp";
440
406
 
407
+ function addressChoice($) {
408
+ return choice(
409
+ $.line_number_address,
410
+ $.last_line_address,
411
+ $.context_address,
412
+ );
413
+ }
414
+
441
415
  function contextAddress($, opening) {
442
416
  return seq(
443
417
  field("opening", opening),
@@ -452,84 +426,97 @@ function addressRules(mode) {
452
426
  );
453
427
  }
454
428
 
429
+ function commaSeparator($) {
430
+ return field(
431
+ "separator",
432
+ alias($._comma_address_separator, $.address_separator),
433
+ );
434
+ }
435
+
455
436
  return {
456
437
  address_clause: ($) =>
457
438
  choice(
458
439
  $._single_address_clause,
459
440
  $._double_address_clause,
460
- $._address_clause_with_excess,
441
+ $._max2_excess_address_clause,
461
442
  ),
462
443
 
463
444
  _single_address_clause: ($) => field("first", $.address),
464
445
 
465
446
  _double_address_clause: ($) =>
466
447
  choice(
467
- prec(
468
- 1,
469
- seq(
470
- field("first", $.address),
471
- field(
472
- "separator",
473
- alias($._address_separator, $.address_separator),
474
- ),
475
- field("second", $.address),
476
- ),
448
+ seq(
449
+ field("first", $.address),
450
+ field("separator", alias($._address_separator, $.address_separator)),
451
+ field("second", $.address),
477
452
  ),
478
- prec(
479
- -1,
480
- choice(
481
- seq(
482
- field(
483
- "separator",
484
- alias($._comma_address_separator, $.address_separator),
485
- ),
486
- field("second", $.address),
487
- issueField($, "omitted_first_address"),
488
- ),
489
- seq(
490
- field("first", $.address),
491
- field(
492
- "separator",
493
- alias($._comma_address_separator, $.address_separator),
494
- ),
495
- issueField($, "omitted_address"),
496
- ),
497
- seq(
498
- field(
499
- "separator",
500
- alias($._comma_address_separator, $.address_separator),
501
- ),
502
- issueField($, "omitted_address"),
503
- ),
504
- ),
453
+ seq(
454
+ issueField($, "omitted_first_address"),
455
+ commaSeparator($),
456
+ field("second", $.address),
505
457
  ),
506
- ),
507
-
508
- _address_clause_with_excess: ($) =>
509
- prec(
510
- 2,
511
458
  seq(
512
- $._double_address_clause,
513
- issueField($, "additional_address"),
514
- repeat1(field("excess", $.excess_address)),
459
+ field("first", $.address),
460
+ commaSeparator($),
461
+ $._omitted_second_address_issue,
462
+ ),
463
+ seq(
464
+ issueField($, "omitted_first_address"),
465
+ commaSeparator($),
466
+ $._omitted_second_address_issue,
515
467
  ),
516
468
  ),
517
469
 
518
- excess_address: ($) =>
470
+ _omitted_second_address_issue: ($) =>
519
471
  choice(
520
- seq(
521
- field("separator", alias($._address_separator, $.address_separator)),
522
- field("address", $.address),
472
+ issueField($, "omitted_address"),
473
+ issueField($, "incomplete_omitted_address"),
474
+ ),
475
+
476
+ _max2_excess_address_clause: ($) =>
477
+ prec.dynamic(
478
+ 1,
479
+ choice(
480
+ seq($._double_address_clause, $._max2_excess_address_tail),
481
+ seq($._max2_excess_address_clause, $._max2_excess_address_tail),
523
482
  ),
524
- seq(
525
- field(
526
- "separator",
527
- alias($._comma_address_separator, $.address_separator),
483
+ ),
484
+
485
+ _max1_address_clause: ($) =>
486
+ choice($._single_address_clause, $._max1_excess_address_clause),
487
+
488
+ _max1_excess_address_clause: ($) =>
489
+ prec.dynamic(
490
+ 1,
491
+ choice(
492
+ seq(field("first", $.address), $._max1_excess_address_tail),
493
+ seq(
494
+ issueField($, "omitted_first_address_unit1"),
495
+ $._max1_excess_address_tail,
496
+ ),
497
+ seq($._max1_excess_address_clause, $._max1_excess_address_tail),
498
+ ),
499
+ ),
500
+
501
+ _max0_address_clause: ($) =>
502
+ prec.dynamic(
503
+ 1,
504
+ choice(
505
+ issueField($, "excess_address"),
506
+ seq(
507
+ issueField($, "omitted_first_address_unit0"),
508
+ $._max0_excess_address_tail,
528
509
  ),
529
- issueField($, "omitted_address"),
510
+ seq($._max0_address_clause, $._max0_excess_address_tail),
530
511
  ),
531
512
  ),
532
513
 
514
+ _max0_excess_address_tail: ($) => issueField($, "excess_address_unit0"),
515
+
516
+ _max1_excess_address_tail: ($) => issueField($, "excess_address_unit1"),
517
+
518
+ _max2_excess_address_tail: ($) => issueField($, "excess_address_unit2"),
519
+
533
520
  _address_separator: ($) =>
534
521
  choice(
535
522
  $._comma_address_separator,
@@ -540,25 +527,23 @@ function addressRules(mode) {
540
527
  ),
541
528
 
542
529
  _comma_address_separator: ($) =>
543
- choice(
544
- prec(
545
- 1,
546
- seq(
547
- issueField($, "blanks_around_address_separator"),
548
- choice(
549
- seq($._blanks, $._address_separator_token, optional($._blanks)),
550
- seq($._address_separator_token, $._blanks),
551
- ),
552
- ),
553
- ),
530
+ seq(
531
+ optional(issueField($, "blanks_around_address_separator")),
554
532
  $._address_separator_token,
533
+ optional(issueField($, "blanks_around_address_separator")),
555
534
  ),
556
535
 
557
536
  _address_separator_token: ($) =>
558
537
  field("token", alias(",", $.address_separator_token)),
559
538
 
560
- address: ($) =>
561
- choice($.line_number_address, $.last_line_address, $.context_address),
539
+ address: addressChoice,
540
+
541
+ // Keep maximum-specific GLR paths distinct until the function verb.
542
+ _max0_address: addressChoice,
543
+
544
+ _max1_address: addressChoice,
545
+
546
+ _max2_address: addressChoice,
562
547
 
563
548
  line_number_address: () => /[[:digit:]]+/,
564
549
 
@@ -588,12 +573,6 @@ function operandRules(mode) {
588
573
  }
589
574
 
590
575
  return {
591
- substitute_function: ($) =>
592
- choice(
593
- $._substitute_function_without_write,
594
- $._substitute_function_with_write,
595
- ),
596
-
597
576
  _substitute_function_without_write: ($) =>
598
577
  choice(
599
578
  seq(
@@ -702,12 +681,6 @@ function operandRules(mode) {
702
681
  escaped_newline: ($) =>
703
682
  namedExternal($, $._replacement_escaped_newline, "escaped_newline_token"),
704
683
 
705
- substitution_flags: ($) =>
706
- choice(
707
- $._substitution_flags_without_write,
708
- $._substitution_flags_with_write,
709
- ),
710
-
711
684
  _substitution_flags_without_write: ($) =>
712
685
  choice(
713
686
  substitutionFlagChoice($),
@@ -731,6 +704,16 @@ function operandRules(mode) {
731
704
  seq(
732
705
  field("verb", alias("w", $.substitution_flag)),
733
706
  choice(
707
+ seq(
708
+ $._flag_after_write_marker,
709
+ issueField($, "flag_after_write_flag"),
710
+ repeat(postWriteSubstitutionFlagChoice($)),
711
+ $._blanks,
712
+ choice(
713
+ field("wfile", alias($._substitution_wfile, $.wfile)),
714
+ missingAtEndOrBoundary($, "missing_wfile"),
715
+ ),
716
+ ),
734
717
  seq($._blanks, field("wfile", alias($._substitution_wfile, $.wfile))),
735
718
  seq($._blanks, missingAtEndOrBoundary($, "missing_wfile")),
736
719
  seq(
@@ -799,8 +782,6 @@ function functionRules() {
799
782
  );
800
783
 
801
784
  const rules = {
802
- function: ($) => choice(...functionDefinitions.map(({ rule }) => $[rule])),
803
-
804
785
  text: ($) =>
805
786
  seq(
806
787
  repeat1(
@@ -847,77 +828,67 @@ function functionRules() {
847
828
  ),
848
829
  };
849
830
 
850
- for (const { rule, spelling } of noArgumentDefinitions) {
851
- rules[rule] = ($) => functionVerb($, spelling);
831
+ function fileForm(form) {
832
+ const missingFileReason = `missing_${form}`;
833
+ return ($) => [
834
+ choice(
835
+ seq($._blanks, field(form, $[form])),
836
+ seq($._blanks, missingAtEndOrBoundary($, missingFileReason)),
837
+ seq(issueField($, "omitted_file_separator"), field(form, $[form])),
838
+ missingAtEndOrBoundary($, missingFileReason),
839
+ ),
840
+ ];
852
841
  }
853
842
 
854
- for (const descriptor of functionDefinitions) {
855
- const { form, rule, spelling } = descriptor;
856
- if (form === undefined || form === "substitute" || form === "translate") {
857
- continue;
858
- }
859
- if (form === "block") {
860
- rules[rule] = ($) =>
861
- seq(
862
- functionVerb($, spelling),
863
- field("commands", alias($._block_commands, $.command_list)),
864
- field("closing", $.closing_brace),
865
- optional($._blanks),
866
- );
867
- } else if (form === "text") {
868
- rules[rule] = ($) =>
869
- seq(
870
- functionVerb($, spelling),
871
- choice(
872
- seq(
873
- field("introducer", $.text_introducer),
874
- choice(
875
- field("text", $.text),
876
- seq(optional(issueField($, "missing_text")), $._text_end),
877
- ),
878
- ),
879
- missingAtEndOrBoundary($, "missing_text_introducer"),
880
- ),
881
- );
882
- } else if (form === "optionalLabel") {
883
- rules[rule] = ($) =>
884
- seq(
885
- functionVerb($, spelling),
886
- optional(
887
- prec.right(
888
- seq(
889
- field("separator", alias(" ", $.argument_separator)),
890
- optional(field("label", $.label)),
891
- ),
892
- ),
893
- ),
894
- );
895
- } else if (form === "requiredLabel") {
896
- rules[rule] = ($) =>
843
+ const formArguments = {
844
+ block: ($) => [
845
+ field("commands", alias($._block_commands, $.command_list)),
846
+ field("closing", $.closing_brace),
847
+ optional($._blanks),
848
+ ],
849
+ text: ($) => [
850
+ choice(
897
851
  seq(
898
- functionVerb($, spelling),
852
+ field("introducer", $.text_introducer),
899
853
  choice(
900
- field("label", $.label),
901
- missingAtEndOrBoundary($, "missing_label"),
854
+ field("text", $.text),
855
+ seq(optional(issueField($, "missing_text")), $._text_end),
902
856
  ),
903
- );
904
- } else if (form === "rfile" || form === "wfile") {
905
- const missingFileReason =
906
- form === "rfile" ? "missing_rfile" : "missing_wfile";
907
- rules[rule] = ($) =>
908
- seq(
909
- functionVerb($, spelling),
910
- choice(
911
- seq($._blanks, field(form, $[form])),
912
- seq($._blanks, missingAtEndOrBoundary($, missingFileReason)),
913
- seq(issueField($, "omitted_file_separator"), field(form, $[form])),
914
- missingAtEndOrBoundary($, missingFileReason),
857
+ ),
858
+ missingAtEndOrBoundary($, "missing_text_introducer"),
859
+ ),
860
+ ],
861
+ optionalLabel: ($) => [
862
+ optional(
863
+ prec.right(
864
+ seq(
865
+ field("separator", alias(" ", $.argument_separator)),
866
+ optional(field("label", $.label)),
915
867
  ),
916
- );
917
- } else if (form === "comment") {
918
- rules[rule] = ($) =>
919
- seq(functionVerb($, spelling), optional(field("comment", $.comment)));
868
+ ),
869
+ ),
870
+ ],
871
+ requiredLabel: ($) => [
872
+ choice(
873
+ field("label", $.label),
874
+ missingAtEndOrBoundary($, "missing_label"),
875
+ ),
876
+ ],
877
+ rfile: fileForm("rfile"),
878
+ wfile: fileForm("wfile"),
879
+ comment: ($) => [optional(field("comment", $.comment))],
880
+ };
881
+
882
+ for (const { rule, spelling } of noArgumentDefinitions) {
883
+ rules[rule] = ($) => functionVerb($, spelling);
884
+ }
885
+
886
+ for (const { form, rule, spelling } of functionDefinitions) {
887
+ const argumentParts = formArguments[form];
888
+ if (argumentParts === undefined) {
889
+ continue;
920
890
  }
891
+ rules[rule] = ($) => seq(functionVerb($, spelling), ...argumentParts($));
921
892
  }
922
893
 
923
894
  return rules;
@@ -948,6 +919,33 @@ function editingCommandRules() {
948
919
  return alias($[name], $.function);
949
920
  }
950
921
 
922
+ function addressClauseForMaximum($, maximum) {
923
+ if (maximum === 0) {
924
+ return alias($._max0_address_clause, $.address_clause);
925
+ }
926
+ if (maximum === 1) {
927
+ return alias($._max1_address_clause, $.address_clause);
928
+ }
929
+ return $.address_clause;
930
+ }
931
+
932
+ function addressedCommand($, addresses, selectedFunction) {
933
+ return seq(
934
+ optional($._blanks),
935
+ optional(seq(field("addresses", addresses), optional($._blanks))),
936
+ optional(field("negation", $.negation)),
937
+ field("function", selectedFunction),
938
+ );
939
+ }
940
+
941
+ function addressedFunction($, functionRule) {
942
+ return addressedCommand(
943
+ $,
944
+ $.address_clause,
945
+ alias(functionRule, $.function),
946
+ );
947
+ }
948
+
951
949
  function addressedForms($, definitions, kind) {
952
950
  const forms = [];
953
951
  for (const maximum of [0, 1, 2]) {
@@ -955,98 +953,24 @@ function editingCommandRules() {
955
953
  if (names.length === 0) {
956
954
  continue;
957
955
  }
958
- const selectedFunction = functionWrapper($, wrapperName(kind, maximum));
959
-
960
- function addressedForm(
961
- addresses,
962
- { optionalAddresses = false, reportExcess = false } = {},
963
- ) {
964
- const parts = [optional($._blanks)];
965
- if (addresses !== null) {
966
- const addressed = seq(
967
- field("addresses", addresses),
968
- optional($._blanks),
969
- );
970
- parts.push(optionalAddresses ? optional(addressed) : addressed);
971
- }
972
- parts.push(
973
- optional(field("negation", $.negation)),
974
- field("function", selectedFunction),
975
- );
976
- if (reportExcess) {
977
- parts.push(issueField($, "excess_addresses"));
978
- }
979
- return seq(...parts);
980
- }
981
-
982
- if (maximum === 2) {
983
- forms.push(
984
- addressedForm($.address_clause, { optionalAddresses: true }),
985
- );
986
- continue;
987
- }
988
-
989
- const excessAddressForm = addressedForm(
990
- alias($._address_clause_with_excess, $.address_clause),
991
- { reportExcess: true },
956
+ forms.push(
957
+ addressedCommand(
958
+ $,
959
+ addressClauseForMaximum($, maximum),
960
+ functionWrapper($, wrapperName(kind, maximum)),
961
+ ),
992
962
  );
993
-
994
- if (maximum === 0) {
995
- forms.push(
996
- choice(
997
- addressedForm(null),
998
- addressedForm(
999
- alias(
1000
- choice($._single_address_clause, $._double_address_clause),
1001
- $.address_clause,
1002
- ),
1003
- { reportExcess: true },
1004
- ),
1005
- excessAddressForm,
1006
- ),
1007
- );
1008
- } else {
1009
- forms.push(
1010
- choice(
1011
- addressedForm(alias($._single_address_clause, $.address_clause), {
1012
- optionalAddresses: true,
1013
- }),
1014
- addressedForm(alias($._double_address_clause, $.address_clause), {
1015
- reportExcess: true,
1016
- }),
1017
- excessAddressForm,
1018
- ),
1019
- );
1020
- }
1021
963
  }
1022
964
 
1023
965
  return choice(...forms);
1024
966
  }
1025
967
 
1026
968
  const rules = {
1027
- editing_command: ($) =>
1028
- choice(
1029
- $._chainable_editing_command,
1030
- $._line_terminated_editing_command,
1031
- $._recovered_line_terminated_editing_command,
1032
- $._recovered_editing_command,
1033
- ),
1034
-
1035
969
  _chainable_editing_command: ($) =>
1036
970
  seq(
1037
971
  choice(
1038
972
  addressedForms($, chainable, "chainable"),
1039
- seq(
1040
- optional($._blanks),
1041
- optional(
1042
- seq(field("addresses", $.address_clause), optional($._blanks)),
1043
- ),
1044
- optional(field("negation", $.negation)),
1045
- field(
1046
- "function",
1047
- alias($._chainable_substitute_function, $.function),
1048
- ),
1049
- ),
973
+ addressedFunction($, $._chainable_substitute_function),
1050
974
  ),
1051
975
  optional(issueField($, "unexpected_command_text")),
1052
976
  ),
@@ -1084,28 +1008,11 @@ function editingCommandRules() {
1084
1008
  addressedForms($, lineTerminated, "line_terminated"),
1085
1009
 
1086
1010
  _line_terminated_substitute_editing_command_body: ($) =>
1087
- seq(
1088
- optional($._blanks),
1089
- optional(
1090
- seq(field("addresses", $.address_clause), optional($._blanks)),
1091
- ),
1092
- optional(field("negation", $.negation)),
1093
- field(
1094
- "function",
1095
- alias($._line_terminated_substitute_function, $.function),
1096
- ),
1097
- ),
1011
+ addressedFunction($, $._line_terminated_substitute_function),
1098
1012
 
1099
1013
  _recovered_editing_command: ($) =>
1100
1014
  choice(
1101
- seq(
1102
- optional($._blanks),
1103
- optional(
1104
- seq(field("addresses", $.address_clause), optional($._blanks)),
1105
- ),
1106
- optional(field("negation", $.negation)),
1107
- field("function", alias($._unknown_function, $.function)),
1108
- ),
1015
+ addressedFunction($, $._unknown_function),
1109
1016
  seq(
1110
1017
  optional($._blanks),
1111
1018
  optional(
@@ -1160,7 +1067,6 @@ function editingCommandRules() {
1160
1067
  repeat(
1161
1068
  seq(
1162
1069
  optional(issueField($, "blanks_after_negation")),
1163
- field("operator", alias("!", $.negation_operator)),
1164
1070
  issueField($, "duplicate_negation"),
1165
1071
  ),
1166
1072
  ),
@@ -1197,12 +1103,11 @@ function sedRules(mode) {
1197
1103
  function missingMarkerNames(mode) {
1198
1104
  return [
1199
1105
  "omitted_address",
1106
+ "incomplete_omitted_address",
1200
1107
  "omitted_first_address",
1201
1108
  "empty_subexpression",
1202
1109
  "missing_subexpression",
1203
1110
  ...(mode === "ere" ? ["empty_alternative"] : []),
1204
- "excess_addresses",
1205
- "additional_address",
1206
1111
  "missing_function",
1207
1112
  "missing_label",
1208
1113
  "missing_rfile",
@@ -1211,9 +1116,7 @@ function missingMarkerNames(mode) {
1211
1116
  "missing_text_introducer",
1212
1117
  "missing_text",
1213
1118
  "missing_command_separator",
1214
- "blanks_around_address_separator",
1215
1119
  "missing_address_separator",
1216
- "duplicate_negation",
1217
1120
  "missing_closing_brace",
1218
1121
  "missing_opening_delimiter",
1219
1122
  "missing_separator_before_unmatched_brace",
@@ -1272,12 +1175,25 @@ function issueDefinitions(mode) {
1272
1175
  outcome: "undefined_syntax",
1273
1176
  rule: missing("omitted_address"),
1274
1177
  },
1178
+ {
1179
+ id: "incomplete_omitted_address",
1180
+ reason: "omitted_address",
1181
+ outcome: "incomplete_syntax",
1182
+ rule: missing("incomplete_omitted_address"),
1183
+ },
1275
1184
  {
1276
1185
  id: "omitted_first_address",
1277
1186
  reason: "omitted_address",
1278
1187
  outcome: "undefined_syntax",
1279
1188
  rule: missing("omitted_first_address"),
1280
1189
  },
1190
+ // The marker is shared, but maximum-specific reductions must not merge.
1191
+ ...[0, 1].map((maximum) => ({
1192
+ id: `omitted_first_address_unit${maximum}`,
1193
+ reason: "omitted_address",
1194
+ outcome: "undefined_syntax",
1195
+ rule: missing("omitted_first_address"),
1196
+ })),
1281
1197
  {
1282
1198
  reason: "ordinary_character_escape",
1283
1199
  outcome: "undefined_syntax",
@@ -1298,12 +1214,22 @@ function issueDefinitions(mode) {
1298
1214
  {
1299
1215
  reason: "unmatched_subexpression_close",
1300
1216
  outcome: "undefined_syntax",
1301
- rule: ($) => $._regex_unmatched_group_close,
1217
+ rule: ($) =>
1218
+ namedExternal(
1219
+ $,
1220
+ $._regex_unmatched_group_close,
1221
+ "back_close_parenthesis_token",
1222
+ ),
1302
1223
  },
1303
1224
  {
1304
1225
  reason: "unmatched_interval_close",
1305
1226
  outcome: "undefined_syntax",
1306
- rule: ($) => $._unmatched_interval_close_marker,
1227
+ rule: ($) =>
1228
+ namedExternal(
1229
+ $,
1230
+ $._regex_unmatched_interval_close,
1231
+ "back_close_brace",
1232
+ ),
1307
1233
  },
1308
1234
  ]
1309
1235
  : []),
@@ -1351,6 +1277,11 @@ function issueDefinitions(mode) {
1351
1277
  outcome: "undefined_syntax",
1352
1278
  rule: () => token.immediate(";"),
1353
1279
  },
1280
+ {
1281
+ reason: "flag_after_write_flag",
1282
+ outcome: "undefined_syntax",
1283
+ rule: ($) => postWriteSubstitutionFlagChoice($),
1284
+ },
1354
1285
  {
1355
1286
  reason: "equivalence_class_range_start",
1356
1287
  outcome: "unspecified_syntax",
@@ -1369,7 +1300,7 @@ function issueDefinitions(mode) {
1369
1300
  {
1370
1301
  reason: "blanks_after_negation",
1371
1302
  outcome: "unspecified_syntax",
1372
- rule: () => token.immediate(/[[:blank:]]+/),
1303
+ rule: ($) => alias(token.immediate(/[[:blank:]]+/), $.blank),
1373
1304
  },
1374
1305
  {
1375
1306
  reason: "unspecified_replacement_escape",
@@ -1426,15 +1357,32 @@ function issueDefinitions(mode) {
1426
1357
  rule: missing("omitted_file_separator"),
1427
1358
  },
1428
1359
  {
1429
- reason: "excess_addresses",
1360
+ reason: "excess_address",
1430
1361
  outcome: "nonconforming_syntax",
1431
- rule: missing("excess_addresses"),
1362
+ rule: ($) => field("address", alias($._max0_address, $.address)),
1432
1363
  },
1433
- {
1434
- reason: "additional_address",
1364
+ ...[0, 1, 2].map((maximum) => ({
1365
+ id: `excess_address_unit${maximum}`,
1366
+ reason: "excess_address",
1435
1367
  outcome: "nonconforming_syntax",
1436
- rule: missing("additional_address"),
1437
- },
1368
+ rule: ($) =>
1369
+ choice(
1370
+ seq(
1371
+ field(
1372
+ "separator",
1373
+ alias($._address_separator, $.address_separator),
1374
+ ),
1375
+ field("address", alias($[`_max${maximum}_address`], $.address)),
1376
+ ),
1377
+ seq(
1378
+ field(
1379
+ "separator",
1380
+ alias($._comma_address_separator, $.address_separator),
1381
+ ),
1382
+ $._omitted_second_address_issue,
1383
+ ),
1384
+ ),
1385
+ })),
1438
1386
  {
1439
1387
  reason: "unknown_function",
1440
1388
  outcome: "nonconforming_syntax",
@@ -1463,7 +1411,7 @@ function issueDefinitions(mode) {
1463
1411
  {
1464
1412
  reason: "blanks_around_address_separator",
1465
1413
  outcome: "nonconforming_syntax",
1466
- rule: missing("blanks_around_address_separator"),
1414
+ rule: ($) => alias($._blanks_around_address_separator, $.blank),
1467
1415
  },
1468
1416
  {
1469
1417
  reason: "missing_address_separator",
@@ -1473,7 +1421,7 @@ function issueDefinitions(mode) {
1473
1421
  {
1474
1422
  reason: "duplicate_negation",
1475
1423
  outcome: "nonconforming_syntax",
1476
- rule: missing("duplicate_negation"),
1424
+ rule: ($) => field("operator", alias("!", $.negation_operator)),
1477
1425
  },
1478
1426
  {
1479
1427
  reason: "unmatched_closing_brace",
@@ -1701,7 +1649,6 @@ function externalTokens($, mode) {
1701
1649
  $._bre_question_mark_escape_marker,
1702
1650
  $._bre_plus_escape_marker,
1703
1651
  $._regex_unmatched_interval_close,
1704
- $._unmatched_interval_close_marker,
1705
1652
  ]
1706
1653
  : []),
1707
1654
  ...(mode === "ere" ? [$._regex_alternation_operator] : []),
@@ -1767,6 +1714,7 @@ function externalTokens($, mode) {
1767
1714
  $._translate_unterminated_source,
1768
1715
  $._translate_unterminated_destination,
1769
1716
  $._invalid_substitution_flag,
1717
+ $._flag_after_write_marker,
1770
1718
  $._text_command_start,
1771
1719
  $._text_literal,
1772
1720
  $._text_backslash_escape,
@@ -1790,6 +1738,7 @@ function externalTokens($, mode) {
1790
1738
  $._replacement_line_unterminated,
1791
1739
  $._translate_line_unterminated_source,
1792
1740
  $._translate_line_unterminated_destination,
1741
+ $._blanks_around_address_separator,
1793
1742
  ...missingMarkerNames(mode).map((name) => $[`_${name}_marker`]),
1794
1743
  $._error_sentinel,
1795
1744
  ];
@@ -1808,16 +1757,19 @@ function defineGrammar(name, mode) {
1808
1757
  extras: () => [],
1809
1758
 
1810
1759
  conflicts: ($) => [
1760
+ [$.address_clause, $._recovered_editing_command],
1761
+ [$.address_clause, $._max1_address_clause],
1762
+ [$._double_address_clause, $._excess_address_unit1_reason],
1763
+ [$.address, $._max0_address],
1764
+ [$.address, $._max1_address],
1811
1765
  [
1812
- $.address_clause,
1813
- $._chainable_editing_command,
1814
- $._line_terminated_regular_editing_command_body,
1766
+ $._omitted_first_address_reason,
1767
+ $._omitted_first_address_unit0_reason,
1768
+ $._omitted_first_address_unit1_reason,
1815
1769
  ],
1816
1770
  [
1817
- $.address_clause,
1818
- $._chainable_editing_command,
1819
- $._line_terminated_regular_editing_command_body,
1820
- $._recovered_editing_command,
1771
+ $._omitted_first_address_unit0_reason,
1772
+ $._omitted_first_address_unit1_reason,
1821
1773
  ],
1822
1774
  ],
1823
1775