@konomanoasa/tree-sitter-diagnostic-sh 0.1.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/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "konomanoasa-tree-sitter-diagnostic-sh"
3
- version = "0.1.0"
3
+ version = "0.3.0"
4
4
  edition = "2024"
5
5
  description = "Tree-sitter grammar for diagnostics of POSIX sh."
6
6
  readme = "README.md"
@@ -27,11 +27,11 @@ name = "bindings"
27
27
  path = "test/bindings.test.rs"
28
28
 
29
29
  [dependencies]
30
- tree-sitter-language = "0.1.8"
30
+ tree-sitter-language = "=0.1.9"
31
31
 
32
32
  [dev-dependencies]
33
- serde_json = "1"
34
- tree-sitter = "0.27.1"
33
+ serde_json = "=1.0.154"
34
+ tree-sitter = "=0.27.1"
35
35
 
36
36
  [build-dependencies]
37
- cc = "1.4"
37
+ cc = "=1.9.0"
package/README.md CHANGED
@@ -17,8 +17,6 @@ npm install @konomanoasa/tree-sitter-diagnostic-sh
17
17
 
18
18
  ## Development
19
19
 
20
- Development uses Node.js 24.21.0 or later.
21
-
22
20
  ```sh
23
21
  npm install
24
22
  npm run build
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konomanoasa/tree-sitter-diagnostic-sh",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Tree-sitter grammar for diagnostics of POSIX sh.",
6
6
  "license": "MIT",
@@ -24,10 +24,11 @@
24
24
  "check": "biome check . && tsc --noEmit",
25
25
  "format": "biome format --write .",
26
26
  "generate": "npm run tree-sitter -- generate-all",
27
- "test": "node --test --test-concurrency=1 \"test/*.test.js\"",
27
+ "test": "node --test --test-concurrency=1 test/cst.test.js test/parser.test.js",
28
28
  "test:sanitize": "node scripts/check-c.js --sanitize",
29
29
  "test:scanner": "node scripts/check-c.js",
30
- "tree-sitter": "node scripts/tree-sitter.js"
30
+ "tree-sitter": "node scripts/tree-sitter.js",
31
+ "test:performance": "node --test test/performance.test.js"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@biomejs/biome": "2.5.15",
package/src/parser.c CHANGED
@@ -15375,7 +15375,7 @@ TS_PUBLIC const TSLanguage *tree_sitter_diagnostic_sh(void) {
15375
15375
  .max_reserved_word_set_size = 0,
15376
15376
  .metadata = {
15377
15377
  .major_version = 0,
15378
- .minor_version = 1,
15378
+ .minor_version = 3,
15379
15379
  .patch_version = 0,
15380
15380
  },
15381
15381
  };
package/src/scanner.c CHANGED
@@ -147,6 +147,7 @@ struct Capture {
147
147
  size_t count, capacity;
148
148
  bool quoted, failed, newlines_only;
149
149
  struct Unit newline;
150
+ size_t uncertain;
150
151
  struct Capture *parent;
151
152
  };
152
153
  struct Rejected {
@@ -179,8 +180,7 @@ struct Owner {
179
180
  };
180
181
  // Defer region extension until publication so rejected probes can discard it.
181
182
  struct Extension {
182
- size_t issue;
183
- struct Recovery recovery;
183
+ size_t position, resume;
184
184
  };
185
185
  struct Plan {
186
186
  struct Issue *issues;
@@ -723,6 +723,213 @@ enum {
723
723
  ARITHMETIC_RULE_COUNT = sizeof(arithmetic_rules) / sizeof(*arithmetic_rules)
724
724
  };
725
725
 
726
+ // Only assignment needs lvalue tracking; operator results are non-lvalues.
727
+ enum ArithmeticOperator {
728
+ ARITHMETIC_OP_LEFT,
729
+ ARITHMETIC_OP_QUESTION,
730
+ ARITHMETIC_OP_ASSIGN,
731
+ ARITHMETIC_OP_CONDITIONAL,
732
+ ARITHMETIC_OP_BINARY,
733
+ ARITHMETIC_OP_UNARY,
734
+ };
735
+
736
+ static unsigned arithmetic_precedence(enum ArithmeticOperator operation) {
737
+ switch (operation) {
738
+ case ARITHMETIC_OP_ASSIGN:
739
+ return 1;
740
+ case ARITHMETIC_OP_QUESTION:
741
+ case ARITHMETIC_OP_CONDITIONAL:
742
+ return 2;
743
+ case ARITHMETIC_OP_BINARY:
744
+ return 3;
745
+ case ARITHMETIC_OP_UNARY:
746
+ return 4;
747
+ default:
748
+ return 0;
749
+ }
750
+ }
751
+
752
+ static bool arithmetic_reduce(
753
+ enum ArithmeticOperator operation,
754
+ uint8_t *values,
755
+ size_t *count
756
+ ) {
757
+ size_t operands = operation == ARITHMETIC_OP_UNARY ? 1
758
+ : operation == ARITHMETIC_OP_CONDITIONAL ? 3
759
+ : 2;
760
+ if (
761
+ operation == ARITHMETIC_OP_LEFT ||
762
+ operation == ARITHMETIC_OP_QUESTION ||
763
+ *count < operands
764
+ )
765
+ return false;
766
+ *count -= operands;
767
+ if (operation == ARITHMETIC_OP_ASSIGN && !values[*count]) return false;
768
+ values[(*count)++] = false;
769
+ return true;
770
+ }
771
+
772
+ static enum ArithmeticValidation
773
+ arithmetic_literal(const int32_t *source, size_t length) {
774
+ uint8_t *values = ts_malloc((length + 1) * sizeof(*values));
775
+ uint8_t *operations = ts_malloc((length + 1) * sizeof(*operations));
776
+ enum ArithmeticValidation result = ARITHMETIC_VALIDATION_RESOURCE_FAILURE;
777
+ if (!values || !operations) goto finish;
778
+ result = ARITHMETIC_VALIDATION_INVALID;
779
+ size_t value_count = 0, operation_count = 0, position = 0;
780
+ bool operand = true;
781
+ while (position < length) {
782
+ if (arithmetic_whitespace(source[position])) {
783
+ position++;
784
+ continue;
785
+ }
786
+ int state = LEX_START;
787
+ while (position < length) {
788
+ int next = arithmetic_lex((uint8_t)state, source[position]);
789
+ if (next == LEX_INVALID) goto finish;
790
+ if (next == LEX_STOP) break;
791
+ state = next;
792
+ position++;
793
+ }
794
+ uint16_t token = arithmetic_tokens[state];
795
+ if (!token) goto finish;
796
+ if (operand) {
797
+ if (token & (1u << ARITHMETIC_UNARY_OPERATOR))
798
+ operations[operation_count++] = ARITHMETIC_OP_UNARY;
799
+ else if (token & (1u << ARITHMETIC_LEFT))
800
+ operations[operation_count++] = ARITHMETIC_OP_LEFT;
801
+ else if (token & ((1u << ARITHMETIC_NAME) | (1u << ARITHMETIC_NUMBER))) {
802
+ values[value_count++] = (token & (1u << ARITHMETIC_NAME)) != 0;
803
+ operand = false;
804
+ } else goto finish;
805
+ continue;
806
+ }
807
+ if (token & ((1u << ARITHMETIC_RIGHT) | (1u << ARITHMETIC_COLON))) {
808
+ enum ArithmeticOperator stop = token & (1u << ARITHMETIC_RIGHT)
809
+ ? ARITHMETIC_OP_LEFT
810
+ : ARITHMETIC_OP_QUESTION;
811
+ while (operation_count && operations[operation_count - 1] != stop) {
812
+ if (!arithmetic_reduce(
813
+ operations[--operation_count],
814
+ values,
815
+ &value_count
816
+ ))
817
+ goto finish;
818
+ }
819
+ if (!operation_count) goto finish;
820
+ if (stop == ARITHMETIC_OP_LEFT) operation_count--;
821
+ else {
822
+ operations[operation_count - 1] = ARITHMETIC_OP_CONDITIONAL;
823
+ operand = true;
824
+ }
825
+ continue;
826
+ }
827
+ enum ArithmeticOperator operation;
828
+ if (token & (1u << ARITHMETIC_ASSIGNMENT_OPERATOR))
829
+ operation = ARITHMETIC_OP_ASSIGN;
830
+ else if (token & (1u << ARITHMETIC_QUESTION))
831
+ operation = ARITHMETIC_OP_QUESTION;
832
+ else if (token & (1u << ARITHMETIC_BINARY_OPERATOR))
833
+ operation = ARITHMETIC_OP_BINARY;
834
+ else goto finish;
835
+ while (operation_count) {
836
+ enum ArithmeticOperator previous = operations[operation_count - 1];
837
+ if (previous == ARITHMETIC_OP_LEFT || previous == ARITHMETIC_OP_QUESTION)
838
+ break;
839
+ if (
840
+ arithmetic_precedence(previous) < arithmetic_precedence(operation) ||
841
+ (arithmetic_precedence(previous) == arithmetic_precedence(operation) &&
842
+ operation != ARITHMETIC_OP_BINARY)
843
+ )
844
+ break;
845
+ operation_count--;
846
+ if (!arithmetic_reduce(previous, values, &value_count)) goto finish;
847
+ }
848
+ operations[operation_count++] = operation;
849
+ operand = true;
850
+ }
851
+ if (operand) goto finish;
852
+ while (operation_count)
853
+ if (!arithmetic_reduce(operations[--operation_count], values, &value_count))
854
+ goto finish;
855
+ if (value_count == 1) result = ARITHMETIC_VALIDATION_VALID;
856
+ finish:
857
+ ts_free(values);
858
+ ts_free(operations);
859
+ return result;
860
+ }
861
+
862
+ // Holes leave the outer stack unknown; only later punctuation constrains it.
863
+ // Check each outermost hole-free group once.
864
+ static enum ArithmeticValidation
865
+ arithmetic_check_structure(const int32_t *source, size_t length) {
866
+ struct ArithmeticGroup {
867
+ size_t start, end;
868
+ };
869
+ struct ArithmeticGroup *groups = NULL;
870
+ size_t *stack = NULL;
871
+ size_t group_count = 0, group_capacity = 0, depth = 0, capacity = 0;
872
+ bool unknown = false;
873
+ enum ArithmeticValidation result = ARITHMETIC_VALIDATION_VALID;
874
+ for (size_t i = 0; i < length; i++) {
875
+ int32_t c = source[i];
876
+ if (c == -1) {
877
+ depth = 0;
878
+ unknown = true;
879
+ } else if (c == '(' || c == '?') {
880
+ size_t *grown =
881
+ diagnostic_grow(stack, &capacity, depth + 1, sizeof(*grown));
882
+ if (!grown) {
883
+ result = ARITHMETIC_VALIDATION_RESOURCE_FAILURE;
884
+ goto finish;
885
+ }
886
+ stack = grown;
887
+ stack[depth++] = i;
888
+ } else if (c == ')' || c == ':') {
889
+ if (!depth) {
890
+ if (unknown) continue;
891
+ result = ARITHMETIC_VALIDATION_INVALID;
892
+ goto finish;
893
+ }
894
+ size_t start = stack[--depth];
895
+ if (source[start] != (c == ')' ? '(' : '?')) {
896
+ result = ARITHMETIC_VALIDATION_INVALID;
897
+ goto finish;
898
+ }
899
+ if (c != ')') continue;
900
+ while (group_count && groups[group_count - 1].start > start)
901
+ group_count--;
902
+ struct ArithmeticGroup *grown = diagnostic_grow(
903
+ groups,
904
+ &group_capacity,
905
+ group_count + 1,
906
+ sizeof(*grown)
907
+ );
908
+ if (!grown) {
909
+ result = ARITHMETIC_VALIDATION_RESOURCE_FAILURE;
910
+ goto finish;
911
+ }
912
+ groups = grown;
913
+ groups[group_count++] = (struct ArithmeticGroup){start, i + 1};
914
+ }
915
+ }
916
+ if (depth) {
917
+ result = ARITHMETIC_VALIDATION_INVALID;
918
+ goto finish;
919
+ }
920
+ for (size_t i = 0; i < group_count; i++) {
921
+ result = arithmetic_literal(
922
+ source + groups[i].start,
923
+ groups[i].end - groups[i].start
924
+ );
925
+ if (result != ARITHMETIC_VALIDATION_VALID) break;
926
+ }
927
+ finish:
928
+ ts_free(groups);
929
+ ts_free(stack);
930
+ return result;
931
+ }
932
+
726
933
  static uint8_t
727
934
  arithmetic_symbol(const struct ArithmeticRule *rule, size_t dot) {
728
935
  return rule->right[rule->length - dot - 1];
@@ -1279,6 +1486,12 @@ validate_arithmetic(const int32_t *source, size_t length) {
1279
1486
  ) {
1280
1487
  return result;
1281
1488
  }
1489
+ bool dynamic = false;
1490
+ for (size_t i = 0; i < length; i++) dynamic |= source[i] == -1;
1491
+ if (!dynamic) return arithmetic_literal(source, length);
1492
+ result = arithmetic_check_structure(source, length);
1493
+ if (result != ARITHMETIC_VALIDATION_VALID) return result;
1494
+ result = ARITHMETIC_VALIDATION_RESOURCE_FAILURE;
1282
1495
  graph.vertex_ids = ts_malloc((length + 1) * sizeof(*graph.vertex_ids));
1283
1496
  graph.visited = ts_calloc(length + 1, sizeof(*graph.visited));
1284
1497
  graph.emitted = ts_calloc(length + 1, sizeof(*graph.emitted));
@@ -1382,6 +1595,17 @@ static void capture_change(
1382
1595
  }
1383
1596
  }
1384
1597
 
1598
+ static void capture_uncertain(struct Input *input, size_t position) {
1599
+ for (
1600
+ struct Capture *capture = input->capture; capture; capture = capture->parent
1601
+ )
1602
+ if (
1603
+ !capture->newlines_only &&
1604
+ (!capture->uncertain || position < capture->uncertain - 1)
1605
+ )
1606
+ capture->uncertain = position + 1;
1607
+ }
1608
+
1385
1609
  static void capture_newline(struct Input *input, struct Unit unit) {
1386
1610
  if (unit.value == '\n')
1387
1611
  for (
@@ -1445,7 +1669,6 @@ static bool expansion_escape(int32_t c) {
1445
1669
  return c == '$' || c == '`' || c == '\\';
1446
1670
  }
1447
1671
 
1448
- // Bound recursion to protect the C stack.
1449
1672
  enum { NESTING_LIMIT = 512 };
1450
1673
 
1451
1674
  static bool nest(struct Parser *parser) {
@@ -1660,7 +1883,8 @@ static void issue(
1660
1883
  report(parser, fault, recovery);
1661
1884
  }
1662
1885
 
1663
- static void extend_issue(struct Parser *parser, size_t index, size_t resume) {
1886
+ static void
1887
+ extend_region(struct Parser *parser, size_t position, size_t resume) {
1664
1888
  struct Plan *plan = parser->plan;
1665
1889
  if (plan->failed) return;
1666
1890
  struct Extension *grown = diagnostic_grow(
@@ -1675,7 +1899,7 @@ static void extend_issue(struct Parser *parser, size_t index, size_t resume) {
1675
1899
  }
1676
1900
  plan->extensions = grown;
1677
1901
  plan->extensions[plan->extension_count++] =
1678
- (struct Extension){index, {resume, false}};
1902
+ (struct Extension){position, resume};
1679
1903
  }
1680
1904
 
1681
1905
  static bool backquote_cut(struct Parser *parser, enum TokenType kind) {
@@ -1976,6 +2200,19 @@ static void parameter(struct Parser *parser, bool double_quoted) {
1976
2200
  parser->closer = parent_closer;
1977
2201
  }
1978
2202
 
2203
+ static bool undecidable_delimiter_fault(enum TokenType kind) {
2204
+ switch (kind) {
2205
+ case UNSPECIFIED_DOLLAR_QUOTE_ESCAPE:
2206
+ case UNSPECIFIED_EMPTY_HEX_ESCAPE:
2207
+ case UNSPECIFIED_CONTROL_ESCAPE:
2208
+ case UNSPECIFIED_HEX_ESCAPE:
2209
+ case UNSPECIFIED_OCTAL_ESCAPE:
2210
+ return true;
2211
+ default:
2212
+ return false;
2213
+ }
2214
+ }
2215
+
1979
2216
  static int hex_value(int32_t c) {
1980
2217
  if (is_decimal_digit(c)) return c - '0';
1981
2218
  if (c >= 'a' && c <= 'f') return c - 'a' + 10;
@@ -2079,6 +2316,7 @@ dollar_quote_escape(struct Parser *parser, struct Unit *unit) {
2079
2316
  unit->end = input->units[input->position - 1].end;
2080
2317
  capture_change(input, *unit, unit->value, true);
2081
2318
  }
2319
+ if (undecidable_delimiter_fault(fault)) capture_uncertain(input, unit->start);
2082
2320
  if (!end) end = unit->end;
2083
2321
  owner_end(parser, owner, end);
2084
2322
  return (struct Fault){fault, unit->start, end, owner};
@@ -2088,28 +2326,26 @@ static void dollar_quote(struct Parser *parser, struct Text *text) {
2088
2326
  struct Input *input = &parser->input;
2089
2327
  struct Unit opening = pop(input);
2090
2328
  capture_change(input, opening, -1, true);
2091
- struct Fault fault = {.kind = TOKEN_TYPE_COUNT};
2092
- size_t boundary = input->end;
2093
2329
  bool closed = false;
2094
2330
  while (!parser->plan->failed && input->position < input->count) {
2095
2331
  struct Unit unit = read_raw(input);
2096
2332
  if (unit.value == '\'') {
2097
2333
  capture_change(input, unit, -1, true);
2098
- boundary = unit.start;
2099
2334
  closed = true;
2100
2335
  break;
2101
2336
  }
2102
2337
  if (unit.value == '\\') {
2103
2338
  struct Fault escape = dollar_quote_escape(parser, &unit);
2104
- if (fault.kind == TOKEN_TYPE_COUNT && escape.kind != TOKEN_TYPE_COUNT)
2105
- fault = escape;
2339
+ if (escape.kind != TOKEN_TYPE_COUNT)
2340
+ report(
2341
+ parser,
2342
+ escape,
2343
+ (struct Recovery){escape.end, escape.kind != INCOMPLETE_ESCAPE}
2344
+ );
2106
2345
  }
2107
2346
  text_push(parser, text, unit);
2108
2347
  }
2109
- if (fault.kind != TOKEN_TYPE_COUNT)
2110
- report(parser, fault, (struct Recovery){boundary, closed});
2111
- else if (!closed)
2112
- unclosed(parser, UNDEFINED_BACKQUOTE_QUOTE, DOLLAR_QUOTE_GAP);
2348
+ if (!closed) unclosed(parser, UNDEFINED_BACKQUOTE_QUOTE, DOLLAR_QUOTE_GAP);
2113
2349
  }
2114
2350
 
2115
2351
  struct BackquoteInput {
@@ -2288,6 +2524,8 @@ arithmetic_valid(struct Plan *plan, const struct Text *content, bool open) {
2288
2524
  static void
2289
2525
  replay_capture(struct Parser *parser, const struct Capture *capture) {
2290
2526
  if (capture->newline.end) capture_newline(&parser->input, capture->newline);
2527
+ if (capture->uncertain)
2528
+ capture_uncertain(&parser->input, capture->uncertain - 1);
2291
2529
  for (size_t i = 0; i < capture->count; i++) {
2292
2530
  struct Change change = capture->changes[i];
2293
2531
  capture_change(
@@ -2560,7 +2798,6 @@ static struct Token lex(struct Parser *parser) {
2560
2798
  return token;
2561
2799
  }
2562
2800
 
2563
- // A newline inside a saved token cannot start the pending here-document body.
2564
2801
  static void saved_token_newline(struct Parser *parser, struct Unit newline) {
2565
2802
  if (!newline.end || parser->token.kind == NEWLINE) return;
2566
2803
  report(
@@ -2824,15 +3061,15 @@ static void documents(struct Parser *parser, bool at_line_start) {
2824
3061
  for (size_t document = 0; document < parser->document_count; document++) {
2825
3062
  struct Document *here = &parser->documents[document];
2826
3063
  if (here->uncertain) {
2827
- extend_issue(parser, here->uncertain - 1, input->end);
3064
+ extend_region(parser, here->uncertain - 1, input->end);
2828
3065
  input->position = input->count;
2829
- at_line_start = false;
2830
- } else {
2831
- at_line_start = here_document(parser, here, at_line_start, body_start);
2832
- body_start = consumed_offset(input);
3066
+ break;
2833
3067
  }
2834
- ts_free(here->delimiter.units);
3068
+ at_line_start = here_document(parser, here, at_line_start, body_start);
3069
+ body_start = consumed_offset(input);
2835
3070
  }
3071
+ for (size_t document = 0; document < parser->document_count; document++)
3072
+ ts_free(parser->documents[document].delimiter.units);
2836
3073
  parser->document_count = 0;
2837
3074
  }
2838
3075
 
@@ -2992,23 +3229,10 @@ static bool stop_or_end(struct Parser *parser, uint32_t stops) {
2992
3229
  return token->kind == END || (stop(token) & (stops | parser->stops));
2993
3230
  }
2994
3231
 
2995
- static bool undecidable_delimiter_fault(enum TokenType kind) {
2996
- switch (kind) {
2997
- case UNSPECIFIED_DOLLAR_QUOTE_ESCAPE:
2998
- case UNSPECIFIED_EMPTY_HEX_ESCAPE:
2999
- case UNSPECIFIED_CONTROL_ESCAPE:
3000
- case UNSPECIFIED_HEX_ESCAPE:
3001
- case UNSPECIFIED_OCTAL_ESCAPE:
3002
- return true;
3003
- default:
3004
- return false;
3005
- }
3006
- }
3007
-
3008
3232
  // Decode faults precede word faults and are sorted by start.
3009
- static size_t uncertain_delimiter(
3233
+ static size_t uncertain_position(
3010
3234
  const struct Plan *plan,
3011
- size_t since,
3235
+ const struct Capture *capture,
3012
3236
  size_t start,
3013
3237
  size_t end
3014
3238
  ) {
@@ -3018,18 +3242,13 @@ static size_t uncertain_delimiter(
3018
3242
  if (plan->issues[middle].start < start) low = middle + 1;
3019
3243
  else high = middle;
3020
3244
  }
3021
- size_t uncertain =
3022
- low < plan->decode_issues && plan->issues[low].start < end ? low + 1 : 0;
3023
- for (size_t i = since; i < plan->issue_count; i++) {
3024
- const struct Issue *found = &plan->issues[i];
3025
- if (
3026
- found->start >= start &&
3027
- found->start < end &&
3028
- undecidable_delimiter_fault(found->kind) &&
3029
- (!uncertain || found->start < plan->issues[uncertain - 1].start)
3030
- )
3031
- uncertain = i + 1;
3032
- }
3245
+ size_t uncertain = capture->uncertain;
3246
+ if (
3247
+ low < plan->decode_issues &&
3248
+ plan->issues[low].start < end &&
3249
+ (!uncertain || plan->issues[low].start < uncertain - 1)
3250
+ )
3251
+ uncertain = plan->issues[low].start + 1;
3033
3252
  return uncertain;
3034
3253
  }
3035
3254
 
@@ -3038,7 +3257,6 @@ static bool declare_here_document(
3038
3257
  bool strip,
3039
3258
  const struct Capture *capture,
3040
3259
  size_t begin,
3041
- size_t since,
3042
3260
  const struct Token *target
3043
3261
  ) {
3044
3262
  struct Document *grown = diagnostic_grow(
@@ -3065,7 +3283,7 @@ static bool declare_here_document(
3065
3283
  .quoted = capture->quoted,
3066
3284
  .strip = strip,
3067
3285
  .uncertain =
3068
- uncertain_delimiter(parser->plan, since, target->start, target->end)
3286
+ uncertain_position(parser->plan, capture, target->start, target->end)
3069
3287
  };
3070
3288
  return true;
3071
3289
  }
@@ -3078,7 +3296,7 @@ enum RecoveryBoundary {
3078
3296
  static bool redirect_operand(struct Parser *parser, enum Lexeme kind) {
3079
3297
  bool here = kind == HERE || kind == HERE_STRIP;
3080
3298
  struct Capture capture = {.parent = parser->input.capture};
3081
- size_t begin = parser->input.position, since = parser->plan->issue_count;
3299
+ size_t begin = parser->input.position;
3082
3300
  bool quoted_delimiter = parser->quoted_delimiter;
3083
3301
  if (here) {
3084
3302
  parser->input.capture = &capture;
@@ -3097,7 +3315,6 @@ static bool redirect_operand(struct Parser *parser, enum Lexeme kind) {
3097
3315
  kind == HERE_STRIP,
3098
3316
  &capture,
3099
3317
  begin,
3100
- since,
3101
3318
  target
3102
3319
  ))
3103
3320
  )
@@ -3773,6 +3990,11 @@ static int compare_issues(const void *left, const void *right) {
3773
3990
  return a->start < b->start ? -1 : a->start != b->start;
3774
3991
  }
3775
3992
 
3993
+ static int compare_extensions(const void *left, const void *right) {
3994
+ const struct Extension *a = left, *b = right;
3995
+ return a->position < b->position ? -1 : a->position != b->position;
3996
+ }
3997
+
3776
3998
  static bool event(struct Scanner *scanner, enum TokenType kind, size_t end) {
3777
3999
  struct Event *grown = diagnostic_grow(
3778
4000
  scanner->events,
@@ -4003,7 +4225,6 @@ static size_t place_unowned(
4003
4225
  return best;
4004
4226
  }
4005
4227
 
4006
- // Stages are compared only after ownership and lexical context agree.
4007
4228
  static unsigned requirement_order(enum TokenType kind) {
4008
4229
  switch (kind) {
4009
4230
  case INVALID_FOR_HEADER:
@@ -4053,15 +4274,17 @@ static bool ancestor(const struct Plan *plan, size_t outer, size_t inner) {
4053
4274
  return false;
4054
4275
  }
4055
4276
 
4277
+ static bool input_fault(enum TokenType kind) {
4278
+ return kind == INVALID_ENCODING || kind == NUL_CHARACTER;
4279
+ }
4280
+
4056
4281
  static bool replaces_issue(
4057
4282
  const struct Plan *plan,
4058
4283
  const struct Issue *candidate,
4059
4284
  const struct Issue *current
4060
4285
  ) {
4061
- bool current_input =
4062
- current->kind == INVALID_ENCODING || current->kind == NUL_CHARACTER;
4063
- bool candidate_input =
4064
- candidate->kind == INVALID_ENCODING || candidate->kind == NUL_CHARACTER;
4286
+ bool current_input = input_fault(current->kind);
4287
+ bool candidate_input = input_fault(candidate->kind);
4065
4288
  if (current_input != candidate_input) return candidate_input;
4066
4289
  if (!!candidate->owner != !!current->owner) return candidate->owner != 0;
4067
4290
  if (candidate->owner && current->owner) {
@@ -4113,13 +4336,29 @@ static size_t select_regions(struct Plan *plan, size_t end) {
4113
4336
  sizeof(*plan->issues),
4114
4337
  compare_issues
4115
4338
  );
4116
- size_t count = 0;
4339
+ if (plan->extension_count > 1)
4340
+ qsort(
4341
+ plan->extensions,
4342
+ plan->extension_count,
4343
+ sizeof(*plan->extensions),
4344
+ compare_extensions
4345
+ );
4346
+ size_t count = 0, input = 0, extension = 0;
4117
4347
  for (size_t i = 0; i < plan->issue_count;) {
4348
+ if (input < i) input = i;
4349
+ while (input < plan->issue_count && !input_fault(plan->issues[input].kind))
4350
+ input++;
4118
4351
  size_t start = plan->issues[i].start;
4119
4352
  struct Issue current = {0};
4120
4353
  bool found = false;
4121
4354
  for (; i < plan->issue_count && plan->issues[i].start == start; i++) {
4122
4355
  const struct Issue *candidate = &plan->issues[i];
4356
+ if (
4357
+ !input_fault(candidate->kind) &&
4358
+ input < plan->issue_count &&
4359
+ plan->issues[input].start < candidate->end
4360
+ )
4361
+ continue;
4123
4362
  if (
4124
4363
  covered(plan, candidate, count ? &plan->issues[count - 1] : NULL, end)
4125
4364
  )
@@ -4130,6 +4369,14 @@ static size_t select_regions(struct Plan *plan, size_t end) {
4130
4369
  }
4131
4370
  }
4132
4371
  if (!found) continue;
4372
+ for (; extension < plan->extension_count; extension++) {
4373
+ size_t position = plan->extensions[extension].position;
4374
+ size_t resume = plan->extensions[extension].resume;
4375
+ if (position > current.start && position >= current.recovery.position)
4376
+ break;
4377
+ if (position >= current.start && resume >= current.recovery.position)
4378
+ current.recovery = (struct Recovery){resume, false};
4379
+ }
4133
4380
  plan->issues[count++] = current;
4134
4381
  if (
4135
4382
  current.owner &&
@@ -4206,9 +4453,6 @@ static bool append_unowned(
4206
4453
 
4207
4454
  static bool publish(struct Scanner *scanner, struct Plan *plan, size_t end) {
4208
4455
  if (plan->failed) return false;
4209
- for (size_t i = 0; i < plan->extension_count; i++)
4210
- plan->issues[plan->extensions[i].issue].recovery =
4211
- plan->extensions[i].recovery;
4212
4456
  plan->issue_count = select_regions(plan, end);
4213
4457
  extend_ancestors(plan);
4214
4458
  size_t count = plan->issue_count;