#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use crate::grain::bytecode::chain::Chain;
use crate::grain::pos::{varint, Site};
#[must_use]
pub fn encode(chains: &[Chain]) -> Vec<u8> {
let sites: Vec<_> = chains
.iter()
.flat_map(Chain::positions)
.map(|pos| Site {
line: pos.line().and_then(|n| u32::try_from(n).ok()).unwrap_or(0),
column: pos
.position()
.and_then(|n| u32::try_from(n).ok())
.unwrap_or(0),
})
.collect();
let mut out = Vec::new();
varint::put_u64(&mut out, sites.len() as u64);
for site in sites {
varint::put_u64(&mut out, u64::from(site.line));
varint::put_u64(&mut out, u64::from(site.column));
}
out
}
#[must_use]
pub fn resolve(stream: &[u8], slot: u32) -> Option<Site> {
let mut at = 0usize;
let count = varint::u32(stream, &mut at).ok()?;
if slot >= count {
return None;
}
for _ in 0..slot {
varint::u32(stream, &mut at).ok()?;
varint::u32(stream, &mut at).ok()?;
}
let line = varint::u32(stream, &mut at).ok()?;
let column = varint::u32(stream, &mut at).ok()?;
(line != 0).then_some(Site { line, column })
}
pub fn decode(stream: &[u8]) -> Result<Vec<Option<Site>>, StreamError> {
let mut at = 0usize;
let count = varint::u32(stream, &mut at).map_err(StreamError::from)?;
let mut sites = Vec::with_capacity(count as usize);
for _ in 0..count {
let line = varint::u32(stream, &mut at).map_err(StreamError::from)?;
let column = varint::u32(stream, &mut at).map_err(StreamError::from)?;
sites.push((line != 0).then_some(Site { line, column }));
}
if at != stream.len() {
return Err(StreamError::TrailingBytes {
count: stream.len() - at,
});
}
Ok(sites)
}
pub fn check(stream: &[u8]) -> Result<(), StreamError> {
decode(stream).map(|_| ())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamError {
Truncated,
Overflow,
TrailingBytes {
count: usize,
},
}
impl From<varint::Error> for StreamError {
fn from(err: varint::Error) -> Self {
match err {
varint::Error::Truncated => Self::Truncated,
varint::Error::Overflow => Self::Overflow,
}
}
}
impl core::fmt::Display for StreamError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Truncated => write!(f, "chain position stream ends mid-slot"),
Self::Overflow => write!(f, "chain position stream holds a number too wide to read"),
Self::TrailingBytes { count } => {
write!(
f,
"chain position stream has {count} bytes past its last slot"
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grain::bytecode::chain::{Root, Step, Tail};
use crate::Position;
fn chain(root: Root, steps: Vec<Step>) -> Chain {
Chain {
root,
steps,
tail: Tail::Read,
operands: 0,
}
}
fn sample() -> Vec<Chain> {
vec![chain(
Root::Named {
name: 0,
pos: Position::new(1, 1),
},
vec![
Step::Index {
operand: 0,
flags: Default::default(),
pos: Position::new(1, 3),
bracket: Position::new(1, 2),
},
Step::Property {
name: 1,
getter: 2,
setter: 3,
flags: Default::default(),
pos: Position::new(1, 6),
},
],
)]
}
#[test]
#[cfg(not(feature = "no_position"))]
fn every_slot_resolves_to_the_position_it_was_written_from() {
let stream = encode(&sample());
assert_eq!(
resolve(&stream, 0),
Some(Site { line: 1, column: 1 }),
"the root"
);
assert_eq!(
resolve(&stream, 1),
Some(Site { line: 1, column: 3 }),
"the index"
);
assert_eq!(
resolve(&stream, 2),
Some(Site { line: 1, column: 2 }),
"the bracket"
);
assert_eq!(
resolve(&stream, 3),
Some(Site { line: 1, column: 6 }),
"the property"
);
}
#[test]
#[cfg(not(feature = "no_position"))]
fn a_steps_slot_names_that_steps_position() {
let chains = sample();
let stream = encode(&chains);
let chain = &chains[0];
let index = chain.step_slot(0).expect("the index step exists");
let property = chain.step_slot(1).expect("the property step exists");
assert_eq!(resolve(&stream, index), Some(Site { line: 1, column: 3 }));
assert_eq!(
resolve(&stream, property),
Some(Site { line: 1, column: 6 })
);
}
#[test]
#[cfg(not(feature = "no_position"))]
fn slots_run_on_across_chains() {
let mut chains = sample();
chains.push(chain(
Root::Temporary,
vec![Step::Method {
name: 0,
argc: 0,
operand: 0,
flags: Default::default(),
pos: Position::new(9, 9),
}],
));
let stream = encode(&chains);
let base: u32 = chains[..1].iter().map(Chain::position_slots).sum();
assert_eq!(base, 4, "the first chain holds four positions");
assert_eq!(resolve(&stream, base), Some(Site { line: 9, column: 9 }));
}
#[test]
fn a_slot_past_the_end_resolves_to_nothing() {
assert_eq!(resolve(&encode(&sample()), 99), None);
}
#[test]
fn an_empty_stream_is_sound_and_resolves_nothing() {
let stream = encode(&[]);
assert_eq!(check(&stream), Ok(()));
assert_eq!(resolve(&stream, 0), None);
}
#[test]
fn a_truncated_stream_is_named_as_such() {
let stream = encode(&sample());
for cut in 0..stream.len() {
assert!(
check(&stream[..cut]).is_err(),
"a {cut}-byte prefix passed the check"
);
}
}
#[test]
fn bytes_past_the_last_slot_are_refused() {
let mut stream = encode(&sample());
stream.push(0);
assert_eq!(check(&stream), Err(StreamError::TrailingBytes { count: 1 }));
}
#[test]
fn no_byte_string_can_make_resolution_panic() {
let stream = encode(&sample());
for index in 0..stream.len() {
for bit in 0..8 {
let mut corrupt = stream.clone();
corrupt[index] ^= 1 << bit;
for slot in 0..64 {
let _ = resolve(&corrupt, slot);
}
let _ = check(&corrupt);
}
}
for junk in [&b""[..], &[0xff][..], &[0xff; 32][..], &[0x80; 12][..]] {
for slot in 0..8 {
let _ = resolve(junk, slot);
}
let _ = check(junk);
}
}
}