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]
|
||||
[NAME:bandit:bandits]
|
||||
[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)]
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::{
|
||||
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}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue