use super::{
Fragment, Part, Pattern, Seat, Seating, Site, Stamp, StampError, TransportedReach, Visibility,
};
use crate::token::{self, GeneratedDelimiter, GeneratedToken};
pub const TRANSCRIBE_ARM: &str = "transcribe";
pub const DECLARED_REACH: &str = "declared_reach";
pub const TRANSPORTED_REACH: &str = "transported_reach";
pub const OPAQUE_REACH_REFUSAL: &str = "this stamp requires visibility tokens at its front door; \
an opaque forwarded `vis` fragment cannot be transported one module deeper, and a reach \
guessed in its place would publish an item nobody declared visible.";
const OPAQUE_REACH: &str = "opaque_reach";
const VIS_FRAGMENT: &str = "vis";
pub fn definition(stamp: &Stamp) -> Result<Vec<GeneratedToken>, StampError> {
let pattern = stamp.pattern();
let mut rules: Vec<GeneratedToken> = Vec::new();
if pattern.reaches() {
for reach in Visibility::ALL {
rules.extend(front_arm(stamp, *reach)?);
}
rules.extend(transcribe_arm(pattern)?);
rules.extend(refusing_arm(pattern)?);
} else {
let expansion = pattern.body().tokens().to_vec();
rules.extend(rule(matched(pattern, &[])?, expansion)?);
}
let mut tokens = token::documentation(pattern.note())?;
tokens.extend(token::attribute(vec![GeneratedToken::word(
"macro_export",
)])?);
tokens.push(GeneratedToken::word("macro_rules"));
tokens.push(GeneratedToken::alone('!'));
tokens.push(GeneratedToken::word(stamp.name().spelling()));
tokens.push(token::group(GeneratedDelimiter::Brace, rules)?);
Ok(tokens)
}
pub fn invocation(stamp: &Stamp, site: &Site) -> Result<Vec<GeneratedToken>, StampError> {
let mut body: Vec<GeneratedToken> = Vec::new();
let mut supplied = site.arguments().iter();
for part in stamp.pattern().parts() {
let written = match part {
Part::Literal(material) => material.tokens().to_vec(),
Part::Seat(_) => supplied
.next()
.map_or_else(Vec::new, |argument| argument.tokens().to_vec()),
Part::Reach => declared_reach(site.reach())?,
};
body.extend(written);
}
let mut tokens = stamp_path(site, stamp.name().spelling());
tokens.push(GeneratedToken::alone('!'));
tokens.push(token::group(GeneratedDelimiter::Brace, body)?);
Ok(tokens)
}
pub fn forwarded(seat: &Seat) -> Result<Vec<GeneratedToken>, StampError> {
match seat.seating() {
Seating::One(_) => Ok(token::metavariable(seat.name())),
Seating::Many(_) => repeated(token::metavariable(seat.name()), Some(',')),
Seating::Attributes => {
let inner = token::group(
GeneratedDelimiter::Bracket,
token::metavariable(seat.name()),
)?;
repeated(vec![GeneratedToken::alone('#'), inner], None)
}
}
}
pub fn declared_reach(reach: Visibility) -> Result<Vec<GeneratedToken>, StampError> {
match reach {
Visibility::Private => Ok(Vec::new()),
Visibility::Module => scoped(vec![GeneratedToken::word("self")]),
Visibility::Parent => scoped(vec![GeneratedToken::word("super")]),
Visibility::Crate => scoped(vec![GeneratedToken::word("crate")]),
Visibility::Public => Ok(vec![GeneratedToken::word("pub")]),
}
}
pub fn transported_reach(reach: TransportedReach) -> Result<Vec<GeneratedToken>, StampError> {
match reach {
TransportedReach::Enclosing => scoped(vec![GeneratedToken::word("super")]),
TransportedReach::Ancestor => scoped(vec![
GeneratedToken::word("in"),
GeneratedToken::word("super"),
GeneratedToken::joint(':'),
GeneratedToken::alone(':'),
GeneratedToken::word("super"),
]),
TransportedReach::Crate => scoped(vec![GeneratedToken::word("crate")]),
TransportedReach::Public => Ok(vec![GeneratedToken::word("pub")]),
}
}
fn front_arm(stamp: &Stamp, reach: Visibility) -> Result<Vec<GeneratedToken>, StampError> {
let pattern = stamp.pattern();
let declared = declared_reach(reach)?;
let matcher = matched(pattern, &declared)?;
let mut carried = vec![
GeneratedToken::joint('@'),
GeneratedToken::word(TRANSCRIBE_ARM),
bracketed_reach(transported_reach(reach.transported())?)?,
bracketed_reach(declared)?,
];
carried.extend(restated(pattern)?);
let mut expansion = token::twin_path("crate", &[stamp.name().spelling()]);
expansion.push(GeneratedToken::alone('!'));
expansion.push(token::group(GeneratedDelimiter::Brace, carried)?);
rule(matcher, expansion)
}
fn transcribe_arm(pattern: &Pattern) -> Result<Vec<GeneratedToken>, StampError> {
let mut matcher = vec![
GeneratedToken::joint('@'),
GeneratedToken::word(TRANSCRIBE_ARM),
bracketed_reach(fragment_of(TRANSPORTED_REACH, VIS_FRAGMENT))?,
bracketed_reach(fragment_of(DECLARED_REACH, VIS_FRAGMENT))?,
];
matcher.extend(matched(pattern, &[])?);
rule(matcher, pattern.body().tokens().to_vec())
}
fn refusing_arm(pattern: &Pattern) -> Result<Vec<GeneratedToken>, StampError> {
let opaque = fragment_of(OPAQUE_REACH, VIS_FRAGMENT);
let matcher = matched(pattern, &opaque)?;
let mut expansion = token::absolute_path(&["core", "compile_error"]);
expansion.push(GeneratedToken::alone('!'));
expansion.push(token::group(
GeneratedDelimiter::Parenthesis,
vec![GeneratedToken::text(OPAQUE_REACH_REFUSAL)],
)?);
rule(matcher, expansion)
}
fn matched(pattern: &Pattern, reach: &[GeneratedToken]) -> Result<Vec<GeneratedToken>, StampError> {
let mut tokens: Vec<GeneratedToken> = Vec::new();
for part in pattern.parts() {
let written = match part {
Part::Literal(material) => material.tokens().to_vec(),
Part::Seat(seat) => seat_matched(seat)?,
Part::Reach => reach.to_vec(),
};
tokens.extend(written);
}
Ok(tokens)
}
fn restated(pattern: &Pattern) -> Result<Vec<GeneratedToken>, StampError> {
let mut tokens: Vec<GeneratedToken> = Vec::new();
for part in pattern.parts() {
let written = match part {
Part::Literal(material) => material.tokens().to_vec(),
Part::Seat(seat) => forwarded(seat)?,
Part::Reach => Vec::new(),
};
tokens.extend(written);
}
Ok(tokens)
}
fn seat_matched(seat: &Seat) -> Result<Vec<GeneratedToken>, StampError> {
match seat.seating() {
Seating::One(fragment) => Ok(fragment_of(seat.name(), fragment.name())),
Seating::Many(fragment) => repeated(fragment_of(seat.name(), fragment.name()), Some(',')),
Seating::Attributes => {
let inner = token::group(
GeneratedDelimiter::Bracket,
fragment_of(seat.name(), Fragment::Attribute.name()),
)?;
repeated(vec![GeneratedToken::alone('#'), inner], None)
}
}
}
fn fragment_of(name: &str, kind: &str) -> Vec<GeneratedToken> {
let mut tokens = token::metavariable(name);
tokens.push(GeneratedToken::alone(':'));
tokens.push(GeneratedToken::word(kind));
tokens
}
fn bracketed_reach(mut reach: Vec<GeneratedToken>) -> Result<GeneratedToken, StampError> {
reach.push(GeneratedToken::alone(','));
Ok(token::group(GeneratedDelimiter::Bracket, reach)?)
}
fn repeated(
inner: Vec<GeneratedToken>,
separator: Option<char>,
) -> Result<Vec<GeneratedToken>, StampError> {
let mut tokens = vec![
GeneratedToken::joint('$'),
token::group(GeneratedDelimiter::Parenthesis, inner)?,
];
if let Some(mark) = separator {
tokens.push(GeneratedToken::alone(mark));
}
tokens.push(GeneratedToken::alone('*'));
Ok(tokens)
}
fn scoped(inside: Vec<GeneratedToken>) -> Result<Vec<GeneratedToken>, StampError> {
Ok(vec![
GeneratedToken::word("pub"),
token::group(GeneratedDelimiter::Parenthesis, inside)?,
])
}
fn rule(
matcher: Vec<GeneratedToken>,
expansion: Vec<GeneratedToken>,
) -> Result<Vec<GeneratedToken>, StampError> {
Ok(vec![
token::group(GeneratedDelimiter::Parenthesis, matcher)?,
GeneratedToken::joint('='),
GeneratedToken::alone('>'),
token::group(GeneratedDelimiter::Brace, expansion)?,
GeneratedToken::alone(';'),
])
}
fn stamp_path(site: &Site, name: &str) -> Vec<GeneratedToken> {
let mut tokens: Vec<GeneratedToken> = Vec::new();
for segment in site.root().segments() {
if !tokens.is_empty() {
tokens.push(GeneratedToken::joint(':'));
tokens.push(GeneratedToken::alone(':'));
}
tokens.push(GeneratedToken::word(segment.as_str()));
}
tokens.push(GeneratedToken::joint(':'));
tokens.push(GeneratedToken::alone(':'));
tokens.push(GeneratedToken::word(name));
tokens
}