use crate::error::{Error, ErrorKind, Span};
use crate::value::Scalar;
use super::value_start::ValueStart;
pub(super) fn classify_value_start(
text: &str,
line_num: usize,
trimmed_span: Span,
) -> Result<ValueStart, Error> {
let trimmed = text.trim_start();
if trimmed == "{" {
return Ok(ValueStart::OpenObject);
}
if trimmed == "[" {
return Ok(ValueStart::OpenArray);
}
if trimmed.starts_with('{') {
if trimmed.ends_with('}') && trimmed[1..trimmed.len() - 1].trim().is_empty() {
return Ok(ValueStart::EmptyObject);
}
return Err(Error::Structured(ErrorKind::InlineNonEmptyCompound {
line: line_num as u32,
span: trimmed_span,
body: "object".to_string(),
}));
}
if trimmed.starts_with('[') {
if trimmed.ends_with(']') && trimmed[1..trimmed.len() - 1].trim().is_empty() {
return Ok(ValueStart::EmptyArray);
}
return Err(Error::Structured(ErrorKind::InlineNonEmptyCompound {
line: line_num as u32,
span: trimmed_span,
body: "array".to_string(),
}));
}
match trimmed {
"(" => return Ok(ValueStart::OpenMultilineStripped),
"((" => return Ok(ValueStart::OpenMultilineVerbatim),
"()" | "(())" => return Ok(ValueStart::Scalar(Scalar::new(""))),
_ => {}
}
if trimmed.starts_with('(') {
return Err(Error::Structured(ErrorKind::InlineNonEmptyCompound {
line: line_num as u32,
span: trimmed_span,
body: "paren-string".to_string(),
}));
}
match trimmed {
"null" => return Ok(ValueStart::Null),
"true" => return Ok(ValueStart::Bool(true)),
"false" => return Ok(ValueStart::Bool(false)),
_ => {}
}
Ok(ValueStart::Scalar(trimmed.into()))
}
pub(super) fn validate_typed_integer(
body: &str,
line_num: usize,
span: Span,
) -> Result<Scalar, Error> {
let trimmed = body.trim();
if trimmed.is_empty() {
return Err(invalid_typed_scalar(
line_num,
'i',
"integer body is empty",
span,
));
}
if opens_compound_or_multiline(trimmed) {
return Err(invalid_typed_scalar(
line_num,
'i',
"typed marker `:i` cannot open a compound or multi-line value",
span,
));
}
if !is_integer_literal(trimmed) {
return Err(invalid_typed_scalar(
line_num,
'i',
&format!("'{}' is not a valid integer literal for `:i`", trimmed),
span,
));
}
Ok(strip_leading_plus(trimmed).into())
}
pub(super) fn validate_typed_float(
body: &str,
line_num: usize,
span: Span,
) -> Result<Scalar, Error> {
let trimmed = body.trim();
if trimmed.is_empty() {
return Err(invalid_typed_scalar(
line_num,
'f',
"float body is empty",
span,
));
}
if opens_compound_or_multiline(trimmed) {
return Err(invalid_typed_scalar(
line_num,
'f',
"typed marker `:f` cannot open a compound or multi-line value",
span,
));
}
if !is_float_literal(trimmed) {
return Err(invalid_typed_scalar(
line_num,
'f',
&format!("'{}' is not a valid float literal for `:f`", trimmed),
span,
));
}
Ok(strip_leading_plus(trimmed).into())
}
fn invalid_typed_scalar(line_num: usize, marker: char, detail: &str, span: Span) -> Error {
Error::Structured(ErrorKind::InvalidTypedScalar {
line: line_num as u32,
marker,
body: detail.to_string(),
span,
})
}
fn opens_compound_or_multiline(s: &str) -> bool {
s.starts_with('{') || s.starts_with('[') || s.starts_with('(')
}
fn strip_leading_plus(s: &str) -> &str {
s.strip_prefix('+').unwrap_or(s)
}
fn is_integer_literal(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
if i == bytes.len() {
return false; }
while i < bytes.len() {
if !bytes[i].is_ascii_digit() {
return false;
}
i += 1;
}
true
}
fn is_float_literal(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
let digits_before = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i == digits_before {
return false;
}
if i < bytes.len() && bytes[i] == b'.' {
i += 1;
let digits_after = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i == digits_after {
return false;
}
}
if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
i += 1;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
let exp_digits = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i == exp_digits {
return false;
}
}
i == bytes.len()
}