did the proc macro; buffer reading stuff (simplecursor0
parent
3df54bfa52
commit
ffe9706f73
@ -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,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)]
|
||||
|
||||
mod encoding;
|
||||
mod utils;
|
||||
|
||||
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 @@
|
||||
pub mod simplecursor;
|
@ -0,0 +1,149 @@
|
||||
#[derive(Clone)]
|
||||
pub struct SimpleCursor<'a> {
|
||||
buffer: &'a [u8],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> SimpleCursor<'a> {
|
||||
pub fn new(buffer: &'a [u8]) -> Self {
|
||||
Self { buffer, index: 0 }
|
||||
}
|
||||
|
||||
pub fn buffer_length(&self) -> usize { self.buffer.len() }
|
||||
|
||||
fn remaining_length(&self) -> usize {
|
||||
self.buffer_length() - self.index
|
||||
}
|
||||
|
||||
pub fn read_const<const N: usize>(&mut self) -> Option<&[u8; N]> {
|
||||
if self.remaining_length() >= N {
|
||||
// safety: first chunk is of length N < remaining_length
|
||||
let out = self.buffer[self.index..].first_chunk().unwrap();
|
||||
self.index += N;
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self, n: usize) -> Option<&[u8]> {
|
||||
if self.remaining_length() >= n {
|
||||
let out = &self.buffer[self.index..(self.index + n)];
|
||||
self.index += n;
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_to_end(&mut self) -> &[u8] {
|
||||
let out = &self.buffer[self.index..self.buffer_length()];
|
||||
self.index = self.buffer_length();
|
||||
out
|
||||
}
|
||||
|
||||
pub fn peek_const<const N: usize>(&self) -> Option<&[u8; N]> {
|
||||
if self.remaining_length() >= N {
|
||||
// safety: first chunk is of length N < remaining_length
|
||||
Some(self.buffer[self.index..].first_chunk().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(&self, n: usize) -> Option<&[u8]> {
|
||||
if self.remaining_length() >= n {
|
||||
Some(&self.buffer[self.index..(self.index + n)])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek_to_end(&self) -> &[u8] {
|
||||
&self.buffer[self.index..self.buffer_length()]
|
||||
}
|
||||
|
||||
pub fn seek(&mut self, index: usize) -> Result<(), ()> {
|
||||
if index <= self.buffer_length() {
|
||||
self.index = index;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at_end(&self) -> bool {
|
||||
self.index == self.buffer_length()
|
||||
}
|
||||
|
||||
pub fn buf(&self) -> &[u8] {
|
||||
self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SimpleCursor<'a> {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.read_const::<1>().map(|s| s[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn read_const() {
|
||||
let buf = &[1, 2, 3, 4, 5];
|
||||
let mut c = SimpleCursor::new(buf);
|
||||
assert_eq!(c.read_const::<3>(), Some(&[1, 2, 3]));
|
||||
assert_eq!(c.read_const::<3>(), None);
|
||||
assert_eq!(c.read_const::<2>(), Some(&[4, 5]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_non_const() {
|
||||
let buf = &[1, 2, 3, 4, 5];
|
||||
let mut c = SimpleCursor::new(buf);
|
||||
let Some(a) = c.read(3) else {panic!("reading 3 should succeed")};
|
||||
assert_eq!(a.len(), 3);
|
||||
assert_eq!(a[0], 1);
|
||||
assert_eq!(a[1], 2);
|
||||
assert_eq!(a[2], 3);
|
||||
assert_eq!(c.read(3), None);
|
||||
let Some(a) = c.read(2) else {panic!("reading 2 should succeed")};
|
||||
assert_eq!(a.len(), 2);
|
||||
assert_eq!(a[0], 4);
|
||||
assert_eq!(a[1], 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_to_end() {
|
||||
let buf = &[1, 2, 3, 4, 5];
|
||||
let mut c = SimpleCursor::new(buf);
|
||||
assert_eq!(c.read_const::<2>(), Some(&[1, 2]));
|
||||
let a = c.read_to_end();
|
||||
assert_eq!(a.len(), 3);
|
||||
assert_eq!(a[0], 3);
|
||||
assert_eq!(a[1], 4);
|
||||
assert_eq!(a[2], 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seek() {
|
||||
let buf = &[1, 2, 3, 4, 5];
|
||||
let mut c = SimpleCursor::new(buf);
|
||||
assert_eq!(c.read_const::<3>(), Some(&[1, 2, 3]));
|
||||
c.seek(1).unwrap();
|
||||
assert_eq!(c.read_const::<3>(), Some(&[2, 3, 4]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seek_fail() {
|
||||
let buf = &[1, 2, 3, 4, 5];
|
||||
let mut c = SimpleCursor::new(buf);
|
||||
assert_eq!(c.seek(5), Ok(()));
|
||||
assert_eq!(c.seek(4), Ok(()));
|
||||
assert_eq!(c.seek(6), Err(()));
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "zingprocmacros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = { version = "1.0.49", features = ["diff"] }
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.36"
|
||||
syn = "2.0.63"
|
@ -0,0 +1,17 @@
|
||||
use zingprocmacros::make_instruction_type;
|
||||
|
||||
struct ZsciiString;
|
||||
|
||||
make_instruction_type!{MyInstruction {
|
||||
1 FirstInst,
|
||||
0b10 SecondInst st,
|
||||
3u8 ThirdInst br txt,
|
||||
12 FourthInst txt,
|
||||
0xC FifthInst br,
|
||||
}}
|
||||
|
||||
fn main() {
|
||||
let one = MyInstruction::FirstInst;
|
||||
let two = MyInstruction::SecondInst { store: 0 };
|
||||
let three = MyInstruction::ThirdInst { branch: 0, text: ZsciiString };
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
use quote::quote;
|
||||
use syn::{braced, parse::{Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, token::Brace, Ident, LitInt, Token};
|
||||
|
||||
mod token {
|
||||
syn::custom_keyword!(st);
|
||||
syn::custom_keyword!(br);
|
||||
syn::custom_keyword!(txt);
|
||||
pub type Store = st;
|
||||
pub type Branch = br;
|
||||
pub type Text = txt;
|
||||
}
|
||||
|
||||
struct InstructionEntry {
|
||||
number: LitInt,
|
||||
name: Ident,
|
||||
store: Option<token::Store>,
|
||||
branch: Option<token::Branch>,
|
||||
text: Option<token::Text>,
|
||||
}
|
||||
|
||||
struct InstructionType {
|
||||
type_name: Ident,
|
||||
brace_token: Brace,
|
||||
entries: Punctuated<InstructionEntry, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for InstructionEntry {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
number: input.parse()?,
|
||||
name: input.parse()?,
|
||||
store: input.parse()?,
|
||||
branch: input.parse()?,
|
||||
text: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for InstructionType {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
Ok(Self {
|
||||
type_name: input.parse()?,
|
||||
brace_token: braced!(content in input),
|
||||
entries: content.parse_terminated(InstructionEntry::parse, Token![,])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn make_instruction_type(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let instruction_type = parse_macro_input!(tokens as InstructionType);
|
||||
let type_name = instruction_type.type_name;
|
||||
|
||||
let number_values = instruction_type.entries.iter().map(|e| e.number.base10_parse::<u8>().unwrap());
|
||||
// check for duplicates
|
||||
{
|
||||
let mut buckets = [0u8; 256];
|
||||
for n in number_values {
|
||||
let n = n as usize;
|
||||
buckets[n] += 1;
|
||||
if buckets[n] == 2 {panic!("no duplicate numeric entries allowed! duplicated number: {}", n)}
|
||||
}
|
||||
}
|
||||
|
||||
let initial_enum_variants = instruction_type.entries.iter()
|
||||
.map(|InstructionEntry { name, store, branch, text, .. }| {
|
||||
if store.is_none() && branch.is_none() && text.is_none() {
|
||||
quote!{#name}
|
||||
} else {
|
||||
let store = store.map(|_| quote! { store: u8, });
|
||||
let branch = branch.map(|_| quote! { branch: u16, });
|
||||
let text = text.map(|_| quote! { text: ZsciiString, });
|
||||
quote! { #name { #store #branch #text } }
|
||||
}
|
||||
});
|
||||
|
||||
let variant_names = instruction_type.entries.iter().map(|e| &e.name);
|
||||
let has_store_branches = instruction_type.entries.iter().map(|e| {
|
||||
let has_store = e.store.is_some();
|
||||
let number = &e.number;
|
||||
quote!{ #number => #has_store }
|
||||
});
|
||||
let has_branch_branches = instruction_type.entries.iter().map(|e| {
|
||||
let has_branch = e.branch.is_some();
|
||||
let number = &e.number;
|
||||
quote!{ #number => #has_branch }
|
||||
});
|
||||
let has_text_branches = instruction_type.entries.iter().map(|e| {
|
||||
let has_text = e.text.is_some();
|
||||
let number = &e.number;
|
||||
quote!{ #number => #has_text }
|
||||
});
|
||||
|
||||
let constructor_function_branches = instruction_type.entries.iter().map(|InstructionEntry { number, name, store, branch, text }| {
|
||||
if store.is_none() && branch.is_none() && text.is_none() {
|
||||
quote!{ #number => ::std::option::Option::Some(Self::#name) }
|
||||
} else {
|
||||
let store = store.map(|_| quote! { store: store?, });
|
||||
let branch = branch.map(|_| quote! { branch: branch?, });
|
||||
let text = text.map(|_| quote! { text: text?, });
|
||||
quote! { #number => ::std::option::Option::Some(Self::#name {#store #branch #text}) }
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub enum #type_name { #(#initial_enum_variants),* }
|
||||
|
||||
impl #type_name {
|
||||
pub fn has_store(inst_no: u8) -> bool {
|
||||
match inst_no {
|
||||
#(#has_store_branches,)*
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_branch(inst_no: u8) -> bool {
|
||||
match inst_no {
|
||||
#(#has_branch_branches,)*
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_text(inst_no: u8) -> bool {
|
||||
match inst_no {
|
||||
#(#has_text_branches,)*
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_number(inst_no: u8, store: Option<u8>, branch: Option<u16>, text: Option<ZsciiString>) -> Option<Self> {
|
||||
match inst_no {
|
||||
#(#constructor_function_branches,)*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn builds() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.pass("tests/*.rs");
|
||||
// t.compile_fail("negative-tests/*.rs");
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
use zingprocmacros::make_instruction_type;
|
||||
|
||||
struct ZsciiString;
|
||||
|
||||
make_instruction_type!{MyInstruction {
|
||||
1 FirstInst,
|
||||
0b10 SecondInst st,
|
||||
3u8 ThirdInst br txt,
|
||||
}}
|
||||
|
||||
fn main() {
|
||||
let one = MyInstruction::FirstInst;
|
||||
let two = MyInstruction::SecondInst { store: 0 };
|
||||
let three = MyInstruction::ThirdInst { branch: 0, text: ZsciiString };
|
||||
}
|
Loading…
Reference in New Issue