Improved value parsing, added parsing from multiple files, improved parse errors

master
hheik 2024-03-21 15:30:37 +02:00
parent ee8db7cb09
commit e9a1cf17b6
5 changed files with 247 additions and 180 deletions

View File

@ -1,7 +1,3 @@
[SITE:CAVE]
[SIZE:1:3]
[UNDERGROUND]
[CREATURE:BANDIT]
[NAME:bandit:bandits]
[HEALTH:8]

3
defs/sites.def Normal file
View File

@ -0,0 +1,3 @@
[SITE:CAVE]
[SIZE:1:3]
[UNDERGROUND]

View File

@ -1,4 +1,6 @@
use std::{borrow::BorrowMut, io::Read, str::FromStr};
use self::parser::{FilePosition, ParseError, ParseQuery};
pub mod parser;
#[derive(Debug)]
pub enum TopLevelTag {
@ -10,51 +12,12 @@ pub enum TopLevelTag {
pub struct Tag {
pub name: String,
pub values: Vec<String>,
pub file_position: FilePosition,
}
impl Tag {
pub fn parse_value<T: ParseQuery>(&self) -> Result<T, ParseError> {
T::parse(&self.values).map_err(|_| ParseError::Parser(format!("Invalid value {:?} in tag {}", self.values, self.name)))
}
}
pub trait ParseQuery: Sized {
fn parse(values: &Vec<String>) -> Result<Self, ()>;
}
trait Parseable: Sized + FromStr {
fn parse(string: &str) -> Result<Self, ()> {
string.parse().map_err(|_| ())
}
}
impl Parseable for String {}
impl Parseable for i32 {}
impl Parseable for f32 {}
impl Parseable for bool {}
impl<A: Parseable> ParseQuery for A {
fn parse(values: &Vec<String>) -> Result<Self, ()> {
Ok(A::parse(values.get(0).ok_or(())?)?)
}
}
impl<A: Parseable, B: Parseable> ParseQuery for (A, B) {
fn parse(values: &Vec<String>) -> Result<Self, ()> {
Ok((
A::parse(values.get(0).ok_or(())?)?,
B::parse(values.get(1).ok_or(())?)?,
))
}
}
impl<A: Parseable, B: Parseable, C: Parseable> ParseQuery for (A, B, C) {
fn parse(values: &Vec<String>) -> Result<Self, ()> {
Ok((
A::parse(values.get(0).ok_or(())?)?,
B::parse(values.get(1).ok_or(())?)?,
C::parse(values.get(2).ok_or(())?)?,
))
fn parse_value<T: ParseQuery>(&self) -> Result<T, ParseError> {
T::parse(&self.values).map_err(|_| ParseError::ValueParseError(self.clone()))
}
}
@ -64,6 +27,13 @@ pub struct Definitions {
pub creatures: Vec<CreatureDef>,
}
impl Definitions {
pub fn append(&mut self, mut other: Self) {
self.sites.append(&mut other.sites);
self.creatures.append(&mut other.creatures);
}
}
#[derive(Default, Debug)]
pub struct SiteDef {
pub id: String,
@ -119,7 +89,10 @@ impl CreatureDef {
"NAME" => (result.name_singular, result.name_plural) = tag.parse_value()?,
"HEALTH" => result.health = tag.parse_value()?,
"AC" => result.armor_class = tag.parse_value()?,
"ATTACK" => (result.to_hit_modifier, result.min_damage, result.max_damage) = tag.parse_value()?,
"ATTACK" => {
(result.to_hit_modifier, result.min_damage, result.max_damage) =
tag.parse_value()?
}
"MULTIATTACK" => result.multiattack = Some(tag.parse_value()?),
"HUMANOID" => result.is_humanoid = true,
"ANIMAL" => result.is_animal = true,
@ -133,133 +106,3 @@ impl CreatureDef {
Ok(result)
}
}
pub enum ValueToken {
String(String),
Integer(i32),
}
#[derive(Debug)]
pub enum ParseError {
Lexer(LexerError),
Syntax(String),
Definition(String),
Parser(String),
Unexpected,
}
#[derive(Debug)]
pub enum LexerError {
Io(std::io::Error),
UnclosedTag,
}
pub struct DefinitionParser;
impl DefinitionParser {
pub fn parse(source: impl Read) -> Result<Definitions, ParseError> {
let mut defs = Definitions {
sites: vec![],
creatures: vec![],
};
let tags = match Self::lexer(source) {
Ok(tags) => tags,
Err(err) => {
eprintln!("{err:?}");
return Err(ParseError::Lexer(err));
}
};
let mut unparsed_defs: Vec<(Tag, Vec<Tag>)> = Vec::new();
let mut definition_builder: Option<(Tag, Vec<Tag>)> = None;
for tag in tags {
match Self::match_top_level_tag(&tag.name) {
Some(_) => {
if let Some(builder) = definition_builder {
unparsed_defs.push(builder);
}
definition_builder = Some((tag, vec![]));
}
None => match definition_builder.borrow_mut() {
Some(builder) => builder.1.push(tag.clone()),
None => {
return Err(ParseError::Syntax(
"First tag must be a top-level-tag".into(),
))
}
},
}
}
match definition_builder {
Some(builder) => unparsed_defs.push(builder),
None => {
return Err(ParseError::Definition("No definitions found".into()));
}
}
for (header, tags) in unparsed_defs {
match Self::match_top_level_tag(&header.name) {
Some(def_type) => match def_type {
TopLevelTag::Site => defs.sites.push(SiteDef::parse(header, &tags)?),
TopLevelTag::Creature => {
defs.creatures.push(CreatureDef::parse(header, &tags)?)
}
},
None => {
return Err(ParseError::Unexpected);
}
}
}
println!("{defs:?}");
Ok(defs)
}
fn lexer(mut source: impl Read) -> Result<Vec<Tag>, LexerError> {
let mut tags = vec![];
let mut tag_builder: Option<Tag> = None;
let mut buffer = String::new();
source
.read_to_string(&mut buffer)
.map_err(|err| LexerError::Io(err))?;
for c in buffer.chars() {
match tag_builder.borrow_mut() {
None => match c {
'[' => tag_builder = Some(Tag::default()),
_ => {}
},
Some(tag) => match c {
']' => {
tags.push(tag.clone());
tag_builder = None
}
':' => tag.values.push("".into()),
'[' => return Err(LexerError::UnclosedTag),
_ => {
if tag.values.is_empty() {
tag.name.push(c)
} else {
tag.values.last_mut().unwrap().push(c)
}
}
},
}
}
if tag_builder.is_some() {
return Err(LexerError::UnclosedTag);
}
Ok(tags)
}
fn match_top_level_tag(tag: &str) -> Option<TopLevelTag> {
Some(match tag {
"SITE" => TopLevelTag::Site,
"CREATURE" => TopLevelTag::Creature,
_ => return None,
})
}
}

192
src/definitions/parser.rs Normal file
View File

@ -0,0 +1,192 @@
use std::{borrow::BorrowMut, io::Read, str::FromStr};
use crate::definitions::{CreatureDef, Definitions, SiteDef, Tag, TopLevelTag};
pub trait ParseQuery: Sized {
fn parse(values: &Vec<String>) -> Result<Self, ()>;
}
trait Parseable: Sized + FromStr {
fn parse(string: &str) -> Result<Self, ()> {
string.parse().map_err(|_| ())
}
}
// Implement for types that tags contain
impl Parseable for String {}
impl Parseable for i32 {}
impl Parseable for u32 {}
impl Parseable for f32 {}
impl Parseable for bool {}
macro_rules! parse_query_impl {
($t:ident) => {
impl<$t: Parseable> ParseQuery for $t {
fn parse(values: &Vec<String>) -> Result<Self, ()> {
let mut iter = values.iter();
Ok(
$t::parse(iter.next().ok_or(())?)?
)
}
}
};
($x:ident, $($t:ident),+) => {
impl<$x: Parseable, $($t: Parseable),+> ParseQuery for ($x, $($t),+) {
fn parse(values: &Vec<String>) -> Result<Self, ()> {
let mut iter = values.iter();
Ok((
$x::parse(iter.next().ok_or(())?)?,
$($t::parse(iter.next().ok_or(())?)?,)+
))
}
}
parse_query_impl!($($t),+);
};
}
// Definitely overkill, but this enables definition files to support up to 20 arguments per tag.
// Although managing that many would be annoying
parse_query_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
#[derive(Clone, Debug)]
pub struct FilePosition {
pub line: u32,
pub column: u32,
}
impl Default for FilePosition {
fn default() -> Self {
Self { line: 1, column: 1 }
}
}
#[derive(Debug)]
pub enum ParseError {
LexerError(LexerError),
SyntaxError(String),
ValueParseError(Tag),
NoDefinitionsError,
UnexpectedError,
}
#[derive(Debug)]
pub enum LexerError {
Io(std::io::Error),
UnclosedTag(Tag),
MissingTagStart(FilePosition),
}
pub struct DefinitionParser;
impl DefinitionParser {
pub fn parse(source: impl Read) -> Result<Definitions, ParseError> {
let tags = match Self::lexer(source) {
Ok(tags) => tags,
Err(err) => return Err(ParseError::LexerError(err)),
};
let mut unparsed_defs: Vec<(Tag, Vec<Tag>)> = Vec::new();
let mut definition_builder: Option<(Tag, Vec<Tag>)> = None;
for tag in tags {
match Self::match_top_level_tag(&tag.name) {
Some(_) => {
if let Some(builder) = definition_builder {
unparsed_defs.push(builder);
}
definition_builder = Some((tag, vec![]));
}
None => match definition_builder.borrow_mut() {
Some(builder) => builder.1.push(tag.clone()),
None => {
return Err(ParseError::SyntaxError(
"First tag must be a top-level-tag".into(),
))
}
},
}
}
match definition_builder {
Some(builder) => unparsed_defs.push(builder),
None => {
return Err(ParseError::NoDefinitionsError);
}
}
let mut defs = Definitions::default();
for (header, tags) in unparsed_defs {
match Self::match_top_level_tag(&header.name) {
Some(def_type) => match def_type {
TopLevelTag::Site => defs.sites.push(SiteDef::parse(header, &tags)?),
TopLevelTag::Creature => {
defs.creatures.push(CreatureDef::parse(header, &tags)?)
}
},
None => {
return Err(ParseError::UnexpectedError);
}
}
}
Ok(defs)
}
fn lexer(mut source: impl Read) -> Result<Vec<Tag>, LexerError> {
let mut tags = vec![];
let mut tag_builder: Option<Tag> = None;
let mut buffer = String::new();
source
.read_to_string(&mut buffer)
.map_err(|err| LexerError::Io(err))?;
let mut file_position = FilePosition::default();
for c in buffer.chars() {
if c == '\n' {
file_position.line += 1;
file_position.column = 0;
} else {
file_position.column += 1;
}
match tag_builder.borrow_mut() {
None => match c {
'[' => {
tag_builder = Some(Tag {
file_position: file_position.clone(),
..Default::default()
});
}
']' => return Err(LexerError::MissingTagStart(file_position.clone())),
_ => {}
},
Some(tag) => match c {
']' => {
tags.push(tag.clone());
tag_builder = None;
}
':' => tag.values.push("".into()),
'[' => return Err(LexerError::UnclosedTag(tag.clone())),
_ => {
if tag.values.is_empty() {
tag.name.push(c)
} else {
tag.values.last_mut().unwrap().push(c)
}
}
},
}
}
if let Some(tag) = tag_builder {
return Err(LexerError::UnclosedTag(tag.clone()));
}
Ok(tags)
}
fn match_top_level_tag(tag: &str) -> Option<TopLevelTag> {
Some(match tag {
"SITE" => TopLevelTag::Site,
"CREATURE" => TopLevelTag::Creature,
_ => return None,
})
}
}

View File

@ -1,10 +1,13 @@
use std::{
collections::{HashMap, HashSet},
fmt::Display,
io::BufReader,
num::NonZeroUsize,
path::PathBuf,
time::Duration,
};
use definitions::{parser::DefinitionParser, Definitions};
use enum_dispatch::enum_dispatch;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@ -13,6 +16,10 @@ pub mod definitions;
const SAVE_FILE: &'static str = "world.bin";
fn resources_path() -> PathBuf {
PathBuf::from(".").join("defs")
}
fn main() {
let mut world = match std::fs::read(SAVE_FILE) {
Ok(data) => bincode::deserialize(&data).unwrap(),
@ -31,8 +38,34 @@ fn main() {
}
};
let source = std::fs::File::open("defs/data.def").unwrap();
definitions::DefinitionParser::parse(source).unwrap();
let mut definitions = Definitions::default();
let mut parse_error_files = vec![];
for entry in std::fs::read_dir(resources_path()).unwrap() {
match entry {
Ok(entry) => {
if entry.file_name().to_string_lossy().ends_with(".def") {
let source = BufReader::new(std::fs::File::open(entry.path()).unwrap());
match DefinitionParser::parse(source) {
Ok(new_defs) => {
println!("Parsed\t{:?}", entry.path());
definitions.append(new_defs);
}
Err(err) => {
parse_error_files.push((err, entry.path()));
}
}
}
}
_ => (),
}
}
for (err, path) in parse_error_files.iter() {
eprintln!("Error\t{path:?}\n\t{err:?}")
}
if !parse_error_files.is_empty() {
eprintln!("{} file(s) had parsing errors!", parse_error_files.len())
}
// for site in world.sites.iter() {
// println!("{site}")