diff --git a/defs/data.def b/defs/creatures.def similarity index 84% rename from defs/data.def rename to defs/creatures.def index 3474cb7..9aabae9 100644 --- a/defs/data.def +++ b/defs/creatures.def @@ -1,7 +1,3 @@ -[SITE:CAVE] - [SIZE:1:3] - [UNDERGROUND] - [CREATURE:BANDIT] [NAME:bandit:bandits] [HEALTH:8] diff --git a/defs/sites.def b/defs/sites.def new file mode 100644 index 0000000..e4a3742 --- /dev/null +++ b/defs/sites.def @@ -0,0 +1,3 @@ +[SITE:CAVE] + [SIZE:1:3] + [UNDERGROUND] diff --git a/src/definitions.rs b/src/definitions.rs index 7972ab4..6aaf3a3 100644 --- a/src/definitions.rs +++ b/src/definitions.rs @@ -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, + pub file_position: FilePosition, } impl Tag { - pub fn parse_value(&self) -> Result { - 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) -> Result; -} - -trait Parseable: Sized + FromStr { - fn parse(string: &str) -> Result { - string.parse().map_err(|_| ()) - } -} - -impl Parseable for String {} -impl Parseable for i32 {} -impl Parseable for f32 {} -impl Parseable for bool {} - -impl ParseQuery for A { - fn parse(values: &Vec) -> Result { - Ok(A::parse(values.get(0).ok_or(())?)?) - } -} - -impl ParseQuery for (A, B) { - fn parse(values: &Vec) -> Result { - Ok(( - A::parse(values.get(0).ok_or(())?)?, - B::parse(values.get(1).ok_or(())?)?, - )) - } -} - -impl ParseQuery for (A, B, C) { - fn parse(values: &Vec) -> Result { - 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(&self) -> Result { + T::parse(&self.values).map_err(|_| ParseError::ValueParseError(self.clone())) } } @@ -64,6 +27,13 @@ pub struct Definitions { pub creatures: Vec, } +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 { - 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)> = Vec::new(); - - let mut definition_builder: Option<(Tag, Vec)> = 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, LexerError> { - let mut tags = vec![]; - let mut tag_builder: Option = 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 { - Some(match tag { - "SITE" => TopLevelTag::Site, - "CREATURE" => TopLevelTag::Creature, - _ => return None, - }) - } -} diff --git a/src/definitions/parser.rs b/src/definitions/parser.rs new file mode 100644 index 0000000..790a013 --- /dev/null +++ b/src/definitions/parser.rs @@ -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) -> Result; +} + +trait Parseable: Sized + FromStr { + fn parse(string: &str) -> Result { + 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) -> Result { + 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) -> Result { + 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 { + let tags = match Self::lexer(source) { + Ok(tags) => tags, + Err(err) => return Err(ParseError::LexerError(err)), + }; + + let mut unparsed_defs: Vec<(Tag, Vec)> = Vec::new(); + + let mut definition_builder: Option<(Tag, Vec)> = 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, LexerError> { + let mut tags = vec![]; + let mut tag_builder: Option = 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 { + Some(match tag { + "SITE" => TopLevelTag::Site, + "CREATURE" => TopLevelTag::Creature, + _ => return None, + }) + } +} diff --git a/src/main.rs b/src/main.rs index 522f465..f2cc43b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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}")