From b0a30a170329e7fd7aef7abbb5b72453a077498a Mon Sep 17 00:00:00 2001 From: Nik Sidnev Date: Thu, 28 Nov 2024 17:54:58 -0500 Subject: [PATCH] generate lists --- .../edgeql-parser-derive/src/lib.rs | 160 ++++++++++++++++-- .../src/grammar/non_terminals.rs | 29 +--- edb/edgeql-parser/src/grammar/stub.rs | 95 ----------- 3 files changed, 152 insertions(+), 132 deletions(-) diff --git a/edb/edgeql-parser/edgeql-parser-derive/src/lib.rs b/edb/edgeql-parser/edgeql-parser-derive/src/lib.rs index 42ac1a80273..554d5430806 100644 --- a/edb/edgeql-parser/edgeql-parser-derive/src/lib.rs +++ b/edb/edgeql-parser/edgeql-parser-derive/src/lib.rs @@ -2,6 +2,12 @@ use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::parse_macro_input; +const OUTPUT_ATTR: &str = "output"; +const STUB_ATTR: &str = "stub"; +const LIST_ATTR: &str = "list"; +const LIST_SEPARATOR_PATH: &str = "separator"; +const LIST_TRAILING_PATH: &str = "trailing"; + /// Implements [edgeql_parser::grammar::Reduce] for conversion from CST nodes to /// AST nodes. /// @@ -18,7 +24,14 @@ use syn::parse_macro_input; /// /// If `#[stub()]` attribute is present, the `From` trait is automatically /// derived, filled with `todo!()`. -#[proc_macro_derive(Reduce, attributes(output, stub))] +/// +/// If `#[list(separator=..., trailing=...)]` attribute is present, +/// a `*List` enum is automatically generated with the +/// [edgeql_parser::grammar::Reduce] implementation for that enum, +/// where the `separator` path value will be used to separate list items. +/// If the optional `trailing` path is set to `true`, a `*ListInner` enum +/// is also generated to allow the use of trailing separators. +#[proc_macro_derive(Reduce, attributes(output, stub, list))] pub fn grammar_non_terminal(input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as syn::Item); @@ -29,19 +42,19 @@ pub fn grammar_non_terminal(input: TokenStream) -> TokenStream { let name = &enum_item.ident; let node_name = format_ident!("{name}Node"); - let output_ty = find_list_attribute(&enum_item, "output") + let output_ty = get_list_attribute_tokens(&enum_item, OUTPUT_ATTR) .unwrap_or_else(|| panic!("missing #[output(...)] attribute")); let output_ty: TokenStream = output_ty.clone().into(); let output_ty: syn::Type = parse_macro_input!(output_ty as syn::Type); - let is_stub = find_list_attribute(&enum_item, "stub").is_some(); + let is_stub = get_list_attribute(&enum_item, STUB_ATTR).is_some(); let mut node_variants = proc_macro2::TokenStream::new(); for variant in &enum_item.variants { let variant_name = &variant.ident; let mut kids = proc_macro2::TokenStream::new(); - for non_term_name in iter_non_terminals(&variant_name.to_string()) { + for (_index, non_term_name) in iter_non_terminals(&variant_name.to_string()) { let non_term_name = format_ident!("{non_term_name}"); kids.extend(quote! { <#non_term_name as Reduce>::Output, @@ -59,7 +72,7 @@ pub fn grammar_non_terminal(input: TokenStream) -> TokenStream { let mut args = proc_macro2::TokenStream::new(); let mut calls = proc_macro2::TokenStream::new(); - for (index, non_term_name) in iter_non_terminals(&variant_name.to_string()).enumerate() { + for (index, non_term_name) in iter_non_terminals(&variant_name.to_string()) { let arg_name = format_ident!("arg{index}"); let non_term_name = format_ident!("{non_term_name}"); @@ -91,6 +104,11 @@ pub fn grammar_non_terminal(input: TokenStream) -> TokenStream { }) } + let mut list = proc_macro2::TokenStream::new(); + if let Some(list_attr) = get_list_attribute(&enum_item, LIST_ATTR) { + generate_list(&mut list, name, &output_ty, list_attr); + } + let output = quote!( enum #node_name { #node_variants @@ -108,19 +126,31 @@ pub fn grammar_non_terminal(input: TokenStream) -> TokenStream { } #stub + + #list ); TokenStream::from(output) } -fn find_list_attribute<'a>( +fn get_list_attribute_tokens<'a>( enum_item: &'a syn::ItemEnum, name: &'static str, ) -> Option<&'a proc_macro2::TokenStream> { - enum_item.attrs.iter().find_map(|x| match &x.meta { + get_list_attribute(enum_item, name).and_then(|a| match &a.meta { + syn::Meta::List(ml) => Some(&ml.tokens), + _ => None, + }) +} + +fn get_list_attribute<'a>( + enum_item: &'a syn::ItemEnum, + name: &'static str, +) -> Option<&'a syn::Attribute> { + enum_item.attrs.iter().find_map(|a| match &a.meta { syn::Meta::List(ml) => { if path_eq(&ml.path, name) { - Some(&ml.tokens) + Some(a) } else { None } @@ -133,8 +163,118 @@ fn path_eq(path: &syn::Path, name: &str) -> bool { path.get_ident().map_or(false, |i| i.to_string() == name) } -fn iter_non_terminals(variant_name: &str) -> impl Iterator { +fn iter_non_terminals(variant_name: &str) -> impl Iterator { variant_name .split('_') - .filter(|c| *c != "epsilon" && *c != c.to_ascii_uppercase()) + .enumerate() + .filter(|c| c.1 != "epsilon" && c.1 != c.1.to_ascii_uppercase()) +} + +fn generate_list( + list_stream: &mut proc_macro2::TokenStream, + element: &syn::Ident, + output_ty: &syn::Type, + attr: &syn::Attribute, +) { + let mut separator = None; + let mut allow_trailing_list = false; + + attr.parse_nested_meta(|m| { + if path_eq(&m.path, LIST_SEPARATOR_PATH) { + separator = m.value()?.parse::>()?; + } else if path_eq(&m.path, LIST_TRAILING_PATH) { + allow_trailing_list = m.value()?.parse::()?.value; + } + + Ok(()) + }) + .unwrap_or_else(|_| panic!("Internal error during parsing of #[list()] attribute")); + + if separator.is_none() { + panic!("`separator` path for `#[list(...)]` attribute is required") + } + + let separator = separator.unwrap(); + + if allow_trailing_list { + let mut list_inner_stream = proc_macro2::TokenStream::new(); + + let list_inner = format_ident!("{element}ListInner"); + let list_inner_node = format_ident!("{element}ListInnerNode"); + let inner_sep_elem = format_ident!("{list_inner}_{separator}_{element}"); + + list_inner_stream.extend(quote! { + #[derive(edgeql_parser_derive::Reduce)] + #[output(Vec::<#output_ty>)] + pub enum #list_inner { + #element, + #inner_sep_elem, + } + + impl From<#list_inner_node> for Vec<#output_ty> { + fn from(val: #list_inner_node) -> Self { + match val { + #list_inner_node::#element(e) => { + vec![e] + }, + #list_inner_node::#inner_sep_elem(mut l, e) => { + l.push(e); + l + }, + } + } + } + }); + + let list = format_ident!("{element}List"); + let list_node = format_ident!("{element}ListNode"); + let list_inner_sep = format_ident!("{list_inner}_{separator}"); + + list_stream.extend(quote! { + #[derive(edgeql_parser_derive::Reduce)] + #[output(Vec::<#output_ty>)] + pub enum #list { + #list_inner, + #list_inner_sep, + } + + impl From<#list_node> for Vec<#output_ty> { + fn from(val: #list_node) -> Self { + match val { + #list_node::#list_inner(l) => l, + #list_node::#list_inner_sep(l) => l, + } + } + } + + #list_inner_stream + }); + } else { + let list = format_ident!("{element}List"); + let list_node = format_ident!("{element}ListNode"); + let list_sep_elem = format_ident!("{list}_{separator}_{element}"); + + list_stream.extend(quote! { + #[derive(edgeql_parser_derive::Reduce)] + #[output(Vec::<#output_ty>)] + pub enum #list { + #element, + #list_sep_elem, + } + + impl From<#list_node> for Vec<#output_ty> { + fn from(val: #list_node) -> Self { + match val { + #list_node::#element(e) => { + vec![e] + }, + #list_node::#list_sep_elem(mut l, e) => { + l.push(e); + l + }, + } + } + } + }); + } } diff --git a/edb/edgeql-parser/src/grammar/non_terminals.rs b/edb/edgeql-parser/src/grammar/non_terminals.rs index 468642dc62d..4e4e4640079 100644 --- a/edb/edgeql-parser/src/grammar/non_terminals.rs +++ b/edb/edgeql-parser/src/grammar/non_terminals.rs @@ -29,6 +29,7 @@ impl Into for EdgeQLGrammarNode { #[derive(edgeql_parser_derive::Reduce)] #[output(Box::)] #[stub()] +#[list(separator=COMMA, trailing=true)] pub enum Expr { BaseAtomicExpr, DETACHED_Expr, @@ -74,6 +75,7 @@ pub enum Expr { #[derive(edgeql_parser_derive::Reduce)] #[output(Vec::)] +#[list(separator=COMMA)] pub enum AccessKind { ALL, DELETE, @@ -100,30 +102,3 @@ impl From for Vec { } } } - -macro_rules! list { - ($name: ident, $inner: ident) => { - #[derive(edgeql_parser_derive::Reduce)] - #[output(Vec::>)] - pub enum $name { - $inner, - AccessKindList_COMMA_AccessKind, - } - - impl From for Vec> { - fn from(value: AccessKindListNode) -> Self { - match value { - AccessKindListNode::AccessKind(a) => { - vec![a] - } - AccessKindListNode::AccessKindList_COMMA_AccessKind(mut list, a) => { - list.push(a); - list - } - } - } - } - }; -} - -list!(AccessKindList, AccessKind); diff --git a/edb/edgeql-parser/src/grammar/stub.rs b/edb/edgeql-parser/src/grammar/stub.rs index 62498f01813..fd2f3d51890 100644 --- a/edb/edgeql-parser/src/grammar/stub.rs +++ b/edb/edgeql-parser/src/grammar/stub.rs @@ -10,27 +10,6 @@ pub enum AbortMigrationStmt { ABORT_MIGRATION_REWRITE, } -#[derive(edgeql_parser_derive::Reduce)] -#[output(TodoAst)] -#[stub()] -pub enum AccessKind { - ALL, - DELETE, - INSERT, - SELECT, - UPDATE, - UPDATE_READ, - UPDATE_WRITE, -} - -#[derive(edgeql_parser_derive::Reduce)] -#[output(TodoAst)] -#[stub()] -pub enum AccessKindList { - AccessKind, - AccessKindList_COMMA_AccessKind, -} - #[derive(edgeql_parser_derive::Reduce)] #[output(TodoAst)] #[stub()] @@ -3429,80 +3408,6 @@ pub enum EdgeQLBlock { StatementBlock_OptSemicolons, } -#[derive(edgeql_parser_derive::Reduce)] -#[output(ast::GrammarEntryPoint)] -#[stub()] -pub enum EdgeQLGrammar { - STARTBLOCK_EdgeQLBlock_EOI, - STARTEXTENSION_CreateExtensionPackageCommandsBlock_EOI, - STARTFRAGMENT_ExprStmt_EOI, - STARTFRAGMENT_Expr_EOI, - STARTMIGRATION_CreateMigrationCommandsBlock_EOI, - STARTSDLDOCUMENT_SDLDocument_EOI, -} - -#[derive(edgeql_parser_derive::Reduce)] -#[output(TodoAst)] -#[stub()] -pub enum Expr { - BaseAtomicExpr, - DETACHED_Expr, - DISTINCT_Expr, - EXISTS_Expr, - Expr_AND_Expr, - Expr_CIRCUMFLEX_Expr, - Expr_CompareOp_Expr_P_COMPARE_OP, - Expr_DOUBLEPLUS_Expr, - Expr_DOUBLEQMARK_Expr_P_DOUBLEQMARK_OP, - Expr_DOUBLESLASH_Expr, - Expr_EXCEPT_Expr, - Expr_IF_Expr_ELSE_Expr, - Expr_ILIKE_Expr, - Expr_INTERSECT_Expr, - Expr_IN_Expr, - Expr_IS_NOT_TypeExpr_P_IS, - Expr_IS_TypeExpr, - Expr_IndirectionEl, - Expr_LIKE_Expr, - Expr_MINUS_Expr, - Expr_NOT_ILIKE_Expr, - Expr_NOT_IN_Expr_P_IN, - Expr_NOT_LIKE_Expr, - Expr_OR_Expr, - Expr_PERCENT_Expr, - Expr_PLUS_Expr, - Expr_SLASH_Expr, - Expr_STAR_Expr, - Expr_Shape, - Expr_UNION_Expr, - GLOBAL_NodeName, - INTROSPECT_TypeExpr, - IfThenElseExpr, - LANGBRACKET_FullTypeExpr_RANGBRACKET_Expr_P_TYPECAST, - LANGBRACKET_OPTIONAL_FullTypeExpr_RANGBRACKET_Expr_P_TYPECAST, - LANGBRACKET_REQUIRED_FullTypeExpr_RANGBRACKET_Expr_P_TYPECAST, - MINUS_Expr_P_UMINUS, - NOT_Expr, - PLUS_Expr_P_UMINUS, - Path, -} - -#[derive(edgeql_parser_derive::Reduce)] -#[output(TodoAst)] -#[stub()] -pub enum ExprList { - ExprListInner, - ExprListInner_COMMA, -} - -#[derive(edgeql_parser_derive::Reduce)] -#[output(TodoAst)] -#[stub()] -pub enum ExprListInner { - Expr, - ExprListInner_COMMA_Expr, -} - #[derive(edgeql_parser_derive::Reduce)] #[output(ast::Query)] #[stub()]