did the proc macro; buffer reading stuff (simplecursor0
parent
3df54bfa52
commit
ffe9706f73
@ -1,8 +1,5 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "zing"
|
resolver = "2"
|
||||||
version = "0.1.0"
|
members = [
|
||||||
edition = "2021"
|
"zing", "zingprocmacros",
|
||||||
|
]
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
/// 5 bits
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct ZChar(u8);
|
|
||||||
|
|
||||||
/// technically 10 bits, but top two unused so they are dropped
|
|
||||||
#[derive(PartialEq, Clone, Copy)]
|
|
||||||
pub struct ZsciiChar(u8);
|
|
||||||
|
|
||||||
pub type ZsciiString = Vec<ZsciiChar>;
|
|
||||||
|
|
||||||
/// Returns:
|
|
||||||
/// - a result that wraps a ZsciiString, erroring if the slice terminates before the string ends
|
|
||||||
/// - a usize indicating how many bytes were consumed
|
|
||||||
pub fn decode_zchars(
|
|
||||||
zchars: &[u8],
|
|
||||||
alphabet_table: Option<&[u8]>,
|
|
||||||
abbreviations_table: &[u8],
|
|
||||||
) -> Option<(ZsciiString, usize)> {
|
|
||||||
fn cut_string(zchars: &[u8]) -> Option<Vec<u16>> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for word in zchars.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])) {
|
|
||||||
out.push(word);
|
|
||||||
if 0x8000 & word != 0 {
|
|
||||||
return Some(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// requires: alphabet_num < 3, 5 < char_idx < 32, !(alphabet_num = 2 AND char_idx = 6)
|
|
||||||
fn index_alphabet(
|
|
||||||
alphabet_table: Option<&[u8]>,
|
|
||||||
alphabet_number: usize,
|
|
||||||
ZChar(char_idx): ZChar,
|
|
||||||
zchars: &mut impl Iterator<Item = ZChar>,
|
|
||||||
) -> Option<ZsciiChar> {
|
|
||||||
let default_alphabet: [[ZsciiChar; 26]; 3] = [
|
|
||||||
b"abcdefghijklmnopqrstuvwxyz".map(ZsciiChar),
|
|
||||||
b"ABCDEFGHIJKLMNOPQRSTUVWXYZ".map(ZsciiChar),
|
|
||||||
br#" 0123456789.,!?_#'"/\-:()"#.map(ZsciiChar)
|
|
||||||
];
|
|
||||||
if alphabet_number == 2 && char_idx == 7 {
|
|
||||||
Some(ZsciiChar(13))
|
|
||||||
} else if alphabet_number == 2 && char_idx == 6 {
|
|
||||||
todo!()
|
|
||||||
} else if let Some(alphabet_table) = alphabet_table {
|
|
||||||
todo!()
|
|
||||||
} else {
|
|
||||||
Some(default_alphabet[alphabet_number][char_idx as usize - 6])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let zwords = cut_string(zchars)?;
|
|
||||||
let consumed_length = zwords.len() * 2;
|
|
||||||
let mut zchars = zwords.iter()
|
|
||||||
.flat_map(|word| [
|
|
||||||
(word >> 10) & 0x1f,
|
|
||||||
(word >> 5) & 0x1f,
|
|
||||||
word & 0x1f
|
|
||||||
])
|
|
||||||
.map(|word| ZChar(word as u8))
|
|
||||||
.peekable();
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
let mut current_alphabet = 0;
|
|
||||||
while let Some(char) = zchars.next() {
|
|
||||||
match char {
|
|
||||||
ZChar(0) => out.push(ZsciiChar(32)),
|
|
||||||
ZChar(1..=3) => todo!("abbreviations"),
|
|
||||||
ZChar(4..=5) => if zchars.peek().is_some_and(|&ZChar(n)| n > 5) {
|
|
||||||
if let Some(zc) = index_alphabet(alphabet_table, char.0 as usize - 3, zchars.next().unwrap(), &mut zchars) { out.push(zc) }
|
|
||||||
},
|
|
||||||
ZChar(_) => if let Some(zc) = index_alphabet(alphabet_table, 0, char, &mut zchars) {
|
|
||||||
out.push(zc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some((out, consumed_length))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Returns:
|
|
||||||
pub fn decode_instruction(memory: &[u8], address: usize) {
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "zing"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
zingprocmacros = { path = "../zingprocmacros" }
|
@ -0,0 +1,33 @@
|
|||||||
|
pub mod instruction;
|
||||||
|
pub mod text;
|
||||||
|
|
||||||
|
// see §11 (page 61) of the spec
|
||||||
|
pub mod header_fields {
|
||||||
|
const VERSION: usize = 0x0;
|
||||||
|
const FLAGS1: usize = 0x1;
|
||||||
|
const HIGH_MEMORY_START_ADDR: usize = 0x4;
|
||||||
|
const PC_INIT_VAL_ADDR: usize = 0x6;
|
||||||
|
const DICTIONARY_ADDR: usize = 0x8;
|
||||||
|
const OBJECT_TABLE_ADDR: usize = 0xA;
|
||||||
|
const GLOBALS_TABLE_ADDR: usize = 0xC;
|
||||||
|
const STATIC_MEMORY_START_ADDR: usize = 0xE;
|
||||||
|
const FLAGS2: usize = 0x10;
|
||||||
|
const ABBREVIATIONS_TABLE_ADDR: usize = 0x18;
|
||||||
|
const FILE_LENGTH: usize = 0x1A; // divided by a constant; see §11.1.6
|
||||||
|
const FILE_CHECKSUM: usize = 0x1C;
|
||||||
|
const INTERPRETER_NUMBER: usize = 0x1E;
|
||||||
|
const INTERPRETER_VERSION: usize = 0x1F;
|
||||||
|
const SCREEN_HEIGHT_CHARS: usize = 0x20;
|
||||||
|
const SCREEN_WIDTH_CHARS: usize = 0x21;
|
||||||
|
const SCREEN_HEIGHT_UNITS: usize = 0x22;
|
||||||
|
const SCREEN_WIDTH_UNITS: usize = 0x24;
|
||||||
|
const FONT_WIDTH_UNITS: usize = 0x26;
|
||||||
|
const FONT_HEIGHT_UNITS: usize = 0x27;
|
||||||
|
const BACKGROUND_COLOR: usize = 0x2C;
|
||||||
|
const FOREGROUND_COLOR: usize = 0x2D;
|
||||||
|
const TERMINATING_CHARS_TABLE_ADDR: usize = 0x2E;
|
||||||
|
const REVISION_NUMBER: usize = 0x32;
|
||||||
|
const ALPHABET_TABLE_ADDR: usize = 0x34;
|
||||||
|
const HEADER_EXTENSION_TABLE_ADDR: usize = 0x36;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,204 @@
|
|||||||
|
use zingprocmacros::make_instruction_type;
|
||||||
|
use crate::encoding::text::ZsciiString;
|
||||||
|
|
||||||
|
pub enum Instruction {
|
||||||
|
Short0Op {
|
||||||
|
opcode: Short0OpOpcode,
|
||||||
|
},
|
||||||
|
Short1Op {
|
||||||
|
operand: Operand,
|
||||||
|
opcode: Short1OpOpcode,
|
||||||
|
},
|
||||||
|
Long2Op {
|
||||||
|
operands: (Operand, Operand),
|
||||||
|
opcode: Long2OpOpcode,
|
||||||
|
},
|
||||||
|
Variable2Op {
|
||||||
|
operands: (Operand, Operand),
|
||||||
|
opcode: Variable2OpOpcode,
|
||||||
|
},
|
||||||
|
VariableVarOp {
|
||||||
|
operands: Vec<Operand>,
|
||||||
|
opcode: VariableVarOpOpcode
|
||||||
|
},
|
||||||
|
ExtendedVarOp {
|
||||||
|
operands: Vec<Operand>,
|
||||||
|
opcode: ExtendedVarOpOpcode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum VariableOperands {
|
||||||
|
TwoOp(Operand, Operand),
|
||||||
|
VarOp(Vec<Operand>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Operand {
|
||||||
|
SmallConstant(u8),
|
||||||
|
LargeConstant(u16),
|
||||||
|
Variable(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
make_instruction_type!(Short0OpOpcode {
|
||||||
|
0x0 Rtrue,
|
||||||
|
0x1 Rfalse,
|
||||||
|
0x2 Print txt,
|
||||||
|
0x3 PrintRet txt,
|
||||||
|
0x4 Nop,
|
||||||
|
0x7 Restart,
|
||||||
|
0x8 RetPopped,
|
||||||
|
0x9 Catch st,
|
||||||
|
0xA Quit,
|
||||||
|
0xB NewLine,
|
||||||
|
0xD Verify,
|
||||||
|
0xF Piracy
|
||||||
|
});
|
||||||
|
|
||||||
|
make_instruction_type!(Short1OpOpcode {
|
||||||
|
0x0 Jz br,
|
||||||
|
0x1 GetSibling st br,
|
||||||
|
0x2 GetChild st br,
|
||||||
|
0x3 GetParent st,
|
||||||
|
0x4 GetPropLen st,
|
||||||
|
0x5 Inc,
|
||||||
|
0x6 Dec,
|
||||||
|
0x7 PrintAddr,
|
||||||
|
0x8 Call1s st,
|
||||||
|
0x9 RemoveObj,
|
||||||
|
0xA PrintObj,
|
||||||
|
0xB Ret,
|
||||||
|
0xC Jump,
|
||||||
|
0xD PrintPaddr,
|
||||||
|
0xE Load st,
|
||||||
|
0xF Call1n,
|
||||||
|
});
|
||||||
|
|
||||||
|
make_instruction_type!(Long2OpOpcode {
|
||||||
|
1 Je br,
|
||||||
|
2 Jl br,
|
||||||
|
3 Jg br,
|
||||||
|
4 DecChk br,
|
||||||
|
5 IncChk br,
|
||||||
|
6 Jin br,
|
||||||
|
7 Test br,
|
||||||
|
8 Or st,
|
||||||
|
9 And st,
|
||||||
|
10 TestAttr br,
|
||||||
|
11 SetAttr,
|
||||||
|
12 ClearAtr,
|
||||||
|
13 Store,
|
||||||
|
14 InsertObj,
|
||||||
|
15 Loadw st,
|
||||||
|
16 Loadb st,
|
||||||
|
17 GetProp st,
|
||||||
|
18 GetPropAddr st,
|
||||||
|
19 GetNextProp st,
|
||||||
|
20 Add st,
|
||||||
|
21 Sub st,
|
||||||
|
22 Mul st,
|
||||||
|
23 Div st,
|
||||||
|
24 Mod st,
|
||||||
|
25 Call2s st,
|
||||||
|
26 Call2n,
|
||||||
|
27 SetColour,
|
||||||
|
28 Throw,
|
||||||
|
});
|
||||||
|
|
||||||
|
type Variable2OpOpcode = Long2OpOpcode;
|
||||||
|
|
||||||
|
make_instruction_type!(VariableVarOpOpcode {
|
||||||
|
0 CallVs,
|
||||||
|
1 Storew,
|
||||||
|
2 Storeb,
|
||||||
|
3 PutProp,
|
||||||
|
4 Aread st,
|
||||||
|
5 PrintChar,
|
||||||
|
6 PrintNum,
|
||||||
|
7 Random st,
|
||||||
|
8 Push,
|
||||||
|
9 Pull,
|
||||||
|
10 SplitWindow,
|
||||||
|
11 SetWindow,
|
||||||
|
12 CallVs2 st,
|
||||||
|
13 EraseWindow,
|
||||||
|
14 EraseLine,
|
||||||
|
15 SetCursor,
|
||||||
|
16 GetCursor,
|
||||||
|
17 SetTextStyle,
|
||||||
|
18 BufferMode,
|
||||||
|
19 OutputStream,
|
||||||
|
20 InputStream,
|
||||||
|
21 SoundEffect,
|
||||||
|
22 ReadChar st,
|
||||||
|
23 ScanTable st br,
|
||||||
|
24 Not st,
|
||||||
|
25 CallVn,
|
||||||
|
26 CallVn2,
|
||||||
|
27 Tokenise,
|
||||||
|
28 EncodeText,
|
||||||
|
29 CopyTable,
|
||||||
|
30 PrintTable,
|
||||||
|
31 CheckArgCount br,
|
||||||
|
});
|
||||||
|
make_instruction_type!(ExtendedVarOpOpcode {
|
||||||
|
0 Save st,
|
||||||
|
1 Restore st,
|
||||||
|
2 LogShift st,
|
||||||
|
3 ArtShift st,
|
||||||
|
4 SetFont st,
|
||||||
|
5 SaveUndo st,
|
||||||
|
6 RestoreUndo st,
|
||||||
|
11 PrintUnicode,
|
||||||
|
12 CheckUnicode,
|
||||||
|
13 SetTrueColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
pub enum InstructionDecodeError {
|
||||||
|
InvalidOpcodeNum(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns: Some((instruction, instruction_length)) if instruction is valid, None otherwise
|
||||||
|
pub fn decode_instruction(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
||||||
|
// TODO
|
||||||
|
fn decode_short(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
||||||
|
let mut cursor = address;
|
||||||
|
let base = memory[cursor];
|
||||||
|
cursor += 1;
|
||||||
|
let opcode_num = base & 0x0f;
|
||||||
|
let has_operand = base & 0x30 != 0x30;
|
||||||
|
if has_operand {
|
||||||
|
todo!("get operand and define store/branch/text if necessary");
|
||||||
|
// let instruction = Short1OpOpcode::from_number(opcode_num, store, branch, text).ok_or(InstructionDecodeError::InvalidOpcodeNum(opcode_num))?;
|
||||||
|
todo!()
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
fn decode_long(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
fn decode_extended(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
fn decode_variable(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
if memory[address] == 190 {
|
||||||
|
decode_extended(memory, address)
|
||||||
|
} else if memory[address] & 0xC0 == 0xC0 {
|
||||||
|
decode_variable(memory, address)
|
||||||
|
} else if memory[address] & 0xC0 == 0x80 {
|
||||||
|
decode_short(memory, address)
|
||||||
|
} else {
|
||||||
|
decode_long(memory, address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
|||||||
|
/// 5 bits
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
struct ZChar(u8);
|
||||||
|
|
||||||
|
/// technically 10 bits, but top two unused so they are dropped
|
||||||
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
|
pub struct ZsciiChar(u8);
|
||||||
|
|
||||||
|
pub type ZsciiString = Vec<ZsciiChar>;
|
||||||
|
|
||||||
|
/// Returns:
|
||||||
|
/// - a result that wraps a ZsciiString, erroring if the slice terminates before the string ends
|
||||||
|
/// - a usize indicating how many bytes were consumed
|
||||||
|
pub fn decode_zchars(
|
||||||
|
zchars: &[u8],
|
||||||
|
alphabet_table_addr: usize,
|
||||||
|
abbreviations_table_addr: usize,
|
||||||
|
memory: &[u8],
|
||||||
|
) -> Option<(ZsciiString, usize)> {
|
||||||
|
fn cut_string(zchars: &[u8]) -> Option<Vec<u16>> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for word in zchars.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])) {
|
||||||
|
out.push(word);
|
||||||
|
if 0x8000 & word != 0 {
|
||||||
|
return Some(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn get_from_alphabet(alphabet_number: usize, ZChar(codepoint): ZChar, alphabet_table: Option<&[u8]>) -> ZsciiChar {
|
||||||
|
|
||||||
|
ZsciiChar(
|
||||||
|
alphabet_table
|
||||||
|
// ~'s indicate invalid characters (reserved values A2:6 and A2:7)
|
||||||
|
.unwrap_or_else(|| br#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~~0123456789.,!?_#'"/\-:()"#)
|
||||||
|
[26 * alphabet_number + codepoint as usize - 6]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
struct ZsciiSequence<'a, T>
|
||||||
|
where T: Iterator<Item = ZChar> {
|
||||||
|
zchars: std::iter::Peekable<T>,
|
||||||
|
subseq: Option<Box<ZsciiSequence<'a, T>>>,
|
||||||
|
alphabet_number: usize,
|
||||||
|
alphabet_table: Option<&'a [u8]>
|
||||||
|
}
|
||||||
|
impl<'a, T> Iterator for ZsciiSequence<'a, T>
|
||||||
|
where T: Iterator<Item = ZChar> {
|
||||||
|
type Item = ZsciiChar;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.subseq.as_mut() {
|
||||||
|
Some(subseq) => {
|
||||||
|
let out = subseq.next();
|
||||||
|
if subseq.next().is_none() { self.subseq = None; }
|
||||||
|
out
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let char = self.zchars.next()?;
|
||||||
|
match char {
|
||||||
|
ZChar(0) => Some(ZsciiChar(32)),
|
||||||
|
ZChar(1..=3) => {
|
||||||
|
self.subseq = Some(Box::new(ZsciiSequence {
|
||||||
|
zchars: todo!(),
|
||||||
|
subseq: None,
|
||||||
|
alphabet_number: self.alphabet_number,
|
||||||
|
alphabet_table: self.alphabet_table
|
||||||
|
}));
|
||||||
|
self.next()
|
||||||
|
},
|
||||||
|
ZChar(4..=5) => {
|
||||||
|
if char == ZChar(5) && self.zchars.peek() == Some(&ZChar(6)) {
|
||||||
|
Some(ZsciiChar(13))
|
||||||
|
} else if char == ZChar(5) && self.zchars.peek() == Some(&ZChar(7)) {
|
||||||
|
let _ = self.zchars.next()?;
|
||||||
|
let ZChar(z0) = self.zchars.next()?;
|
||||||
|
let ZChar(z1) = self.zchars.next()?;
|
||||||
|
Some(ZsciiChar((z0 << 5) | z1))
|
||||||
|
} else {
|
||||||
|
self.alphabet_number = char.0 as usize - 3;
|
||||||
|
let out = self.next();
|
||||||
|
self.alphabet_number = 0;
|
||||||
|
out
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ZChar(_) => Some(get_from_alphabet(self.alphabet_number, char, self.alphabet_table))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let alphabet_table = if alphabet_table_addr == 0 { None } else { Some(memory.split_at(alphabet_table_addr).1) };
|
||||||
|
let zwords = cut_string(zchars)?;
|
||||||
|
let consumed_length = zwords.len() * 2;
|
||||||
|
let zchars = zwords.iter()
|
||||||
|
.flat_map(|word| [
|
||||||
|
(word >> 10) & 0x1f,
|
||||||
|
(word >> 5) & 0x1f,
|
||||||
|
word & 0x1f
|
||||||
|
])
|
||||||
|
.map(|word| ZChar(word as u8))
|
||||||
|
.peekable();
|
||||||
|
Some((ZsciiSequence {
|
||||||
|
zchars,
|
||||||
|
subseq: None,
|
||||||
|
alphabet_number: 0,
|
||||||
|
alphabet_table,
|
||||||
|
}.collect(), consumed_length))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// pub fn decode_zchars_old(
|
||||||
|
// zchars: &[u8],
|
||||||
|
// alphabet_table: Option<&[u8]>,
|
||||||
|
// abbreviations_table: &[u8],
|
||||||
|
// ) -> Option<(ZsciiString, usize)> {
|
||||||
|
// fn cut_string(zchars: &[u8]) -> Option<Vec<u16>> {
|
||||||
|
// let mut out = Vec::new();
|
||||||
|
// for word in zchars.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])) {
|
||||||
|
// out.push(word);
|
||||||
|
// if 0x8000 & word != 0 {
|
||||||
|
// return Some(out);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// None
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// requires: alphabet_num < 3, 5 < char_idx < 32, !(alphabet_num = 2 AND char_idx = 6)
|
||||||
|
// fn index_alphabet(
|
||||||
|
// alphabet_table: Option<&[u8]>,
|
||||||
|
// alphabet_number: usize,
|
||||||
|
// ZChar(char_idx): ZChar,
|
||||||
|
// zchars: &mut impl Iterator<Item = ZChar>,
|
||||||
|
// ) -> Option<ZsciiChar> {
|
||||||
|
// let default_alphabet: [[ZsciiChar; 26]; 3] = [
|
||||||
|
// b"abcdefghijklmnopqrstuvwxyz".map(ZsciiChar),
|
||||||
|
// b"ABCDEFGHIJKLMNOPQRSTUVWXYZ".map(ZsciiChar),
|
||||||
|
// br#" 0123456789.,!?_#'"/\-:()"#.map(ZsciiChar)
|
||||||
|
// ];
|
||||||
|
// if alphabet_number == 2 && char_idx == 7 {
|
||||||
|
// // newline
|
||||||
|
// Some(ZsciiChar(13))
|
||||||
|
// } else if alphabet_number == 2 && char_idx == 6 {
|
||||||
|
// // direct zscii
|
||||||
|
// let ZChar(next_0) = zchars.next()?;
|
||||||
|
// let ZChar(next_1) = zchars.next()?;
|
||||||
|
// Some(ZsciiChar((next_0 << 5) | next_1))
|
||||||
|
// } else if let Some(alphabet_table) = alphabet_table {
|
||||||
|
// // custom alphabet table
|
||||||
|
// Some(ZsciiChar(alphabet_table[alphabet_number * 26 + char_idx as usize - 6]))
|
||||||
|
// } else {
|
||||||
|
// // default alphabet table
|
||||||
|
// Some(default_alphabet[alphabet_number][char_idx as usize - 6])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let zwords = cut_string(zchars)?;
|
||||||
|
// let consumed_length = zwords.len() * 2;
|
||||||
|
// let mut zchars = zwords.iter()
|
||||||
|
// .flat_map(|word| [
|
||||||
|
// (word >> 10) & 0x1f,
|
||||||
|
// (word >> 5) & 0x1f,
|
||||||
|
// word & 0x1f
|
||||||
|
// ])
|
||||||
|
// .map(|word| ZChar(word as u8))
|
||||||
|
// .peekable();
|
||||||
|
|
||||||
|
// let mut out = Vec::new();
|
||||||
|
// let mut current_alphabet = 0;
|
||||||
|
// while let Some(char) = zchars.next() {
|
||||||
|
// match char {
|
||||||
|
// ZChar(0) => out.push(ZsciiChar(32)),
|
||||||
|
// ZChar(1..=3) => todo!("abbreviations"),
|
||||||
|
// ZChar(4..=5) => if zchars.peek().is_some_and(|&ZChar(n)| n > 5) {
|
||||||
|
// if let Some(zc) = index_alphabet(alphabet_table, char.0 as usize - 3, zchars.next().unwrap(), &mut zchars) { out.push(zc) }
|
||||||
|
// },
|
||||||
|
// ZChar(_) => if let Some(zc) = index_alphabet(alphabet_table, 0, char, &mut zchars) {
|
||||||
|
// out.push(zc)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Some((out, consumed_length))
|
||||||
|
// }
|
@ -1,40 +1,10 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
mod encoding;
|
mod encoding;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
// see §11 (page 61) of the spec
|
|
||||||
#[repr(usize)]
|
|
||||||
enum HeaderFields {
|
|
||||||
Version = 0x0,
|
|
||||||
Flags1,
|
|
||||||
HighMemoryStartAddr = 0x4,
|
|
||||||
PCInitValAddr = 0x6,
|
|
||||||
DictionaryAddr = 0x8,
|
|
||||||
ObjectTableAddr = 0xA,
|
|
||||||
GlobalsTableAddr = 0xC,
|
|
||||||
StaticMemoryStartAddr = 0xE,
|
|
||||||
Flags2 = 0x10,
|
|
||||||
AbbreviationsTableaddr = 0x18,
|
|
||||||
FileLength = 0x1A, // divided by a constant; see §11.1.6
|
|
||||||
FileChecksum = 0x1C,
|
|
||||||
InterpreterNumber = 0x1E,
|
|
||||||
InterpreterVersion = 0x1F,
|
|
||||||
ScreenHeightChars = 0x20,
|
|
||||||
ScreenWidthChars = 0x21,
|
|
||||||
ScreenHeightUnits = 0x22,
|
|
||||||
ScreenWidthUnits = 0x24,
|
|
||||||
FontWidthUnits = 0x26,
|
|
||||||
FontHeightUnits = 0x27,
|
|
||||||
BackgroundColor = 0x2C,
|
|
||||||
ForegroundColor = 0x2D,
|
|
||||||
TerminatingCharsTableAddr = 0x2E,
|
|
||||||
RevisionNumber = 0x32,
|
|
||||||
AlphabetTableAddr = 0x34,
|
|
||||||
HeaderExtensionTableAddr = 0x36,
|
|
||||||
}
|
|
||||||
|
|
||||||
// note: this is a table of words, unlike the regular header which is all bytes
|
// note: this is a table of words, unlike the regular header which is all bytes
|
||||||
#[repr(usize)]
|
#[repr(usize)]
|
||||||
enum ExtensionTableFields {
|
enum ExtensionTableFields {
|
@ -0,0 +1 @@
|
|||||||
|
pub mod simplecursor;
|
@ -0,0 +1,149 @@
|
|||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SimpleCursor<'a> {
|
||||||
|
buffer: &'a [u8],
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SimpleCursor<'a> {
|
||||||
|
pub fn new(buffer: &'a [u8]) -> Self {
|
||||||
|
Self { buffer, index: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer_length(&self) -> usize { self.buffer.len() }
|
||||||
|
|
||||||
|
fn remaining_length(&self) -> usize {
|
||||||
|
self.buffer_length() - self.index
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_const<const N: usize>(&mut self) -> Option<&[u8; N]> {
|
||||||
|
if self.remaining_length() >= N {
|
||||||
|
// safety: first chunk is of length N < remaining_length
|
||||||
|
let out = self.buffer[self.index..].first_chunk().unwrap();
|
||||||
|
self.index += N;
|
||||||
|
Some(out)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self, n: usize) -> Option<&[u8]> {
|
||||||
|
if self.remaining_length() >= n {
|
||||||
|
let out = &self.buffer[self.index..(self.index + n)];
|
||||||
|
self.index += n;
|
||||||
|
Some(out)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_to_end(&mut self) -> &[u8] {
|
||||||
|
let out = &self.buffer[self.index..self.buffer_length()];
|
||||||
|
self.index = self.buffer_length();
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek_const<const N: usize>(&self) -> Option<&[u8; N]> {
|
||||||
|
if self.remaining_length() >= N {
|
||||||
|
// safety: first chunk is of length N < remaining_length
|
||||||
|
Some(self.buffer[self.index..].first_chunk().unwrap())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(&self, n: usize) -> Option<&[u8]> {
|
||||||
|
if self.remaining_length() >= n {
|
||||||
|
Some(&self.buffer[self.index..(self.index + n)])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek_to_end(&self) -> &[u8] {
|
||||||
|
&self.buffer[self.index..self.buffer_length()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seek(&mut self, index: usize) -> Result<(), ()> {
|
||||||
|
if index <= self.buffer_length() {
|
||||||
|
self.index = index;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn at_end(&self) -> bool {
|
||||||
|
self.index == self.buffer_length()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buf(&self) -> &[u8] {
|
||||||
|
self.buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for SimpleCursor<'a> {
|
||||||
|
type Item = u8;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.read_const::<1>().map(|s| s[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn read_const() {
|
||||||
|
let buf = &[1, 2, 3, 4, 5];
|
||||||
|
let mut c = SimpleCursor::new(buf);
|
||||||
|
assert_eq!(c.read_const::<3>(), Some(&[1, 2, 3]));
|
||||||
|
assert_eq!(c.read_const::<3>(), None);
|
||||||
|
assert_eq!(c.read_const::<2>(), Some(&[4, 5]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_non_const() {
|
||||||
|
let buf = &[1, 2, 3, 4, 5];
|
||||||
|
let mut c = SimpleCursor::new(buf);
|
||||||
|
let Some(a) = c.read(3) else {panic!("reading 3 should succeed")};
|
||||||
|
assert_eq!(a.len(), 3);
|
||||||
|
assert_eq!(a[0], 1);
|
||||||
|
assert_eq!(a[1], 2);
|
||||||
|
assert_eq!(a[2], 3);
|
||||||
|
assert_eq!(c.read(3), None);
|
||||||
|
let Some(a) = c.read(2) else {panic!("reading 2 should succeed")};
|
||||||
|
assert_eq!(a.len(), 2);
|
||||||
|
assert_eq!(a[0], 4);
|
||||||
|
assert_eq!(a[1], 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_to_end() {
|
||||||
|
let buf = &[1, 2, 3, 4, 5];
|
||||||
|
let mut c = SimpleCursor::new(buf);
|
||||||
|
assert_eq!(c.read_const::<2>(), Some(&[1, 2]));
|
||||||
|
let a = c.read_to_end();
|
||||||
|
assert_eq!(a.len(), 3);
|
||||||
|
assert_eq!(a[0], 3);
|
||||||
|
assert_eq!(a[1], 4);
|
||||||
|
assert_eq!(a[2], 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seek() {
|
||||||
|
let buf = &[1, 2, 3, 4, 5];
|
||||||
|
let mut c = SimpleCursor::new(buf);
|
||||||
|
assert_eq!(c.read_const::<3>(), Some(&[1, 2, 3]));
|
||||||
|
c.seek(1).unwrap();
|
||||||
|
assert_eq!(c.read_const::<3>(), Some(&[2, 3, 4]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seek_fail() {
|
||||||
|
let buf = &[1, 2, 3, 4, 5];
|
||||||
|
let mut c = SimpleCursor::new(buf);
|
||||||
|
assert_eq!(c.seek(5), Ok(()));
|
||||||
|
assert_eq!(c.seek(4), Ok(()));
|
||||||
|
assert_eq!(c.seek(6), Err(()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "zingprocmacros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
trybuild = { version = "1.0.49", features = ["diff"] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1.0.36"
|
||||||
|
syn = "2.0.63"
|
@ -0,0 +1,17 @@
|
|||||||
|
use zingprocmacros::make_instruction_type;
|
||||||
|
|
||||||
|
struct ZsciiString;
|
||||||
|
|
||||||
|
make_instruction_type!{MyInstruction {
|
||||||
|
1 FirstInst,
|
||||||
|
0b10 SecondInst st,
|
||||||
|
3u8 ThirdInst br txt,
|
||||||
|
12 FourthInst txt,
|
||||||
|
0xC FifthInst br,
|
||||||
|
}}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let one = MyInstruction::FirstInst;
|
||||||
|
let two = MyInstruction::SecondInst { store: 0 };
|
||||||
|
let three = MyInstruction::ThirdInst { branch: 0, text: ZsciiString };
|
||||||
|
}
|
@ -0,0 +1,150 @@
|
|||||||
|
use quote::quote;
|
||||||
|
use syn::{braced, parse::{Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, token::Brace, Ident, LitInt, Token};
|
||||||
|
|
||||||
|
mod token {
|
||||||
|
syn::custom_keyword!(st);
|
||||||
|
syn::custom_keyword!(br);
|
||||||
|
syn::custom_keyword!(txt);
|
||||||
|
pub type Store = st;
|
||||||
|
pub type Branch = br;
|
||||||
|
pub type Text = txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InstructionEntry {
|
||||||
|
number: LitInt,
|
||||||
|
name: Ident,
|
||||||
|
store: Option<token::Store>,
|
||||||
|
branch: Option<token::Branch>,
|
||||||
|
text: Option<token::Text>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InstructionType {
|
||||||
|
type_name: Ident,
|
||||||
|
brace_token: Brace,
|
||||||
|
entries: Punctuated<InstructionEntry, Token![,]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for InstructionEntry {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
number: input.parse()?,
|
||||||
|
name: input.parse()?,
|
||||||
|
store: input.parse()?,
|
||||||
|
branch: input.parse()?,
|
||||||
|
text: input.parse()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for InstructionType {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let content;
|
||||||
|
Ok(Self {
|
||||||
|
type_name: input.parse()?,
|
||||||
|
brace_token: braced!(content in input),
|
||||||
|
entries: content.parse_terminated(InstructionEntry::parse, Token![,])?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn make_instruction_type(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let instruction_type = parse_macro_input!(tokens as InstructionType);
|
||||||
|
let type_name = instruction_type.type_name;
|
||||||
|
|
||||||
|
let number_values = instruction_type.entries.iter().map(|e| e.number.base10_parse::<u8>().unwrap());
|
||||||
|
// check for duplicates
|
||||||
|
{
|
||||||
|
let mut buckets = [0u8; 256];
|
||||||
|
for n in number_values {
|
||||||
|
let n = n as usize;
|
||||||
|
buckets[n] += 1;
|
||||||
|
if buckets[n] == 2 {panic!("no duplicate numeric entries allowed! duplicated number: {}", n)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let initial_enum_variants = instruction_type.entries.iter()
|
||||||
|
.map(|InstructionEntry { name, store, branch, text, .. }| {
|
||||||
|
if store.is_none() && branch.is_none() && text.is_none() {
|
||||||
|
quote!{#name}
|
||||||
|
} else {
|
||||||
|
let store = store.map(|_| quote! { store: u8, });
|
||||||
|
let branch = branch.map(|_| quote! { branch: u16, });
|
||||||
|
let text = text.map(|_| quote! { text: ZsciiString, });
|
||||||
|
quote! { #name { #store #branch #text } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let variant_names = instruction_type.entries.iter().map(|e| &e.name);
|
||||||
|
let has_store_branches = instruction_type.entries.iter().map(|e| {
|
||||||
|
let has_store = e.store.is_some();
|
||||||
|
let number = &e.number;
|
||||||
|
quote!{ #number => #has_store }
|
||||||
|
});
|
||||||
|
let has_branch_branches = instruction_type.entries.iter().map(|e| {
|
||||||
|
let has_branch = e.branch.is_some();
|
||||||
|
let number = &e.number;
|
||||||
|
quote!{ #number => #has_branch }
|
||||||
|
});
|
||||||
|
let has_text_branches = instruction_type.entries.iter().map(|e| {
|
||||||
|
let has_text = e.text.is_some();
|
||||||
|
let number = &e.number;
|
||||||
|
quote!{ #number => #has_text }
|
||||||
|
});
|
||||||
|
|
||||||
|
let constructor_function_branches = instruction_type.entries.iter().map(|InstructionEntry { number, name, store, branch, text }| {
|
||||||
|
if store.is_none() && branch.is_none() && text.is_none() {
|
||||||
|
quote!{ #number => ::std::option::Option::Some(Self::#name) }
|
||||||
|
} else {
|
||||||
|
let store = store.map(|_| quote! { store: store?, });
|
||||||
|
let branch = branch.map(|_| quote! { branch: branch?, });
|
||||||
|
let text = text.map(|_| quote! { text: text?, });
|
||||||
|
quote! { #number => ::std::option::Option::Some(Self::#name {#store #branch #text}) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub enum #type_name { #(#initial_enum_variants),* }
|
||||||
|
|
||||||
|
impl #type_name {
|
||||||
|
pub fn has_store(inst_no: u8) -> bool {
|
||||||
|
match inst_no {
|
||||||
|
#(#has_store_branches,)*
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_branch(inst_no: u8) -> bool {
|
||||||
|
match inst_no {
|
||||||
|
#(#has_branch_branches,)*
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_text(inst_no: u8) -> bool {
|
||||||
|
match inst_no {
|
||||||
|
#(#has_text_branches,)*
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_number(inst_no: u8, store: Option<u8>, branch: Option<u16>, text: Option<ZsciiString>) -> Option<Self> {
|
||||||
|
match inst_no {
|
||||||
|
#(#constructor_function_branches,)*
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn builds() {
|
||||||
|
let t = trybuild::TestCases::new();
|
||||||
|
t.pass("tests/*.rs");
|
||||||
|
// t.compile_fail("negative-tests/*.rs");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
use zingprocmacros::make_instruction_type;
|
||||||
|
|
||||||
|
struct ZsciiString;
|
||||||
|
|
||||||
|
make_instruction_type!{MyInstruction {
|
||||||
|
1 FirstInst,
|
||||||
|
0b10 SecondInst st,
|
||||||
|
3u8 ThirdInst br txt,
|
||||||
|
}}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let one = MyInstruction::FirstInst;
|
||||||
|
let two = MyInstruction::SecondInst { store: 0 };
|
||||||
|
let three = MyInstruction::ThirdInst { branch: 0, text: ZsciiString };
|
||||||
|
}
|
Loading…
Reference in New Issue