use std::collections::{HashMap, HashSet};
use indexmap::{IndexMap, IndexSet};
use syn::{Expr, Ident, Lifetime, LitStr, Pat, Path, Type};
use super::{
parse::{Input, Output},
reparse::{
parse_grammars, parse_tokens, ParsedBranch, ParsedExtendConv, ParsedGrammar, ParsedNode,
},
};
use crate::prelude::*;
pub struct Context<'diag> {
acceptor_path: Path,
accept_state_path: Path,
input_ty: Type,
pats: HashMap<Ident, (Pat, Option<LitStr>)>,
unused_pats: IndexSet<Ident>,
diag: &'diag mut TokenStream,
}
enum StateIdent {
Variant(Ident),
Const(Ident),
}
struct NodeCx<'ast> {
accept_ty: Ident,
state_ty: Ident,
start_state: Ident,
free_state: u32,
conv_fn_names: HashMap<&'ast Ident, Ident>,
label_states: HashMap<(Option<&'ast Ident>, &'ast Lifetime), StateIdent>,
states: TokenStream,
op_arms: TokenStream,
accept_items: TokenStream,
accept_arms: TokenStream,
}
#[inline]
fn accept_ty(ident: &Ident) -> Ident { Ident::new(&format!("{ident}Accept"), ident.span()) }
#[inline]
fn state_name(id: u32, span: Span) -> Ident { Ident::new(&format!("S{id}"), span) }
#[inline]
fn state_const_name(id: usize, span: Span) -> Ident { Ident::new(&format!("__STATE{id}"), span) }
#[inline]
fn conv_fn_name(id: usize, span: Span) -> Ident { Ident::new(&format!("__conv{id}"), span) }
type ExtendRefs<'ast> = Vec<(&'ast ParsedBranch, Option<&'ast Ident>)>;
type ExtendConvs<'ast> = HashMap<&'ast Ident, Option<&'ast ParsedExtendConv>>;
impl<'diag> Context<'diag> {
pub fn new(
input: Input,
diag: &'diag mut TokenStream,
) -> (Self, IndexMap<Ident, ParsedGrammar>) {
let Input {
alias,
pream_input_token: _,
pream_eq_token: _,
pream_ty,
pream_semi_token: _,
tokens,
grammars,
} = input;
let unused_pats = tokens.iter().map(|t| t.ident.clone()).collect();
let pats = parse_tokens(tokens, diag);
let grammars = parse_grammars(grammars, diag);
let shibari = alias.map_or_else(
|| syn::parse_quote! { ::shibari },
|super::parse::CrateAlias {
use_token: _,
path,
as_token: _,
shibari_token: _,
semi_token: _,
}| path,
);
(
Self {
acceptor_path: syn::parse_quote! { #shibari::Acceptor },
accept_state_path: syn::parse_quote! { #shibari::AcceptState },
input_ty: pream_ty,
pats,
unused_pats,
diag,
},
grammars,
)
}
#[inline]
fn trap_output(&self, span: Span) -> TokenStream {
let accept_state_path = &self.accept_state_path;
quote_spanned! { span => <Self::Output as #accept_state_path>::TRAP }
}
#[inline]
fn advance_output(&self, span: Span) -> TokenStream {
let accept_state_path = &self.accept_state_path;
quote_spanned! { span => <Self::Output as #accept_state_path>::ADVANCE }
}
fn collect_extends<'ast>(
&mut self,
branch: &'ast ParsedBranch,
grammars: &'ast IndexMap<Ident, ParsedGrammar>,
) -> Option<(ExtendRefs<'ast>, ExtendConvs<'ast>)> {
if branch.extends.is_empty() {
return None;
}
let mut branches = vec![(branch, None)];
let mut extend_convs = HashMap::new();
for (ident, with) in &branch.extends {
use std::collections::hash_map::Entry;
let grammar = grammars.get(ident);
if let Some(grammar) = grammar {
branches.push((&grammar.root, Some(ident)));
} else {
self.diag.extend(
ident
.span()
.error("Unrecognized grammar name")
.to_compile_error(),
);
}
let ident_span = ident.span();
match extend_convs.entry(ident) {
Entry::Vacant(v) => {
v.insert(with.as_ref());
},
Entry::Occupied(_) => self.diag.extend(
ident_span
.error("Duplicate extend declaration")
.to_compile_error(),
),
}
}
Some((branches, extend_convs))
}
pub fn emit(mut self, grammars: &IndexMap<Ident, ParsedGrammar>) -> TokenStream {
let mut refs = HashSet::new();
grammars
.values()
.for_each(|g| g.root.extend_references(&mut refs));
let items = grammars
.iter()
.map(|(i, g)| self.grammar(i, g, refs.contains(i), grammars))
.collect();
for name in self.unused_pats {
self.diag
.extend(quote_spanned! { name.span() => #[doc(hidden)] struct #name; });
}
items
}
fn grammar(
&mut self,
ident: &Ident,
grammar: &ParsedGrammar,
referenced: bool,
grammars: &IndexMap<Ident, ParsedGrammar>,
) -> TokenStream {
let ParsedGrammar { vis, output, root } = grammar;
let start_state = state_name(0, ident.span());
let (branches, extend_convs) = self.collect_extends(root, grammars).unwrap_or_default();
let mut node_cx = NodeCx {
accept_ty: accept_ty(ident),
state_ty: Ident::new(&format!("{ident}State"), ident.span()),
start_state: start_state.clone(),
free_state: 1,
conv_fn_names: HashMap::new(),
label_states: HashMap::new(),
states: TokenStream::new(),
op_arms: TokenStream::new(),
accept_items: TokenStream::new(),
accept_arms: TokenStream::new(),
};
self.visit_branch(
&start_state,
&LitStr::new("", ident.span()),
&branches,
&mut node_cx,
);
let default = self.trap_output(ident.span());
let Context {
input_ty,
acceptor_path,
..
} = self;
let NodeCx {
accept_ty,
state_ty,
start_state,
states,
op_arms,
accept_items: conv_fns,
accept_arms,
..
} = node_cx;
let allow_unused = referenced.then(|| {
quote_spanned! { ident.span() =>
#[allow(dead_code, reason = "Generated code, referenced by another grammar")]
}
});
quote_spanned! { ident.span() =>
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[repr(transparent)]
#allow_unused
#vis struct #accept_ty(#state_ty);
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#allow_unused
enum #state_ty {
#[default] #start_state,
#states
}
impl #acceptor_path<#input_ty> for #accept_ty {
type Output = #output;
fn pending_op(&self) -> &'static str {
match self.0 { #op_arms }
}
fn accept(&mut self, __input: #input_ty) -> Self::Output {
#![allow(clippy::never_loop, clippy::useless_conversion, reason = "Generated code")]
#conv_fns
let Self(__state) = self;
loop {
let __out;
(*__state, __out) = match (*__state, __input) {
#accept_arms
_ => (#state_ty::#start_state, #default),
};
break __out;
}
}
}
}
}
fn visit_branch<'ast>(
&mut self,
state: &Ident,
op: &LitStr,
branches: &[(&'ast ParsedBranch, Option<&'ast Ident>)],
node_cx: &mut NodeCx<'ast>,
) {
let state_ty = &node_cx.state_ty;
node_cx
.op_arms
.extend(quote_spanned! { state.span() => #state_ty::#state => #op, });
let mut collected = IndexMap::new();
let mut first_default = None;
for &(branch, extend_ident) in branches {
let ParsedBranch {
label,
out: _,
nodes,
extends,
default,
} = branch;
if let Some(label) = label {
use std::collections::hash_map::Entry;
match node_cx.label_states.entry((extend_ident, label)) {
Entry::Occupied(o) => match o.get() {
StateIdent::Variant(_) => self.diag.extend(
label
.span()
.error("Duplicate node label")
.to_compile_error(),
),
StateIdent::Const(c) => {
let state_ty = &node_cx.state_ty;
node_cx.accept_items.extend(quote_spanned! { c.span() =>
const #c: #state_ty = #state_ty::#state;
});
*o.into_mut() = StateIdent::Variant(state.clone());
},
},
Entry::Vacant(v) => {
v.insert(StateIdent::Variant(state.clone()));
},
}
}
for (tok_ident, node) in nodes {
collected
.entry(tok_ident)
.or_insert(vec![])
.push((node, extend_ident));
}
first_default = first_default.or(default.as_ref().map(|d| (d, extend_ident)));
}
for (tok_ident, nodes) in collected {
self.visit_node(state, op, tok_ident, &nodes, todo!(), node_cx);
}
if let Some((default, extend_ident)) = first_default {
let next = self.output(default, extend_ident, todo!(), node_cx);
let state_ty = &node_cx.state_ty;
node_cx.accept_arms.extend(quote_spanned! { next.span() =>
(#state_ty::#state, _) => #next,
});
}
}
fn visit_node<'ast>(
&mut self,
state: &Ident,
op: &LitStr,
tok_ident: &Ident,
nodes: &[(&'ast ParsedNode, Option<&'ast Ident>)],
extend_convs: &ExtendConvs<'ast>,
node_cx: &mut NodeCx<'ast>,
) {
let Some((pat, tok_op)) = self.pats.get(tok_ident) else {
self.diag.extend(
tok_ident
.span()
.error("Unknown token name")
.to_compile_error(),
);
return;
};
self.unused_pats.shift_remove(tok_ident);
let mut branches = vec![];
let mut advance = None;
for &(node, extend_ident) in nodes {
match node {
ParsedNode::Leaf(out) => {
let next = self.output(out, extend_ident, extend_convs, node_cx);
let state_ty = &node_cx.state_ty;
node_cx
.accept_arms
.extend(quote_spanned! { tok_ident.span() =>
(#state_ty::#state, #pat) => #next,
});
return;
},
ParsedNode::Branch(b @ ParsedBranch { out: Some(o), .. }) => {
branches = vec![(b, extend_ident)];
advance =
Some(self.convert_output_expr(o, extend_ident, extend_convs, node_cx));
break;
},
ParsedNode::Branch(b @ ParsedBranch { out: None, .. }) => {
branches.push((b, extend_ident));
},
}
}
let free = node_cx.free_state;
let next = state_name(free, tok_ident.span());
node_cx.free_state += 1;
node_cx
.states
.extend(quote_spanned! { next.span() => #next, });
let advance = advance.unwrap_or_else(|| self.advance_output(tok_ident.span()));
let state_ty = &node_cx.state_ty;
node_cx
.accept_arms
.extend(quote_spanned! { tok_ident.span() =>
(#state_ty::#state, #pat) => (#state_ty::#next, #advance),
});
let Some(tok_op) = tok_op else {
self.diag.extend(
pat.span()
.error("Missing operator string for branch token")
.to_compile_error(),
);
return;
};
let mut op = op.value();
op.push_str(&tok_op.value());
self.visit_branch(&next, &LitStr::new(&op, tok_op.span()), &branches, node_cx);
}
fn output<'ast>(
&self,
out: &'ast Output,
extend_ident: Option<&'ast Ident>,
extend_convs: &ExtendConvs<'ast>,
node_cx: &mut NodeCx<'ast>,
) -> TokenStream {
match out {
Output::Expr(_, e) => {
let out = self.convert_output_expr(e, extend_ident, extend_convs, node_cx);
let state_ty = &node_cx.state_ty;
let start_state = &node_cx.start_state;
quote_spanned! { e.span() => (#state_ty::#start_state, #out) }
},
Output::Goto(g, l, o) => {
let out = o.as_ref().map_or_else(
|| self.advance_output(g.span()),
|o| self.convert_output_expr(&o.1, extend_ident, extend_convs, node_cx),
);
let free = node_cx.label_states.len();
let next = node_cx
.label_states
.entry((extend_ident, l))
.or_insert_with(|| StateIdent::Const(state_const_name(free, l.span())));
match next {
StateIdent::Variant(i) => {
let state_ty = &node_cx.state_ty;
quote_spanned! { g.span() => (#state_ty::#i, #out) }
},
StateIdent::Const(i) => {
quote_spanned! { g.span() => (#i, #out) }
},
}
},
Output::Continue(c) => {
let state_ty = &node_cx.state_ty;
let start_state = &node_cx.start_state;
quote_spanned! { c.span() =>
{ *__state = #state_ty::#start_state; continue; }
}
},
}
}
fn convert_output_expr<'ast>(
&self,
out: &Expr,
extend_ident: Option<&'ast Ident>,
extend_convs: &ExtendConvs<'ast>,
node_cx: &mut NodeCx<'ast>,
) -> TokenStream {
if let Some(i) = extend_ident {
use std::collections::hash_map::Entry;
let free = node_cx.conv_fn_names.len();
let conv_fn = match node_cx.conv_fn_names.entry(i) {
Entry::Occupied(o) => &*o.into_mut(),
Entry::Vacant(v) => {
let (pat, block) = extend_convs
.get(i)
.unwrap_or_else(|| unreachable!())
.map_or_else(
|| {
(
quote_spanned! { i.span() => __value },
quote_spanned! { i.span() =>
{ ::core::convert::From::from(__value) }
},
)
},
|(p, e)| (p.to_token_stream(), e.to_token_stream()),
);
let fn_name = conv_fn_name(free, i.span());
let extend_ty = accept_ty(i);
let accept_ty = &node_cx.accept_ty;
let input_ty = &self.input_ty;
let acceptor_path = &self.acceptor_path;
node_cx.accept_items.extend(quote_spanned! { i.span() =>
fn #fn_name(
#pat: <#extend_ty as #acceptor_path<#input_ty>>::Output,
) -> <#accept_ty as #acceptor_path<#input_ty>>::Output #block
});
v.insert(fn_name)
},
};
quote_spanned! { i.span() => #conv_fn(::core::convert::Into::into(#out)) }
} else {
quote_spanned! { out.span() =>
<Self::Output as ::core::convert::From<_>>::from(#out)
}
}
}
}