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()
}
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)]
mod tests {
use super::*;
use insta::assert_debug_snapshot;
use crate::encoding::{decode_multiline, encode_multiline};
fn serialize_code(input: &str) -> Vec<u8> {
encode_multiline(input).expect("Error in test")
}
fn serialize_title(input: &str) -> Vec<u8> {
encode_oneline(input).expect("Error in test")
}
#[test]
fn roundtrip_multiline() {
let bytes: Vec<u8> = (0..=255).collect();
@@ -47,7 +65,13 @@ mod tests {
}
#[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###"
[
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]
fn wingdings() {
assert_debug_snapshot!(decode_multiline(&(0..32).collect::<Vec<_>>()), @r###""\0☺☻♥♦♣♠•◘○◙♂♀\n♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼""###);
+4 -16
View File
@@ -4,15 +4,10 @@ mod preprocess;
mod world;
use anyhow::anyhow;
use encoding::{decode_multiline, encode_multiline};
use preprocess::eval::Context;
use std::{env, error::Error, fs, path::PathBuf, process::exit};
use world::World;
fn to_latin1(bytes: &[u8]) -> String {
bytes.iter().map(|&x| x as char).collect()
}
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
@@ -33,7 +28,7 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("num boards: {}", &world.boards.len());
for board in &mut world.boards {
println!("board: {}", to_latin1(&board.name));
println!("board: {}", board.name);
for stat in &mut board.stats {
// Print some of the data parsed.
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;
Some(board.terrain[terrain_index])
};
println!(
" stat at ({}, {}): {:?}, {:?}",
x,
y,
terrain,
to_latin1(&stat.code)
);
println!(" stat at ({}, {}): {:?}, {:?}", x, y, terrain, stat.code);
// Evaluate macros
let old_code = decode_multiline(&stat.code);
let new_code = eval_context.eval_program(&old_code)?;
stat.code = encode_multiline(&new_code)?;
stat.code = eval_context.eval_program(&stat.code)?;
}
board.name.push_str(" (♪)");
}
// Try to write a modified world file
+13 -7
View File
@@ -10,6 +10,8 @@ use nom::{
sequence::tuple,
};
use crate::encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline};
#[derive(Debug)]
pub struct LoadError {
message: String,
@@ -87,7 +89,7 @@ pub struct World {
}
pub struct Board {
pub name: Vec<u8>,
pub name: String,
pub terrain: Vec<[u8; 2]>,
pub max_shots: u8,
pub is_dark: bool,
@@ -118,7 +120,7 @@ pub struct Stat {
pub under_color: u8,
pub instruction_pointer: i16,
pub bind_index: i16,
pub code: Vec<u8>,
pub code: String,
}
impl World {
@@ -201,7 +203,8 @@ impl Board {
let (input, _) = le_u16.parse(bytes)?;
// Read board name
let (input, name) = pstring(50)(input)?;
let (input, name_bytes) = pstring(50)(input)?;
let name = decode_oneline(&name_bytes);
// Read terrain
const NUM_TILES: usize = 60 * 25;
@@ -256,7 +259,8 @@ impl Board {
pub fn to_bytes(&self) -> Result<Vec<u8>, &'static str> {
let mut result = vec![];
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
if self.terrain.len() != 1500 {
@@ -315,7 +319,8 @@ impl Stat {
let (input, _) = take(4usize)(input)?;
let (input, (instruction_pointer, length)) = tuple((le_i16, le_i16))(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((
input,
Stat {
@@ -333,7 +338,7 @@ impl Stat {
under_color,
instruction_pointer,
bind_index: 0.min(length),
code: Vec::from(code),
code,
},
))
}
@@ -363,7 +368,8 @@ impl Stat {
result.push_padding(8);
if self.bind_index >= 0 {
// 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
}