Skip to content

Commit

Permalink
Quote macro (#1808) (#1810)
Browse files Browse the repository at this point in the history
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Mateusz Kowalski <mateusz.kowalski@swmansion.com>
Co-authored-by: dependabot[bot]
<49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Maksim Zdobnikau
<43750648+DelevoXDG@users.noreply.github.com>

commit-id:face0a80

---

**Stack**:
- #1811
- #1810⚠️ *Part of a stack created by [spr](https://github.com/ejoffe/spr). Do
not merge manually using the UI - doing so may have unexpected results.*
  • Loading branch information
maciektr authored Dec 5, 2024
1 parent 1d8b3d5 commit fe6bc0f
Show file tree
Hide file tree
Showing 9 changed files with 600 additions and 1 deletion.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"plugins/cairo-lang-macro",
"plugins/cairo-lang-macro-attributes",
"plugins/cairo-lang-macro-stable",
"plugins/cairo-lang-quote",
"utils/create-output-dir",
"utils/scarb-proc-macro-server-types",
"utils/scarb-build-metadata",
Expand Down
2 changes: 2 additions & 0 deletions plugins/cairo-lang-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ repository.workspace = true
bumpalo.workspace = true
cairo-lang-macro-attributes = { path = "../cairo-lang-macro-attributes" }
cairo-lang-macro-stable = { path = "../cairo-lang-macro-stable" }
cairo-lang-primitive-token = "1.0.0"
cairo-lang-quote = { path = "../cairo-lang-quote", version = "0.1.0" }
linkme.workspace = true
serde = { workspace = true, optional = true }

Expand Down
4 changes: 3 additions & 1 deletion plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
//!
pub use cairo_lang_macro_attributes::*;
pub use cairo_lang_quote::*;

#[doc(hidden)]
pub use linkme;

use std::cell::RefCell;

use cairo_lang_macro_stable::ffi::StableSlice;
Expand All @@ -28,7 +31,6 @@ use std::ffi::{c_char, CStr, CString};
use std::ops::Deref;

mod types;

pub use types::*;

// A thread-local allocation context for allocating tokens on proc macro side.
Expand Down
76 changes: 76 additions & 0 deletions plugins/cairo-lang-macro/src/types/token.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::{CALL_SITE, CONTEXT};
use bumpalo::Bump;
use cairo_lang_primitive_token::{PrimitiveSpan, PrimitiveToken, ToPrimitiveTokenStream};
use std::fmt::{Debug, Display, Write};
use std::hash::{Hash, Hasher};
use std::iter::{once, Map, Once};
use std::ops::Deref;
use std::rc::Rc;
use std::vec::IntoIter;

/// An abstract stream of Cairo tokens.
///
Expand Down Expand Up @@ -278,6 +281,46 @@ impl TokenStream {
pub fn is_empty(&self) -> bool {
self.tokens.is_empty()
}

pub fn from_primitive_token_stream(
stable_token_stream: impl Iterator<Item = PrimitiveToken>,
) -> Self {
Self::new(
stable_token_stream
.map(|stable_token| {
TokenTree::Ident(Token::new(
stable_token.content,
stable_token
.span
.map(|stable_span| TextSpan {
start: stable_span.start as u32,
end: stable_span.end as u32,
})
.unwrap_or(TextSpan::call_site()),
))
})
.collect(),
)
}

pub fn push_token(&mut self, token_tree: TokenTree) {
self.tokens.push(token_tree);
}
}

impl IntoIterator for TokenStream {
type Item = TokenTree;
type IntoIter = IntoIter<TokenTree>;

fn into_iter(self) -> Self::IntoIter {
self.tokens.into_iter()
}
}

impl Extend<TokenTree> for TokenStream {
fn extend<T: IntoIterator<Item = TokenTree>>(&mut self, iter: T) {
self.tokens.extend(iter);
}
}

impl Display for TokenStream {
Expand Down Expand Up @@ -355,6 +398,39 @@ impl Token {
}
}

impl ToPrimitiveTokenStream for TokenStream {
type Iter = Map<IntoIter<TokenTree>, fn(TokenTree) -> PrimitiveToken>;
fn to_primitive_token_stream(&self) -> Self::Iter {
self.tokens
.clone()
.into_iter()
.map(|token_tree| match token_tree {
TokenTree::Ident(token) => PrimitiveToken::new(
token.content.to_string(),
Some(PrimitiveSpan {
start: token.span.start as usize,
end: token.span.end as usize,
}),
),
})
}
}

impl ToPrimitiveTokenStream for TokenTree {
type Iter = Once<PrimitiveToken>;
fn to_primitive_token_stream(&self) -> Self::Iter {
once(match self {
TokenTree::Ident(token) => PrimitiveToken::new(
token.content.to_string(),
Some(PrimitiveSpan {
start: token.span.start as usize,
end: token.span.end as usize,
}),
),
})
}
}

#[cfg(test)]
mod test {
use crate::{AllocationContext, TextSpan, Token, TokenStream, TokenTree};
Expand Down
19 changes: 19 additions & 0 deletions plugins/cairo-lang-quote/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "cairo-lang-quote"
version = "0.1.0"
edition.workspace = true

authors.workspace = true
categories = ["development-tools"]
description = "Cairo procedural macro helper for constructing procedural macro results."
homepage.workspace = true
keywords = ["scarb"]
license.workspace = true
repository.workspace = true

[lib]
proc-macro = true

[dependencies]
proc-macro2.workspace = true
quote.workspace = true
115 changes: 115 additions & 0 deletions plugins/cairo-lang-quote/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::iter::Peekable;

use proc_macro::{Delimiter, TokenStream as RustTokenStream, TokenTree as RustTokenTree};
use proc_macro2::{Ident, Span};

extern crate proc_macro;
use quote::quote as rust_quote;

#[derive(Debug)]
enum QuoteToken {
Var(Ident),
Content(String),
Whitespace,
}

enum DelimiterVariant {
Open,
Close,
}

impl QuoteToken {
pub fn from_delimiter(delimiter: Delimiter, variant: DelimiterVariant) -> Self {
match (delimiter, variant) {
(Delimiter::Brace, DelimiterVariant::Open) => Self::Content("{".to_string()),
(Delimiter::Brace, DelimiterVariant::Close) => Self::Content("}".to_string()),
(Delimiter::Bracket, DelimiterVariant::Open) => Self::Content("[".to_string()),
(Delimiter::Bracket, DelimiterVariant::Close) => Self::Content("]".to_string()),
(Delimiter::Parenthesis, DelimiterVariant::Open) => Self::Content("(".to_string()),
(Delimiter::Parenthesis, DelimiterVariant::Close) => Self::Content(")".to_string()),
(Delimiter::None, _) => Self::Content(String::default()),
}
}
}

fn process_token_stream(
mut token_stream: Peekable<impl Iterator<Item = RustTokenTree>>,
output: &mut Vec<QuoteToken>,
) {
// Rust proc macro parser to TokenStream gets rid of all whitespaces.
// Here we just make sure no two identifiers are without a space between them.
let mut was_previous_ident: bool = false;
while let Some(token_tree) = token_stream.next() {
match token_tree {
RustTokenTree::Group(group) => {
let token_iter = group.stream().into_iter().peekable();
let delimiter = group.delimiter();
output.push(QuoteToken::from_delimiter(
delimiter,
DelimiterVariant::Open,
));
process_token_stream(token_iter, output);
output.push(QuoteToken::from_delimiter(
delimiter,
DelimiterVariant::Close,
));
was_previous_ident = false;
}
RustTokenTree::Punct(punct) => {
if punct.as_char() == '#' {
if let Some(RustTokenTree::Ident(ident)) = token_stream.next() {
let var_ident = Ident::new(&ident.to_string(), Span::call_site());
output.push(QuoteToken::Var(var_ident))
}
} else {
output.push(QuoteToken::Content(punct.to_string()));
}
was_previous_ident = false;
}
RustTokenTree::Ident(ident) => {
if was_previous_ident {
output.push(QuoteToken::Whitespace);
}
output.push(QuoteToken::Content(ident.to_string()));
was_previous_ident = true;
}
RustTokenTree::Literal(literal) => {
output.push(QuoteToken::Content(literal.to_string()));
was_previous_ident = false;
}
}
}
}

#[proc_macro]
pub fn quote(input: RustTokenStream) -> RustTokenStream {
let mut parsed_input: Vec<QuoteToken> = Vec::new();
let mut output_token_stream = rust_quote! {
let mut quote_macro_result = ::cairo_lang_macro::TokenStream::empty();
};

let token_iter = input.into_iter().peekable();
process_token_stream(token_iter, &mut parsed_input);

for quote_token in parsed_input.iter() {
match quote_token {
QuoteToken::Content(content) => {
output_token_stream.extend(rust_quote! {
quote_macro_result.push_token(::cairo_lang_macro::TokenTree::Ident(::cairo_lang_macro::Token::new(::std::string::ToString::to_string(#content), ::cairo_lang_macro::TextSpan::call_site())));
});
}
QuoteToken::Var(ident) => {
output_token_stream.extend(rust_quote! {
quote_macro_result.extend(::cairo_lang_macro::TokenStream::from_primitive_token_stream(::cairo_lang_primitive_token::ToPrimitiveTokenStream::to_primitive_token_stream(&#ident)).into_iter());
});
}
QuoteToken::Whitespace => output_token_stream.extend(rust_quote! {
quote_macro_result.push_token(::cairo_lang_macro::TokenTree::Ident(::cairo_lang_macro::Token::new(" ".to_string(), ::cairo_lang_macro::TextSpan::call_site())));
}),
}
}
RustTokenStream::from(rust_quote!({
#output_token_stream
quote_macro_result
}))
}
Loading

0 comments on commit fe6bc0f

Please sign in to comment.