use crate::error::{Error, InvalidGroupParameterReason};
use proc_macro2::{
Delimiter as Delimiter2, Ident as Ident2, Span as Span2, TokenStream as TokenStream2,
TokenTree as TokenTree2, token_stream::IntoIter as TokenTreeIter,
};
use quote::ToTokens;
pub enum LinkSegment {
Text(TextSegmentGroup),
Data(DataSegmentGroup),
Rodata,
Bss,
}
impl TryFrom<TokenTreeIter> for LinkSegment {
type Error = Error;
fn try_from(tokens: TokenTreeIter) -> Result<Self, Self::Error> {
let mut tokens = tokens;
let segment = match tokens.next() {
Some(TokenTree2::Ident(segment)) => segment,
Some(invalid) => return Err(Error::SegmentParameterTypeMismatch(invalid)),
None => return Err(Error::EmptyParams),
};
match segment {
segment if segment == "text" => {
let group = Self::parse_segment_group(segment.span(), tokens)?;
let text_group = TextSegmentGroup::try_from(group)?;
Ok(Self::Text(text_group))
}
segment if segment == "data" => {
let group = Self::parse_segment_group(segment.span(), tokens)?;
let data_group = DataSegmentGroup::try_from(group)?;
Ok(Self::Data(data_group))
}
segment if segment == "rodata" => {
if let Some(invalid) = tokens.next() {
return Err(Error::ExtraneousParameter(invalid));
}
Ok(Self::Rodata)
}
segment if segment == "bss" => {
if let Some(invalid) = tokens.next() {
return Err(Error::ExtraneousParameter(invalid));
}
Ok(Self::Bss)
}
invalid => Err(Error::InvalidSegmentValue(invalid)),
}
}
}
impl LinkSegment {
fn parse_segment_group(
segment_span: Span2,
tokens: TokenTreeIter,
) -> Result<SegmentGroup, Error> {
let mut tokens = tokens;
let Some(group) = tokens.next() else {
return Err(Error::MissingGroupParameter(segment_span));
};
if let Some(extraneous) = tokens.next() {
return Err(Error::ExtraneousParameter(extraneous));
}
let group_span = group.span();
let TokenTree2::Group(group) = group else {
return Err(Error::GroupParameterTypeMismatch(group_span));
};
if group.delimiter() != Delimiter2::Parenthesis {
return Err(Error::InvalidGroupDelimiter(group_span));
}
let mut group_tts = group.stream().into_iter();
let Some(TokenTree2::Ident(group)) = group_tts.next() else {
return Err(Error::GroupParameterTypeMismatch(group_span));
};
if let Some(extraneous) = group_tts.next() {
return Err(Error::ExtraneousParameter(extraneous));
}
SegmentGroup::try_from(group)
}
}
struct SegmentGroup(Ident2);
impl ToTokens for SegmentGroup {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.0.to_tokens(tokens)
}
}
impl TryFrom<Ident2> for SegmentGroup {
type Error = Error;
fn try_from(value: Ident2) -> Result<Self, Self::Error> {
let name = value.to_string();
if name.len() > 15 {
return Err(Error::InvalidGroupValue(
value,
InvalidGroupParameterReason::TooLong(name.len()),
));
}
let invalid_symbol = name.find(|ch| match ch {
'a'..='z' | '0'..='9' | '_' => false,
_ => true,
});
if let Some(invalid_symbol) = invalid_symbol {
let ch = name.as_bytes()[invalid_symbol] as char;
return Err(Error::InvalidGroupValue(
value,
InvalidGroupParameterReason::InvalidSymbol(ch),
));
}
Ok(Self(value))
}
}
pub struct TextSegmentGroup(SegmentGroup);
impl ToTokens for TextSegmentGroup {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.0.to_tokens(tokens)
}
}
impl TryFrom<SegmentGroup> for TextSegmentGroup {
type Error = Error;
fn try_from(group: SegmentGroup) -> Result<Self, Self::Error> {
let span = group.0.span();
let name = group.0.to_string();
match name.as_str() {
"const" | "cstring" | "gcc_except_tab" | "eh_frame" => {
return Err(Error::ReservedGroupParameter(span));
}
_ => {}
};
Ok(Self(group))
}
}
pub struct DataSegmentGroup(SegmentGroup);
impl ToTokens for DataSegmentGroup {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.0.to_tokens(tokens)
}
}
impl TryFrom<SegmentGroup> for DataSegmentGroup {
type Error = Error;
#[rustfmt::skip]
fn try_from(group: SegmentGroup) -> Result<Self, Self::Error> {
let span = group.0.span();
let name = group.0.to_string();
match name.as_str() {
| "la_symbol_ptr"
| "nl_symbol_ptr"
| "mod_init_func"
| "mod_term_func"
| "common"
| "bss" => {
return Err(Error::ReservedGroupParameter(span));
}
_ => {}
}
Ok(Self(group))
}
}