mod content;
mod eval;
mod scope;
use std::path::PathBuf;
use patricia_tree::StringPatriciaSet;
use quote::quote;
use syn::punctuated::Punctuated;
use syn::{
token, AngleBracketedGenericArguments, ExprLit, ExprTuple, GenericArgument, GenericParam,
Generics, Ident, Item, ItemFn, ItemMod, ItemUse, Lit, LitStr, PathArguments, PathSegment,
ReturnType, Signature, Stmt, TraitBound, TraitBoundModifier, TypeImplTrait, TypeParam,
TypeParamBound, TypePath, UseGroup, UseName, UsePath, UseTree, Visibility,
};
const DOCS_DIR: &str = "docs";
const MAX_TUPLE_LEN: usize = 10;
pub fn files_to_rust(files: Vec<String>) -> String {
let file = syn::File {
shebang: None,
attrs: Vec::new(),
items: vec![files_to_modules(files)],
};
let output = quote!(#file).to_string();
output
}
fn get_module_at(global_module: &mut ItemMod, limit: usize) -> &mut ItemMod {
dbg!(&global_module, limit);
let mut prev_deepest = global_module;
let mut counter = 0;
while counter < limit {
prev_deepest = match prev_deepest.content.as_mut().unwrap().1.last_mut().unwrap() {
Item::Mod(next) => next,
_ => unreachable!(),
};
counter += 1;
}
prev_deepest
}
fn new_module(name: &str) -> ItemMod {
ItemMod {
attrs: Vec::new(),
vis: Visibility::Public(token::Pub::default()),
unsafety: None,
mod_token: token::Mod::default(),
ident: Ident::new(name, proc_macro2::Span::call_site()),
content: Some((
token::Brace::default(),
vec![Item::Use(ItemUse {
attrs: Vec::new(),
vis: Visibility::Inherited,
use_token: token::Use::default(),
leading_colon: None,
tree: UseTree::Path(UsePath {
ident: Ident::new("xilem_html", proc_macro2::Span::call_site()),
colon2_token: token::PathSep::default(),
tree: Box::new(UseTree::Group(UseGroup {
brace_token: token::Brace::default(),
items: Punctuated::<UseTree, token::Comma>::from_iter(
vec![
UseTree::Name(UseName {
ident: Ident::new("elements", proc_macro2::Span::call_site()),
}),
UseTree::Name(UseName {
ident: Ident::new(
"ViewSequence",
proc_macro2::Span::call_site(),
),
}),
]
.into_iter(),
),
})),
}),
semi_token: token::Semi::default(),
})],
)),
semi: None,
}
}
fn fs_path_to_module_ident(path: &str) -> String {
let file_path = PathBuf::from(path);
file_path
.file_name()
.unwrap()
.to_string_lossy()
.split('.')
.next()
.unwrap()
.to_string()
}
fn files_to_modules(mut files: Vec<String>) -> Item {
let paths = StringPatriciaSet::from_iter(files.iter());
files.sort();
let mut global = new_module(DOCS_DIR);
let mut parent = &mut global;
let mut depth = 0;
for file in &files {
let file_path = PathBuf::from(&file);
let file_parent = file_path.parent().unwrap().to_string_lossy();
dbg!(&file_parent);
let prefix = paths
.get_longest_common_prefix(&file_parent)
.unwrap_or(&file_parent);
dbg!(prefix);
let mut common_prefixed = paths.iter_prefix(prefix);
let first_common = &common_prefixed.next().unwrap();
let last_common = &common_prefixed.last().unwrap_or(file.clone());
let typst_ast = eval::eval_file(&file);
let function = scope::typst_scope_to_rust(typst_ast.scope());
if first_common == file && prefix != DOCS_DIR {
depth += 1;
parent
.content
.as_mut()
.unwrap()
.1
.push(Item::Mod(new_module(
&file_path
.parent()
.unwrap()
.file_name()
.unwrap()
.to_string_lossy(),
)));
parent = get_module_at(&mut global, depth);
}
let content = &mut parent.content.as_mut();
content
.get_or_insert(&mut ((token::Brace::default()), Vec::new()))
.1
.push(function);
dbg!(&first_common, &last_common);
if last_common == file && depth > 0 {
depth -= 1;
parent = get_module_at(&mut global, depth);
}
}
Item::Mod(global)
}
fn xilem_to_function(name: &str, blocks: Vec<syn::Expr>) -> Item {
Item::Fn(ItemFn {
attrs: Vec::new(),
vis: Visibility::Public(token::Pub::default()),
sig: Signature {
constness: None,
asyncness: None,
unsafety: None,
abi: None,
fn_token: token::Fn::default(),
ident: Ident::new(name, proc_macro2::Span::call_site()),
generics: Generics { lt_token: None, params: Punctuated::from_iter(vec![GenericParam::Type(TypeParam { attrs: Vec::new(), ident: Ident::new("T", proc_macro2::Span::call_site()), colon_token: None, bounds: Punctuated::new(), eq_token: None, default: None })].into_iter()), gt_token: None, where_clause: None },
paren_token: token::Paren::default(),
inputs: Punctuated::new(),
variadic: None,
output: ReturnType::Type(
token::RArrow::default(),
Box::new(syn::Type::ImplTrait(TypeImplTrait {
impl_token: token::Impl::default(),
bounds: Punctuated::from_iter(
vec![TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: TraitBoundModifier::None,
lifetimes: None,
path: syn::Path {
leading_colon: None,
segments: Punctuated::from_iter(
vec![PathSegment {
ident: Ident::new(
"ViewSequence",
proc_macro2::Span::call_site(),
),
arguments: PathArguments::AngleBracketed(
AngleBracketedGenericArguments {
colon2_token: None,
lt_token: token::Lt::default(),
args: Punctuated::from_iter(
vec![GenericArgument::Type(
syn::Type::Path(TypePath {
qself: None,
path: syn::Path {
leading_colon: None,
segments: Punctuated::from_iter(vec![PathSegment { ident: Ident::new("T", proc_macro2::Span::call_site()), arguments: PathArguments::None }].into_iter()),
},
}),
)]
.into_iter(),
),
gt_token: token::Gt::default(),
},
),
}]
.into_iter(),
),
},
})]
.into_iter(),
),
})),
),
},
block: Box::new(syn::Block {
brace_token: token::Brace::default(),
stmts: vec![Stmt::Expr(
syn::Expr::Tuple(ExprTuple {
attrs: Vec::new(),
paren_token: token::Paren::default(),
elems: Punctuated::from_iter(blocks.into_iter()),
}),
None,
)],
}),
})
}
fn extract_literal_string(expression: &ExprLit) -> String {
if let Lit::Str(lit_str) = &expression.lit {
lit_str.value()
} else {
unreachable!()
}
}
fn typst_to_xilem(root: typst::model::Content) -> Vec<syn::Expr> {
let typed_content = content::SupportedContent::downcast(&root);
let xilem_expressions = typed_content
.to_xilem()
.into_iter()
.fold(Vec::new(), |mut state, inline| {
if let syn::Expr::Lit(current_literal) = &inline {
if let Some(syn::Expr::Lit(ref mut prev_literal)) = state.last_mut() {
let prev_string = extract_literal_string(&prev_literal);
let current_string = extract_literal_string(¤t_literal);
let merged_string = prev_string + ¤t_string;
*prev_literal = literal_string(merged_string.as_str());
return state;
}
}
state.push(inline);
state
});
let tuples = split_tuple(xilem_expressions);
tuples
}
fn literal_string(value: &str) -> ExprLit {
let lit_str = LitStr::new(value, proc_macro2::Span::call_site());
ExprLit {
attrs: Vec::new(),
lit: Lit::Str(lit_str),
}
}
fn split_tuple(expressions: Vec<syn::Expr>) -> Vec<syn::Expr> {
if expressions.len() <= MAX_TUPLE_LEN {
return expressions;
}
let mut elements: Vec<syn::Expr> = Vec::new();
for expression_chunk in expressions.chunks(MAX_TUPLE_LEN) {
let expression_group: Vec<syn::Expr> = expression_chunk.to_vec();
elements.push(syn::Expr::Tuple(ExprTuple {
attrs: Vec::new(),
paren_token: token::Paren::default(),
elems: Punctuated::from_iter(expression_group.into_iter()),
}))
}
if elements.len() > MAX_TUPLE_LEN {
elements = split_tuple(elements);
}
elements
}