use proc_macro2::TokenStream;
use quote::quote;
use syn::{
Expr, LitStr, Result, Token, Type,
parse::{Parse, ParseStream},
};
use super::term::Term;
pub struct Dsl {
pub terms: Vec<Term>,
pub _doc: Option<TokenStream>,
}
impl Parse for Dsl {
fn parse(input: ParseStream) -> Result<Self> {
let string = input.cursor().token_stream().to_string();
let stripped = string
.replace('\"', "")
.replace("& mut", "")
.replace('&', "")
.replace(" ,", ",");
let string = stripped.split_whitespace().collect::<Vec<_>>().join(" ");
let doc = syn::parse_str::<TokenStream>(&format!("#[doc = \"{string}\"]")).ok();
let doc = doc.map(|doc| {
quote! {
#[allow(clippy::suspicious_doc_comments)]
#doc
const _: () = ();
}
});
let mut terms = Vec::new();
let first_terms = parse_term_or_group(input)?;
terms.extend(first_terms);
while input.peek(Token![,]) || input.peek(Token![|]) {
if input.peek(Token![|]) {
input.parse::<Token![|]>()?;
input.parse::<Token![|]>()?;
terms.last_mut().unwrap().oper = super::types::TermOper::Or;
} else {
input.parse::<Token![,]>()?;
if input.is_empty() {
break;
}
}
let next_terms = parse_term_or_group(input)?;
terms.extend(next_terms);
}
Ok(Dsl { terms, _doc: doc })
}
}
fn parse_term_or_group(input: ParseStream) -> Result<Vec<Term>> {
if input.peek(syn::token::Paren) {
let lookahead = input.fork();
let inner;
syn::parenthesized!(inner in lookahead);
if inner.peek(Token![$]) {
let has_or = contains_or_operator(&inner);
let has_equality = contains_equality_operator(&inner);
if has_or {
let paren_content;
syn::parenthesized!(paren_content in input);
let mut group_terms = Vec::new();
group_terms.push(paren_content.parse::<Term>()?);
while paren_content.peek(Token![|]) && paren_content.peek2(Token![|]) {
paren_content.parse::<Token![|]>()?;
paren_content.parse::<Token![|]>()?;
let last_term = group_terms.last_mut().unwrap();
if last_term.oper == super::types::TermOper::Not {
last_term.oper = super::types::TermOper::NotOr;
} else {
last_term.oper = super::types::TermOper::Or;
}
group_terms.push(paren_content.parse::<Term>()?);
}
return Ok(group_terms);
} else if has_equality {
let paren_content;
syn::parenthesized!(paren_content in input);
return Ok(vec![paren_content.parse::<Term>()?]);
}
}
}
Ok(vec![input.parse::<Term>()?])
}
fn contains_or_operator(input: ParseStream) -> bool {
let mut cursor = input.cursor();
while let Some((tt, next)) = cursor.token_tree() {
if let proc_macro2::TokenTree::Punct(punct) = tt
&& punct.as_char() == '|'
{
if let Some((proc_macro2::TokenTree::Punct(next_punct), _)) = next.token_tree()
&& next_punct.as_char() == '|'
{
return true;
}
}
cursor = next;
}
false
}
fn contains_equality_operator(input: ParseStream) -> bool {
let mut cursor = input.cursor();
while let Some((tt, next)) = cursor.token_tree() {
if let proc_macro2::TokenTree::Punct(punct) = tt {
match punct.as_char() {
'=' | '!' | '~' => {
if let Some((proc_macro2::TokenTree::Punct(next_punct), _)) = next.token_tree()
&& next_punct.as_char() == '='
{
return true;
}
}
_ => {}
}
}
cursor = next;
}
false
}
pub struct Builder {
pub name: Option<LitStr>,
pub world: Expr,
pub dsl: Dsl,
}
impl Parse for Builder {
fn parse(input: ParseStream) -> Result<Self> {
let name = if input.peek(LitStr) {
let name = input.parse::<LitStr>()?;
input.parse::<Token![,]>()?;
Some(name)
} else {
None
};
let world = input.parse::<Expr>()?;
input.parse::<Token![,]>()?;
let dsl = input.parse::<Dsl>()?;
Ok(Builder { name, world, dsl })
}
}
pub struct Observer {
pub name: Option<LitStr>,
pub world: Expr,
pub event: Type,
pub dsl: Dsl,
}
impl Parse for Observer {
fn parse(input: ParseStream) -> Result<Self> {
let name = if input.peek(LitStr) {
let name = input.parse::<LitStr>()?;
input.parse::<Token![,]>()?;
Some(name)
} else {
None
};
let world = input.parse::<Expr>()?;
input.parse::<Token![,]>()?;
let event = input.parse::<Type>()?;
input.parse::<Token![,]>()?;
let dsl = input.parse::<Dsl>()?;
Ok(Observer {
name,
world,
event,
dsl,
})
}
}