#![doc = include_str!("../syntax.md")]
#![forbid(unsafe_code)]
#![deny(
clippy::all,
clippy::pedantic,
clippy::cargo,
clippy::nursery,
missing_docs,
rustdoc::all,
future_incompatible
)]
#![warn(missing_debug_implementations)]
#![allow(clippy::enum_glob_use)]
use parse::Error;
use proc_macro::{
token_stream::IntoIter, Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream,
TokenTree,
};
use crate::parse::{create_raw_string, sgr_string, unwrap_string, UnwrappedLiteral};
mod parse;
#[cfg(test)]
mod test;
macro_rules! def_macros {
($($(#[$docs:meta])* $name:ident : $kind:ident),+) => {
$(
$(#[$docs])*
#[doc = include_str!("../keywords.md")]
#[proc_macro]
pub fn $name(input: TokenStream) -> TokenStream {
build_macro(MacroKind::$kind, input)
}
)+
};
}
macro_rules! build_stream {
($first:expr $(,$unit:expr)+) => {{
let mut stream = $first.to_stream();
$(
$unit.extend_from_self(&mut stream);
)*
stream
}};
(from_trees $($unit:expr),*) => {{
[
$(TokenTree::from($unit)),*
].into_iter().map(TokenTree::from).collect()
}};
([$first:expr]) => {{
$first.into();
}};
($unit:expr) => {{
$unit.to_stream()
}};
() => {
TokenStream::new()
};
}
def_macros!(
format : Format,
write : Write,
writeln : Writeln,
print : Print,
println : Println,
eprint : EPrint,
eprintln : EPrintln,
format_args : FormatArgs,
sgr : Sgr
);
#[derive(Clone, Copy, PartialEq, Eq)]
enum MacroKind {
EPrint,
EPrintln,
Format,
FormatArgs,
Print,
Println,
Sgr,
Write,
Writeln,
}
impl MacroKind {
const fn name(&self) -> &str {
use MacroKind::*;
match self {
EPrint => "eprint",
EPrintln => "eprintln",
Format => "format",
FormatArgs => "format_args",
Print => "print",
Println => "println",
Sgr => "",
Write => "write",
Writeln => "writeln",
}
}
}
fn build_macro(kind: MacroKind, input: TokenStream) -> TokenStream {
match kind {
MacroKind::Sgr => match build_args::<true>(kind, input) {
Ok(tokens) | Err(tokens) => tokens,
},
_ => match build_args::<false>(kind, input) {
Ok(tokens) | Err(tokens) => create_macro(kind.name(), Span::mixed_site(), tokens),
},
}
}
fn build_args<const MERGE_CURLY: bool>(
kind: MacroKind,
input: TokenStream,
) -> Result<TokenStream, TokenStream> {
let tokens = input.into_iter();
let stream = match StreamParts::from_parts(kind, tokens) {
Ok(stream) => stream,
Err(tokens) => return Err(tokens),
};
let (literal, parsed_literal) = match &stream.kind {
StreamKind::Standard(literal) | StreamKind::Writer(_, Some((_, literal))) => (
Some(literal),
unwrap_string(&literal.to_string()).map_or_else(
|| ParsedLiteral::InvalidToken(TokenTree::from(literal.clone())),
|unwrapped| ParsedLiteral::parse::<MERGE_CURLY>(&unwrapped),
),
),
StreamKind::Writer(_, None) | StreamKind::Empty => (None, ParsedLiteral::Empty),
};
Ok(match parsed_literal {
ParsedLiteral::String(literal) => match stream.kind {
StreamKind::Writer(writer, Some((punct, _))) => {
build_stream!(writer, punct, literal, stream.tokens)
}
StreamKind::Writer(writer, None) => build_stream!(writer, literal, stream.tokens),
_ => build_stream!(literal, stream.tokens),
},
ParsedLiteral::RawString(string) => {
build_stream!(
match stream.kind {
StreamKind::Writer(writer, Some((punct, _))) => build_stream!(writer, punct),
StreamKind::Writer(writer, None) => build_stream!(writer),
_ => build_stream!(),
},
string,
stream.tokens
)
}
ParsedLiteral::InvalidToken(token) => build_stream!(token, stream.tokens),
ParsedLiteral::InvalidString(e) => return Err(e.into_stream(literal)),
ParsedLiteral::Empty => match stream.kind {
StreamKind::Writer(writer, Some((punct, _))) => {
build_stream!(writer, punct, stream.tokens)
}
StreamKind::Writer(writer, None) => build_stream!(writer),
_ if kind == MacroKind::Sgr => {
return Err(compile_error(Span::mixed_site(), "missing string literal"))
}
_ => build_stream!(),
},
})
}
enum ParsedLiteral {
String(Literal),
RawString(TokenStream),
InvalidToken(TokenTree),
InvalidString(Error),
Empty,
}
impl ParsedLiteral {
fn parse<const MERGE_CURLY: bool>(unwrapped: &UnwrappedLiteral) -> Self {
use UnwrappedLiteral::*;
let check_curly = |ch| match ch {
'}' => Some("{}"),
'{' => Some(if MERGE_CURLY { "{" } else { "{{" }),
_ => None,
};
match unwrapped {
String(s) => match sgr_string(s, check_curly) {
Ok(s) => Self::String(Literal::string(&s)),
Err(e) => Self::InvalidString(e),
},
RawString(s, i) => Self::RawString(
create_raw_string(s, *i)
.parse()
.expect("Raw string parsing failed, should never fail"),
),
}
}
}
struct StreamParts {
kind: StreamKind,
tokens: IntoIter,
}
impl StreamParts {
fn from_parts(kind: MacroKind, mut tokens: IntoIter) -> Result<Self, TokenStream> {
Ok(Self {
kind: match StreamKind::from_kind(kind, &mut tokens) {
Ok(stream_kind) => stream_kind,
Err(err) => return Err(build_stream!(err, tokens)),
},
tokens,
})
}
}
#[derive(Debug)]
enum StreamKind {
Standard(Literal),
Writer(Vec<TokenTree>, Option<(Punct, Literal)>),
Empty,
}
impl StreamKind {
fn from_kind(kind: MacroKind, tokens: &mut IntoIter) -> Result<Self, TokenStream> {
use MacroKind::*;
use StreamKind::*;
match kind {
EPrint | EPrintln | Format | FormatArgs | Print | Println | Sgr => {
match tokens.next() {
Some(TokenTree::Literal(literal)) => Ok(Standard(literal)),
Some(t) => Err(build_stream!(t)),
None => Ok(Empty),
}
}
Write | Writeln => {
fn find_punct(writer: &mut Vec<TokenTree>, tokens: &mut IntoIter) -> Option<Punct> {
for token in tokens.by_ref() {
match token {
TokenTree::Punct(p) if p == ',' => {
return Some(p);
}
_ => writer.push(token),
}
}
None
}
let mut writer = Vec::new();
let Some(punct) = find_punct(&mut writer, tokens) else {
return Ok(Writer(writer, None))
};
match tokens.next() {
Some(TokenTree::Literal(literal)) => Ok(Writer(writer, Some((punct, literal)))),
Some(t) => Err(build_stream!(writer, punct, t)),
None => Err(build_stream!(writer, punct)),
}
}
}
}
}
pub(crate) fn create_macro(macro_call: &str, span: Span, stream: TokenStream) -> TokenStream {
build_stream!( from_trees
Ident::new("std", span),
Punct::new(':', Spacing::Joint),
Punct::new(':', Spacing::Alone),
Ident::new(macro_call, span),
Punct::new('!', Spacing::Alone),
Group::new(Delimiter::Parenthesis, stream)
)
}
pub(crate) fn compile_error(span: Span, message: &str) -> TokenStream {
create_macro(
"compile_error",
span,
build_stream!(Literal::string(message)),
)
}
impl Error {
fn into_stream(self, literal: Option<&Literal>) -> TokenStream {
use std::num::IntErrorKind::*;
use Error::*;
let span = literal.map_or_else(Span::mixed_site, Literal::span);
match self {
ParseInt(e) => compile_error(
span,
match e.kind() {
Empty => "cannot parse integer from empty string",
InvalidDigit => "invalid digit or keyword found",
PosOverflow => "number too large to fit in u8",
NegOverflow => "number too small to fit in u8",
Zero => "number would be zero for non-zero type",
_ => return compile_error(span, &e.to_string()),
},
),
MissingBracket => compile_error(span, "Missing a close bracket"),
InvalidColorLen => compile_error(span, "Incorrect number of digits found"),
CompilerPassOff => {
literal.map_or_else(|| build_stream!(), |literal| build_stream!(literal.clone()))
}
}
}
}
trait StreamUnit {
fn extend_from_self(self, stream: &mut TokenStream);
fn to_stream(self) -> TokenStream;
}
trait ToTree {
fn to_tree(self) -> TokenTree;
}
macro_rules! to_tree {
($($name:ident),*) => {
$(
impl ToTree for $name {
fn to_tree(self) -> TokenTree {
self.into()
}
}
)*
};
}
to_tree!(Group, Ident, Punct, Literal, TokenTree);
impl<T: ToTree> StreamUnit for T {
fn extend_from_self(self, stream: &mut TokenStream) {
stream.extend(Some(self.to_tree()));
}
fn to_stream(self) -> TokenStream {
self.to_tree().into()
}
}
impl StreamUnit for TokenStream {
fn extend_from_self(self, stream: &mut TokenStream) {
stream.extend(self);
}
fn to_stream(self) -> TokenStream {
self
}
}
impl StreamUnit for IntoIter {
fn extend_from_self(self, stream: &mut TokenStream) {
stream.extend(self);
}
fn to_stream(self) -> TokenStream {
self.collect()
}
}
impl StreamUnit for Vec<TokenTree> {
fn extend_from_self(self, stream: &mut TokenStream) {
stream.extend(self);
}
fn to_stream(self) -> TokenStream {
self.into_iter().collect()
}
}