use super::{lexer, PResult};
use crate::model::Value;
use std::borrow::Cow;
#[inline]
pub fn parse_value<'a>(input: &mut &'a str) -> PResult<'a, Value<'a>> {
parse_concatenated_value(input)
}
#[inline]
pub(crate) fn parse_value_field<'a>(input: &mut &'a str) -> PResult<'a, Value<'a>> {
parse_concatenated_value_field(input)
}
#[inline]
fn parse_concatenated_value<'a>(input: &mut &'a str) -> PResult<'a, Value<'a>> {
let first = parse_single_value(input)?;
if !consume_concat_separator(input) {
return Ok(first);
}
let mut parts = Vec::with_capacity(3);
parts.push(first);
loop {
let part = parse_single_value(input)?;
parts.push(part);
if !consume_concat_separator(input) {
break;
}
}
Ok(Value::Concat(parts.into_boxed_slice()))
}
#[inline]
fn parse_concatenated_value_field<'a>(input: &mut &'a str) -> PResult<'a, Value<'a>> {
let first = parse_single_value(input)?;
if !consume_concat_separator_field(input) {
return Ok(first);
}
let mut parts = Vec::with_capacity(3);
parts.push(first);
loop {
let part = parse_single_value(input)?;
parts.push(part);
if !consume_concat_separator_field(input) {
break;
}
}
Ok(Value::Concat(parts.into_boxed_slice()))
}
#[inline]
fn consume_concat_separator(input: &mut &str) -> bool {
let mut probe = *input;
lexer::skip_whitespace(&mut probe);
if probe.as_bytes().first() != Some(&b'#') {
return false;
}
probe = &probe[1..];
lexer::skip_whitespace(&mut probe);
*input = probe;
true
}
#[inline]
fn consume_concat_separator_field(input: &mut &str) -> bool {
match input.as_bytes().first() {
Some(b'#') => {
*input = &input[1..];
lexer::skip_whitespace(input);
true
}
Some(b' ' | b'\t' | b'\n' | b'\r') => {
lexer::skip_whitespace(input);
if input.as_bytes().first() == Some(&b'#') {
*input = &input[1..];
lexer::skip_whitespace(input);
true
} else {
false
}
}
Some(_) | None => false,
}
}
#[inline]
fn parse_single_value<'a>(input: &mut &'a str) -> PResult<'a, Value<'a>> {
let bytes = input.as_bytes();
if let Some(&first) = bytes.first() {
match first {
b'"' => {
super::simd::find_balanced_quotes(bytes).map_or_else(super::backtrack, |end_pos| {
let content = &input[1..end_pos - 1];
*input = &input[end_pos..];
Ok(Value::Literal(Cow::Borrowed(content)))
})
}
b'{' => {
super::simd::find_balanced_braces(bytes).map_or_else(super::backtrack, |end_pos| {
let content = &input[1..end_pos - 1];
*input = &input[end_pos..];
Ok(Value::Literal(Cow::Borrowed(content)))
})
}
b'0'..=b'9' | b'+' | b'-' => parse_number_or_digit_string(input),
_ => parse_variable_value(input),
}
} else {
super::backtrack()
}
}
#[inline]
fn parse_number_or_digit_string<'a>(input: &mut &'a str) -> PResult<'a, Value<'a>> {
let bytes = input.as_bytes();
let Some(&first) = bytes.first() else {
return super::backtrack();
};
let len = super::simd::scan_identifier(bytes);
if len == 0 {
return super::backtrack();
}
let token = &input[..len];
let token_bytes = token.as_bytes();
if first == b'+' || first == b'-' {
if token_bytes.len() <= 1 || !token_bytes[1..].iter().all(u8::is_ascii_digit) {
return super::backtrack();
}
let num = parse_i64_ascii(token)?;
*input = &input[len..];
return Ok(Value::Number(num));
}
if !first.is_ascii_digit() {
return super::backtrack();
}
*input = &input[len..];
if token_bytes.iter().all(u8::is_ascii_digit) {
let num = parse_i64_ascii(token)?;
Ok(Value::Number(num))
} else {
Ok(Value::Literal(Cow::Borrowed(token)))
}
}
#[inline]
fn parse_i64_ascii(token: &str) -> PResult<'_, i64> {
let bytes = token.as_bytes();
let (negative, start) = match bytes.first() {
Some(b'-') => (true, 1),
Some(b'+') => (false, 1),
_ => (false, 0),
};
if start >= bytes.len() {
return super::backtrack();
}
let mut value: i64 = 0;
for &byte in &bytes[start..] {
if !byte.is_ascii_digit() {
return super::backtrack();
}
let digit = i64::from(byte - b'0');
value = if negative {
value
.checked_mul(10)
.and_then(|v| v.checked_sub(digit))
.ok_or_else(super::backtrack_err)?
} else {
value
.checked_mul(10)
.and_then(|v| v.checked_add(digit))
.ok_or_else(super::backtrack_err)?
};
}
Ok(value)
}
#[inline]
fn parse_variable_value<'a>(input: &mut &'a str) -> PResult<'a, Value<'a>> {
let ident = lexer::identifier(input)?;
Ok(Value::Variable(Cow::Borrowed(ident)))
}
#[must_use]
pub fn normalize_value(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_quoted_value() {
let mut input = r#""hello world" xxx"#;
let value = parse_value(&mut input).unwrap();
assert_eq!(value, Value::Literal(Cow::Borrowed("hello world")));
assert_eq!(input, " xxx");
}
#[test]
fn test_parse_braced_value() {
let mut input = "{hello world} xxx";
let value = parse_value(&mut input).unwrap();
assert_eq!(value, Value::Literal(Cow::Borrowed("hello world")));
assert_eq!(input, " xxx");
}
#[test]
fn test_parse_number_value() {
let mut input = "2023 xxx";
let value = parse_value(&mut input).unwrap();
assert_eq!(value, Value::Number(2023));
assert_eq!(input, " xxx");
}
#[test]
fn test_parse_variable_value() {
let mut input = "myvar xxx";
let value = parse_value(&mut input).unwrap();
assert_eq!(value, Value::Variable(Cow::Borrowed("myvar")));
assert_eq!(input, " xxx");
}
#[test]
fn test_parse_concatenated_value() {
let mut input = r#""hello" # myvar # {world} xxx"#;
let value = parse_value(&mut input).unwrap();
match value {
Value::Concat(parts) => {
assert_eq!(parts.len(), 3);
assert_eq!(parts[0], Value::Literal(Cow::Borrowed("hello")));
assert_eq!(parts[1], Value::Variable(Cow::Borrowed("myvar")));
assert_eq!(parts[2], Value::Literal(Cow::Borrowed("world")));
}
_ => panic!("Expected concatenated value"),
}
assert_eq!(input, " xxx");
}
}