1use std::collections::HashMap;
28
29use crate::pycompat::{py_float_repr, py_repr_str, py_strip};
30
31pub const DEFAULT_MAX_FILE_BYTES: u64 = 1 << 20; pub const MAX_FRONTMATTER_BYTES: usize = 64 << 10; pub const MAX_FRONTMATTER_DEPTH: usize = 32;
38pub const SUPPORTED_SCHEMA_VERSIONS: &[i64] = &[1];
39
40const SUPPORTED_FIELDS: [&str; 5] = ["schema_version", "id", "type", "relationships", "tags"];
41
42pub fn exceeds_byte_cap(text: &str, cap: usize) -> bool {
46 text.len() > cap
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub enum FileCap {
66 Cap(u64),
67 OracleCrash(&'static str),
69}
70
71const ORACLE_READ_OVERFLOW_MIN: i128 = i64::MAX as i128;
73const ORACLE_READ_TOOLARGE_MIN: i128 = i64::MAX as i128 - 33;
75
76pub fn file_cap() -> FileCap {
81 match std::env::var("DECIDED_MAX_FILE_BYTES") {
82 Ok(raw) => file_cap_from(Some(&raw)),
83 Err(_) => FileCap::Cap(DEFAULT_MAX_FILE_BYTES),
84 }
85}
86
87pub fn file_cap_from(raw: Option<&str>) -> FileCap {
88 if let Some(raw) = raw {
89 if let Some(v) = crate::markdown::py_parse_int(raw) {
90 if v >= ORACLE_READ_OVERFLOW_MIN {
91 return FileCap::OracleCrash(
92 "OverflowError: cannot fit 'int' into an index-sized integer",
93 );
94 }
95 if v >= ORACLE_READ_TOOLARGE_MIN {
96 return FileCap::OracleCrash("OverflowError: byte string is too large");
97 }
98 if v > 0 {
99 return FileCap::Cap(v as u64);
100 }
101 }
102 }
103 FileCap::Cap(DEFAULT_MAX_FILE_BYTES)
104}
105
106#[derive(Clone, Debug, PartialEq)]
111pub struct Issue {
112 pub severity: &'static str,
113 pub code: String,
114 pub message: String,
115 pub line: Option<i64>,
116}
117
118impl Issue {
119 fn error(code: &str, message: String) -> Issue {
120 Issue {
121 severity: "error",
122 code: code.to_string(),
123 message,
124 line: None,
125 }
126 }
127}
128
129#[derive(Clone, Debug, PartialEq, Eq)]
133pub enum SchemaVersion {
134 Int(i64),
135 Big(BigInt),
136}
137
138impl std::fmt::Display for SchemaVersion {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 match self {
141 SchemaVersion::Int(v) => write!(f, "{v}"),
142 SchemaVersion::Big(b) => write!(f, "{b}"),
143 }
144 }
145}
146
147#[derive(Clone, Debug, PartialEq)]
148pub struct ArtifactMetadata {
149 pub schema_version: SchemaVersion,
150 pub id: Option<String>,
151 pub artifact_type: Option<String>,
152 pub relationships: Vec<(String, Vec<String>)>,
154 pub tags: Vec<String>,
155 pub provenance: &'static str,
156}
157
158pub fn normalize_id(value: &str) -> String {
160 py_strip(value).to_uppercase()
161}
162
163fn is_crockford(c: char) -> bool {
164 matches!(c, '0'..='9' | 'A'..='H' | 'J' | 'K' | 'M' | 'N' | 'P'..='T' | 'V'..='Z')
165}
166
167pub fn is_valid_id(value: &str) -> bool {
169 let n = normalize_id(value);
170 let chars: Vec<char> = n.chars().collect();
171 if chars.is_empty() || !chars[0].is_ascii_uppercase() {
172 return false;
173 }
174 for keylen in 1..=9usize {
176 let dash = 1 + keylen;
177 if chars.len() != dash + 13 {
178 continue;
179 }
180 if !chars[1..dash]
181 .iter()
182 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
183 {
184 continue;
185 }
186 if chars[dash] != '-' {
187 continue;
188 }
189 if chars[dash + 1..].iter().all(|c| is_crockford(*c)) {
190 return true;
191 }
192 }
193 false
194}
195
196#[derive(Clone, Debug, PartialEq, Eq)]
208pub struct BigInt {
209 pub neg: bool,
210 pub digits: String,
212}
213
214impl std::fmt::Display for BigInt {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 if self.neg {
217 f.write_str("-")?;
218 }
219 f.write_str(&self.digits)
220 }
221}
222
223#[derive(Clone, Debug, PartialEq)]
224pub enum Yaml {
225 Null,
226 Bool(bool),
227 Int(i64),
228 BigInt(BigInt),
230 Float(f64),
231 Str(String),
232 Date {
233 year: i64,
234 month: u32,
235 day: u32,
236 },
237 DateTime {
238 year: i64,
239 month: u32,
240 day: u32,
241 hour: u32,
242 minute: u32,
243 second: u32,
244 micro: u32,
245 tz: Option<i64>,
247 },
248 Bytes(Vec<u8>),
249 List(Vec<Yaml>),
250 Tuple(Vec<Yaml>),
252 Map(Vec<(Yaml, Yaml)>),
254 Set(Vec<Yaml>),
256}
257
258pub fn py_eq(a: &Yaml, b: &Yaml) -> bool {
263 fn num(v: &Yaml) -> Option<f64> {
264 match v {
265 Yaml::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
266 Yaml::Int(i) => Some(*i as f64),
267 Yaml::Float(f) => Some(*f),
268 _ => None,
269 }
270 }
271 match (a, b) {
272 (Yaml::Null, Yaml::Null) => true,
273 (Yaml::Str(x), Yaml::Str(y)) => x == y,
274 (Yaml::Bytes(x), Yaml::Bytes(y)) => x == y,
275 (
276 Yaml::Date { year, month, day },
277 Yaml::Date {
278 year: y2,
279 month: m2,
280 day: d2,
281 },
282 ) => year == y2 && month == m2 && day == d2,
283 (Yaml::DateTime { .. }, Yaml::DateTime { .. }) => datetime_eq(a, b),
284 (Yaml::Tuple(x), Yaml::Tuple(y)) => {
285 x.len() == y.len() && x.iter().zip(y).all(|(p, q)| py_eq(p, q))
286 }
287 (Yaml::List(x), Yaml::List(y)) => {
288 x.len() == y.len() && x.iter().zip(y).all(|(p, q)| py_eq(p, q))
289 }
290 (Yaml::BigInt(x), Yaml::BigInt(y)) => x == y,
291 (Yaml::BigInt(_), Yaml::Int(_) | Yaml::Bool(_))
294 | (Yaml::Int(_) | Yaml::Bool(_), Yaml::BigInt(_)) => false,
295 (Yaml::BigInt(x), Yaml::Float(f)) | (Yaml::Float(f), Yaml::BigInt(x)) => {
297 bigint_eq_float(x, *f)
298 }
299 (Yaml::BigInt(_), _) | (_, Yaml::BigInt(_)) => false,
300 _ => match (num(a), num(b)) {
301 (Some(x), Some(y)) => {
302 if x.is_nan() && y.is_nan() {
303 matches!((a, b), (Yaml::Float(_), Yaml::Float(_)))
305 } else {
306 exact_num_eq(a, b, x, y)
307 }
308 }
309 _ => false,
310 },
311 }
312}
313
314fn exact_num_eq(a: &Yaml, b: &Yaml, x: f64, y: f64) -> bool {
317 fn as_int(v: &Yaml) -> Option<i64> {
318 match v {
319 Yaml::Bool(b) => Some(*b as i64),
320 Yaml::Int(i) => Some(*i),
321 _ => None,
322 }
323 }
324 match (as_int(a), as_int(b)) {
325 (Some(i), Some(j)) => i == j,
326 (Some(i), None) => float_eq_int(y, i),
327 (None, Some(j)) => float_eq_int(x, j),
328 (None, None) => x == y,
329 }
330}
331
332fn float_eq_int(f: f64, i: i64) -> bool {
333 if !f.is_finite() || f != f.trunc() {
334 return false;
335 }
336 if !(-9.223_372_036_854_776E18..9.223_372_036_854_776E18).contains(&f) {
337 return false;
338 }
339 (f as i64) == i && (f as i64) as f64 == f
340}
341
342const INT_MAX_STR_DIGITS: usize = 4300;
351
352fn int_to_str_limit_err() -> YErr {
354 YErr::Internal(
355 "ValueError: Exceeds the limit (4300 digits) for integer string conversion; \
356 use sys.set_int_max_str_digits() to increase the limit"
357 .to_string(),
358 )
359}
360
361fn int_parse_limit_err(ndigits: usize) -> YErr {
363 YErr::Internal(format!(
364 "ValueError: Exceeds the limit (4300 digits) for integer string conversion: \
365 value has {ndigits} digits; use sys.set_int_max_str_digits() to increase the limit"
366 ))
367}
368
369#[derive(Clone, Debug)]
373struct Mag(Vec<u32>);
374
375const MAG_BASE: u64 = 1_000_000_000;
376
377impl Mag {
378 fn zero() -> Mag {
379 Mag(vec![0])
380 }
381
382 fn from_u64(mut v: u64) -> Mag {
383 let mut limbs = Vec::new();
384 loop {
385 limbs.push((v % MAG_BASE) as u32);
386 v /= MAG_BASE;
387 if v == 0 {
388 break;
389 }
390 }
391 Mag(limbs)
392 }
393
394 fn is_zero(&self) -> bool {
395 self.0.iter().all(|&l| l == 0)
396 }
397
398 fn trim(&mut self) {
399 while self.0.len() > 1 && *self.0.last().unwrap() == 0 {
400 self.0.pop();
401 }
402 }
403
404 fn mul_add_small(&mut self, m: u64, a: u64) {
406 let mut carry = a;
407 for limb in &mut self.0 {
408 let v = *limb as u64 * m + carry;
409 *limb = (v % MAG_BASE) as u32;
410 carry = v / MAG_BASE;
411 }
412 while carry > 0 {
413 self.0.push((carry % MAG_BASE) as u32);
414 carry /= MAG_BASE;
415 }
416 self.trim();
417 }
418
419 fn add(&mut self, other: &Mag) {
420 if other.0.len() > self.0.len() {
421 self.0.resize(other.0.len(), 0);
422 }
423 let mut carry = 0u64;
424 for (i, limb) in self.0.iter_mut().enumerate() {
425 let v = *limb as u64 + other.0.get(i).map_or(0, |&l| l as u64) + carry;
426 *limb = (v % MAG_BASE) as u32;
427 carry = v / MAG_BASE;
428 }
429 if carry > 0 {
430 self.0.push(carry as u32);
431 }
432 }
433
434 fn sub(&mut self, other: &Mag) {
436 let mut borrow = 0i64;
437 for (i, limb) in self.0.iter_mut().enumerate() {
438 let mut v = *limb as i64 - other.0.get(i).map_or(0, |&l| l as i64) - borrow;
439 if v < 0 {
440 v += MAG_BASE as i64;
441 borrow = 1;
442 } else {
443 borrow = 0;
444 }
445 *limb = v as u32;
446 }
447 debug_assert_eq!(borrow, 0, "Mag::sub underflow");
448 self.trim();
449 }
450
451 fn cmp_mag(&self, other: &Mag) -> std::cmp::Ordering {
452 let (mut a, mut b) = (self.clone(), other.clone());
453 a.trim();
454 b.trim();
455 if a.0.len() != b.0.len() {
456 return a.0.len().cmp(&b.0.len());
457 }
458 for (x, y) in a.0.iter().rev().zip(b.0.iter().rev()) {
459 if x != y {
460 return x.cmp(y);
461 }
462 }
463 std::cmp::Ordering::Equal
464 }
465
466 fn to_decimal(&self) -> String {
468 let mut limbs = self.0.clone();
469 while limbs.len() > 1 && *limbs.last().unwrap() == 0 {
470 limbs.pop();
471 }
472 let mut out = format!("{}", limbs.last().unwrap());
473 for limb in limbs.iter().rev().skip(1) {
474 out.push_str(&format!("{limb:09}"));
475 }
476 out
477 }
478
479 fn to_i64(&self, neg: bool) -> Option<i64> {
481 let mut acc: i64 = 0;
482 for &limb in self.0.iter().rev() {
483 acc = acc.checked_mul(MAG_BASE as i64)?;
484 acc = if neg {
485 acc.checked_sub(limb as i64)?
486 } else {
487 acc.checked_add(limb as i64)?
488 };
489 }
490 Some(acc)
491 }
492}
493
494fn yaml_int(neg: bool, mag: &Mag) -> Yaml {
497 match mag.to_i64(neg) {
498 Some(v) => Yaml::Int(v),
499 None => Yaml::BigInt(BigInt {
500 neg,
501 digits: mag.to_decimal(),
502 }),
503 }
504}
505
506fn bigint_eq_float(x: &BigInt, f: f64) -> bool {
509 if !f.is_finite() || f != f.trunc() || f == 0.0 {
510 return false;
511 }
512 if (f < 0.0) != x.neg {
513 return false;
514 }
515 f64_integral_magnitude(f.abs()) == x.digits
516}
517
518fn f64_integral_magnitude(f: f64) -> String {
520 let bits = f.to_bits();
521 let exp = ((bits >> 52) & 0x7ff) as i64;
522 let frac = bits & ((1u64 << 52) - 1);
523 let (mut m, mut e) = if exp == 0 {
524 (frac, -1074i64)
525 } else {
526 (frac | (1 << 52), exp - 1075)
527 };
528 while e < 0 {
530 m >>= 1;
531 e += 1;
532 }
533 let mut mag = Mag::from_u64(m);
534 for _ in 0..e {
535 mag.mul_add_small(2, 0);
536 }
537 mag.to_decimal()
538}
539
540fn datetime_eq(a: &Yaml, b: &Yaml) -> bool {
541 if let (
542 Yaml::DateTime {
543 year,
544 month,
545 day,
546 hour,
547 minute,
548 second,
549 micro,
550 tz,
551 },
552 Yaml::DateTime {
553 year: y2,
554 month: m2,
555 day: d2,
556 hour: h2,
557 minute: mi2,
558 second: s2,
559 micro: us2,
560 tz: tz2,
561 },
562 ) = (a, b)
563 {
564 match (tz, tz2) {
565 (None, None) => {
566 (year, month, day, hour, minute, second, micro)
567 == (y2, m2, d2, h2, mi2, s2, us2)
568 }
569 (Some(o1), Some(o2)) => {
570 let u1 = utc_micros(*year, *month, *day, *hour, *minute, *second, *micro) - o1 * 1_000_000;
571 let u2 = utc_micros(*y2, *m2, *d2, *h2, *mi2, *s2, *us2) - o2 * 1_000_000;
572 u1 == u2
573 }
574 _ => false,
576 }
577 } else {
578 false
579 }
580}
581
582fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
583 let y = if m <= 2 { y - 1 } else { y };
585 let era = if y >= 0 { y } else { y - 399 } / 400;
586 let yoe = y - era * 400;
587 let mp = (m + 9) % 12;
588 let doy = (153 * mp + 2) / 5 + d - 1;
589 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
590 era * 146_097 + doe - 719_468
591}
592
593fn utc_micros(y: i64, mo: u32, d: u32, h: u32, mi: u32, s: u32, us: u32) -> i64 {
594 let days = days_from_civil(y, mo as i64, d as i64);
595 ((days * 86_400) + h as i64 * 3600 + mi as i64 * 60 + s as i64) * 1_000_000 + us as i64
596}
597
598fn unhashable_type_name(v: &Yaml) -> Option<&'static str> {
601 match v {
602 Yaml::List(_) => Some("list"),
603 Yaml::Map(_) => Some("dict"),
604 Yaml::Set(_) => Some("set"),
605 Yaml::Tuple(items) => items.iter().find_map(unhashable_type_name),
606 _ => None,
607 }
608}
609
610fn py_repr_bytes(b: &[u8]) -> String {
615 let has_sq = b.contains(&b'\'');
616 let has_dq = b.contains(&b'"');
617 let quote = if has_sq && !has_dq { b'"' } else { b'\'' };
618 let mut out = String::from("b");
619 out.push(quote as char);
620 for &c in b {
621 if c == quote || c == b'\\' {
622 out.push('\\');
623 out.push(c as char);
624 } else if c == b'\t' {
625 out.push_str("\\t");
626 } else if c == b'\n' {
627 out.push_str("\\n");
628 } else if c == b'\r' {
629 out.push_str("\\r");
630 } else if (0x20..0x7f).contains(&c) {
631 out.push(c as char);
632 } else {
633 out.push_str(&format!("\\x{c:02x}"));
634 }
635 }
636 out.push(quote as char);
637 out
638}
639
640fn py_repr_timedelta(offset_seconds: i64) -> String {
641 let days = offset_seconds.div_euclid(86_400);
643 let secs = offset_seconds.rem_euclid(86_400);
644 let mut parts: Vec<String> = Vec::new();
645 if days != 0 {
646 parts.push(format!("days={days}"));
647 }
648 if secs != 0 {
649 parts.push(format!("seconds={secs}"));
650 }
651 if parts.is_empty() {
652 "datetime.timedelta(0)".to_string()
653 } else {
654 format!("datetime.timedelta({})", parts.join(", "))
655 }
656}
657
658fn py_repr_tzinfo(offset_seconds: i64) -> String {
659 if offset_seconds == 0 {
660 "datetime.timezone.utc".to_string()
662 } else {
663 format!("datetime.timezone({})", py_repr_timedelta(offset_seconds))
664 }
665}
666
667fn py_repr(v: &Yaml) -> Result<String, YErr> {
671 fn join(items: &[Yaml]) -> Result<String, YErr> {
672 Ok(items
673 .iter()
674 .map(py_repr)
675 .collect::<Result<Vec<_>, _>>()?
676 .join(", "))
677 }
678 Ok(match v {
679 Yaml::Null => "None".to_string(),
680 Yaml::Bool(true) => "True".to_string(),
681 Yaml::Bool(false) => "False".to_string(),
682 Yaml::Int(i) => i.to_string(),
683 Yaml::BigInt(b) => {
684 if b.digits.len() > INT_MAX_STR_DIGITS {
685 return Err(int_to_str_limit_err());
686 }
687 b.to_string()
688 }
689 Yaml::Float(f) => py_float_repr(*f),
690 Yaml::Str(s) => py_repr_str(s),
691 Yaml::Date { year, month, day } => {
692 format!("datetime.date({year}, {month}, {day})")
693 }
694 Yaml::DateTime {
695 year,
696 month,
697 day,
698 hour,
699 minute,
700 second,
701 micro,
702 tz,
703 } => {
704 let mut out = format!("datetime.datetime({year}, {month}, {day}, {hour}, {minute}");
705 if *second != 0 || *micro != 0 {
706 out.push_str(&format!(", {second}"));
707 }
708 if *micro != 0 {
709 out.push_str(&format!(", {micro}"));
710 }
711 if let Some(off) = tz {
712 out.push_str(&format!(", tzinfo={}", py_repr_tzinfo(*off)));
713 }
714 out.push(')');
715 out
716 }
717 Yaml::Bytes(b) => py_repr_bytes(b),
718 Yaml::Tuple(items) => match items.len() {
719 0 => "()".to_string(),
720 1 => format!("({},)", py_repr(&items[0])?),
721 _ => format!("({})", join(items)?),
722 },
723 Yaml::List(items) => format!("[{}]", join(items)?),
724 Yaml::Map(pairs) => format!(
725 "{{{}}}",
726 pairs
727 .iter()
728 .map(|(k, v)| Ok(format!("{}: {}", py_repr(k)?, py_repr(v)?)))
729 .collect::<Result<Vec<_>, YErr>>()?
730 .join(", ")
731 ),
732 Yaml::Set(items) => {
733 if items.is_empty() {
734 "set()".to_string()
735 } else {
736 format!("{{{}}}", join(items)?)
737 }
738 }
739 })
740}
741
742#[derive(Clone, Debug, PartialEq)]
747pub struct FrontmatterSplit {
748 pub raw: Option<String>,
749 pub body: String,
750 pub line_offset: usize,
751 pub unterminated: bool,
752}
753
754pub fn split_frontmatter(text: &str) -> FrontmatterSplit {
758 let lines: Vec<&str> = text.split('\n').collect();
759 if py_strip(lines[0]) != "---" {
760 return FrontmatterSplit {
761 raw: None,
762 body: text.to_string(),
763 line_offset: 0,
764 unterminated: false,
765 };
766 }
767 for i in 1..lines.len() {
768 let stripped = py_strip(lines[i]);
769 if stripped == "---" || stripped == "..." {
770 return FrontmatterSplit {
771 raw: Some(lines[1..i].join("\n")),
772 body: lines[i + 1..].join("\n"),
773 line_offset: i + 1,
774 unterminated: false,
775 };
776 }
777 }
778 FrontmatterSplit {
779 raw: None,
780 body: text.to_string(),
781 line_offset: 0,
782 unterminated: true,
783 }
784}
785
786#[derive(Clone, Debug)]
791enum YErr {
792 Marked(String),
794 Reader(String),
796 Internal(String),
799}
800
801const TAG_STR: &str = "tag:yaml.org,2002:str";
807const TAG_SEQ: &str = "tag:yaml.org,2002:seq";
808const TAG_MAP: &str = "tag:yaml.org,2002:map";
809const TAG_BOOL: &str = "tag:yaml.org,2002:bool";
810const TAG_INT: &str = "tag:yaml.org,2002:int";
811const TAG_FLOAT: &str = "tag:yaml.org,2002:float";
812const TAG_MERGE: &str = "tag:yaml.org,2002:merge";
813const TAG_NULL: &str = "tag:yaml.org,2002:null";
814const TAG_TIMESTAMP: &str = "tag:yaml.org,2002:timestamp";
815const TAG_VALUE: &str = "tag:yaml.org,2002:value";
816
817fn match_bool(v: &str) -> bool {
818 matches!(
819 v,
820 "yes" | "Yes" | "YES" | "no" | "No" | "NO" | "true" | "True" | "TRUE" | "false"
821 | "False" | "FALSE" | "on" | "On" | "ON" | "off" | "Off" | "OFF"
822 )
823}
824
825fn eat_sign(b: &[u8]) -> &[u8] {
826 if !b.is_empty() && (b[0] == b'-' || b[0] == b'+') {
827 &b[1..]
828 } else {
829 b
830 }
831}
832
833fn all_in(b: &[u8], pred: impl Fn(u8) -> bool) -> bool {
834 b.iter().all(|&c| pred(c))
835}
836
837fn strip_signed_exponent(b: &[u8]) -> Option<&[u8]> {
840 if b.is_empty() {
841 return Some(b);
842 }
843 if b[0] == b'e' || b[0] == b'E' {
844 if b.len() >= 3 && (b[1] == b'-' || b[1] == b'+') && b[2..].iter().all(u8::is_ascii_digit)
845 {
846 Some(&b[..0])
847 } else {
848 None
849 }
850 } else {
851 Some(b)
852 }
853}
854
855fn match_float(v: &str) -> bool {
856 let b = v.as_bytes();
857 if !b.is_ascii() {
858 return false;
859 }
860 {
862 let t = eat_sign(b);
863 if t == b".inf" || t == b".Inf" || t == b".INF" {
864 return true;
865 }
866 }
867 if b == b".nan" || b == b".NaN" || b == b".NAN" {
869 return true;
870 }
871 if b.first() == Some(&b'.') {
873 let t = &b[1..];
874 if !t.is_empty() && t[0].is_ascii_digit() {
875 let end = t
876 .iter()
877 .position(|&c| !(c.is_ascii_digit() || c == b'_'))
878 .unwrap_or(t.len());
879 if let Some(rest) = strip_signed_exponent(&t[end..]) {
880 if rest.is_empty() {
881 return true;
882 }
883 }
884 }
885 }
886 let t = eat_sign(b);
887 if t.is_empty() || !t[0].is_ascii_digit() {
888 return false;
889 }
890 let int_end = t
893 .iter()
894 .position(|&c| !(c.is_ascii_digit() || c == b'_'))
895 .unwrap_or(t.len());
896 let rest = &t[int_end..];
897 if rest.first() == Some(&b'.') {
898 let frac = &rest[1..];
899 let frac_end = frac
900 .iter()
901 .position(|&c| !(c.is_ascii_digit() || c == b'_'))
902 .unwrap_or(frac.len());
903 if let Some(after) = strip_signed_exponent(&frac[frac_end..]) {
904 return after.is_empty();
905 }
906 return false;
907 }
908 if rest.first() == Some(&b':') {
909 let mut r = rest;
911 loop {
912 if r.first() != Some(&b':') {
913 break;
914 }
915 r = &r[1..];
916 let mut ndig = 0;
917 if r.first().is_some_and(|c| (b'0'..=b'5').contains(c))
918 && r.get(1).is_some_and(u8::is_ascii_digit)
919 {
920 ndig = 2;
921 } else if r.first().is_some_and(u8::is_ascii_digit) {
922 ndig = 1;
923 }
924 if ndig == 0 {
925 return false;
926 }
927 r = &r[ndig..];
928 }
929 if r.first() != Some(&b'.') {
930 return false;
931 }
932 return all_in(&r[1..], |c| c.is_ascii_digit() || c == b'_');
933 }
934 false
935}
936
937fn match_int(v: &str) -> bool {
938 let b = v.as_bytes();
939 if !b.is_ascii() {
940 return false;
941 }
942 let t = eat_sign(b);
943 if t.is_empty() {
944 return false;
945 }
946 if t.len() > 2 && t.starts_with(b"0b") && all_in(&t[2..], |c| c == b'0' || c == b'1' || c == b'_') {
948 return true;
949 }
950 if t.len() > 2 && t.starts_with(b"0x") && all_in(&t[2..], |c| c.is_ascii_hexdigit() || c == b'_')
952 {
953 return true;
954 }
955 if t.len() > 1 && t[0] == b'0' && all_in(&t[1..], |c| (b'0'..=b'7').contains(&c) || c == b'_') {
957 return true;
958 }
959 if t == b"0" {
961 return true;
962 }
963 if t[0].is_ascii_digit() && t[0] != b'0' {
964 let end = t
965 .iter()
966 .position(|&c| !(c.is_ascii_digit() || c == b'_'))
967 .unwrap_or(t.len());
968 if end == t.len() {
969 return true;
970 }
971 let mut r = &t[end..];
973 if r.first() != Some(&b':') {
974 return false;
975 }
976 while r.first() == Some(&b':') {
977 r = &r[1..];
978 let ndig = if r.first().is_some_and(|c| (b'0'..=b'5').contains(c))
979 && r.get(1).is_some_and(u8::is_ascii_digit)
980 {
981 2
982 } else if r.first().is_some_and(u8::is_ascii_digit) {
983 1
984 } else {
985 return false;
986 };
987 r = &r[ndig..];
988 }
989 return r.is_empty();
990 }
991 false
992}
993
994fn match_null(v: &str) -> bool {
995 matches!(v, "~" | "null" | "Null" | "NULL" | "")
996}
997
998fn match_timestamp(v: &str) -> bool {
1000 let b = v.as_bytes();
1001 if !b.is_ascii() {
1002 return false;
1003 }
1004 let d = |i: usize| b.get(i).is_some_and(u8::is_ascii_digit);
1005 if b.len() == 10
1007 && d(0)
1008 && d(1)
1009 && d(2)
1010 && d(3)
1011 && b[4] == b'-'
1012 && d(5)
1013 && d(6)
1014 && b[7] == b'-'
1015 && d(8)
1016 && d(9)
1017 {
1018 return true;
1019 }
1020 let mut i = 0;
1022 if !(d(0) && d(1) && d(2) && d(3)) {
1023 return false;
1024 }
1025 i += 4;
1026 if b.get(i) != Some(&b'-') {
1027 return false;
1028 }
1029 i += 1;
1030 if !d(i) {
1031 return false;
1032 }
1033 i += 1;
1034 if d(i) {
1035 i += 1;
1036 }
1037 if b.get(i) != Some(&b'-') {
1038 return false;
1039 }
1040 i += 1;
1041 if !d(i) {
1042 return false;
1043 }
1044 i += 1;
1045 if d(i) {
1046 i += 1;
1047 }
1048 match b.get(i) {
1050 Some(b'T') | Some(b't') => i += 1,
1051 Some(b' ') | Some(b'\t') => {
1052 while matches!(b.get(i), Some(b' ') | Some(b'\t')) {
1053 i += 1;
1054 }
1055 }
1056 _ => return false,
1057 }
1058 if !d(i) {
1059 return false;
1060 }
1061 i += 1;
1062 if d(i) {
1063 i += 1;
1064 }
1065 if b.get(i) != Some(&b':') || !(d(i + 1) && d(i + 2)) {
1066 return false;
1067 }
1068 i += 3;
1069 if b.get(i) != Some(&b':') || !(d(i + 1) && d(i + 2)) {
1070 return false;
1071 }
1072 i += 3;
1073 if b.get(i) == Some(&b'.') {
1074 i += 1;
1075 while d(i) {
1076 i += 1;
1077 }
1078 }
1079 if i == b.len() {
1080 return true;
1081 }
1082 while matches!(b.get(i), Some(b' ') | Some(b'\t')) {
1083 i += 1;
1084 }
1085 match b.get(i) {
1086 Some(b'Z') => i += 1,
1087 Some(b'-') | Some(b'+') => {
1088 i += 1;
1089 if !d(i) {
1090 return false;
1091 }
1092 i += 1;
1093 if d(i) {
1094 i += 1;
1095 }
1096 if b.get(i) == Some(&b':') {
1097 if !(d(i + 1) && d(i + 2)) {
1098 return false;
1099 }
1100 i += 3;
1101 }
1102 }
1103 _ => return false,
1104 }
1105 i == b.len()
1106}
1107
1108fn resolve_plain(value: &str) -> &'static str {
1111 let first = value.chars().next();
1112 let candidates: &[&str] = match first {
1113 None => &[TAG_NULL],
1114 Some(c) => match c {
1115 'y' | 'Y' | 't' | 'T' | 'f' | 'F' | 'o' | 'O' => &[TAG_BOOL],
1116 'n' | 'N' => &[TAG_BOOL, TAG_NULL],
1117 '-' | '+' => &[TAG_FLOAT, TAG_INT],
1118 '0'..='9' => &[TAG_FLOAT, TAG_INT, TAG_TIMESTAMP],
1119 '.' => &[TAG_FLOAT],
1120 '<' => &[TAG_MERGE],
1121 '~' => &[TAG_NULL],
1122 '=' => &[TAG_VALUE],
1123 _ => &[],
1124 },
1125 };
1126 for tag in candidates {
1127 let hit = match *tag {
1128 TAG_BOOL => match_bool(value),
1129 TAG_FLOAT => match_float(value),
1130 TAG_INT => match_int(value),
1131 TAG_MERGE => value == "<<",
1132 TAG_NULL => match_null(value),
1133 TAG_TIMESTAMP => match_timestamp(value),
1134 TAG_VALUE => value == "=",
1135 _ => false,
1136 };
1137 if hit {
1138 return tag;
1139 }
1140 }
1141 TAG_STR
1142}
1143
1144fn yaml_printable(c: char) -> bool {
1149 matches!(c,
1150 '\t' | '\n' | '\r' | '\x20'..='\x7e' | '\u{85}'
1151 | '\u{a0}'..='\u{d7ff}' | '\u{e000}'..='\u{fffd}' | '\u{10000}'..='\u{10ffff}')
1152}
1153
1154fn check_printable(data: &str) -> Result<(), YErr> {
1155 for (pos, c) in data.chars().enumerate() {
1156 if let Some(sur) = crate::pycompat::sentinel_surrogate(c) {
1159 return Err(YErr::Reader(format!(
1160 "unacceptable character #x{sur:04x}: special characters are not allowed\n in \"<unicode string>\", position {pos}",
1161 )));
1162 }
1163 if !yaml_printable(c) {
1164 return Err(YErr::Reader(format!(
1165 "unacceptable character #x{:04x}: special characters are not allowed\n in \"<unicode string>\", position {}",
1166 c as u32, pos
1167 )));
1168 }
1169 }
1170 Ok(())
1171}
1172
1173#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1178enum TK {
1179 StreamStart,
1180 StreamEnd,
1181 Directive,
1182 DocumentStart,
1183 DocumentEnd,
1184 BlockSequenceStart,
1185 BlockMappingStart,
1186 BlockEnd,
1187 FlowSequenceStart,
1188 FlowMappingStart,
1189 FlowSequenceEnd,
1190 FlowMappingEnd,
1191 BlockEntry,
1192 FlowEntry,
1193 Key,
1194 Value,
1195 Alias,
1196 Anchor,
1197 Tag,
1198 Scalar,
1199}
1200
1201#[derive(Clone, Debug)]
1202enum DirectiveVal {
1203 Yaml { major_is_1: bool },
1204 Tag { handle: String, prefix: String },
1205 Other,
1206}
1207
1208#[derive(Clone, Debug)]
1209struct Tok {
1210 kind: TK,
1211 value: String,
1213 plain: bool,
1214 tag: Option<(Option<String>, String)>,
1216 directive: Option<DirectiveVal>,
1217}
1218
1219impl Tok {
1220 fn simple(kind: TK) -> Tok {
1221 Tok {
1222 kind,
1223 value: String::new(),
1224 plain: false,
1225 tag: None,
1226 directive: None,
1227 }
1228 }
1229
1230 fn id(&self) -> &'static str {
1232 match self.kind {
1233 TK::StreamStart => "<stream start>",
1234 TK::StreamEnd => "<stream end>",
1235 TK::Directive => "<directive>",
1236 TK::DocumentStart => "<document start>",
1237 TK::DocumentEnd => "<document end>",
1238 TK::BlockSequenceStart => "<block sequence start>",
1239 TK::BlockMappingStart => "<block mapping start>",
1240 TK::BlockEnd => "<block end>",
1241 TK::FlowSequenceStart => "[",
1242 TK::FlowMappingStart => "{",
1243 TK::FlowSequenceEnd => "]",
1244 TK::FlowMappingEnd => "}",
1245 TK::BlockEntry => "-",
1246 TK::FlowEntry => ",",
1247 TK::Key => "?",
1248 TK::Value => ":",
1249 TK::Alias => "<alias>",
1250 TK::Anchor => "<anchor>",
1251 TK::Tag => "<tag>",
1252 TK::Scalar => "<scalar>",
1253 }
1254 }
1255}
1256
1257#[derive(Clone, Copy)]
1262struct SimpleKey {
1263 token_number: usize,
1264 required: bool,
1265 index: usize,
1266 line: usize,
1267 column: i64,
1268}
1269
1270struct Scanner {
1271 buf: Vec<char>, pointer: usize,
1273 index: usize,
1274 line: usize,
1275 column: i64,
1276 done: bool,
1277 flow_level: usize,
1278 tokens: std::collections::VecDeque<Tok>,
1279 tokens_taken: usize,
1280 indent: i64,
1281 indents: Vec<i64>,
1282 allow_simple_key: bool,
1283 possible_simple_keys: HashMap<usize, SimpleKey>,
1284}
1285
1286fn is_break(c: char) -> bool {
1287 matches!(c, '\r' | '\n' | '\u{85}' | '\u{2028}' | '\u{2029}')
1288}
1289
1290fn is_z_ws_break(c: char) -> bool {
1291 matches!(c, '\0' | ' ' | '\t') || is_break(c)
1292}
1293
1294fn is_z_break(c: char) -> bool {
1295 c == '\0' || is_break(c)
1296}
1297
1298fn is_word_char(c: char) -> bool {
1299 c.is_ascii_alphanumeric() || c == '-' || c == '_'
1300}
1301
1302fn repr_char(c: char) -> String {
1303 py_repr_str(&c.to_string())
1304}
1305
1306impl Scanner {
1307 fn new(data: &str) -> Scanner {
1308 let mut buf: Vec<char> = data.chars().collect();
1309 buf.push('\0');
1310 let mut s = Scanner {
1311 buf,
1312 pointer: 0,
1313 index: 0,
1314 line: 0,
1315 column: 0,
1316 done: false,
1317 flow_level: 0,
1318 tokens: std::collections::VecDeque::new(),
1319 tokens_taken: 0,
1320 indent: -1,
1321 indents: Vec::new(),
1322 allow_simple_key: true,
1323 possible_simple_keys: HashMap::new(),
1324 };
1325 s.tokens.push_back(Tok::simple(TK::StreamStart));
1326 s
1327 }
1328
1329 fn peek(&self, k: usize) -> char {
1330 self.buf.get(self.pointer + k).copied().unwrap_or('\0')
1331 }
1332
1333 fn prefix(&self, l: usize) -> String {
1334 let end = (self.pointer + l).min(self.buf.len());
1335 self.buf[self.pointer..end].iter().collect()
1336 }
1337
1338 fn forward(&mut self, l: usize) {
1339 for _ in 0..l {
1340 let ch = self.buf.get(self.pointer).copied().unwrap_or('\0');
1341 self.pointer += 1;
1342 self.index += 1;
1343 let next = self.buf.get(self.pointer).copied().unwrap_or('\0');
1344 if matches!(ch, '\n' | '\u{85}' | '\u{2028}' | '\u{2029}')
1345 || (ch == '\r' && next != '\n')
1346 {
1347 self.line += 1;
1348 self.column = 0;
1349 } else if ch != '\u{feff}' {
1350 self.column += 1;
1351 }
1352 }
1353 }
1354
1355 fn skip_spaces(&mut self) {
1356 while self.peek(0) == ' ' {
1357 self.forward(1);
1358 }
1359 }
1360
1361 fn need_more_tokens(&mut self) -> Result<bool, YErr> {
1364 if self.done {
1365 return Ok(false);
1366 }
1367 if self.tokens.is_empty() {
1368 return Ok(true);
1369 }
1370 self.stale_possible_simple_keys()?;
1371 if self.next_possible_simple_key() == Some(self.tokens_taken) {
1372 return Ok(true);
1373 }
1374 Ok(false)
1375 }
1376
1377 fn ensure(&mut self) -> Result<(), YErr> {
1378 while self.need_more_tokens()? {
1379 self.fetch_more_tokens()?;
1380 }
1381 Ok(())
1382 }
1383
1384 fn check_token(&mut self, choices: &[TK]) -> Result<bool, YErr> {
1385 self.ensure()?;
1386 if let Some(t) = self.tokens.front() {
1387 if choices.is_empty() {
1388 return Ok(true);
1389 }
1390 return Ok(choices.contains(&t.kind));
1391 }
1392 Ok(false)
1393 }
1394
1395 fn peek_token(&mut self) -> Result<Tok, YErr> {
1396 self.ensure()?;
1397 self.tokens
1398 .front()
1399 .cloned()
1400 .ok_or_else(|| YErr::Internal("IndexError: peek past stream end".to_string()))
1401 }
1402
1403 fn get_token(&mut self) -> Result<Tok, YErr> {
1404 self.ensure()?;
1405 match self.tokens.pop_front() {
1406 Some(t) => {
1407 self.tokens_taken += 1;
1408 Ok(t)
1409 }
1410 None => Err(YErr::Internal("IndexError: get past stream end".to_string())),
1411 }
1412 }
1413
1414 fn next_possible_simple_key(&self) -> Option<usize> {
1417 self.possible_simple_keys
1418 .values()
1419 .map(|k| k.token_number)
1420 .min()
1421 }
1422
1423 fn stale_possible_simple_keys(&mut self) -> Result<(), YErr> {
1424 let levels: Vec<usize> = self.possible_simple_keys.keys().copied().collect();
1425 for level in levels {
1426 let key = self.possible_simple_keys[&level];
1427 if key.line != self.line || self.index - key.index > 1024 {
1428 if key.required {
1429 return Err(YErr::Marked("could not find expected ':'".to_string()));
1430 }
1431 self.possible_simple_keys.remove(&level);
1432 }
1433 }
1434 Ok(())
1435 }
1436
1437 fn save_possible_simple_key(&mut self) -> Result<(), YErr> {
1438 let required = self.flow_level == 0 && self.indent == self.column;
1439 if self.allow_simple_key {
1440 self.remove_possible_simple_key()?;
1441 let token_number = self.tokens_taken + self.tokens.len();
1442 self.possible_simple_keys.insert(
1443 self.flow_level,
1444 SimpleKey {
1445 token_number,
1446 required,
1447 index: self.index,
1448 line: self.line,
1449 column: self.column,
1450 },
1451 );
1452 }
1453 Ok(())
1454 }
1455
1456 fn remove_possible_simple_key(&mut self) -> Result<(), YErr> {
1457 if let Some(key) = self.possible_simple_keys.get(&self.flow_level) {
1458 if key.required {
1459 return Err(YErr::Marked("could not find expected ':'".to_string()));
1460 }
1461 self.possible_simple_keys.remove(&self.flow_level);
1462 }
1463 Ok(())
1464 }
1465
1466 fn unwind_indent(&mut self, column: i64) {
1469 if self.flow_level != 0 {
1470 return;
1471 }
1472 while self.indent > column {
1473 self.indent = self.indents.pop().unwrap_or(-1);
1474 self.tokens.push_back(Tok::simple(TK::BlockEnd));
1475 }
1476 }
1477
1478 fn add_indent(&mut self, column: i64) -> bool {
1479 if self.indent < column {
1480 self.indents.push(self.indent);
1481 self.indent = column;
1482 true
1483 } else {
1484 false
1485 }
1486 }
1487
1488 fn fetch_more_tokens(&mut self) -> Result<(), YErr> {
1491 self.scan_to_next_token();
1492 self.stale_possible_simple_keys()?;
1493 self.unwind_indent(self.column);
1494 let ch = self.peek(0);
1495 if ch == '\0' {
1496 return self.fetch_stream_end();
1497 }
1498 if ch == '%' && self.check_directive() {
1499 return self.fetch_directive();
1500 }
1501 if ch == '-' && self.check_document_indicator("---") {
1502 return self.fetch_document_indicator(TK::DocumentStart);
1503 }
1504 if ch == '.' && self.check_document_indicator("...") {
1505 return self.fetch_document_indicator(TK::DocumentEnd);
1506 }
1507 match ch {
1508 '[' => return self.fetch_flow_collection_start(TK::FlowSequenceStart),
1509 '{' => return self.fetch_flow_collection_start(TK::FlowMappingStart),
1510 ']' => return self.fetch_flow_collection_end(TK::FlowSequenceEnd),
1511 '}' => return self.fetch_flow_collection_end(TK::FlowMappingEnd),
1512 ',' => return self.fetch_flow_entry(),
1513 _ => {}
1514 }
1515 if ch == '-' && is_z_ws_break(self.peek(1)) {
1516 return self.fetch_block_entry();
1517 }
1518 if ch == '?' && (self.flow_level != 0 || is_z_ws_break(self.peek(1))) {
1519 return self.fetch_key();
1520 }
1521 if ch == ':' && (self.flow_level != 0 || is_z_ws_break(self.peek(1))) {
1522 return self.fetch_value();
1523 }
1524 if ch == '*' {
1525 return self.fetch_anchor_or_alias(TK::Alias);
1526 }
1527 if ch == '&' {
1528 return self.fetch_anchor_or_alias(TK::Anchor);
1529 }
1530 if ch == '!' {
1531 return self.fetch_tag();
1532 }
1533 if ch == '|' && self.flow_level == 0 {
1534 return self.fetch_block_scalar('|');
1535 }
1536 if ch == '>' && self.flow_level == 0 {
1537 return self.fetch_block_scalar('>');
1538 }
1539 if ch == '\'' {
1540 return self.fetch_flow_scalar('\'');
1541 }
1542 if ch == '"' {
1543 return self.fetch_flow_scalar('"');
1544 }
1545 if self.check_plain() {
1546 return self.fetch_plain();
1547 }
1548 Err(YErr::Marked(format!(
1549 "found character {} that cannot start any token",
1550 repr_char(ch)
1551 )))
1552 }
1553
1554 fn check_directive(&self) -> bool {
1555 self.column == 0
1556 }
1557
1558 fn check_document_indicator(&self, marker: &str) -> bool {
1559 self.column == 0 && self.prefix(3) == marker && is_z_ws_break(self.peek(3))
1560 }
1561
1562 fn check_plain(&self) -> bool {
1563 let ch = self.peek(0);
1564 let starters = "-?:,[]{}#&*!|>'\"%@`";
1565 (!is_z_ws_break(ch) && !starters.contains(ch))
1566 || (!is_z_ws_break(self.peek(1))
1567 && (ch == '-' || (self.flow_level == 0 && (ch == '?' || ch == ':'))))
1568 }
1569
1570 fn fetch_stream_end(&mut self) -> Result<(), YErr> {
1571 self.unwind_indent(-1);
1572 self.remove_possible_simple_key()?;
1573 self.allow_simple_key = false;
1574 self.possible_simple_keys.clear();
1575 self.tokens.push_back(Tok::simple(TK::StreamEnd));
1576 self.done = true;
1577 Ok(())
1578 }
1579
1580 fn fetch_directive(&mut self) -> Result<(), YErr> {
1581 self.unwind_indent(-1);
1582 self.remove_possible_simple_key()?;
1583 self.allow_simple_key = false;
1584 let tok = self.scan_directive()?;
1585 self.tokens.push_back(tok);
1586 Ok(())
1587 }
1588
1589 fn fetch_document_indicator(&mut self, kind: TK) -> Result<(), YErr> {
1590 self.unwind_indent(-1);
1591 self.remove_possible_simple_key()?;
1592 self.allow_simple_key = false;
1593 self.forward(3);
1594 self.tokens.push_back(Tok::simple(kind));
1595 Ok(())
1596 }
1597
1598 fn fetch_flow_collection_start(&mut self, kind: TK) -> Result<(), YErr> {
1599 self.save_possible_simple_key()?;
1600 self.flow_level += 1;
1601 self.allow_simple_key = true;
1602 self.forward(1);
1603 self.tokens.push_back(Tok::simple(kind));
1604 Ok(())
1605 }
1606
1607 fn fetch_flow_collection_end(&mut self, kind: TK) -> Result<(), YErr> {
1608 self.remove_possible_simple_key()?;
1609 self.flow_level = self.flow_level.saturating_sub(1);
1610 self.allow_simple_key = false;
1611 self.forward(1);
1612 self.tokens.push_back(Tok::simple(kind));
1613 Ok(())
1614 }
1615
1616 fn fetch_flow_entry(&mut self) -> Result<(), YErr> {
1617 self.allow_simple_key = true;
1618 self.remove_possible_simple_key()?;
1619 self.forward(1);
1620 self.tokens.push_back(Tok::simple(TK::FlowEntry));
1621 Ok(())
1622 }
1623
1624 fn fetch_block_entry(&mut self) -> Result<(), YErr> {
1625 if self.flow_level == 0 {
1626 if !self.allow_simple_key {
1627 return Err(YErr::Marked(
1628 "sequence entries are not allowed here".to_string(),
1629 ));
1630 }
1631 if self.add_indent(self.column) {
1632 self.tokens.push_back(Tok::simple(TK::BlockSequenceStart));
1633 }
1634 }
1635 self.allow_simple_key = true;
1636 self.remove_possible_simple_key()?;
1637 self.forward(1);
1638 self.tokens.push_back(Tok::simple(TK::BlockEntry));
1639 Ok(())
1640 }
1641
1642 fn fetch_key(&mut self) -> Result<(), YErr> {
1643 if self.flow_level == 0 {
1644 if !self.allow_simple_key {
1645 return Err(YErr::Marked(
1646 "mapping keys are not allowed here".to_string(),
1647 ));
1648 }
1649 if self.add_indent(self.column) {
1650 self.tokens.push_back(Tok::simple(TK::BlockMappingStart));
1651 }
1652 }
1653 self.allow_simple_key = self.flow_level == 0;
1654 self.remove_possible_simple_key()?;
1655 self.forward(1);
1656 self.tokens.push_back(Tok::simple(TK::Key));
1657 Ok(())
1658 }
1659
1660 fn fetch_value(&mut self) -> Result<(), YErr> {
1661 if let Some(key) = self.possible_simple_keys.remove(&self.flow_level) {
1662 let insert_at = key.token_number - self.tokens_taken;
1663 self.tokens.insert(insert_at, Tok::simple(TK::Key));
1664 if self.flow_level == 0 && self.add_indent(key.column) {
1665 self.tokens
1666 .insert(insert_at, Tok::simple(TK::BlockMappingStart));
1667 }
1668 self.allow_simple_key = false;
1669 } else {
1670 if self.flow_level == 0 {
1671 if !self.allow_simple_key {
1672 return Err(YErr::Marked(
1673 "mapping values are not allowed here".to_string(),
1674 ));
1675 }
1676 if self.add_indent(self.column) {
1677 self.tokens.push_back(Tok::simple(TK::BlockMappingStart));
1678 }
1679 }
1680 self.allow_simple_key = self.flow_level == 0;
1681 self.remove_possible_simple_key()?;
1682 }
1683 self.forward(1);
1684 self.tokens.push_back(Tok::simple(TK::Value));
1685 Ok(())
1686 }
1687
1688 fn fetch_anchor_or_alias(&mut self, kind: TK) -> Result<(), YErr> {
1689 self.save_possible_simple_key()?;
1690 self.allow_simple_key = false;
1691 let tok = self.scan_anchor(kind)?;
1692 self.tokens.push_back(tok);
1693 Ok(())
1694 }
1695
1696 fn fetch_tag(&mut self) -> Result<(), YErr> {
1697 self.save_possible_simple_key()?;
1698 self.allow_simple_key = false;
1699 let tok = self.scan_tag()?;
1700 self.tokens.push_back(tok);
1701 Ok(())
1702 }
1703
1704 fn fetch_block_scalar(&mut self, style: char) -> Result<(), YErr> {
1705 self.allow_simple_key = true;
1706 self.remove_possible_simple_key()?;
1707 let tok = self.scan_block_scalar(style)?;
1708 self.tokens.push_back(tok);
1709 Ok(())
1710 }
1711
1712 fn fetch_flow_scalar(&mut self, style: char) -> Result<(), YErr> {
1713 self.save_possible_simple_key()?;
1714 self.allow_simple_key = false;
1715 let tok = self.scan_flow_scalar(style)?;
1716 self.tokens.push_back(tok);
1717 Ok(())
1718 }
1719
1720 fn fetch_plain(&mut self) -> Result<(), YErr> {
1721 self.save_possible_simple_key()?;
1722 self.allow_simple_key = false;
1723 let tok = self.scan_plain()?;
1724 self.tokens.push_back(tok);
1725 Ok(())
1726 }
1727
1728 fn scan_to_next_token(&mut self) {
1731 if self.index == 0 && self.peek(0) == '\u{feff}' {
1732 self.forward(1);
1733 }
1734 let mut found = false;
1735 while !found {
1736 self.skip_spaces();
1737 if self.peek(0) == '#' {
1738 while !is_z_break(self.peek(0)) {
1739 self.forward(1);
1740 }
1741 }
1742 if !self.scan_line_break().is_empty() {
1743 if self.flow_level == 0 {
1744 self.allow_simple_key = true;
1745 }
1746 } else {
1747 found = true;
1748 }
1749 }
1750 }
1751
1752 fn scan_line_break(&mut self) -> String {
1753 let ch = self.peek(0);
1754 if matches!(ch, '\r' | '\n' | '\u{85}') {
1755 if self.prefix(2) == "\r\n" {
1756 self.forward(2);
1757 } else {
1758 self.forward(1);
1759 }
1760 return "\n".to_string();
1761 } else if matches!(ch, '\u{2028}' | '\u{2029}') {
1762 self.forward(1);
1763 return ch.to_string();
1764 }
1765 String::new()
1766 }
1767
1768 fn scan_directive(&mut self) -> Result<Tok, YErr> {
1769 self.forward(1);
1770 let name = self.scan_directive_name()?;
1771 let value = if name == "YAML" {
1772 let v = self.scan_yaml_directive_value()?;
1773 Some(DirectiveVal::Yaml { major_is_1: v })
1774 } else if name == "TAG" {
1775 let (handle, prefix) = self.scan_tag_directive_value()?;
1776 Some(DirectiveVal::Tag { handle, prefix })
1777 } else {
1778 while !is_z_break(self.peek(0)) {
1779 self.forward(1);
1780 }
1781 Some(DirectiveVal::Other)
1782 };
1783 self.scan_ignored_line()?;
1784 Ok(Tok {
1785 kind: TK::Directive,
1786 value: name,
1787 plain: false,
1788 tag: None,
1789 directive: value,
1790 })
1791 }
1792
1793 fn scan_directive_name(&mut self) -> Result<String, YErr> {
1794 let mut length = 0;
1795 let mut ch = self.peek(length);
1796 while is_word_char(ch) {
1797 length += 1;
1798 ch = self.peek(length);
1799 }
1800 if length == 0 {
1801 return Err(YErr::Marked(format!(
1802 "expected alphabetic or numeric character, but found {}",
1803 repr_char(ch)
1804 )));
1805 }
1806 let value = self.prefix(length);
1807 self.forward(length);
1808 let ch = self.peek(0);
1809 if !(ch == '\0' || ch == ' ' || is_break(ch)) {
1810 return Err(YErr::Marked(format!(
1811 "expected alphabetic or numeric character, but found {}",
1812 repr_char(ch)
1813 )));
1814 }
1815 Ok(value)
1816 }
1817
1818 fn scan_yaml_directive_value(&mut self) -> Result<bool, YErr> {
1819 self.skip_spaces();
1820 let major = self.scan_yaml_directive_number()?;
1821 if self.peek(0) != '.' {
1822 return Err(YErr::Marked(format!(
1823 "expected a digit or '.', but found {}",
1824 repr_char(self.peek(0))
1825 )));
1826 }
1827 self.forward(1);
1828 let _minor = self.scan_yaml_directive_number()?;
1829 let ch = self.peek(0);
1830 if !(ch == '\0' || ch == ' ' || is_break(ch)) {
1831 return Err(YErr::Marked(format!(
1832 "expected a digit or ' ', but found {}",
1833 repr_char(ch)
1834 )));
1835 }
1836 Ok(major == Some(1))
1837 }
1838
1839 fn scan_yaml_directive_number(&mut self) -> Result<Option<u64>, YErr> {
1840 let ch = self.peek(0);
1841 if !ch.is_ascii_digit() {
1842 return Err(YErr::Marked(format!(
1843 "expected a digit, but found {}",
1844 repr_char(ch)
1845 )));
1846 }
1847 let mut length = 0;
1848 while self.peek(length).is_ascii_digit() {
1849 length += 1;
1850 }
1851 let text = self.prefix(length);
1852 self.forward(length);
1853 Ok(text.parse::<u64>().ok()) }
1855
1856 fn scan_tag_directive_value(&mut self) -> Result<(String, String), YErr> {
1857 self.skip_spaces();
1858 let handle = self.scan_tag_handle("directive")?;
1859 let ch = self.peek(0);
1860 if ch != ' ' {
1861 return Err(YErr::Marked(format!(
1862 "expected ' ', but found {}",
1863 repr_char(ch)
1864 )));
1865 }
1866 self.skip_spaces();
1867 let prefix = self.scan_tag_uri("directive")?;
1868 let ch = self.peek(0);
1869 if !(ch == '\0' || ch == ' ' || is_break(ch)) {
1870 return Err(YErr::Marked(format!(
1871 "expected ' ', but found {}",
1872 repr_char(ch)
1873 )));
1874 }
1875 Ok((handle, prefix))
1876 }
1877
1878 fn scan_ignored_line(&mut self) -> Result<(), YErr> {
1879 self.skip_spaces();
1880 if self.peek(0) == '#' {
1881 while !is_z_break(self.peek(0)) {
1882 self.forward(1);
1883 }
1884 }
1885 let ch = self.peek(0);
1886 if !is_z_break(ch) {
1887 return Err(YErr::Marked(format!(
1888 "expected a comment or a line break, but found {}",
1889 repr_char(ch)
1890 )));
1891 }
1892 self.scan_line_break();
1893 Ok(())
1894 }
1895
1896 fn scan_anchor(&mut self, kind: TK) -> Result<Tok, YErr> {
1897 self.forward(1);
1898 let mut length = 0;
1899 let mut ch = self.peek(length);
1900 while is_word_char(ch) {
1901 length += 1;
1902 ch = self.peek(length);
1903 }
1904 if length == 0 {
1905 return Err(YErr::Marked(format!(
1906 "expected alphabetic or numeric character, but found {}",
1907 repr_char(ch)
1908 )));
1909 }
1910 let value = self.prefix(length);
1911 self.forward(length);
1912 let ch = self.peek(0);
1913 if !(is_z_ws_break(ch) || "?:,]}%@`".contains(ch)) {
1914 return Err(YErr::Marked(format!(
1915 "expected alphabetic or numeric character, but found {}",
1916 repr_char(ch)
1917 )));
1918 }
1919 Ok(Tok {
1920 kind,
1921 value,
1922 plain: false,
1923 tag: None,
1924 directive: None,
1925 })
1926 }
1927
1928 fn scan_tag(&mut self) -> Result<Tok, YErr> {
1929 let ch = self.peek(1);
1930 let (handle, suffix): (Option<String>, String);
1931 if ch == '<' {
1932 self.forward(2);
1933 let s = self.scan_tag_uri("tag")?;
1934 if self.peek(0) != '>' {
1935 return Err(YErr::Marked(format!(
1936 "expected '>', but found {}",
1937 repr_char(self.peek(0))
1938 )));
1939 }
1940 self.forward(1);
1941 handle = None;
1942 suffix = s;
1943 } else if is_z_ws_break(ch) {
1944 handle = None;
1945 suffix = "!".to_string();
1946 self.forward(1);
1947 } else {
1948 let mut length = 1;
1949 let mut c = ch;
1950 let mut use_handle = false;
1951 while !(c == '\0' || c == ' ' || is_break(c)) {
1952 if c == '!' {
1953 use_handle = true;
1954 break;
1955 }
1956 length += 1;
1957 c = self.peek(length);
1958 }
1959 if use_handle {
1960 handle = Some(self.scan_tag_handle("tag")?);
1961 } else {
1962 handle = Some("!".to_string());
1963 self.forward(1);
1964 }
1965 suffix = self.scan_tag_uri("tag")?;
1966 }
1967 let ch = self.peek(0);
1968 if !(ch == '\0' || ch == ' ' || is_break(ch)) {
1969 return Err(YErr::Marked(format!(
1970 "expected ' ', but found {}",
1971 repr_char(ch)
1972 )));
1973 }
1974 Ok(Tok {
1975 kind: TK::Tag,
1976 value: String::new(),
1977 plain: false,
1978 tag: Some((handle, suffix)),
1979 directive: None,
1980 })
1981 }
1982
1983 fn scan_tag_handle(&mut self, _name: &str) -> Result<String, YErr> {
1984 let ch = self.peek(0);
1985 if ch != '!' {
1986 return Err(YErr::Marked(format!(
1987 "expected '!', but found {}",
1988 repr_char(ch)
1989 )));
1990 }
1991 let mut length = 1;
1992 let mut c = self.peek(length);
1993 if c != ' ' {
1994 while is_word_char(c) {
1995 length += 1;
1996 c = self.peek(length);
1997 }
1998 if c != '!' {
1999 self.forward(length);
2000 return Err(YErr::Marked(format!(
2001 "expected '!', but found {}",
2002 repr_char(c)
2003 )));
2004 }
2005 length += 1;
2006 }
2007 let value = self.prefix(length);
2008 self.forward(length);
2009 Ok(value)
2010 }
2011
2012 fn scan_tag_uri(&mut self, name: &str) -> Result<String, YErr> {
2013 let mut chunks = String::new();
2014 let mut length = 0;
2015 let mut ch = self.peek(length);
2016 while ch.is_ascii_alphanumeric() || "-;/?:@&=+$,_.!~*'()[]%".contains(ch) {
2017 if ch == '%' {
2018 chunks.push_str(&self.prefix(length));
2019 self.forward(length);
2020 length = 0;
2021 chunks.push_str(&self.scan_uri_escapes(name)?);
2022 } else {
2023 length += 1;
2024 }
2025 ch = self.peek(length);
2026 }
2027 if length > 0 {
2028 chunks.push_str(&self.prefix(length));
2029 self.forward(length);
2030 }
2031 if chunks.is_empty() {
2032 return Err(YErr::Marked(format!(
2033 "expected URI, but found {}",
2034 repr_char(ch)
2035 )));
2036 }
2037 Ok(chunks)
2038 }
2039
2040 fn scan_uri_escapes(&mut self, _name: &str) -> Result<String, YErr> {
2041 let mut codes: Vec<u8> = Vec::new();
2042 while self.peek(0) == '%' {
2043 self.forward(1);
2044 for k in 0..2 {
2045 if !self.peek(k).is_ascii_hexdigit() {
2046 return Err(YErr::Marked(format!(
2047 "expected URI escape sequence of 2 hexadecimal numbers, but found {}",
2048 repr_char(self.peek(k))
2049 )));
2050 }
2051 }
2052 let hex = self.prefix(2);
2053 codes.push(u8::from_str_radix(&hex, 16).unwrap_or(0));
2054 self.forward(2);
2055 }
2056 match String::from_utf8(codes.clone()) {
2057 Ok(s) => Ok(s),
2058 Err(e) => {
2059 let pos = e.utf8_error().valid_up_to();
2062 let byte = codes.get(pos).copied().unwrap_or(0);
2063 let reason = if pos + 1 >= codes.len() && e.utf8_error().error_len().is_none() {
2064 "unexpected end of data"
2065 } else if (0x80..0xc0).contains(&byte) {
2066 "invalid start byte"
2067 } else {
2068 "invalid continuation byte"
2069 };
2070 Err(YErr::Marked(format!(
2071 "'utf-8' codec can't decode byte 0x{byte:02x} in position {pos}: {reason}"
2072 )))
2073 }
2074 }
2075 }
2076
2077 fn scan_block_scalar(&mut self, style: char) -> Result<Tok, YErr> {
2078 let folded = style == '>';
2079 let mut chunks = String::new();
2080 self.forward(1);
2081 let (chomping, increment) = self.scan_block_scalar_indicators()?;
2082 self.scan_ignored_line()?;
2083
2084 let mut min_indent = self.indent + 1;
2085 if min_indent < 1 {
2086 min_indent = 1;
2087 }
2088 let (mut breaks, indent) = if let Some(inc) = increment {
2089 let indent = min_indent + inc - 1;
2090 (self.scan_block_scalar_breaks(indent), indent)
2091 } else {
2092 let (breaks, max_indent) = self.scan_block_scalar_indentation();
2093 (breaks, min_indent.max(max_indent))
2094 };
2095 let mut line_break = String::new();
2096
2097 while self.column == indent && self.peek(0) != '\0' {
2098 chunks.push_str(&breaks);
2099 let leading_non_space = !matches!(self.peek(0), ' ' | '\t');
2100 let mut length = 0;
2101 while !is_z_break(self.peek(length)) {
2102 length += 1;
2103 }
2104 chunks.push_str(&self.prefix(length));
2105 self.forward(length);
2106 line_break = self.scan_line_break();
2107 breaks = self.scan_block_scalar_breaks(indent);
2108 if self.column == indent && self.peek(0) != '\0' {
2109 if folded
2110 && line_break == "\n"
2111 && leading_non_space
2112 && !matches!(self.peek(0), ' ' | '\t')
2113 {
2114 if breaks.is_empty() {
2115 chunks.push(' ');
2116 }
2117 } else {
2118 chunks.push_str(&line_break);
2119 }
2120 } else {
2121 break;
2122 }
2123 }
2124
2125 if chomping != Some(false) {
2126 chunks.push_str(&line_break);
2127 }
2128 if chomping == Some(true) {
2129 chunks.push_str(&breaks);
2130 }
2131 Ok(Tok {
2132 kind: TK::Scalar,
2133 value: chunks,
2134 plain: false,
2135 tag: None,
2136 directive: None,
2137 })
2138 }
2139
2140 fn scan_block_scalar_indicators(&mut self) -> Result<(Option<bool>, Option<i64>), YErr> {
2141 let mut chomping: Option<bool> = None;
2142 let mut increment: Option<i64> = None;
2143 let mut ch = self.peek(0);
2144 if ch == '+' || ch == '-' {
2145 chomping = Some(ch == '+');
2146 self.forward(1);
2147 ch = self.peek(0);
2148 if ch.is_ascii_digit() {
2149 let inc = ch.to_digit(10).unwrap() as i64;
2150 if inc == 0 {
2151 return Err(YErr::Marked(
2152 "expected indentation indicator in the range 1-9, but found 0"
2153 .to_string(),
2154 ));
2155 }
2156 increment = Some(inc);
2157 self.forward(1);
2158 }
2159 } else if ch.is_ascii_digit() {
2160 let inc = ch.to_digit(10).unwrap() as i64;
2161 if inc == 0 {
2162 return Err(YErr::Marked(
2163 "expected indentation indicator in the range 1-9, but found 0".to_string(),
2164 ));
2165 }
2166 increment = Some(inc);
2167 self.forward(1);
2168 ch = self.peek(0);
2169 if ch == '+' || ch == '-' {
2170 chomping = Some(ch == '+');
2171 self.forward(1);
2172 }
2173 }
2174 let ch = self.peek(0);
2175 if !(ch == '\0' || ch == ' ' || is_break(ch)) {
2176 return Err(YErr::Marked(format!(
2177 "expected chomping or indentation indicators, but found {}",
2178 repr_char(ch)
2179 )));
2180 }
2181 Ok((chomping, increment))
2182 }
2183
2184 fn scan_block_scalar_indentation(&mut self) -> (String, i64) {
2185 let mut chunks = String::new();
2186 let mut max_indent = 0i64;
2187 loop {
2188 let ch = self.peek(0);
2189 if !(ch == ' ' || is_break(ch)) {
2190 break;
2191 }
2192 if ch != ' ' {
2193 chunks.push_str(&self.scan_line_break());
2194 } else {
2195 self.forward(1);
2196 if self.column > max_indent {
2197 max_indent = self.column;
2198 }
2199 }
2200 }
2201 (chunks, max_indent)
2202 }
2203
2204 fn scan_block_scalar_breaks(&mut self, indent: i64) -> String {
2205 let mut chunks = String::new();
2206 while self.column < indent && self.peek(0) == ' ' {
2207 self.forward(1);
2208 }
2209 while is_break(self.peek(0)) {
2210 chunks.push_str(&self.scan_line_break());
2211 while self.column < indent && self.peek(0) == ' ' {
2212 self.forward(1);
2213 }
2214 }
2215 chunks
2216 }
2217
2218 fn scan_flow_scalar(&mut self, style: char) -> Result<Tok, YErr> {
2219 let double = style == '"';
2220 let mut chunks = String::new();
2221 let quote = self.peek(0);
2222 self.forward(1);
2223 chunks.push_str(&self.scan_flow_scalar_non_spaces(double)?);
2224 while self.peek(0) != quote {
2225 chunks.push_str(&self.scan_flow_scalar_spaces()?);
2226 chunks.push_str(&self.scan_flow_scalar_non_spaces(double)?);
2227 }
2228 self.forward(1);
2229 Ok(Tok {
2230 kind: TK::Scalar,
2231 value: chunks,
2232 plain: false,
2233 tag: None,
2234 directive: None,
2235 })
2236 }
2237
2238 fn scan_flow_scalar_non_spaces(&mut self, double: bool) -> Result<String, YErr> {
2239 let mut chunks = String::new();
2240 loop {
2241 let mut length = 0;
2242 while !matches!(self.peek(length), '\'' | '"' | '\\')
2243 && !is_z_ws_break(self.peek(length))
2244 {
2245 length += 1;
2246 }
2247 if length > 0 {
2248 chunks.push_str(&self.prefix(length));
2249 self.forward(length);
2250 }
2251 let ch = self.peek(0);
2252 if !double && ch == '\'' && self.peek(1) == '\'' {
2253 chunks.push('\'');
2254 self.forward(2);
2255 } else if (double && ch == '\'') || (!double && (ch == '"' || ch == '\\')) {
2256 chunks.push(ch);
2257 self.forward(1);
2258 } else if double && ch == '\\' {
2259 self.forward(1);
2260 let ch = self.peek(0);
2261 let simple: Option<&str> = match ch {
2262 '0' => Some("\0"),
2263 'a' => Some("\x07"),
2264 'b' => Some("\x08"),
2265 't' | '\t' => Some("\t"),
2266 'n' => Some("\n"),
2267 'v' => Some("\x0b"),
2268 'f' => Some("\x0c"),
2269 'r' => Some("\r"),
2270 'e' => Some("\x1b"),
2271 ' ' => Some(" "),
2272 '"' => Some("\""),
2273 '\\' => Some("\\"),
2274 '/' => Some("/"),
2275 'N' => Some("\u{85}"),
2276 '_' => Some("\u{a0}"),
2277 'L' => Some("\u{2028}"),
2278 'P' => Some("\u{2029}"),
2279 _ => None,
2280 };
2281 if let Some(s) = simple {
2282 chunks.push_str(s);
2283 self.forward(1);
2284 } else if matches!(ch, 'x' | 'u' | 'U') {
2285 let length = match ch {
2286 'x' => 2,
2287 'u' => 4,
2288 _ => 8,
2289 };
2290 self.forward(1);
2291 for k in 0..length {
2292 if !self.peek(k).is_ascii_hexdigit() {
2293 return Err(YErr::Marked(format!(
2294 "expected escape sequence of {} hexadecimal numbers, but found {}",
2295 length,
2296 repr_char(self.peek(k))
2297 )));
2298 }
2299 }
2300 let code = u32::from_str_radix(&self.prefix(length), 16).unwrap_or(0);
2301 match char::from_u32(code) {
2302 Some(c) => chunks.push(c),
2303 None => {
2304 if code > 0x10ffff {
2305 return Err(YErr::Internal(
2307 "ValueError: chr() arg not in range(0x110000)".to_string(),
2308 ));
2309 }
2310 return Err(YErr::Internal(format!(
2313 "surrogate escape \\u{code:04x} not representable"
2314 )));
2315 }
2316 }
2317 self.forward(length);
2318 } else if is_break(ch) {
2319 self.scan_line_break();
2320 chunks.push_str(&self.scan_flow_scalar_breaks()?);
2321 } else {
2322 return Err(YErr::Marked(format!(
2323 "found unknown escape character {}",
2324 repr_char(ch)
2325 )));
2326 }
2327 } else {
2328 return Ok(chunks);
2329 }
2330 }
2331 }
2332
2333 fn scan_flow_scalar_spaces(&mut self) -> Result<String, YErr> {
2334 let mut chunks = String::new();
2335 let mut length = 0;
2336 while matches!(self.peek(length), ' ' | '\t') {
2337 length += 1;
2338 }
2339 let whitespaces = self.prefix(length);
2340 self.forward(length);
2341 let ch = self.peek(0);
2342 if ch == '\0' {
2343 return Err(YErr::Marked("found unexpected end of stream".to_string()));
2344 } else if is_break(ch) {
2345 let line_break = self.scan_line_break();
2346 let breaks = self.scan_flow_scalar_breaks()?;
2347 if line_break != "\n" {
2348 chunks.push_str(&line_break);
2349 } else if breaks.is_empty() {
2350 chunks.push(' ');
2351 }
2352 chunks.push_str(&breaks);
2353 } else {
2354 chunks.push_str(&whitespaces);
2355 }
2356 Ok(chunks)
2357 }
2358
2359 fn scan_flow_scalar_breaks(&mut self) -> Result<String, YErr> {
2360 let mut chunks = String::new();
2361 loop {
2362 let prefix = self.prefix(3);
2363 if (prefix == "---" || prefix == "...") && is_z_ws_break(self.peek(3)) {
2364 return Err(YErr::Marked(
2365 "found unexpected document separator".to_string(),
2366 ));
2367 }
2368 while matches!(self.peek(0), ' ' | '\t') {
2369 self.forward(1);
2370 }
2371 if is_break(self.peek(0)) {
2372 chunks.push_str(&self.scan_line_break());
2373 } else {
2374 return Ok(chunks);
2375 }
2376 }
2377 }
2378
2379 fn scan_plain(&mut self) -> Result<Tok, YErr> {
2380 let mut chunks = String::new();
2381 let indent = self.indent + 1;
2382 let mut spaces = String::new();
2383 loop {
2384 let mut length = 0;
2385 if self.peek(0) == '#' {
2386 break;
2387 }
2388 loop {
2389 let ch = self.peek(length);
2390 let stop = is_z_ws_break(ch)
2391 || (ch == ':'
2392 && (is_z_ws_break(self.peek(length + 1))
2393 || (self.flow_level != 0
2394 && ",[]{}".contains(self.peek(length + 1)))))
2395 || (self.flow_level != 0 && ",?[]{}".contains(ch));
2396 if stop {
2397 break;
2398 }
2399 length += 1;
2400 }
2401 if length == 0 {
2402 break;
2403 }
2404 self.allow_simple_key = false;
2405 chunks.push_str(&spaces);
2406 chunks.push_str(&self.prefix(length));
2407 self.forward(length);
2408 spaces = match self.scan_plain_spaces()? {
2409 Some(s) => s,
2410 None => break,
2411 };
2412 if spaces.is_empty()
2413 || self.peek(0) == '#'
2414 || (self.flow_level == 0 && self.column < indent)
2415 {
2416 break;
2417 }
2418 }
2419 Ok(Tok {
2420 kind: TK::Scalar,
2421 value: chunks,
2422 plain: true,
2423 tag: None,
2424 directive: None,
2425 })
2426 }
2427
2428 fn scan_plain_spaces(&mut self) -> Result<Option<String>, YErr> {
2431 let mut chunks = String::new();
2432 let mut length = 0;
2433 while self.peek(length) == ' ' {
2434 length += 1;
2435 }
2436 let whitespaces = self.prefix(length);
2437 self.forward(length);
2438 let ch = self.peek(0);
2439 if is_break(ch) {
2440 let line_break = self.scan_line_break();
2441 self.allow_simple_key = true;
2442 let prefix = self.prefix(3);
2443 if (prefix == "---" || prefix == "...") && is_z_ws_break(self.peek(3)) {
2444 return Ok(None);
2445 }
2446 let mut breaks = String::new();
2447 loop {
2448 let c = self.peek(0);
2449 if !(c == ' ' || is_break(c)) {
2450 break;
2451 }
2452 if c == ' ' {
2453 self.forward(1);
2454 } else {
2455 breaks.push_str(&self.scan_line_break());
2456 let prefix = self.prefix(3);
2457 if (prefix == "---" || prefix == "...") && is_z_ws_break(self.peek(3)) {
2458 return Ok(None);
2459 }
2460 }
2461 }
2462 if line_break != "\n" {
2463 chunks.push_str(&line_break);
2464 } else if breaks.is_empty() {
2465 chunks.push(' ');
2466 }
2467 chunks.push_str(&breaks);
2468 } else if !whitespaces.is_empty() {
2469 chunks.push_str(&whitespaces);
2470 }
2471 Ok(Some(chunks))
2472 }
2473}
2474
2475#[derive(Clone, Debug)]
2480enum Ev {
2481 StreamStart,
2482 StreamEnd,
2483 DocStart,
2484 DocEnd,
2485 Alias,
2486 Scalar {
2487 tag: Option<String>,
2488 implicit: (bool, bool),
2489 value: String,
2490 anchor: Option<String>,
2491 },
2492 SeqStart {
2493 tag: Option<String>,
2494 anchor: Option<String>,
2495 },
2496 SeqEnd,
2497 MapStart {
2498 tag: Option<String>,
2499 anchor: Option<String>,
2500 },
2501 MapEnd,
2502}
2503
2504#[derive(Clone, Copy, Debug)]
2505enum St {
2506 StreamStart,
2507 ImplicitDocumentStart,
2508 DocumentStart,
2509 DocumentContent,
2510 DocumentEnd,
2511 BlockNode,
2512 BlockSequenceFirstEntry,
2513 BlockSequenceEntry,
2514 IndentlessSequenceEntry,
2515 BlockMappingFirstKey,
2516 BlockMappingKey,
2517 BlockMappingValue,
2518 FlowSequenceFirstEntry,
2519 FlowSequenceEntry { first: bool },
2520 FlowSequenceEntryMappingKey,
2521 FlowSequenceEntryMappingValue,
2522 FlowSequenceEntryMappingEnd,
2523 FlowMappingFirstKey,
2524 FlowMappingKey { first: bool },
2525 FlowMappingValue,
2526 FlowMappingEmptyValue,
2527}
2528
2529struct Parser {
2530 sc: Scanner,
2531 current_event: Option<Ev>,
2532 yaml_version_seen: bool,
2533 tag_handles: HashMap<String, String>,
2534 states: Vec<St>,
2535 state: Option<St>,
2536}
2537
2538fn default_tag_handles() -> HashMap<String, String> {
2539 let mut m = HashMap::new();
2540 m.insert("!".to_string(), "!".to_string());
2541 m.insert("!!".to_string(), "tag:yaml.org,2002:".to_string());
2542 m
2543}
2544
2545impl Parser {
2546 fn new(sc: Scanner) -> Parser {
2547 Parser {
2548 sc,
2549 current_event: None,
2550 yaml_version_seen: false,
2551 tag_handles: HashMap::new(),
2552 states: Vec::new(),
2553 state: Some(St::StreamStart),
2554 }
2555 }
2556
2557 fn produce(&mut self) -> Result<(), YErr> {
2558 if self.current_event.is_none() {
2559 if let Some(st) = self.state {
2560 let ev = self.step(st)?;
2561 self.current_event = Some(ev);
2562 }
2563 }
2564 Ok(())
2565 }
2566
2567 fn peek_event(&mut self) -> Result<Option<&Ev>, YErr> {
2568 self.produce()?;
2569 Ok(self.current_event.as_ref())
2570 }
2571
2572 fn get_event(&mut self) -> Result<Ev, YErr> {
2573 self.produce()?;
2574 self.current_event
2575 .take()
2576 .ok_or_else(|| YErr::Internal("StopIteration: no more events".to_string()))
2577 }
2578
2579 fn step(&mut self, st: St) -> Result<Ev, YErr> {
2580 match st {
2581 St::StreamStart => {
2582 self.sc.get_token()?;
2583 self.state = Some(St::ImplicitDocumentStart);
2584 Ok(Ev::StreamStart)
2585 }
2586 St::ImplicitDocumentStart => {
2587 if !self
2588 .sc
2589 .check_token(&[TK::Directive, TK::DocumentStart, TK::StreamEnd])?
2590 {
2591 self.tag_handles = default_tag_handles();
2592 self.states.push(St::DocumentEnd);
2593 self.state = Some(St::BlockNode);
2594 Ok(Ev::DocStart)
2595 } else {
2596 self.step(St::DocumentStart)
2597 }
2598 }
2599 St::DocumentStart => {
2600 while self.sc.check_token(&[TK::DocumentEnd])? {
2601 self.sc.get_token()?;
2602 }
2603 if !self.sc.check_token(&[TK::StreamEnd])? {
2604 self.process_directives()?;
2605 if !self.sc.check_token(&[TK::DocumentStart])? {
2606 let tok = self.sc.peek_token()?;
2607 return Err(YErr::Marked(format!(
2608 "expected '<document start>', but found {}",
2609 py_repr_str(tok.id())
2610 )));
2611 }
2612 self.sc.get_token()?;
2613 self.states.push(St::DocumentEnd);
2614 self.state = Some(St::DocumentContent);
2615 Ok(Ev::DocStart)
2616 } else {
2617 self.sc.get_token()?;
2618 self.state = None;
2619 Ok(Ev::StreamEnd)
2620 }
2621 }
2622 St::DocumentEnd => {
2623 if self.sc.check_token(&[TK::DocumentEnd])? {
2624 self.sc.get_token()?;
2625 }
2626 self.state = Some(St::DocumentStart);
2627 Ok(Ev::DocEnd)
2628 }
2629 St::DocumentContent => {
2630 if self.sc.check_token(&[
2631 TK::Directive,
2632 TK::DocumentStart,
2633 TK::DocumentEnd,
2634 TK::StreamEnd,
2635 ])? {
2636 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2637 Ok(empty_scalar())
2638 } else {
2639 self.parse_node(true, false)
2640 }
2641 }
2642 St::BlockNode => self.parse_node(true, false),
2643 St::BlockSequenceFirstEntry => {
2644 self.sc.get_token()?;
2645 self.block_sequence_entry()
2646 }
2647 St::BlockSequenceEntry => self.block_sequence_entry(),
2648 St::IndentlessSequenceEntry => {
2649 if self.sc.check_token(&[TK::BlockEntry])? {
2650 self.sc.get_token()?;
2651 if !self.sc.check_token(&[
2652 TK::BlockEntry,
2653 TK::Key,
2654 TK::Value,
2655 TK::BlockEnd,
2656 ])? {
2657 self.states.push(St::IndentlessSequenceEntry);
2658 return self.parse_node(true, false);
2659 }
2660 self.state = Some(St::IndentlessSequenceEntry);
2661 return Ok(empty_scalar());
2662 }
2663 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2664 Ok(Ev::SeqEnd)
2665 }
2666 St::BlockMappingFirstKey => {
2667 self.sc.get_token()?;
2668 self.block_mapping_key()
2669 }
2670 St::BlockMappingKey => self.block_mapping_key(),
2671 St::BlockMappingValue => {
2672 if self.sc.check_token(&[TK::Value])? {
2673 self.sc.get_token()?;
2674 if !self.sc.check_token(&[TK::Key, TK::Value, TK::BlockEnd])? {
2675 self.states.push(St::BlockMappingKey);
2676 return self.parse_node(true, true);
2677 }
2678 self.state = Some(St::BlockMappingKey);
2679 return Ok(empty_scalar());
2680 }
2681 self.state = Some(St::BlockMappingKey);
2682 Ok(empty_scalar())
2683 }
2684 St::FlowSequenceFirstEntry => {
2685 self.sc.get_token()?;
2686 self.flow_sequence_entry(true)
2687 }
2688 St::FlowSequenceEntry { first } => self.flow_sequence_entry(first),
2689 St::FlowSequenceEntryMappingKey => {
2690 self.sc.get_token()?;
2691 if !self
2692 .sc
2693 .check_token(&[TK::Value, TK::FlowEntry, TK::FlowSequenceEnd])?
2694 {
2695 self.states.push(St::FlowSequenceEntryMappingValue);
2696 return self.parse_node(false, false);
2697 }
2698 self.state = Some(St::FlowSequenceEntryMappingValue);
2699 Ok(empty_scalar())
2700 }
2701 St::FlowSequenceEntryMappingValue => {
2702 if self.sc.check_token(&[TK::Value])? {
2703 self.sc.get_token()?;
2704 if !self.sc.check_token(&[TK::FlowEntry, TK::FlowSequenceEnd])? {
2705 self.states.push(St::FlowSequenceEntryMappingEnd);
2706 return self.parse_node(false, false);
2707 }
2708 self.state = Some(St::FlowSequenceEntryMappingEnd);
2709 return Ok(empty_scalar());
2710 }
2711 self.state = Some(St::FlowSequenceEntryMappingEnd);
2712 Ok(empty_scalar())
2713 }
2714 St::FlowSequenceEntryMappingEnd => {
2715 self.state = Some(St::FlowSequenceEntry { first: false });
2716 Ok(Ev::MapEnd)
2717 }
2718 St::FlowMappingFirstKey => {
2719 self.sc.get_token()?;
2720 self.flow_mapping_key(true)
2721 }
2722 St::FlowMappingKey { first } => self.flow_mapping_key(first),
2723 St::FlowMappingValue => {
2724 if self.sc.check_token(&[TK::Value])? {
2725 self.sc.get_token()?;
2726 if !self.sc.check_token(&[TK::FlowEntry, TK::FlowMappingEnd])? {
2727 self.states.push(St::FlowMappingKey { first: false });
2728 return self.parse_node(false, false);
2729 }
2730 self.state = Some(St::FlowMappingKey { first: false });
2731 return Ok(empty_scalar());
2732 }
2733 self.state = Some(St::FlowMappingKey { first: false });
2734 Ok(empty_scalar())
2735 }
2736 St::FlowMappingEmptyValue => {
2737 self.state = Some(St::FlowMappingKey { first: false });
2738 Ok(empty_scalar())
2739 }
2740 }
2741 }
2742
2743 fn block_sequence_entry(&mut self) -> Result<Ev, YErr> {
2744 if self.sc.check_token(&[TK::BlockEntry])? {
2745 self.sc.get_token()?;
2746 if !self.sc.check_token(&[TK::BlockEntry, TK::BlockEnd])? {
2747 self.states.push(St::BlockSequenceEntry);
2748 return self.parse_node(true, false);
2749 }
2750 self.state = Some(St::BlockSequenceEntry);
2751 return Ok(empty_scalar());
2752 }
2753 if !self.sc.check_token(&[TK::BlockEnd])? {
2754 let tok = self.sc.peek_token()?;
2755 return Err(YErr::Marked(format!(
2756 "expected <block end>, but found {}",
2757 py_repr_str(tok.id())
2758 )));
2759 }
2760 self.sc.get_token()?;
2761 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2762 Ok(Ev::SeqEnd)
2763 }
2764
2765 fn block_mapping_key(&mut self) -> Result<Ev, YErr> {
2766 if self.sc.check_token(&[TK::Key])? {
2767 self.sc.get_token()?;
2768 if !self.sc.check_token(&[TK::Key, TK::Value, TK::BlockEnd])? {
2769 self.states.push(St::BlockMappingValue);
2770 return self.parse_node(true, true);
2771 }
2772 self.state = Some(St::BlockMappingValue);
2773 return Ok(empty_scalar());
2774 }
2775 if !self.sc.check_token(&[TK::BlockEnd])? {
2776 let tok = self.sc.peek_token()?;
2777 return Err(YErr::Marked(format!(
2778 "expected <block end>, but found {}",
2779 py_repr_str(tok.id())
2780 )));
2781 }
2782 self.sc.get_token()?;
2783 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2784 Ok(Ev::MapEnd)
2785 }
2786
2787 fn flow_sequence_entry(&mut self, first: bool) -> Result<Ev, YErr> {
2788 if !self.sc.check_token(&[TK::FlowSequenceEnd])? {
2789 if !first {
2790 if self.sc.check_token(&[TK::FlowEntry])? {
2791 self.sc.get_token()?;
2792 } else {
2793 let tok = self.sc.peek_token()?;
2794 return Err(YErr::Marked(format!(
2795 "expected ',' or ']', but got {}",
2796 py_repr_str(tok.id())
2797 )));
2798 }
2799 }
2800 if self.sc.check_token(&[TK::Key])? {
2801 self.state = Some(St::FlowSequenceEntryMappingKey);
2802 return Ok(Ev::MapStart {
2803 tag: None,
2804 anchor: None,
2805 });
2806 } else if !self.sc.check_token(&[TK::FlowSequenceEnd])? {
2807 self.states.push(St::FlowSequenceEntry { first: false });
2808 return self.parse_node(false, false);
2809 }
2810 }
2811 self.sc.get_token()?;
2812 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2813 Ok(Ev::SeqEnd)
2814 }
2815
2816 fn flow_mapping_key(&mut self, first: bool) -> Result<Ev, YErr> {
2817 if !self.sc.check_token(&[TK::FlowMappingEnd])? {
2818 if !first {
2819 if self.sc.check_token(&[TK::FlowEntry])? {
2820 self.sc.get_token()?;
2821 } else {
2822 let tok = self.sc.peek_token()?;
2823 return Err(YErr::Marked(format!(
2824 "expected ',' or '}}', but got {}",
2825 py_repr_str(tok.id())
2826 )));
2827 }
2828 }
2829 if self.sc.check_token(&[TK::Key])? {
2830 self.sc.get_token()?;
2831 if !self
2832 .sc
2833 .check_token(&[TK::Value, TK::FlowEntry, TK::FlowMappingEnd])?
2834 {
2835 self.states.push(St::FlowMappingValue);
2836 return self.parse_node(false, false);
2837 }
2838 self.state = Some(St::FlowMappingValue);
2839 return Ok(empty_scalar());
2840 } else if !self.sc.check_token(&[TK::FlowMappingEnd])? {
2841 self.states.push(St::FlowMappingEmptyValue);
2842 return self.parse_node(false, false);
2843 }
2844 }
2845 self.sc.get_token()?;
2846 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2847 Ok(Ev::MapEnd)
2848 }
2849
2850 fn process_directives(&mut self) -> Result<(), YErr> {
2851 self.yaml_version_seen = false;
2852 self.tag_handles = HashMap::new();
2853 while self.sc.check_token(&[TK::Directive])? {
2854 let tok = self.sc.get_token()?;
2855 match tok.directive {
2856 Some(DirectiveVal::Yaml { major_is_1 }) => {
2857 if self.yaml_version_seen {
2858 return Err(YErr::Marked("found duplicate YAML directive".to_string()));
2859 }
2860 if !major_is_1 {
2861 return Err(YErr::Marked(
2862 "found incompatible YAML document (version 1.* is required)"
2863 .to_string(),
2864 ));
2865 }
2866 self.yaml_version_seen = true;
2867 }
2868 Some(DirectiveVal::Tag { handle, prefix }) => {
2869 if self.tag_handles.contains_key(&handle) {
2870 return Err(YErr::Marked(format!(
2871 "duplicate tag handle {}",
2872 py_repr_str(&handle)
2873 )));
2874 }
2875 self.tag_handles.insert(handle, prefix);
2876 }
2877 _ => {}
2878 }
2879 }
2880 for (k, v) in default_tag_handles() {
2881 self.tag_handles.entry(k).or_insert(v);
2882 }
2883 Ok(())
2884 }
2885
2886 fn parse_node(&mut self, block: bool, indentless_sequence: bool) -> Result<Ev, YErr> {
2887 if self.sc.check_token(&[TK::Alias])? {
2888 self.sc.get_token()?;
2889 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2890 return Ok(Ev::Alias);
2891 }
2892 let mut anchor: Option<String> = None;
2893 let mut tag: Option<(Option<String>, String)> = None;
2894 let mut saw_properties = false;
2895 if self.sc.check_token(&[TK::Anchor])? {
2896 let tok = self.sc.get_token()?;
2897 anchor = Some(tok.value);
2898 saw_properties = true;
2899 if self.sc.check_token(&[TK::Tag])? {
2900 let tok = self.sc.get_token()?;
2901 tag = tok.tag;
2902 }
2903 } else if self.sc.check_token(&[TK::Tag])? {
2904 let tok = self.sc.get_token()?;
2905 tag = tok.tag;
2906 saw_properties = true;
2907 if self.sc.check_token(&[TK::Anchor])? {
2908 let tok = self.sc.get_token()?;
2909 anchor = Some(tok.value);
2910 }
2911 }
2912 let resolved_tag: Option<String> = match tag {
2913 Some((Some(handle), suffix)) => match self.tag_handles.get(&handle) {
2914 Some(prefix) => Some(format!("{prefix}{suffix}")),
2915 None => {
2916 return Err(YErr::Marked(format!(
2917 "found undefined tag handle {}",
2918 py_repr_str(&handle)
2919 )));
2920 }
2921 },
2922 Some((None, suffix)) => Some(suffix),
2923 None => None,
2924 };
2925 let implicit = resolved_tag.is_none() || resolved_tag.as_deref() == Some("!");
2926 if indentless_sequence && self.sc.check_token(&[TK::BlockEntry])? {
2927 self.state = Some(St::IndentlessSequenceEntry);
2928 return Ok(Ev::SeqStart {
2929 tag: resolved_tag,
2930 anchor,
2931 });
2932 }
2933 if self.sc.check_token(&[TK::Scalar])? {
2934 let tok = self.sc.get_token()?;
2935 let implicit_pair = if (tok.plain && resolved_tag.is_none())
2936 || resolved_tag.as_deref() == Some("!")
2937 {
2938 (true, false)
2939 } else if resolved_tag.is_none() {
2940 (false, true)
2941 } else {
2942 (false, false)
2943 };
2944 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2945 return Ok(Ev::Scalar {
2946 tag: resolved_tag,
2947 implicit: implicit_pair,
2948 value: tok.value,
2949 anchor,
2950 });
2951 }
2952 if self.sc.check_token(&[TK::FlowSequenceStart])? {
2953 self.state = Some(St::FlowSequenceFirstEntry);
2954 return Ok(Ev::SeqStart {
2955 tag: resolved_tag,
2956 anchor,
2957 });
2958 }
2959 if self.sc.check_token(&[TK::FlowMappingStart])? {
2960 self.state = Some(St::FlowMappingFirstKey);
2961 return Ok(Ev::MapStart {
2962 tag: resolved_tag,
2963 anchor,
2964 });
2965 }
2966 if block && self.sc.check_token(&[TK::BlockSequenceStart])? {
2967 self.state = Some(St::BlockSequenceFirstEntry);
2968 return Ok(Ev::SeqStart {
2969 tag: resolved_tag,
2970 anchor,
2971 });
2972 }
2973 if block && self.sc.check_token(&[TK::BlockMappingStart])? {
2974 self.state = Some(St::BlockMappingFirstKey);
2975 return Ok(Ev::MapStart {
2976 tag: resolved_tag,
2977 anchor,
2978 });
2979 }
2980 if saw_properties {
2981 self.state = Some(self.states.pop().ok_or_else(state_underflow)?);
2982 return Ok(Ev::Scalar {
2983 tag: resolved_tag,
2984 implicit: (implicit, false),
2985 value: String::new(),
2986 anchor,
2987 });
2988 }
2989 let tok = self.sc.peek_token()?;
2992 Err(YErr::Marked(format!(
2993 "expected the node content, but found {}",
2994 py_repr_str(tok.id())
2995 )))
2996 }
2997}
2998
2999fn empty_scalar() -> Ev {
3000 Ev::Scalar {
3001 tag: None,
3002 implicit: (true, false),
3003 value: String::new(),
3004 anchor: None,
3005 }
3006}
3007
3008fn state_underflow() -> YErr {
3009 YErr::Internal("IndexError: pop from empty parser state stack".to_string())
3010}
3011
3012#[derive(Clone, Debug)]
3017enum Node {
3018 Scalar { tag: String, value: String },
3019 Seq { tag: String, items: Vec<Node> },
3020 Map { tag: String, pairs: Vec<(Node, Node)> },
3021}
3022
3023impl Node {
3024 fn tag(&self) -> &str {
3025 match self {
3026 Node::Scalar { tag, .. } | Node::Seq { tag, .. } | Node::Map { tag, .. } => tag,
3027 }
3028 }
3029
3030 fn id(&self) -> &'static str {
3031 match self {
3032 Node::Scalar { .. } => "scalar",
3033 Node::Seq { .. } => "sequence",
3034 Node::Map { .. } => "mapping",
3035 }
3036 }
3037}
3038
3039fn py_node_class(n: &Node) -> &'static str {
3041 match n {
3042 Node::Scalar { .. } => "ScalarNode",
3043 Node::Seq { .. } => "SequenceNode",
3044 Node::Map { .. } => "MappingNode",
3045 }
3046}
3047
3048struct Composer {
3049 p: Parser,
3050 anchors: std::collections::HashSet<String>,
3051 depth: usize,
3052}
3053
3054impl Composer {
3055 fn get_single_node(&mut self) -> Result<Option<Node>, YErr> {
3056 self.p.get_event()?;
3058 let mut document = None;
3059 if !matches!(self.p.peek_event()?, Some(Ev::StreamEnd)) {
3060 document = Some(self.compose_document()?);
3061 }
3062 if !matches!(self.p.peek_event()?, Some(Ev::StreamEnd)) {
3063 return Err(YErr::Marked("but found another document".to_string()));
3064 }
3065 self.p.get_event()?;
3066 Ok(document)
3067 }
3068
3069 fn compose_document(&mut self) -> Result<Node, YErr> {
3070 self.p.get_event()?; let node = self.compose_node()?;
3072 self.p.get_event()?; self.anchors.clear();
3074 Ok(node)
3075 }
3076
3077 fn compose_node(&mut self) -> Result<Node, YErr> {
3080 if matches!(self.p.peek_event()?, Some(Ev::Alias)) {
3081 return Err(YErr::Marked(
3082 "YAML aliases are not permitted in frontmatter".to_string(),
3083 ));
3084 }
3085 self.depth += 1;
3086 if self.depth > MAX_FRONTMATTER_DEPTH {
3087 self.depth -= 1;
3088 return Err(YErr::Marked(format!(
3089 "frontmatter nesting exceeds the {MAX_FRONTMATTER_DEPTH}-level cap"
3090 )));
3091 }
3092 let result = self.compose_node_inner();
3093 self.depth -= 1;
3094 result
3095 }
3096
3097 fn compose_node_inner(&mut self) -> Result<Node, YErr> {
3098 let anchor: Option<String> = match self.p.peek_event()? {
3100 Some(Ev::Scalar { anchor, .. })
3101 | Some(Ev::SeqStart { anchor, .. })
3102 | Some(Ev::MapStart { anchor, .. }) => anchor.clone(),
3103 _ => None,
3104 };
3105 if let Some(a) = &anchor {
3106 if self.anchors.contains(a) {
3107 return Err(YErr::Marked("second occurrence".to_string()));
3108 }
3109 self.anchors.insert(a.clone());
3110 }
3111 match self.p.peek_event()? {
3112 Some(Ev::Scalar { .. }) => {
3113 if let Ev::Scalar {
3114 tag,
3115 implicit,
3116 value,
3117 ..
3118 } = self.p.get_event()?
3119 {
3120 let tag = match tag.as_deref() {
3121 None | Some("!") => {
3122 if implicit.0 {
3123 resolve_plain(&value).to_string()
3124 } else {
3125 TAG_STR.to_string()
3126 }
3127 }
3128 Some(t) => t.to_string(),
3129 };
3130 Ok(Node::Scalar { tag, value })
3131 } else {
3132 unreachable!()
3133 }
3134 }
3135 Some(Ev::SeqStart { .. }) => {
3136 let tag = if let Ev::SeqStart { tag, .. } = self.p.get_event()? {
3137 match tag.as_deref() {
3138 None | Some("!") => TAG_SEQ.to_string(),
3139 Some(t) => t.to_string(),
3140 }
3141 } else {
3142 unreachable!()
3143 };
3144 let mut items = Vec::new();
3145 while !matches!(self.p.peek_event()?, Some(Ev::SeqEnd)) {
3146 items.push(self.compose_node()?);
3147 }
3148 self.p.get_event()?;
3149 Ok(Node::Seq { tag, items })
3150 }
3151 Some(Ev::MapStart { .. }) => {
3152 let tag = if let Ev::MapStart { tag, .. } = self.p.get_event()? {
3153 match tag.as_deref() {
3154 None | Some("!") => TAG_MAP.to_string(),
3155 Some(t) => t.to_string(),
3156 }
3157 } else {
3158 unreachable!()
3159 };
3160 let mut pairs = Vec::new();
3161 while !matches!(self.p.peek_event()?, Some(Ev::MapEnd)) {
3162 let key = self.compose_node()?;
3163 let value = self.compose_node()?;
3164 pairs.push((key, value));
3165 }
3166 self.p.get_event()?;
3167 Ok(Node::Map { tag, pairs })
3168 }
3169 _ => Err(YErr::Internal(
3170 "UnboundLocalError: compose_node on non-node event".to_string(),
3171 )),
3172 }
3173 }
3174}
3175
3176fn construct_scalar_value(node: &Node) -> Result<String, YErr> {
3181 if let Node::Map { pairs, .. } = node {
3182 for (k, v) in pairs {
3183 if k.tag() == TAG_VALUE {
3184 return construct_scalar_value(v);
3185 }
3186 }
3187 }
3188 match node {
3189 Node::Scalar { value, .. } => Ok(value.clone()),
3190 _ => Err(YErr::Marked(format!(
3191 "expected a scalar node, but found {}",
3192 node.id()
3193 ))),
3194 }
3195}
3196
3197fn construct_yaml_bool(node: &Node) -> Result<Yaml, YErr> {
3198 let value = construct_scalar_value(node)?;
3199 match value.to_lowercase().as_str() {
3200 "yes" | "true" | "on" => Ok(Yaml::Bool(true)),
3201 "no" | "false" | "off" => Ok(Yaml::Bool(false)),
3202 other => Err(YErr::Internal(format!(
3204 "KeyError: {}",
3205 py_repr_str(other)
3206 ))),
3207 }
3208}
3209
3210fn py_int_parse(s: &str, base: u32) -> Result<(bool, Mag), YErr> {
3222 let invalid = || {
3223 YErr::Internal(format!(
3224 "ValueError: invalid literal for int() with base {}: {}",
3225 base,
3226 py_repr_str(s)
3227 ))
3228 };
3229 let t = py_strip(s);
3230 let chars: Vec<char> = t.chars().collect();
3231 let mut i = 0;
3232 let neg = match chars.first() {
3233 Some('-') => {
3234 i = 1;
3235 true
3236 }
3237 Some('+') => {
3238 i = 1;
3239 false
3240 }
3241 _ => false,
3242 };
3243 let prefix = match base {
3245 2 => Some(('b', 'B')),
3246 8 => Some(('o', 'O')),
3247 16 => Some(('x', 'X')),
3248 _ => None,
3249 };
3250 if let Some((lo, hi)) = prefix {
3251 if chars.get(i) == Some(&'0') && matches!(chars.get(i + 1), Some(c) if *c == lo || *c == hi)
3252 {
3253 i += 2;
3254 if chars.get(i) == Some(&'_') && chars.get(i + 1).is_some_and(|c| c.is_digit(base)) {
3256 i += 1;
3257 }
3258 }
3259 }
3260 if base == 10 {
3262 let span_digits = chars[i..]
3263 .iter()
3264 .take_while(|c| c.is_ascii_digit() || **c == '_')
3265 .filter(|c| c.is_ascii_digit())
3266 .count();
3267 if span_digits > INT_MAX_STR_DIGITS {
3268 return Err(int_parse_limit_err(span_digits));
3269 }
3270 }
3271 let mut mag = Mag::zero();
3272 let mut prev_digit = false;
3273 let mut ndigits = 0usize;
3274 while i < chars.len() {
3275 let c = chars[i];
3276 if c == '_' {
3277 if !prev_digit || !chars.get(i + 1).is_some_and(|c| c.is_digit(base)) {
3278 return Err(invalid());
3279 }
3280 prev_digit = false;
3281 } else if let Some(d) = c.to_digit(base) {
3282 if !c.is_ascii() {
3283 return Err(invalid());
3285 }
3286 mag.mul_add_small(base as u64, d as u64);
3287 prev_digit = true;
3288 ndigits += 1;
3289 } else {
3290 return Err(invalid());
3291 }
3292 i += 1;
3293 }
3294 if ndigits == 0 {
3295 return Err(invalid());
3296 }
3297 Ok((neg, mag))
3298}
3299
3300fn construct_yaml_int(node: &Node) -> Result<Yaml, YErr> {
3301 let raw = construct_scalar_value(node)?;
3302 let value = raw.replace('_', "");
3303 if value.is_empty() {
3304 return Err(YErr::Internal(
3306 "IndexError: string index out of range".to_string(),
3307 ));
3308 }
3309 let mut neg = false;
3310 let mut v = value.as_str();
3311 if v.starts_with('-') {
3312 neg = true;
3313 v = &v[1..];
3314 } else if v.starts_with('+') {
3315 v = &v[1..];
3316 }
3317 if v == "0" {
3318 return Ok(Yaml::Int(0));
3319 }
3320 if let Some(rest) = v.strip_prefix("0b") {
3321 let (pneg, mag) = py_int_parse(rest, 2)?;
3322 return Ok(yaml_int(neg ^ pneg, &mag));
3323 }
3324 if let Some(rest) = v.strip_prefix("0x") {
3325 let (pneg, mag) = py_int_parse(rest, 16)?;
3326 return Ok(yaml_int(neg ^ pneg, &mag));
3327 }
3328 if v.starts_with('0') {
3329 let (pneg, mag) = py_int_parse(v, 8)?;
3330 return Ok(yaml_int(neg ^ pneg, &mag));
3331 }
3332 if v.contains(':') {
3333 let mut acc_neg = false;
3336 let mut acc = Mag::zero();
3337 for part in v.split(':') {
3338 let (pneg, pmag) = py_int_parse(part, 10)?;
3339 acc.mul_add_small(60, 0);
3340 if acc_neg == pneg || pmag.is_zero() {
3341 acc.add(&pmag);
3342 } else {
3343 match acc.cmp_mag(&pmag) {
3344 std::cmp::Ordering::Less => {
3345 let mut m = pmag.clone();
3346 m.sub(&acc);
3347 acc = m;
3348 acc_neg = pneg;
3349 }
3350 std::cmp::Ordering::Equal => {
3351 acc = Mag::zero();
3352 acc_neg = false;
3353 }
3354 std::cmp::Ordering::Greater => acc.sub(&pmag),
3355 }
3356 }
3357 if acc.is_zero() {
3358 acc_neg = false;
3359 }
3360 }
3361 return Ok(yaml_int(neg ^ acc_neg, &acc));
3362 }
3363 let (pneg, mag) = py_int_parse(v, 10)?;
3364 Ok(yaml_int(neg ^ pneg, &mag))
3365}
3366
3367fn construct_yaml_float(node: &Node) -> Result<Yaml, YErr> {
3368 let raw = construct_scalar_value(node)?;
3369 let value = raw.replace('_', "").to_lowercase();
3370 if value.is_empty() {
3371 return Err(YErr::Internal(
3372 "IndexError: string index out of range".to_string(),
3373 ));
3374 }
3375 let mut sign = 1.0f64;
3376 let mut v = value.as_str();
3377 if v.starts_with('-') {
3378 sign = -1.0;
3379 v = &v[1..];
3380 } else if v.starts_with('+') {
3381 v = &v[1..];
3382 }
3383 if v == ".inf" {
3384 return Ok(Yaml::Float(sign * f64::INFINITY));
3385 }
3386 if v == ".nan" {
3387 return Ok(Yaml::Float(f64::NAN));
3389 }
3390 if v.contains(':') {
3391 let mut digits: Vec<f64> = Vec::new();
3392 for part in v.split(':') {
3393 digits.push(py_float_parse(part)?);
3394 }
3395 digits.reverse();
3396 let mut base = 1.0f64;
3397 let mut acc = 0.0f64;
3398 for d in digits {
3399 acc += d * base;
3400 base *= 60.0;
3401 }
3402 return Ok(Yaml::Float(sign * acc));
3403 }
3404 Ok(Yaml::Float(sign * py_float_parse(v)?))
3405}
3406
3407fn py_float_parse(s: &str) -> Result<f64, YErr> {
3410 let t = py_strip(s);
3411 match t.parse::<f64>() {
3412 Ok(v) => Ok(v),
3413 Err(_) => Err(YErr::Internal(format!(
3414 "ValueError: could not convert string to float: {}",
3415 py_repr_str(s)
3416 ))),
3417 }
3418}
3419
3420fn construct_yaml_binary(node: &Node) -> Result<Yaml, YErr> {
3421 let value = construct_scalar_value(node)?;
3422 if let Some((pos, c)) = value.chars().enumerate().find(|(_, c)| !c.is_ascii()) {
3423 let cp = c as u32;
3424 let esc = if cp < 0x100 {
3425 format!("\\x{cp:02x}")
3426 } else if cp < 0x10000 {
3427 format!("\\u{cp:04x}")
3428 } else {
3429 format!("\\U{cp:08x}")
3430 };
3431 return Err(YErr::Marked(format!(
3432 "failed to convert base64 data into ascii: 'ascii' codec can't encode character '{esc}' in position {pos}: ordinal not in range(128)"
3433 )));
3434 }
3435 match decode_base64_lenient(value.as_bytes()) {
3436 Ok(bytes) => Ok(Yaml::Bytes(bytes)),
3437 Err(msg) => Err(YErr::Marked(format!(
3438 "failed to decode base64 data: {msg}"
3439 ))),
3440 }
3441}
3442
3443fn decode_base64_lenient(data: &[u8]) -> Result<Vec<u8>, String> {
3447 fn val(b: u8) -> Option<u32> {
3448 match b {
3449 b'A'..=b'Z' => Some((b - b'A') as u32),
3450 b'a'..=b'z' => Some((b - b'a' + 26) as u32),
3451 b'0'..=b'9' => Some((b - b'0' + 52) as u32),
3452 b'+' => Some(62),
3453 b'/' => Some(63),
3454 _ => None,
3455 }
3456 }
3457 let mut out = Vec::new();
3458 let mut acc: u32 = 0;
3459 let mut quad = 0usize;
3460 let mut ndata = 0usize;
3461 for &b in data {
3462 if b == b'=' && quad >= 2 {
3463 match quad {
3464 2 => out.push((acc >> 4) as u8),
3465 3 => {
3466 out.push((acc >> 10) as u8);
3467 out.push(((acc >> 2) & 0xff) as u8);
3468 }
3469 _ => {}
3470 }
3471 return Ok(out);
3472 }
3473 if let Some(v) = val(b) {
3474 acc = (acc << 6) | v;
3475 quad += 1;
3476 ndata += 1;
3477 if quad == 4 {
3478 out.push((acc >> 16) as u8);
3479 out.push(((acc >> 8) & 0xff) as u8);
3480 out.push((acc & 0xff) as u8);
3481 acc = 0;
3482 quad = 0;
3483 }
3484 }
3485 }
3486 match quad {
3487 0 => Ok(out),
3488 1 => Err(format!(
3489 "Invalid base64-encoded string: number of data characters ({ndata}) cannot be 1 more than a multiple of 4"
3490 )),
3491 _ => Err("Incorrect padding".to_string()),
3492 }
3493}
3494
3495fn days_in_month(year: i64, month: u32) -> u32 {
3496 match month {
3497 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
3498 4 | 6 | 9 | 11 => 30,
3499 2 => {
3500 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
3501 29
3502 } else {
3503 28
3504 }
3505 }
3506 _ => 0,
3507 }
3508}
3509
3510struct TsParts {
3513 year: i64,
3514 month: u32,
3515 day: u32,
3516 time: Option<TsTime>,
3517 }
3519
3520type TsTime = (u32, u32, u32, String, Option<(i64, u32, u32)>, bool);
3521
3522fn match_ts_constructor(v: &str) -> Option<TsParts> {
3523 let b = v.as_bytes();
3524 if !b.is_ascii() {
3525 return None;
3526 }
3527 let d = |i: usize| b.get(i).is_some_and(u8::is_ascii_digit);
3528 if !(d(0) && d(1) && d(2) && d(3)) {
3529 return None;
3530 }
3531 let year: i64 = v[0..4].parse().ok()?;
3532 let mut i = 4;
3533 if b.get(i) != Some(&b'-') || !d(i + 1) {
3534 return None;
3535 }
3536 i += 1;
3537 let m_start = i;
3538 i += 1;
3539 if d(i) {
3540 i += 1;
3541 }
3542 let month: u32 = v[m_start..i].parse().ok()?;
3543 if b.get(i) != Some(&b'-') || !d(i + 1) {
3544 return None;
3545 }
3546 i += 1;
3547 let d_start = i;
3548 i += 1;
3549 if d(i) {
3550 i += 1;
3551 }
3552 let day: u32 = v[d_start..i].parse().ok()?;
3553 if i == b.len() {
3554 return Some(TsParts {
3555 year,
3556 month,
3557 day,
3558 time: None,
3559 });
3560 }
3561 match b.get(i) {
3562 Some(b'T') | Some(b't') => i += 1,
3563 Some(b' ') | Some(b'\t') => {
3564 while matches!(b.get(i), Some(b' ') | Some(b'\t')) {
3565 i += 1;
3566 }
3567 }
3568 _ => return None,
3569 }
3570 if !d(i) {
3571 return None;
3572 }
3573 let h_start = i;
3574 i += 1;
3575 if d(i) {
3576 i += 1;
3577 }
3578 let hour: u32 = v[h_start..i].parse().ok()?;
3579 if b.get(i) != Some(&b':') || !(d(i + 1) && d(i + 2)) {
3580 return None;
3581 }
3582 let minute: u32 = v[i + 1..i + 3].parse().ok()?;
3583 i += 3;
3584 if b.get(i) != Some(&b':') || !(d(i + 1) && d(i + 2)) {
3585 return None;
3586 }
3587 let second: u32 = v[i + 1..i + 3].parse().ok()?;
3588 i += 3;
3589 let mut fraction = String::new();
3590 if b.get(i) == Some(&b'.') {
3591 i += 1;
3592 while d(i) {
3593 fraction.push(b[i] as char);
3594 i += 1;
3595 }
3596 }
3597 let mut tz: Option<(i64, u32, u32)> = None;
3598 let mut tz_z = false;
3599 if i < b.len() {
3600 while matches!(b.get(i), Some(b' ') | Some(b'\t')) {
3601 i += 1;
3602 }
3603 match b.get(i) {
3604 Some(b'Z') => {
3605 tz_z = true;
3606 i += 1;
3607 }
3608 Some(&s @ (b'-' | b'+')) => {
3609 i += 1;
3610 if !d(i) {
3611 return None;
3612 }
3613 let th_start = i;
3614 i += 1;
3615 if d(i) {
3616 i += 1;
3617 }
3618 let tz_hour: u32 = v[th_start..i].parse().ok()?;
3619 let mut tz_minute = 0u32;
3620 if b.get(i) == Some(&b':') {
3621 if !(d(i + 1) && d(i + 2)) {
3622 return None;
3623 }
3624 tz_minute = v[i + 1..i + 3].parse().ok()?;
3625 i += 3;
3626 }
3627 tz = Some((if s == b'-' { -1 } else { 1 }, tz_hour, tz_minute));
3628 }
3629 _ => return None,
3630 }
3631 if i != b.len() {
3632 return None;
3633 }
3634 }
3635 Some(TsParts {
3636 year,
3637 month,
3638 day,
3639 time: Some((hour, minute, second, fraction, tz, tz_z)),
3640 })
3641}
3642
3643fn construct_yaml_timestamp(node: &Node) -> Result<Yaml, YErr> {
3644 let value = match node {
3645 Node::Scalar { value, .. } => value.clone(),
3646 _ => {
3653 construct_scalar_value(node)?;
3654 return Err(YErr::Internal(
3655 "TypeError: expected string or bytes-like object, got 'list'".to_string(),
3656 ));
3657 }
3658 };
3659 let parts = match match_ts_constructor(&value) {
3660 Some(p) => p,
3661 None => {
3663 return Err(YErr::Internal(
3664 "AttributeError: 'NoneType' object has no attribute 'groupdict'".to_string(),
3665 ))
3666 }
3667 };
3668 if parts.year < 1 {
3671 return Err(YErr::Internal(format!(
3672 "ValueError: year {} is out of range",
3673 parts.year
3674 )));
3675 }
3676 if !(1..=12).contains(&parts.month) {
3677 return Err(YErr::Internal(
3678 "ValueError: month must be in 1..12".to_string(),
3679 ));
3680 }
3681 if parts.day < 1 || parts.day > days_in_month(parts.year, parts.month) {
3682 return Err(YErr::Internal(
3683 "ValueError: day is out of range for month".to_string(),
3684 ));
3685 }
3686 let Some((hour, minute, second, fraction, tz, tz_z)) = parts.time else {
3687 return Ok(Yaml::Date {
3688 year: parts.year,
3689 month: parts.month,
3690 day: parts.day,
3691 });
3692 };
3693 if hour > 23 {
3694 return Err(YErr::Internal(
3695 "ValueError: hour must be in 0..23".to_string(),
3696 ));
3697 }
3698 if minute > 59 {
3699 return Err(YErr::Internal(
3700 "ValueError: minute must be in 0..59".to_string(),
3701 ));
3702 }
3703 if second > 59 {
3704 return Err(YErr::Internal(
3705 "ValueError: second must be in 0..59".to_string(),
3706 ));
3707 }
3708 let micro = if fraction.is_empty() {
3709 0
3710 } else {
3711 let mut f: String = fraction.chars().take(6).collect();
3712 while f.len() < 6 {
3713 f.push('0');
3714 }
3715 f.parse::<u32>().unwrap_or(0)
3716 };
3717 let tzinfo: Option<i64> = if let Some((sign, th, tm)) = tz {
3718 let offset = sign * (th as i64 * 3600 + tm as i64 * 60);
3719 if offset.abs() >= 24 * 3600 {
3720 return Err(YErr::Internal(format!(
3721 "ValueError: offset must be a timedelta strictly between -timedelta(hours=24) and timedelta(hours=24), not {}.",
3722 py_repr_timedelta(offset)
3723 )));
3724 }
3725 Some(offset)
3726 } else if tz_z {
3727 Some(0)
3728 } else {
3729 None
3730 };
3731 Ok(Yaml::DateTime {
3732 year: parts.year,
3733 month: parts.month,
3734 day: parts.day,
3735 hour,
3736 minute,
3737 second,
3738 micro,
3739 tz: tzinfo,
3740 })
3741}
3742
3743fn flatten_pairs(pairs: &[(Node, Node)]) -> Result<Vec<(Node, Node)>, YErr> {
3747 let mut merge: Vec<(Node, Node)> = Vec::new();
3748 let mut rest: Vec<(Node, Node)> = Vec::new();
3749 for (k, v) in pairs {
3750 if k.tag() == TAG_MERGE {
3751 match v {
3752 Node::Map {
3753 pairs: sub_pairs, ..
3754 } => {
3755 merge.extend(flatten_pairs(sub_pairs)?);
3756 }
3757 Node::Seq { items, .. } => {
3758 let mut submerge = Vec::new();
3759 for sub in items {
3760 let Node::Map {
3761 pairs: sub_pairs, ..
3762 } = sub
3763 else {
3764 return Err(YErr::Marked(format!(
3765 "expected a mapping for merging, but found {}",
3766 sub.id()
3767 )));
3768 };
3769 submerge.push(flatten_pairs(sub_pairs)?);
3770 }
3771 submerge.reverse();
3772 for value in submerge {
3773 merge.extend(value);
3774 }
3775 }
3776 _ => {
3777 return Err(YErr::Marked(format!(
3778 "expected a mapping or list of mappings for merging, but found {}",
3779 v.id()
3780 )));
3781 }
3782 }
3783 } else if k.tag() == TAG_VALUE {
3784 let retagged = match k {
3785 Node::Scalar { value, .. } => Node::Scalar {
3786 tag: TAG_STR.to_string(),
3787 value: value.clone(),
3788 },
3789 other => other.clone(),
3790 };
3791 rest.push((retagged, v.clone()));
3792 } else {
3793 rest.push((k.clone(), v.clone()));
3794 }
3795 }
3796 merge.extend(rest);
3797 Ok(merge)
3798}
3799
3800fn construct_mapping_plain(node: &Node) -> Result<Vec<(Yaml, Yaml)>, YErr> {
3803 let Node::Map { pairs, .. } = node else {
3804 return Err(YErr::Marked(format!(
3805 "expected a mapping node, but found {}",
3806 node.id()
3807 )));
3808 };
3809 let flat = flatten_pairs(pairs)?;
3810 let mut out: Vec<(Yaml, Yaml)> = Vec::new();
3811 for (k_node, v_node) in &flat {
3812 let key = construct_value(k_node)?;
3813 if let Some(_name) = unhashable_type_name(&key) {
3814 return Err(YErr::Marked("found unhashable key".to_string()));
3815 }
3816 let value = construct_value(v_node)?;
3817 if let Some(slot) = out.iter_mut().find(|(k, _)| py_eq(k, &key)) {
3818 slot.1 = value;
3819 } else {
3820 out.push((key, value));
3821 }
3822 }
3823 Ok(out)
3824}
3825
3826fn construct_strict_map(node: &Node) -> Result<Yaml, YErr> {
3830 let Node::Map { pairs, .. } = node else {
3831 return match node {
3839 Node::Scalar { value, .. } if !value.is_empty() => {
3840 Err(YErr::Internal(
3842 "ValueError: not enough values to unpack (expected 2, got 1)".to_string(),
3843 ))
3844 }
3845 Node::Seq { items, .. } if !items.is_empty() => Err(YErr::Internal(format!(
3846 "TypeError: cannot unpack non-iterable {} object",
3847 py_node_class(&items[0])
3848 ))),
3849 _ => Err(YErr::Marked(format!(
3850 "expected a mapping node, but found {}",
3851 node.id()
3852 ))),
3853 };
3854 };
3855 let mut seen: Vec<Yaml> = Vec::new();
3856 for (k_node, _) in pairs {
3857 let key = construct_value(k_node)?;
3858 if unhashable_type_name(&key).is_some() {
3859 return Err(YErr::Marked(format!(
3865 "unhashable frontmatter key: {}",
3866 py_repr(&key)?
3867 )));
3868 }
3869 if seen.iter().any(|s| py_eq(s, &key)) {
3870 return Err(YErr::Marked(format!(
3872 "duplicate frontmatter key: {}",
3873 py_repr(&key)?
3874 )));
3875 }
3876 seen.push(key);
3877 }
3878 let mut out: Vec<(Yaml, Yaml)> = Vec::new();
3881 for (k_node, v_node) in pairs {
3882 let key = construct_value(k_node)?;
3883 let value = construct_value(v_node)?;
3884 out.push((key, value));
3885 }
3886 Ok(Yaml::Map(out))
3887}
3888
3889fn construct_omap_pairs(node: &Node) -> Result<Yaml, YErr> {
3890 let Node::Seq { items, .. } = node else {
3893 return Err(YErr::Marked(format!(
3894 "expected a sequence, but found {}",
3895 node.id()
3896 )));
3897 };
3898 let mut out: Vec<Yaml> = Vec::new();
3899 for sub in items {
3900 let Node::Map { pairs, .. } = sub else {
3901 return Err(YErr::Marked(format!(
3902 "expected a mapping of length 1, but found {}",
3903 sub.id()
3904 )));
3905 };
3906 if pairs.len() != 1 {
3907 return Err(YErr::Marked(format!(
3908 "expected a single mapping item, but found {} items",
3909 pairs.len()
3910 )));
3911 }
3912 let key = construct_value(&pairs[0].0)?;
3913 let value = construct_value(&pairs[0].1)?;
3914 out.push(Yaml::Tuple(vec![key, value]));
3915 }
3916 Ok(Yaml::List(out))
3917}
3918
3919fn construct_value(node: &Node) -> Result<Yaml, YErr> {
3920 match node.tag() {
3921 TAG_MAP => construct_strict_map(node),
3922 TAG_STR => Ok(Yaml::Str(construct_scalar_value(node)?)),
3923 TAG_NULL => {
3924 construct_scalar_value(node)?;
3925 Ok(Yaml::Null)
3926 }
3927 TAG_BOOL => construct_yaml_bool(node),
3928 TAG_INT => construct_yaml_int(node),
3929 TAG_FLOAT => construct_yaml_float(node),
3930 "tag:yaml.org,2002:binary" => construct_yaml_binary(node),
3931 TAG_TIMESTAMP => construct_yaml_timestamp(node),
3932 TAG_SEQ => {
3933 let Node::Seq { items, .. } = node else {
3934 return Err(YErr::Marked(format!(
3935 "expected a sequence node, but found {}",
3936 node.id()
3937 )));
3938 };
3939 let mut out = Vec::new();
3940 for item in items {
3941 out.push(construct_value(item)?);
3942 }
3943 Ok(Yaml::List(out))
3944 }
3945 "tag:yaml.org,2002:omap" | "tag:yaml.org,2002:pairs" => construct_omap_pairs(node),
3946 "tag:yaml.org,2002:set" => {
3947 let pairs = construct_mapping_plain(node)?;
3948 Ok(Yaml::Set(pairs.into_iter().map(|(k, _)| k).collect()))
3949 }
3950 other => Err(YErr::Marked(format!(
3951 "could not determine a constructor for the tag {}",
3952 py_repr_str(other)
3953 ))),
3954 }
3955}
3956
3957fn yaml_load(raw: &str) -> Result<Yaml, YErr> {
3959 check_printable(raw)?;
3960 let scanner = Scanner::new(raw);
3961 let parser = Parser::new(scanner);
3962 let mut composer = Composer {
3963 p: parser,
3964 anchors: std::collections::HashSet::new(),
3965 depth: 0,
3966 };
3967 match composer.get_single_node()? {
3968 None => Ok(Yaml::Null),
3969 Some(node) => construct_value(&node),
3970 }
3971}
3972
3973fn map_get<'a>(pairs: &'a [(Yaml, Yaml)], name: &str) -> Option<&'a Yaml> {
3978 pairs.iter().find_map(|(k, v)| match k {
3979 Yaml::Str(s) if s == name => Some(v),
3980 _ => None,
3981 })
3982}
3983
3984pub fn load_frontmatter_mapping(raw: &str) -> (Option<Vec<(Yaml, Yaml)>>, Vec<Issue>) {
3988 if exceeds_byte_cap(raw, MAX_FRONTMATTER_BYTES) {
3989 return (
3990 None,
3991 vec![Issue::error(
3992 "malformed-frontmatter",
3993 format!("frontmatter exceeds the {MAX_FRONTMATTER_BYTES}-byte cap"),
3994 )],
3995 );
3996 }
3997 match yaml_load(raw) {
3998 Ok(Yaml::Map(pairs)) => (Some(pairs), Vec::new()),
3999 Ok(_) => (
4000 None,
4001 vec![Issue::error(
4002 "malformed-frontmatter",
4003 "frontmatter must be a YAML mapping of supported fields".to_string(),
4004 )],
4005 ),
4006 Err(YErr::Marked(problem)) => {
4007 if problem.contains("duplicate frontmatter key") {
4008 (
4009 None,
4010 vec![Issue::error("duplicate-frontmatter-key", problem)],
4011 )
4012 } else {
4013 (
4014 None,
4015 vec![Issue::error(
4016 "malformed-frontmatter",
4017 format!("frontmatter is not valid YAML: {problem}"),
4018 )],
4019 )
4020 }
4021 }
4022 Err(YErr::Reader(msg)) => (
4023 None,
4024 vec![Issue::error(
4025 "malformed-frontmatter",
4026 format!("frontmatter is not valid YAML: {msg}"),
4027 )],
4028 ),
4029 Err(YErr::Internal(msg)) => (
4034 None,
4035 vec![Issue {
4036 severity: "error",
4037 code: "internal-oracle-divergence".to_string(),
4038 message: msg,
4039 line: None,
4040 }],
4041 ),
4042 }
4043}
4044
4045pub fn yaml_load_config(raw: &str) -> Result<Yaml, String> {
4052 yaml_load(raw).map_err(|e| match e {
4053 YErr::Marked(p) | YErr::Reader(p) | YErr::Internal(p) => p,
4054 })
4055}
4056
4057fn check_unknown_fields(pairs: &[(Yaml, Yaml)], issues: &mut Vec<Issue>) -> Result<(), YErr> {
4058 for (key, _) in pairs {
4059 let known = matches!(key, Yaml::Str(s) if SUPPORTED_FIELDS.contains(&s.as_str()));
4060 if !known {
4061 issues.push(Issue::error(
4062 "invalid-metadata-field",
4063 format!(
4064 "unsupported frontmatter field: {} (supported: {})",
4065 py_repr(key)?,
4066 SUPPORTED_FIELDS.join(", ")
4067 ),
4068 ));
4069 }
4070 }
4071 Ok(())
4072}
4073
4074fn validate_schema_version(
4075 pairs: &[(Yaml, Yaml)],
4076 issues: &mut Vec<Issue>,
4077) -> Result<Option<SchemaVersion>, YErr> {
4078 let Some(value) = map_get(pairs, "schema_version") else {
4079 issues.push(Issue::error(
4080 "invalid-metadata-field",
4081 "frontmatter is missing required field 'schema_version'".to_string(),
4082 ));
4083 return Ok(None); };
4085 let (supported, sv) = match value {
4087 Yaml::Int(v) => (SUPPORTED_SCHEMA_VERSIONS.contains(v), SchemaVersion::Int(*v)),
4088 Yaml::BigInt(b) => (false, SchemaVersion::Big(b.clone())),
4089 _ => {
4090 issues.push(Issue::error(
4091 "invalid-metadata-field",
4092 "frontmatter field 'schema_version' must be an integer".to_string(),
4093 ));
4094 return Ok(None);
4095 }
4096 };
4097 if !supported {
4098 if let SchemaVersion::Big(b) = &sv {
4100 if b.digits.len() > INT_MAX_STR_DIGITS {
4101 return Err(int_to_str_limit_err());
4102 }
4103 }
4104 issues.push(Issue::error(
4105 "unsupported-schema-version",
4106 format!(
4107 "unsupported frontmatter schema_version: {} (supported: {})",
4108 sv,
4109 SUPPORTED_SCHEMA_VERSIONS
4110 .iter()
4111 .map(|s| s.to_string())
4112 .collect::<Vec<_>>()
4113 .join(", ")
4114 ),
4115 ));
4116 }
4117 Ok(Some(sv))
4118}
4119
4120fn validate_id(pairs: &[(Yaml, Yaml)], issues: &mut Vec<Issue>) -> Option<String> {
4121 let value = map_get(pairs, "id")?;
4122 if matches!(value, Yaml::Null) {
4123 return None;
4124 }
4125 let Yaml::Str(s) = value else {
4126 issues.push(Issue::error(
4127 "invalid-metadata-field",
4128 "frontmatter field 'id' must be a string".to_string(),
4129 ));
4130 return None;
4131 };
4132 if !is_valid_id(s) {
4133 issues.push(Issue::error(
4134 "invalid-id-syntax",
4135 format!(
4136 "invalid artifact ID syntax: {} (expected <KEY>-<12-char Crockford base32 suffix>, e.g. RAC-01JY4M8X2QZ7)",
4137 py_repr_str(s)
4138 ),
4139 ));
4140 return None;
4141 }
4142 Some(normalize_id(s))
4143}
4144
4145fn validate_type(
4146 pairs: &[(Yaml, Yaml)],
4147 issues: &mut Vec<Issue>,
4148) -> Result<Option<String>, YErr> {
4149 let Some(value) = map_get(pairs, "type") else {
4150 return Ok(None);
4151 };
4152 if matches!(value, Yaml::Null) {
4153 return Ok(None);
4154 }
4155 let registered = match value {
4156 Yaml::Str(s) => crate::spec::spec_for(s).is_some(),
4157 _ => false,
4158 };
4159 if !registered {
4160 issues.push(Issue::error(
4161 "invalid-metadata-field",
4162 format!(
4163 "frontmatter field 'type' is not a registered artifact type: {}",
4164 py_repr(value)?
4165 ),
4166 ));
4167 return Ok(None);
4168 }
4169 Ok(match value {
4170 Yaml::Str(s) => Some(s.clone()),
4171 _ => None,
4172 })
4173}
4174
4175fn validate_relationships(
4176 pairs: &[(Yaml, Yaml)],
4177 issues: &mut Vec<Issue>,
4178) -> Vec<(String, Vec<String>)> {
4179 let Some(value) = map_get(pairs, "relationships") else {
4180 return Vec::new();
4181 };
4182 if matches!(value, Yaml::Null) {
4183 return Vec::new();
4184 }
4185 let well_formed = match value {
4186 Yaml::Map(rel_pairs) => rel_pairs.iter().all(|(kind, targets)| {
4187 matches!(kind, Yaml::Str(_))
4188 && matches!(targets, Yaml::List(items) if items.iter().all(|t| matches!(t, Yaml::Str(_))))
4189 }),
4190 _ => false,
4191 };
4192 if !well_formed {
4193 issues.push(Issue::error(
4194 "invalid-metadata-field",
4195 "frontmatter field 'relationships' must map relationship kinds to lists of artifact IDs"
4196 .to_string(),
4197 ));
4198 return Vec::new();
4199 }
4200 let Yaml::Map(rel_pairs) = value else {
4201 return Vec::new();
4202 };
4203 rel_pairs
4204 .iter()
4205 .map(|(kind, targets)| {
4206 let k = match kind {
4207 Yaml::Str(s) => s.clone(),
4208 _ => String::new(),
4209 };
4210 let t = match targets {
4211 Yaml::List(items) => items
4212 .iter()
4213 .map(|t| match t {
4214 Yaml::Str(s) => normalize_id(s),
4215 _ => String::new(),
4216 })
4217 .collect(),
4218 _ => Vec::new(),
4219 };
4220 (k, t)
4221 })
4222 .collect()
4223}
4224
4225fn parse_tags(pairs: &[(Yaml, Yaml)], issues: &mut Vec<Issue>) -> Vec<String> {
4226 let Some(value) = map_get(pairs, "tags") else {
4227 return Vec::new();
4228 };
4229 if matches!(value, Yaml::Null) {
4230 return Vec::new();
4231 }
4232 let well_formed = matches!(value, Yaml::List(items) if items
4233 .iter()
4234 .all(|t| matches!(t, Yaml::Str(s) if !py_strip(s).is_empty())));
4235 if !well_formed {
4236 issues.push(Issue::error(
4237 "invalid-metadata-field",
4238 "frontmatter field 'tags' must be a list of non-empty strings".to_string(),
4239 ));
4240 return Vec::new();
4241 }
4242 let Yaml::List(items) = value else {
4243 return Vec::new();
4244 };
4245 items
4246 .iter()
4247 .map(|t| match t {
4248 Yaml::Str(s) => py_strip(s).to_string(),
4249 _ => String::new(),
4250 })
4251 .collect()
4252}
4253
4254pub fn parse_frontmatter(raw: &str) -> (Option<ArtifactMetadata>, Vec<Issue>) {
4262 let (data, mut issues) = load_frontmatter_mapping(raw);
4263 let Some(pairs) = data else {
4264 return (None, issues);
4265 };
4266 match validate_fields(&pairs, &mut issues) {
4267 Ok(metadata) => (Some(metadata), issues),
4268 Err(YErr::Internal(msg)) => (
4273 None,
4274 vec![Issue {
4275 severity: "error",
4276 code: "internal-oracle-divergence".to_string(),
4277 message: msg,
4278 line: None,
4279 }],
4280 ),
4281 Err(_) => unreachable!("field validators raise only internal errors"),
4283 }
4284}
4285
4286fn validate_fields(
4287 pairs: &[(Yaml, Yaml)],
4288 issues: &mut Vec<Issue>,
4289) -> Result<ArtifactMetadata, YErr> {
4290 check_unknown_fields(pairs, issues)?;
4291 let schema_version = validate_schema_version(pairs, issues)?;
4292 let id = validate_id(pairs, issues);
4293 let artifact_type = validate_type(pairs, issues)?;
4294 let relationships = validate_relationships(pairs, issues);
4295 let tags = parse_tags(pairs, issues);
4296 Ok(ArtifactMetadata {
4297 schema_version: schema_version.unwrap_or(SchemaVersion::Int(0)),
4298 id,
4299 artifact_type,
4300 relationships,
4301 tags,
4302 provenance: "frontmatter",
4303 })
4304}
4305
4306pub fn oversize_file_issue(cap: u64) -> Issue {
4313 Issue {
4314 severity: "error",
4315 code: "artifact-oversize".to_string(),
4316 message: format!("artifact exceeds the {cap}-byte file cap (set DECIDED_MAX_FILE_BYTES to raise it)"),
4317 line: Some(1),
4318 }
4319}
4320
4321pub fn oversize_parse_issue(cap: u64) -> Issue {
4324 Issue {
4325 severity: "error",
4326 code: "artifact-oversize".to_string(),
4327 message: format!("artifact exceeds the {cap}-byte parse cap (set DECIDED_MAX_FILE_BYTES to raise it)"),
4328 line: Some(1),
4329 }
4330}
4331
4332pub fn non_utf8_issue() -> Issue {
4333 Issue {
4334 severity: "warning",
4335 code: "non-utf8-content".to_string(),
4336 message: "artifact is not valid UTF-8; decoded lossily".to_string(),
4337 line: Some(1),
4338 }
4339}
4340
4341pub fn unterminated_issue() -> Issue {
4344 Issue {
4345 severity: "error",
4346 code: "malformed-frontmatter".to_string(),
4347 message: "frontmatter block opened with --- on line 1 but never closed".to_string(),
4348 line: Some(1),
4349 }
4350}
4351
4352#[derive(Debug)]
4353pub struct ArtifactRead {
4354 pub text: Option<String>,
4356 pub issue: Option<Issue>,
4358 pub lossy: bool,
4361}
4362
4363fn py_oserror_message(e: &std::io::Error, path: &str) -> String {
4365 let s = e.to_string();
4366 let msg = match s.find(" (os error") {
4367 Some(pos) => &s[..pos],
4368 None => s.as_str(),
4369 };
4370 match e.raw_os_error() {
4371 Some(n) => format!("[Errno {n}] {msg}: '{path}'"),
4372 None => format!("{msg}: '{path}'"),
4373 }
4374}
4375
4376pub fn read_artifact_text(path: &str) -> ArtifactRead {
4380 use std::io::Read;
4381 let cap_state = file_cap();
4382 let unreadable = |e: &std::io::Error| ArtifactRead {
4383 text: None,
4384 issue: Some(Issue {
4385 severity: "error",
4386 code: "unreadable-artifact".to_string(),
4387 message: format!("cannot read artifact: {}", py_oserror_message(e, path)),
4388 line: Some(1),
4389 }),
4390 lossy: false,
4391 };
4392 let size = match std::fs::metadata(path) {
4393 Ok(m) => m.len(),
4394 Err(e) => return unreadable(&e),
4395 };
4396 let cap = match cap_state {
4397 FileCap::Cap(cap) => cap,
4398 FileCap::OracleCrash(msg) => {
4403 return match std::fs::File::open(path) {
4404 Ok(_) => ArtifactRead {
4405 text: None,
4406 issue: Some(Issue {
4407 severity: "error",
4408 code: "internal-oracle-divergence".to_string(),
4409 message: msg.to_string(),
4410 line: None,
4411 }),
4412 lossy: false,
4413 },
4414 Err(e) => unreadable(&e),
4415 };
4416 }
4417 };
4418 if size > cap {
4419 return ArtifactRead {
4420 text: None,
4421 issue: Some(oversize_file_issue(cap)),
4422 lossy: false,
4423 };
4424 }
4425 let mut data = Vec::new();
4426 match std::fs::File::open(path) {
4427 Ok(f) => {
4428 let mut handle = f.take(cap + 1);
4429 if let Err(e) = handle.read_to_end(&mut data) {
4430 return unreadable(&e);
4431 }
4432 }
4433 Err(e) => return unreadable(&e),
4434 }
4435 if data.len() as u64 > cap {
4436 return ArtifactRead {
4437 text: None,
4438 issue: Some(oversize_file_issue(cap)),
4439 lossy: false,
4440 };
4441 }
4442 match String::from_utf8(data) {
4443 Ok(text) => ArtifactRead {
4444 text: Some(text),
4445 issue: None,
4446 lossy: false,
4447 },
4448 Err(e) => {
4449 let text = String::from_utf8_lossy(e.as_bytes()).into_owned();
4450 ArtifactRead {
4451 text: Some(text),
4452 issue: None,
4453 lossy: true,
4454 }
4455 }
4456 }
4457}