Compare commits
4 Commits
3df54bfa52
...
4f4e27e03c
Author | SHA1 | Date |
---|---|---|
aprzn | 4f4e27e03c | 7 months ago |
aprzn | 123792a0df | 7 months ago |
aprzn | abb9713fce | 7 months ago |
aprzn | ffe9706f73 | 7 months ago |
@ -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 +1,3 @@
|
|||||||
# ZING: Z-machine Implementation for ruNning Games
|
# 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)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
mod encoding;
|
mod encoding;
|
||||||
|
mod utils;
|
||||||
|
mod state;
|
||||||
|
|
||||||
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,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…
Reference in New Issue