Created
February 9, 2023 21:51
-
-
Save kaleidawave/9ed40e987c8d1f6a04d55f937529cfde to your computer and use it in GitHub Desktop.
Quasi-Quoted + constant compilation AST generating macro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use proc_macro::{token_stream, Delimiter, Spacing, TokenStream, TokenTree}; | |
use proc_macro2::Span; | |
use quote::{format_ident, quote}; | |
/// Used for generating parser::ASTNodes using proc macros | |
/// | |
/// Turns token stream into string. | |
/// - Finds expressions and registers cursor locations | |
/// - Parses structure from string and turns it into Rust tokens | |
#[proc_macro] | |
pub fn expr(item: TokenStream) -> TokenStream { | |
token_stream_to_ast_node::<parser::Expression>(item) | |
} | |
#[proc_macro] | |
pub fn stmt(item: TokenStream) -> TokenStream { | |
token_stream_to_ast_node::<parser::Statement>(item) | |
} | |
struct InterpolationPoint { | |
position: usize, | |
expr_name: String, | |
} | |
fn token_stream_to_ast_node<T: parser::ASTNode + self_rust_tokenize::SelfRustTokenize>( | |
item: TokenStream, | |
) -> TokenStream { | |
let mut cursor_locations = Vec::new(); | |
let mut string = String::new(); | |
parse_token_stream(item.into_iter(), &mut string, &mut cursor_locations); | |
let cursors = cursor_locations | |
.iter() | |
.enumerate() | |
.map(|(idx, InterpolationPoint { position, expr_name: _ })| { | |
( | |
*position, | |
parser::CursorId(idx.try_into().unwrap(), std::marker::PhantomData::default()), | |
) | |
}) | |
.collect(); | |
// dbg!(&cursors, &string); | |
let parser::ParseOutput(node, _) = T::from_string( | |
string, | |
parser::ParseSettings::default(), | |
parser::SourceId::NULL, | |
None, | |
cursors, | |
) | |
.unwrap(); | |
let node_as_tokens = self_rust_tokenize::SelfRustTokenize::to_tokens(&node); | |
let interpolation_tokens = cursor_locations.iter().enumerate().map( | |
|(idx, InterpolationPoint { position: _, expr_name })| { | |
let ident = format_ident!("_cursor_{idx}"); | |
let expr_ident = proc_macro2::Ident::new(expr_name, Span::call_site()); | |
quote!(let #ident = #expr_ident) | |
}, | |
); | |
let tokens = quote! { | |
{ | |
use parser::{ast::*, Span, SourceId}; | |
#(#interpolation_tokens;)* | |
const CURRENT_SOURCE_ID: SourceId = SourceId::NULL; | |
#node_as_tokens | |
} | |
}; | |
// eprintln!("{tokens}"); | |
tokens.into() | |
} | |
fn parse_token_stream( | |
mut token_iter: token_stream::IntoIter, | |
string: &mut String, | |
cursor_locations: &mut Vec<InterpolationPoint>, | |
) { | |
let mut last_was_ident = false; | |
while let Some(token_tree) = token_iter.next() { | |
let current_ident = matches!(token_tree, TokenTree::Ident(_)); | |
match token_tree { | |
TokenTree::Group(group) => { | |
let delimiter = group.delimiter(); | |
let (start, end) = match delimiter { | |
Delimiter::Parenthesis => ("(", ")"), | |
Delimiter::Brace => ("[", "]"), | |
Delimiter::Bracket => ("{", "}"), | |
Delimiter::None => ("", ""), | |
}; | |
string.push_str(start); | |
parse_token_stream(group.stream().into_iter(), string, cursor_locations); | |
string.push_str(end); | |
} | |
TokenTree::Ident(ident) => { | |
if last_was_ident { | |
string.push(' '); | |
} | |
string.push_str(ident.to_string().as_str()); | |
} | |
TokenTree::Punct(punctuation) => { | |
let chr = punctuation.as_char(); | |
if chr == '#' { | |
if let Some(TokenTree::Ident(ident)) = token_iter.next() { | |
let expr_name = ident.to_string(); | |
cursor_locations | |
.push(InterpolationPoint { position: string.len(), expr_name }); | |
} else { | |
panic!("Expected ident") | |
} | |
} else { | |
let spacing = matches!(punctuation.spacing(), Spacing::Alone); | |
if spacing { | |
string.push(' '); | |
} | |
string.push(chr); | |
if spacing { | |
string.push(' '); | |
} | |
} | |
} | |
TokenTree::Literal(literal) => { | |
string.push_str(literal.to_string().as_str()); | |
} | |
} | |
last_was_ident = current_ident; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment