//! Module that holds useful presets for interacting with other modules. use std::env; use std::fmt::Display; use std::fs; use std::io; use std::io::prelude::*; use std::rc::Rc; use lalrpop_util::ParseError; use petgraph::Graph; // grammar is defined in lib.rs, calling lalrpop_mod! twice, generates twice // the code use crate::grammar; use super::structure::RSlabel; use super::structure::{RSset, RSsystem}; use super::translator::Translator; use super::*; // ----------------------------------------------------------------------------- // Structures // ----------------------------------------------------------------------------- #[derive(Debug)] pub struct SaveOptions { pub print: bool, pub save: Option> } impl SaveOptions { pub fn combine(&mut self, other: &mut Self) { self.print = self.print || other.print; match (self.save.is_some(), other.save.is_some()) { (false, false) | (true, false) => {} (false, true) => { self.save = other.save.to_owned(); }, (true, true) => { self.save .as_mut() .unwrap() .append(other.save.as_mut().unwrap());} } } pub fn new() -> Self { SaveOptions { print: false, save: None } } } impl Default for SaveOptions { fn default() -> Self { SaveOptions::new() } } #[derive(Debug)] pub enum GraphSaveOptions { Dot { so: SaveOptions }, GraphML { so: SaveOptions }, Serialize { path: String } } #[derive(Debug)] pub enum Instruction { Stats { so: SaveOptions }, Target { so: SaveOptions }, Run { so: SaveOptions }, Loop { symbol: String, so: SaveOptions }, Frequency { experiment: String, so: SaveOptions }, LimitFrequency { experiment: String, so: SaveOptions }, FastFrequency { experiment: String, so: SaveOptions }, Digraph { gso: Vec }, } #[derive(Debug)] pub enum System { Deserialize { path: String }, RSsystem { sys: RSsystem } } #[derive(Debug)] pub struct Instructions { pub system: System, pub instructions: Vec } // ----------------------------------------------------------------------------- // Helper Functions // ----------------------------------------------------------------------------- fn read_file( translator: &mut Translator, path_string: String, parser: F ) -> Result where F: Fn(&mut Translator, String) -> Result { // relative path let mut path = match env::current_dir() { Ok(p) => p, Err(_) => return Err("Error getting current directory.".into()) }; path = path.join(path_string); // we read the file with a buffer let f = match fs::File::open(path) { Ok(f) => f, Err(_) => return Err("Error opening file.".into()) }; let mut buf_reader = io::BufReader::new(f); let mut contents = String::new(); match buf_reader.read_to_string(&mut contents) { Ok(_) => {}, Err(_) => return Err("Error reading file.".into()) } // parse let result = parser(translator, contents)?; Ok(result) } fn reformat_error( e: ParseError ) -> Result where T: Display { match e { ParseError::ExtraToken { token: (l, t, r) } => { Err(format!( "Unexpected token \"{t}\" \ between positions {l} and {r}." )) }, ParseError::UnrecognizedEof { location: _, expected: _ } => { Err("End of file encountered while parsing.".into()) }, ParseError::InvalidToken { location } => { Err(format!("Invalid token at position {location}.")) }, ParseError::UnrecognizedToken { token: (l, t, r), expected } => { Err(format!( "Unrecognized token \"{t}\" \ between positions {l} and {r}. Expected: {expected:?}" )) }, ParseError::User { error } => { Err(error.to_string()) } } } fn parser_system( translator: &mut Translator, contents: String ) -> Result { match grammar::SystemParser::new() .parse(translator, &contents) { Ok(sys) => Ok(sys), Err(e) => reformat_error(e) } } fn parser_experiment( translator: &mut Translator, contents: String ) -> Result<(Vec, Vec), String> { match grammar::ExperimentParser::new() .parse(translator, &contents) { Ok(sys) => Ok(sys), Err(e) => reformat_error(e) } } fn parser_instructions( translator: &mut Translator, contents: String ) -> Result { match grammar::RunParser::new() .parse(translator, &contents) { Ok(sys) => Ok(sys), Err(e) => reformat_error(e) } } fn save_file( contents: String, path_string: String, extension: String ) -> Result<(), String> { // relative path let mut path = match env::current_dir() { Ok(p) => p, Err(_) => return Err("Error getting current directory.".into()) }; path = path.join(path_string); path.set_extension(extension); let mut f = match fs::File::create(&path) { Ok(f) => f, Err(_) => return Err(format!("Error creating file {}.", path.to_str().unwrap())) }; match write!(f, "{contents}") { Ok(_) => {} Err(_) => return Err("Error writing to file.".into()) } Ok(()) } // ----------------------------------------------------------------------------- // main_do // ----------------------------------------------------------------------------- /// Prints statistics of the system. /// Equivalent main_do(stat) or main_do(stat, MissingE) pub fn stats(path_string: String) -> Result<(), String> { let mut translator = Translator::new(); let system = read_file(&mut translator, path_string, parser_system)?; // print statistics to screan println!("{}", statistics::of_RSsystem(&translator, &system)); Ok(()) } /// Prints a final set of entities in a terminating Reaction System. /// Equivalent to main_do(target, E) pub fn target(path_string: String) -> Result<(), String> { let mut translator = Translator::new(); let system = read_file(&mut translator, path_string, parser_system)?; // the system needs to terminate to return let res = transitions::target(&system)?; println!( "After {} steps we arrive at state:\n{}", res.0, translator::RSsetDisplay::from(&translator, &res.1) ); Ok(()) } /// Finds the list of traversed states in a (deterministic) terminating /// reaction. /// equivalent to main_do(run,Es) pub fn traversed(path_string: String) -> Result<(), String> { let mut translator = Translator::new(); let system = read_file(&mut translator, path_string, parser_system)?; // the system needs to terminate to return let res = transitions::run_separated(&system)?; println!("The trace is composed by the set of entities:"); for (e, _c, _t) in res { println!("{}", translator::RSsetDisplay::from(&translator, &e)); } Ok(()) } /// Finds the looping list of states in a reaction system with a perpetual /// context. IMPORTANT: for loops, we assume Delta defines the process constant /// x = Q.x and the context process is x . /// equivalent to main_do(loop,Es) pub fn hoop(path_string: String) -> Result<(), String> { let mut translator = Translator::new(); let system = read_file(&mut translator, path_string, parser_system)?; // we retrieve the id for "x" and use it to find the corresponding loop let res = match perpetual::lollipops_only_loop_named(system, translator.encode("x")) { Some(o) => o, None => { return Err("No loop found.".into()); } }; println!("The loop is composed by the sets:"); for e in res { println!("{}", translator::RSsetDisplay::from(&translator, &e)); } Ok(()) } /// Finds the frequency of each entity in the traversed states for a /// (deterministic) terminating Reaction System. /// equivalent to main_do(freq, PairList) pub fn freq(path_string: String) -> Result<(), String> { let mut translator = Translator::new(); let system = read_file(&mut translator, path_string, parser_system)?; let res = frequency::naive_frequency(&system)?; println!( "Frequency of encountered symbols:\n{}", translator::FrequencyDisplay::from(&translator, &res) ); Ok(()) } /// Finds the frequency of each entity in the limit loop of a nonterminating /// Reaction System whose context has the form Q1 ... Q1.Q2 ... Q2 ... Qn ... /// equivalent to main_do(limitfreq, PairList) pub fn limit_freq( path_string_system: String, path_string_experiment: String ) -> Result<(), String> { let mut translator = Translator::new(); let system = read_file(&mut translator, path_string_system, parser_system)?; let (_, sets) = read_file(&mut translator, path_string_experiment, parser_experiment)?; let res = match frequency::limit_frequency(&sets, &system.reaction_rules, &system.available_entities) { Some(e) => e, None => {return Err("Error calculating frequency.".into());} }; println!( "Frequency of encountered symbols:\n{}", translator::FrequencyDisplay::from(&translator, &res) ); Ok(()) } /// Finds the frequency of each entity in the traversed loops of a terminating /// reaction system whose context has the form /// Q1 ... Q1.Q2 ... Q2 ... Qn ... Qn.nil and each Qi is repeated Wi times /// read from a corresponding file. /// equivalent to main_do(fastfreq, PairList) pub fn fast_freq( path_string_system: String, path_string_experiment: String ) -> Result<(), String> { let mut translator = Translator::new(); let system = read_file(&mut translator, path_string_system, parser_system)?; let (weights, sets) = read_file(&mut translator, path_string_experiment, parser_experiment)?; let res = match frequency::fast_frequency(&sets, &system.reaction_rules, &system.available_entities, &weights) { Some(e) => e, None => {return Err("Error calculating frequency.".into());} }; println!( "Frequency of encountered symbols:\n{}", translator::FrequencyDisplay::from(&translator, &res) ); Ok(()) } /// Computes the LTS. /// equivalent to main_do(digraph, Arcs) or to main_do(advdigraph, Arcs) pub fn digraph( path_string: String ) -> Result<(Graph, Translator), String> { let mut translator = Translator::new(); let system = read_file(&mut translator, path_string, parser_system)?; // the system needs to terminate to return let res = graph::digraph(system)?; Ok((res, translator)) } // ----------------------------------------------------------------------------- // Output Functions // ----------------------------------------------------------------------------- /// Writes the specified graph to a file in .dot format. pub fn dot( graph: &Graph, translator: &Translator, output: String ) -> Result<(), String> { let rc_translator = Rc::new(translator.to_owned()); // map each value to the corresponding value we want to display let modified_graph = graph.map( |id, node| graph::GraphMapNodesTy::from( graph::GraphMapNodes::Entities, Rc::clone(&rc_translator) ).get()(id, node) + "; " + &graph::GraphMapNodesTy::from( graph::GraphMapNodes::Context, Rc::clone(&rc_translator) ).get()(id, node), graph::GraphMapEdgesTy::from( graph::GraphMapEdges::EntitiesAdded, Rc::clone(&rc_translator) ).get() ); let graph = Rc::new(graph.to_owned()); let edge_formatter = graph::default_edge_formatter(Rc::clone(&graph)); let node_formatter = graph::default_node_formatter(Rc::clone(&graph)); let dot = rsdot::RSDot::with_attr_getters( &modified_graph, &[], &edge_formatter, &node_formatter, ); save_file(format!("{dot}"), output, "dot".into())?; Ok(()) } /// Writes the specified graph to a file in .graphml format. pub fn graphml( graph: &Graph, translator: &Translator, output: String ) -> Result<(), String> { let rc_translator = Rc::new(translator.to_owned()); // map each value to the corresponding value we want to display let modified_graph = graph.map( |id, node| graph::GraphMapNodesTy::from( graph::GraphMapNodes::Entities, Rc::clone(&rc_translator) ).get()(id, node) + "; " + &graph::GraphMapNodesTy::from( graph::GraphMapNodes::Context, Rc::clone(&rc_translator) ).get()(id, node), graph::GraphMapEdgesTy::from(graph::GraphMapEdges::EntitiesAdded, Rc::clone(&rc_translator)).get() ); use petgraph_graphml::GraphMl; let graphml = GraphMl::new(&modified_graph) .pretty_print(true) .export_node_weights_display() .export_edge_weights_display(); save_file(format!("{graphml}"), output, "graphml".into())?; Ok(()) } /// Writes the specified graph, translator tuple to file. /// N.B. graph size in memory might be much larger after serialization and /// deserialization. pub fn serialize( graph: &Graph, translator: &Translator, output_path: String ) -> Result<(), String> { // relative path let mut path = match env::current_dir() { Ok(p) => p, Err(_) => return Err("Error getting current directory.".into()) }; path = path.join(output_path); path.set_extension("cbor"); let f = match fs::File::create(&path) { Ok(f) => f, Err(_) => return Err(format!("Error creating file {}.", path.to_str().unwrap())) }; match serialize::ser(f, graph, translator) { Ok(_) => Ok(()), Err(_) => Err("Error during serialization.".into()) } } /// Reads the specified serialized system from a file. /// N.B. graph size in memory might be much larger after serialization and /// deserialization pub fn deserialize( input_path: String ) -> Result<(Graph, Translator), String> { // relative path let mut path = match env::current_dir() { Ok(p) => p, Err(_) => return Err("Error getting current directory.".into()) }; path = path.join(input_path); path.set_extension("cbor"); let f = match fs::File::open(&path) { Ok(f) => f, Err(_) => return Err(format!("Error opening file {}.", path.to_str().unwrap())) }; match serialize::de(f) { Ok(a) => Ok(a), Err(_) => Err("Error during deserialization.".into()) } } //------------------------------------------------------------------------------ // Interpreting Instructions //------------------------------------------------------------------------------ pub fn run(path: String) -> Result<(), String> { let mut translator = Translator::new(); let instructions = read_file(&mut translator, path, parser_instructions)?; println!("{:?}", instructions); Ok(()) }