tree-sitter-sed 0.7.0 → 0.9.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 +43 -0
- package/README.md +6 -4
- package/bindings/rust/build.rs +40 -0
- package/bindings/rust/lib.rs +572 -0
- package/common/grammar.js +365 -350
- package/common/regex.js +104 -208
- package/common/scanner.h +968 -1387
- package/package.json +7 -8
- package/queries/highlights.scm +25 -26
- package/sed_ere/queries/highlights.scm +25 -26
- package/sed_ere/src/grammar.json +1939 -2053
- package/sed_ere/src/node-types.json +309 -501
- package/sed_ere/src/parser.c +36568 -38566
- package/src/grammar.json +1926 -1899
- package/src/node-types.json +329 -566
- package/src/parser.c +43892 -45119
- package/tree-sitter.json +3 -3
- package/CMakeLists.txt +0 -121
- package/Makefile +0 -145
- package/bindings/c/tree-sitter-sed.pc.in +0 -10
- package/bindings/c/tree_sitter/tree-sitter-sed.h +0 -17
- package/test/binding.test.c +0 -9
package/Cargo.toml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
authors = ["konomanoasa"]
|
|
3
|
+
autoexamples = false
|
|
4
|
+
categories = ["parser-implementations", "parsing", "text-editors"]
|
|
5
|
+
description = "Tree-sitter grammars for POSIX sed."
|
|
6
|
+
edition = "2024"
|
|
7
|
+
keywords = ["incremental", "parsing", "tree-sitter", "sed"]
|
|
8
|
+
license = "MIT"
|
|
9
|
+
name = "tree-sitter-sed"
|
|
10
|
+
publish = false
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
repository = "https://github.com/konomanoasa/tree-sitter-sed"
|
|
13
|
+
version = "0.9.0"
|
|
14
|
+
|
|
15
|
+
build = "bindings/rust/build.rs"
|
|
16
|
+
include = [
|
|
17
|
+
"/LICENSE",
|
|
18
|
+
"/README.md",
|
|
19
|
+
"/bindings/rust/*.rs",
|
|
20
|
+
"/common/scanner.h",
|
|
21
|
+
"/queries/*.scm",
|
|
22
|
+
"/sed_ere/queries/*.scm",
|
|
23
|
+
"/sed_ere/src/node-types.json",
|
|
24
|
+
"/sed_ere/src/parser.c",
|
|
25
|
+
"/sed_ere/src/scanner.c",
|
|
26
|
+
"/sed_ere/src/tree_sitter/*.h",
|
|
27
|
+
"/src/node-types.json",
|
|
28
|
+
"/src/parser.c",
|
|
29
|
+
"/src/scanner.c",
|
|
30
|
+
"/src/tree_sitter/*.h",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[lib]
|
|
34
|
+
path = "bindings/rust/lib.rs"
|
|
35
|
+
|
|
36
|
+
[dependencies]
|
|
37
|
+
tree-sitter-language = "0.1"
|
|
38
|
+
|
|
39
|
+
[build-dependencies]
|
|
40
|
+
cc = "1.2"
|
|
41
|
+
|
|
42
|
+
[dev-dependencies]
|
|
43
|
+
tree-sitter = "0.27.0"
|
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ POSIX.1-2024 `sed`.
|
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
+
### npm
|
|
12
|
+
|
|
11
13
|
```sh
|
|
12
14
|
npm install tree-sitter-sed
|
|
13
15
|
```
|
|
@@ -16,10 +18,10 @@ npm install tree-sitter-sed
|
|
|
16
18
|
|
|
17
19
|
This repository contains the following two grammars.
|
|
18
20
|
|
|
19
|
-
| Grammar | Regexp |
|
|
20
|
-
| --------- | ------ |
|
|
21
|
-
| `sed` | BRE | `
|
|
22
|
-
| `sed_ere` | ERE | `
|
|
21
|
+
| Grammar | Regexp | Rust constant |
|
|
22
|
+
| --------- | ------ | -------------- |
|
|
23
|
+
| `sed` | BRE | `LANGUAGE` |
|
|
24
|
+
| `sed_ere` | ERE | `LANGUAGE_ERE` |
|
|
23
25
|
|
|
24
26
|
## Development
|
|
25
27
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
fn compile_parser(source_dir: &Path, library_name: &str, wasm_headers: Option<&Path>) {
|
|
4
|
+
let parser_path = source_dir.join("parser.c");
|
|
5
|
+
let scanner_path = source_dir.join("scanner.c");
|
|
6
|
+
let mut c_config = cc::Build::new();
|
|
7
|
+
c_config.std("c11").include(source_dir);
|
|
8
|
+
|
|
9
|
+
#[cfg(target_env = "msvc")]
|
|
10
|
+
c_config.flag("-utf-8");
|
|
11
|
+
|
|
12
|
+
if let Some(headers) = wasm_headers {
|
|
13
|
+
c_config.include(headers);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
c_config.file(&parser_path).file(&scanner_path);
|
|
17
|
+
c_config.compile(library_name);
|
|
18
|
+
|
|
19
|
+
println!("cargo:rerun-if-changed={}", parser_path.display());
|
|
20
|
+
println!("cargo:rerun-if-changed={}", scanner_path.display());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fn main() {
|
|
24
|
+
let target = std::env::var("TARGET").expect("Cargo must provide TARGET");
|
|
25
|
+
let wasm_headers = (target == "wasm32-unknown-unknown").then(|| {
|
|
26
|
+
std::env::var_os("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS")
|
|
27
|
+
.map(std::path::PathBuf::from)
|
|
28
|
+
.expect(
|
|
29
|
+
"tree-sitter-language must provide its headers when compiling for wasm32-unknown-unknown",
|
|
30
|
+
)
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
compile_parser(Path::new("src"), "tree-sitter-sed", wasm_headers.as_deref());
|
|
34
|
+
compile_parser(
|
|
35
|
+
Path::new("sed_ere/src"),
|
|
36
|
+
"tree-sitter-sed-ere",
|
|
37
|
+
wasm_headers.as_deref(),
|
|
38
|
+
);
|
|
39
|
+
println!("cargo:rerun-if-changed=common/scanner.h");
|
|
40
|
+
}
|
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
//! POSIX sed grammars for the Tree-sitter parsing library.
|
|
2
|
+
//!
|
|
3
|
+
//! [`LANGUAGE`] parses basic regular expressions, while [`LANGUAGE_ERE`] parses
|
|
4
|
+
//! extended regular expressions.
|
|
5
|
+
//!
|
|
6
|
+
//! ```
|
|
7
|
+
//! let mut parser = tree_sitter::Parser::new();
|
|
8
|
+
//! parser
|
|
9
|
+
//! .set_language(&tree_sitter_sed::LANGUAGE.into())
|
|
10
|
+
//! .expect("POSIX sed grammar must load");
|
|
11
|
+
//! let tree = parser.parse("p\n", None).expect("parser must return a tree");
|
|
12
|
+
//! assert!(!tree.root_node().has_error());
|
|
13
|
+
//! ```
|
|
14
|
+
|
|
15
|
+
use tree_sitter_language::LanguageFn;
|
|
16
|
+
|
|
17
|
+
unsafe extern "C" {
|
|
18
|
+
fn tree_sitter_sed() -> *const ();
|
|
19
|
+
fn tree_sitter_sed_ere() -> *const ();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// The Tree-sitter [`LanguageFn`] for POSIX sed with basic regular expressions.
|
|
23
|
+
pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_sed) };
|
|
24
|
+
|
|
25
|
+
/// The Tree-sitter [`LanguageFn`] for POSIX sed with extended regular expressions.
|
|
26
|
+
pub const LANGUAGE_ERE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_sed_ere) };
|
|
27
|
+
|
|
28
|
+
/// The node type definitions for [`LANGUAGE`].
|
|
29
|
+
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
|
|
30
|
+
|
|
31
|
+
/// The node type definitions for [`LANGUAGE_ERE`].
|
|
32
|
+
pub const NODE_TYPES_ERE: &str = include_str!("../../sed_ere/src/node-types.json");
|
|
33
|
+
|
|
34
|
+
/// The syntax highlighting query for [`LANGUAGE`].
|
|
35
|
+
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
|
|
36
|
+
|
|
37
|
+
/// The syntax highlighting query for [`LANGUAGE_ERE`].
|
|
38
|
+
pub const HIGHLIGHTS_QUERY_ERE: &str = include_str!("../../sed_ere/queries/highlights.scm");
|
|
39
|
+
|
|
40
|
+
#[cfg(test)]
|
|
41
|
+
mod tests {
|
|
42
|
+
fn assert_parses(language: tree_sitter_language::LanguageFn) {
|
|
43
|
+
let mut parser = tree_sitter::Parser::new();
|
|
44
|
+
parser
|
|
45
|
+
.set_language(&language.into())
|
|
46
|
+
.expect("generated grammar must be compatible with the Tree-sitter runtime");
|
|
47
|
+
let tree = parser
|
|
48
|
+
.parse("p\n", None)
|
|
49
|
+
.expect("parser must return a tree");
|
|
50
|
+
assert_eq!(tree.root_node().kind(), "script");
|
|
51
|
+
assert!(!tree.root_node().has_error());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn sed_grammar_loads_and_parses() {
|
|
56
|
+
assert_parses(super::LANGUAGE);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[test]
|
|
60
|
+
fn sed_ere_grammar_loads_and_parses() {
|
|
61
|
+
assert_parses(super::LANGUAGE_ERE);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn sed_and_sed_ere_are_distinct_grammars() {
|
|
66
|
+
let sed = tree_sitter::Language::new(super::LANGUAGE);
|
|
67
|
+
let sed_ere = tree_sitter::Language::new(super::LANGUAGE_ERE);
|
|
68
|
+
assert_ne!(sed, sed_ere);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn issue_signatures(tree: &tree_sitter::Tree) -> Vec<(String, String, tree_sitter::Range)> {
|
|
72
|
+
let mut issues = Vec::new();
|
|
73
|
+
let mut nodes = vec![tree.root_node()];
|
|
74
|
+
while let Some(node) = nodes.pop() {
|
|
75
|
+
if node.kind() == "syntax_issue" {
|
|
76
|
+
let outcome = node.named_child(0).expect("issue must have an outcome");
|
|
77
|
+
let reason = outcome.named_child(0).expect("issue must have a reason");
|
|
78
|
+
issues.push((
|
|
79
|
+
outcome.kind().to_owned(),
|
|
80
|
+
reason.kind().to_owned(),
|
|
81
|
+
node.range(),
|
|
82
|
+
));
|
|
83
|
+
}
|
|
84
|
+
for index in (0..node.named_child_count()).rev() {
|
|
85
|
+
nodes.push(
|
|
86
|
+
node.named_child(index as u32)
|
|
87
|
+
.expect("named child must exist"),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
issues
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[test]
|
|
95
|
+
fn invalid_byte_issues_preserve_classification_and_ranges_through_edits() {
|
|
96
|
+
use tree_sitter::{InputEdit, Point, Range};
|
|
97
|
+
|
|
98
|
+
for (language, mode) in [(super::LANGUAGE, "BRE"), (super::LANGUAGE_ERE, "ERE")] {
|
|
99
|
+
let mut parser = tree_sitter::Parser::new();
|
|
100
|
+
parser
|
|
101
|
+
.set_language(&language.into())
|
|
102
|
+
.expect("generated grammar must load");
|
|
103
|
+
for (name, source, start, end, row, column, reason) in [
|
|
104
|
+
(
|
|
105
|
+
"first function",
|
|
106
|
+
&b"p\nq\n"[..],
|
|
107
|
+
0,
|
|
108
|
+
1,
|
|
109
|
+
0,
|
|
110
|
+
0,
|
|
111
|
+
"unknown_function",
|
|
112
|
+
),
|
|
113
|
+
(
|
|
114
|
+
"function after newline",
|
|
115
|
+
&b"p\np\nq\n"[..],
|
|
116
|
+
2,
|
|
117
|
+
3,
|
|
118
|
+
1,
|
|
119
|
+
0,
|
|
120
|
+
"unknown_function",
|
|
121
|
+
),
|
|
122
|
+
(
|
|
123
|
+
"substitute delimiter",
|
|
124
|
+
&b"p\ns///\nq\n"[..],
|
|
125
|
+
3,
|
|
126
|
+
6,
|
|
127
|
+
1,
|
|
128
|
+
1,
|
|
129
|
+
"invalid_delimiter",
|
|
130
|
+
),
|
|
131
|
+
(
|
|
132
|
+
"translate delimiter",
|
|
133
|
+
&b"p\ny///\nq\n"[..],
|
|
134
|
+
3,
|
|
135
|
+
6,
|
|
136
|
+
1,
|
|
137
|
+
1,
|
|
138
|
+
"invalid_delimiter",
|
|
139
|
+
),
|
|
140
|
+
(
|
|
141
|
+
"address delimiter",
|
|
142
|
+
&b"p\n\\##p\nq\n"[..],
|
|
143
|
+
3,
|
|
144
|
+
5,
|
|
145
|
+
1,
|
|
146
|
+
1,
|
|
147
|
+
"invalid_delimiter",
|
|
148
|
+
),
|
|
149
|
+
(
|
|
150
|
+
"trailing command text",
|
|
151
|
+
&b"p\np\nq\n"[..],
|
|
152
|
+
3,
|
|
153
|
+
3,
|
|
154
|
+
1,
|
|
155
|
+
1,
|
|
156
|
+
"unexpected_command_text",
|
|
157
|
+
),
|
|
158
|
+
] {
|
|
159
|
+
for invalid_byte in [0, 0xff] {
|
|
160
|
+
let mut damaged = source.to_vec();
|
|
161
|
+
damaged.splice(start..end, [invalid_byte]);
|
|
162
|
+
let mut tree = parser
|
|
163
|
+
.parse(source, None)
|
|
164
|
+
.expect("parser must return a tree");
|
|
165
|
+
assert!(!tree.root_node().has_error(), "{mode}, {name}");
|
|
166
|
+
assert!(issue_signatures(&tree).is_empty(), "{mode}, {name}");
|
|
167
|
+
for damage in [true, false, true] {
|
|
168
|
+
let (next, old_length, new_length) = if damage {
|
|
169
|
+
(damaged.as_slice(), end - start, 1)
|
|
170
|
+
} else {
|
|
171
|
+
(source, 1, end - start)
|
|
172
|
+
};
|
|
173
|
+
tree.edit(&InputEdit {
|
|
174
|
+
start_byte: start,
|
|
175
|
+
old_end_byte: start + old_length,
|
|
176
|
+
new_end_byte: start + new_length,
|
|
177
|
+
start_position: Point::new(row, column),
|
|
178
|
+
old_end_position: Point::new(row, column + old_length),
|
|
179
|
+
new_end_position: Point::new(row, column + new_length),
|
|
180
|
+
});
|
|
181
|
+
let incremental =
|
|
182
|
+
parser.parse(next, Some(&tree)).expect("incremental tree");
|
|
183
|
+
let fresh = parser.parse(next, None).expect("fresh tree");
|
|
184
|
+
let mut expected = Vec::new();
|
|
185
|
+
if damage {
|
|
186
|
+
let range = Range {
|
|
187
|
+
start_byte: start,
|
|
188
|
+
end_byte: start + 1,
|
|
189
|
+
start_point: Point::new(row, column),
|
|
190
|
+
end_point: Point::new(row, column + 1),
|
|
191
|
+
};
|
|
192
|
+
expected.push((
|
|
193
|
+
"nonconforming_syntax".to_owned(),
|
|
194
|
+
reason.to_owned(),
|
|
195
|
+
range,
|
|
196
|
+
));
|
|
197
|
+
if invalid_byte == 0 && reason == "unexpected_command_text" {
|
|
198
|
+
expected.push((
|
|
199
|
+
"nonconforming_syntax".to_owned(),
|
|
200
|
+
"nul_character".to_owned(),
|
|
201
|
+
range,
|
|
202
|
+
));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
let context =
|
|
206
|
+
format!("{mode}, {name}, byte {invalid_byte:#x}, damage {damage}");
|
|
207
|
+
assert_eq!(issue_signatures(&fresh), expected, "fresh: {context}");
|
|
208
|
+
assert_eq!(
|
|
209
|
+
issue_signatures(&incremental),
|
|
210
|
+
expected,
|
|
211
|
+
"incremental: {context}"
|
|
212
|
+
);
|
|
213
|
+
if !damage {
|
|
214
|
+
assert!(!incremental.root_node().has_error(), "{context}");
|
|
215
|
+
assert_eq!(
|
|
216
|
+
incremental.root_node().to_sexp(),
|
|
217
|
+
fresh.root_node().to_sexp(),
|
|
218
|
+
"{context}"
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
tree = incremental;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[test]
|
|
229
|
+
fn invalid_bytes_inside_intervals_and_escapes_remain_visible_through_edits() {
|
|
230
|
+
use tree_sitter::{InputEdit, Point, Range};
|
|
231
|
+
|
|
232
|
+
for (language, mode, interval, count_start) in [
|
|
233
|
+
(super::LANGUAGE, "BRE", &b"/a\\{1,2\\}/p\n"[..], 6),
|
|
234
|
+
(super::LANGUAGE_ERE, "ERE", &b"/a{1,2}/p\n"[..], 5),
|
|
235
|
+
] {
|
|
236
|
+
let mut parser = tree_sitter::Parser::new();
|
|
237
|
+
parser
|
|
238
|
+
.set_language(&language.into())
|
|
239
|
+
.expect("generated grammar must load");
|
|
240
|
+
for (name, source, start, prefix, reasons) in [
|
|
241
|
+
(
|
|
242
|
+
"malformed interval",
|
|
243
|
+
interval,
|
|
244
|
+
count_start,
|
|
245
|
+
&b"x"[..],
|
|
246
|
+
&[
|
|
247
|
+
("undefined_syntax", "malformed_interval", 0, 2),
|
|
248
|
+
(
|
|
249
|
+
"invalid_syntax",
|
|
250
|
+
"invalid_regular_expression_character",
|
|
251
|
+
1,
|
|
252
|
+
2,
|
|
253
|
+
),
|
|
254
|
+
][..],
|
|
255
|
+
),
|
|
256
|
+
(
|
|
257
|
+
"escaped invalid character",
|
|
258
|
+
&b"/\\*/p\n"[..],
|
|
259
|
+
2,
|
|
260
|
+
&b""[..],
|
|
261
|
+
&[(
|
|
262
|
+
"invalid_syntax",
|
|
263
|
+
"invalid_regular_expression_character",
|
|
264
|
+
0,
|
|
265
|
+
1,
|
|
266
|
+
)][..],
|
|
267
|
+
),
|
|
268
|
+
] {
|
|
269
|
+
for invalid_byte in [0, 0xff] {
|
|
270
|
+
let replacement = [prefix, &[invalid_byte]].concat();
|
|
271
|
+
let mut damaged = source.to_vec();
|
|
272
|
+
damaged.splice(start..start + 1, replacement.iter().copied());
|
|
273
|
+
let expected: Vec<_> = reasons
|
|
274
|
+
.iter()
|
|
275
|
+
.map(|&(outcome, reason, first, last)| {
|
|
276
|
+
(
|
|
277
|
+
outcome.to_owned(),
|
|
278
|
+
reason.to_owned(),
|
|
279
|
+
Range {
|
|
280
|
+
start_byte: start + first,
|
|
281
|
+
end_byte: start + last,
|
|
282
|
+
start_point: Point::new(0, start + first),
|
|
283
|
+
end_point: Point::new(0, start + last),
|
|
284
|
+
},
|
|
285
|
+
)
|
|
286
|
+
})
|
|
287
|
+
.collect();
|
|
288
|
+
let mut tree = parser.parse(source, None).expect("initial tree");
|
|
289
|
+
for damage in [true, false, true] {
|
|
290
|
+
let (next, old_length, new_length) = if damage {
|
|
291
|
+
(damaged.as_slice(), 1, replacement.len())
|
|
292
|
+
} else {
|
|
293
|
+
(source, replacement.len(), 1)
|
|
294
|
+
};
|
|
295
|
+
tree.edit(&InputEdit {
|
|
296
|
+
start_byte: start,
|
|
297
|
+
old_end_byte: start + old_length,
|
|
298
|
+
new_end_byte: start + new_length,
|
|
299
|
+
start_position: Point::new(0, start),
|
|
300
|
+
old_end_position: Point::new(0, start + old_length),
|
|
301
|
+
new_end_position: Point::new(0, start + new_length),
|
|
302
|
+
});
|
|
303
|
+
let incremental = parser.parse(next, Some(&tree)).expect("edited tree");
|
|
304
|
+
let fresh = parser.parse(next, None).expect("fresh tree");
|
|
305
|
+
let wanted = if damage { expected.as_slice() } else { &[] };
|
|
306
|
+
let context =
|
|
307
|
+
format!("{mode}, {name}, byte {invalid_byte:#x}, damage {damage}");
|
|
308
|
+
for parsed in [&fresh, &incremental] {
|
|
309
|
+
assert_eq!(issue_signatures(parsed), wanted, "{context}");
|
|
310
|
+
if damage && name == "malformed interval" {
|
|
311
|
+
let mut nodes = vec![parsed.root_node()];
|
|
312
|
+
let mut nested = false;
|
|
313
|
+
while let Some(node) = nodes.pop() {
|
|
314
|
+
if node.kind() == "malformed_interval" {
|
|
315
|
+
nested = node.to_sexp().contains(
|
|
316
|
+
"(syntax_issue (invalid_syntax (invalid_regular_expression_character",
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
for index in 0..node.named_child_count() {
|
|
320
|
+
nodes.push(node.named_child(index as u32).unwrap());
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
assert!(
|
|
324
|
+
nested,
|
|
325
|
+
"{context}: invalid byte must belong to the interval issue"
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if !damage {
|
|
330
|
+
assert!(!incremental.root_node().has_error(), "{context}");
|
|
331
|
+
assert_eq!(
|
|
332
|
+
incremental.root_node().to_sexp(),
|
|
333
|
+
fresh.root_node().to_sexp(),
|
|
334
|
+
"{context}"
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
tree = incremental;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#[test]
|
|
345
|
+
fn literal_ranges_remain_stable_when_edits_split_multibyte_delimiters() {
|
|
346
|
+
use tree_sitter::{InputEdit, Point};
|
|
347
|
+
|
|
348
|
+
for (language, mode) in [(super::LANGUAGE, "BRE"), (super::LANGUAGE_ERE, "ERE")] {
|
|
349
|
+
let mut parser = tree_sitter::Parser::new();
|
|
350
|
+
parser
|
|
351
|
+
.set_language(&language.into())
|
|
352
|
+
.expect("generated grammar must load");
|
|
353
|
+
for (source, edited, positions, literal, expected) in [
|
|
354
|
+
(
|
|
355
|
+
"séaébé",
|
|
356
|
+
"séaébèé",
|
|
357
|
+
[8, 9, 11],
|
|
358
|
+
"replacement_literal",
|
|
359
|
+
&[(6, 9)][..],
|
|
360
|
+
),
|
|
361
|
+
(
|
|
362
|
+
"yéaébé",
|
|
363
|
+
"yéaébèé",
|
|
364
|
+
[8, 9, 11],
|
|
365
|
+
"translation_literal",
|
|
366
|
+
&[(3, 4), (6, 9)][..],
|
|
367
|
+
),
|
|
368
|
+
(
|
|
369
|
+
"s🙂a🙂b🙂",
|
|
370
|
+
"s🙂a🙂b🙁🙂",
|
|
371
|
+
[14, 15, 19],
|
|
372
|
+
"replacement_literal",
|
|
373
|
+
&[(10, 15)][..],
|
|
374
|
+
),
|
|
375
|
+
(
|
|
376
|
+
"y🙂a🙂b🙂",
|
|
377
|
+
"y🙂a🙂b🙁🙂",
|
|
378
|
+
[14, 15, 19],
|
|
379
|
+
"translation_literal",
|
|
380
|
+
&[(5, 6), (10, 15)][..],
|
|
381
|
+
),
|
|
382
|
+
] {
|
|
383
|
+
let [start, old_end, new_end] = positions;
|
|
384
|
+
assert_eq!(&source.as_bytes()[..start], &edited.as_bytes()[..start]);
|
|
385
|
+
assert_eq!(&source.as_bytes()[old_end..], &edited.as_bytes()[new_end..]);
|
|
386
|
+
let mut tree = parser.parse(source, None).expect("initial tree");
|
|
387
|
+
assert!(!tree.root_node().has_error());
|
|
388
|
+
assert!(issue_signatures(&tree).is_empty());
|
|
389
|
+
tree.edit(&InputEdit {
|
|
390
|
+
start_byte: start,
|
|
391
|
+
old_end_byte: old_end,
|
|
392
|
+
new_end_byte: new_end,
|
|
393
|
+
start_position: Point::new(0, start),
|
|
394
|
+
old_end_position: Point::new(0, old_end),
|
|
395
|
+
new_end_position: Point::new(0, new_end),
|
|
396
|
+
});
|
|
397
|
+
let incremental = parser.parse(edited, Some(&tree)).expect("edited tree");
|
|
398
|
+
let fresh = parser.parse(edited, None).expect("fresh tree");
|
|
399
|
+
for (parse_kind, parsed) in [("fresh", &fresh), ("incremental", &incremental)] {
|
|
400
|
+
let context = format!("{mode}, {parse_kind}, {source:?} -> {edited:?}");
|
|
401
|
+
assert!(!parsed.root_node().has_error(), "{context}");
|
|
402
|
+
assert!(issue_signatures(parsed).is_empty(), "{context}");
|
|
403
|
+
let mut ranges = Vec::new();
|
|
404
|
+
let mut nodes = vec![parsed.root_node()];
|
|
405
|
+
while let Some(node) = nodes.pop() {
|
|
406
|
+
if node.kind() == literal {
|
|
407
|
+
ranges.push((node.start_byte(), node.end_byte()));
|
|
408
|
+
assert_eq!(node.start_position(), Point::new(0, node.start_byte()));
|
|
409
|
+
assert_eq!(node.end_position(), Point::new(0, node.end_byte()));
|
|
410
|
+
}
|
|
411
|
+
for index in (0..node.named_child_count()).rev() {
|
|
412
|
+
nodes.push(node.named_child(index as u32).unwrap());
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
assert_eq!(ranges, expected, "{context}");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
#[test]
|
|
422
|
+
fn completed_groups_replace_recovery_after_multibyte_delimiter_edits() {
|
|
423
|
+
use tree_sitter::{InputEdit, Point};
|
|
424
|
+
|
|
425
|
+
for (language, source, edited, edit_start, group_kind, boundaries) in [
|
|
426
|
+
(
|
|
427
|
+
super::LANGUAGE,
|
|
428
|
+
r"sé\(é",
|
|
429
|
+
r"sé\(è\)éé",
|
|
430
|
+
6,
|
|
431
|
+
"nondupl_bre",
|
|
432
|
+
[3, 5, 7, 9],
|
|
433
|
+
),
|
|
434
|
+
(
|
|
435
|
+
super::LANGUAGE_ERE,
|
|
436
|
+
"sé(é",
|
|
437
|
+
"sé(è)éé",
|
|
438
|
+
5,
|
|
439
|
+
"ere_expression",
|
|
440
|
+
[3, 4, 6, 7],
|
|
441
|
+
),
|
|
442
|
+
(
|
|
443
|
+
super::LANGUAGE,
|
|
444
|
+
r"s🙂\(🙂",
|
|
445
|
+
r"s🙂\(🙁\)🙂🙂",
|
|
446
|
+
10,
|
|
447
|
+
"nondupl_bre",
|
|
448
|
+
[5, 7, 11, 13],
|
|
449
|
+
),
|
|
450
|
+
(
|
|
451
|
+
super::LANGUAGE_ERE,
|
|
452
|
+
"s🙂(🙂",
|
|
453
|
+
"s🙂(🙁)🙂🙂",
|
|
454
|
+
9,
|
|
455
|
+
"ere_expression",
|
|
456
|
+
[5, 6, 10, 11],
|
|
457
|
+
),
|
|
458
|
+
] {
|
|
459
|
+
let mut parser = tree_sitter::Parser::new();
|
|
460
|
+
parser.set_language(&language.into()).unwrap();
|
|
461
|
+
let mut tree = parser.parse(source, None).unwrap();
|
|
462
|
+
assert!(!issue_signatures(&tree).is_empty());
|
|
463
|
+
assert_eq!(
|
|
464
|
+
&source.as_bytes()[..edit_start],
|
|
465
|
+
&edited.as_bytes()[..edit_start]
|
|
466
|
+
);
|
|
467
|
+
tree.edit(&InputEdit {
|
|
468
|
+
start_byte: edit_start,
|
|
469
|
+
old_end_byte: source.len(),
|
|
470
|
+
new_end_byte: edited.len(),
|
|
471
|
+
start_position: Point::new(0, edit_start),
|
|
472
|
+
old_end_position: Point::new(0, source.len()),
|
|
473
|
+
new_end_position: Point::new(0, edited.len()),
|
|
474
|
+
});
|
|
475
|
+
let incremental = parser.parse(edited, Some(&tree)).unwrap();
|
|
476
|
+
let fresh = parser.parse(edited, None).unwrap();
|
|
477
|
+
for (kind, parsed) in [("fresh", &fresh), ("incremental", &incremental)] {
|
|
478
|
+
let context = format!("{kind}: {source:?} -> {edited:?}");
|
|
479
|
+
assert!(!parsed.root_node().has_error(), "{context}");
|
|
480
|
+
assert!(issue_signatures(parsed).is_empty(), "{context}");
|
|
481
|
+
let mut nodes = vec![parsed.root_node()];
|
|
482
|
+
let mut groups = Vec::new();
|
|
483
|
+
while let Some(node) = nodes.pop() {
|
|
484
|
+
if node.kind() == group_kind && node.child_by_field_name("opening").is_some() {
|
|
485
|
+
groups.push(node);
|
|
486
|
+
}
|
|
487
|
+
for index in (0..node.named_child_count()).rev() {
|
|
488
|
+
nodes.push(node.named_child(index as u32).unwrap());
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
assert_eq!(groups.len(), 1, "{context}");
|
|
492
|
+
let [start, content, closing, end] = boundaries;
|
|
493
|
+
let group = groups[0];
|
|
494
|
+
assert_eq!(group.byte_range(), start..end, "{context}");
|
|
495
|
+
for (field, range) in [
|
|
496
|
+
("opening", start..content),
|
|
497
|
+
("expression", content..closing),
|
|
498
|
+
("closing", closing..end),
|
|
499
|
+
] {
|
|
500
|
+
let child = group.child_by_field_name(field).unwrap();
|
|
501
|
+
assert_eq!(child.byte_range(), range, "{field}: {context}");
|
|
502
|
+
assert_eq!(child.start_position(), Point::new(0, range.start));
|
|
503
|
+
assert_eq!(child.end_position(), Point::new(0, range.end));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
assert_eq!(
|
|
507
|
+
incremental.root_node().to_sexp(),
|
|
508
|
+
fresh.root_node().to_sexp()
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
#[test]
|
|
514
|
+
fn balanced_regex_groups_preserve_depth_across_the_16_bit_boundary() {
|
|
515
|
+
for (language, mode, opening, closing, opening_kind, closing_kind) in [
|
|
516
|
+
(
|
|
517
|
+
super::LANGUAGE,
|
|
518
|
+
"BRE",
|
|
519
|
+
"\\(",
|
|
520
|
+
"\\)",
|
|
521
|
+
"back_open_parenthesis",
|
|
522
|
+
"back_close_parenthesis_token",
|
|
523
|
+
),
|
|
524
|
+
(
|
|
525
|
+
super::LANGUAGE_ERE,
|
|
526
|
+
"ERE",
|
|
527
|
+
"(",
|
|
528
|
+
")",
|
|
529
|
+
"open_parenthesis",
|
|
530
|
+
"close_parenthesis_token",
|
|
531
|
+
),
|
|
532
|
+
] {
|
|
533
|
+
let mut parser = tree_sitter::Parser::new();
|
|
534
|
+
parser
|
|
535
|
+
.set_language(&language.into())
|
|
536
|
+
.expect("generated grammar must load");
|
|
537
|
+
for depth in [65_535, 65_536] {
|
|
538
|
+
let source = format!("/{}a{}/p\n", opening.repeat(depth), closing.repeat(depth));
|
|
539
|
+
let tree = parser
|
|
540
|
+
.parse(&source, None)
|
|
541
|
+
.expect("parser must return a tree");
|
|
542
|
+
let root = tree.root_node();
|
|
543
|
+
assert_eq!(root.kind(), "script", "{mode}, depth {depth}");
|
|
544
|
+
assert_eq!(root.byte_range(), 0..source.len(), "{mode}, depth {depth}");
|
|
545
|
+
assert!(!root.has_error(), "{mode}, depth {depth}");
|
|
546
|
+
|
|
547
|
+
let mut cursor = root.walk();
|
|
548
|
+
let mut openings = 0;
|
|
549
|
+
let mut closings = 0;
|
|
550
|
+
'walk: loop {
|
|
551
|
+
let node = cursor.node();
|
|
552
|
+
assert_ne!(node.kind(), "syntax_issue", "{mode}, depth {depth}");
|
|
553
|
+
if node.kind() == opening_kind {
|
|
554
|
+
openings += 1;
|
|
555
|
+
} else if node.kind() == closing_kind {
|
|
556
|
+
closings += 1;
|
|
557
|
+
}
|
|
558
|
+
if cursor.goto_first_child() {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
while !cursor.goto_next_sibling() {
|
|
562
|
+
if !cursor.goto_parent() {
|
|
563
|
+
break 'walk;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
assert_eq!(openings, depth, "{mode}, depth {depth}");
|
|
568
|
+
assert_eq!(closings, depth, "{mode}, depth {depth}");
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|