mirror of
https://codeberg.org/icewind/vdf-reader.git
synced 2026-06-03 10:04:08 +02:00
better errors for keys without values
This commit is contained in:
parent
2f6fc3e180
commit
9c06896b34
8 changed files with 191 additions and 6 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
|
@ -406,6 +406,15 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
|
|
@ -603,6 +612,17 @@ dependencies = [
|
|||
"serde",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -621,6 +641,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
|
|
|||
|
|
@ -14,3 +14,4 @@ serde = { version = "1.0.193", features = ["derive"] }
|
|||
test-case = "3.3.1"
|
||||
insta = { version = "1.34.0", features = ["ron"] }
|
||||
miette = { version = "5.10.0", features = ["fancy"] }
|
||||
walkdir = "2.4.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
use miette::{Context, IntoDiagnostic, Result};
|
||||
use std::env::args;
|
||||
use std::fs::read_to_string;
|
||||
use std::path::Path;
|
||||
use vdf_reader::entry::Table;
|
||||
use vdf_reader::Reader;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut success = 0;
|
||||
let mut err = Vec::new();
|
||||
let dir = args().nth(1).expect("no path provided");
|
||||
for entry in WalkDir::new(dir)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| {
|
||||
let name = e.file_name().to_str().unwrap_or_default();
|
||||
name.ends_with(".vmt") || name.ends_with(".vdf")
|
||||
})
|
||||
{
|
||||
if let Err(e) = try_parse(entry.path()) {
|
||||
err.push(e);
|
||||
let e = try_parse(entry.path()).unwrap_err();
|
||||
println!("{:?}", e);
|
||||
} else {
|
||||
success += 1;
|
||||
println!("{}", entry.path().display());
|
||||
}
|
||||
}
|
||||
|
||||
println!("successfully parsed {success} files");
|
||||
println!("found errors in {} files", err.len());
|
||||
for e in err {
|
||||
println!("{:?}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_parse(path: &Path) -> Result<Table> {
|
||||
let raw = read_to_string(path)
|
||||
.into_diagnostic()
|
||||
.wrap_err_with(|| format!("failed to read {}", path.display()))?;
|
||||
let mut reader = Reader::from(raw.as_str());
|
||||
Table::load(&mut reader).wrap_err_with(|| format!("failed to parse {}", path.display()))
|
||||
}
|
||||
16
examples/tokens.rs
Normal file
16
examples/tokens.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use miette::{Context, IntoDiagnostic, Result};
|
||||
use std::env::args;
|
||||
use std::fs::read_to_string;
|
||||
use vdf_reader::Reader;
|
||||
|
||||
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 reader = Reader::from(raw.as_str());
|
||||
for event in reader {
|
||||
println!("{:?}", event?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -4,9 +4,13 @@ use std::str;
|
|||
|
||||
/// Parser token.
|
||||
#[derive(PartialEq, Debug, Logos, Display, Clone)]
|
||||
#[logos(skip r"[ \t\n\f\r]+")] // whitespace
|
||||
#[logos(skip r"[ \t\f\r]+")] // 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")]
|
||||
|
|
@ -16,7 +20,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.
|
||||
|
|
@ -92,17 +96,26 @@ mod tests {
|
|||
// a comment
|
||||
#include other
|
||||
empty ""
|
||||
\\"broken" comment
|
||||
}"#
|
||||
),
|
||||
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, "}")
|
||||
])
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::{Result, Token};
|
||||
use crate::error::{NoValidTokenError, UnexpectedTokenError};
|
||||
use logos::{Lexer, Span, SpannedIter};
|
||||
use logos::{Lexer, Logos, Span, SpannedIter};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Kinds of item.
|
||||
|
|
@ -67,6 +67,7 @@ impl Event<'_> {
|
|||
pub struct Reader<'a> {
|
||||
pub(crate) content: &'a str,
|
||||
lexer: SpannedIter<'a, Token>,
|
||||
peeked: Option<(Result<Token, <Token as Logos<'a>>::Error>, Span)>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Reader<'a> {
|
||||
|
|
@ -74,14 +75,43 @@ impl<'a> From<&'a str> for Reader<'a> {
|
|||
Reader {
|
||||
content,
|
||||
lexer: Lexer::new(content).spanned(),
|
||||
peeked: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
fn token(&mut self) -> Option<(Result<Token, <Token as Logos>::Error>, Span)> {
|
||||
if let Some((token, span)) = self.peeked.take() {
|
||||
Some((token, span))
|
||||
} else {
|
||||
self.lexer.next()
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> Option<(Result<Token, <Token as Logos>::Error>, Span)> {
|
||||
if self.peeked.is_none() {
|
||||
self.peeked = self.lexer.next();
|
||||
}
|
||||
self.peeked.clone()
|
||||
}
|
||||
|
||||
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>> {
|
||||
pub fn event(&mut self) -> Option<Result<Event<'a>>> {
|
||||
const VALID_KEY: &[Token] = &[
|
||||
Token::Item,
|
||||
Token::QuotedItem,
|
||||
|
|
@ -90,7 +120,7 @@ impl<'a> Reader<'a> {
|
|||
Token::QuotedStatement,
|
||||
];
|
||||
|
||||
let key = match self.lexer.next() {
|
||||
let key = match self.token_eat_newlines() {
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -137,7 +167,46 @@ impl<'a> Reader<'a> {
|
|||
|
||||
const VALID_VALUE: &[Token] = &[Token::Item, Token::QuotedItem, Token::GroupStart];
|
||||
|
||||
let value = match self.lexer.next() {
|
||||
// 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.content.into(),
|
||||
)
|
||||
.into()))
|
||||
}
|
||||
Some((Err(_), _)) => {
|
||||
return Some(Err(NoValidTokenError::new(
|
||||
VALID_VALUE,
|
||||
span.into(),
|
||||
self.content.into(),
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
None => {
|
||||
return Some(Err(UnexpectedTokenError::new(
|
||||
VALID_VALUE,
|
||||
None,
|
||||
span.into(),
|
||||
self.content.into(),
|
||||
)
|
||||
.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let value = match self.token() {
|
||||
None => {
|
||||
return Some(Err(UnexpectedTokenError::new(
|
||||
VALID_VALUE,
|
||||
|
|
@ -190,6 +259,14 @@ impl<'a> Reader<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Reader<'a> {
|
||||
type Item = Result<Event<'a>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.event()
|
||||
}
|
||||
}
|
||||
|
||||
fn quoted_string(source: &str) -> Cow<str> {
|
||||
string(&source[1..source.len() - 1])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,6 @@
|
|||
array 2
|
||||
array "3"
|
||||
windows_path "C:\test\no newline"
|
||||
|
||||
\\"$translucent" 1 // this is read vdf written by real valve developers
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ expression: parsed
|
|||
Table({
|
||||
"#base": Statement(Statement("panelBase.res")),
|
||||
"Resource/specificPanel.res": Table(Table({
|
||||
"\\\"$translucent\"": Value(Value("1")),
|
||||
"array": Array(Array([
|
||||
Value(Value("1")),
|
||||
Value(Value("2")),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue