This commit is contained in:
Robin Appelman 2023-12-18 01:49:06 +01:00
commit 1f0a53d241
14 changed files with 1219 additions and 100 deletions

38
examples/serde.rs Normal file
View file

@ -0,0 +1,38 @@
use miette::{Context, IntoDiagnostic, Result};
use serde::Deserialize;
use std::env::args;
use std::fs::read_to_string;
use vdf_reader::from_str;
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
enum Material {
LightmappedGeneric {
#[serde(rename = "$baseTexture")]
base_texture: String,
#[serde(rename = "$bumpmap")]
bumpmap: String,
#[serde(rename = "$ssbump")]
ssbump: bool,
#[serde(rename = "%keywords")]
keywords: String,
#[serde(rename = "$detail")]
detail: String,
#[serde(rename = "$detailscale")]
detailscale: f32,
#[serde(rename = "$detailblendmode")]
detailblendmode: i32,
#[serde(rename = "$detailblendfactor")]
detailblendfactor: f32,
},
}
fn main() -> Result<()> {
let path = args().nth(1).expect("no path provided");
let raw = read_to_string(path)
.into_diagnostic()
.wrap_err("failed to read input")?;
let material: Material = from_str(&raw)?;
dbg!(material);
Ok(())
}

View file

@ -3,7 +3,7 @@ mod statement;
mod table;
mod value;
use crate::error::{ParseEntryError, ParseItemError};
use crate::error::{ParseEntryError, ParseItemError, ParseStringError};
use crate::Item;
pub use array::Array;
pub use statement::Statement;
@ -124,10 +124,23 @@ impl Entry {
/// Parsable types.
pub trait ParseItem: Sized {
/// Try to cast the entry into a concrete type
fn from_entry(entry: Entry) -> Result<Self, ParseEntryError>;
fn from_entry(entry: Entry) -> Result<Self, ParseEntryError> {
let string = match entry.as_str() {
Some(string) => string,
None => {
return Err(ParseEntryError::new(type_name::<Self>(), entry));
}
};
Self::from_str(string).map_err(|e| ParseEntryError::new(e.ty, entry))
}
/// Try to cast the item into a concrete type
fn from_item(item: Item) -> Result<Self, ParseItemError>;
fn from_item(item: Item) -> Result<Self, ParseItemError> {
Self::from_str(item.as_str()).map_err(|e| ParseItemError::new(e.ty, item))
}
/// Try to cast the string into a concrete type
fn from_str(item: &str) -> Result<Self, ParseStringError>;
}
macro_rules! from_str {
@ -153,6 +166,10 @@ macro_rules! from_str {
fn from_item(item: Item) -> Result<Self, ParseItemError> {
item.as_str().parse::<$ty>().map_err(|_| ParseItemError::new(type_name::<Self>(), item))
}
fn from_str(item: &str) -> Result<Self, ParseStringError> {
item.parse::<$ty>().map_err(|_| ParseStringError::new(type_name::<Self>(), item))
}
}
);
}
@ -163,29 +180,13 @@ from_str!(for IpAddr Ipv4Addr Ipv6Addr SocketAddr SocketAddrV4 SocketAddrV6);
from_str!(for i8 i16 i32 i64 isize u8 u16 u32 u64 usize f32 f64);
impl ParseItem for bool {
fn from_entry(entry: Entry) -> Result<Self, ParseEntryError> {
let string = match entry.as_str() {
Some(string) => string,
None => {
return Err(ParseEntryError::new(type_name::<Self>(), entry));
}
};
match string {
fn from_str(item: &str) -> Result<Self, ParseStringError> {
match item {
"0" => Ok(false),
"1" => Ok(true),
v => v
.parse::<bool>()
.map_err(|_| ParseEntryError::new(type_name::<Self>(), entry)),
}
}
fn from_item(item: Item) -> Result<Self, ParseItemError> {
match item.as_str() {
"0" => Ok(false),
"1" => Ok(true),
v => v
.parse::<bool>()
.map_err(|_| ParseItemError::new(type_name::<Self>(), item)),
.map_err(|_| ParseStringError::new(type_name::<Self>(), item)),
}
}
}
@ -209,6 +210,10 @@ impl ParseItem for String {
fn from_item(item: Item) -> Result<Self, ParseItemError> {
Ok(item.into_content().into())
}
fn from_str(item: &str) -> Result<Self, ParseStringError> {
Ok(item.into())
}
}
impl<T: ParseItem> ParseItem for Option<T> {
@ -219,4 +224,8 @@ impl<T: ParseItem> ParseItem for Option<T> {
fn from_item(item: Item) -> Result<Self, ParseItemError> {
T::from_item(item).map(Some)
}
fn from_str(item: &str) -> Result<Self, ParseStringError> {
T::from_str(item).map(Some)
}
}

View file

@ -1,5 +1,7 @@
use crate::entry::Entry;
use crate::tokenizer::SpannedToken;
use crate::{Event, Item, Token};
use logos::Span;
use miette::{Diagnostic, SourceSpan};
use std::error::Error;
use std::fmt::{Display, Formatter};
@ -28,6 +30,12 @@ pub enum VdfError {
#[diagnostic(transparent)]
/// Failed to parse item into type
ParseItem(#[from] ParseItemError),
#[error(transparent)]
#[diagnostic(transparent)]
/// Failed to parse string into type
ParseString(#[from] ParseStringError),
#[error("{0}")]
Other(String),
}
struct ExpectedTokens<'a>(&'a [Token]);
@ -193,3 +201,82 @@ impl ParseItemError {
}
}
}
#[derive(Debug, Clone, Error, Diagnostic)]
#[error("Can't parse entry {value:?} as {ty}")]
#[diagnostic(code(vmt_parser::eof))]
pub struct ParseStringError {
pub ty: &'static str,
pub value: String,
}
impl ParseStringError {
pub fn new(ty: &'static str, value: &str) -> Self {
ParseStringError {
ty,
value: value.into(),
}
}
}
pub trait ExpectToken<'source> {
fn expect_token(
self,
expected: &'static [Token],
source: &'source str,
) -> Result<SpannedToken, VdfError>;
}
impl<'source, T: ExpectToken<'source>> ExpectToken<'source> for Option<T> {
fn expect_token(
self,
expected: &'static [Token],
source: &'source str,
) -> Result<SpannedToken, VdfError> {
self.ok_or_else(|| {
NoValidTokenError::new(expected, (source.len()..source.len()).into(), source.into())
.into()
})
.and_then(|token| token.expect_token(expected, source))
}
}
impl<'source> ExpectToken<'source> for Result<SpannedToken, Span> {
fn expect_token(
self,
expected: &'static [Token],
source: &'source str,
) -> Result<SpannedToken, VdfError> {
self.map_err(|span| NoValidTokenError::new(expected, span.into(), source.into()).into())
.and_then(|token| token.expect_token(expected, source))
}
}
impl<'source> ExpectToken<'source> for SpannedToken {
fn expect_token(
self,
expected: &'static [Token],
source: &'source str,
) -> Result<SpannedToken, VdfError> {
if expected.iter().any(|expect| self.token.eq(expect)) {
Ok(self)
} else {
Err(UnexpectedTokenError::new(
expected,
Some(self.token),
self.span.into(),
source.into(),
)
.into())
}
}
}
impl serde::de::Error for VdfError {
fn custom<T>(msg: T) -> Self
where
T: Display,
{
VdfError::Other(msg.to_string())
}
}

View file

@ -4,13 +4,9 @@ use std::str;
/// Parser token.
#[derive(PartialEq, Debug, Logos, Display, Clone)]
#[logos(skip r"[ \t\f\r]+")] // whitespace
#[logos(skip r"[ \t\f\r\n]+")] // whitespace
#[logos(skip r"//[^\n]*")] // comments
pub enum Token {
/// Newline
#[regex("\n([ \t\r]\n)*")]
#[display("newline")]
NewLine,
/// A group is starting.
#[token("{")]
#[display("start of group")]
@ -20,7 +16,7 @@ pub enum Token {
#[display("end of group")]
GroupEnd,
/// An enclosed or bare item.
#[regex("[^# \t\n{}\"][^ \t\n]*", priority = 0)]
#[regex("[^# \t\n{}\"][^ \t\n{}]*", priority = 0)]
#[display("item")]
Item,
/// An enclosed or bare item.
@ -28,7 +24,7 @@ pub enum Token {
#[display("quoted item")]
QuotedItem,
/// An enclosed or bare statement.
#[regex("#[^ \"\t\n]+")]
#[regex("#[^ \"\t\n{}]+")]
#[display("statement")]
Statement,
/// An enclosed or bare statement.
@ -51,7 +47,6 @@ mod tests {
Token::lexer(input)
.spanned()
.map(|(res, span)| res.map(|token| (token, &input[span])))
// .map(|res| dbg!(res))
.collect()
}
@ -102,20 +97,14 @@ mod tests {
Ok(vec![
(Token::Item, "foo"),
(Token::GroupStart, "{"),
(Token::NewLine, "\n"),
(Token::QuotedItem, r#""asd""#),
(Token::QuotedItem, r#""bar""#),
(Token::NewLine, "\n"),
(Token::NewLine, "\n"),
(Token::Statement, r#"#include"#),
(Token::Item, r#"other"#),
(Token::NewLine, "\n"),
(Token::Item, r#"empty"#),
(Token::QuotedItem, r#""""#),
(Token::NewLine, "\n"),
(Token::Item, r#"\\"broken""#),
(Token::Item, r#"comment"#),
(Token::NewLine, "\n"),
(Token::GroupEnd, "}")
])
)

View file

@ -1,12 +1,15 @@
pub mod entry;
pub mod error;
mod event;
mod parser;
mod lexer;
mod reader;
mod serde;
mod tokenizer;
pub use error::VdfError;
pub type Result<T, E = VdfError> = std::result::Result<T, E>;
pub use event::{EntryEvent, Event, GroupEndEvent, GroupStartEvent, Item};
pub use parser::Token;
pub use lexer::Token;
pub use reader::Reader;
pub use serde::from_str;

View file

@ -41,19 +41,6 @@ impl<'a> Reader<'a> {
self.lexer.span()
}
fn token_eat_newlines(&mut self) -> Option<(Result<Token, <Token as Logos>::Error>, Span)> {
loop {
let (token, span) = self.token()?;
match token {
Err(e) => return Some((Err(e), span)),
Ok(Token::NewLine) => {
continue;
}
Ok(token) => return Some((Ok(token), span)),
}
}
}
/// Get the next event, this does copies.
#[allow(dead_code)]
pub fn event(&mut self) -> Option<Result<Event<'a>>> {
@ -65,7 +52,7 @@ impl<'a> Reader<'a> {
Token::QuotedStatement,
];
let key = match self.token_eat_newlines() {
let key = match self.token() {
None => {
return None;
}
@ -112,46 +99,13 @@ impl<'a> Reader<'a> {
}
};
const VALID_VALUE: &[Token] = &[Token::Item, Token::QuotedItem, Token::GroupStart];
// only a group start is allowed to have newlines between the key and value
while matches!(self.peek(), Some((Ok(Token::NewLine), _))) {
let _newline = self.token();
if !matches!(
self.peek(),
Some((Ok(Token::GroupStart | Token::NewLine), _))
) {
let span = key.span().end..key.span().end;
match self.peeked.clone() {
Some((Ok(token), _)) => {
return Some(Err(UnexpectedTokenError::new(
&[Token::GroupStart],
Some(token),
span.into(),
self.source.into(),
)
.into()))
}
Some((Err(_), _)) => {
return Some(Err(NoValidTokenError::new(
VALID_VALUE,
span.into(),
self.source.into(),
)
.into()));
}
None => {
return Some(Err(UnexpectedTokenError::new(
VALID_VALUE,
None,
span.into(),
self.source.into(),
)
.into()))
}
}
}
}
const VALID_VALUE: &[Token] = &[
Token::Item,
Token::QuotedItem,
Token::GroupStart,
Token::Statement,
Token::QuotedStatement,
];
let value = match self.token() {
None => {
@ -224,11 +178,9 @@ impl<'a> Iterator for Reader<'a> {
}
}
fn quoted_string(source: &str) -> Cow<str> {
string(&source[1..source.len() - 1])
}
pub(crate) fn quoted_string(source: &str) -> Cow<str> {
let source = &source[1..source.len() - 1];
fn string(source: &str) -> Cow<str> {
if source.contains(r#"\""#) || source.contains(r#"\\"#) {
let mut buffer = source.bytes();
let mut string = Vec::with_capacity(buffer.len());
@ -251,3 +203,7 @@ fn string(source: &str) -> Cow<str> {
source.into()
}
}
fn string(source: &str) -> Cow<str> {
source.into()
}

644
src/serde.rs Normal file
View file

@ -0,0 +1,644 @@
use crate::entry::ParseItem;
use crate::error::{ExpectToken, NoValidTokenError, ParseStringError};
use crate::tokenizer::{SpannedToken, Tokenizer};
use crate::{Token, VdfError};
use logos::Span;
use serde::de::{self, DeserializeSeed, EnumAccess, MapAccess, SeqAccess, VariantAccess, Visitor};
use serde::Deserialize;
use std::borrow::Cow;
type Result<T, E = VdfError> = std::result::Result<T, E>;
pub struct Deserializer<'de> {
tokenizer: Tokenizer<'de>,
peeked: Option<Result<SpannedToken, Span>>,
last_key: Cow<'de, str>,
}
const STRING_ITEMS: &[Token] = &[
Token::Item,
Token::QuotedItem,
Token::Statement,
Token::QuotedStatement,
];
impl<'de> Deserializer<'de> {
pub fn from_str(input: &'de str) -> Self {
Deserializer {
tokenizer: Tokenizer::from_str(input),
peeked: None,
last_key: "".into(),
}
}
pub fn source(&self) -> &'de str {
self.tokenizer.source()
}
pub fn next(&mut self) -> Option<Result<SpannedToken, Span>> {
self.peeked.take().or_else(|| self.tokenizer.next())
}
pub fn peek(&mut self) -> Option<Result<SpannedToken, Span>> {
if self.peeked.is_none() {
self.peeked = self.tokenizer.next();
}
self.peeked.clone()
}
pub fn push_peeked(&mut self, token: SpannedToken) {
self.peeked = Some(Ok(token))
}
fn read_str(&mut self) -> Result<Cow<'de, str>> {
let token = self.next().expect_token(STRING_ITEMS, self.source())?;
Ok(token.string(self.source()))
}
fn parse<T: ParseItem>(&mut self) -> Result<T> {
T::from_str(self.read_str()?.as_ref()).map_err(VdfError::from)
}
fn set_last_key(&mut self, key: Cow<'de, str>) {
self.last_key = key;
}
}
pub fn from_str<'a, T>(s: &'a str) -> Result<T>
where
T: Deserialize<'a>,
{
let mut deserializer = Deserializer::from_str(s);
T::deserialize(&mut deserializer)
}
const VALUE_TOKEN: &[Token] = &[
Token::Item,
Token::QuotedItem,
Token::Statement,
Token::QuotedStatement,
Token::GroupStart,
];
impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
type Error = VdfError;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let source = self.source();
let peek = self.peek().expect_token(VALUE_TOKEN, source)?;
match peek.token {
Token::Item | Token::QuotedItem | Token::Statement | Token::QuotedStatement => {
self.deserialize_str(visitor)
}
Token::GroupStart => self.deserialize_map(visitor),
_ => unreachable!(),
}
}
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_bool(self.parse()?)
}
// The `parse_signed` function is generic over the integer type `T` so here
// it is invoked with `T=i8`. The next 8 methods are similar.
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_i8(self.parse()?)
}
fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_i16(self.parse()?)
}
fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_i32(self.parse()?)
}
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_i64(self.parse()?)
}
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_u8(self.parse()?)
}
fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_u16(self.parse()?)
}
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_u32(self.parse()?)
}
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_u64(self.parse()?)
}
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_f32(self.parse()?)
}
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_f64(self.parse()?)
}
// The `Serializer` implementation on the previous page serialized chars as
// single-character strings so handle that representation here.
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let str = self.read_str()?;
let mut chars = str.chars();
match (chars.next(), chars.next()) {
(Some(_), None) => Ok(()),
_ => Err(ParseStringError::new("char", str.as_ref())),
}?;
visitor.visit_str(str.as_ref())
}
// Refer to the "Understanding deserializer lifetimes" page for information
// about the three deserialization flavors of strings in Serde.
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_str(self.read_str()?.as_ref())
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_string(self.read_str()?.into())
}
// The `Serializer` implementation on the previous page serialized byte
// arrays as JSON arrays of bytes. Handle that representation here.
fn deserialize_bytes<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
todo!()
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_byte_buf(self.read_str()?.as_bytes().into())
}
// An absent optional is represented as the JSON `null` and a present
// optional is represented as just the contained value.
//
// As commented in `Serializer` implementation, this is a lossy
// representation. For example the values `Some(())` and `None` both
// serialize as just `null`. Unfortunately this is typically what people
// expect when working with JSON. Other formats are encouraged to behave
// more intelligently if possible.
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let token = match self.next() {
None => return visitor.visit_none(),
Some(Err(span)) => {
return Err(
NoValidTokenError::new(VALUE_TOKEN, span.into(), self.source().into()).into(),
)
}
Some(Ok(token)) => token,
};
if token.span.len() == 0 {
return visitor.visit_none();
}
self.push_peeked(token);
visitor.visit_some(self)
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let str = self.read_str()?;
if !str.is_empty() {
return Err(ParseStringError::new("unit", str.as_ref()).into());
}
visitor.visit_unit()
}
fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let str = self.read_str()?;
if !str.is_empty() {
return Err(ParseStringError::new("unit", str.as_ref()).into());
}
visitor.visit_unit()
}
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>(mut self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let key = self.last_key.clone();
let value = visitor.visit_seq(SeqWalker::new(&mut self, key))?;
Ok(value)
}
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>(mut self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.next()
.expect_token(&[Token::GroupStart], self.source())?;
let value = visitor.visit_map(TableWalker::new(&mut self))?;
Ok(value)
}
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>,
{
visitor.visit_enum(Enum::new(self))
}
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_any(visitor)
}
}
struct TableWalker<'source, 'a> {
de: &'a mut Deserializer<'source>,
done: bool,
}
impl<'source, 'a> TableWalker<'source, 'a> {
pub fn new(de: &'a mut Deserializer<'source>) -> Self {
TableWalker { de, done: false }
}
fn source(&self) -> &'source str {
self.de.source()
}
fn key_token(&mut self) -> Result<Option<SpannedToken>> {
if self.done {
return Ok(None);
}
let key = self.de.next().expect_token(
&[
Token::Item,
Token::QuotedItem,
Token::Statement,
Token::QuotedStatement,
Token::GroupEnd,
],
self.source(),
)?;
if key.token == Token::GroupEnd {
self.done = true;
return Ok(None);
}
Ok(Some(key))
}
}
impl<'de, 'a> MapAccess<'de> for TableWalker<'de, 'a> {
type Error = VdfError;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
where
K: DeserializeSeed<'de>,
{
let key = match self.key_token() {
Ok(Some(key)) => key,
Ok(None) => {
return Ok(None);
}
Err(e) => return Err(e),
};
self.de.set_last_key(key.string(self.source()));
self.de.push_peeked(key);
seed.deserialize(&mut *self.de).map(Some)
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
where
V: DeserializeSeed<'de>,
{
seed.deserialize(&mut *self.de)
}
}
struct SeqWalker<'source, 'a> {
table: TableWalker<'source, 'a>,
key: Cow<'source, str>,
done: bool,
}
impl<'source, 'a> SeqWalker<'source, 'a> {
pub fn new(de: &'a mut Deserializer<'source>, key: Cow<'source, str>) -> Self {
SeqWalker {
done: false,
key,
table: TableWalker::new(de),
}
}
fn source(&self) -> &'source str {
self.table.source()
}
}
impl<'de, 'a> SeqAccess<'de> for SeqWalker<'de, 'a> {
type Error = VdfError;
fn next_element_seed<T>(
&mut self,
seed: T,
) -> std::result::Result<Option<T::Value>, Self::Error>
where
T: DeserializeSeed<'de>,
{
if self.done {
return Ok(None);
}
let value = seed.deserialize(&mut *self.table.de).map(Some)?;
let key_token = match self.table.key_token() {
Ok(Some(key)) => key,
Ok(None) => {
return Ok(None);
}
Err(e) => return Err(e),
};
let key = key_token.string(self.source());
if key != self.key {
self.table.de.push_peeked(key_token);
self.done = true;
}
Ok(value)
}
}
struct Enum<'a, 'de: 'a> {
de: &'a mut Deserializer<'de>,
}
impl<'a, 'de> Enum<'a, 'de> {
fn new(de: &'a mut Deserializer<'de>) -> Self {
Enum { de }
}
fn source(&self) -> &'de str {
self.de.source()
}
}
// `EnumAccess` is provided to the `Visitor` to give it the ability to determine
// which variant of the enum is supposed to be deserialized.
//
// Note that all enum deserialization methods in Serde refer exclusively to the
// "externally tagged" enum representation.
impl<'de, 'a> EnumAccess<'de> for Enum<'a, 'de> {
type Error = VdfError;
type Variant = Self;
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
where
V: DeserializeSeed<'de>,
{
let val = seed.deserialize(&mut *self.de)?;
Ok((val, self))
}
}
// `VariantAccess` is provided to the `Visitor` to give it the ability to see
// the content of the single variant that it decided to deserialize.
impl<'de, 'a> VariantAccess<'de> for Enum<'a, 'de> {
type Error = VdfError;
fn unit_variant(self) -> Result<()> {
let str = self.de.read_str()?;
if !str.is_empty() {
return Err(ParseStringError::new("unit", str.as_ref()).into());
}
Ok(())
}
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
where
T: DeserializeSeed<'de>,
{
seed.deserialize(self.de)
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
de::Deserializer::deserialize_seq(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(self.de, visitor)
}
}
////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use crate::VdfError;
use serde::Deserialize;
fn unwrap_err<T>(r: Result<T, VdfError>) -> T {
r.map_err(miette::Error::from).unwrap()
}
fn from_str<'a, T>(source: &'a str) -> super::Result<T>
where
T: serde::Deserialize<'a>,
{
match super::from_str(source) {
Ok(res) => Ok(res),
Err(err) => {
eprintln!("{}", err);
Err(err)
}
}
}
#[test]
fn test_struct() {
#[derive(Deserialize, PartialEq, Debug)]
struct Test {
int: u32,
seq: String,
}
let j = r#"{"int" 1 "seq" "b"}"#;
let expected = Test {
int: 1,
seq: "b".into(),
};
assert_eq!(expected, unwrap_err(from_str(j)));
}
#[test]
fn test_struct_nested() {
#[derive(Deserialize, PartialEq, Debug)]
struct Inner {
a: f32,
b: bool,
}
#[derive(Deserialize, PartialEq, Debug)]
struct Test {
int: u32,
nested: Inner,
}
let j = r#"{"int" 1 "nested" {"a" 1.0 "b" false}}"#;
let expected = Test {
int: 1,
nested: Inner { a: 1.0, b: false },
};
assert_eq!(expected, unwrap_err(from_str(j)));
}
#[test]
fn test_enum() {
#[derive(Deserialize, PartialEq, Debug)]
enum E {
Unit,
Newtype1(u32),
Newtype2(u32),
Struct { a: u32 },
Struct2 { a: u32 },
}
let j = r#""Unit" """#;
let expected = E::Unit;
assert_eq!(expected, unwrap_err(from_str(j)));
let j = r#""Newtype1" 1"#;
let expected = E::Newtype1(1);
assert_eq!(expected, unwrap_err(from_str(j)));
let j = r#"Newtype2 1"#;
let expected = E::Newtype2(1);
assert_eq!(expected, unwrap_err(from_str(j)));
let j = r#"Struct {"a" 1}"#;
let expected = E::Struct { a: 1 };
assert_eq!(expected, unwrap_err(from_str(j)));
let j = r#"Struct2 {"a" 1}"#;
let expected = E::Struct2 { a: 1 };
assert_eq!(expected, unwrap_err(from_str(j)));
}
}

56
src/tokenizer.rs Normal file
View file

@ -0,0 +1,56 @@
use crate::reader::quoted_string;
use crate::Token;
use logos::{Lexer, Span};
use std::borrow::Cow;
#[derive(Clone, Debug)]
pub struct SpannedToken {
pub token: Token,
pub span: Span,
}
impl SpannedToken {
pub fn string<'source>(&self, source: &'source str) -> Cow<'source, str> {
let full = &source[self.span.clone()];
match self.token {
Token::QuotedItem | Token::QuotedStatement => quoted_string(full),
_ => full.into(),
}
}
}
pub struct Tokenizer<'source> {
lexer: Lexer<'source, Token>,
}
impl<'source> Tokenizer<'source> {
pub fn from_str(input: &'source str) -> Self {
Tokenizer {
lexer: Lexer::new(input),
}
}
pub fn source(&self) -> &'source str {
self.lexer.source()
}
}
impl<'source> Iterator for Tokenizer<'source> {
type Item = Result<SpannedToken, Span>;
fn next(&mut self) -> Option<Self::Item> {
let token = match self.lexer.next() {
Some(Ok(token)) => token,
Some(Err(_)) => {
return Some(Err(self.lexer.span()));
}
None => {
return None;
}
};
Some(Ok(SpannedToken {
token,
span: self.lexer.span(),
}))
}
}

View file

@ -1,5 +1,3 @@
"#base" "panelBase.res"
"Resource/specificPanel.res"
{
// Specify panel-specific controls here

158
tests/serde.rs Normal file
View file

@ -0,0 +1,158 @@
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::fs::read_to_string;
use test_case::test_case;
use vdf_reader::from_str;
#[derive(Debug, Serialize, Deserialize)]
enum Expected {
LightmappedGeneric {
#[serde(rename = "$baseTexture")]
base_texture: String,
#[serde(rename = "$bumpmap")]
bumpmap: String,
#[serde(rename = "$ssbump")]
ssbump: bool,
#[serde(rename = "%keywords")]
keywords: String,
#[serde(rename = "$detail")]
detail: String,
#[serde(rename = "$detailscale")]
detailscale: f32,
#[serde(rename = "$detailblendmode")]
detailblendmode: i32,
#[serde(rename = "$detailblendfactor")]
detailblendfactor: f32,
},
#[serde(rename = "Resource/specificPanel.res")]
Messy {
empty: (),
array: Vec<u32>,
windows_path: String,
#[serde(rename = r#"\\"$translucent""#)]
translucent: bool,
#[serde(rename = "$envmaptint")]
env_map_tint: f32,
#[serde(rename = ".5")]
spare: f32,
},
UserConfigData {
#[serde(rename = "Steam")]
steam: UserConfigDataSteam,
#[serde(rename = "FriendsMainDialog")]
friends_main_dialog: UserConfigDataFriendsMainDialog,
#[serde(rename = "Servers")]
servers: UserConfigDataServers,
},
}
#[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataSteam {
cached: UserConfigDataSteamCached,
}
#[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataSteamCached {
#[serde(rename = "OverlaySplash.res")]
overlay_splash: BTreeMap<String, String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataFriendsMainDialog {
xpos: u32,
ypos: u32,
wide: u16,
tall: u16,
#[serde(rename = "FriendPanelSelf")]
friends_panel_self: BTreeMap<String, String>,
#[serde(rename = "FriendsDialogSheet")]
friends_dialog_sheet: UserConfigDataFriendsMainDialogFriendsDialogSheet,
#[serde(rename = "FriendsState")]
friends_state: BTreeMap<String, u8>,
}
#[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataFriendsMainDialogFriendsDialogSheet {
#[serde(rename = "FriendsFriendsPage")]
friends_friends_page: UserConfigDataFriendsMainDialogFriendsDialogSheetFriendsPage,
#[serde(rename = "FriendsClansPage")]
friends_clan_page: UserConfigDataFriendsMainDialogFriendsDialogSheetFriendsPage,
}
#[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataFriendsMainDialogFriendsDialogSheetFriendsPage {
#[serde(rename = "BuddyList")]
buddy_list: BTreeMap<String, bool>,
}
#[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataServers {
#[serde(rename = "DialogServerBrowser.res")]
dialog_server_browser: UserConfigDataServersDialog,
}
#[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataServersDialog {
xpos: u32,
ypos: u32,
wide: u16,
tall: u16,
#[serde(rename = "GameTabs")]
game_tabs: UserConfigDataServersDialogGameTabs,
}
#[derive(Debug, Serialize, Deserialize)]
struct UserConfigDataServersDialogGameTabs {
#[serde(rename = "InternetGames")]
internet_games: GameListHaver,
#[serde(rename = "FavoriteGames")]
favorite_games: GameListHaver,
#[serde(rename = "HistoryGames")]
history_games: GameListHaver,
#[serde(rename = "SpectateGames")]
spectate_games: GameListHaver,
#[serde(rename = "LanGames")]
lan_games: GameListHaver,
#[serde(rename = "FriendsGames")]
friends_games: GameListHaver,
}
#[derive(Debug, Serialize, Deserialize)]
struct GameListHaver {
gamelist: GameList,
}
#[derive(Debug, Serialize, Deserialize)]
struct GameList {
#[serde(rename = "#ServerBrowser_Password_hidden")]
server_browser_password_hidden: bool,
#[serde(rename = "#ServerBrowser_Bots_hidden")]
server_browser_bots_hidden: bool,
#[serde(rename = "#ServerBrowser_Secure_hidden")]
server_browser_secure_hidden: bool,
#[serde(rename = "#ServerBrowser_Servers_hidden")]
server_browser_servers_hidden: bool,
#[serde(rename = "#ServerBrowser_IPAddress_hidden")]
server_browser_ip_address_hidden: bool,
#[serde(rename = "#ServerBrowser_Game_hidden")]
server_browser_game_hidden: bool,
#[serde(rename = "#ServerBrowser_Players_hidden")]
server_browser_players_hidden: bool,
#[serde(rename = "#ServerBrowser_Map_hidden")]
server_browser_map_hidden: bool,
#[serde(rename = "#ServerBrowser_Latency_hidden")]
server_browser_latency_hidden: bool,
sort_column: String,
sort_column_secondary: Option<String>,
sort_column_asc: bool,
sort_column_secondary_asc: bool,
}
#[test_case("tests/data/concrete.vmt")]
#[test_case("tests/data/messy.vdf")]
#[test_case("tests/data/DialogConfigOverlay_1280x720.vdf")]
fn test_serde(path: &str) {
let raw = read_to_string(path).unwrap();
let result: Expected = from_str(&raw).map_err(miette::Error::from).unwrap();
insta::assert_ron_snapshot!(path, result);
}

View file

@ -3,11 +3,10 @@ source: tests/parse.rs
expression: parsed
---
Table({
"#base": Statement(Statement("panelBase.res")),
"Resource/specificPanel.res": Table(Table({
"$envmaptint": Value(Value(".5")),
".5": Value(Value(".5")),
"\\\"$translucent\"": Value(Value("1")),
"\\\\\"$translucent\"": Value(Value("1")),
"array": Array(Array([
Value(Value("1")),
Value(Value("2")),

View file

@ -0,0 +1,152 @@
---
source: tests/serde.rs
expression: result
---
UserConfigData(
Steam: UserConfigDataSteam(
cached: UserConfigDataSteamCached(
r#OverlaySplash.res: {},
),
),
FriendsMainDialog: UserConfigDataFriendsMainDialog(
xpos: 1028,
ypos: 280,
wide: 252,
tall: 440,
FriendPanelSelf: {},
FriendsDialogSheet: UserConfigDataFriendsMainDialogFriendsDialogSheet(
FriendsFriendsPage: UserConfigDataFriendsMainDialogFriendsDialogSheetFriendsPage(
BuddyList: {
"0_collapsed": false,
"1_collapsed": false,
"2_collapsed": false,
"3_collapsed": false,
"4_collapsed": false,
"5_collapsed": false,
"allfriends_collapsed": false,
},
),
FriendsClansPage: UserConfigDataFriendsMainDialogFriendsDialogSheetFriendsPage(
BuddyList: {
"0_collapsed": false,
"1_collapsed": false,
"2_collapsed": false,
"3_collapsed": false,
},
),
),
FriendsState: {},
),
Servers: UserConfigDataServers(
r#DialogServerBrowser.res: UserConfigDataServersDialog(
xpos: 0,
ypos: 0,
wide: 1280,
tall: 720,
GameTabs: UserConfigDataServersDialogGameTabs(
InternetGames: GameListHaver(
gamelist: GameList(
r##ServerBrowser_Password_hidden: false,
r##ServerBrowser_Bots_hidden: true,
r##ServerBrowser_Secure_hidden: false,
r##ServerBrowser_Servers_hidden: false,
r##ServerBrowser_IPAddress_hidden: true,
r##ServerBrowser_Game_hidden: false,
r##ServerBrowser_Players_hidden: false,
r##ServerBrowser_Map_hidden: false,
r##ServerBrowser_Latency_hidden: false,
sort_column: "#ServerBrowser_Latency",
sort_column_secondary: Some(""),
sort_column_asc: true,
sort_column_secondary_asc: true,
),
),
FavoriteGames: GameListHaver(
gamelist: GameList(
r##ServerBrowser_Password_hidden: false,
r##ServerBrowser_Bots_hidden: true,
r##ServerBrowser_Secure_hidden: false,
r##ServerBrowser_Servers_hidden: false,
r##ServerBrowser_IPAddress_hidden: true,
r##ServerBrowser_Game_hidden: false,
r##ServerBrowser_Players_hidden: false,
r##ServerBrowser_Map_hidden: false,
r##ServerBrowser_Latency_hidden: false,
sort_column: "#ServerBrowser_Latency",
sort_column_secondary: Some(""),
sort_column_asc: true,
sort_column_secondary_asc: true,
),
),
HistoryGames: GameListHaver(
gamelist: GameList(
r##ServerBrowser_Password_hidden: false,
r##ServerBrowser_Bots_hidden: true,
r##ServerBrowser_Secure_hidden: false,
r##ServerBrowser_Servers_hidden: false,
r##ServerBrowser_IPAddress_hidden: true,
r##ServerBrowser_Game_hidden: false,
r##ServerBrowser_Players_hidden: false,
r##ServerBrowser_Map_hidden: false,
r##ServerBrowser_Latency_hidden: false,
sort_column: "#ServerBrowser_LastPlayed",
sort_column_secondary: Some("#ServerBrowser_Latency"),
sort_column_asc: true,
sort_column_secondary_asc: true,
),
),
SpectateGames: GameListHaver(
gamelist: GameList(
r##ServerBrowser_Password_hidden: false,
r##ServerBrowser_Bots_hidden: true,
r##ServerBrowser_Secure_hidden: false,
r##ServerBrowser_Servers_hidden: false,
r##ServerBrowser_IPAddress_hidden: true,
r##ServerBrowser_Game_hidden: false,
r##ServerBrowser_Players_hidden: false,
r##ServerBrowser_Map_hidden: false,
r##ServerBrowser_Latency_hidden: false,
sort_column: "#ServerBrowser_Latency",
sort_column_secondary: Some(""),
sort_column_asc: true,
sort_column_secondary_asc: true,
),
),
LanGames: GameListHaver(
gamelist: GameList(
r##ServerBrowser_Password_hidden: false,
r##ServerBrowser_Bots_hidden: true,
r##ServerBrowser_Secure_hidden: false,
r##ServerBrowser_Servers_hidden: false,
r##ServerBrowser_IPAddress_hidden: true,
r##ServerBrowser_Game_hidden: false,
r##ServerBrowser_Players_hidden: false,
r##ServerBrowser_Map_hidden: false,
r##ServerBrowser_Latency_hidden: false,
sort_column: "#ServerBrowser_Latency",
sort_column_secondary: Some(""),
sort_column_asc: true,
sort_column_secondary_asc: true,
),
),
FriendsGames: GameListHaver(
gamelist: GameList(
r##ServerBrowser_Password_hidden: false,
r##ServerBrowser_Bots_hidden: true,
r##ServerBrowser_Secure_hidden: false,
r##ServerBrowser_Servers_hidden: false,
r##ServerBrowser_IPAddress_hidden: true,
r##ServerBrowser_Game_hidden: false,
r##ServerBrowser_Players_hidden: false,
r##ServerBrowser_Map_hidden: false,
r##ServerBrowser_Latency_hidden: false,
sort_column: "#ServerBrowser_Latency",
sort_column_secondary: Some(""),
sort_column_asc: true,
sort_column_secondary_asc: true,
),
),
),
),
),
)

View file

@ -0,0 +1,14 @@
---
source: tests/serde.rs
expression: result
---
LightmappedGeneric(
r#$baseTexture: "cp_mountainlab/concrete/concretefloor003",
r#$bumpmap: "concrete/concretefloor007b_height-ssbump",
r#$ssbump: true,
r#%keywords: "tf",
r#$detail: "overlays/detail001",
r#$detailscale: 1.9,
r#$detailblendmode: 0,
r#$detailblendfactor: 1.0,
)

View file

@ -0,0 +1,16 @@
---
source: tests/serde.rs
expression: result
---
r#Resource/specificPanel.res(
empty: (),
array: [
1,
2,
3,
],
windows_path: "C:\\test\\no newline",
r#\\"$translucent": true,
r#$envmaptint: 0.5,
r#.5: 0.5,
)