Skip to content

Commit

Permalink
Bugfixes and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
TCA166 committed Dec 29, 2024
1 parent 7f3d5fa commit be3f18b
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 28 deletions.
22 changes: 22 additions & 0 deletions src/parser/game_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,20 @@ impl GameObject<HashMap<String, SaveFileValue>> {
}
}
}

pub fn to_array(&self) -> Result<GameObjectArray, ConversionError> {
let mut keys = self
.inner
.keys()
.map(|k| usize::from_str_radix(k, 10))
.collect::<Result<Vec<_>, _>>()?;
keys.sort();
let mut arr = GameObjectArray::from_name(self.name.clone());
for key in keys {
arr.push(self.inner[&key.to_string()].clone());
}
Ok(arr)
}
}

impl<'a> IntoIterator for &'a GameObject<HashMap<String, SaveFileValue>> {
Expand Down Expand Up @@ -490,6 +504,14 @@ impl GameObject<Vec<SaveFileValue>> {
pub fn pop(&mut self) -> Result<SaveFileValue, KeyError> {
self.inner.pop().ok_or(KeyError::ArrEmpty)
}

pub fn to_map(&self) -> GameObjectMap {
let mut map = GameObjectMap::from_name(self.name.clone());
for (i, val) in self.inner.iter().enumerate() {
map.insert(i.to_string(), val.clone());
}
map
}
}

impl Index<usize> for GameObject<Vec<SaveFileValue>> {
Expand Down
15 changes: 10 additions & 5 deletions src/parser/save_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,9 @@ impl<'a> SaveFile {
pub fn tape(&'a self) -> Result<Tape<'a>, jomini::Error> {
// FIXME is broken
if self.binary {
Ok(Tape::Text(TextTape::from_slice(&self.contents.as_slice())?))
Ok(Tape::Binary(BinaryTape::from_slice(&self.contents)?))
} else {
Ok(Tape::Binary(BinaryTape::from_slice(
&self.contents.as_slice(),
)?))
Ok(Tape::Text(TextTape::from_slice(&self.contents)?))
}
}
}
Expand Down Expand Up @@ -196,11 +194,18 @@ mod tests {

#[test]
fn test_tape() {
let mut file = Cursor::new(b"test");
let mut file = Cursor::new(b"test=a");
let save = SaveFile::read(&mut file, None).unwrap();
let tape = save.tape().unwrap();
if let Tape::Binary(_) = tape {
panic!("Expected text tape, got binary tape");
}
}

#[test]
fn test_tape_invalid() {
let mut file = Cursor::new(b"test");
let save = SaveFile::read(&mut file, None).unwrap();
assert!(save.tape().is_err());
}
}
39 changes: 37 additions & 2 deletions src/parser/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ impl<'tape, 'data> Section<'tape, 'data> {
let mut key = false;
match self.tape {
Tokens::Text(text) => {
// TODO the mixed object handling is totally borked. Need a total redesign
while offset < self.length {
match &text[offset] {
TextToken::Array { .. } => {
Expand Down Expand Up @@ -325,8 +326,42 @@ impl<'tape, 'data> Section<'tape, 'data> {
}
}
}
return Ok(stack.pop().unwrap());
if stack.is_empty() {
return Ok(SaveFileObject::Map(GameObjectMap::from_name(
self.name.clone(),
)));
} else {
return Ok(stack.pop().unwrap());
}
}
}

// TODO add tests covering section parsing
#[cfg(test)]
mod tests {

use jomini::TextTape;

use super::*;

use super::super::types::Tape;

#[test]
fn test_empty() {
let tape = Tape::Text(TextTape::from_slice(b"").unwrap());
let tokens = tape.tokens();
let section = Section::new(tokens, "empty".to_string(), 0, 0);
let obj = section.parse().unwrap();
assert_eq!(obj.get_name(), "empty");
assert!(matches!(obj, SaveFileObject::Map(_)));
}

#[test]
fn test_mixed_obj() {
// MAYBE support this in the future, haven't seen it in the wild yet
let tape = Tape::Text(TextTape::from_slice(b"test={a b 1=c 2={d=5}}").unwrap());
let tokens = tape.tokens();
let section = Section::new(tokens, "test".to_string(), 1, 14);
let obj = section.parse();
assert!(obj.is_err());
}
}
101 changes: 84 additions & 17 deletions src/parser/section_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,17 @@ impl<'tape, 'data> Iterator for SectionReader<'tape, 'data> {
type Item = Result<Section<'tape, 'data>, SectionReaderError<'data>>;

fn next(&mut self) -> Option<Result<Section<'tape, 'data>, SectionReaderError<'data>>> {
let mut mixed = false; // mixed is local to the section, so the tokenizer sets it once every section
match self.tape {
Tokens::Text(text) => {
while self.offset < self.length {
let tok = &text[self.offset];
match tok {
TextToken::Object { end, mixed: _ }
| TextToken::Array { end, mixed: _ } => {
if let Some(scalar) = text[self.offset - 1].as_scalar() {
// if we are in a mixed container, the key is two tokens back because = is inserted
let key_offset = if mixed { 2 } else { 1 };
if let Some(scalar) = text[self.offset - key_offset].as_scalar() {
if !scalar.is_ascii() {
return Some(Err(SectionReaderError::UnexpectedToken(
self.offset,
Expand All @@ -118,28 +121,35 @@ impl<'tape, 'data> Iterator for SectionReader<'tape, 'data> {
let key = scalar.to_string();
let section =
Section::new(Tokens::Text(&text), key, self.offset, *end);
self.offset += *end;
self.offset = *end + 1;
return Some(Ok(section));
} else {
return Some(Err(SectionReaderError::UnexpectedToken(
self.offset,
Token::from_text(tok),
self.offset - key_offset,
Token::from_text(&text[self.offset - key_offset]),
"non-scalar key encountered",
)));
}
}
TextToken::Quoted(_) | TextToken::Unquoted(_) | TextToken::Operator(_) => {
// any token that may exist in the spaces between sections
// skip here, we aren't looking for un-sectioned key-value pairs
TextToken::MixedContainer => {
mixed = true;
self.offset += 1;
}
_ => {
TextToken::End(_)
| TextToken::Header(_)
| TextToken::UndefinedParameter(_)
| TextToken::Parameter(_) => {
return Some(Err(SectionReaderError::UnexpectedToken(
self.offset,
Token::from_text(tok),
"weird token in between sections",
)))
}
_ => {
// any token that may exist in the spaces between sections
// skip here, we aren't looking for un-sectioned key-value pairs
self.offset += 1;
}
}
}
}
Expand Down Expand Up @@ -170,14 +180,14 @@ impl<'tape, 'data> Iterator for SectionReader<'tape, 'data> {
}
}
}
BinaryToken::End(_) | BinaryToken::MixedContainer => {
BinaryToken::End(_) => {
return Some(Err(SectionReaderError::UnexpectedToken(
self.offset,
Token::from_binary(tok),
"Weird token in between sections",
)));
}
_ => {
BinaryToken::MixedContainer | _ => {
self.offset += 1;
}
}
Expand All @@ -188,10 +198,67 @@ impl<'tape, 'data> Iterator for SectionReader<'tape, 'data> {
}
}

// TODO add tests covering section initialization and iteration
// what happens when the tape is empty?
// what happens when the tape has a single section?
// what happens when the tape has multiple sections?
// what happens when the tape has a section with a non-ascii key?
// what happens when the tape has a section with a non-scalar key?
// what happens when the tape has an unclosed section?
#[cfg(test)]
mod tests {
use jomini::TextTape;

use super::*;

#[test]
fn test_empty() {
let tape = Tape::Text(TextTape::from_slice(b"").unwrap());
let mut reader = SectionReader::new(&tape);
assert!(reader.next().is_none());
}

#[test]
fn test_single_section() {
let tape = Tape::Text(TextTape::from_slice(b"test={a=1}").unwrap());
let mut reader = SectionReader::new(&tape);
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test");
}

#[test]
fn test_single_section_messy() {
let tape = Tape::Text(TextTape::from_slice(b" \t\r test={a=1} \t\r ").unwrap());
let mut reader = SectionReader::new(&tape);
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test");
assert!(reader.next().is_none());
}

#[test]
fn test_multiple_sections() {
let tape = Tape::Text(TextTape::from_slice(b"test={a=1}test2={b=2}test3={c=3}").unwrap());
let mut reader = SectionReader::new(&tape);
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test");
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test2");
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test3");
assert!(reader.next().is_none());
}

#[test]
fn test_non_ascii_key() {
let tape = Tape::Text(TextTape::from_slice(b"test={\x80=1}").unwrap());
let mut reader = SectionReader::new(&tape);
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test");
}

#[test]
fn test_mixed() {
let tape =
Tape::Text(TextTape::from_slice(b"a\na=b\ntest={a=1}test2={b=2}test3={c=3}").unwrap());
let mut reader = SectionReader::new(&tape);
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test");
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test2");
let section = reader.next().unwrap().unwrap();
assert_eq!(section.get_name(), "test3");
}
}
12 changes: 8 additions & 4 deletions src/structures/dynasty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,15 @@ impl DummyInit for Dynasty {
}
}
if let Some(paret) = base.get("dynasty") {
let p = game_state.get_dynasty(&paret.as_id()?).clone();
if let Ok(mut p) = p.try_get_internal_mut() {
p.register_house();
let paret = paret.as_id()?;
if paret != self.id {
// MAYBE this is bad? I don't know
let p = game_state.get_dynasty(&paret).clone();
if let Ok(mut p) = p.try_get_internal_mut() {
p.register_house();
}
self.parent = Some(p.clone());
}
self.parent = Some(p.clone());
}
match base.get("name").or(base.get("localized_name")) {
Some(name) => {
Expand Down

0 comments on commit be3f18b

Please sign in to comment.