use std::cell::Cell;
use serde::Deserialize;
use serde::de::{self, DeserializeSeed, IntoDeserializer, MapAccess, SeqAccess, Visitor};
use crate::error::{Error, ErrorKind, Result};
thread_local! {
static IS_LIST_CONTEXT: Cell<Option<bool>> = const { Cell::new(None) };
}
pub(crate) fn take_list_context() -> Option<bool> {
IS_LIST_CONTEXT.with(|c| c.take())
}
pub(crate) fn set_list_context(is_list: bool) {
IS_LIST_CONTEXT.with(|c| c.set(Some(is_list)));
}
pub(crate) fn clear_list_context() {
IS_LIST_CONTEXT.with(|c| c.set(None));
}
pub(crate) fn strip_trivia(mut s: &str) -> &str {
loop {
s = s.trim_start();
if s.starts_with('#') || s.starts_with("//") {
match s.find('\n') {
Some(pos) => s = &s[pos + 1..],
None => return "",
}
} else if s.starts_with("/*") {
match s.find("*/") {
Some(pos) => s = &s[pos + 2..],
None => return s,
}
} else {
return s;
}
}
}
fn is_name_char(c: char) -> bool {
c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '*')
}
pub struct Deserializer<'de> {
input: &'de str,
at_root: bool,
}
impl<'de> Deserializer<'de> {
#[allow(clippy::should_implement_trait)]
pub fn from_str(input: &'de str) -> Self {
Deserializer {
input,
at_root: true,
}
}
pub fn end(&mut self) -> Result<()> {
self.skip_whitespace();
if self.input.is_empty() {
Ok(())
} else {
Err(ErrorKind::TrailingCharacters.into())
}
}
fn peek_char(&self) -> Result<char> {
self.input.chars().next().ok_or_else(|| ErrorKind::Eof.into())
}
fn next_char(&mut self) -> Result<char> {
let ch = self.peek_char()?;
self.input = &self.input[ch.len_utf8()..];
Ok(ch)
}
fn skip_whitespace(&mut self) {
self.input = strip_trivia(self.input);
}
fn is_implicit_group_start(&self) -> bool {
let input = self.input;
let mut len = 0;
for (idx, ch) in input.char_indices() {
if idx == 0 {
if ch.is_ascii_alphabetic() || ch == '*' {
len = ch.len_utf8();
} else {
return false;
}
} else if is_name_char(ch) {
len = idx + ch.len_utf8();
} else {
break;
}
}
if len == 0 {
return false;
}
let rest = strip_trivia(&input[len..]);
matches!(rest.chars().next(), Some('=' | ':'))
}
fn parse_bool(&mut self) -> Result<bool> {
self.skip_whitespace();
if self
.input
.get(..4)
.is_some_and(|s| s.eq_ignore_ascii_case("true"))
&& !self.input[4..].chars().next().is_some_and(is_name_char)
{
self.input = &self.input[4..];
Ok(true)
} else if self
.input
.get(..5)
.is_some_and(|s| s.eq_ignore_ascii_case("false"))
&& !self.input[5..].chars().next().is_some_and(is_name_char)
{
self.input = &self.input[5..];
Ok(false)
} else {
Err(ErrorKind::ExpectedBoolean.into())
}
}
fn parse_identifier(&mut self) -> Result<&'de str> {
self.skip_whitespace();
let start = self.input;
let mut len = 0;
for (idx, ch) in self.input.char_indices() {
if idx == 0 {
if ch.is_ascii_alphabetic() || ch == '*' {
len = ch.len_utf8();
} else {
break;
}
} else if is_name_char(ch) {
len = idx + ch.len_utf8();
} else {
break;
}
}
if len == 0 {
if self.input.starts_with("/*") {
return Err(ErrorKind::UnterminatedComment.into());
}
return Err(ErrorKind::ExpectedIdentifier.into());
}
let ident = &start[..len];
self.input = &self.input[len..];
Ok(ident)
}
fn parse_single_string(&mut self) -> Result<String> {
if self.next_char()? != '"' {
return Err(ErrorKind::ExpectedString.into());
}
let mut result = String::new();
let mut escape = false;
loop {
if self.input.is_empty() {
return Err(ErrorKind::Eof.into());
}
let ch = self.next_char()?;
if escape {
match ch {
'"' => result.push('"'),
'\\' => result.push('\\'),
'n' => result.push('\n'),
'r' => result.push('\r'),
't' => result.push('\t'),
'f' => result.push('\x0C'),
'b' => result.push('\x08'),
'v' => result.push('\x0B'),
'a' => result.push('\x07'),
'x' => {
let hex = self.input.get(..2).ok_or(ErrorKind::InvalidEscape)?;
if let Ok(byte) = u8::from_str_radix(hex, 16) {
result.push(byte as char);
self.input = &self.input[2..];
} else {
return Err(ErrorKind::InvalidEscape.into());
}
}
_ => return Err(ErrorKind::InvalidEscape.into()),
}
escape = false;
} else if ch == '\\' {
escape = true;
} else if ch == '"' {
break;
} else {
result.push(ch);
}
}
Ok(result)
}
fn parse_borrowed_string(&mut self) -> Result<Option<&'de str>> {
let original = self.input;
if !original.starts_with('"') {
return Err(ErrorKind::ExpectedString.into());
}
let bytes = original.as_bytes();
let mut i = 1;
while i < bytes.len() {
match bytes[i] {
b'\\' => return Ok(None),
b'"' => {
if strip_trivia(&original[i + 1..]).starts_with('"') {
return Ok(None);
}
let inner = &original[1..i];
self.input = &original[i + 1..];
return Ok(Some(inner));
}
_ => i += 1,
}
}
Err(ErrorKind::Eof.into())
}
fn parse_string(&mut self) -> Result<String> {
self.skip_whitespace();
let mut result = self.parse_single_string()?;
loop {
self.skip_whitespace();
if self.input.starts_with('"') {
result.push_str(&self.parse_single_string()?);
} else {
break;
}
}
Ok(result)
}
fn parse_number(&mut self) -> Result<Number> {
self.skip_whitespace();
let s = self.input;
let bytes = s.as_bytes();
let mut i = 0;
#[derive(PartialEq, Clone, Copy)]
enum Base {
Dec,
Hex,
Bin,
Oct,
}
let has_sign = matches!(bytes.first(), Some(b'+' | b'-'));
if has_sign {
i += 1;
}
let mut base = Base::Dec;
if !has_sign && bytes.len() > i + 1 && bytes[i] == b'0' {
match bytes[i + 1] {
b'x' | b'X' => {
base = Base::Hex;
i += 2;
}
b'b' | b'B' => {
base = Base::Bin;
i += 2;
}
b'o' | b'O' | b'q' | b'Q' => {
base = Base::Oct;
i += 2;
}
_ => {}
}
}
let digits_start = i;
let mut digit_count = 0usize;
let mut is_float = false;
if base == Base::Dec {
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
digit_count += 1;
}
if i < bytes.len() && bytes[i] == b'.' {
is_float = true;
i += 1;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
digit_count += 1;
}
}
if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
let mut j = i + 1;
if j < bytes.len() && (bytes[j] == b'+' || bytes[j] == b'-') {
j += 1;
}
let exp_digits_start = j;
while j < bytes.len() && bytes[j].is_ascii_digit() {
j += 1;
}
if j > exp_digits_start {
is_float = true;
i = j;
}
}
} else {
while i < bytes.len()
&& match base {
Base::Hex => bytes[i].is_ascii_hexdigit(),
Base::Bin => matches!(bytes[i], b'0' | b'1'),
Base::Oct => matches!(bytes[i], b'0'..=b'7'),
Base::Dec => unreachable!(),
}
{
i += 1;
digit_count += 1;
}
}
if digit_count == 0 {
return Err(ErrorKind::ExpectedInteger.into());
}
let max_digits = match base {
Base::Dec => usize::MAX, Base::Hex => 16,
Base::Bin => 64,
Base::Oct => 21,
};
if digit_count > max_digits {
return Err(ErrorKind::InvalidNumber.into());
}
let digits_end = i;
let mut is_long = false;
if !is_float && i < bytes.len() && bytes[i] == b'L' {
is_long = true;
i += 1;
if i < bytes.len() && bytes[i] == b'L' {
i += 1;
}
}
let num_str = &s[..i];
self.input = &s[i..];
if is_float {
let val = num_str.parse::<f64>().map_err(|_| ErrorKind::InvalidNumber)?;
return Ok(Number::Float(val));
}
let digits = &s[digits_start..digits_end];
let (radix, promote_digits) = match base {
Base::Dec => (10, usize::MAX),
Base::Hex => (16, 8),
Base::Bin => (2, 32),
Base::Oct => (8, 10),
};
if base == Base::Dec {
let signed = &s[..digits_end];
if is_long {
let val = signed.parse::<i64>().map_err(|_| ErrorKind::InvalidNumber)?;
Ok(Number::I64(val))
} else {
match signed.parse::<i32>() {
Ok(val) => Ok(Number::I32(val)),
Err(_) => {
let val = signed.parse::<i64>().map_err(|_| ErrorKind::InvalidNumber)?;
Ok(Number::I64(val))
}
}
}
} else {
let val = i64::from_str_radix(digits, radix).map_err(|_| ErrorKind::InvalidNumber)?;
if is_long || digit_count > promote_digits {
Ok(Number::I64(val))
} else {
Ok(Number::I32(val as i32))
}
}
}
}
pub fn from_str<'a, T>(s: &'a str) -> Result<T>
where
T: Deserialize<'a>,
{
let mut deserializer = Deserializer::from_str(s);
let result = T::deserialize(&mut deserializer).and_then(|t| {
deserializer.end()?;
Ok(t)
});
clear_list_context();
result.map_err(|e| {
let (line, column) = locate(s, deserializer.input);
e.with_location(line, column)
})
}
pub fn from_reader<R, T>(mut reader: R) -> Result<T>
where
R: std::io::Read,
T: de::DeserializeOwned,
{
let mut s = String::new();
reader
.read_to_string(&mut s)
.map_err(|e| ErrorKind::Io(e.to_string()))?;
from_str(&s)
}
fn locate(original: &str, remaining: &str) -> (usize, usize) {
let consumed = original.len().saturating_sub(remaining.len());
let consumed_text = &original[..consumed.min(original.len())];
let line = consumed_text.matches('\n').count() + 1;
let column = consumed_text
.rsplit('\n')
.next()
.map(|l| l.chars().count())
.unwrap_or(0)
+ 1;
(line, column)
}
#[derive(Debug, Clone)]
enum Number {
I32(i32),
I64(i64),
Float(f64),
}
macro_rules! deserialize_integer {
($method:ident, $visit:ident, $ty:ty) => {
fn $method<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match self.parse_number()? {
Number::I32(v) => {
visitor.$visit(<$ty>::try_from(v).map_err(|_| ErrorKind::IntegerOutOfRange)?)
}
Number::I64(v) => {
visitor.$visit(<$ty>::try_from(v).map_err(|_| ErrorKind::IntegerOutOfRange)?)
}
Number::Float(_) => Err(ErrorKind::ExpectedInteger.into()),
}
}
};
}
impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.skip_whitespace();
if self.input.is_empty() {
return visitor.visit_map(ImplicitGroupAccess::new(self));
}
match self.peek_char()? {
't' | 'T' | 'f' | 'F' => {
if self.is_implicit_group_start() {
visitor.visit_map(ImplicitGroupAccess::new(self))
} else {
self.deserialize_bool(visitor)
}
}
'"' => self.deserialize_string(visitor),
'[' => self.deserialize_seq(visitor),
'(' => self.deserialize_seq(visitor), '{' => self.deserialize_map(visitor),
'-' | '+' | '0'..='9' | '.' => {
let num = self.parse_number()?;
match num {
Number::I32(v) => visitor.visit_i32(v),
Number::I64(v) => visitor.visit_i64(v),
Number::Float(v) => visitor.visit_f64(v),
}
}
c if c.is_ascii_alphabetic() || c == '*' => {
visitor.visit_map(ImplicitGroupAccess::new(self))
}
'/' if self.input.starts_with("/*") => Err(ErrorKind::UnterminatedComment.into()),
_ => Err(ErrorKind::Syntax.into()),
}
}
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_bool(self.parse_bool()?)
}
deserialize_integer!(deserialize_i8, visit_i8, i8);
deserialize_integer!(deserialize_i16, visit_i16, i16);
deserialize_integer!(deserialize_i32, visit_i32, i32);
deserialize_integer!(deserialize_i64, visit_i64, i64);
deserialize_integer!(deserialize_u8, visit_u8, u8);
deserialize_integer!(deserialize_u16, visit_u16, u16);
deserialize_integer!(deserialize_u32, visit_u32, u32);
deserialize_integer!(deserialize_u64, visit_u64, u64);
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let num = self.parse_number()?;
match num {
Number::I32(v) => visitor.visit_f32(v as f32),
Number::I64(v) => visitor.visit_f32(v as f32),
Number::Float(v) => {
let f = v as f32;
if f.is_infinite() && v.is_finite() {
return Err(ErrorKind::FloatOutOfRange.into());
}
visitor.visit_f32(f)
}
}
}
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let num = self.parse_number()?;
match num {
Number::I32(v) => visitor.visit_f64(v as f64),
Number::I64(v) => visitor.visit_f64(v as f64),
Number::Float(v) => visitor.visit_f64(v),
}
}
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let s = self.parse_string()?;
let mut chars = s.chars();
let ch = chars.next().ok_or(ErrorKind::ExpectedString)?;
if chars.next().is_some() {
return Err(ErrorKind::ExpectedString.into());
}
visitor.visit_char(ch)
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.skip_whitespace();
match self.parse_borrowed_string()? {
Some(s) => visitor.visit_borrowed_str(s),
None => visitor.visit_string(self.parse_string()?),
}
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_unit(visitor)
}
fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.skip_whitespace();
let ch = self.peek_char()?;
if ch == '[' {
self.next_char()?;
set_list_context(false);
let value = visitor.visit_seq(ArrayAccess::new(self, ']', false))?;
self.skip_whitespace();
if self.next_char()? == ']' {
Ok(value)
} else {
Err(ErrorKind::ExpectedArrayEnd.into())
}
} else if ch == '(' {
self.next_char()?;
set_list_context(true);
let value = visitor.visit_seq(ArrayAccess::new(self, ')', true))?;
self.skip_whitespace();
if self.next_char()? == ')' {
Ok(value)
} else {
Err(ErrorKind::ExpectedListEnd.into())
}
} else {
Err(ErrorKind::ExpectedArray.into())
}
}
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.skip_whitespace();
if self.input.starts_with('{') {
if self.at_root {
return Err(ErrorKind::TopLevelBraces.into());
}
self.next_char()?;
let value = visitor.visit_map(GroupAccess::new(self))?;
self.skip_whitespace();
if self.next_char()? == '}' {
Ok(value)
} else {
Err(ErrorKind::ExpectedGroupEnd.into())
}
} else {
visitor.visit_map(ImplicitGroupAccess::new(self))
}
}
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_map(visitor)
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.skip_whitespace();
match self.peek_char()? {
'"' => visitor.visit_enum(self.parse_string()?.into_deserializer()),
'{' => {
self.next_char()?;
self.at_root = false;
let value = visitor.visit_enum(EnumGroupAccess { de: &mut *self })?;
self.skip_whitespace();
if matches!(self.peek_char(), Ok(';' | ',')) {
self.next_char()?;
self.skip_whitespace();
}
if self.next_char()? == '}' {
Ok(value)
} else {
Err(ErrorKind::ExpectedGroupEnd.into())
}
}
_ => Err(ErrorKind::ExpectedString.into()),
}
}
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_str(self.parse_identifier()?)
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_any(visitor)
}
}
struct MapKeyDeserializer<'a, 'de: 'a> {
de: &'a mut Deserializer<'de>,
}
impl<'de> de::Deserializer<'de> for MapKeyDeserializer<'_, 'de> {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_borrowed_str(self.de.parse_identifier()?)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string
bytes byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum identifier ignored_any
}
}
struct EnumGroupAccess<'a, 'de: 'a> {
de: &'a mut Deserializer<'de>,
}
impl<'de, 'a> de::EnumAccess<'de> for EnumGroupAccess<'a, 'de> {
type Error = Error;
type Variant = VariantGroupAccess<'a, 'de>;
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
where
V: DeserializeSeed<'de>,
{
let variant = self.de.parse_identifier()?;
let variant_de: de::value::StrDeserializer<'de, Error> = variant.into_deserializer();
let value = seed.deserialize(variant_de)?;
self.de.skip_whitespace();
let ch = self.de.next_char()?;
if ch != '=' && ch != ':' {
return Err(ErrorKind::ExpectedEquals.into());
}
Ok((value, VariantGroupAccess { de: self.de }))
}
}
struct VariantGroupAccess<'a, 'de: 'a> {
de: &'a mut Deserializer<'de>,
}
impl<'de> de::VariantAccess<'de> for VariantGroupAccess<'_, 'de> {
type Error = Error;
fn unit_variant(self) -> Result<()> {
Err(ErrorKind::ExpectedString.into())
}
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
where
T: DeserializeSeed<'de>,
{
seed.deserialize(&mut *self.de)
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
de::Deserializer::deserialize_seq(&mut *self.de, visitor)
}
fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
de::Deserializer::deserialize_map(&mut *self.de, visitor)
}
}
struct ArrayAccess<'a, 'de: 'a> {
de: &'a mut Deserializer<'de>,
first: bool,
end_char: char,
is_list: bool,
}
impl<'a, 'de> ArrayAccess<'a, 'de> {
fn new(de: &'a mut Deserializer<'de>, end_char: char, is_list: bool) -> Self {
ArrayAccess {
de,
first: true,
end_char,
is_list,
}
}
}
impl<'de, 'a> SeqAccess<'de> for ArrayAccess<'a, 'de> {
type Error = Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: DeserializeSeed<'de>,
{
self.de.skip_whitespace();
if self.de.peek_char()? == self.end_char {
return Ok(None);
}
if !self.first {
if self.de.peek_char()? == ',' {
self.de.next_char()?;
self.de.skip_whitespace();
if self.de.peek_char()? == self.end_char {
return Ok(None);
}
} else {
return Err(ErrorKind::ExpectedArrayComma.into());
}
}
self.first = false;
if !self.is_list && matches!(self.de.peek_char()?, '{' | '[' | '(') {
return Err(ErrorKind::ArrayElementNotScalar.into());
}
self.de.at_root = false;
seed.deserialize(&mut *self.de).map(Some)
}
}
struct GroupAccess<'a, 'de: 'a> {
de: &'a mut Deserializer<'de>,
first: bool,
}
impl<'a, 'de> GroupAccess<'a, 'de> {
fn new(de: &'a mut Deserializer<'de>) -> Self {
GroupAccess { de, first: true }
}
}
struct ImplicitGroupAccess<'a, 'de: 'a> {
de: &'a mut Deserializer<'de>,
first: bool,
}
impl<'a, 'de> ImplicitGroupAccess<'a, 'de> {
fn new(de: &'a mut Deserializer<'de>) -> Self {
ImplicitGroupAccess { de, first: true }
}
}
impl<'de, 'a> MapAccess<'de> for GroupAccess<'a, 'de> {
type Error = Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
where
K: DeserializeSeed<'de>,
{
self.de.skip_whitespace();
if self.de.peek_char()? == '}' {
return Ok(None);
}
if !self.first {
let ch = self.de.peek_char()?;
if ch == ';' || ch == ',' {
self.de.next_char()?;
self.de.skip_whitespace();
if self.de.peek_char()? == '}' {
return Ok(None);
}
}
}
self.first = false;
seed.deserialize(MapKeyDeserializer { de: &mut *self.de })
.map(Some)
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
where
V: DeserializeSeed<'de>,
{
self.de.skip_whitespace();
let ch = self.de.next_char()?;
if ch != '=' && ch != ':' {
return Err(ErrorKind::ExpectedEquals.into());
}
self.de.at_root = false;
seed.deserialize(&mut *self.de)
}
}
impl<'de, 'a> MapAccess<'de> for ImplicitGroupAccess<'a, 'de> {
type Error = Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
where
K: DeserializeSeed<'de>,
{
self.de.skip_whitespace();
if self.de.input.is_empty() {
return Ok(None);
}
if !self.first {
let ch = self.de.peek_char()?;
if ch == ';' || ch == ',' {
self.de.next_char()?;
self.de.skip_whitespace();
if self.de.input.is_empty() {
return Ok(None);
}
}
}
self.first = false;
seed.deserialize(MapKeyDeserializer { de: &mut *self.de })
.map(Some)
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
where
V: DeserializeSeed<'de>,
{
self.de.skip_whitespace();
let ch = self.de.next_char()?;
if ch != '=' && ch != ':' {
return Err(ErrorKind::ExpectedEquals.into());
}
self.de.at_root = false;
seed.deserialize(&mut *self.de)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[test]
fn test_deserialize_primitives() {
assert_eq!(from_str::<u32>("42").unwrap(), 42);
assert_eq!(from_str::<i64>("42L").unwrap(), 42);
assert_eq!(from_str::<f64>("2.25").unwrap(), 2.25);
assert!(from_str::<bool>("true").unwrap());
assert_eq!(from_str::<String>("\"hello\"").unwrap(), "hello");
}
#[test]
fn test_deserialize_array() {
let arr: Vec<i32> = from_str("[1, 2, 3]").unwrap();
assert_eq!(arr, vec![1, 2, 3]);
}
#[test]
fn test_deserialize_struct() {
#[derive(Debug, Deserialize, PartialEq)]
struct Test {
a: i32,
b: String,
}
let input = r#"a = 42; b = "hello";"#;
let result: Test = from_str(input).unwrap();
assert_eq!(
result,
Test {
a: 42,
b: "hello".to_string()
}
);
}
#[test]
fn test_deserialize_with_comments() {
let input = r#"
# This is a comment
// Another comment
a = 42; /* inline comment */
b = "hello";
"#;
#[derive(Debug, Deserialize, PartialEq)]
struct Test {
a: i32,
b: String,
}
let result: Test = from_str(input).unwrap();
assert_eq!(
result,
Test {
a: 42,
b: "hello".to_string()
}
);
}
#[test]
fn test_case_insensitive_booleans() {
assert!(from_str::<bool>("true").unwrap());
assert!(from_str::<bool>("True").unwrap());
assert!(from_str::<bool>("TRUE").unwrap());
assert!(!from_str::<bool>("false").unwrap());
assert!(!from_str::<bool>("False").unwrap());
assert!(!from_str::<bool>("FALSE").unwrap());
assert!(!from_str::<bool>("FaLsE").unwrap());
}
#[test]
fn test_bool_word_boundary() {
assert!(from_str::<bool>("trueblah").is_err());
assert!(from_str::<bool>("falsehood").is_err());
}
#[test]
fn test_adjacent_string_concatenation() {
let result: String = from_str(r#""hello" " world""#).unwrap();
assert_eq!(result, "hello world");
let result: String = from_str(r#""a" "b" "c""#).unwrap();
assert_eq!(result, "abc");
}
#[test]
fn test_integer_auto_promotion() {
let val: i64 = from_str("3000000000").unwrap();
assert_eq!(val, 3_000_000_000i64);
}
#[test]
fn test_hex_binary_octal() {
assert_eq!(from_str::<i32>("0xFF").unwrap(), 255);
assert_eq!(from_str::<i32>("0b1010").unwrap(), 10);
assert_eq!(from_str::<i32>("0o17").unwrap(), 15);
assert_eq!(from_str::<i32>("0q17").unwrap(), 15);
}
#[test]
fn test_hex_auto_promotion_by_digit_count() {
let val: i64 = from_str("0x1FFFFFFFF").unwrap();
assert_eq!(val, 0x1FFFFFFFFi64);
}
#[test]
fn test_float_leading_dot() {
let val: f64 = from_str(".5").unwrap();
assert_eq!(val, 0.5);
}
#[test]
fn test_list_deserialization() {
let arr: Vec<i32> = from_str("(1, 2, 3)").unwrap();
assert_eq!(arr, vec![1, 2, 3]);
}
#[test]
fn test_implicit_group_as_value() {
use crate::Value;
let raw_string = r#"
title = "My HTTP server";
listen_ports = [ 80, 443 ];
misc = {
owner = "Chuck Norris";
location = "CA";
contact = {
phone = "415-256-9999";
emails = ["chuck@norris.com", "chuck.norris@gmail.com"];
};
};"#;
let parsed_value: Value = from_str(raw_string).expect("Failed to parse value");
assert_eq!(parsed_value["title"], "My HTTP server");
assert_eq!(parsed_value["listen_ports"][0], 80);
assert_eq!(parsed_value["listen_ports"][1], 443);
assert_eq!(parsed_value["misc"]["owner"], "Chuck Norris");
assert_eq!(parsed_value["misc"]["contact"]["phone"], "415-256-9999");
}
#[test]
fn test_implicit_group_bool_key() {
use crate::Value;
let input = r#"flag = true; total = 100;"#;
let v: Value = from_str(input).expect("Failed to parse");
assert_eq!(v["flag"], true);
assert_eq!(v["total"], 100);
}
#[test]
fn test_standalone_bool_still_works() {
assert!(from_str::<bool>("true").unwrap());
assert!(!from_str::<bool>("false").unwrap());
use crate::Value;
let v: Value = from_str("true").unwrap();
assert_eq!(v, Value::Bool(true));
let v: Value = from_str("false").unwrap();
assert_eq!(v, Value::Bool(false));
}
#[test]
fn test_optional_semicolons() {
#[derive(Debug, Deserialize, PartialEq)]
struct Test {
a: i32,
b: i32,
c: i32,
}
let result: Test = from_str(r#"a = 1; b = 2; c = 3;"#).unwrap();
assert_eq!(result, Test { a: 1, b: 2, c: 3 });
let result: Test = from_str("a = 1\n b = 2\n c = 3").unwrap();
assert_eq!(result, Test { a: 1, b: 2, c: 3 });
let result: Test = from_str("a = 1; b = 2\n c = 3").unwrap();
assert_eq!(result, Test { a: 1, b: 2, c: 3 });
let result: Test = from_str(r#"a = 1; b = 2; c = 3;"#).unwrap();
assert_eq!(result, Test { a: 1, b: 2, c: 3 });
}
#[test]
fn test_commas_as_setting_separators() {
#[derive(Debug, Deserialize, PartialEq)]
struct Test {
a: i32,
b: i32,
c: i32,
}
let result: Test = from_str(r#"a = 1, b = 2, c = 3"#).unwrap();
assert_eq!(result, Test { a: 1, b: 2, c: 3 });
let result: Test = from_str(r#"a = 1, b = 2, c = 3,"#).unwrap();
assert_eq!(result, Test { a: 1, b: 2, c: 3 });
}
#[test]
fn test_optional_separators_in_implicit_group() {
use crate::Value;
let v: Value = from_str("a = 1\nb = 2\nc = 3").unwrap();
assert_eq!(v["a"], 1);
assert_eq!(v["b"], 2);
assert_eq!(v["c"], 3);
let v: Value = from_str("a = 1; b = 2; c = 3;").unwrap();
assert_eq!(v["a"], 1);
assert_eq!(v["b"], 2);
assert_eq!(v["c"], 3);
}
#[test]
fn test_auto_promotion_i64_via_value() {
use crate::Value;
let v: Value = from_str("42").unwrap();
assert!(v.is_i32());
assert_eq!(v, Value::Integer(42));
let v: Value = from_str("3000000000").unwrap();
assert!(v.is_i64());
assert_eq!(v.as_i64(), Some(3_000_000_000i64));
let v: Value = from_str("42L").unwrap();
assert!(v.is_i64());
assert_eq!(v.as_i64(), Some(42i64));
let v: Value = from_str("-3000000000").unwrap();
assert!(v.is_i64());
assert_eq!(v.as_i64(), Some(-3_000_000_000i64));
let v: Value = from_str("2147483647").unwrap();
assert!(v.is_i32());
let v: Value = from_str("2147483648").unwrap();
assert!(v.is_i64());
}
#[test]
fn test_negative_numbers() {
assert_eq!(from_str::<i32>("-42").unwrap(), -42);
assert_eq!(from_str::<i32>("-1").unwrap(), -1);
assert_eq!(from_str::<i64>("-42L").unwrap(), -42);
assert_eq!(from_str::<f64>("-2.25").unwrap(), -2.25);
assert_eq!(from_str::<f64>("-0.5").unwrap(), -0.5);
#[derive(Debug, Deserialize, PartialEq)]
struct Coords {
x: i32,
y: i32,
z: f64,
}
let result: Coords = from_str(r#"x = -10; y = -20; z = -1.5;"#).unwrap();
assert_eq!(
result,
Coords {
x: -10,
y: -20,
z: -1.5,
}
);
use crate::Value;
let v: Value = from_str("-42").unwrap();
assert_eq!(v, Value::Integer(-42));
let v: Value = from_str("-2.25").unwrap();
assert_eq!(v, Value::Float(-2.25));
}
#[test]
fn test_identifier_must_start_with_letter_or_asterisk() {
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Test {
a: i32,
}
let _: Test = from_str(r#"a = 1;"#).unwrap();
assert!(from_str::<Test>(r#"1a = 1;"#).is_err());
}
#[test]
fn test_float_signed_exponent() {
assert_eq!(from_str::<f64>("1e+5").unwrap(), 1e5);
assert_eq!(from_str::<f64>("1e-5").unwrap(), 1e-5);
assert_eq!(from_str::<f64>("3.1e-2").unwrap(), 3.1e-2);
assert_eq!(from_str::<f64>("-2.5E+3").unwrap(), -2.5e3);
assert_eq!(from_str::<f64>("1e5").unwrap(), 1e5);
use crate::Value;
let v: Value = from_str("avogadro = 6.02e+23;").unwrap();
assert_eq!(v["avogadro"], 6.02e23);
assert!(from_str::<f64>("1e").is_err());
assert!(from_str::<f64>("1e+").is_err());
}
#[test]
fn test_ll_suffix() {
assert_eq!(from_str::<i64>("42LL").unwrap(), 42);
assert_eq!(from_str::<i64>("-42LL").unwrap(), -42);
assert_eq!(from_str::<i64>("0xFFLL").unwrap(), 255);
assert_eq!(from_str::<i64>("0b1010LL").unwrap(), 10);
assert_eq!(from_str::<i64>("0o17LL").unwrap(), 15);
use crate::Value;
let v: Value = from_str("42LL").unwrap();
assert!(v.is_i64());
}
#[test]
fn test_multibyte_input_does_not_panic() {
use crate::Value;
assert!(from_str::<Value>("tr€").is_err());
assert!(from_str::<bool>("tr€").is_err());
assert!(from_str::<bool>("fals€").is_err());
assert!(from_str::<String>("\"\\x€\"").is_err());
}
#[test]
fn test_integer_range_checked() {
assert!(matches!(from_str::<u8>("300"), Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange));
assert!(matches!(from_str::<u32>("-1"), Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange));
assert!(matches!(
from_str::<i8>("128"),
Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange
));
assert!(matches!(
from_str::<i32>("3000000000"),
Err(e) if *e.kind() == ErrorKind::IntegerOutOfRange
));
assert_eq!(from_str::<u8>("255").unwrap(), 255);
assert_eq!(from_str::<i32>("100L").unwrap(), 100);
assert_eq!(from_str::<i8>("-128").unwrap(), -128);
}
#[test]
fn test_float_not_coerced_to_integer() {
assert!(from_str::<i8>("3.9").is_err());
assert!(from_str::<i64>("1.0").is_err());
assert_eq!(from_str::<f64>("42").unwrap(), 42.0);
}
#[test]
fn test_unquoted_string_values_rejected() {
assert!(from_str::<String>("foo").is_err());
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct S {
name: String,
}
assert!(from_str::<S>("name = foo;").is_err());
assert!(from_str::<S>("name = \"foo\";").is_ok());
}
#[test]
fn test_empty_configuration() {
use crate::Value;
let v: Value = from_str("").unwrap();
assert_eq!(v, Value::Group(crate::Map::new()));
let v: Value = from_str(" # just a comment\n").unwrap();
assert_eq!(v, Value::Group(crate::Map::new()));
#[derive(Debug, Deserialize, PartialEq)]
struct AllOptional {
a: Option<i32>,
}
assert_eq!(from_str::<AllOptional>("").unwrap(), AllOptional { a: None });
}
#[test]
fn test_map_from_implicit_group() {
use indexmap::IndexMap;
let m: IndexMap<String, i32> = from_str("a = 1; b = 2;").unwrap();
assert_eq!(m["a"], 1);
assert_eq!(m["b"], 2);
let m: IndexMap<String, i32> = from_str("").unwrap();
assert!(m.is_empty());
}
#[test]
fn test_top_level_braces_rejected() {
use crate::Value;
use indexmap::IndexMap;
assert!(matches!(
from_str::<Value>("{ a = 1; }"),
Err(e) if *e.kind() == ErrorKind::TopLevelBraces
));
assert!(matches!(
from_str::<IndexMap<String, i32>>("{ a = 1; }"),
Err(e) if *e.kind() == ErrorKind::TopLevelBraces
));
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct S {
a: i32,
}
assert!(matches!(
from_str::<S>("{ a = 1; }"),
Err(e) if *e.kind() == ErrorKind::TopLevelBraces
));
let v: Value = from_str("g = { a = 1; }; l = ({ b = 2; });").unwrap();
assert_eq!(v["g.a"], 1);
assert_eq!(v["l.[0].b"], 2);
}
#[test]
fn test_arrays_are_scalar_only() {
use crate::Value;
assert!(matches!(
from_str::<Value>("x = [{ a = 1; }];"),
Err(e) if *e.kind() == ErrorKind::ArrayElementNotScalar
));
assert!(matches!(
from_str::<Value>("x = [[1, 2]];"),
Err(e) if *e.kind() == ErrorKind::ArrayElementNotScalar
));
assert!(matches!(
from_str::<Value>("x = [(1, 2)];"),
Err(e) if *e.kind() == ErrorKind::ArrayElementNotScalar
));
assert!(from_str::<Value>("x = ({ a = 1; }, [1, 2], (3));").is_ok());
}
#[test]
fn test_array_elements_same_type() {
use crate::Value;
assert!(from_str::<Value>("x = [1, \"a\"];").is_err());
assert!(from_str::<Value>("x = [true, 1];").is_err());
assert!(from_str::<Value>("x = [1, 5000000000];").is_ok());
assert!(from_str::<Value>("x = (1, \"a\");").is_ok());
}
#[test]
fn test_duplicate_setting_names_rejected() {
use crate::Value;
assert!(from_str::<Value>("a = 1; a = 2;").is_err());
assert!(from_str::<Value>("g = { x = 1; x = 2; };").is_err());
}
#[test]
fn test_enum_round_trip() {
use crate::to_string;
#[derive(Debug, Deserialize, serde::Serialize, PartialEq)]
enum E {
Unit,
Newtype(i32),
Tuple(i32, bool),
Struct { x: i32 },
}
for e in [
E::Unit,
E::Newtype(5),
E::Tuple(7, true),
E::Struct { x: 9 },
] {
let s = to_string(&e).unwrap();
let back: E = from_str(&s).unwrap();
assert_eq!(back, e, "round-trip failed for {s:?}");
}
assert_eq!(from_str::<E>("\"Unit\"").unwrap(), E::Unit);
assert_eq!(from_str::<E>("{ Newtype = 5; }").unwrap(), E::Newtype(5));
assert_eq!(
from_str::<E>("{ Tuple = (7, true); }").unwrap(),
E::Tuple(7, true)
);
assert_eq!(
from_str::<E>("{ Struct = { x = 9; }; }").unwrap(),
E::Struct { x: 9 }
);
assert_eq!(from_str::<E>("{ Newtype: 5 }").unwrap(), E::Newtype(5));
}
#[test]
fn test_identifier_is_ascii_only() {
use crate::Value;
assert!(from_str::<Value>("café = 1;").is_err());
assert!(from_str::<Value>("*wild-card_2* = 1;").is_ok());
}
#[test]
fn test_comment_between_identifier_and_equals() {
use crate::Value;
let v: Value = from_str("total /* count */ = 3;").unwrap();
assert_eq!(v["total"], 3);
}
#[test]
fn test_string_concatenation_with_comments() {
let s: String =
from_str("\"The quick\" /* comment */ \" brown fox \" // another comment\n\"jumped\"")
.unwrap();
assert_eq!(s, "The quick brown fox jumped");
}
#[test]
fn test_parse_errors_carry_line_and_column() {
use crate::Value;
let err = from_str::<Value>("a = 1;\nb = 2;\nc = @;\n").unwrap_err();
assert_eq!(err.line(), Some(3));
assert!(err.column().is_some());
let msg = err.to_string();
assert!(msg.contains("line 3"), "message was: {msg}");
let err = crate::to_string(&f64::NAN).unwrap_err();
assert_eq!(err.line(), None);
}
#[test]
fn test_deserializer_end() {
let mut de = Deserializer::from_str("42 # trailing comment");
let v: i32 = serde::Deserialize::deserialize(&mut de).unwrap();
assert_eq!(v, 42);
assert!(de.end().is_ok());
let mut de = Deserializer::from_str("42 garbage");
let _: i32 = serde::Deserialize::deserialize(&mut de).unwrap();
assert!(matches!(
de.end().unwrap_err().kind(),
ErrorKind::TrailingCharacters
));
}
#[test]
fn test_f32_out_of_range_errors() {
assert!(matches!(
from_str::<f32>("1e300").unwrap_err().kind(),
ErrorKind::FloatOutOfRange
));
assert_eq!(from_str::<f32>("2.5").unwrap(), 2.5);
assert!(from_str::<f32>("5000000000L").is_ok());
}
use crate::Value;
fn parses(input: &str) -> Value {
from_str::<Value>(input)
.unwrap_or_else(|e| panic!("expected {input:?} to parse, got: {e}"))
}
fn rejects(input: &str) {
assert!(
from_str::<Value>(input).is_err(),
"expected {input:?} to be rejected"
);
}
#[test]
fn appendix_b_configuration() {
assert_eq!(parses(""), Value::Group(crate::Map::new()));
parses("a = 1;");
parses("a = 1; b = 2;");
rejects("{ a = 1; }");
}
#[test]
fn appendix_b_setting() {
parses("a = 1;"); parses("a : 1;"); parses("a = 1,"); parses("a = 1"); parses("a = 1 b = 2"); parses("a = 1, b = 2; c = 3");
rejects("a 1;"); rejects("a = ;"); rejects("= 1;"); }
#[test]
fn appendix_b_name() {
parses("a = 1;");
parses("A = 1;");
parses("* = 1;");
parses("*a-b_c9* = 1;");
rejects("1a = 1;"); rejects("_a = 1;"); rejects("-a = 1;"); rejects("café = 1;"); }
#[test]
fn appendix_b_group() {
let v = parses("g = { a = 1; b = 2; };");
assert_eq!(v["g.a"], 1);
let v = parses("g = { };");
assert_eq!(v["g"], Value::Group(crate::Map::new()));
rejects("g = { ; };"); rejects("g = { a = 1; ;");
}
#[test]
fn appendix_b_array() {
parses("x = [];");
parses("x = [1];");
parses("x = [1, 2, 3];");
parses("x = [1, 2,];"); parses("x = [-1, +2];"); parses("x = [\"a\" \"b\", \"c\"];"); rejects("x = [,];"); rejects("x = [1,, 2];"); rejects("x = [{ a = 1; }];"); rejects("x = [[1]];"); rejects("x = [(1)];"); }
#[test]
fn appendix_b_list() {
parses("x = ();");
parses("x = (1, \"two\", true, 4.5);");
parses("x = (1, 2,);"); parses("x = (( \"abc\", 123, true ), 1.234, ( ));"); parses("x = ({ a = 1; }, [1, 2]);"); rejects("x = (,);");
}
#[test]
fn appendix_b_boolean() {
for t in ["true", "True", "TRUE", "tRuE"] {
assert_eq!(parses(&format!("b = {t};"))["b"], true);
}
for f in ["false", "False", "FALSE", "fAlSe"] {
assert_eq!(parses(&format!("b = {f};"))["b"], false);
}
rejects("b = truex;"); rejects("b = yes;");
}
#[test]
fn appendix_b_integers() {
assert_eq!(parses("i = 0;")["i"], 0);
assert_eq!(parses("i = +42;")["i"], 42);
assert_eq!(parses("i = -42;")["i"], -42);
assert_eq!(parses("i = 007;")["i"], 7); assert!(parses("i = 42L;")["i"].is_i64());
assert!(parses("i = 42LL;")["i"].is_i64());
assert!(parses("i = -42LL;")["i"].is_i64());
}
#[test]
fn appendix_b_radix_integers() {
assert_eq!(parses("i = 0b1010;")["i"], 10);
assert_eq!(parses("i = 0B1;")["i"], 1);
assert_eq!(parses("i = 0o17;")["i"], 15);
assert_eq!(parses("i = 0O17;")["i"], 15);
assert_eq!(parses("i = 0q17;")["i"], 15);
assert_eq!(parses("i = 0Q17;")["i"], 15);
assert_eq!(parses("i = 0xff;")["i"], 255);
assert_eq!(parses("i = 0XFF;")["i"], 255);
assert!(parses("i = 0xffLL;")["i"].is_i64());
rejects("i = -0x10;");
rejects("i = +0b1;");
rejects("i = 0o8;");
rejects("i = 0b2;");
rejects("i = 0x;");
parses(&format!("i = 0b{};", "0".repeat(64)));
rejects(&format!("i = 0b{};", "0".repeat(65)));
parses(&format!("i = 0x{};", "0".repeat(16)));
rejects(&format!("i = 0x{};", "0".repeat(17)));
parses(&format!("i = 0o{};", "0".repeat(21)));
rejects(&format!("i = 0o{};", "0".repeat(22)));
}
#[test]
fn appendix_b_float_exponent_case_insensitive() {
assert_eq!(from_str::<f64>("1E5").unwrap(), 1e5);
assert_eq!(from_str::<f64>("1E+5").unwrap(), 1e5);
assert_eq!(from_str::<f64>("1E-5").unwrap(), 1e-5);
assert_eq!(from_str::<f64>("2.5E3").unwrap(), 2500.0);
assert_eq!(from_str::<f64>(".5E-2").unwrap(), 0.005);
assert_eq!(from_str::<f64>("5.E3").unwrap(), 5000.0);
assert_eq!(from_str::<f64>("-2.5E+3").unwrap(), -2500.0);
assert_eq!(
from_str::<f64>("1E5").unwrap(),
from_str::<f64>("1e5").unwrap()
);
}
#[test]
fn test_zero_copy_borrowed_strings() {
#[derive(Debug, Deserialize, PartialEq)]
struct S<'a> {
name: &'a str,
owned: String,
}
let input = r#"name = "zero copy"; owned = "also fine";"#;
let s: S = from_str(input).unwrap();
assert_eq!(s.name, "zero copy");
assert_eq!(s.owned, "also fine");
assert!(from_str::<S>(r#"name = "no\tborrow"; owned = "x";"#).is_err());
#[derive(Debug, Deserialize)]
struct Owned {
owned: String,
}
let o: Owned = from_str(r#"owned = "has\ttab";"#).unwrap();
assert_eq!(o.owned, "has\ttab");
let o: Owned = from_str(r#"owned = "a" "b";"#).unwrap();
assert_eq!(o.owned, "ab");
}
#[test]
fn appendix_b_float() {
assert_eq!(parses("f = 1.5;")["f"], 1.5);
assert_eq!(parses("f = .5;")["f"], 0.5);
assert_eq!(parses("f = 5.;")["f"], 5.0);
assert_eq!(parses("f = 5.e3;")["f"], 5000.0);
assert_eq!(parses("f = 1e5;")["f"], 1e5);
assert_eq!(parses("f = 1e+5;")["f"], 1e5);
assert_eq!(parses("f = 1.25e-2;")["f"], 1.25e-2);
assert_eq!(parses("f = -2.5E+3;")["f"], -2.5e3);
rejects("f = 1e;");
rejects("f = 1e+;");
rejects("f = .;");
rejects("f = 1.5L;");
}
#[test]
fn section_2_10_comments() {
let v = parses("# script\n// c++\n/* c */ a = /* mid */ 1; # end");
assert_eq!(v["a"], 1);
assert_eq!(parses("a = /* spans\nseveral\nlines */ 1;")["a"], 1);
let v = parses("a = 1; /* outer /* inner */ b = 2;");
assert_eq!(v["b"], 2);
let v = parses(r#"s = "has # and // and /* inside";"#);
assert_eq!(v["s"], "has # and // and /* inside");
assert_eq!(parses("a = 1; # no newline")["a"], 1);
assert_eq!(parses("a = 1; // no newline")["a"], 1);
assert!(matches!(
from_str::<Value>("a = 1; /* never closed").unwrap_err().kind(),
ErrorKind::UnterminatedComment
));
assert!(matches!(
from_str::<Value>("/* only").unwrap_err().kind(),
ErrorKind::UnterminatedComment
));
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct S {
a: i32,
}
assert!(from_str::<S>("a = /* open 1;").is_err());
assert_eq!(parses("/* nothing */"), Value::Group(crate::Map::new()));
}
#[test]
fn appendix_b_string() {
assert_eq!(parses("s = \"\";")["s"], "");
assert_eq!(parses("s = \"hello\";")["s"], "hello");
assert_eq!(parses("s = \"line1\nline2\";")["s"], "line1\nline2");
assert_eq!(
parses(r#"s = "\"\\\a\b\f\n\r\t\v";"#)["s"],
"\"\\\x07\x08\x0C\n\r\t\x0B"
);
assert_eq!(parses(r#"s = "\x41\x4212";"#)["s"], "AB12");
rejects(r#"s = "\q";"#);
rejects("s = \"unterminated;");
assert_eq!(parses(r#"s = "a" "b" "c";"#)["s"], "abc");
}
}