bitsy-parser/src/lib.rs

215 lines
4.9 KiB
Rust
Raw Normal View History

extern crate loe;
use std::io::Cursor;
2020-04-13 12:30:08 +00:00
use radix_fmt::radix_36;
use loe::{process, Config, TransformMode};
2020-04-13 12:30:08 +00:00
2020-04-12 11:22:20 +00:00
pub mod colour;
2020-04-12 12:26:33 +00:00
pub mod dialogue;
2020-04-12 12:52:36 +00:00
pub mod ending;
2020-04-12 12:49:19 +00:00
pub mod exit;
2020-04-12 13:38:07 +00:00
pub mod game;
2020-04-12 11:48:07 +00:00
pub mod image;
2020-04-12 12:46:55 +00:00
pub mod item;
2020-04-12 12:50:07 +00:00
pub mod mock;
2020-04-18 15:58:30 +00:00
pub mod palette;
2020-04-12 12:21:27 +00:00
pub mod position;
2020-04-12 13:28:11 +00:00
pub mod room;
2020-04-12 12:35:17 +00:00
pub mod sprite;
2020-04-18 15:58:30 +00:00
pub mod text;
2020-04-12 11:53:11 +00:00
pub mod tile;
2020-04-12 13:20:53 +00:00
pub mod variable;
2020-04-12 11:22:20 +00:00
2020-04-12 11:35:39 +00:00
use colour::Colour;
2020-04-12 12:26:33 +00:00
use dialogue::Dialogue;
2020-04-12 12:52:36 +00:00
use ending::Ending;
2020-04-18 15:58:30 +00:00
use exit::{Exit, Transition};
2020-04-18 10:03:24 +00:00
use game::{Game, Version};
2020-04-12 11:48:07 +00:00
use image::Image;
2020-04-12 12:46:55 +00:00
use item::Item;
2020-04-18 15:58:30 +00:00
use palette::Palette;
2020-04-12 12:21:27 +00:00
use position::Position;
2020-04-12 13:28:11 +00:00
use room::Room;
2020-04-12 12:35:17 +00:00
use sprite::Sprite;
2020-04-18 15:58:30 +00:00
use std::fmt::Display;
use text::{Font, TextDirection};
2020-04-12 11:53:11 +00:00
use tile::Tile;
2020-04-12 13:20:53 +00:00
use variable::Variable;
2020-04-05 17:58:04 +00:00
2020-04-10 15:27:23 +00:00
#[derive(Debug, Eq, PartialEq)]
2020-04-12 11:53:11 +00:00
pub struct Instance {
2020-04-10 15:27:23 +00:00
position: Position,
2020-04-12 12:52:36 +00:00
id: String, // item / ending.rs id
2020-04-10 15:27:23 +00:00
}
#[derive(Debug, Eq, PartialEq)]
2020-04-12 11:53:11 +00:00
pub struct ExitInstance {
2020-04-10 15:27:23 +00:00
position: Position,
exit: Exit,
transition: Option<Transition>,
dialogue_id: Option<String>,
2020-04-10 15:27:23 +00:00
}
pub trait AnimationFrames {
fn to_string(&self) -> String;
}
impl AnimationFrames for Vec<Image> {
#[inline]
fn to_string(&self) -> String {
let mut string = String::new();
let last_frame = self.len() - 1;
for (i, frame) in self.into_iter().enumerate() {
string.push_str(&frame.to_string());
if i < last_frame {
string.push_str(&"\n>\n".to_string());
}
}
string
}
}
2020-04-13 12:30:08 +00:00
#[inline]
2020-04-13 12:30:08 +00:00
fn from_base36(str: &str) -> u64 {
2020-04-26 12:33:53 +00:00
u64::from_str_radix(str, 36).expect(&format!("Invalid base36 string: {}", str))
2020-04-13 12:30:08 +00:00
}
/// this doesn't work inside ToBase36 for some reason
#[inline]
fn to_base36(int: u64) -> String {
format!("{}", radix_36(int))
}
pub trait ToBase36 {
fn to_base36(&self) -> String;
}
impl ToBase36 for u64 {
#[inline]
fn to_base36(&self) -> String {
to_base36(*self)
}
2020-04-13 12:30:08 +00:00
}
/// e.g. `\nNAME DLG_0`
#[inline]
fn optional_data_line<T: Display>(label: &str, item: Option<T>) -> String {
if item.is_some() {
format!("\n{} {}", label, item.unwrap())
} else {
"".to_string()
}
}
#[inline]
fn transform_line_endings(input: String, mode: TransformMode) -> String {
let mut input = Cursor::new(input);
let mut output = Cursor::new(Vec::new());
process(&mut input, &mut output, Config::default().transform(mode)).unwrap();
String::from_utf8(output.into_inner()).unwrap()
}
#[inline]
fn segments_from_string(string: String) -> Vec<String> {
let mut output:Vec<String> = Vec::new();
// are we inside `"""\n...\n"""`? if so, ignore empty lines
let mut inside_escaped_block = false;
let mut current_segment : Vec<String> = Vec::new();
let lines: Vec<&str> = string.lines().collect();
for line in lines {
if line == "\"\"\"" {
inside_escaped_block = ! inside_escaped_block;
}
if line == "" && !inside_escaped_block {
output.push(current_segment.join("\n"));
current_segment = Vec::new();
} else {
current_segment.push(line.to_string());
}
}
output.push(current_segment.join("\n"));
output
}
/// for some reason the sprites with numeric IDs go first,
/// then SPR A (avatar), then all the non-numeric IDs
#[inline]
fn is_string_numeric(str: String) -> bool {
for c in str.chars() {
if !c.is_numeric() {
return false;
}
}
return true;
}
2020-04-28 18:30:18 +00:00
pub trait Quote {
fn quote(&self) -> String;
}
impl Quote for String {
#[inline]
fn quote(&self) -> String {
format!("\"\"\"\n{}\n\"\"\"", self).to_string()
}
}
pub trait Unquote {
fn unquote(&self) -> String;
}
impl Unquote for String {
#[inline]
fn unquote(&self) -> String {
self.trim_matches('\"').trim_matches('\n').to_string()
}
}
2020-04-19 07:13:55 +00:00
#[cfg(test)]
mod test {
use crate::{from_base36, ToBase36, optional_data_line, mock, segments_from_string};
2020-04-19 07:13:55 +00:00
#[test]
fn test_from_base36() {
assert_eq!(from_base36("0"), 0);
assert_eq!(from_base36("0z"), 35);
assert_eq!(from_base36("11"), 37);
}
#[test]
fn test_to_base36() {
assert_eq!((37 as u64).to_base36(), "11");
}
#[test]
fn test_optional_data_line() {
let output = optional_data_line("NAME", mock::item().name);
assert_eq!(output, "\nNAME door".to_string());
}
#[test]
fn test_string_to_segments() {
let output = segments_from_string(
include_str!("./test-resources/segments").to_string()
);
let expected = vec![
"\"\"\"\nthe first segment is a long bit of text\n\n\nit contains empty lines\n\n\"\"\"".to_string(),
"this is a new segment\nthis is still the second segment\nblah\nblah".to_string(),
"DLG SEGMENT_3\n\"\"\"\nthis is a short \"long\" bit of text\n\"\"\"".to_string(),
"this is the last segment".to_string(),
];
assert_eq!(output, expected);
}
}