@konomanoasa/tree-sitter-yaml 0.3.0 → 0.5.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.
@@ -166,6 +166,382 @@ fn parses_valid_source() {
166
166
  Query::new(&language, grammar::HIGHLIGHTS_QUERY).unwrap();
167
167
  }
168
168
 
169
+ #[test]
170
+ #[ignore = "Run with --release --ignored --nocapture to measure incremental reuse."]
171
+ fn profile_incremental_reuse() {
172
+ use std::collections::BTreeMap;
173
+ use std::sync::{Arc, Mutex};
174
+
175
+ for rows in [1200, 4800] {
176
+ for (case, prefix, entry, indentation_edit) in [
177
+ ("flat", "", "k%: value\n", false),
178
+ ("indented", "", " k%: value\n", false),
179
+ ("commented", "", "k%: value # comment\n", false),
180
+ (
181
+ "nested",
182
+ "services:\n",
183
+ " k%:\n image: value\n ports:\n - 80\n",
184
+ false,
185
+ ),
186
+ ("block-values", "", "k%: |\n value\n", false),
187
+ ("flow-values", "", "k%: [value]\n", false),
188
+ (
189
+ "plain-prefix",
190
+ "first: text\n continuation\n",
191
+ "k%: value\n",
192
+ true,
193
+ ),
194
+ (
195
+ "quote-prefix",
196
+ "first: \"text\n continuation\"\n",
197
+ "k%: value\n",
198
+ true,
199
+ ),
200
+ ("block-prefix", "first: |\n body\n", "k%: value\n", true),
201
+ ("flat-indent", "", "k%: value\n", true),
202
+ ] {
203
+ if std::env::var("YAML_PROFILE_CASE")
204
+ .is_ok_and(|selected| selected != case)
205
+ {
206
+ continue;
207
+ }
208
+ let source = format!(
209
+ "{prefix}{}",
210
+ (0..rows)
211
+ .map(|i| entry.replace('%', &format!("{i:04}")))
212
+ .collect::<String>()
213
+ );
214
+ for (row, target) in [
215
+ (0, "key"),
216
+ (0, "value"),
217
+ (1, "value"),
218
+ (rows / 2, "value"),
219
+ (rows - 2, "value"),
220
+ ] {
221
+ if indentation_edit && (row != 1 || target != "value") {
222
+ continue;
223
+ }
224
+ let start = if indentation_edit {
225
+ source.find('\n').unwrap() + 1
226
+ } else {
227
+ let key = source.find(&format!("k{row:04}")).unwrap();
228
+ if target == "key" {
229
+ key
230
+ } else {
231
+ key + source[key..].find("value").unwrap()
232
+ }
233
+ };
234
+ let mut parser = parser();
235
+ let mut old = parser.parse(&source, None).unwrap();
236
+ let edited = edit_source(
237
+ source.as_bytes(),
238
+ &mut old,
239
+ start,
240
+ usize::from(!indentation_edit),
241
+ if indentation_edit { b" " } else { b"V" },
242
+ );
243
+ let fresh = parser.parse(&edited, None).unwrap();
244
+ assert!(
245
+ !fresh.root_node().has_error(),
246
+ "{case}/{rows}/{row}/{target}"
247
+ );
248
+ let result = parser.parse(&edited, Some(&old)).unwrap();
249
+ assert_eq!(
250
+ snapshot(result.root_node()),
251
+ snapshot(fresh.root_node()),
252
+ "{case}/{rows}/{row}/{target}"
253
+ );
254
+ let mut timings = [Vec::new(), Vec::new()];
255
+ for iteration in 0..17 {
256
+ for (index, previous) in [None, Some(&old)].into_iter().enumerate() {
257
+ let before = Instant::now();
258
+ let tree = parser.parse(&edited, previous).unwrap();
259
+ let elapsed = before.elapsed().as_secs_f64() * 1000.0;
260
+ std::hint::black_box(&tree);
261
+ if iteration >= 2 {
262
+ timings[index].push(elapsed);
263
+ }
264
+ }
265
+ }
266
+ for samples in &mut timings {
267
+ samples.sort_by(f64::total_cmp);
268
+ }
269
+ let counts = Arc::new(Mutex::new(BTreeMap::<String, usize>::new()));
270
+ let recorded = counts.clone();
271
+ parser.set_logger(Some(Box::new(move |kind, message| {
272
+ if kind == tree_sitter::LogType::Parse {
273
+ let event = if message.starts_with("reuse_node ")
274
+ || message.starts_with("cant_reuse_node_is_fragile ")
275
+ {
276
+ message.split(",").next().unwrap()
277
+ } else {
278
+ message.split(' ').next().unwrap()
279
+ };
280
+ *recorded
281
+ .lock()
282
+ .unwrap()
283
+ .entry(event.to_owned())
284
+ .or_default() += 1;
285
+ }
286
+ })));
287
+ parser.parse(&edited, Some(&old)).unwrap();
288
+ parser.set_logger(None);
289
+ println!(
290
+ "{case},{rows},{row},{target},fresh_ms={:.3},incremental_ms={:.3},events={:?}",
291
+ timings[0][7],
292
+ timings[1][7],
293
+ counts.lock().unwrap()
294
+ );
295
+ }
296
+ }
297
+ }
298
+ }
299
+
300
+ #[test]
301
+ fn completed_scalar_edits_reuse_following_entries() {
302
+ use std::sync::{
303
+ Arc,
304
+ atomic::{AtomicUsize, Ordering},
305
+ };
306
+
307
+ for prefix in [
308
+ "first: text\n continuation\n",
309
+ "first: 'text\n continuation'\n",
310
+ "first: \"text\n continuation\"\n",
311
+ "first: |\n body\n",
312
+ "first: >\n body\n",
313
+ ] {
314
+ for rows in [32, 512] {
315
+ let source = format!(
316
+ "{prefix}{}",
317
+ (0..rows)
318
+ .map(|i| format!("k{i}: value\n"))
319
+ .collect::<String>()
320
+ );
321
+ let mut parser = parser();
322
+ let mut old = parser.parse(&source, None).unwrap();
323
+ let edited = edit_source(
324
+ source.as_bytes(),
325
+ &mut old,
326
+ source.find('\n').unwrap() + 1,
327
+ 0,
328
+ b" ",
329
+ );
330
+ let calls = Arc::new(AtomicUsize::new(0));
331
+ let recorded = calls.clone();
332
+ parser.set_logger(Some(Box::new(move |kind, message| {
333
+ if kind == tree_sitter::LogType::Parse
334
+ && message.starts_with("lex_external ")
335
+ {
336
+ recorded.fetch_add(1, Ordering::Relaxed);
337
+ }
338
+ })));
339
+ let incremental = parser.parse(&edited, Some(&old)).unwrap();
340
+ parser.set_logger(None);
341
+ let count = calls.load(Ordering::Relaxed);
342
+ assert!(
343
+ (1..=64).contains(&count),
344
+ "Relexed unchanged entries after {prefix:?}, {rows} rows: {count} scanner calls"
345
+ );
346
+ let fresh = parser.parse(&edited, None).unwrap();
347
+ assert_eq!(
348
+ snapshot(incremental.root_node()),
349
+ snapshot(fresh.root_node())
350
+ );
351
+ }
352
+ }
353
+ }
354
+
355
+ #[test]
356
+ fn completed_construct_edits_reuse_following_entries() {
357
+ use std::sync::{
358
+ Arc,
359
+ atomic::{AtomicUsize, Ordering},
360
+ };
361
+
362
+ for (before, after, row) in [
363
+ ("z: 0\na: # c\n", "z: 0\na: v # c\n", "k%: value\n"),
364
+ ("z: 0\na: v # c\n", "z: 0\na: # c\n", "k%: value\n"),
365
+ ("z: 0\na: x\n", "z: 0\na: !t x\n", "k%: value\n"),
366
+ ("z: 0\na: !t x\n", "z: 0\na: !!t x\n", "k%: value\n"),
367
+ ("z: 0\na: !t :x\n", "z: 0\na: !t x\n", "k%: value\n"),
368
+ ("z: 0\na: !t[b]\n", "z: 0\na: !t [b]\n", "k%: value\n"),
369
+ ("z: 0\na:\n b: c\n", "z: 0\na:\nb: c\n", "k%: value\n"),
370
+ ("- z\n- [a: b]\n", "- z\n- [a]\n", "- k%\n"),
371
+ (
372
+ "a: 1\n...\n%TAG !e! tag:x:\n---\n",
373
+ "a: 1\n...\n%TAGX !e! tag:x:\n---\n",
374
+ "k%: value\n",
375
+ ),
376
+ ("x:\n- a\n- b\n", "x:\n- a\n- b: c\n", "- v%\n"),
377
+ ("z: 0\nk: \"v\"\n", "z: 0\nk: v\n", "# c%\n"),
378
+ ("z: 0\nk: [a]\n", "z: 0\nk: a\n", "# c%\n"),
379
+ ] {
380
+ let start = before
381
+ .bytes()
382
+ .zip(after.bytes())
383
+ .take_while(|(a, b)| a == b)
384
+ .count();
385
+ let end = before[start..]
386
+ .bytes()
387
+ .rev()
388
+ .zip(after[start..].bytes().rev())
389
+ .take_while(|(a, b)| a == b)
390
+ .count();
391
+ for rows in [32, 512] {
392
+ let source = format!(
393
+ "{before}{}",
394
+ (0..rows)
395
+ .map(|i| row.replace('%', &i.to_string()))
396
+ .collect::<String>()
397
+ );
398
+ let mut parser = parser();
399
+ let mut old = parser.parse(&source, None).unwrap();
400
+ let edited = edit_source(
401
+ source.as_bytes(),
402
+ &mut old,
403
+ start,
404
+ before.len() - start - end,
405
+ &after.as_bytes()[start..after.len() - end],
406
+ );
407
+ let calls = Arc::new(AtomicUsize::new(0));
408
+ let recorded = calls.clone();
409
+ parser.set_logger(Some(Box::new(move |kind, message| {
410
+ if kind == tree_sitter::LogType::Parse
411
+ && message.starts_with("lex_external ")
412
+ {
413
+ recorded.fetch_add(1, Ordering::Relaxed);
414
+ }
415
+ })));
416
+ let incremental = parser.parse(&edited, Some(&old)).unwrap();
417
+ parser.set_logger(None);
418
+ let count = calls.load(Ordering::Relaxed);
419
+ assert!(
420
+ (1..=64).contains(&count),
421
+ "Relexed unchanged entries after {before:?} -> {after:?}, {rows} rows: {count} scanner calls"
422
+ );
423
+ let fresh = parser.parse(&edited, None).unwrap();
424
+ assert_eq!(
425
+ snapshot(incremental.root_node()),
426
+ snapshot(fresh.root_node())
427
+ );
428
+ }
429
+ }
430
+ }
431
+
432
+ #[test]
433
+ fn mapping_layout_is_structural_between_pairs_and_at_eof() {
434
+ for (source, expected) in [
435
+ (
436
+ "a: b # one\n # two\nc: d\n \t",
437
+ &[
438
+ ("comment", 5..10, "block_mapping", false),
439
+ ("line_prefix", 11..13, "block_mapping", false),
440
+ ("comment", 13..18, "block_mapping", false),
441
+ ("line_prefix", 24..26, "block_mapping", false),
442
+ ][..],
443
+ ),
444
+ (
445
+ "a: # one\n # two\nc:\n \t",
446
+ &[
447
+ ("comment", 3..8, "block_mapping", false),
448
+ ("line_prefix", 9..11, "block_mapping", false),
449
+ ("comment", 11..16, "block_mapping", false),
450
+ ("line_prefix", 20..22, "block_mapping", false),
451
+ ],
452
+ ),
453
+ (
454
+ "? a\n# one\nb:\n\n# two",
455
+ &[
456
+ ("comment", 4..9, "block_mapping", false),
457
+ ("comment", 14..19, "block_mapping", false),
458
+ ],
459
+ ),
460
+ (
461
+ "# zero\n--- a:\n# one\n",
462
+ &[
463
+ ("comment", 0..6, "stream", true),
464
+ ("comment", 14..19, "block_mapping", false),
465
+ ],
466
+ ),
467
+ ] {
468
+ let tree = parser().parse(source, None).unwrap();
469
+ assert!(!tree.root_node().has_error());
470
+ let mut layout = Vec::new();
471
+ let mut pending = vec![tree.root_node()];
472
+ while let Some(node) = pending.pop() {
473
+ if matches!(node.kind(), "comment" | "line_prefix") {
474
+ layout.push((
475
+ node.kind(),
476
+ node.byte_range(),
477
+ node.parent().unwrap().kind(),
478
+ node.is_extra(),
479
+ ));
480
+ }
481
+ let mut cursor = node.walk();
482
+ pending.extend(node.children(&mut cursor));
483
+ }
484
+ layout.sort_by_key(|(_, range, _, _)| range.start);
485
+ assert_eq!(layout, expected, "{source:?}");
486
+ }
487
+ }
488
+
489
+ #[test]
490
+ fn mapping_layout_reuses_unchanged_indentation_and_comments() {
491
+ use std::sync::{Arc, Mutex};
492
+
493
+ for (prefix, indent, suffix) in [
494
+ ("", " ", "\n"),
495
+ ("", "", " # comment\n"),
496
+ ("", " ", "\n # comment\n\n"),
497
+ ("root:\n", " ", " # comment\n"),
498
+ ] {
499
+ for rows in [32, 512] {
500
+ let source = format!(
501
+ "{prefix}{}",
502
+ (0..rows)
503
+ .map(|i| format!("{indent}k{i}: value{suffix}"))
504
+ .collect::<String>()
505
+ );
506
+ for row in [1, rows / 2, rows - 2] {
507
+ let mut parser = parser();
508
+ let mut old = parser.parse(&source, None).unwrap();
509
+ assert!(!old.root_node().has_error());
510
+ let key = format!("k{row}: ");
511
+ let start = source.find(&key).unwrap() + key.len();
512
+ let edited = edit_source(source.as_bytes(), &mut old, start, 1, b"V");
513
+ let counts = Arc::new(Mutex::new([0, 0]));
514
+ let recorded = counts.clone();
515
+ parser.set_logger(Some(Box::new(move |kind, message| {
516
+ if kind == tree_sitter::LogType::Parse {
517
+ let mut counts = recorded.lock().unwrap();
518
+ if message.starts_with("lex_external ") {
519
+ counts[0] += 1;
520
+ }
521
+ if message == "no_lookahead_after_non_terminal_extra" {
522
+ counts[1] += 1;
523
+ }
524
+ }
525
+ })));
526
+ let incremental = parser.parse(&edited, Some(&old)).unwrap();
527
+ parser.set_logger(None);
528
+ let fresh = parser.parse(&edited, None).unwrap();
529
+ assert!(!fresh.root_node().has_error());
530
+ assert_eq!(
531
+ snapshot(incremental.root_node()),
532
+ snapshot(fresh.root_node())
533
+ );
534
+ let counts = *counts.lock().unwrap();
535
+ assert!(
536
+ (1..=32).contains(&counts[0]),
537
+ "{prefix:?}/{indent:?}/{suffix:?}/{rows}/{row}: {counts:?}"
538
+ );
539
+ assert_eq!(counts[1], 0);
540
+ }
541
+ }
542
+ }
543
+ }
544
+
169
545
  #[test]
170
546
  fn utf16_uses_original_byte_ranges() {
171
547
  for big_endian in [false, true] {
@@ -234,19 +610,6 @@ fn utf16_unpaired_surrogates_have_exact_decoding_issue_ranges() {
234
610
  assert_eq!(tree.root_node().end_byte(), 8);
235
611
  }
236
612
 
237
- #[test]
238
- fn leading_bom_is_removed_by_input_layer_without_shifting_ranges() {
239
- let tree = parser().parse("\u{feff}a", None).unwrap();
240
- let scalar = tree
241
- .root_node()
242
- .named_child(0)
243
- .unwrap()
244
- .child_by_field_name("content")
245
- .unwrap();
246
- assert_eq!(scalar.byte_range(), 3..4);
247
- assert_eq!(tree.root_node().end_byte(), 4);
248
- }
249
-
250
613
  #[test]
251
614
  fn deeply_nested_flow_nodes_and_expired_keys_keep_their_structure() {
252
615
  let depth = 4096;
package/tree-sitter.json CHANGED
@@ -12,7 +12,7 @@
12
12
  }
13
13
  ],
14
14
  "metadata": {
15
- "version": "0.3.0",
15
+ "version": "0.5.0",
16
16
  "license": "MIT",
17
17
  "description": "Tree-sitter grammar for YAML.",
18
18
  "links": {