use crate::parse_scalars::parse_yaml11_bool;
use regex::Regex;
use std::sync::OnceLock;
fn is_numeric_looking(s: &str) -> bool {
static RE: OnceLock<Regex> = OnceLock::new();
let re = RE.get_or_init(|| {
Regex::new(
r"(?x)
^[+-]?(?:
# Explicit radices
0x[0-9A-Fa-f_]+ |
0o[0-7_]+ |
0b[01_]+ |
# Decimal floats / integers
(?:
# Float with dot: 1. , 1.0 , .5
(?:[0-9][0-9_]*\.[0-9_]*|\.[0-9][0-9_]*)
(?:[eE][+-]?[0-9][0-9_]*)?
|
# Scientific without dot: 1e9
[0-9][0-9_]*[eE][+-]?[0-9][0-9_]*
|
# Plain integer: 123
[0-9][0-9_]*
)
)$
",
)
.expect("valid numeric-looking regex")
});
re.is_match(s)
}
fn is_ambiguous(s: &str) -> bool {
if s.is_empty() {
return true;
}
if s == "~"
|| s.eq_ignore_ascii_case("null")
|| s.eq_ignore_ascii_case("true")
|| s.eq_ignore_ascii_case("false")
{
return true;
}
#[inline]
fn is_ascii_lower(b: u8) -> u8 {
b | 0x20
}
#[inline]
fn is_special_inf_nan_ascii(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0usize;
if let Some(&c) = bytes.first()
&& (c == b'+' || c == b'-')
{
i = 1;
}
if let Some(&c) = bytes.get(i)
&& c == b'.'
{
i += 1;
} else {
return false;
}
if bytes.len() == i + 3 {
let a = is_ascii_lower(bytes[i]);
let b = is_ascii_lower(bytes[i + 1]);
let c = is_ascii_lower(bytes[i + 2]);
return (a == b'n' && b == b'a' && c == b'n') || (a == b'i' && b == b'n' && c == b'f');
}
false
}
if is_special_inf_nan_ascii(s) {
return true;
}
if is_numeric_looking(s) {
return true;
}
false
}
#[inline]
fn is_ambiguous_value(s: &str, yaml_12: bool) -> bool {
if is_ambiguous(s) {
return true;
}
if !yaml_12 && parse_yaml11_bool(s).is_ok() {
return true;
}
s.eq_ignore_ascii_case("nan")
|| s.eq_ignore_ascii_case("inf")
|| s.eq_ignore_ascii_case("+inf")
|| s.eq_ignore_ascii_case("-inf")
}
#[inline]
pub(crate) fn is_plain_safe(s: &str) -> bool {
if is_ambiguous(s) {
return false;
}
let bytes = s.as_bytes();
if bytes[0].is_ascii_whitespace() {
return false;
}
match bytes[0] {
b'-' | b'?' => {
if bytes.len() == 1 {
return false;
}
if bytes[1].is_ascii_whitespace() {
return false;
}
}
b',' => return false,
b':' | b'[' | b']' | b'{' | b'}' | b'#' | b'&' | b'*' | b'!' | b'|' | b'>' | b'\''
| b'"' | b'%' | b'@' | b'`' => return false,
_ => {}
}
!contains_any_or_is_control(s, &[':', '#'])
}
#[inline]
pub(crate) fn is_plain_value_safe(s: &str, yaml_12: bool, in_flow: bool) -> bool {
if is_ambiguous_value(s, yaml_12) {
return false;
}
let bytes = s.as_bytes();
if bytes[0].is_ascii_whitespace() {
return false;
}
match bytes[0] {
b'-' | b'?' => {
if bytes.len() == 1 {
return false;
}
if bytes[1].is_ascii_whitespace() {
return false;
}
}
b',' => return false,
b':' | b'[' | b']' | b'{' | b'}' | b'#' | b'&' | b'*' | b'!' | b'|' | b'>' | b'\''
| b'"' | b'%' | b'@' | b'`' => return false,
_ => {}
}
if s.contains(": ") || s.trim().ends_with(':') {
return false;
}
if in_flow {
!contains_any_or_is_control(s, &[',', '[', ']', '{', '}', '#'])
} else {
!contains_any_or_is_control(s, &['#'])
}
}
fn contains_any_or_is_control(string: &str, values: &[char]) -> bool {
string
.chars()
.any(|x| values.iter().any(|v| &x == v || x.is_control()))
}