yerba 0.9.1-arm-linux-gnu → 0.10.0-arm-linux-gnu

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -16
  3. data/exe/arm-linux-gnu/yerba +0 -0
  4. data/ext/yerba/include/yerba.h +67 -32
  5. data/ext/yerba/yerba.c +296 -186
  6. data/lib/yerba/3.2/yerba.so +0 -0
  7. data/lib/yerba/3.3/yerba.so +0 -0
  8. data/lib/yerba/3.4/yerba.so +0 -0
  9. data/lib/yerba/4.0/yerba.so +0 -0
  10. data/lib/yerba/collection.rb +4 -4
  11. data/lib/yerba/document.rb +134 -0
  12. data/lib/yerba/formatting.rb +1 -0
  13. data/lib/yerba/location.rb +8 -1
  14. data/lib/yerba/map.rb +16 -6
  15. data/lib/yerba/node.rb +41 -0
  16. data/lib/yerba/query_result.rb +33 -10
  17. data/lib/yerba/scalar.rb +29 -0
  18. data/lib/yerba/selector.rb +142 -0
  19. data/lib/yerba/sequence.rb +38 -33
  20. data/lib/yerba/version.rb +1 -1
  21. data/lib/yerba.rb +1 -0
  22. data/rust/Cargo.lock +1 -1
  23. data/rust/Cargo.toml +1 -1
  24. data/rust/src/commands/get.rs +12 -12
  25. data/rust/src/commands/init.rs +8 -8
  26. data/rust/src/commands/insert.rs +4 -18
  27. data/rust/src/commands/location.rs +2 -0
  28. data/rust/src/commands/mod.rs +34 -6
  29. data/rust/src/commands/move_item.rs +1 -1
  30. data/rust/src/commands/move_key.rs +8 -8
  31. data/rust/src/commands/rename.rs +1 -1
  32. data/rust/src/commands/selectors.rs +1 -1
  33. data/rust/src/commands/set.rs +14 -6
  34. data/rust/src/document/condition.rs +25 -25
  35. data/rust/src/document/delete.rs +57 -43
  36. data/rust/src/document/get.rs +91 -82
  37. data/rust/src/document/insert.rs +77 -77
  38. data/rust/src/document/mod.rs +138 -64
  39. data/rust/src/document/nodes.rs +162 -0
  40. data/rust/src/document/schema.rs +8 -8
  41. data/rust/src/document/set.rs +49 -26
  42. data/rust/src/document/sort.rs +54 -54
  43. data/rust/src/document/style.rs +50 -49
  44. data/rust/src/document/unique.rs +5 -5
  45. data/rust/src/error.rs +8 -4
  46. data/rust/src/ffi.rs +272 -102
  47. data/rust/src/json.rs +62 -2
  48. data/rust/src/lib.rs +2 -1
  49. data/rust/src/main.rs +2 -1
  50. data/rust/src/selector.rs +48 -7
  51. data/rust/src/syntax.rs +449 -56
  52. data/rust/src/validation.rs +5 -3
  53. data/rust/src/yerbafile.rs +124 -76
  54. metadata +4 -2
data/rust/src/syntax.rs CHANGED
@@ -24,11 +24,7 @@ pub enum YerbaValueType {
24
24
  }
25
25
 
26
26
  pub fn detect_yaml_type(scalar: &ScalarValue) -> YerbaValueType {
27
- if scalar.kind != SyntaxKind::PLAIN_SCALAR {
28
- return YerbaValueType::String;
29
- }
30
-
31
- detect_yaml_type_from_plain(&scalar.text)
27
+ resolve_scalar(scalar).value_type()
32
28
  }
33
29
 
34
30
  pub fn raw_scalar_value(token: &SyntaxToken) -> Option<String> {
@@ -325,11 +321,416 @@ pub fn is_inline_scalar_safe(value: &str) -> bool {
325
321
  }
326
322
 
327
323
  pub fn needs_quoting(value: &str) -> bool {
328
- is_yaml_non_string(value) || !is_plain_safe(value)
324
+ is_yaml_non_string(value) || resolves_to_non_string_in_psych(value) || !is_plain_safe(value)
329
325
  }
330
326
 
331
327
  pub fn needs_quoting_in_flow(value: &str) -> bool {
332
- is_yaml_non_string(value) || !is_plain_safe_in_flow(value)
328
+ is_yaml_non_string(value) || resolves_to_non_string_in_psych(value) || !is_plain_safe_in_flow(value)
329
+ }
330
+
331
+ /// Psych loads these as `Date`, `Time` or `Symbol`, or raises (`.e+5`), so they need quotes although Yerba reads them as strings.
332
+ pub fn resolves_to_non_string_in_psych(value: &str) -> bool {
333
+ !matches!(resolve_plain_scalar(value), PlainScalar::String)
334
+ || is_psych_float(value)
335
+ || is_psych_date_or_time(value)
336
+ || (value.len() > 1 && value.starts_with(':'))
337
+ }
338
+
339
+ #[derive(Debug, Clone, Copy, PartialEq)]
340
+ pub enum PlainScalar {
341
+ Null,
342
+ Boolean(bool),
343
+ Integer(i128),
344
+ Float(f64),
345
+ String,
346
+ }
347
+
348
+ impl PlainScalar {
349
+ pub fn value_type(self) -> YerbaValueType {
350
+ match self {
351
+ PlainScalar::Null => YerbaValueType::Null,
352
+ PlainScalar::Boolean(_) => YerbaValueType::Boolean,
353
+ PlainScalar::Integer(_) => YerbaValueType::Integer,
354
+ PlainScalar::Float(_) => YerbaValueType::Float,
355
+ PlainScalar::String => YerbaValueType::String,
356
+ }
357
+ }
358
+ }
359
+
360
+ pub fn resolve_scalar(scalar: &ScalarValue) -> PlainScalar {
361
+ if scalar.kind != SyntaxKind::PLAIN_SCALAR {
362
+ return PlainScalar::String;
363
+ }
364
+
365
+ resolve_plain_scalar(&scalar.text)
366
+ }
367
+
368
+ pub fn resolve_plain_scalar(value: &str) -> PlainScalar {
369
+ if value.is_empty() {
370
+ return PlainScalar::Null;
371
+ }
372
+
373
+ if value.len() <= 5 {
374
+ let is = |word: &str| value.eq_ignore_ascii_case(word);
375
+
376
+ if is("~") || is("null") {
377
+ return PlainScalar::Null;
378
+ }
379
+
380
+ if is("yes") || is("true") || is("on") {
381
+ return PlainScalar::Boolean(true);
382
+ }
383
+
384
+ if is("no") || is("false") || is("off") {
385
+ return PlainScalar::Boolean(false);
386
+ }
387
+
388
+ if is(".inf") || is("+.inf") {
389
+ return PlainScalar::Float(f64::INFINITY);
390
+ }
391
+
392
+ if is("-.inf") {
393
+ return PlainScalar::Float(f64::NEG_INFINITY);
394
+ }
395
+
396
+ if is(".nan") {
397
+ return PlainScalar::Float(f64::NAN);
398
+ }
399
+ }
400
+
401
+ if is_psych_sexagesimal(value) {
402
+ return sexagesimal_value(value);
403
+ }
404
+
405
+ if is_psych_float(value) {
406
+ let mut cleaned: String = value.chars().filter(|character| !matches!(character, ',' | '_')).collect();
407
+
408
+ if let Some(dot) = cleaned.find('.') {
409
+ if matches!(cleaned.as_bytes().get(dot + 1), None | Some(b'e') | Some(b'E')) {
410
+ cleaned.remove(dot);
411
+ }
412
+ }
413
+
414
+ return cleaned.parse::<f64>().map(PlainScalar::Float).unwrap_or(PlainScalar::String);
415
+ }
416
+
417
+ if is_psych_integer(value) {
418
+ return integer_value(value);
419
+ }
420
+
421
+ PlainScalar::String
422
+ }
423
+
424
+ fn integer_value(value: &str) -> PlainScalar {
425
+ let cleaned: String = value.chars().filter(|character| !matches!(character, ',' | '_')).collect();
426
+
427
+ let (negative, digits) = match cleaned.as_bytes()[0] {
428
+ b'-' => (true, &cleaned[1..]),
429
+ b'+' => (false, &cleaned[1..]),
430
+ _ => (false, cleaned.as_str()),
431
+ };
432
+
433
+ let (radix, digits) = if let Some(digits) = digits.strip_prefix("0b") {
434
+ (2, digits)
435
+ } else if let Some(digits) = digits.strip_prefix("0x") {
436
+ (16, digits)
437
+ } else if digits.len() > 1 && digits.starts_with('0') {
438
+ (8, &digits[1..])
439
+ } else {
440
+ (10, digits)
441
+ };
442
+
443
+ let magnitude = u128::from_str_radix(digits, radix).ok().and_then(|magnitude| i128::try_from(magnitude).ok());
444
+ let integer = magnitude.map(|magnitude| if negative { -magnitude } else { magnitude });
445
+
446
+ integer_or_float(integer, || {
447
+ let magnitude = match radix {
448
+ 10 => digits.parse::<f64>().unwrap_or(f64::INFINITY),
449
+ _ => digits
450
+ .chars()
451
+ .fold(0.0, |total, digit| total * f64::from(radix) + f64::from(digit.to_digit(radix).unwrap_or(0))),
452
+ };
453
+
454
+ if negative {
455
+ -magnitude
456
+ } else {
457
+ magnitude
458
+ }
459
+ })
460
+ }
461
+
462
+ fn integer_or_float(integer: Option<i128>, approximate: impl FnOnce() -> f64) -> PlainScalar {
463
+ match integer {
464
+ Some(integer) if i64::try_from(integer).is_ok() || u64::try_from(integer).is_ok() => PlainScalar::Integer(integer),
465
+ _ => PlainScalar::Float(approximate()),
466
+ }
467
+ }
468
+
469
+ /// Psych sums the parts as `part * 60 ** (index - 2).abs`, so `1:30` is 5400, not 90.
470
+ fn sexagesimal_value(value: &str) -> PlainScalar {
471
+ let parts: Vec<&str> = value.split(':').collect();
472
+ let exponent = |index: usize| (index as i32 - 2).unsigned_abs();
473
+
474
+ let float_total = || {
475
+ parts.iter().enumerate().fold(0.0, |total, (index, part)| {
476
+ total + ruby_number_prefix(part, true).parse::<f64>().unwrap_or(0.0) * 60f64.powi(exponent(index) as i32)
477
+ })
478
+ };
479
+
480
+ if value.contains('.') {
481
+ return PlainScalar::Float(float_total());
482
+ }
483
+
484
+ let integer_total = parts.iter().enumerate().try_fold(0i128, |total, (index, part)| {
485
+ let part = ruby_number_prefix(part, false).parse::<i128>().ok()?;
486
+
487
+ total.checked_add(part.checked_mul(60i128.pow(exponent(index)))?)
488
+ });
489
+
490
+ integer_or_float(integer_total, float_total)
491
+ }
492
+
493
+ fn ruby_number_prefix(text: &str, fraction: bool) -> String {
494
+ let bytes = text.as_bytes();
495
+ let mut number = String::new();
496
+ let mut position = skip_sign(bytes);
497
+
498
+ if position == 1 {
499
+ number.push(bytes[0] as char);
500
+ }
501
+
502
+ position = push_ruby_digits(bytes, position, &mut number);
503
+
504
+ if fraction && bytes.get(position) == Some(&b'.') && bytes.get(position + 1).is_some_and(u8::is_ascii_digit) {
505
+ number.push('.');
506
+ push_ruby_digits(bytes, position + 1, &mut number);
507
+ }
508
+
509
+ number
510
+ }
511
+
512
+ fn push_ruby_digits(bytes: &[u8], mut position: usize, number: &mut String) -> usize {
513
+ let start = position;
514
+
515
+ while let Some(&byte) = bytes.get(position) {
516
+ let underscore_between_digits =
517
+ byte == b'_' && position > start && bytes[position - 1].is_ascii_digit() && bytes.get(position + 1).is_some_and(u8::is_ascii_digit);
518
+
519
+ if byte.is_ascii_digit() {
520
+ number.push(byte as char);
521
+ } else if !underscore_between_digits {
522
+ break;
523
+ }
524
+
525
+ position += 1;
526
+ }
527
+
528
+ position
529
+ }
530
+
531
+ pub fn is_yaml_1_2_core_non_string(value: &str) -> bool {
532
+ if matches!(
533
+ value,
534
+ "" | "~" | "null" | "Null" | "NULL" | "true" | "True" | "TRUE" | "false" | "False" | "FALSE" | ".nan" | ".NaN" | ".NAN"
535
+ ) {
536
+ return true;
537
+ }
538
+
539
+ let bytes = value.as_bytes();
540
+ let unsigned = &bytes[skip_sign(bytes)..];
541
+
542
+ if matches!(unsigned, b".inf" | b".Inf" | b".INF") {
543
+ return true;
544
+ }
545
+
546
+ let all = |digits: &[u8], valid: fn(&u8) -> bool| !digits.is_empty() && digits.iter().all(valid);
547
+
548
+ match bytes {
549
+ [b'0', b'o', digits @ ..] if all(digits, |byte| matches!(byte, b'0'..=b'7')) => return true,
550
+ [b'0', b'x', digits @ ..] if all(digits, u8::is_ascii_hexdigit) => return true,
551
+ _ => {}
552
+ }
553
+
554
+ let integer = unsigned.iter().take_while(|byte| byte.is_ascii_digit()).count();
555
+ let mut position = integer;
556
+
557
+ if unsigned.get(position) == Some(&b'.') {
558
+ let fraction = unsigned[position + 1..].iter().take_while(|byte| byte.is_ascii_digit()).count();
559
+
560
+ if integer == 0 && fraction == 0 {
561
+ return false;
562
+ }
563
+
564
+ position += 1 + fraction;
565
+ } else if integer == 0 {
566
+ return false;
567
+ }
568
+
569
+ if matches!(unsigned.get(position), Some(b'e') | Some(b'E')) {
570
+ let exponent_start = position + 1 + usize::from(matches!(unsigned.get(position + 1), Some(b'+') | Some(b'-')));
571
+ let exponent = unsigned
572
+ .get(exponent_start..)
573
+ .unwrap_or_default()
574
+ .iter()
575
+ .take_while(|byte| byte.is_ascii_digit())
576
+ .count();
577
+
578
+ if exponent == 0 {
579
+ return false;
580
+ }
581
+
582
+ position = exponent_start + exponent;
583
+ }
584
+
585
+ position == unsigned.len()
586
+ }
587
+
588
+ fn take_digits(bytes: &[u8], mut position: usize, min: usize, max: usize) -> Option<usize> {
589
+ let start = position;
590
+
591
+ while position < bytes.len() && position - start < max && bytes[position].is_ascii_digit() {
592
+ position += 1;
593
+ }
594
+
595
+ (position - start >= min).then_some(position)
596
+ }
597
+
598
+ fn skip_sign(bytes: &[u8]) -> usize {
599
+ usize::from(matches!(bytes.first(), Some(b'+') | Some(b'-')))
600
+ }
601
+
602
+ fn is_psych_date_or_time(value: &str) -> bool {
603
+ let bytes = value.as_bytes();
604
+ let start = usize::from(bytes.first() == Some(&b'-'));
605
+
606
+ let Some(position) = take_digits(bytes, start, 4, 4) else { return false };
607
+ if bytes.get(position) != Some(&b'-') {
608
+ return false;
609
+ }
610
+
611
+ let Some(position) = take_digits(bytes, position + 1, 1, 2) else {
612
+ return false;
613
+ };
614
+ if bytes.get(position) != Some(&b'-') {
615
+ return false;
616
+ }
617
+
618
+ let Some(position) = take_digits(bytes, position + 1, 1, 2) else {
619
+ return false;
620
+ };
621
+
622
+ match bytes.get(position) {
623
+ None => true,
624
+ Some(byte) => matches!(byte, b'T' | b't') || byte.is_ascii_whitespace(),
625
+ }
626
+ }
627
+
628
+ fn is_psych_sexagesimal(value: &str) -> bool {
629
+ let bytes = value.as_bytes();
630
+ let mut position = skip_sign(bytes);
631
+
632
+ if !bytes.get(position).is_some_and(u8::is_ascii_digit) {
633
+ return false;
634
+ }
635
+
636
+ while bytes.get(position).is_some_and(|byte| byte.is_ascii_digit() || *byte == b'_') {
637
+ position += 1;
638
+ }
639
+
640
+ let mut groups = 0;
641
+
642
+ while bytes.get(position) == Some(&b':') && groups < 2 {
643
+ let Some(next) = take_digits(bytes, position + 1, 1, 2) else { return false };
644
+
645
+ if next - position == 3 && bytes[position + 1] > b'5' {
646
+ return false;
647
+ }
648
+
649
+ position = next;
650
+ groups += 1;
651
+ }
652
+
653
+ if groups == 0 {
654
+ return false;
655
+ }
656
+
657
+ if bytes.get(position) == Some(&b'.') {
658
+ position += 1;
659
+
660
+ while bytes.get(position).is_some_and(|byte| byte.is_ascii_digit() || *byte == b'_') {
661
+ position += 1;
662
+ }
663
+ }
664
+
665
+ position == bytes.len()
666
+ }
667
+
668
+ fn is_psych_float(value: &str) -> bool {
669
+ let bytes = value.as_bytes();
670
+ let sign = skip_sign(bytes);
671
+ let mut position = sign;
672
+
673
+ if bytes.get(position).is_some_and(u8::is_ascii_digit) {
674
+ while bytes.get(position).is_some_and(|byte| byte.is_ascii_digit() || *byte == b'_' || *byte == b',') {
675
+ position += 1;
676
+ }
677
+ }
678
+
679
+ if bytes.get(position) != Some(&b'.') {
680
+ return false;
681
+ }
682
+
683
+ position += 1;
684
+
685
+ while bytes.get(position).is_some_and(u8::is_ascii_digit) {
686
+ position += 1;
687
+ }
688
+
689
+ if matches!(bytes.get(position), Some(b'e') | Some(b'E')) {
690
+ if !matches!(bytes.get(position + 1), Some(b'+') | Some(b'-')) {
691
+ return false;
692
+ }
693
+
694
+ let Some(next) = take_digits(bytes, position + 2, 1, usize::MAX) else {
695
+ return false;
696
+ };
697
+ position = next;
698
+ }
699
+
700
+ position == bytes.len() && bytes.len() > sign + 1
701
+ }
702
+
703
+ fn is_psych_integer(value: &str) -> bool {
704
+ let bytes = value.as_bytes();
705
+ let rest = &bytes[skip_sign(bytes)..];
706
+
707
+ let prefixed = |digits: &[u8], valid: fn(&u8) -> bool| {
708
+ let digits: Vec<&u8> = digits.iter().skip_while(|byte| matches!(byte, b'_' | b',')).collect();
709
+
710
+ digits.first().is_some_and(|byte| valid(byte)) && digits.iter().all(|byte| valid(byte) || matches!(byte, b'_' | b','))
711
+ };
712
+
713
+ match rest {
714
+ [] => false,
715
+ [b'0'] => true,
716
+ [b'0', b'b', digits @ ..] => prefixed(digits, |byte| matches!(byte, b'0' | b'1')),
717
+ [b'0', b'x', digits @ ..] => prefixed(digits, u8::is_ascii_hexdigit),
718
+ [b'0', digits @ ..] => prefixed(digits, |byte| matches!(byte, b'0'..=b'7')),
719
+ [first, tail @ ..] if (b'1'..=b'9').contains(first) => {
720
+ let mut tail = tail.iter().peekable();
721
+
722
+ while let Some(byte) = tail.next() {
723
+ match byte {
724
+ b'0'..=b'9' => {}
725
+ b'_' | b',' if tail.peek().is_some_and(|next| next.is_ascii_digit()) => {}
726
+ _ => return false,
727
+ }
728
+ }
729
+
730
+ true
731
+ }
732
+ _ => false,
733
+ }
333
734
  }
334
735
 
335
736
  pub fn quote_scalar(value: &str) -> String {
@@ -344,6 +745,8 @@ pub fn quote_scalar_styled(value: &str, style: Option<&str>) -> String {
344
745
  match style {
345
746
  Some("double") => format_scalar_value(value, SyntaxKind::DOUBLE_QUOTED_SCALAR),
346
747
  Some("single") if is_single_quotable(value) => format_scalar_value(value, SyntaxKind::SINGLE_QUOTED_SCALAR),
748
+ Some("flow") if needs_quoting_in_flow(value) => format_scalar_value(value, SyntaxKind::DOUBLE_QUOTED_SCALAR),
749
+ Some("flow") => value.to_string(),
347
750
 
348
751
  _ => quote_scalar(value),
349
752
  }
@@ -537,6 +940,43 @@ pub fn column_at(source: &str, offset: usize) -> usize {
537
940
  offset - line_start_at(source, offset)
538
941
  }
539
942
 
943
+ #[derive(Debug)]
944
+ pub struct LineIndex {
945
+ source: String,
946
+ line_starts: Vec<usize>,
947
+ length: usize,
948
+ }
949
+
950
+ impl LineIndex {
951
+ pub fn new(source: &str) -> Self {
952
+ let line_starts = std::iter::once(0).chain(source.match_indices('\n').map(|(position, _)| position + 1)).collect();
953
+
954
+ Self {
955
+ source: source.to_string(),
956
+ line_starts,
957
+ length: source.len(),
958
+ }
959
+ }
960
+
961
+ pub fn source_length(&self) -> usize {
962
+ self.length
963
+ }
964
+
965
+ pub fn line_at(&self, offset: usize) -> usize {
966
+ self.line_starts.partition_point(|&start| start <= offset)
967
+ }
968
+
969
+ pub fn column_at(&self, offset: usize) -> usize {
970
+ offset - self.line_starts[self.line_at(offset) - 1]
971
+ }
972
+
973
+ pub fn character_column_at(&self, offset: usize) -> usize {
974
+ let line_start = self.line_starts[self.line_at(offset) - 1];
975
+
976
+ self.source.get(line_start..offset).map_or(offset - line_start, |text| text.chars().count())
977
+ }
978
+ }
979
+
540
980
  pub fn preceding_whitespace_indent(node: &SyntaxNode) -> String {
541
981
  if let Some(token) = preceding_whitespace_token(node) {
542
982
  let text = token.text();
@@ -598,56 +1038,9 @@ pub fn removal_range(node: &SyntaxNode) -> TextRange {
598
1038
  }
599
1039
 
600
1040
  pub fn is_yaml_non_string(value: &str) -> bool {
601
- detect_yaml_type_from_plain(value) != YerbaValueType::String
602
- }
603
-
604
- pub fn is_yaml_truthy(value: &str) -> bool {
605
- matches!(value, "true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON")
1041
+ detect_yaml_type_from_plain(value) != YerbaValueType::String || is_yaml_1_2_core_non_string(value)
606
1042
  }
607
1043
 
608
1044
  pub fn detect_yaml_type_from_plain(value: &str) -> YerbaValueType {
609
- // Null (YAML 1.1 + 1.2)
610
- if matches!(value, "null" | "Null" | "NULL" | "~" | "") {
611
- return YerbaValueType::Null;
612
- }
613
-
614
- // Boolean, resolved the way libyaml/Psych resolve it: the YAML 1.1 bool set
615
- // without the single-letter y/Y/n/N forms (which libyaml treats as strings),
616
- // and broader than YAML 1.2 core schema (which only accepts true/false).
617
- if matches!(
618
- value,
619
- "true" | "True" | "TRUE" | "false" | "False" | "FALSE" | "yes" | "Yes" | "YES" | "no" | "No" | "NO" | "on" | "On" | "ON" | "off" | "Off" | "OFF"
620
- ) {
621
- return YerbaValueType::Boolean;
622
- }
623
-
624
- // Integer
625
- if value.parse::<i64>().is_ok() {
626
- return YerbaValueType::Integer;
627
- }
628
-
629
- // Hex (0x...) — only valid hex digits after prefix
630
- if (value.starts_with("0x") || value.starts_with("0X")) && value.len() > 2 && value[2..].chars().all(|c| c.is_ascii_hexdigit()) {
631
- return YerbaValueType::Integer;
632
- }
633
-
634
- // Octal (0o...) — only valid octal digits after prefix
635
- if (value.starts_with("0o") || value.starts_with("0O")) && value.len() > 2 && value[2..].chars().all(|c| matches!(c, '0'..='7')) {
636
- return YerbaValueType::Integer;
637
- }
638
-
639
- // Special floats (YAML 1.1 + 1.2)
640
- if matches!(
641
- value,
642
- ".inf" | ".Inf" | ".INF" | "-.inf" | "-.Inf" | "-.INF" | "+.inf" | "+.Inf" | "+.INF" | ".nan" | ".NaN" | ".NAN"
643
- ) {
644
- return YerbaValueType::Float;
645
- }
646
-
647
- // Float
648
- if value.parse::<f64>().is_ok() {
649
- return YerbaValueType::Float;
650
- }
651
-
652
- YerbaValueType::String
1045
+ resolve_plain_scalar(value).value_type()
653
1046
  }
@@ -5,11 +5,12 @@ pub struct ControlCharacter {
5
5
  pub character: char,
6
6
  pub line: usize,
7
7
  pub column: usize,
8
+ pub character_column: usize,
8
9
  }
9
10
 
10
11
  impl std::fmt::Display for ControlCharacter {
11
12
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12
- write!(f, "U+{:04X} at line {}, column {}", self.character as u32, self.line, self.column)
13
+ write!(f, "U+{:04X} at line {}, column {}", self.character as u32, self.line, self.character_column + 1)
13
14
  }
14
15
  }
15
16
 
@@ -18,11 +19,12 @@ pub fn find_control_characters(source: &str) -> Vec<ControlCharacter> {
18
19
  .lines()
19
20
  .enumerate()
20
21
  .flat_map(|(index, line)| {
21
- line.chars().enumerate().filter_map(move |(column, character)| {
22
+ line.char_indices().enumerate().filter_map(move |(character_column, (column, character))| {
22
23
  is_control_character(character).then_some(ControlCharacter {
23
24
  character,
24
25
  line: index + 1,
25
- column: column + 1,
26
+ column,
27
+ character_column,
26
28
  })
27
29
  })
28
30
  })