use super::packet::PACKET_SIZE;
use crate::emulator::DataModel;
use crate::soc::Width;
const STACK: usize = 64;
const STEPS: u32 = 10_000;
const LONGEST_STRING: u64 = 256;
const WIDEST: usize = PACKET_SIZE;
pub(super) trait Inspect {
fn register(&mut self, number: u16) -> Option<u64>;
fn read(&mut self, address: u64, width: Width) -> Option<u64>;
fn data_model(&mut self) -> DataModel;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum Failed {
Bytecode(u8),
Truncated,
Stack,
DivideByZero,
Memory(u64),
Register(u16),
TooLong,
Format,
}
impl std::fmt::Display for Failed {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bytecode(code) => write!(formatter, "bytecode {code:#04x} isn't one it runs"),
Self::Truncated => write!(formatter, "it ends partway through a bytecode"),
Self::Stack => write!(formatter, "its stack ran out or over {STACK}"),
Self::DivideByZero => write!(formatter, "it divides by zero"),
Self::Memory(address) => write!(formatter, "nothing answers at {address:#x}"),
Self::Register(number) => write!(formatter, "there's no register {number}"),
Self::TooLong => write!(formatter, "it ran past {STEPS} bytecodes"),
Self::Format => write!(formatter, "its printf format isn't one it follows"),
}
}
}
mod code {
pub(super) const ADD: u8 = 0x02;
pub(super) const SUB: u8 = 0x03;
pub(super) const MUL: u8 = 0x04;
pub(super) const DIV_SIGNED: u8 = 0x05;
pub(super) const DIV_UNSIGNED: u8 = 0x06;
pub(super) const REM_SIGNED: u8 = 0x07;
pub(super) const REM_UNSIGNED: u8 = 0x08;
pub(super) const LSH: u8 = 0x09;
pub(super) const RSH_SIGNED: u8 = 0x0A;
pub(super) const RSH_UNSIGNED: u8 = 0x0B;
pub(super) const LOG_NOT: u8 = 0x0E;
pub(super) const BIT_AND: u8 = 0x0F;
pub(super) const BIT_OR: u8 = 0x10;
pub(super) const BIT_XOR: u8 = 0x11;
pub(super) const BIT_NOT: u8 = 0x12;
pub(super) const EQUAL: u8 = 0x13;
pub(super) const LESS_SIGNED: u8 = 0x14;
pub(super) const LESS_UNSIGNED: u8 = 0x15;
pub(super) const EXT: u8 = 0x16;
pub(super) const REF8: u8 = 0x17;
pub(super) const REF16: u8 = 0x18;
pub(super) const REF32: u8 = 0x19;
pub(super) const REF64: u8 = 0x1A;
pub(super) const IF_GOTO: u8 = 0x20;
pub(super) const GOTO: u8 = 0x21;
pub(super) const CONST8: u8 = 0x22;
pub(super) const CONST16: u8 = 0x23;
pub(super) const CONST32: u8 = 0x24;
pub(super) const CONST64: u8 = 0x25;
pub(super) const REG: u8 = 0x26;
pub(super) const END: u8 = 0x27;
pub(super) const DUP: u8 = 0x28;
pub(super) const POP: u8 = 0x29;
pub(super) const ZERO_EXT: u8 = 0x2A;
pub(super) const SWAP: u8 = 0x2B;
pub(super) const PICK: u8 = 0x32;
pub(super) const ROT: u8 = 0x33;
pub(super) const PRINTF: u8 = 0x34;
}
struct Machine<'a> {
code: &'a [u8],
at: usize,
stack: Vec<u64>,
}
impl Machine<'_> {
fn pop(&mut self) -> Result<u64, Failed> {
self.stack.pop().ok_or(Failed::Stack)
}
fn push(&mut self, value: u64) -> Result<(), Failed> {
if self.stack.len() == STACK {
return Err(Failed::Stack);
}
self.stack.push(value);
Ok(())
}
fn operand(&mut self, bytes: usize) -> Result<u64, Failed> {
let end = self.at.checked_add(bytes).ok_or(Failed::Truncated)?;
let field = self.code.get(self.at..end).ok_or(Failed::Truncated)?;
self.at = end;
Ok(field
.iter()
.fold(0, |value, &byte| value << 8 | u64::from(byte)))
}
fn binary(&mut self, with: Binary) -> Result<(), Failed> {
let b = self.pop()?;
let a = self.pop()?;
self.push(with(a, b)?)
}
}
pub(super) fn evaluate(
expression: &[u8],
inspect: &mut dyn Inspect,
printed: &mut Vec<u8>,
) -> Result<u64, Failed> {
let mut machine = Machine {
code: expression,
at: 0,
stack: Vec::new(),
};
for _ in 0..STEPS {
let &bytecode = machine.code.get(machine.at).ok_or(Failed::Truncated)?;
machine.at += 1;
if let Some(operation) = binary(bytecode) {
machine.binary(operation)?;
continue;
}
match bytecode {
code::END => return Ok(machine.stack.last().copied().unwrap_or(0)),
code::LOG_NOT => {
let a = machine.pop()?;
machine.push(u64::from(a == 0))?;
}
code::BIT_NOT => {
let a = machine.pop()?;
machine.push(!a)?;
}
code::EXT => {
let bits = machine.operand(1)?;
let a = machine.pop()?;
machine.push(sign_extend(a, bits))?;
}
code::ZERO_EXT => {
let bits = machine.operand(1)?;
let a = machine.pop()?;
machine.push(if bits >= 64 { a } else { a & ((1 << bits) - 1) })?;
}
code::REF8 | code::REF16 | code::REF32 | code::REF64 => {
let width = match bytecode {
code::REF8 => Width::W8,
code::REF16 => Width::W16,
code::REF32 => Width::W32,
_ => Width::W64,
};
let address = machine.pop()?;
let value = inspect
.read(address, width)
.ok_or(Failed::Memory(address))?;
machine.push(value)?;
}
code::IF_GOTO | code::GOTO => {
let to =
usize::try_from(machine.operand(2)?).map_err(|_overflow| Failed::Truncated)?;
if bytecode == code::GOTO || machine.pop()? != 0 {
machine.at = to;
}
}
code::CONST8 | code::CONST16 | code::CONST32 | code::CONST64 => {
let bytes = 1 << (bytecode - code::CONST8);
let value = machine.operand(bytes)?;
machine.push(value)?;
}
code::REG => {
let number = u16::try_from(machine.operand(2)?).expect("two bytes");
let value = inspect.register(number).ok_or(Failed::Register(number))?;
machine.push(value)?;
}
code::DUP => {
let a = *machine.stack.last().ok_or(Failed::Stack)?;
machine.push(a)?;
}
code::POP => {
machine.pop()?;
}
code::SWAP => {
let b = machine.pop()?;
let a = machine.pop()?;
machine.push(b)?;
machine.push(a)?;
}
code::PICK => {
let depth = usize::try_from(machine.operand(1)?).expect("a byte");
let index = machine
.stack
.len()
.checked_sub(depth + 1)
.ok_or(Failed::Stack)?;
let value = machine.stack[index];
machine.push(value)?;
}
code::ROT => {
let c = machine.pop()?;
let b = machine.pop()?;
let a = machine.pop()?;
for value in [c, a, b] {
machine.push(value)?;
}
}
code::PRINTF => printf(&mut machine, inspect, printed)?,
other => return Err(Failed::Bytecode(other)),
}
}
Err(Failed::TooLong)
}
type Binary = fn(u64, u64) -> Result<u64, Failed>;
fn binary(bytecode: u8) -> Option<Binary> {
Some(match bytecode {
code::ADD => |a, b| Ok(a.wrapping_add(b)),
code::SUB => |a, b| Ok(a.wrapping_sub(b)),
code::MUL => |a, b| Ok(a.wrapping_mul(b)),
code::DIV_SIGNED => |a: u64, b: u64| {
let (a, b) = (a.cast_signed(), b.cast_signed());
(b != 0)
.then(|| a.wrapping_div(b).cast_unsigned())
.ok_or(Failed::DivideByZero)
},
code::DIV_UNSIGNED => |a: u64, b| a.checked_div(b).ok_or(Failed::DivideByZero),
code::REM_SIGNED => |a: u64, b: u64| {
let (a, b) = (a.cast_signed(), b.cast_signed());
(b != 0)
.then(|| a.wrapping_rem(b).cast_unsigned())
.ok_or(Failed::DivideByZero)
},
code::REM_UNSIGNED => |a: u64, b| a.checked_rem(b).ok_or(Failed::DivideByZero),
code::LSH => |a: u64, b| Ok(a.checked_shl(shift(b)).unwrap_or(0)),
code::RSH_SIGNED => |a: u64, b| Ok((a.cast_signed() >> shift(b).min(63)).cast_unsigned()),
code::RSH_UNSIGNED => |a: u64, b| Ok(a.checked_shr(shift(b)).unwrap_or(0)),
code::BIT_AND => |a, b| Ok(a & b),
code::BIT_OR => |a, b| Ok(a | b),
code::BIT_XOR => |a, b| Ok(a ^ b),
code::EQUAL => |a, b| Ok(u64::from(a == b)),
code::LESS_SIGNED => |a: u64, b: u64| Ok(u64::from(a.cast_signed() < b.cast_signed())),
code::LESS_UNSIGNED => |a, b| Ok(u64::from(a < b)),
_ => return None,
})
}
fn shift(count: u64) -> u32 {
u32::try_from(count).unwrap_or(u32::MAX)
}
fn sign_extend(value: u64, bits: u64) -> u64 {
if bits == 0 || bits >= 64 {
return value;
}
let unused = 64 - bits;
((value << unused).cast_signed() >> unused).cast_unsigned()
}
fn printf(
machine: &mut Machine<'_>,
inspect: &mut dyn Inspect,
printed: &mut Vec<u8>,
) -> Result<(), Failed> {
let count = usize::try_from(machine.operand(1)?).expect("a byte");
let length = usize::try_from(machine.operand(2)?).expect("two bytes");
let end = machine.at.checked_add(length).ok_or(Failed::Truncated)?;
let format = machine.code.get(machine.at..end).ok_or(Failed::Truncated)?;
machine.at = end;
let format = format.strip_suffix(&[0]).ok_or(Failed::Format)?;
machine.pop()?;
machine.pop()?;
let arguments = (0..count)
.map(|_| machine.pop())
.collect::<Result<Vec<u64>, Failed>>()?;
let format = unescape(format).ok_or(Failed::Format)?;
format_into(&format, &arguments, inspect, printed)
}
fn unescape(text: &[u8]) -> Option<Vec<u8>> {
let mut bytes = Vec::with_capacity(text.len());
let mut rest = text.iter().copied().peekable();
while let Some(byte) = rest.next() {
if byte != b'\\' {
bytes.push(byte);
continue;
}
let escaped = rest.next()?;
bytes.push(match escaped {
b'n' => b'\n',
b't' => b'\t',
b'r' => b'\r',
b'a' => 0x07,
b'b' => 0x08,
b'f' => 0x0C,
b'v' => 0x0B,
b'e' => 0x1B,
b'0'..=b'7' => {
let mut value = u32::from(escaped - b'0');
for _ in 0..2 {
match rest.peek() {
Some(&digit @ b'0'..=b'7') => {
value = value * 8 + u32::from(digit - b'0');
rest.next();
}
_ => break,
}
}
u8::try_from(value).ok()?
}
b'x' => {
let mut value = 0u32;
let mut digits = 0;
while let Some(digit) = rest.peek().and_then(|&byte| char::from(byte).to_digit(16))
{
value = value.checked_mul(16)? + digit;
digits += 1;
rest.next();
}
if digits == 0 {
return None;
}
u8::try_from(value).ok()?
}
other => other,
});
}
Some(bytes)
}
#[derive(Default)]
struct Spec {
left: bool,
zero: bool,
sign: &'static [u8],
alternate: bool,
width: usize,
precision: Option<usize>,
}
impl Spec {
fn pad(&self, digits: &[u8], sign: &[u8], prefix: &[u8]) -> Vec<u8> {
let mut body: Vec<u8> = match self.precision {
Some(0) if digits == b"0" => Vec::new(),
Some(precision) => {
let mut body = vec![b'0'; precision.saturating_sub(digits.len())];
body.extend_from_slice(digits);
body
}
None => digits.to_vec(),
};
let fill = self
.width
.saturating_sub(sign.len() + prefix.len() + body.len());
let zeros = self.zero && !self.left && self.precision.is_none();
let mut padded = Vec::with_capacity(fill + sign.len() + prefix.len() + body.len());
if !self.left && !zeros {
padded.resize(fill, b' ');
}
padded.extend_from_slice(sign);
padded.extend_from_slice(prefix);
if zeros {
padded.resize(padded.len() + fill, b'0');
}
padded.append(&mut body);
if self.left {
padded.resize(padded.len() + fill, b' ');
}
padded
}
fn pad_text(&self, text: &[u8]) -> Vec<u8> {
let fill = self.width.saturating_sub(text.len());
let mut padded = Vec::with_capacity(fill + text.len());
if !self.left {
padded.resize(fill, b' ');
}
padded.extend_from_slice(text);
if self.left {
padded.resize(padded.len() + fill, b' ');
}
padded
}
}
fn format_into(
format: &[u8],
arguments: &[u64],
inspect: &mut dyn Inspect,
printed: &mut Vec<u8>,
) -> Result<(), Failed> {
let mut arguments = arguments.iter().copied();
let mut rest = format.iter().copied().peekable();
while let Some(byte) = rest.next() {
if byte != b'%' {
printed.push(byte);
continue;
}
let mut spec = Spec::default();
while let Some(&flag) = rest.peek() {
match flag {
b'-' => spec.left = true,
b'0' => spec.zero = true,
b'+' => spec.sign = b"+",
b' ' if spec.sign.is_empty() => spec.sign = b" ",
b' ' => {}
b'#' => spec.alternate = true,
_ => break,
}
rest.next();
}
let number = |rest: &mut std::iter::Peekable<_>| {
let mut value = 0usize;
while let Some(digit) = rest
.peek()
.and_then(|&byte: &u8| char::from(byte).to_digit(10))
{
value = value.saturating_mul(10).saturating_add(digit as usize);
rest.next();
}
(value <= WIDEST).then_some(value).ok_or(Failed::Format)
};
spec.width = number(&mut rest)?;
if rest.peek() == Some(&b'.') {
rest.next();
spec.precision = Some(number(&mut rest)?);
}
let (mut halves, mut longs, mut widest) = (0, 0, false);
while let Some(&length) = rest.peek() {
match length {
b'h' => halves += 1,
b'l' => longs += 1,
b'j' | b'q' => widest = true,
b'z' | b't' => {}
_ => break,
}
rest.next();
}
let bits = match (halves, longs) {
_ if widest || longs >= 2 => 64,
(0, 1) => u64::from(inspect.data_model().long().bits()),
(0, _) => 32,
(1, _) => 16,
_ => 8,
};
let conversion = rest.next().ok_or(Failed::Format)?;
if conversion == b'%' {
printed.push(b'%');
continue;
}
let value = arguments.next().ok_or(Failed::Format)?;
let unsigned = if bits == 64 {
value
} else {
value & ((1 << bits) - 1)
};
let text = match conversion {
b'd' | b'i' => {
let signed = sign_extend(value, bits).cast_signed();
let sign = if signed < 0 { b"-" } else { spec.sign };
spec.pad(signed.unsigned_abs().to_string().as_bytes(), sign, b"")
}
b'u' => spec.pad(unsigned.to_string().as_bytes(), b"", b""),
b'x' | b'X' => {
let digits = if conversion == b'X' {
format!("{unsigned:X}")
} else {
format!("{unsigned:x}")
};
let prefix: &[u8] = match (spec.alternate && unsigned != 0, conversion) {
(false, _) => b"",
(true, b'X') => b"0X",
(true, _) => b"0x",
};
spec.pad(digits.as_bytes(), b"", prefix)
}
b'o' => {
let digits = format!("{unsigned:o}");
if spec.alternate && !digits.starts_with('0') {
spec.precision = Some(spec.precision.unwrap_or(0).max(digits.len() + 1));
} else if spec.alternate {
spec.precision = Some(spec.precision.unwrap_or(1).max(1));
}
spec.pad(digits.as_bytes(), b"", b"")
}
b'p' => spec.pad(format!("{value:x}").as_bytes(), b"", b"0x"),
b'c' => spec.pad_text(&[u8::try_from(value & 0xFF).expect("a byte")]),
b's' => {
let longest = spec
.precision
.map_or(LONGEST_STRING, |precision| precision as u64);
let mut bytes = Vec::new();
for offset in 0..longest {
let address = value.checked_add(offset).ok_or(Failed::Memory(value))?;
match inspect
.read(address, Width::W8)
.ok_or(Failed::Memory(address))?
{
0 => break,
byte => bytes.push(u8::try_from(byte).expect("a byte")),
}
}
spec.pad_text(&bytes)
}
_ => return Err(Failed::Format),
};
printed.extend_from_slice(&text);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
struct Fake {
bytes: Vec<u8>,
model: DataModel,
}
impl Inspect for Fake {
fn register(&mut self, number: u16) -> Option<u64> {
(number < 40).then_some(u64::from(number) * 10)
}
fn data_model(&mut self) -> DataModel {
self.model
}
fn read(&mut self, address: u64, width: Width) -> Option<u64> {
let start = usize::try_from(address.checked_sub(0x100)?).ok()?;
let bytes = self.bytes.get(start..start + width.bytes() as usize)?;
Some(
bytes
.iter()
.rev()
.fold(0, |value, &byte| value << 8 | u64::from(byte)),
)
}
}
fn run(code: &[u8]) -> Result<u64, Failed> {
let mut fake = Fake {
bytes: vec![0x78, 0x56, 0x34, 0x12, 0xFE, 0xFF, b'h', b'i', 0],
model: DataModel::Ilp32,
};
evaluate(code, &mut fake, &mut Vec::new())
}
const MINUS_ONE: u64 = u64::MAX;
#[test]
fn each_opcode_does_what_f_2_says() {
use code::*;
let c8 = |n: u8| [CONST8, n];
let both = |a: u8, b: u8, op: u8| [CONST8, a, CONST8, b, op, END];
for (code, expected) in [
(both(7, 5, ADD).to_vec(), 12),
(both(7, 5, SUB).to_vec(), 2),
(both(5, 7, SUB).to_vec(), (-2i64).cast_unsigned()),
(both(7, 5, MUL).to_vec(), 35),
(both(7, 2, DIV_UNSIGNED).to_vec(), 3),
(both(7, 2, REM_UNSIGNED).to_vec(), 1),
(both(1, 4, LSH).to_vec(), 16),
(both(16, 4, RSH_UNSIGNED).to_vec(), 1),
(both(6, 3, BIT_AND).to_vec(), 2),
(both(6, 3, BIT_OR).to_vec(), 7),
(both(6, 3, BIT_XOR).to_vec(), 5),
(both(6, 6, EQUAL).to_vec(), 1),
(both(6, 7, EQUAL).to_vec(), 0),
(both(6, 7, LESS_UNSIGNED).to_vec(), 1),
([&c8(0)[..], &[LOG_NOT, END]].concat(), 1),
([&c8(9)[..], &[LOG_NOT, END]].concat(), 0),
([&c8(0)[..], &[BIT_NOT, END]].concat(), MINUS_ONE),
([&c8(0xFF)[..], &[EXT, 8, END]].concat(), MINUS_ONE),
([&c8(0x7F)[..], &[EXT, 8, END]].concat(), 0x7F),
(
[&[CONST16, 0x12, 0x34][..], &[ZERO_EXT, 8, END]].concat(),
0x34,
),
(vec![CONST32, 0x80, 0, 0, 0, END], 0x8000_0000),
(
vec![CONST64, 1, 2, 3, 4, 5, 6, 7, 8, END],
0x0102_0304_0506_0708,
),
(vec![REG, 0, 3, END], 30),
(vec![CONST16, 1, 0, REF8, END], 0x78),
(vec![CONST16, 1, 0, REF16, END], 0x5678),
(vec![CONST16, 1, 0, REF32, END], 0x1234_5678),
(
vec![CONST16, 1, 4, REF16, EXT, 16, END],
(-2i64).cast_unsigned(),
),
(both(1, 2, SWAP).to_vec(), 1),
([&c8(1)[..], &[DUP, ADD, END]].concat(), 2),
([&c8(1)[..], &c8(2), &[POP, END]].concat(), 1),
([&c8(1)[..], &c8(2), &c8(3), &[PICK, 2, END]].concat(), 1),
([&c8(1)[..], &c8(2), &c8(3), &[ROT, END]].concat(), 2),
([&c8(1)[..], &c8(2), &c8(3), &[ROT, POP, END]].concat(), 1),
(
[&c8(1)[..], &[IF_GOTO, 0, 8, CONST8, 9, END, CONST8, 4, END]].concat(),
4,
),
(
[&c8(0)[..], &[IF_GOTO, 0, 8, CONST8, 9, END, CONST8, 4, END]].concat(),
9,
),
(vec![GOTO, 0, 5, CONST8, 9, CONST8, 4, END], 4),
(vec![END], 0),
] {
assert_eq!(run(&code), Ok(expected), "{code:02x?}");
}
let minus = |n: u8| [CONST8, n.wrapping_neg(), EXT, 8];
let least = [&[CONST64][..], &i64::MIN.to_be_bytes()].concat();
let minus_one = [CONST8, 0xFF, EXT, 8];
for (op, expected) in [(DIV_SIGNED, i64::MIN), (REM_SIGNED, 0)] {
let code = [&least[..], &minus_one, &[op, END]].concat();
assert_eq!(run(&code), Ok(expected.cast_unsigned()), "{op:#x} wraps");
}
for (op, expected) in [
(DIV_SIGNED, -3i64),
(REM_SIGNED, -1),
(LESS_SIGNED, 1),
(RSH_SIGNED, -2),
] {
let code = [&minus(7)[..], &[CONST8, 2, op, END]].concat();
assert_eq!(run(&code), Ok(expected.cast_unsigned()), "{op:#x}");
}
}
#[test]
fn an_expression_that_cant_finish_fails() {
use code::*;
assert_eq!(
run(&[CONST8, 1, CONST8, 0, DIV_UNSIGNED, END]),
Err(Failed::DivideByZero)
);
assert_eq!(
run(&[CONST8, 1, CONST8, 0, REM_SIGNED, END]),
Err(Failed::DivideByZero)
);
assert_eq!(
run(&[CONST16, 0x20, 0, REF32, END]),
Err(Failed::Memory(0x2000))
);
assert_eq!(run(&[REG, 0, 99, END]), Err(Failed::Register(99)));
assert_eq!(run(&[ADD, END]), Err(Failed::Stack));
assert_eq!(run(&[0x0C, END]), Err(Failed::Bytecode(0x0C)), "trace");
assert_eq!(run(&[0x01, END]), Err(Failed::Bytecode(0x01)), "float");
assert_eq!(run(&[CONST16, 1]), Err(Failed::Truncated));
assert_eq!(run(&[CONST8, 1]), Err(Failed::Truncated), "no end");
assert_eq!(run(&[GOTO, 0, 0]), Err(Failed::TooLong));
let full: Vec<u8> = std::iter::repeat_n([CONST8, 1], STACK + 1)
.flatten()
.collect();
assert_eq!(run(&full), Err(Failed::Stack));
}
#[test]
fn a_long_is_as_wide_as_the_data_model_says() {
let print = |model| {
let mut fake = Fake {
bytes: Vec::new(),
model,
};
let mut printed = Vec::new();
format_into(b"%ld", &[0x1_0000_0001], &mut fake, &mut printed).unwrap();
String::from_utf8(printed).unwrap()
};
assert_eq!(print(DataModel::Ilp32), "1");
assert_eq!(print(DataModel::Lp64), "4294967297");
}
#[test]
fn printf_formats_as_c_does() {
let print = |format: &str, arguments: &[u64]| {
let mut code = Vec::new();
for &argument in arguments.iter().rev() {
code.push(code::CONST64);
code.extend_from_slice(&argument.to_be_bytes());
}
code.extend([code::CONST8, 0, code::CONST8, 0, code::PRINTF]);
let format = [format.as_bytes(), &[0]].concat();
code.push(u8::try_from(arguments.len()).unwrap());
code.extend_from_slice(&u16::try_from(format.len()).unwrap().to_be_bytes());
code.extend(format);
code.push(code::END);
let mut fake = Fake {
bytes: b"\0\0\0\0\0\0hi\0".to_vec(),
model: DataModel::Ilp32,
};
let mut printed = Vec::new();
evaluate(&code, &mut fake, &mut printed).map(|_| printed)
};
let printed = |format: &str, arguments: &[u64]| {
String::from_utf8(print(format, arguments).unwrap()).unwrap()
};
assert_eq!(printed("tick %d\\n", &[5]), "tick 5\n");
assert_eq!(printed("%d %i", &[u64::MAX, 0xFFFF_FFFF]), "-1 -1");
assert_eq!(printed("%u %lld", &[u64::MAX, u64::MAX]), "4294967295 -1");
assert_eq!(
printed("%x %#X %08x %o", &[255, 255, 0xAB, 8]),
"ff 0XFF 000000ab 10"
);
assert_eq!(printed("%hhd %hu", &[0xFF, 0x1_0002]), "-1 2");
assert_eq!(
printed("[%5d|%-5d|%+d]", &[42, 42, 42]),
"[ 42|42 |+42]"
);
assert_eq!(
printed("%+u %+x % u", &[7, 7, 7]),
"7 7 7",
"a sign is for d and i"
);
assert_eq!(
printed("%#o %#o %#.0o %#5o", &[0, 8, 0, 8]),
"0 010 0 010",
"the first digit a zero, once"
);
assert_eq!(printed("%c%s%%", &[u64::from(b'>'), 0x106]), ">hi%");
assert_eq!(print("%c", &[0xE9]).unwrap(), [0xE9], "a byte as it is");
assert_eq!(printed("%.1s|%4s", &[0x106, 0x106]), "h| hi");
assert_eq!(printed("%p", &[0x2000_0000]), "0x20000000");
assert_eq!(printed("\\t\\101\\x42\\\\", &[]), "\tAB\\");
assert_eq!(print("%s", &[0x3000]), Err(Failed::Memory(0x3000)));
assert_eq!(
print("%d", &[]),
Err(Failed::Format),
"a conversion with no argument"
);
assert_eq!(print("%y", &[1]), Err(Failed::Format));
assert_eq!(
printed(&format!("%{WIDEST}d"), &[1]).len(),
WIDEST,
"as wide as it goes"
);
for too_wide in ["%99999999999d", "%4097d", "%.4294967295s", "%.4097d"] {
assert_eq!(print(too_wide, &[0x106]), Err(Failed::Format), "{too_wide}");
}
}
proptest::proptest! {
#[test]
fn any_expression_ends(code in proptest::collection::vec(proptest::prelude::any::<u8>(), 0..512)) {
let mut fake = Fake {
bytes: vec![0x41; 64],
model: DataModel::Ilp32,
};
let mut printed = Vec::new();
evaluate(&code, &mut fake, &mut printed).ok();
proptest::prop_assert!(printed.len() <= STEPS as usize * (WIDEST + 512));
}
}
}