make bare sequences worth with Entry

This commit is contained in:
Robin Appelman 2025-03-01 15:32:22 +01:00
commit d29d06166c
10 changed files with 145 additions and 24 deletions

View file

@ -10,6 +10,7 @@ pub use statement::Statement;
use std::any::type_name; use std::any::type_name;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::mem::swap;
use std::slice; use std::slice;
pub use table::Table; pub use table::Table;
pub use value::Value; pub use value::Value;
@ -116,9 +117,7 @@ impl Entry {
pub fn as_str(&self) -> Option<&str> { pub fn as_str(&self) -> Option<&str> {
match self { match self {
Entry::Value(value) => Some(value), Entry::Value(value) => Some(value),
Entry::Statement(value) => Some(value), Entry::Statement(value) => Some(value),
_ => None, _ => None,
} }
} }
@ -136,6 +135,32 @@ impl Entry {
Ok(result) Ok(result)
} }
} }
fn into_array(self) -> Result<Self, ParseEntryError> {
match self {
Entry::Array(array) => Ok(Entry::Array(array)),
Entry::Value(value) => {
let mut array = Array::default();
array.push(value.into());
Ok(Entry::Array(array))
}
entry => Err(ParseEntryError::new("array", entry)),
}
}
/// Try to take the entry as a slice.
pub fn push(&mut self, value: Entry) -> Result<(), ParseEntryError> {
let mut tmp = Entry::Value(Value::default());
swap(self, &mut tmp);
*self = tmp.into_array()?;
if let Entry::Array(array) = self {
array.push(value);
} else {
panic!("into_array ensured this is an array")
}
Ok(())
}
} }
/// Parsable types. /// Parsable types.

View file

@ -1,7 +1,7 @@
use super::{Array, Entry}; use super::{Array, Entry};
use crate::entry::{string_is_array, Statement, Value}; use crate::entry::{string_is_array, Statement, Value};
use crate::error::UnknownError; use crate::error::UnknownError;
use crate::event::{EntryEvent, GroupStartEvent}; use crate::event::{EntryEvent, GroupStartEvent, ValueContinuationEvent};
use crate::{Event, Item, Reader, Result, VdfError}; use crate::{Event, Item, Reader, Result, VdfError};
use serde::de::{DeserializeSeed, MapAccess}; use serde::de::{DeserializeSeed, MapAccess};
use serde::{Deserialize, Serialize, Serializer}; use serde::{Deserialize, Serialize, Serializer};
@ -63,15 +63,17 @@ impl Table {
/// Load a table from the given `Reader`. /// Load a table from the given `Reader`.
pub fn load(reader: &mut Reader) -> Result<Table> { pub fn load(reader: &mut Reader) -> Result<Table> {
let mut map = HashMap::new(); let mut map = HashMap::new();
let mut last_key = None;
while let Some(event) = reader.event() { while let Some(event) = reader.event() {
match event? { last_key = match event? {
Event::Entry(EntryEvent { Event::Entry(EntryEvent {
key: Item::Item { content: key, .. }, key: Item::Item { content: key, .. },
value, value,
.. ..
}) => { }) => {
let str = value.as_str(); let str = value.as_str();
let key_clone = key.clone();
if string_is_array(str) { if string_is_array(str) {
insert( insert(
&mut map, &mut map,
@ -81,16 +83,31 @@ impl Table {
} else { } else {
insert(&mut map, key, Value::from(value.into_content())) insert(&mut map, key, Value::from(value.into_content()))
} }
Some(key_clone)
} }
Event::Entry(EntryEvent { Event::Entry(EntryEvent {
key: Item::Statement { content: key, .. }, key: Item::Statement { content: key, .. },
value, value,
.. ..
}) => insert(&mut map, key, Statement::from(value.into_content())), }) => {
let key_clone = key.clone();
insert(&mut map, key, Statement::from(value.into_content()));
Some(key_clone)
}
Event::ValueContinuation(ValueContinuationEvent { value, .. }) => {
if let Some(key) = last_key.as_ref() {
if let Some(last_value) = map.get_mut(key.as_ref()) {
last_value.push(Value::from(value.into_content()).into())?;
}
}
last_key
}
Event::GroupStart(GroupStartEvent { name, .. }) => { Event::GroupStart(GroupStartEvent { name, .. }) => {
insert(&mut map, name, Table::load(reader)?) insert(&mut map, name, Table::load(reader)?);
None
} }
Event::GroupEnd(_) => break, Event::GroupEnd(_) => break,

View file

@ -82,6 +82,13 @@ impl<'de> Deserialize<'de> for Value {
write!(formatter, "any string like value") write!(formatter, "any string like value")
} }
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: Error,
{
Ok(if v { "1".into() } else { "0".into() })
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where where
E: Error, E: Error,
@ -116,13 +123,6 @@ impl<'de> Deserialize<'de> for Value {
{ {
Ok(v) Ok(v)
} }
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: Error,
{
Ok(if v { "1".into() } else { "0".into() })
}
} }
deserializer.deserialize_str(ValueVisitor).map(Value) deserializer.deserialize_str(ValueVisitor).map(Value)

View file

@ -21,7 +21,7 @@ pub enum VdfError {
#[error(transparent)] #[error(transparent)]
#[diagnostic(transparent)] #[diagnostic(transparent)]
/// Wrong event to for conversion /// Wrong event to for conversion
WrongEntryType(Box<WrongEventTypeError>), WrongEventType(Box<WrongEventTypeError>),
#[error(transparent)] #[error(transparent)]
#[diagnostic(transparent)] #[diagnostic(transparent)]
/// Failed to parse entry into type /// Failed to parse entry into type
@ -49,7 +49,7 @@ pub enum VdfError {
impl From<WrongEventTypeError> for VdfError { impl From<WrongEventTypeError> for VdfError {
fn from(value: WrongEventTypeError) -> Self { fn from(value: WrongEventTypeError) -> Self {
Self::WrongEntryType(value.into()) Self::WrongEventType(value.into())
} }
} }
@ -59,7 +59,7 @@ impl VdfError {
VdfError::Other(e) => e.src.as_str(), VdfError::Other(e) => e.src.as_str(),
VdfError::UnexpectedToken(e) => e.src.as_str(), VdfError::UnexpectedToken(e) => e.src.as_str(),
VdfError::NoValidToken(e) => e.src.as_str(), VdfError::NoValidToken(e) => e.src.as_str(),
VdfError::WrongEntryType(e) => e.src.as_str(), VdfError::WrongEventType(e) => e.src.as_str(),
VdfError::SerdeParse(e) => e.src.as_str(), VdfError::SerdeParse(e) => e.src.as_str(),
VdfError::UnknownVariant(e) => e.src.as_str(), VdfError::UnknownVariant(e) => e.src.as_str(),
_ => { _ => {
@ -73,7 +73,7 @@ impl VdfError {
VdfError::Other(e) => e.err_span, VdfError::Other(e) => e.err_span,
VdfError::UnexpectedToken(e) => e.err_span, VdfError::UnexpectedToken(e) => e.err_span,
VdfError::NoValidToken(e) => e.err_span, VdfError::NoValidToken(e) => e.err_span,
VdfError::WrongEntryType(e) => e.err_span, VdfError::WrongEventType(e) => e.err_span,
VdfError::SerdeParse(e) => e.err_span, VdfError::SerdeParse(e) => e.err_span,
VdfError::UnknownVariant(e) => e.err_span, VdfError::UnknownVariant(e) => e.err_span,
_ => { _ => {
@ -118,7 +118,7 @@ impl VdfError {
..e ..e
} }
.into(), .into(),
VdfError::WrongEntryType(e) => WrongEventTypeError { VdfError::WrongEventType(e) => WrongEventTypeError {
src: source.into(), src: source.into(),
err_span: span.into(), err_span: span.into(),
..*e ..*e

View file

@ -60,6 +60,17 @@ pub enum Event<'a> {
/// An entry. /// An entry.
Entry(EntryEvent<'a>), Entry(EntryEvent<'a>),
/// An additional value for the previous entry.
ValueContinuation(ValueContinuationEvent<'a>),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EventType {
GroupStart,
GroupEnd,
Entry,
ValueContinuation,
} }
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
@ -87,6 +98,9 @@ impl<'a> TryFrom<Event<'a>> for GroupStartEvent<'a> {
Err(WrongEventTypeError::new(event, "group start", "group end").into()) Err(WrongEventTypeError::new(event, "group start", "group end").into())
} }
Event::Entry(_) => Err(WrongEventTypeError::new(event, "group start", "entry").into()), Event::Entry(_) => Err(WrongEventTypeError::new(event, "group start", "entry").into()),
Event::ValueContinuation(_) => {
Err(WrongEventTypeError::new(event, "group start", "value continuation").into())
}
} }
} }
} }
@ -106,6 +120,9 @@ impl<'a> TryFrom<Event<'a>> for GroupEndEvent {
Err(WrongEventTypeError::new(event, "group end", "group start").into()) Err(WrongEventTypeError::new(event, "group end", "group start").into())
} }
Event::Entry(_) => Err(WrongEventTypeError::new(event, "group start", "entry").into()), Event::Entry(_) => Err(WrongEventTypeError::new(event, "group start", "entry").into()),
Event::ValueContinuation(_) => {
Err(WrongEventTypeError::new(event, "group start", "value continuation").into())
}
} }
} }
} }
@ -137,6 +154,24 @@ impl<'a> TryFrom<Event<'a>> for EntryEvent<'a> {
Event::GroupStart(_) => { Event::GroupStart(_) => {
Err(WrongEventTypeError::new(event, "entry", "group start").into()) Err(WrongEventTypeError::new(event, "entry", "group start").into())
} }
Event::ValueContinuation(_) => {
Err(WrongEventTypeError::new(event, "entry", "value continuation").into())
}
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ValueContinuationEvent<'a> {
pub value: Item<'a>,
pub span: Span,
}
impl ValueContinuationEvent<'_> {
pub fn into_owned(self) -> ValueContinuationEvent<'static> {
ValueContinuationEvent {
value: self.value.into_owned(),
span: self.span,
} }
} }
} }
@ -148,6 +183,7 @@ impl Event<'_> {
Event::GroupStart(GroupStartEvent { span, .. }) => span.clone(), Event::GroupStart(GroupStartEvent { span, .. }) => span.clone(),
Event::GroupEnd(GroupEndEvent { span, .. }) => span.clone(), Event::GroupEnd(GroupEndEvent { span, .. }) => span.clone(),
Event::Entry(EntryEvent { span, .. }) => span.clone(), Event::Entry(EntryEvent { span, .. }) => span.clone(),
Event::ValueContinuation(ValueContinuationEvent { span, .. }) => span.clone(),
} }
} }
pub fn into_owned(self) -> Event<'static> { pub fn into_owned(self) -> Event<'static> {
@ -155,6 +191,16 @@ impl Event<'_> {
Event::GroupStart(event) => Event::GroupStart(event.into_owned()), Event::GroupStart(event) => Event::GroupStart(event.into_owned()),
Event::GroupEnd(event) => Event::GroupEnd(event), Event::GroupEnd(event) => Event::GroupEnd(event),
Event::Entry(event) => Event::Entry(event.into_owned()), Event::Entry(event) => Event::Entry(event.into_owned()),
Event::ValueContinuation(event) => Event::ValueContinuation(event.into_owned()),
}
}
pub fn ty(&self) -> EventType {
match self {
Event::GroupStart(GroupStartEvent { .. }) => EventType::GroupStart,
Event::GroupEnd(GroupEndEvent { .. }) => EventType::GroupEnd,
Event::Entry(EntryEvent { .. }) => EventType::Entry,
Event::ValueContinuation(ValueContinuationEvent { .. }) => EventType::ValueContinuation,
} }
} }
} }

View file

@ -1,12 +1,15 @@
use super::{Result, Token}; use super::{Result, Token};
use crate::error::{NoValidTokenError, UnexpectedTokenError}; use crate::error::{NoValidTokenError, UnexpectedTokenError};
use crate::event::{EntryEvent, Event, GroupEndEvent, GroupStartEvent, Item}; use crate::event::{
EntryEvent, Event, EventType, GroupEndEvent, GroupStartEvent, Item, ValueContinuationEvent,
};
use logos::{Lexer, Logos, Span, SpannedIter}; use logos::{Lexer, Logos, Span, SpannedIter};
use std::borrow::Cow; use std::borrow::Cow;
/// A VDF token reader. /// A VDF token reader.
pub struct Reader<'a> { pub struct Reader<'a> {
pub source: &'a str, pub source: &'a str,
pub last_event: Option<EventType>,
lexer: SpannedIter<'a, Token>, lexer: SpannedIter<'a, Token>,
} }
@ -14,6 +17,7 @@ impl<'a> From<&'a str> for Reader<'a> {
fn from(content: &'a str) -> Self { fn from(content: &'a str) -> Self {
Reader { Reader {
source: content, source: content,
last_event: None,
lexer: Lexer::new(content).spanned(), lexer: Lexer::new(content).spanned(),
} }
} }
@ -29,8 +33,16 @@ impl<'a> Reader<'a> {
} }
/// Get the next event, this does copies. /// Get the next event, this does copies.
#[allow(dead_code)]
pub fn event(&mut self) -> Option<Result<Event<'a>>> { pub fn event(&mut self) -> Option<Result<Event<'a>>> {
let result = self.event_inner();
if let Some(Ok(event)) = &result {
self.last_event = Some(event.ty());
}
result
}
#[allow(dead_code)]
fn event_inner(&mut self) -> Option<Result<Event<'a>>> {
const VALID_KEY: &[Token] = &[ const VALID_KEY: &[Token] = &[
Token::Item, Token::Item,
Token::QuotedItem, Token::QuotedItem,
@ -39,6 +51,8 @@ impl<'a> Reader<'a> {
Token::QuotedStatement, Token::QuotedStatement,
]; ];
let whitespace_start = self.span().end;
let key = match self.token() { let key = match self.token() {
None => { None => {
return None; return None;
@ -86,6 +100,21 @@ impl<'a> Reader<'a> {
} }
}; };
let whitespace_end = self.span().start;
let skipped_newline = self.source[whitespace_start..whitespace_end].contains('\n');
let last_event_has_value = matches!(
self.last_event,
Some(EventType::Entry | EventType::ValueContinuation)
);
// multiple values on the same line create an array
if last_event_has_value && !skipped_newline {
return Some(Ok(Event::ValueContinuation(ValueContinuationEvent {
value: key,
span: self.span(),
})));
}
const VALID_VALUE: &[Token] = &[ const VALID_VALUE: &[Token] = &[
Token::Item, Token::Item,
Token::QuotedItem, Token::QuotedItem,

View file

@ -250,6 +250,7 @@ fn test_serde_table(path: &str) {
fn test_serde_from_table(path: &str) { fn test_serde_from_table(path: &str) {
let raw = read_to_string(path).unwrap(); let raw = read_to_string(path).unwrap();
let result = Table::load_from_str(&raw).unwrap(); let result = Table::load_from_str(&raw).unwrap();
dbg!(&result);
let material: Expected = from_entry(result.into()).expect("table to material"); let material: Expected = from_entry(result.into()).expect("table to material");
insta::assert_ron_snapshot!(format!("table_to_material__{}", path), material); insta::assert_ron_snapshot!(format!("table_to_material__{}", path), material);

View file

@ -4,8 +4,11 @@ expression: parsed
--- ---
{ {
"Resource/specificPanel.res": { "Resource/specificPanel.res": {
"$envmaptint": ".5", "$envmaptint": [
".5": ".5", ".5",
".5",
".5",
],
"\\\\\"$translucent\"": "1", "\\\\\"$translucent\"": "1",
"array": [ "array": [
"1", "1",

View file

@ -11,6 +11,5 @@ r#Resource/specificPanel.res(
], ],
windows_path: "C:\\test\\no newline", windows_path: "C:\\test\\no newline",
r#\\"$translucent": true, r#\\"$translucent": true,
r#$envmaptint: 0.5, r#$envmaptint: (0.5, 0.5, 0.5),
r#.5: 0.5,
) )

View file

@ -12,4 +12,5 @@ Types(
single: 1.2, single: 1.2,
triple: (1.2, 1.3, 1.4), triple: (1.2, 1.3, 1.4),
single_int: 2.0, single_int: 2.0,
another_tuple: (8, "foo", false),
) )