use std::{
any::type_name,
borrow::Cow,
collections::HashMap,
convert::AsRef,
env::{current_dir, var},
error::Error,
fmt::{self, Debug, Write as fmtWrite},
fs::{self, create_dir_all, read_to_string, File},
hash::Hash,
io::{self, Write},
marker::PhantomData,
path::{Path, PathBuf},
};
use bincode::{deserialize, serialize_into};
use cfgrammar::{
yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind},
RIdx, Symbol,
};
use filetime::FileTime;
use lazy_static::lazy_static;
use lrtable::{from_yacc, statetable::Conflicts, Minimiser, StateGraph, StateTable};
use num_traits::{AsPrimitive, PrimInt, Unsigned};
use regex::Regex;
use serde::{de::DeserializeOwned, Serialize};
use crate::RecoveryKind;
const ACTION_PREFIX: &str = "__gt_";
const GLOBAL_PREFIX: &str = "__GT_";
const ACTIONS_KIND: &str = "__GTActionsKind";
const ACTIONS_KIND_PREFIX: &str = "AK";
const ACTIONS_KIND_HIDDEN: &str = "__GTActionsKindHidden";
const RUST_FILE_EXT: &str = "rs";
const GRM_CONST_NAME: &str = "__GRM_DATA";
const STABLE_CONST_NAME: &str = "__STABLE_DATA";
lazy_static! {
static ref RE_DOL_NUM: Regex = Regex::new(r"\$([0-9]+)").unwrap();
}
struct CTConflictsError<StorageT: Eq + Hash> {
pub grm: YaccGrammar<StorageT>,
pub sgraph: StateGraph<StorageT>,
pub stable: StateTable<StorageT>,
}
impl<StorageT> fmt::Display for CTConflictsError<StorageT>
where
StorageT: 'static + Debug + Hash + PrimInt + Serialize + Unsigned,
usize: AsPrimitive<StorageT>,
u32: AsPrimitive<StorageT>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let conflicts = self.stable.conflicts().unwrap();
write!(
f,
"CTConflictsError{{{} Shift/Reduce, {} Reduce/Reduce}}",
conflicts.sr_len(),
conflicts.rr_len()
)
}
}
impl<StorageT> fmt::Debug for CTConflictsError<StorageT>
where
StorageT: 'static + Debug + Hash + PrimInt + Serialize + Unsigned,
usize: AsPrimitive<StorageT>,
u32: AsPrimitive<StorageT>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let conflicts = self.stable.conflicts().unwrap();
write!(
f,
"CTConflictsError{{{} Shift/Reduce, {} Reduce/Reduce}}",
conflicts.sr_len(),
conflicts.rr_len()
)
}
}
impl<StorageT> Error for CTConflictsError<StorageT>
where
StorageT: 'static + Debug + Hash + PrimInt + Serialize + Unsigned,
usize: AsPrimitive<StorageT>,
u32: AsPrimitive<StorageT>,
{
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Visibility {
Private,
Public,
PublicSuper,
PublicSelf,
PublicCrate,
PublicIn(String),
}
impl Visibility {
fn cow_str(&self) -> Cow<'static, str> {
match self {
Visibility::Private => Cow::from(""),
Visibility::Public => Cow::from("pub"),
Visibility::PublicSuper => Cow::from("pub(super)"),
Visibility::PublicSelf => Cow::from("pub(self)"),
Visibility::PublicCrate => Cow::from("pub(crate)"),
Visibility::PublicIn(data) => Cow::from(format!("pub(in {})", data)),
}
}
}
pub struct CTParserBuilder<'a, StorageT = u32>
where
StorageT: Eq + Hash,
{
mod_name: Option<&'a str>,
recoverer: RecoveryKind,
yacckind: Option<YaccKind>,
error_on_conflicts: bool,
visibility: Visibility,
conflicts: Option<(
YaccGrammar<StorageT>,
StateGraph<StorageT>,
StateTable<StorageT>,
)>,
phantom: PhantomData<StorageT>,
}
impl<'a> CTParserBuilder<'a, u32> {
pub fn new() -> Self {
CTParserBuilder::<u32>::new_with_storaget()
}
}
impl<'a, StorageT> CTParserBuilder<'a, StorageT>
where
StorageT: 'static + Debug + Hash + PrimInt + Serialize + Unsigned,
usize: AsPrimitive<StorageT>,
u32: AsPrimitive<StorageT>,
{
pub fn new_with_storaget() -> Self {
CTParserBuilder {
mod_name: None,
recoverer: RecoveryKind::CPCTPlus,
yacckind: None,
error_on_conflicts: true,
visibility: Visibility::Private,
conflicts: None,
phantom: PhantomData,
}
}
pub fn mod_name(mut self, mod_name: &'a str) -> Self {
self.mod_name = Some(mod_name);
self
}
pub fn visibility(mut self, vis: Visibility) -> Self {
self.visibility = vis;
self
}
pub fn recoverer(mut self, rk: RecoveryKind) -> Self {
self.recoverer = rk;
self
}
pub fn yacckind(mut self, yk: YaccKind) -> Self {
self.yacckind = Some(yk);
self
}
pub fn error_on_conflicts(mut self, b: bool) -> Self {
self.error_on_conflicts = b;
self
}
pub fn conflicts(
&self,
) -> Option<(
&YaccGrammar<StorageT>,
&StateGraph<StorageT>,
&StateTable<StorageT>,
&Conflicts<StorageT>,
)> {
if let Some((grm, sgraph, stable)) = &self.conflicts {
return Some((grm, sgraph, stable, &stable.conflicts().unwrap()));
}
None
}
pub fn process_file_in_src(
&mut self,
srcp: &str,
) -> Result<HashMap<String, StorageT>, Box<dyn Error>> {
let mut inp = current_dir()?;
inp.push("src");
inp.push(srcp);
let mut outp = PathBuf::new();
outp.push(var("OUT_DIR").unwrap());
outp.push(Path::new(srcp).parent().unwrap().to_str().unwrap());
create_dir_all(&outp)?;
let mut leaf = Path::new(srcp)
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned();
leaf.push_str(&format!(".{}", RUST_FILE_EXT));
outp.push(leaf);
self.process_file(inp, outp)
}
pub fn process_file<P, Q>(
&mut self,
inp: P,
outp: Q,
) -> Result<HashMap<String, StorageT>, Box<dyn Error>>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let yk = match self.yacckind {
None => panic!("yacckind must be specified before processing."),
Some(YaccKind::Original(x)) => YaccKind::Original(x),
Some(YaccKind::Grmtools) => YaccKind::Grmtools,
Some(YaccKind::Eco) => panic!("Eco compile-time grammar generation not supported."),
};
let inc = read_to_string(&inp).unwrap();
let grm = YaccGrammar::<StorageT>::new_with_storaget(yk, &inc)?;
let rule_ids = grm
.tokens_map()
.iter()
.map(|(&n, &i)| (n.to_owned(), i.as_storaget()))
.collect::<HashMap<_, _>>();
let cache = self.rebuild_cache(&grm);
if let Ok(ref inmd) = fs::metadata(&inp) {
if let Ok(ref out_rs_md) = fs::metadata(&outp) {
if FileTime::from_last_modification_time(out_rs_md)
> FileTime::from_last_modification_time(inmd)
{
if let Ok(outc) = read_to_string(&outp) {
if outc.contains(&cache) {
return Ok(rule_ids);
}
}
}
}
}
fs::remove_file(&outp).ok();
let (sgraph, stable) = from_yacc(&grm, Minimiser::Pager)?;
if stable.conflicts().is_some() && self.error_on_conflicts {
return Err(Box::new(CTConflictsError {
grm,
sgraph,
stable,
}));
}
let mod_name = match self.mod_name {
Some(s) => s.to_owned(),
None => {
let mut stem = inp.as_ref().to_str().unwrap();
loop {
let new_stem = Path::new(stem).file_stem().unwrap().to_str().unwrap();
if stem == new_stem {
break;
}
stem = new_stem;
}
format!("{}_y", stem)
}
};
self.output_file(&grm, &stable, &mod_name, &outp, &cache)?;
if stable.conflicts().is_some() {
self.conflicts = Some((grm, sgraph, stable));
}
Ok(rule_ids)
}
fn output_file<P: AsRef<Path>>(
&self,
grm: &YaccGrammar<StorageT>,
stable: &StateTable<StorageT>,
mod_name: &str,
outp_rs: P,
cache: &str,
) -> Result<(), Box<dyn Error>> {
let mut outs = String::new();
outs.push_str(&format!(
"{} mod {} {{\n",
self.visibility.cow_str(),
mod_name
));
outs.push_str(
" #![allow(clippy::type_complexity)]
#![allow(clippy::unnecessary_wraps)]
",
);
outs.push_str(&self.gen_parse_function(grm, stable)?);
outs.push_str(&self.gen_rule_consts(grm));
outs.push_str(&self.gen_token_epp(&grm));
match self.yacckind.unwrap() {
YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
outs.push_str(&self.gen_wrappers(&grm));
outs.push_str(&self.gen_user_actions(&grm));
}
YaccKind::Original(YaccOriginalActionKind::NoAction)
| YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => (),
_ => unreachable!(),
}
outs.push_str("}\n\n");
outs.push_str(&cache);
let mut f = File::create(outp_rs)?;
f.write_all(outs.as_bytes())?;
Ok(())
}
fn rebuild_cache(&self, grm: &YaccGrammar<StorageT>) -> String {
let mut cache = String::new();
cache.push_str("\n/* CACHE INFORMATION\n");
cache.push_str(&format!(
" Build time: {:?}\n",
env!("VERGEN_BUILD_TIMESTAMP")
));
cache.push_str(&format!(" Mod name: {:?}\n", self.mod_name));
cache.push_str(&format!(" Recoverer: {:?}\n", self.recoverer));
cache.push_str(&format!(" YaccKind: {:?}\n", self.yacckind));
cache.push_str(&format!(" Visibility: {:?}\n", self.visibility.cow_str()));
cache.push_str(&format!(
" Error on conflicts: {:?}\n",
self.error_on_conflicts
));
for tidx in grm.iter_tidxs() {
let n = match grm.token_name(tidx) {
Some(n) => format!("'{}'", n),
None => "<unknown>".to_string(),
};
cache.push_str(&format!(" {} {}\n", usize::from(tidx), n));
}
cache.push_str("*/\n");
cache
}
fn gen_parse_function(
&self,
grm: &YaccGrammar<StorageT>,
stable: &StateTable<StorageT>,
) -> Result<String, Box<dyn Error>> {
let mut outs = String::new();
serialize_bin_output(grm, GRM_CONST_NAME, &mut outs)?;
serialize_bin_output(stable, STABLE_CONST_NAME, &mut outs)?;
match self.yacckind.unwrap() {
YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
outs.push_str(&format!(
"
#[allow(dead_code)]
pub fn parse<'lexer, 'input: 'lexer>(lexer: &'lexer dyn ::lrpar::NonStreamingLexer<'input, {storaget}>)
-> (::std::option::Option<{actiont}>, ::std::vec::Vec<::lrpar::LexParseError<{storaget}>>)
{{",
storaget = type_name::<StorageT>(),
actiont = grm.actiontype(self.user_start_ridx(grm)).as_ref().unwrap()
));
}
YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => {
outs.push_str(&format!(
"
#[allow(dead_code)]
pub fn parse(lexer: &dyn ::lrpar::NonStreamingLexer<{storaget}>)
-> (::std::option::Option<::lrpar::Node<{storaget}>>,
::std::vec::Vec<::lrpar::LexParseError<{storaget}>>)
{{",
storaget = type_name::<StorageT>()
));
}
YaccKind::Original(YaccOriginalActionKind::NoAction) => {
outs.push_str(&format!(
"
#[allow(dead_code)]
pub fn parse(lexer: &dyn ::lrpar::NonStreamingLexer<{storaget}>)
-> ::std::vec::Vec<::lrpar::LexParseError<{storaget}>>
{{",
storaget = type_name::<StorageT>()
));
}
YaccKind::Eco => unreachable!(),
};
outs.push_str(&format!(
"
let (grm, stable) = ::lrpar::ctbuilder::_reconstitute({}, {});",
GRM_CONST_NAME, STABLE_CONST_NAME
));
let recoverer = match self.recoverer {
RecoveryKind::CPCTPlus => "CPCTPlus",
RecoveryKind::None => "None",
};
match self.yacckind.unwrap() {
YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
outs.push_str(&format!(
"\n #[allow(clippy::type_complexity)]
let mut actions: ::std::vec::Vec<&dyn Fn(::cfgrammar::RIdx<{storaget}>,
&'lexer dyn ::lrpar::NonStreamingLexer<'input, {storaget}>,
::lrpar::Span,
::std::vec::Drain<::lrpar::parser::AStackType<{actionskind}<'input>, {storaget}>>)
-> {actionskind}<'input>> = ::std::vec::Vec::new();\n",
actionskind = ACTIONS_KIND,
storaget = type_name::<StorageT>()
));
for pidx in grm.iter_pidxs() {
outs.push_str(&format!(
" actions.push(&{prefix}wrapper_{});\n",
usize::from(pidx),
prefix = ACTION_PREFIX
))
}
outs.push_str(&format!(
"
match ::lrpar::RTParserBuilder::new(&grm, &stable)
.recoverer(::lrpar::RecoveryKind::{recoverer})
.parse_actions(lexer, &actions) {{
(Some({actionskind}::{actionskindprefix}{ridx}(x)), y) => (Some(x), y),
(None, y) => (None, y),
_ => unreachable!()
}}",
actionskind = ACTIONS_KIND,
actionskindprefix = ACTIONS_KIND_PREFIX,
ridx = usize::from(self.user_start_ridx(grm)),
recoverer = recoverer,
));
}
YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => {
outs.push_str(&format!(
"
::lrpar::RTParserBuilder::new(&grm, &stable)
.recoverer(::lrpar::RecoveryKind::{})
.parse_generictree(lexer)\n",
recoverer
));
}
YaccKind::Original(YaccOriginalActionKind::NoAction) => {
outs.push_str(&format!(
"
::lrpar::RTParserBuilder::new(&grm, &stable)
.recoverer(::lrpar::RecoveryKind::{})
.parse_noaction(lexer)\n",
recoverer
));
}
YaccKind::Eco => unreachable!(),
};
outs.push_str("\n }\n\n");
Ok(outs)
}
fn gen_rule_consts(&self, grm: &YaccGrammar<StorageT>) -> String {
let mut outs = String::new();
for ridx in grm.iter_rules() {
if !grm.rule_to_prods(ridx).contains(&grm.start_prod()) {
outs.push_str(&format!(
" #[allow(dead_code)]\n pub const R_{}: {} = {:?};\n",
grm.rule_name(ridx).to_ascii_uppercase(),
type_name::<StorageT>(),
usize::from(ridx)
));
}
}
outs
}
fn gen_token_epp(&self, grm: &YaccGrammar<StorageT>) -> String {
let mut tidxs = Vec::new();
for tidx in grm.iter_tidxs() {
match grm.token_epp(tidx) {
Some(n) => tidxs.push(format!("Some(\"{}\")", str_escape(n))),
None => tidxs.push("None".to_string()),
}
}
format!(
" const {prefix}EPP: &[::std::option::Option<&str>] = &[{}];
/// Return the %epp entry for token `tidx` (where `None` indicates \"the token has no
/// pretty-printed value\"). Panics if `tidx` doesn't exist.
#[allow(dead_code)]
pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<{storaget}>) -> ::std::option::Option<&'a str> {{
{prefix}EPP[usize::from(tidx)]
}}",
tidxs.join(", "),
storaget = type_name::<StorageT>(),
prefix = GLOBAL_PREFIX
)
}
fn gen_wrappers(&self, grm: &YaccGrammar<StorageT>) -> String {
let mut outs = String::new();
outs.push_str("\n\n // Wrappers\n\n");
for pidx in grm.iter_pidxs() {
let ridx = grm.prod_to_rule(pidx);
outs.push_str(&format!(
" fn {prefix}wrapper_{}<'lexer, 'input: 'lexer>({prefix}ridx: ::cfgrammar::RIdx<{storaget}>,
{prefix}lexer: &'lexer dyn ::lrpar::NonStreamingLexer<'input, {storaget}>,
{prefix}span: ::lrpar::Span,
mut {prefix}args: ::std::vec::Drain<::lrpar::parser::AStackType<{actionskind}<'input>, {storaget}>>)
-> {actionskind}<'input> {{",
usize::from(pidx),
storaget = type_name::<StorageT>(),
prefix = ACTION_PREFIX,
actionskind = ACTIONS_KIND,
));
if grm.action(pidx).is_some() {
for i in 0..grm.prod(pidx).len() {
match grm.prod(pidx)[i] {
Symbol::Rule(ref_ridx) => outs.push_str(&format!(
"
let {prefix}arg_{i} = match {prefix}args.next().unwrap() {{
::lrpar::parser::AStackType::ActionType({actionskind}::{actionskindprefix}{ref_ridx}(x)) => x,
_ => unreachable!()
}};",
i = i + 1,
ref_ridx = usize::from(ref_ridx),
prefix = ACTION_PREFIX,
actionskind = ACTIONS_KIND,
actionskindprefix = ACTIONS_KIND_PREFIX
)),
Symbol::Token(_) => outs.push_str(&format!(
"
let {prefix}arg_{} = match {prefix}args.next().unwrap() {{
::lrpar::parser::AStackType::Lexeme(l) => {{
if l.inserted() {{
Err(l)
}} else {{
Ok(l)
}}
}},
::lrpar::parser::AStackType::ActionType(_) => unreachable!()
}};",
i + 1,
prefix = ACTION_PREFIX
))
}
}
let args = (0..grm.prod(pidx).len())
.map(|i| format!("{prefix}arg_{i}", prefix = ACTION_PREFIX, i = i + 1))
.collect::<Vec<_>>();
match grm.actiontype(ridx) {
Some(s) if s == "()" => {
outs.push_str(&format!("\n {prefix}action_{pidx}({prefix}ridx, {prefix}lexer, {prefix}span, {args});
{actionskind}::{actionskindprefix}{ridx}(())",
actionskind = ACTIONS_KIND,
actionskindprefix = ACTIONS_KIND_PREFIX,
prefix = ACTION_PREFIX,
ridx = usize::from(ridx),
pidx = usize::from(pidx),
args = args.join(", ")));
}
_ => {
outs.push_str(&format!("\n {actionskind}::{actionskindprefix}{ridx}({prefix}action_{pidx}({prefix}ridx, {prefix}lexer, {prefix}span, {args}))",
actionskind = ACTIONS_KIND,
actionskindprefix = ACTIONS_KIND_PREFIX,
prefix = ACTION_PREFIX,
ridx = usize::from(ridx),
pidx = usize::from(pidx),
args = args.join(", ")));
}
}
} else if pidx == grm.start_prod() {
outs.push_str(" unreachable!()");
} else {
panic!(
"Production in rule '{}' must have an action body.",
grm.rule_name(grm.prod_to_rule(pidx))
);
}
outs.push_str("\n }\n\n");
}
outs.push_str(&format!(
" #[allow(dead_code)]
enum {}<'input> {{\n",
ACTIONS_KIND
));
for ridx in grm.iter_rules() {
if grm.actiontype(ridx).is_none() {
continue;
}
outs.push_str(&format!(
" {actionskindprefix}{ridx}({actiont}),\n",
actionskindprefix = ACTIONS_KIND_PREFIX,
ridx = usize::from(ridx),
actiont = grm.actiontype(ridx).as_ref().unwrap()
));
}
outs.push_str(&format!(
" _{actionskindhidden}(::std::marker::PhantomData<&'input ()>)
}}\n\n",
actionskindhidden = ACTIONS_KIND_HIDDEN
));
outs
}
fn gen_user_actions(&self, grm: &YaccGrammar<StorageT>) -> String {
let mut outs = String::new();
if let Some(s) = grm.programs() {
outs.push_str("\n// User code from the program section\n\n");
outs.push_str(s);
}
outs.push_str("\n // User actions\n\n");
for pidx in grm.iter_pidxs() {
if pidx == grm.start_prod() {
continue;
}
let mut args = Vec::with_capacity(grm.prod(pidx).len());
for i in 0..grm.prod(pidx).len() {
let argt = match grm.prod(pidx)[i] {
Symbol::Rule(ref_ridx) => grm.actiontype(ref_ridx).as_ref().unwrap().clone(),
Symbol::Token(_) => format!(
"::std::result::Result<::lrpar::Lexeme<{storaget}>, ::lrpar::Lexeme<{storaget}>>",
storaget = type_name::<StorageT>()
)
};
args.push(format!("mut {}arg_{}: {}", ACTION_PREFIX, i + 1, argt));
}
let returnt = {
let actiont = grm.actiontype(grm.prod_to_rule(pidx)).as_ref().unwrap();
if actiont == "()" {
"".to_owned()
} else {
format!("\n-> {}", actiont)
}
};
outs.push_str(&format!(
" // {rulename}
#[allow(clippy::too_many_arguments)]
fn {prefix}action_{}<'lexer, 'input: 'lexer>({prefix}ridx: ::cfgrammar::RIdx<{storaget}>,
{prefix}lexer: &'lexer dyn ::lrpar::NonStreamingLexer<'input, {storaget}>,
{prefix}span: ::lrpar::Span,
{args}) {returnt} {{\n",
usize::from(pidx),
rulename = grm.rule_name(grm.prod_to_rule(pidx)),
storaget = type_name::<StorageT>(),
prefix = ACTION_PREFIX,
returnt = returnt,
args = args.join(",\n ")
));
let pre_action = grm.action(pidx).as_ref().unwrap();
let mut last = 0;
loop {
match pre_action[last..].find('$') {
Some(off) => {
if pre_action[last + off..].starts_with("$$") {
outs.push_str(&pre_action[last..last + off + "$".len()]);
last = last + off + "$$".len();
} else if pre_action[last + off..].starts_with("$lexer") {
outs.push_str(&pre_action[last..last + off]);
outs.push_str(
format!("{prefix}lexer", prefix = ACTION_PREFIX).as_str(),
);
last = last + off + "$lexer".len();
} else if pre_action[last + off..].starts_with("$span") {
outs.push_str(&pre_action[last..last + off]);
outs.push_str(format!("{prefix}span", prefix = ACTION_PREFIX).as_str());
last = last + off + "$span".len();
} else if last + off + 1 < pre_action.len()
&& pre_action[last + off + 1..].starts_with(|c: char| c.is_numeric())
{
outs.push_str(&pre_action[last..last + off]);
outs.push_str(format!("{prefix}arg_", prefix = ACTION_PREFIX).as_str());
last = last + off + "$".len();
} else {
panic!(
"Unknown text following '$' operator: {}",
&pre_action[last + off..]
);
}
}
None => {
outs.push_str(&pre_action[last..]);
break;
}
}
}
outs.push_str("\n }\n\n");
}
outs
}
fn user_start_ridx(&self, grm: &YaccGrammar<StorageT>) -> RIdx<StorageT> {
debug_assert_eq!(grm.prod(grm.start_prod()).len(), 1);
match grm.prod(grm.start_prod())[0] {
Symbol::Rule(ridx) => ridx,
_ => unreachable!(),
}
}
}
fn str_escape(s: &str) -> String {
s.replace("\\", "\\\\").replace("\"", "\\\"")
}
#[doc(hidden)]
pub fn _reconstitute<StorageT: DeserializeOwned + Hash + PrimInt + Unsigned>(
grm_buf: &[u8],
stable_buf: &[u8],
) -> (YaccGrammar<StorageT>, StateTable<StorageT>) {
let grm = deserialize(grm_buf).unwrap();
let stable = deserialize(stable_buf).unwrap();
(grm, stable)
}
fn serialize_bin_output<T: Serialize + ?Sized>(
ser: &T,
name: &str,
buffer: &mut String,
) -> Result<(), Box<dyn Error>> {
let mut w = ArrayWriter::new(name);
serialize_into(&mut w, ser)?;
let data = w.finish();
buffer.push_str(&data);
Ok(())
}
struct ArrayWriter {
buffer: String,
}
impl ArrayWriter {
fn new(name: &str) -> Self {
Self {
buffer: format!(r#"#[allow(dead_code)] const {}: &[u8] = &["#, name),
}
}
fn finish(mut self) -> String {
self.buffer.push_str("];\n");
self.buffer
}
}
impl Write for ArrayWriter {
#[allow(dead_code)]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
for b in buf {
self.buffer.write_fmt(format_args!("{},", b)).unwrap();
}
Ok(buf.len())
}
#[allow(dead_code)]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod test {
use std::{fs::File, io::Write, path::PathBuf};
use super::{CTConflictsError, CTParserBuilder};
use cfgrammar::yacc::{YaccKind, YaccOriginalActionKind};
use tempfile::TempDir;
#[test]
fn test_conflicts() {
let temp = TempDir::new().unwrap();
let mut file_path = PathBuf::from(temp.as_ref());
file_path.push("grm.y");
let mut f = File::create(&file_path).unwrap();
let _ = f.write_all(
"%start A
%%
A : 'a' 'b' | B 'b';
B : 'a' | C;
C : 'a';"
.as_bytes(),
);
let mut ct = CTParserBuilder::new()
.error_on_conflicts(false)
.yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree));
ct.process_file_in_src(file_path.to_str().unwrap()).unwrap();
match ct.conflicts() {
Some((_, _, _, conflicts)) => {
assert_eq!(conflicts.sr_len(), 1);
assert_eq!(conflicts.rr_len(), 1);
}
None => panic!("Expected error data"),
}
}
#[test]
fn test_conflicts_error() {
let temp = TempDir::new().unwrap();
let mut file_path = PathBuf::from(temp.as_ref());
file_path.push("grm.y");
let mut f = File::create(&file_path).unwrap();
let _ = f.write_all(
"%start A
%%
A : 'a' 'b' | B 'b';
B : 'a' | C;
C : 'a';"
.as_bytes(),
);
match CTParserBuilder::new()
.yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
.process_file_in_src(file_path.to_str().unwrap())
{
Ok(_) => panic!("Expected error"),
Err(e) => {
let cs = e.downcast_ref::<CTConflictsError<u32>>();
assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
}
}
}
}