use proc_macro::TokenStream;
use std::ops::Range;
#[proc_macro]
pub fn asm_ext(input: TokenStream) -> TokenStream {
let src = input.to_string();
let bytes = src.as_bytes();
let mut out = Vec::with_capacity(bytes.len() + 64); out.extend_from_slice(b"::core::arch::asm!(");
let is_for = |bytes: &[u8]| -> bool {
debug_assert!(bytes[0] == b'f');
let Some(last) = char::from_u32(bytes[3] as u32) else { return false };
let last_is_white = last.is_ascii_whitespace();
bytes.len() >= 4 && bytes[1] == b'o' && bytes[2] == b'r' && last_is_white
};
let mut is_in_quotes = false;
let mut i = 0;
while i < bytes.len() {
let byte = bytes[i];
match byte {
b'f' if !is_in_quotes && is_for(&bytes[i..]) => {
let ForLoop { ident, range_or_array: range, body_span } = parse_for(&src, i);
let ident = format!("{{{}}}", ident); let body = &src[body_span.clone()];
for i in range.into_dyn_iter() {
out.extend_from_slice(body.replace(&ident, &i).as_bytes());
}
i = body_span.end + 1;
continue;
}
b'"' => {
is_in_quotes = !is_in_quotes;
}
_ => (),
}
out.push(byte);
i += 1;
}
if is_in_quotes {
panic!("bad quoting");
}
out.extend_from_slice(b")");
String::from_utf8(out)
.expect("BAD: output was somehow not utf-8")
.parse()
.expect("error parsing output to TokenSream")
}
#[derive(Debug)]
struct ForLoop<'a> {
ident: &'a str,
range_or_array: RangeOrArray<'a>,
body_span: Range<usize>,
}
fn parse_for(src: &str, index: usize) -> ForLoop {
fn is_non_quoted_char(char: char, is_in_quotes: &mut bool) -> impl FnMut(char) -> bool + '_ {
move |c: char| {
if c == '"' {
*is_in_quotes = !*is_in_quotes;
} else if c == char && !*is_in_quotes {
return true;
}
false
}
}
let mut is_in_quotes = false;
let open_brace = src[index..]
.find(is_non_quoted_char('{', &mut is_in_quotes))
.expect("didn't find for loop open brace")
+ index
+ 1;
if is_in_quotes {
panic!("bad quoting");
}
let close_brace = src[open_brace..]
.find(is_non_quoted_char('}', &mut is_in_quotes))
.expect("didn't find for loop closing brace")
+ open_brace;
if is_in_quotes {
panic!("bad quoting");
}
let is_whitespace = |c: char| c.is_ascii_whitespace();
let s = &src[index..];
let (_for, rest) = s.split_once(is_whitespace).expect("malformed for");
let (ident, rest) = rest.split_once(is_whitespace).expect("malformed for");
let (_in, rest) = rest.split_once(is_whitespace).expect("malformed for");
let (expression, _) = rest
.split_once(is_non_quoted_char('{', &mut is_in_quotes))
.expect("malformed for");
if is_in_quotes {
panic!("bad quoting");
}
let range_or_array = parse_range_or_array(expression);
ForLoop { ident, range_or_array, body_span: open_brace..close_brace }
}
#[derive(Debug)]
enum RangeOrArray<'a> {
Range(Range<i64>),
Array(Vec<&'a str>),
}
impl<'a> RangeOrArray<'a> {
fn into_dyn_iter(self) -> Box<dyn Iterator<Item = String> + 'a> {
match self {
RangeOrArray::Range(range) => Box::new(range.map(|r| r.to_string())),
RangeOrArray::Array(array) => Box::new(array.into_iter().map(|a| a.to_string())),
}
}
}
fn parse_range(s: &str) -> Range<i64> {
let (start, end) = s.split_once("..").expect("expected range dots ..");
start.parse().expect("bad start range")..end.parse().expect("bad end range")
}
fn parse_array(s: &str) -> Vec<&str> {
s.split(|c| matches!(c, '[' | ']' | ','))
.filter_map(|s| {
let s = s.trim();
if !s.is_empty() {
Some(s.trim_matches('"'))
} else {
None
}
})
.collect()
}
#[allow(clippy::needless_lifetimes)] fn parse_range_or_array<'a>(s: &'a str) -> RangeOrArray<'a> {
let s = s.trim();
if s.starts_with('[') {
RangeOrArray::Array(parse_array(s))
} else {
RangeOrArray::Range(parse_range(s))
}
}