@konomanoasa/tree-sitter-diagnostic-sh 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/Cargo.toml +5 -5
- package/README.md +0 -2
- package/package.json +4 -3
- package/src/parser.c +1 -1
- package/src/scanner.c +305 -61
- package/test/support/parser-cases.json +735 -6
- package/tree-sitter.json +1 -1
package/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "konomanoasa-tree-sitter-diagnostic-sh"
|
|
3
|
-
version = "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.
|
|
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.
|
|
37
|
+
cc = "=1.9.0"
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konomanoasa/tree-sitter-diagnostic-sh",
|
|
3
|
-
"version": "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
|
|
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 =
|
|
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
|
|
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
|
|
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){
|
|
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 (
|
|
2105
|
-
|
|
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 (
|
|
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
|
-
|
|
3064
|
+
extend_region(parser, here->uncertain - 1, input->end);
|
|
2828
3065
|
input->position = input->count;
|
|
2829
|
-
|
|
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
|
-
|
|
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
|
|
3233
|
+
static size_t uncertain_position(
|
|
3010
3234
|
const struct Plan *plan,
|
|
3011
|
-
|
|
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
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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;
|