mod diagnostics;
mod item_cases;
use core::convert::Infallible;
use core::hash::{Hash, Hasher};
use macroonz_compiler::{
AuthoredItemKind, AuthoredItemReadIssue, CaptureBuildRefusal, CaptureBuilder, CapturedAtom,
CapturedDelimiter, FragmentGenerationIssue, GeneratedLiteralRefusal, SpanHandle, TextCapture,
encode_bytes,
};
use std::collections::hash_map::DefaultHasher;
fn exact_literal_receipt() -> Vec<u8> {
let mut bytes = vec![8, 0];
encode_bytes(b"0xFFu8", &mut bytes);
bytes.extend_from_slice(&[8, 1]);
encode_bytes("é".as_bytes(), &mut bytes);
bytes.extend_from_slice(&[8, 2, 0xff, 8, 3]);
encode_bytes(b"ab", &mut bytes);
bytes
}
fn item_facts(source: &str) -> Result<(AuthoredItemKind, Option<String>, bool), ()> {
let captured = TextCapture::read(source).map_err(|_| ())?;
let item = captured.input().authored_item().map_err(|_| ())?;
assert_eq!(
item.preserved().canonical_bytes(),
captured.input().canonical_bytes()
);
let name = item.name().map(|(_, spelling)| spelling.to_owned());
Ok((item.kind(), name, item.unsafe_token().is_some()))
}
#[test]
fn exact_literal_forms_project_without_a_source_string_round_trip() -> Result<(), ()> {
let captured = TextCapture::read("0xFFu8 'é' b'\\xff' c\"ab\"").map_err(|_| ())?;
let fragment = captured.input().fragment();
let generated = fragment.generated().map_err(|_| ())?;
assert_eq!(generated.canonical_bytes(), exact_literal_receipt());
assert_eq!(generated.inspected(), "0xFFu8 '\\u{e9}' b'\\xFF' c\"ab\" ");
assert_eq!(fragment.first_span().map(SpanHandle::index), Some(0));
assert_eq!(fragment.last_span().map(SpanHandle::index), Some(3));
Ok(())
}
#[test]
fn item_lens_preserves_required_rust_families() -> Result<(), ()> {
for case in item_cases::REQUIRED_ITEM_CASES {
let (kind, name, item_unsafe) = item_facts(case.source)?;
assert_eq!(kind, case.kind);
assert_eq!(name.as_deref(), case.name);
assert_eq!(item_unsafe, case.safety.expects_unsafe());
}
Ok(())
}
#[test]
fn one_item_reading_supplies_checked_structural_fragments() -> Result<(), ()> {
let captured = TextCapture::read(
"#[doc = \"read\"] pub(crate) async fn read<'a, T, const N: usize>(value: &'a T) -> impl Copy + use<'a, T> where T: Copy + 'a { (value, N) }",
)
.map_err(|_| ())?;
let item = captured.input().authored_item().map_err(|_| ())?;
assert!(!item.attributes().is_empty());
assert_eq!(
item.visibility().generated().map_err(|_| ())?.inspected(),
"pub ( crate ) "
);
assert_eq!(
item.qualifiers().generated().map_err(|_| ())?.inspected(),
"async "
);
assert_eq!(
item.generics()
.ok_or(())?
.generated()
.map_err(|_| ())?
.inspected(),
"< 'a , T , const N : usize > "
);
assert!(
item.where_clause()
.ok_or(())?
.generated()
.map_err(|_| ())?
.inspected()
.starts_with("where T : Copy + 'a ")
);
let (delimiter, body) = item.body().ok_or(())?;
assert_eq!(delimiter, CapturedDelimiter::Brace);
assert_eq!(
body.generated().map_err(|_| ())?.inspected(),
"( value , N ) "
);
Ok(())
}
#[test]
fn tuple_structure_where_clause_remains_visible_after_its_fields() -> Result<(), ()> {
let captured =
TextCapture::read("pub struct Packet<T, const N: usize>(T, [u8; N]) where T: Copy;")
.map_err(|_| ())?;
let item = captured.input().authored_item().map_err(|_| ())?;
let (delimiter, fields) = item.body().ok_or(())?;
assert_eq!(delimiter, CapturedDelimiter::Parenthesis);
assert_eq!(
fields.generated().map_err(|_| ())?.inspected(),
"T , [ u8 ; N ] "
);
assert_eq!(
item.where_clause()
.ok_or(())?
.generated()
.map_err(|_| ())?
.inspected(),
"where T : Copy "
);
Ok(())
}
#[test]
fn explicit_unsafe_custody_moves_the_authored_identity() -> Result<(), ()> {
let safe =
TextCapture::read("pub fn read_raw<T: Copy>(pointer: *const T) -> T { pointer.read() }")
.map_err(|_| ())?;
let explicit = TextCapture::read(
"#[doc = \"Read one pointer.\\n\\n# Safety\\n\\nThe pointer must be valid.\"] pub unsafe fn read_raw<T: Copy>(pointer: *const T) -> T { unsafe { pointer.read() } }",
)
.map_err(|_| ())?;
let item = explicit.input().authored_item().map_err(|_| ())?;
let inspected = item.preserved().generated().map_err(|_| ())?.inspected();
assert!(!item.attributes().is_empty());
assert!(item.unsafe_token().is_some());
assert!(inspected.contains("# Safety"));
assert!(inspected.contains("unsafe fn read_raw"));
assert!(inspected.contains("unsafe { pointer . read ( ) }"));
assert_ne!(
safe.input().canonical_bytes(),
explicit.input().canonical_bytes()
);
Ok(())
}
#[test]
fn cursor_read_and_exact_fragment_are_one_operation() -> Result<(), ()> {
let captured = TextCapture::read("r#type::Item<'a>").map_err(|_| ())?;
let mut cursor = captured.input().cursor();
let (fragment, ()) = cursor
.fragment(|reading| {
while !reading.is_finished() {
let _token = reading.token()?;
}
Ok(())
})
.map_err(|_| ())?;
cursor.finish().map_err(|_| ())?;
assert_eq!(
fragment.canonical_bytes(),
captured.input().canonical_bytes()
);
assert_eq!(
fragment.generated().map_err(|_| ())?.inspected(),
"r#type :: Item < 'a > "
);
Ok(())
}
#[test]
fn span_only_movement_does_not_move_fragment_identity() -> Result<(), ()> {
let first = one_word(7)?;
let moved = one_word(700)?;
let first_fragment = first.fragment();
let moved_fragment = moved.fragment();
assert_ne!(first_fragment.first_span(), moved_fragment.first_span());
assert_eq!(
first_fragment.canonical_bytes(),
moved_fragment.canonical_bytes()
);
let first_generated = first_fragment.generated().map_err(|_| ())?;
let moved_generated = moved_fragment.generated().map_err(|_| ())?;
assert_eq!(first_generated, moved_generated);
assert_eq!(
standard_hash(&first_generated),
standard_hash(&moved_generated)
);
assert_eq!(
first_generated.canonical_bytes(),
moved_generated.canonical_bytes()
);
assert_eq!(first_generated.inspected(), moved_generated.inspected());
assert_eq!(
format!("{first_generated:?}"),
format!("{moved_generated:?}")
);
Ok(())
}
#[test]
fn invalid_exact_c_string_never_reaches_generation() -> Result<(), ()> {
let mut builder = CaptureBuilder::declared();
let level = builder
.open()
.atom(44u64, |_| {
Ok::<_, Infallible>(CapturedAtom::NulTerminatedText(vec![b'a', 0, b'b']))
})
.map_err(|_refusal: CaptureBuildRefusal<u64, Infallible>| ())?;
let input = level.finish();
let refusal = input.fragment().generated().err().ok_or(())?;
assert_eq!(
refusal.issue(),
FragmentGenerationIssue::Literal(GeneratedLiteralRefusal::InteriorNul)
);
assert_eq!(refusal.token().map(SpanHandle::index), Some(0));
Ok(())
}
#[test]
fn malformed_item_envelopes_refuse_under_typed_causes() -> Result<(), ()> {
let empty = TextCapture::read("").map_err(|_| ())?;
let missing = empty.input().authored_item().err().ok_or(())?;
assert_eq!(missing.issue(), AuthoredItemReadIssue::ItemMissing);
assert_eq!(missing.token(), None);
let unknown = TextCapture::read("pub mystery;").map_err(|_| ())?;
let missing_kind = unknown.input().authored_item().err().ok_or(())?;
assert_eq!(missing_kind.issue(), AuthoredItemReadIssue::ItemKindMissing);
assert_eq!(missing_kind.token().map(SpanHandle::index), Some(1));
for malformed_attribute in ["# pub fn read() {}", "mystery [cfg] pub fn read() {}"] {
let captured = TextCapture::read(malformed_attribute).map_err(|_| ())?;
let refusal = captured.input().authored_item().err().ok_or(())?;
assert_eq!(refusal.issue(), AuthoredItemReadIssue::ItemKindMissing);
assert_eq!(refusal.token().map(SpanHandle::index), Some(0));
}
let unnamed = TextCapture::read("pub struct ;").map_err(|_| ())?;
let missing_name = unnamed.input().authored_item().err().ok_or(())?;
assert_eq!(
missing_name.issue(),
AuthoredItemReadIssue::ItemNameMissing(AuthoredItemKind::Structure)
);
assert_eq!(missing_name.token().map(SpanHandle::index), Some(2));
let unfinished = TextCapture::read("pub fn read()").map_err(|_| ())?;
let missing_end = unfinished.input().authored_item().err().ok_or(())?;
assert_eq!(
missing_end.issue(),
AuthoredItemReadIssue::ItemBoundaryUnfinished(AuthoredItemKind::Function)
);
assert_eq!(missing_end.token().map(SpanHandle::index), Some(3));
Ok(())
}
fn one_word(prior: usize) -> Result<macroonz_compiler::CapturedInput, ()> {
let mut builder = CaptureBuilder::declared();
for position in 0..prior {
let position = u64::try_from(position).map_err(|_| ())?;
let earlier = builder
.open()
.atom(position, |_| {
Ok::<_, Infallible>(CapturedAtom::Word(String::from("earlier")))
})
.map_err(|_| ())?;
let _earlier = earlier.finish();
}
let level = builder
.open()
.atom(u64::try_from(prior).map_err(|_| ())?, |_| {
Ok::<_, Infallible>(CapturedAtom::Word(String::from("same")))
})
.map_err(|_| ())?;
Ok(level.finish())
}
fn standard_hash(value: &impl Hash) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}