pub mod helpers;
mod attr;
mod expr;
mod function;
mod impls;
mod item;
mod other;
mod structs;
mod types;
use std::cell::RefCell;
use super::ast::PureFile;
thread_local! {
static VERBATIM_EXPR_REGISTRY: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
static VERBATIM_STMT_REGISTRY: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
}
pub(crate) fn register_verbatim_expr(raw: String) -> usize {
VERBATIM_EXPR_REGISTRY.with(|r| {
let mut v = r.borrow_mut();
let idx = v.len();
v.push(raw);
idx
})
}
pub(crate) fn register_verbatim_stmt(raw: String) -> usize {
VERBATIM_STMT_REGISTRY.with(|r| {
let mut v = r.borrow_mut();
let idx = v.len();
v.push(raw);
idx
})
}
fn take_verbatim_expr_registry() -> Vec<String> {
VERBATIM_EXPR_REGISTRY.with(|r| std::mem::take(&mut *r.borrow_mut()))
}
fn take_verbatim_stmt_registry() -> Vec<String> {
VERBATIM_STMT_REGISTRY.with(|r| std::mem::take(&mut *r.borrow_mut()))
}
fn clear_verbatim_expr_registry() {
VERBATIM_EXPR_REGISTRY.with(|r| r.borrow_mut().clear());
VERBATIM_STMT_REGISTRY.with(|r| r.borrow_mut().clear());
}
pub fn format_files_with_rustfmt<P: AsRef<std::path::Path>>(files: &[P]) -> Result<(), String> {
use std::process::Command;
if files.is_empty() {
return Ok(());
}
let output = Command::new("rustfmt")
.args(["--edition", "2021"])
.args(files.iter().map(|p| p.as_ref()))
.output()
.map_err(|e| format!("Failed to spawn rustfmt: {}", e))?;
if output.status.success() {
Ok(())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
pub trait ToSyn {
type Output;
fn to_syn(&self) -> Result<Self::Output, ToSynError>;
}
#[derive(Debug, Clone)]
pub enum ToSynError {
ParsePath {
input: String,
message: String,
},
ParseExpr {
input: String,
message: String,
},
ParsePattern {
input: String,
message: String,
},
ParseType {
input: String,
message: String,
},
MissingValue {
context: String,
},
Other {
message: String,
},
}
impl std::fmt::Display for ToSynError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParsePath { input, message } => {
write!(f, "Failed to parse path '{}': {}", input, message)
}
Self::ParseExpr { input, message } => {
write!(f, "Failed to parse expression '{}': {}", input, message)
}
Self::ParsePattern { input, message } => {
write!(f, "Failed to parse pattern '{}': {}", input, message)
}
Self::ParseType { input, message } => {
write!(f, "Failed to parse type '{}': {}", input, message)
}
Self::MissingValue { context } => {
write!(f, "Missing required value: {}", context)
}
Self::Other { message } => write!(f, "Conversion error: {}", message),
}
}
}
impl std::error::Error for ToSynError {}
impl PureFile {
pub fn to_syn_file(&self) -> Result<syn::File, ToSynError> {
self.to_syn()
}
pub fn to_source(&self) -> Result<String, ToSynError> {
clear_verbatim_expr_registry();
let mut verbatims: Vec<String> = Vec::new();
let staged_items: Vec<crate::pure::PureItem> = self
.items
.iter()
.map(|item| {
if let crate::pure::PureItem::Verbatim(raw) = item {
let idx = verbatims.len();
verbatims.push(raw.clone());
crate::pure::PureItem::Other(format!("fn __ryo_verbatim_{}() {{}}", idx))
} else {
item.clone()
}
})
.collect();
let staged = Self {
attrs: self.attrs.clone(),
items: staged_items,
};
let file = staged.to_syn()?;
let mut out = catch_unwind_silent(|| prettyplease::unparse(&file)).unwrap_or_else(|_| {
use quote::ToTokens;
file.to_token_stream().to_string()
});
for (idx, raw) in verbatims.iter().enumerate() {
let needle = format!("__ryo_verbatim_{}", idx);
if let Some(pos) = out.find(&needle) {
let bol = out[..pos].rfind('\n').map(|p| p + 1).unwrap_or(0);
let eol = out[pos..].find('\n').map(|p| pos + p).unwrap_or(out.len());
let mut rebuilt = String::with_capacity(out.len() + raw.len());
rebuilt.push_str(&out[..bol]);
rebuilt.push_str(raw);
if !raw.ends_with('\n') {
rebuilt.push('\n');
}
rebuilt.push_str(&out[eol..]);
out = rebuilt;
}
}
let expr_verbatims = take_verbatim_expr_registry();
for (idx, raw) in expr_verbatims.iter().enumerate() {
let needle = format!("__ryo_verbatim_expr_{}", idx);
if let Some(pos) = out.find(&needle) {
out.replace_range(pos..pos + needle.len(), raw);
}
}
let stmt_verbatims = take_verbatim_stmt_registry();
for (idx, raw) in stmt_verbatims.iter().enumerate() {
let needle = format!("__ryo_verbatim_stmt_{}", idx);
if let Some(pos) = out.find(&needle) {
let bol = out[..pos].rfind('\n').map(|p| p + 1).unwrap_or(0);
let eol = out[pos..].find('\n').map(|p| pos + p).unwrap_or(out.len());
let mut rebuilt = String::with_capacity(out.len() + raw.len());
rebuilt.push_str(&out[..bol]);
rebuilt.push_str(raw);
if !raw.ends_with('\n') {
rebuilt.push('\n');
}
rebuilt.push_str(&out[eol..]);
out = rebuilt;
}
}
Ok(out)
}
}
fn catch_unwind_silent<F, R>(f: F) -> std::thread::Result<R>
where
F: FnOnce() -> R + std::panic::UnwindSafe,
{
let prev_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let result = std::panic::catch_unwind(f);
std::panic::set_hook(prev_hook);
result
}
impl ToSyn for PureFile {
type Output = syn::File;
fn to_syn(&self) -> Result<syn::File, ToSynError> {
Ok(syn::File {
shebang: None,
attrs: self
.attrs
.iter()
.map(|a| a.to_syn())
.collect::<Result<Vec<_>, _>>()?,
items: self
.items
.iter()
.map(|i| i.to_syn())
.collect::<Result<Vec<_>, _>>()?,
})
}
}
#[cfg(test)]
mod tests;