Improved value parsing, added parsing from multiple files, improved parse errors
parent
ee8db7cb09
commit
e9a1cf17b6
|
|
@ -1,7 +1,3 @@
|
||||||
[SITE:CAVE]
|
|
||||||
[SIZE:1:3]
|
|
||||||
[UNDERGROUND]
|
|
||||||
|
|
||||||
[CREATURE:BANDIT]
|
[CREATURE:BANDIT]
|
||||||
[NAME:bandit:bandits]
|
[NAME:bandit:bandits]
|
||||||
[HEALTH:8]
|
[HEALTH:8]
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
[SITE:CAVE]
|
||||||
|
[SIZE:1:3]
|
||||||
|
[UNDERGROUND]
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use std::{borrow::BorrowMut, io::Read, str::FromStr};
|
use self::parser::{FilePosition, ParseError, ParseQuery};
|
||||||
|
|
||||||
|
pub mod parser;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TopLevelTag {
|
pub enum TopLevelTag {
|
||||||
|
|
@ -10,51 +12,12 @@ pub enum TopLevelTag {
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub values: Vec<String>,
|
pub values: Vec<String>,
|
||||||
|
pub file_position: FilePosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
pub fn parse_value<T: ParseQuery>(&self) -> Result<T, ParseError> {
|
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)))
|
T::parse(&self.values).map_err(|_| ParseError::ValueParseError(self.clone()))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(())?)?,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,6 +27,13 @@ pub struct Definitions {
|
||||||
pub creatures: Vec<CreatureDef>,
|
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)]
|
#[derive(Default, Debug)]
|
||||||
pub struct SiteDef {
|
pub struct SiteDef {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|
@ -119,7 +89,10 @@ impl CreatureDef {
|
||||||
"NAME" => (result.name_singular, result.name_plural) = tag.parse_value()?,
|
"NAME" => (result.name_singular, result.name_plural) = tag.parse_value()?,
|
||||||
"HEALTH" => result.health = tag.parse_value()?,
|
"HEALTH" => result.health = tag.parse_value()?,
|
||||||
"AC" => result.armor_class = 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()?),
|
"MULTIATTACK" => result.multiattack = Some(tag.parse_value()?),
|
||||||
"HUMANOID" => result.is_humanoid = true,
|
"HUMANOID" => result.is_humanoid = true,
|
||||||
"ANIMAL" => result.is_animal = true,
|
"ANIMAL" => result.is_animal = true,
|
||||||
|
|
@ -133,133 +106,3 @@ impl CreatureDef {
|
||||||
Ok(result)
|
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/main.rs
37
src/main.rs
|
|
@ -1,10 +1,13 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
|
io::BufReader,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
|
path::PathBuf,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use definitions::{parser::DefinitionParser, Definitions};
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
@ -13,6 +16,10 @@ pub mod definitions;
|
||||||
|
|
||||||
const SAVE_FILE: &'static str = "world.bin";
|
const SAVE_FILE: &'static str = "world.bin";
|
||||||
|
|
||||||
|
fn resources_path() -> PathBuf {
|
||||||
|
PathBuf::from(".").join("defs")
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut world = match std::fs::read(SAVE_FILE) {
|
let mut world = match std::fs::read(SAVE_FILE) {
|
||||||
Ok(data) => bincode::deserialize(&data).unwrap(),
|
Ok(data) => bincode::deserialize(&data).unwrap(),
|
||||||
|
|
@ -31,8 +38,34 @@ fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let source = std::fs::File::open("defs/data.def").unwrap();
|
let mut definitions = Definitions::default();
|
||||||
definitions::DefinitionParser::parse(source).unwrap();
|
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() {
|
// for site in world.sites.iter() {
|
||||||
// println!("{site}")
|
// println!("{site}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue