Compare commits

...

4 Commits

288
Cargo.lock generated

@ -2,6 +2,294 @@
# It is not intended for manual editing.
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]]
name = "zing"
version = "0.1.0"
dependencies = [
"zingprocmacros",
]
[[package]]
name = "zingprocmacros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
"trybuild",
]

@ -1,8 +1,5 @@
[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]
[workspace]
resolver = "2"
members = [
"zing", "zingprocmacros",
]

@ -1 +1,3 @@
# ZING: Z-machine Implementation for ruNning Games
to use for tests when ready: https://github.com/jeffnyman/zifmia

@ -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,363 @@
use zingprocmacros::make_instruction_type;
use crate::{encoding::text::{decode_zchars, ZsciiString}, utils::simplecursor::SimpleCursor};
trait Opcode: Sized {
fn has_store(inst_no: u8) -> bool;
fn has_branch(inst_no: u8) -> bool;
fn has_text(inst_no: u8) -> bool;
fn from_number(inst_no: u8, store: Option<u8>, branch: Option<Branch>, text: Option<ZsciiString>) -> Result<Self, InstructionDecodeError>;
}
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 Operand {
SmallConstant(u8),
LargeConstant(u16),
Variable(u8),
}
impl Operand {
fn make_small(input: &[u8; 1]) -> Self {Self::SmallConstant(input[0])}
fn make_large(input: &[u8; 2]) -> Self {Self::LargeConstant(u16::from_be_bytes(*input))}
fn make_var(input: &[u8; 1]) -> Self {Self::Variable(input[0])}
}
enum BranchTarget {
ReturnsFalse,
ReturnsTrue,
ShiftedOffset(i16),
}
struct Branch {
branch_if_true: bool,
target: BranchTarget
}
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!(Variable2OpOpcode {
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 Long2OpOpcode = Variable2OpOpcode;
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),
/// Should not occur unless there is an error in Zing
MissingStore,
/// Should not occur unless there is an error in Zing
MissingBranch,
/// Should not occur unless there is an error in Zing
MissingText,
InsufficientOperands,
}
pub fn decode_instruction(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
struct HasStoreBranchText { store: bool, branch: bool, text: bool }
struct PotentialStoreBranchText {
store: Option<u8>,
branch: Option<Branch>,
text: Option<ZsciiString>,
}
fn decode_store_branch_text<T: Opcode>(
cursor: &mut SimpleCursor,
opcode_num: u8
) -> PotentialStoreBranchText {
let required = HasStoreBranchText {
store: T::has_store(opcode_num),
branch: T::has_branch(opcode_num),
text: T::has_text(opcode_num)
};
let store = required.store.then(|| cursor.read_const::<1>().unwrap()[0]);
let branch = required.branch.then(|| {
let &[branch_first_byte] = cursor.read_const::<1>().unwrap();
let branch_if_true = branch_first_byte & 0x80 != 0;
let branch_target_raw_high = branch_first_byte & 0x3f;
let branch_target_raw = if branch_first_byte & 0x40 == 0 {
// one byte
branch_target_raw_high as i16
} else {
// two bytes
let &[branch_target_raw_low] = cursor.read_const::<1>().unwrap();
let branch_target_raw_unsigned = i16::from_be_bytes([branch_target_raw_high, branch_target_raw_low]);
(branch_target_raw_unsigned << 2) / 4
};
match branch_target_raw {
0 => Branch { branch_if_true, target: BranchTarget::ReturnsFalse },
1 => Branch { branch_if_true, target: BranchTarget::ReturnsTrue },
n => Branch { branch_if_true, target: BranchTarget::ShiftedOffset(n - 2) },
}
});
// TODO: switch away from cursor.buf(); should not assume that the cursor is looking at the
// entire slice
let extra_cursor = cursor.clone();
let mem = extra_cursor.buf();
let alphabet_addr = u16::from_be_bytes([mem[0x08], mem[0x09]]) as usize;
let abbreviations_addr = u16::from_be_bytes([mem[0x18], mem[0x18]]) as usize;
let text = required.branch.then(|| decode_zchars(cursor, alphabet_addr, abbreviations_addr, mem).unwrap());
PotentialStoreBranchText { store, branch, text }
}
fn decode_short(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
let &[base] = cursor.read_const::<1>().unwrap();
let opcode_num = base & 0x0f;
let operand_desc = (base >> 4) & 0b11;
let operand = match operand_desc {
0b00 => Some(Operand::make_large(cursor.read_const().unwrap())),
0b01 => Some(Operand::make_small(cursor.read_const().unwrap())),
0b10 => Some(Operand::make_var(cursor.read_const().unwrap())),
0b11 => None,
_ => unreachable!()
};
let PotentialStoreBranchText { store, branch, text } = if operand.is_none() {
decode_store_branch_text::<Short0OpOpcode>(cursor, opcode_num)
} else {
decode_store_branch_text::<Short1OpOpcode>(cursor, opcode_num)
};
if let Some(operand) = operand {
Short1OpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::Short1Op { operand, opcode })
} else {
Short0OpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::Short0Op { opcode })
}
}
fn decode_long(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
enum OperandType { SmallConst, Var }
impl From<bool> for OperandType {
fn from(b: bool) -> Self {if b {Self::Var} else {Self::SmallConst}}
}
let &[base] = cursor.read_const().unwrap();
let opcode_num = base & 0x1f;
let op1_type = OperandType::from(base & 0x40 != 0);
let op2_type = OperandType::from(base & 0x20 != 0);
let operand_1 = match op1_type {
OperandType::SmallConst => Operand::make_small(cursor.read_const().unwrap()),
OperandType::Var => Operand::make_var(cursor.read_const().unwrap()),
};
let operand_2 = match op2_type {
OperandType::SmallConst => Operand::make_small(cursor.read_const().unwrap()),
OperandType::Var => Operand::make_var(cursor.read_const().unwrap()),
};
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<Long2OpOpcode>(cursor, opcode_num);
Long2OpOpcode::from_number(opcode_num, store, branch,text)
.map(|opcode| Instruction::Long2Op { operands: (operand_1, operand_2), opcode })
}
fn decode_extended(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
let _ = cursor.read_const::<1>();
let &[opcode_num] = cursor.read_const().unwrap();
let &[op_descs] = cursor.read_const().unwrap();
let operands = {
let mut operands = vec![];
for i in 0..4 {
match (op_descs >> (6 - 2 * i)) & 0b11 {
0b00 => operands.push(Operand::make_large(cursor.read_const().unwrap())),
0b01 => operands.push(Operand::make_small(cursor.read_const().unwrap())),
0b10 => operands.push(Operand::make_var(cursor.read_const().unwrap())),
0b11 => (),
_ => unreachable!()
};
}
operands
};
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<ExtendedVarOpOpcode>(cursor, opcode_num);
ExtendedVarOpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::ExtendedVarOp { operands, opcode })
}
fn decode_variable(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
const CALL_VS2_OPCODE_NUM: u8 = 12;
const CALL_VN2_OPCODE_NUM: u8 = 26;
let &[base] = cursor.read_const().unwrap();
let opcode_num = base & 0x1f;
let is_varop = base & 0x20 != 0;
if is_varop {
let mut operands = vec![];
let num_operand_desc_bytes =
if opcode_num == CALL_VN2_OPCODE_NUM
|| opcode_num == CALL_VS2_OPCODE_NUM
{2} else {1};
for _ in 0..num_operand_desc_bytes {
let &[op_descs] = cursor.read_const().unwrap();
for i in 0..4 {
match (op_descs >> (6 - 2 * i)) & 0b11 {
0b00 => operands.push(Operand::make_large(cursor.read_const().unwrap())),
0b01 => operands.push(Operand::make_small(cursor.read_const().unwrap())),
0b10 => operands.push(Operand::make_var(cursor.read_const().unwrap())),
0b11 => (),
_ => unreachable!()
};
}
}
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<VariableVarOpOpcode>(cursor, opcode_num);
VariableVarOpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::VariableVarOp { operands, opcode })
} else {
let &[op_descs] = cursor.read_const().unwrap();
let op0 = match (op_descs >> 6) & 0b11 {
0b00 => Operand::make_large(cursor.read_const().unwrap()),
0b01 => Operand::make_small(cursor.read_const().unwrap()),
0b10 => Operand::make_var(cursor.read_const().unwrap()),
0b11 => return Err(InstructionDecodeError::InsufficientOperands),
_ => unreachable!()
};
let op1 = match (op_descs >> 4) & 0b11 {
0b00 => Operand::make_large(cursor.read_const().unwrap()),
0b01 => Operand::make_small(cursor.read_const().unwrap()),
0b10 => Operand::make_var(cursor.read_const().unwrap()),
0b11 => return Err(InstructionDecodeError::InsufficientOperands),
_ => unreachable!()
};
let operands = (op0, op1);
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<Variable2OpOpcode>(cursor, opcode_num);
Variable2OpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::Variable2Op { operands, opcode })
}
}
let &[first_byte] = cursor.peek_const::<1>().unwrap();
if first_byte == 190 {
decode_extended(cursor)
} else if first_byte & 0xC0 == 0xC0 {
decode_variable(cursor)
} else if first_byte & 0xC0 == 0x80 {
decode_short(cursor)
} else {
decode_long(cursor)
}
}

@ -0,0 +1,373 @@
use crate::utils::simplecursor::SimpleCursor;
/// 5 bits
#[derive(Clone, Copy, PartialEq, Debug)]
struct ZChar(u8);
/// technically 10 bits, but top two unused so they are dropped
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct ZsciiChar(u8);
pub type ZsciiString = Vec<ZsciiChar>;
fn zscii_from_bytes(bytes: &[u8]) -> ZsciiString {
bytes.into_iter().cloned().map(ZsciiChar).collect()
}
/// 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: &mut SimpleCursor,
alphabet_table_addr: usize,
abbreviations_table_addr: usize,
memory: &[u8],
) -> Option<ZsciiString> {
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(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!("abbreviations"),
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))
}
}
}
}
}
struct ZWordIter<'a, 'b> {
cursor: &'a mut SimpleCursor<'b>,
should_continue: bool,
}
impl<'a, 'b> ZWordIter<'a, 'b> {
fn new(cursor: &'a mut SimpleCursor<'b>) -> Option<Self> {
Some(Self { cursor, should_continue: true })
}
}
impl<'a, 'b> Iterator for ZWordIter<'a, 'b> {
type Item = u16;
fn next(&mut self) -> Option<Self::Item> {
if self.should_continue {
let next_word = u16::from_be_bytes(*self.cursor.read_const()?);
if next_word & 0x8000 != 0 {
self.should_continue = false;
}
Some(next_word)
} else {
None
}
}
}
let alphabet_table = if alphabet_table_addr == 0 { None } else { Some(memory.split_at(alphabet_table_addr).1) };
let zwords = ZWordIter::new(zchars)?;
let zchars = zwords
.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())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_hello_world() {
// Encoding steps:
//
// Hello World
//
// A1: 0d
// A0: 0a 11 11 14
// 00
// A1: 1c
// A0: 14 17 11 09
//
// 04 0d 0a
// 11 11 14
// 00 04 1c
// 14 17 11
// 09 04 04
//
// 00100 01101 01010
// 10001 10001 10100
// 00000 00100 11100
// 10100 10111 10001
// 01001 00100 00100
//
// 0 00100 01101 01010
// 0 10001 10001 10100
// 0 00000 00100 11100
// 0 10100 10111 10001
// 1 01001 00100 00100
//
// 00010001 10101010
// 01000110 00110100
// 00000000 10011100
// 01010010 11110001
// 10100100 10000100
//
// 11 AA
// 46 34
// 00 9C
// 52 F1
// A4 84
// add a couple extra chars to ensure it leaves the cursor in the proper location
let memory_buf = &[
0x11,
0xaa,
0x46,
0x34,
0x00,
0x9c,
0x52,
0xf1,
0xa4,
0x84,
0x11,
0xaa
];
let mut cur = SimpleCursor::new(memory_buf);
let out = decode_zchars(&mut cur, 0, 0, memory_buf);
assert_eq!(cur.read_const(), Some(&[0x11u8]));
assert_eq!(out, Some(zscii_from_bytes(b"Hello World")));
}
}
// pub fn decode_zchars_old_2(
// 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!("abbreviations"),
// 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,11 @@
#![allow(dead_code)]
mod encoding;
mod utils;
mod state;
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
#[repr(usize)]
enum ExtensionTableFields {

@ -0,0 +1,27 @@
pub struct Z5 {
memory: [u8; 128 * 1024],
pc: usize,
call_stack: Vec<StackFrame>,
}
pub enum ReturnTarget {
ReturnIllegal,
Discard,
/// note that writing to a local variable targets the local on the stack frame
/// being returned *to*, not the stack frame being returned *from*, as the latter
/// is discarded during the return
WriteVariable(u8),
}
pub struct StackFrame {
stack: Vec<u16>,
// length must be no greater than 15
locals: Vec<u16>,
return_address: usize,
}
impl Z5 {
pub fn run_instruction(&mut self) {
todo!()
}
}

@ -0,0 +1 @@
pub mod simplecursor;

@ -0,0 +1,161 @@
#[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 new_with_index(buffer: &'a [u8], index: usize) -> Self {
Self { buffer, index }
}
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 seek_relative(&mut self, distance: isize) -> Result<(), ()> {
if -distance > self.index as isize {
Err(())
} else {
self.seek((self.index as isize + distance) as usize)
}
}
pub fn at_end(&self) -> bool {
self.index == self.buffer_length()
}
pub fn buf(&self) -> &[u8] { self.buffer }
pub fn index(&self) -> usize { self.index }
}
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: Branch, });
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::result::Result::Ok(Self::#name) }
} else {
let store = store.map(|_| quote! { store: store.ok_or(InstructionDecodeError::MissingStore)?, });
let branch = branch.map(|_| quote! { branch: branch.ok_or(InstructionDecodeError::MissingBranch)?, });
let text = text.map(|_| quote! { text: text.ok_or(InstructionDecodeError::MissingText)?, });
quote! { #number => ::std::result::Result::Ok(Self::#name {#store #branch #text}) }
}
});
quote! {
pub enum #type_name { #(#initial_enum_variants),* }
impl Opcode for #type_name {
fn has_store(inst_no: u8) -> bool {
match inst_no {
#(#has_store_branches,)*
_ => false
}
}
fn has_branch(inst_no: u8) -> bool {
match inst_no {
#(#has_branch_branches,)*
_ => false
}
}
fn has_text(inst_no: u8) -> bool {
match inst_no {
#(#has_text_branches,)*
_ => false
}
}
fn from_number(inst_no: u8, store: Option<u8>, branch: Option<Branch>, text: Option<ZsciiString>) -> Result<Self, InstructionDecodeError> {
match inst_no {
#(#constructor_function_branches,)*
_ => Err(InstructionDecodeError::InvalidOpcodeNum(inst_no))
}
}
}
}.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