Skip to content

Commit

Permalink
feat: add 'FromStr' implementation, replacing 'from_str' function
Browse files Browse the repository at this point in the history
  • Loading branch information
ShenMian committed Apr 3, 2024
1 parent fa693ea commit 2c0373d
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 200 deletions.
2 changes: 1 addition & 1 deletion benches/benchmark.rs
@@ -1,4 +1,4 @@
use std::{fs, path::Path};
use std::{fs, path::Path, str::FromStr};

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use nalgebra::Vector2;
Expand Down
29 changes: 17 additions & 12 deletions src/actions.rs
Expand Up @@ -3,6 +3,7 @@
use std::{
fmt,
ops::{Deref, DerefMut},
str::FromStr,
};

use nalgebra::Vector2;
Expand All @@ -14,18 +15,6 @@ use crate::{action::Action, error::ParseActionError, run_length::rle_decode};
pub struct Actions(pub Vec<Action>);

impl Actions {
/// Creates a new `Actions` with LURD format string.
pub fn from_str(str: &str) -> Result<Actions, ParseActionError> {
if str.contains(char::is_numeric) {
return Actions::from_str(&rle_decode(str).unwrap());
}
let mut instance = Actions::default();
for char in str.chars() {
instance.push(Action::try_from(char)?);
}
Ok(instance)
}

/// Returns the total number of moves.
pub fn moves(&self) -> usize {
self.len()
Expand Down Expand Up @@ -77,6 +66,22 @@ impl Actions {
}
}

impl FromStr for Actions {
type Err = ParseActionError;

/// Creates a new `Actions` with LURD format string.
fn from_str(lurd: &str) -> Result<Self, Self::Err> {
if lurd.contains(char::is_numeric) {
return Actions::from_str(&rle_decode(lurd).unwrap());
}
let mut instance = Actions::default();
for char in lurd.chars() {
instance.push(Action::try_from(char)?);
}
Ok(instance)
}
}

impl Deref for Actions {
type Target = Vec<Action>;

Expand Down
197 changes: 101 additions & 96 deletions src/level.rs
Expand Up @@ -4,6 +4,7 @@ use std::{
collections::{HashMap, HashSet},
fmt,
ops::{Deref, DerefMut},
str::FromStr,
};

use itertools::Itertools;
Expand All @@ -29,102 +30,6 @@ pub struct Level {
}

impl Level {
/// Creates a new `Level` from XSB format string.
///
/// Reads level map and metadata from XSB formatted strings.
pub fn from_str(str: &str) -> Result<Self, ParseLevelError> {
let mut map_offset = 0;
let mut map_len = 0;
let mut metadata = HashMap::new();
let mut comments = String::new();
let mut in_block_comment = false;
for line in str.split_inclusive(['\n', '|']) {
if map_len == 0 {
map_offset += line.len();
}

let trimmed_line = line.trim();
if trimmed_line.is_empty() {
continue;
}

// Parse comments
if in_block_comment {
if trimmed_line.to_lowercase().starts_with("comment-end") {
// Exit block comment
in_block_comment = false;
continue;
}
comments += trimmed_line;
comments.push('\n');
continue;
}
if let Some(comment) = trimmed_line.strip_prefix(';') {
comments += comment.trim_start();
comments.push('\n');
continue;
}

// Parse metadata
if let Some((key, value)) = trimmed_line.split_once(':') {
let key = key.trim().to_lowercase();
let value = value.trim();

if key == "comment" {
if value.is_empty() {
// Enter block comment
in_block_comment = true;
} else {
comments += value;
comments.push('\n');
}
continue;
}

if metadata.insert(key.clone(), value.to_string()).is_some() {
return Err(ParseLevelError::DuplicateMetadata(key));
}
continue;
}

// Discard line that are not map data (with RLE)
if !is_xsb_string(trimmed_line) {
if map_len != 0 {
return Err(ParseMapError::InvalidCharacter(
trimmed_line
.chars()
.find(|&c| !is_xsb_symbol_with_rle(c))
.unwrap(),
)
.into());
}
continue;
}

if map_len == 0 {
map_offset -= line.len();
}
map_len += line.len();
}
if !comments.is_empty() {
debug_assert!(!metadata.contains_key("comments"));
metadata.insert("comments".to_string(), comments);
}
if in_block_comment {
return Err(ParseLevelError::UnterminatedBlockComment);
}
if map_len == 0 {
return Err(ParseLevelError::NoMap);
}

Ok(Self {
map: Map::from_str(&str[map_offset..map_offset + map_len])?,
metadata,
actions: Actions::default(),
undone_actions: Actions::default(),
})
}

/// Creates a new `Level` from map.
pub fn from_map(map: Map) -> Self {
Self {
Expand Down Expand Up @@ -305,6 +210,106 @@ impl fmt::Display for Level {
}
}

impl FromStr for Level {
type Err = ParseLevelError;

/// Creates a new `Level` from XSB format string.
///
/// Reads level map and metadata from XSB formatted strings.
fn from_str(xsb: &str) -> Result<Self, Self::Err> {
let mut map_offset = 0;
let mut map_len = 0;
let mut metadata = HashMap::new();
let mut comments = String::new();
let mut in_block_comment = false;
for line in xsb.split_inclusive(['\n', '|']) {
if map_len == 0 {
map_offset += line.len();
}

let trimmed_line = line.trim();
if trimmed_line.is_empty() {
continue;
}

// Parse comments
if in_block_comment {
if trimmed_line.to_lowercase().starts_with("comment-end") {
// Exit block comment
in_block_comment = false;
continue;
}
comments += trimmed_line;
comments.push('\n');
continue;
}
if let Some(comment) = trimmed_line.strip_prefix(';') {
comments += comment.trim_start();
comments.push('\n');
continue;
}

// Parse metadata
if let Some((key, value)) = trimmed_line.split_once(':') {
let key = key.trim().to_lowercase();
let value = value.trim();

if key == "comment" {
if value.is_empty() {
// Enter block comment
in_block_comment = true;
} else {
comments += value;
comments.push('\n');
}
continue;
}

if metadata.insert(key.clone(), value.to_string()).is_some() {
return Err(ParseLevelError::DuplicateMetadata(key));
}
continue;
}

// Discard line that are not map data (with RLE)
if !is_xsb_string(trimmed_line) {
if map_len != 0 {
return Err(ParseMapError::InvalidCharacter(
trimmed_line
.chars()
.find(|&c| !is_xsb_symbol_with_rle(c))
.unwrap(),
)
.into());
}
continue;
}

if map_len == 0 {
map_offset -= line.len();
}
map_len += line.len();
}
if !comments.is_empty() {
debug_assert!(!metadata.contains_key("comments"));
metadata.insert("comments".to_string(), comments);
}
if in_block_comment {
return Err(ParseLevelError::UnterminatedBlockComment);
}
if map_len == 0 {
return Err(ParseLevelError::NoMap);
}

Ok(Self {
map: Map::from_str(&xsb[map_offset..map_offset + map_len])?,
metadata,
actions: Actions::default(),
undone_actions: Actions::default(),
})
}
}

impl Deref for Level {
type Target = Map;

Expand Down

0 comments on commit 2c0373d

Please sign in to comment.