Move string encoding/decoding into world parser

This commit is contained in:
2025-05-04 14:07:04 -07:00
parent e5d47a0f3c
commit 228188ffd0
3 changed files with 54 additions and 26 deletions
+37 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
} }