use std::iter::repeat_n;
use quote::format_ident;
use syn::parse_quote;
use crate::{
error::Result,
grammar::types::{to_snake_case, SymbolTypeKind},
lang::rustemo_actions::Recognizer,
BuilderType, LexerType, ParserAlgo,
};
use super::{action_name, actions::generate_parser_actions, ParserGenerator, PartGenerator};
pub(crate) struct BasePartGenerator {}
impl BasePartGenerator {
pub fn new() -> Self {
BasePartGenerator {}
}
}
impl<'g, 's> PartGenerator<'g, 's> for BasePartGenerator {
fn header(&self, generator: &ParserGenerator<'g, 's>) -> Result<Vec<syn::Stmt>> {
let actions_file = &generator.actions_file;
let input_type = &generator.input_type;
let mut imports: Vec<syn::Stmt> = vec![];
if let LexerType::Default = generator.settings.lexer_type {
let regex: syn::Stmt = if generator.settings.fancy_regex {
parse_quote! {
use rustemo::fancy_regex::Regex;
}
} else {
parse_quote! {
use rustemo::regex::Regex;
}
};
imports.extend::<Vec<syn::Stmt>>(parse_quote! {
#regex
use rustemo::once_cell::sync::Lazy;
use rustemo::StringLexer;
});
}
imports.push(parse_quote! {
use rustemo::LRBuilder;
});
imports.extend::<Vec<syn::Stmt>>(match generator.settings.builder_type {
BuilderType::Default => parse_quote! {
use super::#actions_file;
},
BuilderType::Generic => parse_quote! {
use rustemo::{TreeNode, TreeBuilder};
},
BuilderType::Custom => parse_quote! {
use std::cell::RefCell;
},
});
imports.extend::<Vec<syn::Stmt>>(match generator.settings.parser_algo {
ParserAlgo::LR => parse_quote! {
use rustemo::{LRParser, LRContext};
},
ParserAlgo::GLR => parse_quote! {
use rustemo::{GlrParser, Forest, GssHead};
},
});
let header: Vec<syn::Stmt> = parse_quote! {
use std::fmt::Debug;
use std::hash::Hash;
use rustemo::{Result, Input as InputT, Lexer, Token,
TokenRecognizer as TokenRecognizerT,
Parser, ParserDefinition, State as StateT, Builder};
#(#imports)*
use rustemo::Action::{self, Shift, Reduce, Accept};
#[allow(unused_imports)]
use rustemo::debug::{log, logn};
#[allow(unused_imports)]
#[cfg(debug_assertions)]
use rustemo::colors::*;
#[allow(unused_imports)]
#[cfg(debug_assertions)]
use rustemo::yansi::Paint;
pub type Input = #input_type;
};
Ok(header)
}
fn symbols(&self, generator: &ParserGenerator<'g, 's>) -> Result<Vec<syn::Stmt>> {
let mut ast: Vec<syn::Stmt> = vec![];
if !matches!(generator.settings.builder_type, BuilderType::Default) {
return Ok(ast);
}
let actions_file = &generator.actions_file;
ast.push(parse_quote! {
#[derive(Debug)]
pub enum Symbol {
Terminal(Terminal),
NonTerminal(NonTerminal)
}
});
let term_variants: Vec<syn::Variant> = generator.grammar.terminals[1..]
.iter()
.filter(|t| t.reachable.get())
.map(|t| {
let name = format_ident!("{}", t.name);
if t.has_content {
parse_quote! {
#name(#actions_file::#name)
}
} else {
parse_quote! {
#name
}
}
})
.collect();
ast.push(parse_quote! {
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
pub enum Terminal {
#(#term_variants),*
}
});
let nonterm_variants: Vec<syn::Variant> = generator
.grammar
.nonterminals()
.iter()
.filter(|nt| nt.reachable.get())
.map(|nt| {
let name = format_ident!("{}", nt.name);
parse_quote! {
#name(#actions_file::#name)
}
})
.collect();
ast.push(parse_quote! {
#[derive(Debug)]
pub enum NonTerminal {
#(#nonterm_variants),*
}
});
Ok(ast)
}
fn types(&self, generator: &ParserGenerator<'g, 's>) -> Result<Vec<syn::Stmt>> {
let mut ast: Vec<syn::Stmt> = vec![];
let token_kind_variants: Vec<syn::Variant> = generator
.grammar
.terminals
.iter()
.map(|t| {
let name = format_ident!("{}", t.name);
parse_quote! { #name }
})
.collect();
ast.extend::<Vec<_>>(parse_quote! {
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TokenKind {
#[default]
#(#token_kind_variants),*
}
use TokenKind as TK;
});
ast.push(parse_quote! {
impl From<TokenKind> for usize {
fn from(t: TokenKind) -> Self {
t as usize
}
}
});
let prodkind_variants: Vec<syn::Variant> = generator
.grammar
.productions()
.iter()
.map(|prod| {
let prod_kind = generator.prod_kind_ident(prod);
parse_quote! {#prod_kind}
})
.collect();
ast.extend::<Vec<_>>(parse_quote! {
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy, PartialEq)]
pub enum ProdKind {
#(#prodkind_variants),*
}
use ProdKind as PK;
});
let display_arms: Vec<syn::Arm> = generator
.grammar
.productions()
.iter()
.map(|&prod| {
let prod_kind_ident = generator.prod_kind_ident(prod);
let prod_str = prod.to_string(generator.grammar);
parse_quote! { ProdKind::#prod_kind_ident => #prod_str }
})
.collect();
ast.push(parse_quote! {
impl std::fmt::Debug for ProdKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
#(#display_arms),*
};
write!(f, "{name}")
}
}
});
let nonterm_kind_variants: Vec<syn::Variant> = generator
.grammar
.nonterminals
.iter()
.map(|nt| {
let nt_kind = format_ident!("{}", nt.name);
parse_quote! {#nt_kind}
})
.collect();
ast.push(parse_quote! {
#[allow(clippy::upper_case_acronyms)]
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub enum NonTermKind {
#(#nonterm_kind_variants),*
}
});
let from_arms: Vec<syn::Arm> = generator
.grammar
.productions()
.iter()
.map(|&prod| {
let prod_kind = generator.prod_kind_ident(prod);
let nt_kind = format_ident!("{}", prod.nonterminal(generator.grammar).name);
parse_quote! { ProdKind::#prod_kind => NonTermKind::#nt_kind }
})
.collect();
ast.push(parse_quote! {
impl From<ProdKind> for NonTermKind {
fn from(prod: ProdKind) -> Self {
match prod {
#(#from_arms),*,
}
}
}
});
let state_variants: Vec<syn::Variant> = generator
.table
.states
.iter()
.map(|state| {
let state_kind = generator.state_kind_ident(state.idx);
parse_quote! {#state_kind}
})
.collect();
ast.push(parse_quote! {
#[allow(clippy::enum_variant_names)]
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum State {
#[default]
#(#state_variants),*
}
});
let layout_state = generator
.table
.layout_state
.map(|s| generator.state_kind_ident(s));
let layout_state: syn::Expr = match layout_state {
Some(state) => parse_quote! { Some(State::#state) },
None => parse_quote! { None },
};
ast.push(parse_quote! {
impl StateT for State {
fn default_layout() -> Option<Self> {
#layout_state
}
}
});
ast.push(parse_quote! {
impl From<State> for usize {
fn from(s: State) -> Self {
s as usize
}
}
});
let state_display_arms: Vec<syn::Arm> = generator
.table
.states
.iter()
.map(|state| {
let state_kind_ident = generator.state_kind_ident(state.idx);
let state_index_str = format!(
"{}:{}",
state.idx,
generator.grammar.symbol_name(state.symbol)
);
parse_quote! { State::#state_kind_ident => #state_index_str }
})
.collect();
ast.push(parse_quote! {
impl std::fmt::Debug for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
#(#state_display_arms),*,
};
write!(f, "{name}")
}
}
});
Ok(ast)
}
fn parser(&self, generator: &ParserGenerator<'g, 's>) -> Result<Vec<syn::Stmt>> {
let parser = &generator.parser;
let parser_definition = &generator.parser_definition;
let mut ast: Vec<syn::Stmt> = vec![];
ast.push(match generator.settings.parser_algo {
ParserAlgo::LR => parse_quote! {
pub(crate) type Context<'i, I> = LRContext<'i, I, State, TokenKind>;
},
ParserAlgo::GLR => parse_quote! {
pub(crate) type Context<'i, I> = GssHead<'i, I, State, TokenKind>;
},
});
let partial_parse: syn::Expr = if generator.settings.partial_parse {
parse_quote! { true }
} else {
parse_quote! { false }
};
let skip_ws = generator.settings.skip_ws && !generator.grammar.has_layout();
let lexer_instance: syn::Expr = match generator.settings.lexer_type {
LexerType::Default => parse_quote! {
StringLexer::new(#skip_ws, &RECOGNIZERS)
},
LexerType::Custom => parse_quote! {
lexer
},
};
let builder_instance: syn::Expr = match generator.settings.builder_type {
BuilderType::Default => parse_quote! {
DefaultBuilder::new()
},
BuilderType::Generic => parse_quote! {
TreeBuilder::new()
},
BuilderType::Custom => {
parse_quote! { builder }
}
};
let has_layout = generator.grammar.has_layout();
let parser_instance: syn::Expr = match generator.settings.parser_algo {
ParserAlgo::LR => parse_quote! {
LRParser::new(&PARSER_DEFINITION, State::default(), #partial_parse, #has_layout,
#lexer_instance, #builder_instance)
},
ParserAlgo::GLR => parse_quote! {
GlrParser::new(&PARSER_DEFINITION, #partial_parse,
#has_layout, #lexer_instance)
},
};
let mut new_parameters: Vec<syn::FnArg> = vec![];
let mut parser_impl_generics: syn::Generics = parse_quote! {};
let mut parser_type_params: Vec<syn::TypeParamBound> = vec![];
let mut where_clause: Vec<syn::WherePredicate> = vec![];
parser_impl_generics.params.push(parse_quote! { 'i });
parser_type_params.push(parse_quote! { 'i });
parser_type_params.push(parse_quote! { Input });
match generator.settings.lexer_type {
LexerType::Default => {
parser_type_params.push(parse_quote! {
StringLexer<Context<'i, Input>, State, TokenKind, TokenRecognizer,
TERMINAL_COUNT>
});
}
LexerType::Custom => {
parser_impl_generics.params.push(parse_quote! { L });
parser_type_params.push(parse_quote! { L });
where_clause.push(parse_quote!{L: Lexer<'i, Context<'i, Input>, State, TokenKind, Input = Input> });
new_parameters.push(parse_quote! { lexer: L });
}
}
match generator.settings.builder_type {
BuilderType::Default => {
parser_type_params.push(parse_quote! { DefaultBuilder });
}
BuilderType::Generic => {
parser_type_params
.push(parse_quote! { TreeBuilder<'i, Input, ProdKind, TokenKind> });
}
BuilderType::Custom => {
parser_impl_generics.params.push(parse_quote! { B });
parser_type_params.push(parse_quote! { B });
where_clause.push(parse_quote! { B: LRBuilder<'i, Input, Context<'i, Input>,
State, ProdKind, TokenKind> });
new_parameters.push(parse_quote! { builder: B });
}
}
let parser_type: syn::Type = if let ParserAlgo::LR = generator.settings.parser_algo {
parse_quote! {
LRParser<'i, Context<'i, I>, State, ProdKind,
TokenKind, NonTermKind, #parser_definition, L, B, I>
}
} else {
parse_quote! {
GlrParser<'i, State, L, ProdKind, TokenKind, NonTermKind,
#parser_definition, I, B>
}
};
ast.push(parse_quote! {
pub struct #parser <'i, I: InputT + ?Sized, L: Lexer<'i, Context<'i, I>,
State, TokenKind, Input = I>, B>(#parser_type);
});
ast.push(if where_clause.is_empty() {
parse_quote! {
#[allow(dead_code)]
impl #parser_impl_generics #parser <#(#parser_type_params),*>
{
pub fn new(#(#new_parameters),*) -> Self {
Self(#parser_instance)
}
}
}
} else {
parse_quote! {
#[allow(dead_code)]
impl #parser_impl_generics #parser <#(#parser_type_params),*>
where
#(#where_clause),*
{
pub fn new(#(#new_parameters),*) -> Self {
Self(#parser_instance)
}
}
}
});
let output_type: syn::Type = if let ParserAlgo::GLR = generator.settings.parser_algo {
parse_quote! {
Forest<'i, I, ProdKind, TokenKind>
}
} else {
parse_quote! {
B::Output
}
};
ast.push(parse_quote! {
#[allow(dead_code)]
impl<'i, I, L, B> Parser<'i, I, Context<'i, I>, State, TokenKind> for #parser <'i, I, L, B>
where
I: InputT + ?Sized + Debug,
L: Lexer<'i, Context<'i, I>, State, TokenKind, Input = I>,
B: LRBuilder<'i, I, Context<'i, I>, State, ProdKind, TokenKind>
{
type Output = #output_type;
fn parse(&self, input: &'i I) -> Result<Self::Output> {
self.0.parse(input)
}
fn parse_with_context(
&self,
context: &mut Context<'i, I>,
input: &'i I,
) -> Result<Self::Output> {
self.0.parse_with_context(context, input)
}
fn parse_file<'a, F: AsRef<std::path::Path>>(
&'a mut self,
file: F,
) -> Result<Self::Output>
where
'a: 'i {
self.0.parse_file(file)
}
}
});
Ok(ast)
}
fn lexer_definition(&self, generator: &ParserGenerator<'g, 's>) -> Result<Vec<syn::Stmt>> {
let mut ast: Vec<syn::Stmt> = vec![];
if !matches!(generator.settings.lexer_type, LexerType::Default) {
return Ok(ast);
}
ast.push(parse_quote! {
#[allow(dead_code)]
#[derive(Debug)]
pub enum Recognizer {
Stop,
StrMatch(&'static str),
RegexMatch(Lazy<Regex>)
}
});
ast.push(parse_quote! {
#[allow(dead_code)]
#[derive(Debug)]
pub struct TokenRecognizer(TokenKind, Recognizer);
});
let regex: syn::Expr = if generator.settings.fancy_regex {
parse_quote! {
Ok(Some(x))
}
} else {
parse_quote! {
Some(x)
}
};
ast.push(parse_quote! {
impl<'i> TokenRecognizerT<'i> for TokenRecognizer {
fn recognize(&self, input: &'i str) -> Option<&'i str> {
match &self {
#[allow(unused_variables)]
TokenRecognizer(token_kind, Recognizer::StrMatch(s)) => {
logn!("{} {:?} -- ", " Recognizing".paint(LOG), token_kind);
if input.starts_with(s){
log!("{}", "recognized".paint(LOG_BOLD));
Some(s)
} else {
log!("{}", "not recognized".paint(WARN));
None
}
},
#[allow(unused_variables)]
TokenRecognizer(token_kind, Recognizer::RegexMatch(r)) => {
logn!("{} {:?} -- ", " Recognizing".paint(LOG), token_kind);
let match_str = r.find(input);
match match_str {
#regex => {
let x_str = x.as_str();
log!("{} '{}'", "recognized".paint(LOG_BOLD), x_str);
Some(x_str)
},
_ => {
log!("{}", "not recognized".paint(WARN));
None
}
}
},
TokenRecognizer(_, Recognizer::Stop) => {
logn!("{} STOP -- "," Recognizing".paint(LOG));
if input.is_empty() {
log!("{}", "recognized".paint(LOG_BOLD));
Some("")
} else {
log!("{}", "not recognized".paint(WARN));
None
}
},
}
}
}
});
let regex_recognizers: Vec<syn::Expr> = generator
.grammar
.terminals
.iter()
.map(|term| {
let token_kind = format_ident!("{}", &term.name);
if term.name == "STOP" {
parse_quote! { TokenRecognizer(TokenKind::STOP, Recognizer::Stop) }
} else {
match &term.recognizer {
Some(r) => match r {
Recognizer::StrConst(s) => {
let s = s.as_ref();
parse_quote! {
TokenRecognizer(TokenKind::#token_kind, Recognizer::StrMatch(#s))
}
},
Recognizer::RegexTerm(r) => {
let r = r.as_ref();
parse_quote! {
TokenRecognizer(TokenKind::#token_kind, Recognizer::RegexMatch(Lazy::new(|| {
Regex::new(concat!("^", #r)).unwrap()
})))
}
},
},
None => panic!("Undefined recognizer for terminal {}", term.name)
}
}
})
.collect();
ast.push(parse_quote! {
pub(crate) static RECOGNIZERS: [TokenRecognizer; TERMINAL_COUNT] = [
#(#regex_recognizers,)*
];
});
Ok(ast)
}
fn builder(&self, generator: &ParserGenerator<'g, 's>) -> Result<Vec<syn::Stmt>> {
let mut ast: Vec<syn::Stmt> = vec![];
if !matches!(generator.settings.builder_type, BuilderType::Default) {
return Ok(ast);
}
if generator.settings.actions {
generate_parser_actions(generator)?;
}
let actions_file = &generator.actions_file;
let root_symbol = &generator.root_symbol;
let context_var = format_ident!("context");
ast.extend::<Vec<syn::Stmt>>(parse_quote! {
pub struct DefaultBuilder {
res_stack: Vec<Symbol>,
}
impl DefaultBuilder {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
res_stack: vec![]
}
}
}
impl Builder for DefaultBuilder
{
type Output = #actions_file::#root_symbol;
fn get_result(&mut self) -> Self::Output {
match self.res_stack.pop().unwrap() {
Symbol::NonTerminal(NonTerminal::#root_symbol(r)) => r,
_ => panic!("Invalid result on the parse stack!"),
}
}
}
});
let mut shift_match_arms: Vec<syn::Arm> = generator.grammar.terminals[1..]
.iter()
.filter(|t| t.reachable.get())
.map(|terminal| {
let action = format_ident!("{}", to_snake_case(&terminal.name));
let term = format_ident!("{}", terminal.name);
if let Some(Recognizer::StrConst(_)) = terminal.recognizer {
parse_quote! {
TokenKind::#term => Terminal::#term
}
} else {
parse_quote! {
TokenKind::#term => Terminal::#term(#actions_file::#action(context, token))
}
}
})
.collect();
if generator.grammar.terminals[1..]
.iter()
.any(|t| !t.reachable.get())
{
shift_match_arms.push(parse_quote! {
_ => panic!("Shift of unreachable terminal!")
})
}
let shift_match_arms = shift_match_arms;
let mut has_nonreachable_nonterminals = false;
let mut reduce_match_arms: Vec<syn::Arm> =
generator.grammar.productions().iter().filter_map(|production| {
let nonterminal = &generator.grammar.nonterminals[production.nonterminal];
if !nonterminal.reachable.get() {
has_nonreachable_nonterminals = true;
return None
}
let rhs_null_len = match &generator.table.production_rn_lengths {
Some(prod_lens) => prod_lens[production.idx],
None => production.rhs.len(),
};
let rhs_len = production.rhs.len();
let choice = &generator.types.as_ref().unwrap().get_type(
nonterminal.idx.symbol_index(generator.grammar.terminals.len()))
.choices[production.ntidx];
let action = format_ident!("{}", action_name(nonterminal, choice));
let prod_kind = generator.prod_kind_ident(production);
let nonterminal = format_ident!("{}", nonterminal.name);
if rhs_len == 0 {
Some(parse_quote!{
ProdKind::#prod_kind => NonTerminal::#nonterminal(#actions_file::#action(#context_var))
})
} else {
if production.rhs_with_content(generator.grammar).is_empty() {
Some(parse_quote! {
ProdKind::#prod_kind => {
let _ = self.res_stack.split_off(self.res_stack.len()-#rhs_len).into_iter();
NonTerminal::#nonterminal(#actions_file::#action(#context_var))
}
})
} else {
let match_expr = |len: usize| -> syn::Expr {
let mut next_rep: Vec<syn::Expr> = repeat_n(parse_quote!{ i.next().unwrap() }, len).collect();
if len > 1 {
parse_quote!{ (#(#next_rep),*) }
} else {
next_rep.pop().unwrap()
}
};
let match_lhs_items = |len: usize| -> Vec<syn::Expr> {
let mut param_count = 0usize;
production.rhs_symbols()
.iter()
.take(len)
.map( |&symbol| {
let param = format_ident!("p{}", param_count);
if generator.grammar.symbol_has_content(symbol) {
param_count += 1;
if generator.grammar.is_term(symbol){
let terminal = format_ident!("{}", generator.grammar.symbol_to_term(symbol).name);
parse_quote!{ Symbol::Terminal(Terminal::#terminal(#param)) }
} else {
let nonterminal = format_ident!("{}", generator.grammar.symbol_to_nonterm(symbol).name);
parse_quote!{ Symbol::NonTerminal(NonTerminal::#nonterminal(#param)) }
}
} else {
parse_quote! { _ }
}
}).collect()
};
let match_lhs = |rhs_len: usize| -> syn::Expr {
let match_lhs_items = match_lhs_items(rhs_len);
if rhs_len > 1 {
parse_quote! { (#(#match_lhs_items),*) }
} else {
parse_quote! { #(#match_lhs_items),* }
}
};
let params = |len| -> Vec<syn::Expr> {
let mut param_count = 0usize;
production.rhs_with_content(generator.grammar).iter()
.map( |assignment| {
if assignment.idx < len {
let param = format_ident! { "p{}", param_count };
param_count += 1;
parse_quote! { #param }
} else {
let ty = generator.types.as_ref().unwrap().get_type(assignment.symbol);
if let SymbolTypeKind::Ref { ref recursive, .. } = ty.kind {
if recursive.get() {
parse_quote! { Box::new(None) }
} else {
parse_quote! { None }
}
} else {
parse_quote! { None }
}
}
}).collect()
};
let match_stmt = |match_lhs, match_expr, params: Vec<syn::Expr>, len| -> syn::Expr {
if len == 1
&& !generator.grammar.symbol_has_content(production.rhs_symbol(0)) {
parse_quote! {
{
NonTerminal::#nonterminal(#actions_file::#action(context, #(#params),*))
}
}
} else {
parse_quote! {
match #match_expr {
#match_lhs => NonTerminal::#nonterminal(#actions_file::#action(context, #(#params),*)),
_ => panic!("Invalid symbol parse stack data.")
}
}
}
};
if rhs_null_len == rhs_len {
let match_lhs = match_lhs(rhs_len);
let match_expr = match_expr(rhs_len);
let params = params(rhs_len);
let match_stmt = match_stmt(match_lhs, match_expr, params, rhs_len);
Some(parse_quote! {
ProdKind::#prod_kind => {
let mut i = self.res_stack.split_off(self.res_stack.len()-#rhs_len).into_iter();
#match_stmt
}
})
} else {
let match_lhs_arms: Vec<syn::Arm> = (rhs_null_len..=rhs_len).map(|len| {
let params = params(len);
if len == 0 {
parse_quote! { 0 => NonTerminal::#nonterminal(#actions_file::#action(context, #(#params),*)) }
} else {
let match_lhs = match_lhs(len);
let match_expr = match_expr(len);
let match_stmt = match_stmt(match_lhs, match_expr, params, len);
parse_quote! { #len => #match_stmt }
}
}).collect();
Some(parse_quote! {
ProdKind::#prod_kind => {
let mut i = self.res_stack.split_off(self.res_stack.len() - prod_len).into_iter();
match prod_len {
#(#match_lhs_arms),*,
_ => panic!("Invalid reduction size!")
}
}
})
}
}
}
}).collect();
if has_nonreachable_nonterminals {
reduce_match_arms.push(parse_quote!(
_ => panic!("Reduce of unreachable nonterminal!")
))
}
let reduce_match_arms = reduce_match_arms;
ast.push(parse_quote! {
impl<'i> LRBuilder<'i, Input,
Context<'i, Input>, State, ProdKind, TokenKind> for DefaultBuilder
{
#![allow(unused_variables)]
fn shift_action(
&mut self,
#context_var: &Context<'i, Input>,
token: Token<'i, Input, TokenKind>) {
let val = match token.kind {
TokenKind::STOP => panic!("Cannot shift STOP token!"),
#(#shift_match_arms),*
};
self.res_stack.push(Symbol::Terminal(val));
}
fn reduce_action(
&mut self,
#context_var: &Context<'i, Input>,
prod: ProdKind,
prod_len: usize) {
let prod = match prod {
#(#reduce_match_arms),*
};
self.res_stack.push(Symbol::NonTerminal(prod));
}
}
});
Ok(ast)
}
fn delegate(&self) -> &dyn PartGenerator<'g, 's> {
unimplemented!("Delegate not defined!")
}
}