did the proc macro; buffer reading stuff (simplecursor0

main
aprzn 7 months ago
parent 3df54bfa52
commit ffe9706f73

288
Cargo.lock generated

@ -2,6 +2,294 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "dissimilar"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "proc-macro2"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
[[package]]
name = "syn"
version = "2.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "toml"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "trybuild"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33a5f13f11071020bb12de7a16b925d2d58636175c20c11dc5f96cb64bb6c9b3"
dependencies = [
"dissimilar",
"glob",
"serde",
"serde_derive",
"serde_json",
"termcolor",
"toml",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "zing" name = "zing"
version = "0.1.0" version = "0.1.0"
dependencies = [
"zingprocmacros",
]
[[package]]
name = "zingprocmacros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
"trybuild",
]

@ -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…
Cancel
Save