diff --git a/Cargo.toml b/Cargo.toml index a9042c2..d546dae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,7 @@ log = "0.4.17" eframe = "0.19.0" egui = "0.19.0" wasm-bindgen = "0.2.83" +nom = "7.1.1" +serde = { version = "1.0.151", features = ["derive"] } +serde_yaml = "0.9.16" +serde_json = "1.0.91" diff --git a/src/app.rs b/src/app.rs index c6a695d..2104f8d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,40 +1,16 @@ use eframe::egui; -use egui::{emath, Color32, Frame, Pos2, Rect, RichText, Window}; +use egui::{panel::TopBottomSide, CentralPanel, SidePanel, TopBottomPanel}; -use crate::automata::DFA; -use crate::automata::NFA; -use crate::automata::ReOperator; -use crate::display::DisplayGraphParameter; -use crate::display::Visualizer; -use crate::utils::Graph; +use crate::display::RegularGui; pub struct EguiApp { - error: Option, - regex_text: String, - - // This is indexed accordingly - // 0: Regex - // 1: NFA - // 2: DFA - // 3: Minimized DFA - // a union structure would be useful for accessing the Visualizers - // with both indixes and names, but it's problematic how to do it - // in rust. - to_visualize: [Visualizer; 4], + regular_gui: RegularGui, } impl Default for EguiApp { fn default() -> Self { Self { - error: None, - regex_text: String::new(), - - to_visualize: [ - Visualizer::new("Regex Syntax Tree".to_string()), - Visualizer::new("NFA".to_string()), - Visualizer::new("DFA".to_string()), - Visualizer::new("Minimized DFA".to_string()), - ], + regular_gui: RegularGui::new(), } } } @@ -43,93 +19,21 @@ impl EguiApp { pub fn new(_cc: &eframe::CreationContext) -> Self { Self::default() } - - pub fn get_converter(index: i32) -> impl Fn(ReOperator) -> Graph { - match index { - 0 => |re: ReOperator| re.into(), - 1 => |re: ReOperator| NFA::from(&re).into(), - 2 => |re: ReOperator| DFA::from(&NFA::from(&re)).into(), - 3 => |re: ReOperator| DFA::from(&NFA::from(&re)).get_minimized_dfa().into(), - _ => panic!("Invalid index"), - } - } } impl eframe::App for EguiApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - egui::SidePanel::left("Main").show(ctx, |ui| { - for (index, visualizer) in self.to_visualize.iter_mut().enumerate() { - ui.heading(&visualizer.box_title); - if index == 0 { - ui.horizontal(|ui| { - ui.label("inserisci la regex"); - ui.text_edit_singleline(&mut self.regex_text) - .on_hover_text("Enter a regular expression"); - }); - } - if ui - .button(format!("Generate {}", visualizer.box_title)) - .clicked() - { - match ReOperator::from_string(&self.regex_text) { - Ok(re) => { - visualizer.set_graph(Self::get_converter(index as i32)(re).into()); - self.error = None; - } - - Err(e) => { - self.error = Some(e.to_string()); - } - }; - } - - ui.collapsing( - format!("{} visualizer option", visualizer.box_title), - |ui| { - ui.add( - egui::Slider::new(&mut visualizer.padding_x, 10.0..=100.0) - .text("padding x"), - ); - ui.add( - egui::Slider::new(&mut visualizer.padding_y, 10.0..=100.0) - .text("padding y"), - ); - ui.add( - egui::Slider::new(&mut visualizer.size_node, 10.0..=100.0) - .text("node size"), - ); - }, - ); - } - if let Some(err) = &self.error { - ui.label(RichText::new(err).color(Color32::RED)); - } + SidePanel::left("Left").show(ctx, |ui| { + self.regular_gui.draw_left_panel(ui); + }); + SidePanel::right("Right").show(ctx, |ui| { + self.regular_gui.draw_right_panel(ui); + }); + TopBottomPanel::bottom("Visualizer").show(ctx, |ui| { + self.regular_gui.draw_bottom_panel(ui); + }); + CentralPanel::default().show(ctx, |ui| { + self.regular_gui.center_panel(ui); }); - for visualizer in self.to_visualize.iter_mut() { - visualizer.check_open(); - let syntaxTree = Window::new(format!("{}", visualizer.box_title)); - let syntaxTree = syntaxTree.open(&mut visualizer.is_win_open); - let syntaxTree = syntaxTree.scroll2([true, true]); - syntaxTree.show(ctx, |ui| { - Frame::canvas(ui.style()).show(ui, |ui| { - if let Some(tree) = &mut visualizer.graph { - let scren_size = tree.position(DisplayGraphParameter { - padding_x: visualizer.padding_x, - padding_y: visualizer.padding_y, - node_size: visualizer.size_node, - }); - let (mut response, painter) = - ui.allocate_painter(scren_size, egui::Sense::hover()); - - let to_screen = emath::RectTransform::from_to( - Rect::from_min_size(Pos2::ZERO, response.rect.size()), - response.rect, - ); - tree.drag_nodes(to_screen, ui, &mut response); - tree.draw(&painter, to_screen, &ui); - } - }) - }); - } } } diff --git a/src/automata/dfa.rs b/src/automata/dfa.rs index a84d982..c66ce3b 100644 --- a/src/automata/dfa.rs +++ b/src/automata/dfa.rs @@ -1,11 +1,12 @@ use std::collections::{BTreeMap, BTreeSet}; -use crate::automata::NFA; use crate::automata::regular_expression as RE; -use crate::utils::{Graph, IndEdge, IndNode}; +use crate::automata::NFA; use crate::utils::DisjointUnionFind; +use crate::utils::IntoGraph; +use crate::utils::{Graph, IndEdge, IndNode}; -type NfaStates = BTreeSet; +pub type NfaStates = BTreeSet; #[derive(Debug, Clone)] pub struct DFA { @@ -22,7 +23,7 @@ pub struct DFA { const INVALID_STATE: i32 = -1; impl DFA { - fn new() -> Self { + pub fn new() -> Self { Self { num_states: 0, start_state: 0, @@ -106,7 +107,7 @@ impl DFA { new_transitions[*idx].insert(*transition_ch, *head_to_idx.get(&dest_head).unwrap()); } } - + // create new end states, these should be unique let mut new_end_states = BTreeSet::new(); for end_state in self.end_states.iter() { @@ -117,7 +118,9 @@ impl DFA { Self { num_states, - start_state: *head_to_idx.get(&unequal_sets.find(self.start_state)).unwrap(), + start_state: *head_to_idx + .get(&unequal_sets.find(self.start_state)) + .unwrap(), end_states: new_end_states.into_iter().collect(), transitions: new_transitions, alphabet: self.alphabet.clone(), @@ -217,12 +220,14 @@ impl DFA { fn add_state(dfa: &mut DFA, states: T) -> usize { dfa.transitions.push(BTreeMap::new()); - if let Some(map) = &mut dfa.idx_to_data { map.insert(dfa.num_states, states); } else { dfa.idx_to_data = Some(BTreeMap::new()); - dfa.idx_to_data.as_mut().unwrap().insert(dfa.num_states, states); + dfa.idx_to_data + .as_mut() + .unwrap() + .insert(dfa.num_states, states); } dfa.num_states += 1; @@ -276,8 +281,8 @@ impl From<&RE::ReOperator> for DFA { } } -impl Into for DFA { - fn into(self) -> Graph { +impl IntoGraph for DFA { + fn into_graph(&self) -> Graph { let mut graph = Graph::new(); let finals_nodes = self diff --git a/src/automata/nfa.rs b/src/automata/nfa.rs index 4a0340b..4bd3c5f 100644 --- a/src/automata/nfa.rs +++ b/src/automata/nfa.rs @@ -1,11 +1,13 @@ -use log::info; +use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; use crate::automata::regular_expression as RE; -use crate::display::DisplayGraph; -use crate::utils::Graph; -#[derive(Debug)] +use crate::utils::{Graph, IntoGraph}; + +mod string_transform; + +#[derive(Debug, Serialize, Deserialize)] pub struct NFA { start_state: usize, num_states: usize, @@ -18,7 +20,7 @@ pub struct NFA { } impl NFA { - fn new() -> Self { + pub fn new() -> Self { Self { num_states: 0, start_state: 0, @@ -213,8 +215,8 @@ impl From<&RE::ReOperator> for NFA { } } -impl Into for NFA { - fn into(self) -> Graph { +impl IntoGraph for NFA { + fn into_graph(&self) -> Graph { let mut graph = Graph::new(); let finals_nodes = self @@ -264,6 +266,7 @@ mod test { Box::new(RE::ReOperator::Char('b')), ); let nfa = NFA::from(®ex); - println!("{:?}", nfa); + let out = serde_json::to_string(&nfa); + panic!("{:?}\n", out.unwrap()); } } diff --git a/src/automata/nfa/string_transform.rs b/src/automata/nfa/string_transform.rs new file mode 100644 index 0000000..3948cad --- /dev/null +++ b/src/automata/nfa/string_transform.rs @@ -0,0 +1,179 @@ +use std::collections::BTreeMap; + +use super::NFA; + +//TODO rifare tutto con strade +use nom::{ + bytes::streaming::tag, + character::complete::{char, one_of}, + character::complete::{digit1, multispace0}, + combinator::{map_res, opt}, + multi::separated_list0, + sequence::{delimited, preceded, tuple}, + IResult, +}; + +const START_STATE_LABEL: &str = "start_state"; +const NUM_STATE_LABEL: &str = "num_states"; +const END_STATES_LABEL: &str = "end_states"; +const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + +fn read_num(input: &str) -> IResult<&str, usize> { + map_res(digit1, |digit_str: &str| digit_str.parse::())(input) +} + +fn custom_label<'a>( + label: &str, +) -> impl Fn(&'a str) -> IResult<&'a str, (&'a str, &'a str, &'a str, &'a str, &'a str)> + '_ { + move |input| tuple((multispace0, tag(label), multispace0, tag(":"), multispace0))(input) +} + +fn read_start_stete(input: &str) -> IResult<&str, usize> { + preceded(custom_label(START_STATE_LABEL), read_num)(input) +} +fn read_num_states(input: &str) -> IResult<&str, usize> { + preceded(custom_label(NUM_STATE_LABEL), read_num)(input) +} + +fn read_char(input: &str) -> IResult<&str, char> { + preceded( + multispace0, + delimited(char('\''), one_of(ALPHABET), char('\'')), + )(input) +} + +fn read_num_array(input: &str) -> IResult<&str, Vec> { + delimited( + tag("["), + separated_list0(tag(","), delimited(multispace0, read_num, multispace0)), + tag("]"), + )(input) +} + +fn read_finish_states(input: &str) -> IResult<&str, Vec> { + preceded(custom_label(END_STATES_LABEL), read_num_array)(input) +} + +fn read_transiction(input: &str) -> IResult<&str, (usize, usize, char)> { + let (input, from) = delimited(multispace0, read_num, multispace0)(input)?; + let (input, _) = tag("--")(input)?; + let (input, ch) = opt(read_char)(input)?; + let (input, _) = preceded(multispace0, tag("-->"))(input)?; + let (input, to) = preceded(multispace0, read_num)(input)?; + + Ok((input, (from, to, ch.unwrap_or('ε')))) +} + +fn read_all_transictions(input: &str) -> IResult<&str, Vec<(usize, usize, char)>> { + separated_list0(multispace0, read_transiction)(input) +} + +impl TryFrom<&str> for NFA { + // TODO: maybe we can change it + type Error = String; + + fn try_from(value: &str) -> Result { + let out = tuple(( + read_start_stete, + read_finish_states, + read_num_states, + read_all_transictions, + ))(value); + + match out { + Ok((_, (start_state, end_states, num_states, all_transitions))) => { + let mut transitions: Vec>> = + (0..num_states).map(|_| BTreeMap::new()).collect(); + for (from, to, ch) in all_transitions.into_iter() { + if from >= num_states { + return Err(String::from("from out of num_state")); + } else if to >= num_states { + return Err(String::from("to out of num_state")); + } else { + transitions[from] + .entry(ch) + .and_modify(|x| x.push(to)) + .or_insert(vec![to]); + } + } + Ok(Self { + start_state, + num_states, + end_states, + transitions, + used_alphabet: ALPHABET.chars().collect(), + }) + } + Err(e) => Err(e.to_string()), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn string_to_nfa() { + //todo + if let Ok(_) = NFA::try_from(concat!( + "start_state: 0\n", + "finish_states: [ 2 ]\n", + " num_states: 5\n", + "0 --'b'--> 1\n", + "1 ----> 2\n", + "1 ----> 3\n", + "3 -- 'a' --> 4\n", + "4 ----> 3\n", + "4 ----> 2\n" + )) { + } else { + assert!(false); + } + } + + #[test] + fn test_edge() { + assert_eq!(read_transiction(" 1 ----> 3"), Ok(("", (1, 3, 'ε')))); + assert_eq!(read_transiction(" 2 -- 'a'--> 3"), Ok(("", (2, 3, 'a')))); + assert_eq!(read_transiction(" 2-- 'a' --> 3 "), Ok((" ", (2, 3, 'a')))); + assert_eq!(read_transiction("2 ----> 3"), Ok(("", (2, 3, 'ε')))); + } + + #[test] + fn test_start_state() { + assert_eq!(read_start_stete("start_state : 100\n"), Ok(("\n", 100))); + } + #[test] + fn test_number_list() { + assert_eq!( + read_num_array("[ 10, 20 , 30 ]"), + Ok(("", vec![10, 20, 30])) + ); + } + #[test] + fn test_number() { + assert_eq!(read_num("10"), Ok(("", 10))); + assert_eq!(read_num("1234"), Ok(("", 1234))); + } + + #[test] + fn test_label() { + assert_eq!( + custom_label("start_state")("start_state : 20"), + Ok(("20", ("", "start_state", " ", ":", " "))) + ); + assert_eq!( + custom_label("start_state")(" start_state: 30"), + Ok(("30", (" ", "start_state", "", ":", " "))) + ); + assert_eq!( + custom_label("start_state")("start_state : 10"), + Ok(("10", ("", "start_state", " ", ":", " "))) + ); + assert_eq!( + custom_label("start_state")("start_state :10"), + Ok(("10", ("", "start_state", " ", ":", ""))) + ); + } +} diff --git a/src/automata/regular_expression.rs b/src/automata/regular_expression.rs index 835690c..43ce44b 100644 --- a/src/automata/regular_expression.rs +++ b/src/automata/regular_expression.rs @@ -3,7 +3,7 @@ use std::iter::Peekable; use std::str::Chars; use crate::error::{InvalidCharacter, InvalidTokenError, UnvalidParentesis}; -use crate::utils::{Graph, IndNode}; +use crate::utils::{Graph, IndNode, IntoGraph}; /// Structure that represents a regular expression parse tree /// The current regular expression is defined by the following grammar: @@ -28,6 +28,14 @@ pub enum ReOperator { KleeneStar(Box), } +impl IntoGraph for ReOperator { + fn into_graph(&self) -> Graph { + let mut graph= Graph::new(); + self.build_recursive_graph(&mut graph); + graph + } +} + impl PartialEq for ReOperator { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -283,13 +291,6 @@ impl ReOperator { } } -impl Into for ReOperator { - fn into(self) -> Graph { - let mut graph= Graph::new(); - self.build_recursive_graph(&mut graph); - graph - } -} #[cfg(test)] mod test { diff --git a/src/display/display_graph.rs b/src/display/display_graph.rs index cddc07e..a363722 100644 --- a/src/display/display_graph.rs +++ b/src/display/display_graph.rs @@ -4,7 +4,7 @@ use egui::{ emath::RectTransform, epaint::CubicBezierShape, Color32, Painter, Pos2, Rect, Sense, Stroke, Vec2, }; -use std::collections::BTreeMap; +use std::{cmp::max, collections::BTreeMap}; const ARROW_TIP_LENGHT: f32 = 10.; const ARROW_WIDTH: f32 = 3.; @@ -39,6 +39,7 @@ pub struct DisplayGraphParameter { pub padding_x: f32, pub padding_y: f32, pub node_size: f32, + pub canvas_size: Vec2, // we can add more parameters here as we need them or like the color of node edge etc.. } @@ -48,6 +49,7 @@ impl DisplayGraphParameter { padding_x: -1., padding_y: -1., node_size: -1., + canvas_size: Vec2::new(0., 0.), } } } @@ -118,8 +120,8 @@ impl DisplayGraph { bfs_depth as f32 * (params.node_size + params.padding_y) + params.padding_y; Vec2 { - x: width_painting_area, - y: height_painting_area, + x: width_painting_area.max(params.canvas_size.x), + y: height_painting_area.max(params.canvas_size.y), } } @@ -189,8 +191,6 @@ impl DisplayGraph { painter.line_segment([tip, tip + tip_length * (rot.inverse() * dir)], stroke); } - - //TODO refactor for legibilities fn draw_edge_and_get_label_pos( &self, @@ -343,7 +343,6 @@ impl DisplayGraph { } } - pub fn draw(&self, painter: &egui::Painter, to_screen: RectTransform, ui: &egui::Ui) { self.draw_edge(painter, to_screen, ui); self.draw_nodes(painter, to_screen, ui); diff --git a/src/display/input_gui.rs b/src/display/input_gui.rs new file mode 100644 index 0000000..41abbd3 --- /dev/null +++ b/src/display/input_gui.rs @@ -0,0 +1,9 @@ +pub enum InputGui { + NFA(), + DFA(), + REGEX(), +} + +impl InputGui { + pub fn from() {} +} diff --git a/src/display/mod.rs b/src/display/mod.rs index b04c95f..1b85dbd 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -1,5 +1,7 @@ mod display_graph; -mod visualizer; +mod regular_gui; +// mod visualizer; pub use display_graph::*; -pub use visualizer::*; \ No newline at end of file +pub use regular_gui::*; +// pub use visualizer::*; diff --git a/src/display/regular_gui.rs b/src/display/regular_gui.rs new file mode 100644 index 0000000..1cd9687 --- /dev/null +++ b/src/display/regular_gui.rs @@ -0,0 +1,170 @@ +use egui::{emath, Frame, Pos2, Rect, Ui, Vec2}; + +use crate::automata::{NfaStates, ReOperator, DFA, NFA}; + +use crate::utils::{Graph, IntoGraph}; + +use super::{DisplayGraph, DisplayGraphParameter}; + +pub struct RegularGui { + dfa: Vec<(String, DFA)>, + nfa: Vec<(String, NFA)>, + regex: Vec<(String, ReOperator)>, + current_workspace: CurrentWorkspace, + object_created: i32, + display_graph: Option, + display_params: DisplayGraphParameter, + input_gui: InputGui, +} + +enum CurrentWorkspace { + NFA(NFA), + DFA(DFA), + REGEX(ReOperator), +} + +impl RegularGui { + pub fn new() -> Self { + return RegularGui { + dfa: Vec::new(), + nfa: Vec::new(), + regex: Vec::new(), + current_workspace: CurrentWorkspace::REGEX(ReOperator::Char('a')), + object_created: 0, + display_graph: None, + display_params: DisplayGraphParameter { + padding_x: 40., + padding_y: 40., + node_size: 30., + canvas_size: Vec2::new(0., 0.), + }, + }; + } + + pub fn draw_left_panel(&mut self, ui: &mut Ui) {} + + pub fn draw_bottom_panel(&mut self, ui: &mut Ui) { + ui.heading("Visualizer Parameter"); + + ui.add( + egui::Slider::new(&mut self.display_params.padding_x, 10.0..=200.0).text("padding x"), + ); + ui.add( + egui::Slider::new(&mut self.display_params.padding_y, 10.0..=200.0).text("padding y"), + ); + ui.add( + egui::Slider::new(&mut self.display_params.node_size, 10.0..=200.0).text("node size"), + ); + if ui.button("Reset").clicked() { + self.display_graph = None; + } + } + + pub fn center_panel(&mut self, canvas_ui: &mut Ui) { + if let None = self.display_graph { + let tree: Graph = match &self.current_workspace { + CurrentWorkspace::NFA(nfa) => nfa.into_graph(), + CurrentWorkspace::DFA(dfa) => dfa.into_graph(), + CurrentWorkspace::REGEX(re) => re.into_graph(), + }; + let tree: DisplayGraph = tree.into(); + self.display_graph = Some(tree); + } + if let Some(tree) = &mut self.display_graph { + self.display_params.canvas_size = canvas_ui.available_size(); + let screen_size = tree.position(self.display_params.clone()); + let (mut response, painter) = + canvas_ui.allocate_painter(screen_size, egui::Sense::hover()); + + let to_screen = emath::RectTransform::from_to( + Rect::from_min_size(Pos2::ZERO, response.rect.size()), + response.rect, + ); + tree.drag_nodes(to_screen, canvas_ui, &mut response); + tree.draw(&painter, to_screen, canvas_ui); + } + } + + pub fn draw_right_panel(&mut self, ui: &mut Ui) { + self.draw_re_operator_menu(ui); + self.draw_nfa_menu(ui); + self.draw_dfa_menu(ui); + } + + fn gen_snapshot_row(ui: &mut Ui, title: &mut String, load: F1, menu_fun: F2) + where + F1: Fn(), + F2: FnOnce(&mut Ui), + { + ui.horizontal(|ui| { + ui.add(egui::TextEdit::singleline(title).desired_width(120.0)); + ui.menu_button("Option", menu_fun); + if ui.button("load").clicked() { + load(); + } + }); + } + + fn draw_nfa_menu(&mut self, ui: &mut Ui) { + ui.heading("NFA"); + if ui.button("New").clicked() { + self.nfa.push(( + String::from("NFA ") + self.object_created.to_string().as_str(), + NFA::new(), + )); + self.object_created += 1; + } + self.nfa.iter_mut().for_each(|(s, _obj)| { + Self::gen_snapshot_row( + ui, + s, + || {}, + |ui| { + if ui.button("Open...").clicked() { + ui.close_menu(); + } + }, + ) + }); + } + fn draw_dfa_menu(&mut self, ui: &mut Ui) { + ui.heading("DFA"); + if ui.button("New").clicked() { + self.object_created += 1; + } + self.dfa.iter_mut().for_each(|(s, _obj)| { + Self::gen_snapshot_row( + ui, + s, + || {}, + |ui| { + if ui.button("Open...").clicked() { + ui.close_menu(); + } + }, + ) + }); + } + fn draw_re_operator_menu(&mut self, ui: &mut Ui) { + ui.heading("ReOperator"); + if ui.button("New").clicked() { + self.regex.push(( + String::from("Regex ") + self.object_created.to_string().as_str(), + ReOperator::Char('a'), + )); + self.object_created += 1; + } + self.regex.iter_mut().for_each(|(s, _obj)| { + Self::gen_snapshot_row( + ui, + s, + || {}, + |ui| { + if ui.button("Open...").clicked() { + ui.close_menu(); + } + }, + ) + }); + } +} diff --git a/src/display/visualizer.rs b/src/display/visualizer.rs deleted file mode 100644 index f74603c..0000000 --- a/src/display/visualizer.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::display::DisplayGraph; - - -/// this struct rappresent a visualizer of a graph -/// it contains the information to show the window and display the graph -pub struct Visualizer { - pub box_title: String, - pub graph: Option, - pub size_node: f32, - pub padding_y: f32, - pub padding_x: f32, - pub is_win_open: bool, - - // we can add a lot of paramters such color of nodes, etc.. -} - -impl Visualizer { - pub fn new(box_title: String) -> Self { - Self { - box_title: box_title, - graph: None, - is_win_open: false, - padding_x: 40., - padding_y: 40., - size_node: 30., - } - } - - pub fn check_open(&mut self) { - if let None = self.graph { - self.is_win_open = false; - } - if !self.is_win_open { - self.graph = None; - } - } - - pub fn set_graph(&mut self, graph: DisplayGraph) { - self.graph = Some(graph); - self.is_win_open = true; - } -} diff --git a/src/utils/graph.rs b/src/utils/graph.rs index 66a964a..c9b70a3 100644 --- a/src/utils/graph.rs +++ b/src/utils/graph.rs @@ -9,6 +9,9 @@ pub type IndNode = usize; /// Index of a edge, this type makes soure that is returned by the graph pub type IndEdge = usize; +pub trait IntoGraph { + fn into_graph(&self) -> Graph; +} pub struct Edge { // maybe label can be set to a generic type