From 1fc300e27e89318305162acc899332bb231b1434 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sun, 5 Nov 2023 23:02:42 +0100 Subject: [PATCH 1/5] Automatically set the transition_coverage feature when EXPANDABLE_INSTRUMENT is set Signed-off-by: Sasha Pourcelot --- expandable-impl/build.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 expandable-impl/build.rs diff --git a/expandable-impl/build.rs b/expandable-impl/build.rs new file mode 100644 index 0000000..f50c50c --- /dev/null +++ b/expandable-impl/build.rs @@ -0,0 +1,11 @@ +use std::env; + +const EXPANDABLE_INSTRUMENT: &str = "EXPANDABLE_INSTRUMENT"; + +fn main() { + println!("cargo:rerun-if-env-changed={EXPANDABLE_INSTRUMENT}"); + + if env::var(EXPANDABLE_INSTRUMENT).is_ok() { + println!("cargo:rustc-cfg=transition_coverage"); + } +} From 872d5160c7d2099b0c3f4fb66c8c0007fce66dad Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sun, 5 Nov 2023 23:10:51 +0100 Subject: [PATCH 2/5] Add logging entry point Signed-off-by: Sasha Pourcelot --- expandable-impl/src/grammar.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/expandable-impl/src/grammar.rs b/expandable-impl/src/grammar.rs index 142953d..b0bfec4 100644 --- a/expandable-impl/src/grammar.rs +++ b/expandable-impl/src/grammar.rs @@ -1,6 +1,9 @@ // Architectural invariant: this module contains basic types that allow to parse // the Rust language. +#[cfg(transition_coverage)] +mod log; + use smallvec::{smallvec, SmallVec}; use crate::{FragmentKind, Terminal}; @@ -23,9 +26,12 @@ impl DynamicState { self, descr: TokenDescription, ) -> Result> { - self.state - .trans(descr, self.stack_top()) - .map(|transition| self.with(transition)) + self.state.trans(descr, self.stack_top()).map(|transition| { + #[cfg(transition_coverage)] + log::log_transition(self.state, descr, self.stack_top(), transition); + + self.with(transition) + }) } pub(crate) fn is_accepting(&self) -> bool { From 19df7ff0c0bac1df72ff438bdc7b69d67832f000 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sun, 5 Nov 2023 23:11:01 +0100 Subject: [PATCH 3/5] Implement logging logic Signed-off-by: Sasha Pourcelot --- expandable-impl/src/grammar.rs | 2 +- expandable-impl/src/grammar/log.rs | 112 +++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 expandable-impl/src/grammar/log.rs diff --git a/expandable-impl/src/grammar.rs b/expandable-impl/src/grammar.rs index b0bfec4..c1c1765 100644 --- a/expandable-impl/src/grammar.rs +++ b/expandable-impl/src/grammar.rs @@ -75,7 +75,7 @@ impl DynamicState { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct Transition { pub(crate) state: State, pub(crate) pop: bool, diff --git a/expandable-impl/src/grammar/log.rs b/expandable-impl/src/grammar/log.rs new file mode 100644 index 0000000..0e5a5c9 --- /dev/null +++ b/expandable-impl/src/grammar/log.rs @@ -0,0 +1,112 @@ +use std::{ + fmt::{Debug, Write as _}, + fs, + fs::OpenOptions, + io::Write as _, + process, + sync::Mutex, +}; + +use crate::{ + grammar::{StackSymbol, State, Transition}, + TokenDescription, +}; + +struct LoggableTransition { + in_state: State, + in_top: Option, + descr: TokenDescription, + out_state: State, + out_top: Option, +} + +impl LoggableTransition { + fn from_actual_transition( + state: State, + descr: TokenDescription, + top: Option, + transition: Transition, + ) -> LoggableTransition { + // Don't count the top symbol as consumed if it has not been popped. + let top = if transition.pop { top } else { None }; + + LoggableTransition { + in_state: state, + in_top: top, + descr, + out_state: transition.state, + out_top: transition.push, + } + } + + fn pp(&self) -> String { + let LoggableTransition { + in_state, + in_top, + descr, + out_state, + out_top, + } = self; + + let mut fields = vec![ + ("in_state", in_state as &dyn Debug), + ("out_state", out_state as &dyn Debug), + ("token", &descr as &dyn Debug), + ]; + + if let Some(in_top) = in_top { + fields.push(("in_top", in_top as &dyn Debug)); + } + + if let Some(out_top) = out_top { + fields.push(("out_top", out_top as &dyn Debug)); + } + + let mut buf = String::new(); + + buf.push_str("{"); + + for (name, value) in fields { + write!(buf, " \"{name}\": \"{:?}\",", value).expect("failed to write to string"); + } + + buf.pop(); + buf.push_str(" }"); + + buf + } +} + +fn transition_id() -> usize { + static TRANSITION_ID: Mutex = Mutex::new(0); + + let mut lock = TRANSITION_ID.lock().unwrap(); + let id = *lock; + *lock += 1; + id +} + +pub(crate) fn log_transition( + state: State, + descr: TokenDescription, + top: Option, + transition: Transition, +) { + let dir_path = format!("target/coverage/{:?}", process::id()); + fs::create_dir_all(&dir_path).expect("failed to create coverage directory"); + + let file_path = format!( + "target/coverage/{:?}/{:?}.json", + process::id(), + transition_id() + ); + let transition = LoggableTransition::from_actual_transition(state, descr, top, transition); + + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(file_path) + .expect("failed to create coverage file"); + + writeln!(file, "{}", transition.pp()).expect("failed to write to coverage file"); +} From a30755b176dcd9cfb11278e5ac75b4ec98fab099 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Fri, 10 Nov 2023 22:07:17 +0100 Subject: [PATCH 4/5] Add the state as well Signed-off-by: Sasha Pourcelot --- expandable-impl/src/grammar.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/expandable-impl/src/grammar.rs b/expandable-impl/src/grammar.rs index c1c1765..781093a 100644 --- a/expandable-impl/src/grammar.rs +++ b/expandable-impl/src/grammar.rs @@ -190,10 +190,12 @@ macro_rules! generate_grammar { } impl $name { + #[allow(unused_variables)] const TRANSITIONS: &'static[ ( &'static [(TokenDescription, Option, State, Option)], Option, + $name, ) ] = &[ $( @@ -209,6 +211,7 @@ macro_rules! generate_grammar { ),* ], generate_grammar!(@inherit $( $inherit )?), + generate_grammar!(@state $in_state), ) ),* ]; From 0e21c4b762403187efbf44c5e1fbca6762647421 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Fri, 10 Nov 2023 22:08:41 +0100 Subject: [PATCH 5/5] Ok this seem to work Signed-off-by: Sasha Pourcelot --- expandable-impl/src/grammar/log.rs | 84 +++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/expandable-impl/src/grammar/log.rs b/expandable-impl/src/grammar/log.rs index 0e5a5c9..9439d3b 100644 --- a/expandable-impl/src/grammar/log.rs +++ b/expandable-impl/src/grammar/log.rs @@ -1,8 +1,9 @@ use std::{ - fmt::{Debug, Write as _}, + fmt::{Debug, Write}, fs, fs::OpenOptions, io::Write as _, + path::Path, process, sync::Mutex, }; @@ -12,6 +13,35 @@ use crate::{ TokenDescription, }; +pub(crate) fn log_transition( + state: State, + descr: TokenDescription, + top: Option, + transition: Transition, +) { + if !Path::new("target/coverage/all.json").exists() { + // Let's pretend fs errors never happen for a while. + log_all_transitions(); + } + let dir_path = format!("target/coverage/{:?}", process::id()); + fs::create_dir_all(&dir_path).expect("failed to create coverage directory"); + + let file_path = format!( + "target/coverage/{:?}/{:?}.json", + process::id(), + transition_id() + ); + let transition = LoggableTransition::from_actual_transition(state, descr, top, transition); + + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(file_path) + .expect("failed to create coverage file"); + + writeln!(file, "{}", transition.pp()).expect("failed to write to coverage file"); +} + struct LoggableTransition { in_state: State, in_top: Option, @@ -86,27 +116,41 @@ fn transition_id() -> usize { id } -pub(crate) fn log_transition( - state: State, - descr: TokenDescription, - top: Option, - transition: Transition, -) { - let dir_path = format!("target/coverage/{:?}", process::id()); - fs::create_dir_all(&dir_path).expect("failed to create coverage directory"); - - let file_path = format!( - "target/coverage/{:?}/{:?}.json", - process::id(), - transition_id() - ); - let transition = LoggableTransition::from_actual_transition(state, descr, top, transition); - +fn log_all_transitions() { + fs::create_dir_all("target/coverage").expect("failed to create coverage directory"); let mut file = OpenOptions::new() - .create(true) + .create_new(true) .append(true) - .open(file_path) + .open("target/coverage/all.json") .expect("failed to create coverage file"); - writeln!(file, "{}", transition.pp()).expect("failed to write to coverage file"); + writeln!(file, "[").expect("failed to write to coverage file"); + + let mut first = true; + State::TRANSITIONS + .iter() + .flat_map(|(transes, _, state)| { + transes + .iter() + .map(|(descr, pop, out, push)| (*state, descr, pop, out, push)) + }) + .for_each(|(state, descr, pop, out, push)| { + if first { + first = false; + } else { + writeln!(file, ",").expect("failed to write to coverage file"); + } + + write!( + file, + " {{ \"in_state\": \"{:?}\", \"out_state\": \"{:?}\", \"token\": \"{:?}\", \ + \"in_top\": \"{:?}\", \"out_top\": \"{:?}\" }}", + state, out, descr, pop, push + ) + .expect("failed to write to coverage file"); + }); + + writeln!(file, ",\n]"); } + +fn log_transitions(f: &mut impl Write, state: State) {}