Skip to content

Commit

Permalink
generate lists
Browse files Browse the repository at this point in the history
  • Loading branch information
nsidnev authored and aljazerzen committed Dec 6, 2024
1 parent 9e4361f commit b0a30a1
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 132 deletions.
160 changes: 150 additions & 10 deletions edb/edgeql-parser/edgeql-parser-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -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);

Expand All @@ -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,
Expand All @@ -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}");

Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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<Item = &str> {
fn iter_non_terminals(variant_name: &str) -> impl Iterator<Item = (usize, &str)> {
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::<Option<syn::Ident>>()?;
} else if path_eq(&m.path, LIST_TRAILING_PATH) {
allow_trailing_list = m.value()?.parse::<syn::LitBool>()?.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
},
}
}
}
});
}
}
29 changes: 2 additions & 27 deletions edb/edgeql-parser/src/grammar/non_terminals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl Into<ast::GrammarEntryPoint> for EdgeQLGrammarNode {
#[derive(edgeql_parser_derive::Reduce)]
#[output(Box::<ast::Expr>)]
#[stub()]
#[list(separator=COMMA, trailing=true)]
pub enum Expr {
BaseAtomicExpr,
DETACHED_Expr,
Expand Down Expand Up @@ -74,6 +75,7 @@ pub enum Expr {

#[derive(edgeql_parser_derive::Reduce)]
#[output(Vec::<ast::AccessKind>)]
#[list(separator=COMMA)]
pub enum AccessKind {
ALL,
DELETE,
Expand All @@ -100,30 +102,3 @@ impl From<AccessKindNode> for Vec<ast::AccessKind> {
}
}
}

macro_rules! list {
($name: ident, $inner: ident) => {
#[derive(edgeql_parser_derive::Reduce)]
#[output(Vec::<Vec::<ast::AccessKind>>)]
pub enum $name {
$inner,
AccessKindList_COMMA_AccessKind,
}

impl From<AccessKindListNode> for Vec<Vec<ast::AccessKind>> {
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);
95 changes: 0 additions & 95 deletions edb/edgeql-parser/src/grammar/stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()]
Expand Down Expand Up @@ -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()]
Expand Down

0 comments on commit b0a30a1

Please sign in to comment.