Move string encoding/decoding into world parser
This commit is contained in:
+37
-3
@@ -30,16 +30,34 @@ pub fn decode_multiline(input: &[u8]) -> String {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn encode_oneline(input: &str) -> Result<Vec<u8>> {
|
||||||
|
input
|
||||||
|
.chars()
|
||||||
|
.map(|c| {
|
||||||
|
CP437_WINGDINGS
|
||||||
|
.encode(c)
|
||||||
|
.ok_or(anyhow!("Couldn't encode char: {}", c))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_oneline(input: &[u8]) -> String {
|
||||||
|
input.iter().map(|&x| CP437_WINGDINGS.decode(x)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
use insta::assert_debug_snapshot;
|
use insta::assert_debug_snapshot;
|
||||||
|
|
||||||
use crate::encoding::{decode_multiline, encode_multiline};
|
|
||||||
|
|
||||||
fn serialize_code(input: &str) -> Vec<u8> {
|
fn serialize_code(input: &str) -> Vec<u8> {
|
||||||
encode_multiline(input).expect("Error in test")
|
encode_multiline(input).expect("Error in test")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize_title(input: &str) -> Vec<u8> {
|
||||||
|
encode_oneline(input).expect("Error in test")
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn roundtrip_multiline() {
|
fn roundtrip_multiline() {
|
||||||
let bytes: Vec<u8> = (0..=255).collect();
|
let bytes: Vec<u8> = (0..=255).collect();
|
||||||
@@ -47,7 +65,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn newlines() {
|
fn roundtrip_oneline() {
|
||||||
|
let bytes: Vec<u8> = (0..=255).collect();
|
||||||
|
assert_eq!(bytes, serialize_title(&decode_oneline(&bytes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn newlines_to_cr() {
|
||||||
assert_debug_snapshot!(serialize_code("ABC\nDEF"), @r###"
|
assert_debug_snapshot!(serialize_code("ABC\nDEF"), @r###"
|
||||||
[
|
[
|
||||||
65,
|
65,
|
||||||
@@ -61,6 +85,16 @@ mod tests {
|
|||||||
"###)
|
"###)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn byte_13_to_wingding() {
|
||||||
|
// Code is allowed to have newlines
|
||||||
|
let bytes = serialize_code("ABC\nDEF");
|
||||||
|
assert!(bytes.contains(&13));
|
||||||
|
|
||||||
|
// But in a board title, that same byte is a wingding
|
||||||
|
assert_debug_snapshot!(decode_oneline(&bytes), @r#""ABC♪DEF""#);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wingdings() {
|
fn wingdings() {
|
||||||
assert_debug_snapshot!(decode_multiline(&(0..32).collect::<Vec<_>>()), @r###""\0☺☻♥♦♣♠•◘○◙♂♀\n♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼""###);
|
assert_debug_snapshot!(decode_multiline(&(0..32).collect::<Vec<_>>()), @r###""\0☺☻♥♦♣♠•◘○◙♂♀\n♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼""###);
|
||||||
|
|||||||
+4
-16
@@ -4,15 +4,10 @@ mod preprocess;
|
|||||||
mod world;
|
mod world;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use encoding::{decode_multiline, encode_multiline};
|
|
||||||
use preprocess::eval::Context;
|
use preprocess::eval::Context;
|
||||||
use std::{env, error::Error, fs, path::PathBuf, process::exit};
|
use std::{env, error::Error, fs, path::PathBuf, process::exit};
|
||||||
use world::World;
|
use world::World;
|
||||||
|
|
||||||
fn to_latin1(bytes: &[u8]) -> String {
|
|
||||||
bytes.iter().map(|&x| x as char).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
if args.len() != 2 {
|
if args.len() != 2 {
|
||||||
@@ -33,7 +28,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
println!("num boards: {}", &world.boards.len());
|
println!("num boards: {}", &world.boards.len());
|
||||||
for board in &mut world.boards {
|
for board in &mut world.boards {
|
||||||
println!("board: {}", to_latin1(&board.name));
|
println!("board: {}", board.name);
|
||||||
for stat in &mut board.stats {
|
for stat in &mut board.stats {
|
||||||
// Print some of the data parsed.
|
// Print some of the data parsed.
|
||||||
let (x, y) = (stat.x as usize, stat.y as usize);
|
let (x, y) = (stat.x as usize, stat.y as usize);
|
||||||
@@ -43,19 +38,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let terrain_index = (x - 1) + (y - 1) * 60;
|
let terrain_index = (x - 1) + (y - 1) * 60;
|
||||||
Some(board.terrain[terrain_index])
|
Some(board.terrain[terrain_index])
|
||||||
};
|
};
|
||||||
println!(
|
println!(" stat at ({}, {}): {:?}, {:?}", x, y, terrain, stat.code);
|
||||||
" stat at ({}, {}): {:?}, {:?}",
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
terrain,
|
|
||||||
to_latin1(&stat.code)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Evaluate macros
|
// Evaluate macros
|
||||||
let old_code = decode_multiline(&stat.code);
|
stat.code = eval_context.eval_program(&stat.code)?;
|
||||||
let new_code = eval_context.eval_program(&old_code)?;
|
|
||||||
stat.code = encode_multiline(&new_code)?;
|
|
||||||
}
|
}
|
||||||
|
board.name.push_str(" (♪)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to write a modified world file
|
// Try to write a modified world file
|
||||||
|
|||||||
+13
-7
@@ -10,6 +10,8 @@ use nom::{
|
|||||||
sequence::tuple,
|
sequence::tuple,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LoadError {
|
pub struct LoadError {
|
||||||
message: String,
|
message: String,
|
||||||
@@ -87,7 +89,7 @@ pub struct World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
pub name: Vec<u8>,
|
pub name: String,
|
||||||
pub terrain: Vec<[u8; 2]>,
|
pub terrain: Vec<[u8; 2]>,
|
||||||
pub max_shots: u8,
|
pub max_shots: u8,
|
||||||
pub is_dark: bool,
|
pub is_dark: bool,
|
||||||
@@ -118,7 +120,7 @@ pub struct Stat {
|
|||||||
pub under_color: u8,
|
pub under_color: u8,
|
||||||
pub instruction_pointer: i16,
|
pub instruction_pointer: i16,
|
||||||
pub bind_index: i16,
|
pub bind_index: i16,
|
||||||
pub code: Vec<u8>,
|
pub code: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
@@ -201,7 +203,8 @@ impl Board {
|
|||||||
let (input, _) = le_u16.parse(bytes)?;
|
let (input, _) = le_u16.parse(bytes)?;
|
||||||
|
|
||||||
// Read board name
|
// Read board name
|
||||||
let (input, name) = pstring(50)(input)?;
|
let (input, name_bytes) = pstring(50)(input)?;
|
||||||
|
let name = decode_oneline(&name_bytes);
|
||||||
|
|
||||||
// Read terrain
|
// Read terrain
|
||||||
const NUM_TILES: usize = 60 * 25;
|
const NUM_TILES: usize = 60 * 25;
|
||||||
@@ -256,7 +259,8 @@ impl Board {
|
|||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, &'static str> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, &'static str> {
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
result.push_padding(2); // reserve space for board size
|
result.push_padding(2); // reserve space for board size
|
||||||
result.push_string(50, &self.name)?;
|
let name_bytes = encode_oneline(&self.name).unwrap();
|
||||||
|
result.push_string(50, &name_bytes)?;
|
||||||
|
|
||||||
// Encode terrain
|
// Encode terrain
|
||||||
if self.terrain.len() != 1500 {
|
if self.terrain.len() != 1500 {
|
||||||
@@ -315,7 +319,8 @@ impl Stat {
|
|||||||
let (input, _) = take(4usize)(input)?;
|
let (input, _) = take(4usize)(input)?;
|
||||||
let (input, (instruction_pointer, length)) = tuple((le_i16, le_i16))(input)?;
|
let (input, (instruction_pointer, length)) = tuple((le_i16, le_i16))(input)?;
|
||||||
let (input, _) = take(8usize)(input)?;
|
let (input, _) = take(8usize)(input)?;
|
||||||
let (input, code) = take(0.max(length) as usize)(input)?;
|
let (input, code_bytes) = take(0.max(length) as usize)(input)?;
|
||||||
|
let code = decode_multiline(&code_bytes);
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Stat {
|
Stat {
|
||||||
@@ -333,7 +338,7 @@ impl Stat {
|
|||||||
under_color,
|
under_color,
|
||||||
instruction_pointer,
|
instruction_pointer,
|
||||||
bind_index: 0.min(length),
|
bind_index: 0.min(length),
|
||||||
code: Vec::from(code),
|
code,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -363,7 +368,8 @@ impl Stat {
|
|||||||
result.push_padding(8);
|
result.push_padding(8);
|
||||||
if self.bind_index >= 0 {
|
if self.bind_index >= 0 {
|
||||||
// TODO: more safety around bind-index XOR code
|
// TODO: more safety around bind-index XOR code
|
||||||
result.extend_from_slice(&self.code);
|
let code_bytes = encode_multiline(&self.code).unwrap();
|
||||||
|
result.extend_from_slice(&code_bytes);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user