use core::convert::Infallible;
use macroonz_compiler::{
CaptureBuildRefusal, CaptureBuilder, CaptureLevel, CapturedAtom, CapturedDelimiter,
CapturedInput, TextCapture, TextLexicalCause, TextReadCause, TextReadRefusal, capture_literal,
};
const SOURCE: &str = "r#type::Item<'a, 'r#kind> /* gone */ /// docs\n";
const LITERAL_SPELLINGS: &[&str] = &[
r#""a\nb""#,
"r#\"raw\"#",
r#"b"\xff""#,
r#"br"bytes""#,
r#"c"nul""#,
"'é'",
r"b'\xff'",
"0xFFu8",
];
const LITERAL_SOURCE: &str = r##""a\nb" r#"raw"# b"\xff" br"bytes" c"nul" 'é' b'\xff' 0xFFu8"##;
fn atom(
level: CaptureLevel<'_, u64>,
position: u64,
value: CapturedAtom,
) -> Result<CaptureLevel<'_, u64>, CaptureBuildRefusal<u64, Infallible>> {
level.atom(position, |_| Ok(value))
}
#[test]
fn compound_punctuation_keeps_proc_macro_spacing() -> Result<(), ()> {
let text = TextCapture::read(":: ..= <<= -> ++ ?# ,, <-").map_err(|_| ())?;
let trees = text.input().trees();
let observed: Vec<(Option<char>, Option<char>)> = trees
.iter()
.map(|tree| (tree.joint_punct(), tree.punct()))
.collect();
assert_eq!(
observed,
[
(Some(':'), Some(':')),
(None, Some(':')),
(Some('.'), Some('.')),
(Some('.'), Some('.')),
(None, Some('=')),
(Some('<'), Some('<')),
(Some('<'), Some('<')),
(None, Some('=')),
(Some('-'), Some('-')),
(None, Some('>')),
(Some('+'), Some('+')),
(None, Some('+')),
(Some('?'), Some('?')),
(None, Some('#')),
(Some(','), Some(',')),
(None, Some(',')),
(Some('<'), Some('<')),
(None, Some('-')),
]
);
Ok(())
}
#[test]
fn forbidden_raw_identifier_spelling_refuses_typed() {
assert_eq!(
TextCapture::read("r#self"),
Err(TextReadRefusal {
cause: TextReadCause::Lexical(TextLexicalCause::InvalidIdentifier),
at: 0,
})
);
}
#[test]
fn comment_dispositions_match_the_proc_macro_boundary() -> Result<(), ()> {
let text = TextCapture::read("// ordinary\n/*! inner */").map_err(|_| ())?;
let [pound, bang, attribute] = text.input().trees() else {
return Err(());
};
assert_eq!(pound.punct(), Some('#'));
assert_eq!(bang.punct(), Some('!'));
let Some((CapturedDelimiter::Bracket, [doc, equals, body])) = attribute.group() else {
return Err(());
};
assert_eq!(doc.word(), Some("doc"));
assert_eq!(equals.punct(), Some('='));
assert_eq!(body.text(), Some(" inner "));
assert_eq!(text.input().issued(), 6);
Ok(())
}
fn compiler_shaped_capture() -> Result<CapturedInput, CaptureBuildRefusal<u64, Infallible>> {
let mut builder = CaptureBuilder::declared();
let level = builder.open();
let level = atom(
level,
100,
CapturedAtom::RawIdentifier(String::from("type")),
)?;
let level = atom(level, 101, CapturedAtom::JointPunct(':'))?;
let level = atom(level, 102, CapturedAtom::Punct(':'))?;
let level = atom(level, 103, CapturedAtom::Word(String::from("Item")))?;
let level = atom(level, 104, CapturedAtom::Punct('<'))?;
let level = atom(level, 105, CapturedAtom::JointPunct('\''))?;
let level = atom(level, 106, CapturedAtom::Word(String::from("a")))?;
let level = atom(level, 107, CapturedAtom::Punct(','))?;
let level = atom(level, 108, CapturedAtom::JointPunct('\''))?;
let level = atom(
level,
109,
CapturedAtom::RawIdentifier(String::from("kind")),
)?;
let level = atom(level, 110, CapturedAtom::Punct('>'))?;
let level = atom(level, 111, CapturedAtom::Punct('#'))?;
let level = level.group(112, CapturedDelimiter::Bracket, |_span, inner| {
let inner = atom(inner, 113, CapturedAtom::Word(String::from("doc")))?;
let inner = atom(inner, 114, CapturedAtom::Punct('='))?;
atom(inner, 115, CapturedAtom::Text(String::from(" docs")))
})?;
Ok(level.finish())
}
fn literal_owner_capture() -> Result<CapturedInput, ()> {
let mut builder = CaptureBuilder::declared();
let mut level = builder.open();
for (index, spelling) in LITERAL_SPELLINGS.iter().enumerate() {
let position = u64::try_from(index).map_err(|_| ())?;
level = level
.atom(position, |_| capture_literal(spelling))
.map_err(|_| ())?;
}
Ok(level.finish())
}
#[test]
fn text_and_compiler_shaped_tokens_have_one_normalized_identity() -> Result<(), ()> {
let text = TextCapture::read(SOURCE).map_err(|_| ())?;
let compiler = compiler_shaped_capture().map_err(|_| ())?;
assert_eq!(text.input().canonical_bytes(), compiler.canonical_bytes());
assert_eq!(text.input().issued(), compiler.issued());
assert_eq!(text.input().issued(), 16);
Ok(())
}
#[test]
fn text_literals_match_the_literal_owner() -> Result<(), ()> {
let text = TextCapture::read(LITERAL_SOURCE).map_err(|_| ())?;
let owned = literal_owner_capture()?;
assert_eq!(text.input().canonical_bytes(), owned.canonical_bytes());
assert_eq!(text.input().issued(), LITERAL_SPELLINGS.len());
Ok(())
}
#[test]
fn normalized_tokens_keep_rawness_and_adjacency() -> Result<(), ()> {
let text = TextCapture::read(SOURCE).map_err(|_| ())?;
let trees = text.input().trees();
assert_eq!(
trees.first().and_then(|tree| tree.raw_identifier()),
Some("type")
);
assert_eq!(
trees
.get(1)
.and_then(macroonz_compiler::CapturedTokenTree::joint_punct),
Some(':')
);
assert_eq!(
trees
.get(2)
.and_then(macroonz_compiler::CapturedTokenTree::punct),
Some(':')
);
assert_eq!(
trees
.get(5)
.and_then(macroonz_compiler::CapturedTokenTree::joint_punct),
Some('\'')
);
assert_eq!(
trees
.get(8)
.and_then(macroonz_compiler::CapturedTokenTree::joint_punct),
Some('\'')
);
assert_eq!(
trees.get(9).and_then(|tree| tree.raw_identifier()),
Some("kind")
);
Ok(())
}