From fd33c09c001675e373d7e198ea4273a2b7854ab3 Mon Sep 17 00:00:00 2001 From: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:50:10 -0500 Subject: [PATCH] feat: Add support for converting `regex_syntax::ast::Ast` to `NFA`; Deprecate the old navie AST. (#9) --- src/dfa/dfa.rs | 175 +-- src/error_handling/error.rs | 7 + src/nfa/nfa.rs | 1346 +++++++++++++++------- src/parser/ast_node/ast_node.rs | 135 --- src/parser/ast_node/ast_node_concat.rs | 35 - src/parser/ast_node/ast_node_group.rs | 29 - src/parser/ast_node/ast_node_literal.rs | 27 - src/parser/ast_node/ast_node_optional.rs | 29 - src/parser/ast_node/ast_node_plus.rs | 29 - src/parser/ast_node/ast_node_star.rs | 29 - src/parser/ast_node/ast_node_union.rs | 35 - src/parser/ast_node/mod.rs | 8 - src/parser/mod.rs | 4 +- 13 files changed, 985 insertions(+), 903 deletions(-) delete mode 100644 src/parser/ast_node/ast_node.rs delete mode 100644 src/parser/ast_node/ast_node_concat.rs delete mode 100644 src/parser/ast_node/ast_node_group.rs delete mode 100644 src/parser/ast_node/ast_node_literal.rs delete mode 100644 src/parser/ast_node/ast_node_optional.rs delete mode 100644 src/parser/ast_node/ast_node_plus.rs delete mode 100644 src/parser/ast_node/ast_node_star.rs delete mode 100644 src/parser/ast_node/ast_node_union.rs delete mode 100644 src/parser/ast_node/mod.rs diff --git a/src/dfa/dfa.rs b/src/dfa/dfa.rs index d3eb5e4..359dc51 100644 --- a/src/dfa/dfa.rs +++ b/src/dfa/dfa.rs @@ -2,7 +2,6 @@ use crate::nfa::nfa::NFA; use std::collections::{HashMap, HashSet}; use std::hash::Hash; use std::rc::Rc; -use std::sync::Arc; #[derive(Clone, Debug, Eq, Hash, PartialEq)] struct State(usize); @@ -354,9 +353,11 @@ impl DfaSimulator { #[cfg(test)] mod tests { use crate::dfa::dfa::{State, DFA}; + use crate::error_handling::Result; use crate::nfa::nfa::NFA; + use crate::parser::regex_parser::parser::RegexParser; use crate::{dfa, nfa}; - use std::collections::{HashMap, HashSet}; + use std::collections::HashMap; use std::rc::Rc; #[test] @@ -388,137 +389,44 @@ mod tests { assert_eq!(dfa.simulate("ba"), (None, false)); } - #[cfg(test)] - fn create_nfa1() -> NFA { - // input NFA - // 0 -> 1 epsilon - // 0 -> 2 epsilon - // 1 -> 3 a - // 2 -> 4 a - // 3 -> 5 b - // 4 -> 6 epsilon - // 5 -> 6 epsilon - // 0: start state - // 6: accept state + fn create_nfa1() -> Result { // Should only match "a" or "ab" + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast("(a)|(ab)")?; - let mut nfa = NFA::new(nfa::nfa::State(0), nfa::nfa::State(6)); + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; - for i in 1..=6 { - nfa.test_extern_add_state(nfa::nfa::State(i)); - } - - nfa.test_extern_add_epsilon_transition(nfa::nfa::State(0), nfa::nfa::State(1)); - nfa.test_extern_add_epsilon_transition(nfa::nfa::State(0), nfa::nfa::State(2)); - - nfa.test_extern_add_transition(nfa::nfa::Transition::new( - nfa::nfa::State(1), - nfa::nfa::State(3), - nfa::nfa::Transition::convert_char_to_symbol_onehot_encoding('a'), - -1, - )); - - nfa.test_extern_add_transition(nfa::nfa::Transition::new( - nfa::nfa::State(2), - nfa::nfa::State(4), - nfa::nfa::Transition::convert_char_to_symbol_onehot_encoding('a'), - -1, - )); - - nfa.test_extern_add_transition(nfa::nfa::Transition::new( - nfa::nfa::State(3), - nfa::nfa::State(5), - nfa::nfa::Transition::convert_char_to_symbol_onehot_encoding('b'), - -1, - )); - - nfa.test_extern_add_epsilon_transition(nfa::nfa::State(5), nfa::nfa::State(6)); - nfa.test_extern_add_epsilon_transition(nfa::nfa::State(4), nfa::nfa::State(6)); - - nfa + Ok(nfa) } - #[cfg(test)] - fn create_nfa2() -> NFA { - // input NFA - // 0 -> 1 epsilon - // 1 -> 1 c - // 1 -> 2 epsilon + fn create_nfa2() -> Result { // Should match "c*" + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast("c*")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; - let mut nfa = NFA::new(nfa::nfa::State(0), nfa::nfa::State(2)); - nfa.test_extern_add_state(nfa::nfa::State(9)); - nfa.test_extern_add_state(nfa::nfa::State(1)); - nfa.test_extern_add_state(nfa::nfa::State(2)); - - nfa.test_extern_add_epsilon_transition(nfa::nfa::State(0), nfa::nfa::State(1)); - nfa.test_extern_add_epsilon_transition(nfa::nfa::State(1), nfa::nfa::State(2)); - nfa.test_extern_add_transition(nfa::nfa::Transition::new( - nfa::nfa::State(1), - nfa::nfa::State(1), - nfa::nfa::Transition::convert_char_to_symbol_onehot_encoding('c'), - -1, - )); - - nfa + Ok(nfa) } - #[cfg(test)] - fn create_nfa3() -> NFA { - // input NFA - // 0 -> 1 epsilon - // 1 -> 2 c - // 2 -> 2 c - // 2 -> 3 a - // 3 -> 4 b - // 4 -> 5 epsilon + fn create_nfa3() -> crate::error_handling::Result { // Should match "c+ab" + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast("c+ab")?; - let mut nfa = NFA::new(nfa::nfa::State(0), nfa::nfa::State(5)); - for i in 1..=5 { - nfa.test_extern_add_state(nfa::nfa::State(i)); - } + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; - nfa.test_extern_add_epsilon_transition(nfa::nfa::State(0), nfa::nfa::State(1)); - nfa.test_extern_add_epsilon_transition(nfa::nfa::State(4), nfa::nfa::State(5)); - - nfa.test_extern_add_transition(nfa::nfa::Transition::new( - nfa::nfa::State(1), - nfa::nfa::State(2), - nfa::nfa::Transition::convert_char_to_symbol_onehot_encoding('c'), - -1, - )); - nfa.test_extern_add_transition(nfa::nfa::Transition::new( - nfa::nfa::State(2), - nfa::nfa::State(2), - nfa::nfa::Transition::convert_char_to_symbol_onehot_encoding('c'), - -1, - )); - nfa.test_extern_add_transition(nfa::nfa::Transition::new( - nfa::nfa::State(2), - nfa::nfa::State(3), - nfa::nfa::Transition::convert_char_to_symbol_onehot_encoding('a'), - -1, - )); - nfa.test_extern_add_transition(nfa::nfa::Transition::new( - nfa::nfa::State(3), - nfa::nfa::State(4), - nfa::nfa::Transition::convert_char_to_symbol_onehot_encoding('b'), - -1, - )); - - nfa + Ok(nfa) } #[test] - fn test_nfa1_from_nfa_to_dfa() { - let nfa = create_nfa1(); + fn test_nfa1_from_nfa_to_dfa() -> Result<()> { + let nfa = create_nfa1()?; let dfa = DFA::from_multiple_nfas(vec![nfa]); - // 0 1 2 : 0 - // 3 4 6 : 1 - // 5 6 : 2 - assert_eq!(dfa.start, dfa::dfa::State(0)); assert_eq!(dfa.accept.len(), 2); assert_eq!(dfa.accept.contains(&State(1)), true); @@ -550,11 +458,14 @@ mod tests { assert_eq!(dfa.simulate("aa"), (None, false)); assert_eq!(dfa.simulate("abb"), (None, false)); assert_eq!(dfa.simulate("aba"), (None, false)); + + Ok(()) } #[test] - fn test_nfa2_from_nfa_to_dfa() { - let nfa = create_nfa2(); + fn test_nfa2_from_nfa_to_dfa() -> crate::error_handling::Result<()> { + let nfa = create_nfa2()?; + println!("{:?}", nfa); let dfa = DFA::from_multiple_nfas(vec![nfa]); // Check correctness given some examples @@ -565,11 +476,13 @@ mod tests { assert_eq!(dfa.simulate("ccccab"), (None, false)); assert_eq!(dfa.simulate("cab"), (None, false)); assert_eq!(dfa.simulate(""), (Some(0usize), true)); + + Ok(()) } #[test] - fn test_nfa3_from_nfa_to_dfa() { - let nfa = create_nfa3(); + fn test_nfa3_from_nfa_to_dfa() -> Result<()> { + let nfa = create_nfa3()?; let dfa = DFA::from_multiple_nfas(vec![nfa]); // Check correctness given some examples @@ -581,13 +494,15 @@ mod tests { assert_eq!(dfa.simulate("cab"), (Some(0usize), true)); assert_eq!(dfa.simulate("ab"), (None, false)); assert_eq!(dfa.simulate(""), (None, false)); + + Ok(()) } #[test] - fn test_easy_from_multi_nfas_to_dfa() { - let nfa1 = create_nfa1(); - let nfa2 = create_nfa2(); - let nfa3 = create_nfa3(); + fn test_easy_from_multi_nfas_to_dfa() -> Result<()> { + let nfa1 = create_nfa1()?; + let nfa2 = create_nfa2()?; + let nfa3 = create_nfa3()?; let dfa = DFA::from_multiple_nfas(vec![nfa1, nfa2, nfa3]); @@ -609,13 +524,15 @@ mod tests { assert_eq!(dfa.simulate("cccccab"), (Some(2usize), true)); assert_eq!(dfa.simulate("cab"), (Some(2usize), true)); assert_eq!(dfa.simulate(""), (Some(1usize), true)); + + Ok(()) } #[test] - fn test_esay_from_multi_nfas_to_dfa_single_char_simulation() { - let nfa1 = create_nfa1(); - let nfa2 = create_nfa2(); - let nfa3 = create_nfa3(); + fn test_esay_from_multi_nfas_to_dfa_single_char_simulation() -> Result<()> { + let nfa1 = create_nfa1()?; + let nfa2 = create_nfa2()?; + let nfa3 = create_nfa3()?; let dfa = DFA::from_multiple_nfas(vec![nfa1, nfa2, nfa3]); @@ -660,5 +577,7 @@ mod tests { (Some(1usize), true) ); assert_eq!(dfa_simulator.simulate_single_char('b'), (None, false)); + + Ok(()) } } diff --git a/src/error_handling/error.rs b/src/error_handling/error.rs index 0ecc215..4d3bf94 100644 --- a/src/error_handling/error.rs +++ b/src/error_handling/error.rs @@ -3,6 +3,13 @@ use regex_syntax::ast; #[derive(Debug)] pub enum Error { RegexParsingError(ast::Error), + UnsupportedAstNodeType(&'static str), + NoneASCIICharacters, + NegationNotSupported(&'static str), + NonGreedyRepetitionNotSupported, + UnsupportedAstBracketedKind, + UnsupportedClassSetType, + UnsupportedGroupKindType, } pub type Result = std::result::Result; diff --git a/src/nfa/nfa.rs b/src/nfa/nfa.rs index 287bcd7..4c96f39 100644 --- a/src/nfa/nfa.rs +++ b/src/nfa/nfa.rs @@ -1,14 +1,27 @@ -use std::collections::{HashMap, HashSet}; +use crate::error_handling::Result; +use crate::parser::regex_parser::parser::RegexParser; +use std::collections::HashMap; use std::fmt::Debug; use std::hash::Hash; -use crate::parser::ast_node::ast_node::AstNode; -use crate::parser::ast_node::ast_node_concat::AstNodeConcat; -use crate::parser::ast_node::ast_node_literal::AstNodeLiteral; -use crate::parser::ast_node::ast_node_optional::AstNodeOptional; -use crate::parser::ast_node::ast_node_plus::AstNodePlus; -use crate::parser::ast_node::ast_node_star::AstNodeStar; -use crate::parser::ast_node::ast_node_union::AstNodeUnion; +use crate::error_handling::Error::{ + NegationNotSupported, NonGreedyRepetitionNotSupported, NoneASCIICharacters, + UnsupportedAstBracketedKind, UnsupportedAstNodeType, UnsupportedClassSetType, + UnsupportedGroupKindType, +}; +use regex_syntax::ast::{ + Alternation, Ast, ClassBracketed, ClassPerl, ClassPerlKind, ClassSet, ClassSetItem, + ClassSetRange, ClassSetUnion, Concat, Group, GroupKind, Literal, Repetition, RepetitionKind, + RepetitionRange, +}; + +const DIGIT_TRANSITION: u128 = 0x000000000000000003ff000000000000; +const SPACE_TRANSITION: u128 = 0x00000000000000000000000100003e00; +const WORD_TRANSITION: u128 = 0x07fffffe87fffffe03ff000000000000; + +const EPSILON_TRANSITION: u128 = 0x0; + +const DOT_TRANSITION: u128 = !EPSILON_TRANSITION; #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub(crate) struct State(pub usize); @@ -40,6 +53,29 @@ impl Transition { symbol_onehot_encoding } + pub fn convert_char_range_to_symbol_onehot_encoding(range: Option<(u8, u8)>) -> u128 { + let mut symbol_onehot_encoding: u128 = 0; + + match range { + Some((begin, end)) => { + for c in begin..=end { + symbol_onehot_encoding |= 1 << c; + } + } + None => {} + } + + symbol_onehot_encoding + } + + pub fn convert_char_vec_to_symbol_onehot_encoding(char_vec: Vec) -> u128 { + let mut symbol_onehot_encoding: u128 = 0; + for c in char_vec { + symbol_onehot_encoding |= 1 << c; + } + symbol_onehot_encoding + } + pub fn new(from: State, to: State, symbol_onehot_encoding: u128, tag: i16) -> Self { Transition { from, @@ -71,235 +107,253 @@ impl Transition { pub(crate) struct NFA { start: State, accept: State, - states: HashSet, + states: Vec, transitions: HashMap>, } +impl NFA { + pub const START_STATE: State = State(0); + pub const ACCEPT_STATE: State = State(1); +} + // NFA implementation for NFA construction from AST impl NFA { - fn from_ast(ast: &AstNode) -> Self { + pub fn new() -> Self { + let states_vec = vec![NFA::START_STATE.clone(), NFA::ACCEPT_STATE.clone()]; + NFA { + start: NFA::START_STATE, + accept: NFA::ACCEPT_STATE, + states: states_vec, + transitions: HashMap::new(), + } + } + + pub fn add_ast_to_nfa(&mut self, ast: &Ast, start: State, end: State) -> Result<()> { match ast { - AstNode::Literal(ast_node) => { - let start = State(0); - let accept = State(1); - let mut nfa = NFA::new(start.clone(), accept.clone()); - nfa.add_state(start.clone()); - nfa.add_state(accept.clone()); - nfa.add_transition(Transition { - from: start.clone(), - to: accept.clone(), - symbol_onehot_encoding: Transition::convert_char_to_symbol_onehot_encoding( - ast_node.get_value(), - ), - tag: -1, - }); - nfa + Ast::Literal(literal) => self.add_literal(&**literal, start, end)?, + Ast::Dot(dot) => self.add_dot(start, end)?, + Ast::ClassPerl(perl) => self.add_perl(&**perl, start, end)?, + Ast::Repetition(repetition) => self.add_repetition(&**repetition, start, end)?, + Ast::Concat(concat) => self.add_concat(&**concat, start, end)?, + Ast::ClassBracketed(bracketed) => self.add_bracketed(&**bracketed, start, end)?, + Ast::Alternation(alternation) => self.add_alternation(&**alternation, start, end)?, + Ast::Group(group) => self.add_group(&**group, start, end)?, + _ => { + return Err(UnsupportedAstNodeType("Ast Type not supported")); } - AstNode::Concat(ast_node) => { - // create NFA for left hand side and this will be the result NFA - let mut nfa = NFA::from_ast(&ast_node.get_op1()); - let offset = nfa.states.len(); - - // create NFA for right hand side and offset the states by the number of states on the left hand side NFA - let mut rhs_nfa = NFA::from_ast(&ast_node.get_op2()); - rhs_nfa.offset_states(offset); - - // add the states from the right hand side NFA to the result NFA - nfa.states = nfa.states.union(&rhs_nfa.states).cloned().collect(); - - // add the transitions from the right hand side NFA to the result NFA - nfa.add_epsilon_transition(nfa.accept.clone(), rhs_nfa.start.clone()); - // the accept state of the right hand side NFA is the accept state of the result NFA, - // the initial state of the result NFA is the initial state of the left hand side NFA, so no op - nfa.accept = rhs_nfa.accept.clone(); - for (from, transitions) in rhs_nfa.transitions { - nfa.transitions - .entry(from) - .or_insert(vec![]) - .extend(transitions); - } + } + Ok(()) + } - nfa - } - AstNode::Union(ast_node) => { - let start = State(0); - let accept = State(1); - let mut nfa = NFA::new(start.clone(), accept.clone()); - nfa.add_state(start.clone()); - nfa.add_state(accept.clone()); - let mut offset = 2; - - // Lambda function to handle NFA integration - let mut integrate_nfa = |sub_nfa: &mut NFA| { - sub_nfa.offset_states(offset); - nfa.add_epsilon_transition(start.clone(), sub_nfa.start.clone()); - nfa.add_epsilon_transition(sub_nfa.accept.clone(), accept.clone()); - nfa.states = nfa.states.union(&sub_nfa.states).cloned().collect(); - for (from, transitions) in sub_nfa.transitions.drain() { - nfa.transitions - .entry(from) - .or_insert(vec![]) - .extend(transitions); - } - offset += sub_nfa.states.len(); - }; + fn add_literal(&mut self, literal: &Literal, start: State, end: State) -> Result<()> { + let c = get_ascii_char(literal.c)?; + self.add_transition_from_range(start, end, Some((c, c))); + Ok(()) + } - let mut lhs_nfa = NFA::from_ast(&ast_node.get_op1()); - integrate_nfa(&mut lhs_nfa); + fn add_dot(&mut self, start: State, end: State) -> Result<()> { + self.add_transition(start, end, DOT_TRANSITION); + Ok(()) + } - let mut rhs_nfa = NFA::from_ast(&ast_node.get_op2()); - integrate_nfa(&mut rhs_nfa); + fn add_perl(&mut self, perl: &ClassPerl, start: State, end: State) -> Result<()> { + if perl.negated { + return Err(NegationNotSupported("Negation in perl not yet supported.")); + } + match perl.kind { + ClassPerlKind::Digit => self.add_transition(start, end, DIGIT_TRANSITION), + ClassPerlKind::Space => self.add_transition(start, end, SPACE_TRANSITION), + ClassPerlKind::Word => self.add_transition(start, end, WORD_TRANSITION), + } + Ok(()) + } - nfa - } - AstNode::Star(ast_node) => { - let mut sub_nfa = NFA::from_ast(ast_node.get_op1()); - sub_nfa.offset_states(1); - - let start = State(0); - let accept = State(sub_nfa.states.len() + 1); - - let mut nfa = NFA::new(start.clone(), accept.clone()); - nfa.add_state(start.clone()); - nfa.add_state(accept.clone()); - - // TODO: We may not need so many transitions - nfa.add_epsilon_transition(start.clone(), sub_nfa.start.clone()); - nfa.add_epsilon_transition(start.clone(), accept.clone()); - nfa.add_epsilon_transition(sub_nfa.accept.clone(), sub_nfa.start.clone()); - nfa.add_epsilon_transition(sub_nfa.accept.clone(), accept.clone()); - - nfa.states = nfa.states.union(&sub_nfa.states).cloned().collect(); - for (from, transitions) in sub_nfa.transitions { - nfa.transitions - .entry(from) - .or_insert(vec![]) - .extend(transitions); - } + fn add_concat(&mut self, concat: &Concat, start: State, end: State) -> Result<()> { + let mut curr_start = start.clone(); + for (idx, sub_ast) in concat.asts.iter().enumerate() { + let curr_end = if concat.asts.len() - 1 == idx { + end.clone() + } else { + self.new_state() + }; + self.add_ast_to_nfa(sub_ast, curr_start.clone(), curr_end.clone())?; + curr_start = curr_end.clone(); + } + Ok(()) + } - nfa - } - AstNode::Plus(ast_node) => { - let mut sub_nfa = NFA::from_ast(ast_node.get_op1()); - sub_nfa.offset_states(1); - - let start = State(0); - let accept = State(sub_nfa.states.len() + 1); - - let mut nfa = NFA::new(start.clone(), accept.clone()); - nfa.add_state(start.clone()); - nfa.add_state(accept.clone()); - - // Very similar to the Star case, but we don't allow the empty string, so - // we don't need the epsilon transition from start to accept - nfa.add_epsilon_transition(start.clone(), sub_nfa.start.clone()); - nfa.add_epsilon_transition(sub_nfa.accept.clone(), sub_nfa.start.clone()); - nfa.add_epsilon_transition(sub_nfa.accept.clone(), accept.clone()); - - nfa.states = nfa.states.union(&sub_nfa.states).cloned().collect(); - for (from, transitions) in sub_nfa.transitions { - nfa.transitions - .entry(from) - .or_insert(vec![]) - .extend(transitions); - } + fn add_group(&mut self, group: &Group, start: State, end: State) -> Result<()> { + match &group.kind { + GroupKind::CaptureIndex(_) => self.add_ast_to_nfa(&group.ast, start, end)?, + _ => return Err(UnsupportedGroupKindType), + } + Ok(()) + } + + fn add_alternation( + &mut self, + alternation: &Alternation, + start: State, + end: State, + ) -> Result<()> { + for sub_ast in alternation.asts.iter() { + let sub_ast_start = self.new_state(); + let sub_ast_end = self.new_state(); + self.add_epsilon_transition(start.clone(), sub_ast_start.clone()); + self.add_epsilon_transition(sub_ast_end.clone(), end.clone()); + self.add_ast_to_nfa(sub_ast, sub_ast_start, sub_ast_end)?; + } + Ok(()) + } + + fn add_repetition(&mut self, repetition: &Repetition, start: State, end: State) -> Result<()> { + if false == repetition.greedy { + return Err(NonGreedyRepetitionNotSupported); + } - nfa + let (min, optional_max) = Self::get_repetition_range(&repetition.op.kind); + let mut start_state = start.clone(); + + if 0 == min { + // 0 repetitions at minimum, meaning that there's an epsilon transition start -> end + self.add_epsilon_transition(start_state.clone(), end.clone()); + } else { + for _ in 1..min { + let intermediate_state = self.new_state(); + self.add_ast_to_nfa( + &repetition.ast, + start_state.clone(), + intermediate_state.clone(), + )?; + start_state = intermediate_state; } - AstNode::Optional(ast_node) => { - let mut sub_nfa = NFA::from_ast(ast_node.get_op1()); - sub_nfa.offset_states(1); - - let start = State(0); - let accept = State(sub_nfa.states.len() + 1); - - let mut nfa = NFA::new(start.clone(), accept.clone()); - nfa.add_state(start.clone()); - nfa.add_state(accept.clone()); - - // We can either have empty string (bypass) - nfa.add_epsilon_transition(start.clone(), accept.clone()); - // Or we can have the string from the sub NFA - nfa.add_epsilon_transition(start.clone(), sub_nfa.start.clone()); - nfa.add_epsilon_transition(sub_nfa.accept.clone(), accept.clone()); - - nfa.states.extend(sub_nfa.states); - for (from, transitions) in sub_nfa.transitions { - nfa.transitions - .entry(from) - .or_insert(vec![]) - .extend(transitions); - } + self.add_ast_to_nfa(&repetition.ast, start_state.clone(), end.clone())?; + } - nfa + match optional_max { + None => self.add_ast_to_nfa(&repetition.ast, end.clone(), end.clone())?, + Some(max) => { + if min == max { + // Already handled in the section above + return Ok(()); + } + start_state = end.clone(); + for _ in min..max { + let intermediate_state = self.new_state(); + self.add_ast_to_nfa( + &repetition.ast, + start_state.clone(), + intermediate_state.clone(), + )?; + self.add_epsilon_transition(intermediate_state.clone(), end.clone()); + start_state = intermediate_state; + } } - AstNode::Group(ast_node) => NFA::from_ast(ast_node.get_op1()), } + + Ok(()) } - pub fn new(start: State, accept: State) -> Self { - NFA { + fn add_bracketed( + &mut self, + bracketed: &ClassBracketed, + start: State, + end: State, + ) -> Result<()> { + if bracketed.negated { + return Err(NegationNotSupported( + "Negation in bracket not yet supported", + )); + } + match &bracketed.kind { + ClassSet::Item(item) => self.add_class_set_item(item, start, end)?, + _ => return Err(UnsupportedAstBracketedKind), + } + Ok(()) + } + + fn add_class_set_item(&mut self, item: &ClassSetItem, start: State, end: State) -> Result<()> { + match item { + ClassSetItem::Literal(literal) => self.add_literal(literal, start, end)?, + ClassSetItem::Bracketed(bracketed) => self.add_bracketed(bracketed, start, end)?, + ClassSetItem::Range(range) => self.add_range(range, start, end)?, + ClassSetItem::Perl(perl) => self.add_perl(perl, start, end)?, + ClassSetItem::Union(union) => self.add_union(union, start, end)?, + _ => return Err(UnsupportedClassSetType), + } + Ok(()) + } + + fn add_range(&mut self, range: &ClassSetRange, start: State, end: State) -> Result<()> { + self.add_transition_from_range( start, - accept, - states: HashSet::new(), - transitions: HashMap::new(), + end, + Some((get_ascii_char(range.start.c)?, get_ascii_char(range.end.c)?)), + ); + Ok(()) + } + + fn add_union(&mut self, union: &ClassSetUnion, start: State, end: State) -> Result<()> { + let mut curr_start = start.clone(); + for (idx, item) in union.items.iter().enumerate() { + let curr_end = if union.items.len() - 1 == idx { + end.clone() + } else { + self.new_state() + }; + self.add_class_set_item(item, curr_start.clone(), curr_end.clone())?; + curr_start = curr_end.clone(); + } + + Ok(()) + } + + fn get_repetition_range(kind: &RepetitionKind) -> (u32, Option) { + match kind { + RepetitionKind::ZeroOrOne => (0, Some(1)), + RepetitionKind::ZeroOrMore => (0, None), + RepetitionKind::OneOrMore => (1, None), + RepetitionKind::Range(range) => match range { + RepetitionRange::Exactly(num) => (*num, Some(*num)), + RepetitionRange::AtLeast(num) => (*num, None), + RepetitionRange::Bounded(begin, end) => (*begin, Some(*end)), + }, } } - fn add_state(&mut self, state: State) { - self.states.insert(state); + fn new_state(&mut self) -> State { + self.states.push(State(self.states.len())); + self.states.last().unwrap().clone() } - fn add_transition(&mut self, transition: Transition) { + fn add_transition_from_range(&mut self, from: State, to: State, range: Option<(u8, u8)>) { + let transition = Transition { + from: from.clone(), + to: to.clone(), + symbol_onehot_encoding: Transition::convert_char_range_to_symbol_onehot_encoding(range), + tag: -1, + }; self.transitions - .entry(transition.from.clone()) + .entry(from) .or_insert(vec![]) .push(transition); } - fn add_epsilon_transition(&mut self, from: State, to: State) { - self.add_transition(Transition { - from, - to, - symbol_onehot_encoding: 0, + fn add_transition(&mut self, from: State, to: State, onehot: u128) { + let transition = Transition { + from: from.clone(), + to: to.clone(), + symbol_onehot_encoding: onehot, tag: -1, - }); - } - - // Offset all states by a given amount - fn offset_states(&mut self, offset: usize) { - if offset == 0 { - return; - } - - // Update start and accept states - self.start = State(self.start.0 + offset); - self.accept = State(self.accept.0 + offset); - - // Update all states - let mut new_states = HashSet::new(); - for state in self.states.iter() { - new_states.insert(State(state.0 + offset)); - } - self.states = new_states; - - // Update transitions in place by adding the offset to each state's "from" and "to" values - let mut updated_transitions: HashMap> = HashMap::new(); - for (start, transitions) in self.transitions.iter() { - let updated_start = State(start.0 + offset); - let updated_transitions_list: Vec = transitions - .iter() - .map(|transition| Transition { - from: State(transition.from.0 + offset), - to: State(transition.to.0 + offset), - symbol_onehot_encoding: transition.symbol_onehot_encoding, - tag: transition.tag, - }) - .collect(); - updated_transitions.insert(updated_start, updated_transitions_list); - } + }; + self.transitions + .entry(from) + .or_insert(vec![]) + .push(transition); + } - self.transitions = updated_transitions; + fn add_epsilon_transition(&mut self, from: State, to: State) { + self.add_transition(from, to, EPSILON_TRANSITION); } } @@ -376,21 +430,12 @@ impl NFA { } } -// Test use only functions for DFA - -#[cfg(test)] -impl NFA { - pub fn test_extern_add_state(&mut self, state: State) { - self.add_state(state); - } - - pub fn test_extern_add_transition(&mut self, transition: Transition) { - self.add_transition(transition); - } - - pub fn test_extern_add_epsilon_transition(&mut self, from: State, to: State) { - self.add_epsilon_transition(from, to); +// Helper functions +fn get_ascii_char(c: char) -> Result { + if false == c.is_ascii() { + return Err(NoneASCIICharacters); } + Ok(c as u8) } #[cfg(test)] @@ -398,266 +443,735 @@ mod tests { use super::*; #[test] - fn offset_test() { - let mut nfa = NFA::new(State(0), State(1)); - nfa.add_state(State(0)); - nfa.add_state(State(1)); - nfa.add_transition(Transition { - from: State(0), - to: State(1), - symbol_onehot_encoding: Transition::convert_char_to_symbol_onehot_encoding('a'), - tag: -1, - }); - - nfa.offset_states(2); + fn test_single_char() -> Result<()> { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"&")?; + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + Transition::convert_char_to_symbol_onehot_encoding('&') + )); + Ok(()) + } - assert_eq!(nfa.start, State(2)); - assert_eq!(nfa.accept, State(3)); - assert_eq!(nfa.states.len(), 2); - assert_eq!(nfa.transitions.len(), 1); - assert_eq!(nfa.transitions.contains_key(&State(2)), true); + #[test] + fn test_dot() -> Result<()> { + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r".")?; + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + Transition::convert_char_range_to_symbol_onehot_encoding(Some((0, 127))) + )); + } - let transitions = nfa.transitions.get(&State(2)).unwrap(); - assert_eq!(transitions.len(), 1); - assert_eq!(transitions[0].from, State(2)); - assert_eq!(transitions[0].to, State(3)); + { + // Testing escaped `.` + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"\.")?; + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + Transition::convert_char_to_symbol_onehot_encoding('.') + )); + } + Ok(()) } #[test] - fn nfa_from_ast_literal() { - let ast = AstNode::Literal(AstNodeLiteral::new('a')); - let nfa = NFA::from_ast(&ast); - assert_eq!(nfa.start, State(0)); - assert_eq!(nfa.accept, State(1)); + fn test_perl() -> Result<()> { + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"\d")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + let char_vec: Vec = (b'0'..=b'9').collect(); + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + Transition::convert_char_vec_to_symbol_onehot_encoding(char_vec) + )); + } + + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"\s")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + let char_vec = vec![ + b' ', // Space + b'\t', // Horizontal Tab + b'\n', // Line Feed + b'\r', // Carriage Return + b'\x0B', // Vertical Tab + b'\x0C', // Form Feed + ]; + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + Transition::convert_char_vec_to_symbol_onehot_encoding(char_vec) + )); + } + + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"\w")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + let char_vec: Vec = (b'0'..=b'9') + .chain(b'A'..=b'Z') + .chain(b'a'..=b'z') + .chain(std::iter::once(b'_')) + .collect(); + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + Transition::convert_char_vec_to_symbol_onehot_encoding(char_vec) + )); + } - let states = nfa.states; - let transitions = nfa.transitions; + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"\D")?; - assert_eq!(states.len(), 2); - assert_eq!(transitions.len(), 1); - assert_eq!(transitions.contains_key(&State(0)), true); + let mut nfa = NFA::new(); + let nfa_result = nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE); + assert!(nfa_result.is_err()); + } - let transitions_from_start = transitions.get(&State(0)).unwrap(); - assert_eq!(transitions_from_start.len(), 1); - assert_eq!(transitions_from_start[0].from, State(0)); - assert_eq!(transitions_from_start[0].to, State(1)); + Ok(()) } #[test] - fn nfa_from_ast_concat() { - let ast = AstNode::Concat(AstNodeConcat::new( - AstNode::Literal(AstNodeLiteral::new('a')), - AstNode::Literal(AstNodeLiteral::new('b')), + fn test_concat_simple() -> Result<()> { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"<\d>")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(2), + Transition::convert_char_to_symbol_onehot_encoding('<') + )); + assert!(has_transition(&nfa, State(2), State(3), DIGIT_TRANSITION)); + assert!(has_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + Transition::convert_char_to_symbol_onehot_encoding('>') )); - let nfa = NFA::from_ast(&ast); - assert_eq!(nfa.states.len(), 4); - assert_eq!(nfa.transitions.len(), 3); - assert_eq!(nfa.start, State(0)); - assert_eq!(nfa.accept, State(3)); - let transitions = nfa.transitions; + Ok(()) + } - let transitions_from_start = transitions.get(&State(0)).unwrap(); - assert_eq!(transitions_from_start.len(), 1); - assert_eq!(transitions_from_start[0].from, State(0)); - assert_eq!(transitions_from_start[0].to, State(1)); + #[test] + fn test_alternation_simple() -> Result<()> { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"\d|a|bcd")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(2), + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(2), State(3), DIGIT_TRANSITION)); + assert!(has_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); - let transitions_from_1 = transitions.get(&State(1)).unwrap(); - assert_eq!(transitions_from_1.len(), 1); - assert_eq!(transitions_from_1[0].from, State(1)); - assert_eq!(transitions_from_1[0].to, State(2)); + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(4), + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + State(4), + State(5), + Transition::convert_char_to_symbol_onehot_encoding('a') + )); + assert!(has_transition( + &nfa, + State(5), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); - let transitions_from_2 = transitions.get(&State(2)).unwrap(); - assert_eq!(transitions_from_2.len(), 1); - assert_eq!(transitions_from_2[0].from, State(2)); - assert_eq!(transitions_from_2[0].to, State(3)); + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(6), + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + State(6), + State(8), + Transition::convert_char_to_symbol_onehot_encoding('b') + )); + assert!(has_transition( + &nfa, + State(8), + State(9), + Transition::convert_char_to_symbol_onehot_encoding('c') + )); + assert!(has_transition( + &nfa, + State(9), + State(7), + Transition::convert_char_to_symbol_onehot_encoding('d') + )); + assert!(has_transition( + &nfa, + State(7), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); - assert_eq!(transitions.contains_key(&State(3)), false); + Ok(()) } #[test] - fn nfa_from_ast_union() { - let ast = AstNode::Union(AstNodeUnion::new( - AstNode::Literal(AstNodeLiteral::new('a')), - AstNode::Literal(AstNodeLiteral::new('b')), - )); - let nfa = NFA::from_ast(&ast); - assert_eq!(nfa.states.len(), 6); // 6 states in total - assert_eq!(nfa.transitions.len(), 5); // 5 nodes have transitions + fn test_repetition() -> Result<()> { + let a_transition = Transition::convert_char_to_symbol_onehot_encoding('a'); + + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"a{0,3}")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(2), + a_transition + )); + assert!(has_transition( + &nfa, + State(2), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(2), State(3), a_transition)); + assert!(has_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(3), State(4), a_transition)); + assert!(has_transition( + &nfa, + State(4), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + + assert_eq!(nfa.states.len(), 5); + } - assert_eq!(nfa.start, State(0)); - assert_eq!(nfa.accept, State(1)); + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"a{0,1}")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(2), + a_transition + )); + assert!(has_transition( + &nfa, + State(2), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + + assert_eq!(nfa.states.len(), 3); + } - let transitions = nfa.transitions; + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"a*")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(1), + a_transition + )); + + assert_eq!(nfa.states.len(), 2); + } - let transitions_from_start = transitions.get(&State(0)).unwrap(); - assert_eq!(transitions_from_start.len(), 2); - assert_eq!(transitions_from_start[0].from, State(0)); - assert_eq!(transitions_from_start[0].to, State(2)); - assert_eq!(transitions_from_start[1].from, State(0)); - assert_eq!(transitions_from_start[1].to, State(4)); + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"a+")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_no_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + a_transition + )); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(1), + a_transition + )); + + assert_eq!(nfa.states.len(), 2); + } - let transitions_from_2 = transitions.get(&State(2)).unwrap(); - assert_eq!(transitions_from_2.len(), 1); - assert_eq!(transitions_from_2[0].from, State(2)); - assert_eq!(transitions_from_2[0].to, State(3)); + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"a{1,}")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_no_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + NFA::START_STATE, + NFA::ACCEPT_STATE, + a_transition + )); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(1), + a_transition + )); + + assert_eq!(nfa.states.len(), 2); + } - let transitions_from_4 = transitions.get(&State(4)).unwrap(); - assert_eq!(transitions_from_4.len(), 1); - assert_eq!(transitions_from_4[0].from, State(4)); - assert_eq!(transitions_from_4[0].to, State(5)); + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"a{3,}")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(2), + a_transition + )); + assert!(has_no_transition( + &nfa, + State(2), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(2), State(3), a_transition)); + assert!(has_no_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + a_transition + )); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(1), + a_transition + )); + + assert_eq!(nfa.states.len(), 4); + } - let transitions_from_3 = transitions.get(&State(3)).unwrap(); - assert_eq!(transitions_from_3.len(), 1); - assert_eq!(transitions_from_3[0].from, State(3)); - assert_eq!(transitions_from_3[0].to, State(1)); + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"a{3}")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(2), + a_transition + )); + assert!(has_no_transition( + &nfa, + State(2), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(2), State(3), a_transition)); + assert!(has_no_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + a_transition + )); + assert!(has_no_transition( + &nfa, + NFA::ACCEPT_STATE, + State(1), + a_transition + )); + + assert_eq!(nfa.states.len(), 4); + } - let transitions_from_5 = transitions.get(&State(5)).unwrap(); - assert_eq!(transitions_from_5.len(), 1); - assert_eq!(transitions_from_5[0].from, State(5)); - assert_eq!(transitions_from_5[0].to, State(1)); + { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"a{3,6}")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(2), + a_transition + )); + assert!(has_no_transition( + &nfa, + State(2), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(2), State(3), a_transition)); + assert!(has_no_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + a_transition + )); + + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(4), + a_transition + )); + assert!(has_transition( + &nfa, + State(4), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(4), State(5), a_transition)); + assert!(has_transition( + &nfa, + State(5), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(5), State(6), a_transition)); + assert!(has_transition( + &nfa, + State(6), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + + assert_eq!(nfa.states.len(), 7); + } - assert_eq!(transitions.contains_key(&State(1)), false); + Ok(()) } #[test] - fn nfa_from_ast_star() { - let ast = AstNode::Star(AstNodeStar::new(AstNode::Literal(AstNodeLiteral::new('a')))); - let nfa = NFA::from_ast(&ast); - assert_eq!(nfa.states.len(), 4); - assert_eq!(nfa.transitions.len(), 3); // except the accept state, all other states have transitions - - assert_eq!(nfa.start, State(0)); - assert_eq!(nfa.accept, State(3)); - - let transitions = nfa.transitions; + fn test_group() -> Result<()> { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"(\s|\d)+")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(2), + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(2), State(3), SPACE_TRANSITION)); + assert!(has_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(4), + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(4), State(5), DIGIT_TRANSITION)); + assert!(has_transition( + &nfa, + State(5), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); - let transitions_from_start = transitions.get(&State(0)).unwrap(); - assert_eq!(transitions_from_start.len(), 2); - assert_eq!(transitions_from_start[0].from, State(0)); - assert_eq!(transitions_from_start[0].to, State(1)); - assert_eq!(transitions_from_start[1].from, State(0)); - assert_eq!(transitions_from_start[1].to, State(3)); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(6), + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(6), State(7), SPACE_TRANSITION)); + assert!(has_transition( + &nfa, + State(7), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(8), + EPSILON_TRANSITION + )); + assert!(has_transition(&nfa, State(8), State(9), DIGIT_TRANSITION)); + assert!(has_transition( + &nfa, + State(9), + NFA::ACCEPT_STATE, + EPSILON_TRANSITION + )); - let transitions_from_1 = transitions.get(&State(1)).unwrap(); - assert_eq!(transitions_from_1.len(), 1); - assert_eq!(transitions_from_1[0].from, State(1)); - assert_eq!(transitions_from_1[0].to, State(2)); + assert_eq!(nfa.states.len(), 10); - let transitions_from_2 = transitions.get(&State(2)).unwrap(); - assert_eq!(transitions_from_2.len(), 2); - assert_eq!(transitions_from_2[0].from, State(2)); - assert_eq!(transitions_from_2[0].to, State(1)); - assert_eq!(transitions_from_2[1].from, State(2)); - assert_eq!(transitions_from_2[1].to, State(3)); + Ok(()) } #[test] - fn nfa_from_ast_plus() { - let ast = AstNode::Plus(AstNodePlus::new(AstNode::Literal(AstNodeLiteral::new('a')))); - let nfa = NFA::from_ast(&ast); - assert_eq!(nfa.states.len(), 4); - assert_eq!(nfa.transitions.len(), 3); // except the accept state, all other states have transitions + fn test_bracketed() -> Result<()> { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"[a-c3-9[A-X]]")?; + + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; + + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(2), + Transition::convert_char_range_to_symbol_onehot_encoding(Some((b'a', b'c'))) + )); + assert!(has_transition( + &nfa, + State(2), + State(3), + Transition::convert_char_range_to_symbol_onehot_encoding(Some((b'3', b'9'))) + )); + assert!(has_transition( + &nfa, + State(3), + NFA::ACCEPT_STATE, + Transition::convert_char_range_to_symbol_onehot_encoding(Some((b'A', b'X'))) + )); - assert_eq!(nfa.start, State(0)); - assert_eq!(nfa.accept, State(3)); + Ok(()) + } - let transitions = nfa.transitions; + #[test] + fn test_floating_point_regex() -> Result<()> { + let mut parser = RegexParser::new(); + let parsed_ast = parser.parse_into_ast(r"\-{0,1}[0-9]+\.[0-9]+")?; - let transitions_from_start = transitions.get(&State(0)).unwrap(); - assert_eq!(transitions_from_start.len(), 1); - assert_eq!(transitions_from_start[0].from, State(0)); - assert_eq!(transitions_from_start[0].to, State(1)); + let mut nfa = NFA::new(); + nfa.add_ast_to_nfa(&parsed_ast, NFA::START_STATE, NFA::ACCEPT_STATE)?; - let transitions_from_1 = transitions.get(&State(1)).unwrap(); - assert_eq!(transitions_from_1.len(), 1); - assert_eq!(transitions_from_1[0].from, State(1)); - assert_eq!(transitions_from_1[0].to, State(2)); + println!("{:?}", nfa); - let transitions_from_2 = transitions.get(&State(2)).unwrap(); - assert_eq!(transitions_from_2.len(), 2); - assert_eq!(transitions_from_2[0].from, State(2)); - assert_eq!(transitions_from_2[0].to, State(1)); - assert_eq!(transitions_from_2[1].from, State(2)); - assert_eq!(transitions_from_2[1].to, State(3)); - } + assert!(has_transition( + &nfa, + NFA::START_STATE, + State(2), + EPSILON_TRANSITION + )); + assert!(has_transition( + &nfa, + State(2), + State(3), + Transition::convert_char_to_symbol_onehot_encoding('-') + )); + assert!(has_transition(&nfa, State(3), State(2), EPSILON_TRANSITION)); - #[test] - fn nfa_from_ast_optional() { - let ast = AstNode::Optional(AstNodeOptional::new(AstNode::Literal(AstNodeLiteral::new( - 'a', - )))); - let nfa = NFA::from_ast(&ast); - assert_eq!(nfa.states.len(), 4); - assert_eq!(nfa.transitions.len(), 3); // except the accept state, all other states have transitions + assert!(has_transition(&nfa, State(2), State(4), DIGIT_TRANSITION)); + assert!(has_transition(&nfa, State(4), State(4), DIGIT_TRANSITION)); - assert_eq!(nfa.start, State(0)); - assert_eq!(nfa.accept, State(3)); + assert!(has_transition( + &nfa, + State(4), + State(5), + Transition::convert_char_to_symbol_onehot_encoding('.') + )); - let transitions = nfa.transitions; + assert!(has_transition( + &nfa, + State(5), + NFA::ACCEPT_STATE, + DIGIT_TRANSITION + )); + assert!(has_transition( + &nfa, + NFA::ACCEPT_STATE, + State(1), + DIGIT_TRANSITION + )); - let transitions_from_start = transitions.get(&State(0)).unwrap(); - assert_eq!(transitions_from_start.len(), 2); - assert_eq!(transitions_from_start[0].from, State(0)); - assert_eq!(transitions_from_start[0].to, State(3)); - assert_eq!(transitions_from_start[1].from, State(0)); - assert_eq!(transitions_from_start[1].to, State(1)); + assert_eq!(nfa.states.len(), 6); - let transitions_from_1 = transitions.get(&State(1)).unwrap(); - assert_eq!(transitions_from_1.len(), 1); - assert_eq!(transitions_from_1[0].from, State(1)); - assert_eq!(transitions_from_1[0].to, State(2)); + Ok(()) + } - let transitions_from_2 = transitions.get(&State(2)).unwrap(); - assert_eq!(transitions_from_2.len(), 1); - assert_eq!(transitions_from_2[0].from, State(2)); - assert_eq!(transitions_from_2[0].to, State(3)); + fn has_transition(nfa: &NFA, from: State, to: State, onehot_trans: u128) -> bool { + if from.0 >= nfa.states.len() || to.0 >= nfa.states.len() { + return false; + } + if false == nfa.transitions.contains_key(&from) { + return false; + } + for trans in nfa.transitions.get(&from).unwrap() { + if to != trans.to { + continue; + } + if trans.symbol_onehot_encoding == onehot_trans { + return true; + } + } + false } - #[test] - fn nfa_simple_debug_print() { - let ast = AstNode::Concat(AstNodeConcat::new( - AstNode::Optional(AstNodeOptional::new(AstNode::Literal(AstNodeLiteral::new( - 'a', - )))), - AstNode::Literal(AstNodeLiteral::new('b')), - )); - let nfa = NFA::from_ast(&ast); - println!("{:?}", nfa); + fn has_no_transition(nfa: &NFA, from: State, to: State, onehot_trans: u128) -> bool { + false == has_transition(nfa, from, to, onehot_trans) } #[test] fn nfa_epsilon_closure() { - let mut nfa = NFA::new(State(0), State(3)); - for i in 0..=10 { - nfa.add_state(State(i)); - } - nfa.add_epsilon_transition(State(0), State(1)); - nfa.add_epsilon_transition(State(1), State(2)); - nfa.add_epsilon_transition(State(0), State(2)); - nfa.add_transition(Transition { - from: State(2), - to: State(3), - symbol_onehot_encoding: Transition::convert_char_to_symbol_onehot_encoding('a'), - tag: -1, - }); + let mut nfa = NFA::new(); + for _ in 0..=10 { + _ = nfa.new_state(); + } + nfa.add_epsilon_transition(NFA::START_STATE, NFA::ACCEPT_STATE); + nfa.add_epsilon_transition(NFA::ACCEPT_STATE, State(2)); + nfa.add_epsilon_transition(NFA::START_STATE, State(2)); + nfa.add_transition( + State(2), + State(3), + Transition::convert_char_to_symbol_onehot_encoding('a'), + ); nfa.add_epsilon_transition(State(3), State(5)); nfa.add_epsilon_transition(State(3), State(4)); - nfa.add_epsilon_transition(State(4), State(5)); - nfa.add_epsilon_transition(State(5), State(3)); + nfa.add_epsilon_transition(State(4), State(6)); + nfa.add_epsilon_transition(State(6), State(3)); - let closure = nfa.epsilon_closure(&vec![State(0)]); + let closure = nfa.epsilon_closure(&vec![NFA::START_STATE]); assert_eq!(closure.len(), 3); - assert_eq!(closure.contains(&State(0)), true); - assert_eq!(closure.contains(&State(1)), true); + assert_eq!(closure.contains(&NFA::START_STATE), true); + assert_eq!(closure.contains(&NFA::ACCEPT_STATE), true); assert_eq!(closure.contains(&State(2)), true); - assert_eq!(closure.contains(&State(3)), false); - assert_eq!(closure.contains(&State(10)), false); let closure = nfa.epsilon_closure(&vec![State(3)]); - assert_eq!(closure.len(), 3); + assert_eq!(closure.len(), 4); assert_eq!(closure.contains(&State(3)), true); assert_eq!(closure.contains(&State(4)), true); assert_eq!(closure.contains(&State(5)), true); + assert_eq!(closure.contains(&State(6)), true); } } diff --git a/src/parser/ast_node/ast_node.rs b/src/parser/ast_node/ast_node.rs deleted file mode 100644 index 644a198..0000000 --- a/src/parser/ast_node/ast_node.rs +++ /dev/null @@ -1,135 +0,0 @@ -// #[derive(Debug)] -use super::ast_node_concat::AstNodeConcat; -use super::ast_node_group::AstNodeGroup; -use super::ast_node_literal::AstNodeLiteral; -use super::ast_node_optional::AstNodeOptional; -use super::ast_node_plus::AstNodePlus; -use super::ast_node_star::AstNodeStar; -use super::ast_node_union::AstNodeUnion; - -pub(crate) enum AstNode { - Literal(AstNodeLiteral), - Concat(AstNodeConcat), - Union(AstNodeUnion), - Star(AstNodeStar), - Plus(AstNodePlus), - Optional(AstNodeOptional), - Group(AstNodeGroup), -} - -impl PartialEq for AstNode { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (AstNode::Literal(lhs), AstNode::Literal(rhs)) => lhs == rhs, - (AstNode::Concat(lhs), AstNode::Concat(rhs)) => lhs == rhs, - (AstNode::Union(lhs), AstNode::Union(rhs)) => lhs == rhs, - (AstNode::Star(lhs), AstNode::Star(rhs)) => lhs == rhs, - (AstNode::Plus(lhs), AstNode::Plus(rhs)) => lhs == rhs, - (AstNode::Optional(lhs), AstNode::Optional(rhs)) => lhs == rhs, - (AstNode::Group(lhs), AstNode::Group(rhs)) => lhs == rhs, - _ => false, - } - } -} - -impl std::fmt::Debug for AstNode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AstNode::Literal(ast_node) => write!(f, "{:?}", ast_node), - AstNode::Concat(ast_node) => write!(f, "{:?}", ast_node), - AstNode::Union(ast_node) => write!(f, "{:?}", ast_node), - AstNode::Star(ast_node) => write!(f, "{:?}", ast_node), - AstNode::Plus(ast_node) => write!(f, "{:?}", ast_node), - AstNode::Optional(ast_node) => write!(f, "{:?}", ast_node), - AstNode::Group(ast_node) => write!(f, "{:?}", ast_node), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn ast_node_literal_equality() { - let node1 = AstNode::Literal(AstNodeLiteral::new('a')); - let node2 = AstNode::Literal(AstNodeLiteral::new('a')); - assert_eq!(node1, node2); - } - - #[test] - fn ast_node_concat_equality() { - let node1 = AstNode::Concat(AstNodeConcat::new( - AstNode::Literal(AstNodeLiteral::new('a')), - AstNode::Literal(AstNodeLiteral::new('b')), - )); - let node2 = AstNode::Concat(AstNodeConcat::new( - AstNode::Literal(AstNodeLiteral::new('a')), - AstNode::Literal(AstNodeLiteral::new('b')), - )); - assert_eq!(node1, node2); - } - - #[test] - fn ast_node_union_equality() { - let node1 = AstNode::Union(AstNodeUnion::new( - AstNode::Literal(AstNodeLiteral::new('a')), - AstNode::Literal(AstNodeLiteral::new('b')), - )); - let node2 = AstNode::Union(AstNodeUnion::new( - AstNode::Literal(AstNodeLiteral::new('a')), - AstNode::Literal(AstNodeLiteral::new('b')), - )); - assert_eq!(node1, node2); - } - - #[test] - fn ast_node_star_equality() { - let node1 = AstNode::Star(AstNodeStar::new(AstNode::Literal(AstNodeLiteral::new('a')))); - let node2 = AstNode::Star(AstNodeStar::new(AstNode::Literal(AstNodeLiteral::new('a')))); - assert_eq!(node1, node2); - } - - #[test] - fn ast_node_plus_equality() { - let node1 = AstNode::Plus(AstNodePlus::new(AstNode::Literal(AstNodeLiteral::new('a')))); - let node2 = AstNode::Plus(AstNodePlus::new(AstNode::Literal(AstNodeLiteral::new('a')))); - assert_eq!(node1, node2); - } - - #[test] - fn ast_node_optional_equality() { - let node1 = AstNode::Optional(AstNodeOptional::new(AstNode::Literal(AstNodeLiteral::new( - 'a', - )))); - let node2 = AstNode::Optional(AstNodeOptional::new(AstNode::Literal(AstNodeLiteral::new( - 'a', - )))); - assert_eq!(node1, node2); - } - - #[test] - fn ast_node_group_equality() { - let node1 = AstNode::Group(AstNodeGroup::new(AstNode::Literal(AstNodeLiteral::new( - 'a', - )))); - let node2 = AstNode::Group(AstNodeGroup::new(AstNode::Literal(AstNodeLiteral::new( - 'a', - )))); - assert_eq!(node1, node2); - } - - #[test] - fn ast_node_basic_debug() { - let node = AstNode::Concat(AstNodeConcat::new( - AstNode::Star(AstNodeStar::new(AstNode::Union(AstNodeUnion::new( - AstNode::Literal(AstNodeLiteral::new('a')), - AstNode::Literal(AstNodeLiteral::new('b')), - )))), - AstNode::Optional(AstNodeOptional::new(AstNode::Group(AstNodeGroup::new( - AstNode::Plus(AstNodePlus::new(AstNode::Literal(AstNodeLiteral::new('c')))), - )))), - )); - assert_eq!(format!("{:?}", node), "Concat( Star( Union( Literal('a') Literal('b') ) ) Optional( Group( Plus ( Literal('c') ) ) ) )"); - } -} diff --git a/src/parser/ast_node/ast_node_concat.rs b/src/parser/ast_node/ast_node_concat.rs deleted file mode 100644 index bba0467..0000000 --- a/src/parser/ast_node/ast_node_concat.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::parser::ast_node::ast_node::AstNode; - -pub(crate) struct AstNodeConcat { - m_op1: Box, - m_op2: Box, -} - -impl AstNodeConcat { - pub(crate) fn new(p0: AstNode, p1: AstNode) -> AstNodeConcat { - AstNodeConcat { - m_op1: Box::new(p0), - m_op2: Box::new(p1), - } - } - - pub(crate) fn get_op1(&self) -> &AstNode { - &self.m_op1 - } - - pub(crate) fn get_op2(&self) -> &AstNode { - &self.m_op2 - } -} - -impl PartialEq for AstNodeConcat { - fn eq(&self, other: &Self) -> bool { - self.m_op1 == other.m_op1 && self.m_op2 == other.m_op2 - } -} - -impl std::fmt::Debug for AstNodeConcat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Concat( {:?} {:?} )", self.m_op1, self.m_op2) - } -} diff --git a/src/parser/ast_node/ast_node_group.rs b/src/parser/ast_node/ast_node_group.rs deleted file mode 100644 index 6653004..0000000 --- a/src/parser/ast_node/ast_node_group.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::parser::ast_node::ast_node::AstNode; - -pub(crate) struct AstNodeGroup { - m_op1: Box, -} - -impl AstNodeGroup { - pub(crate) fn new(p0: AstNode) -> AstNodeGroup { - AstNodeGroup { - m_op1: Box::new(p0), - } - } - - pub(crate) fn get_op1(&self) -> &AstNode { - &self.m_op1 - } -} - -impl PartialEq for AstNodeGroup { - fn eq(&self, other: &Self) -> bool { - self.m_op1 == other.m_op1 - } -} - -impl std::fmt::Debug for AstNodeGroup { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Group( {:?} )", self.m_op1) - } -} diff --git a/src/parser/ast_node/ast_node_literal.rs b/src/parser/ast_node/ast_node_literal.rs deleted file mode 100644 index eb0f563..0000000 --- a/src/parser/ast_node/ast_node_literal.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::fmt; - -pub(crate) struct AstNodeLiteral { - m_value: char, -} - -impl AstNodeLiteral { - pub(crate) fn new(p0: char) -> AstNodeLiteral { - AstNodeLiteral { m_value: p0 } - } - - pub(crate) fn get_value(&self) -> char { - self.m_value - } -} - -impl PartialEq for AstNodeLiteral { - fn eq(&self, other: &Self) -> bool { - self.m_value == other.m_value - } -} - -impl fmt::Debug for AstNodeLiteral { - fn fmt(&self, p: &mut fmt::Formatter) -> fmt::Result { - write!(p, "Literal({:?})", self.m_value) - } -} diff --git a/src/parser/ast_node/ast_node_optional.rs b/src/parser/ast_node/ast_node_optional.rs deleted file mode 100644 index 877ec1e..0000000 --- a/src/parser/ast_node/ast_node_optional.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::parser::ast_node::ast_node::AstNode; - -pub(crate) struct AstNodeOptional { - m_op1: Box, -} - -impl AstNodeOptional { - pub(crate) fn new(p0: AstNode) -> AstNodeOptional { - AstNodeOptional { - m_op1: Box::new(p0), - } - } - - pub(crate) fn get_op1(&self) -> &AstNode { - &self.m_op1 - } -} - -impl PartialEq for AstNodeOptional { - fn eq(&self, other: &Self) -> bool { - self.m_op1 == other.m_op1 - } -} - -impl std::fmt::Debug for AstNodeOptional { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Optional( {:?} )", self.m_op1) - } -} diff --git a/src/parser/ast_node/ast_node_plus.rs b/src/parser/ast_node/ast_node_plus.rs deleted file mode 100644 index 7f8b0c5..0000000 --- a/src/parser/ast_node/ast_node_plus.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::parser::ast_node::ast_node::AstNode; - -pub(crate) struct AstNodePlus { - m_op1: Box, -} - -impl AstNodePlus { - pub(crate) fn new(p0: AstNode) -> AstNodePlus { - AstNodePlus { - m_op1: Box::new(p0), - } - } - - pub(crate) fn get_op1(&self) -> &AstNode { - &self.m_op1 - } -} - -impl PartialEq for AstNodePlus { - fn eq(&self, other: &Self) -> bool { - self.m_op1 == other.m_op1 - } -} - -impl std::fmt::Debug for AstNodePlus { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Plus ( {:?} )", self.m_op1) - } -} diff --git a/src/parser/ast_node/ast_node_star.rs b/src/parser/ast_node/ast_node_star.rs deleted file mode 100644 index 37a9382..0000000 --- a/src/parser/ast_node/ast_node_star.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::parser::ast_node::ast_node::AstNode; - -pub(crate) struct AstNodeStar { - m_op1: Box, -} - -impl AstNodeStar { - pub(crate) fn new(p0: AstNode) -> AstNodeStar { - AstNodeStar { - m_op1: Box::new(p0), - } - } - - pub(crate) fn get_op1(&self) -> &AstNode { - &self.m_op1 - } -} - -impl PartialEq for AstNodeStar { - fn eq(&self, other: &Self) -> bool { - self.m_op1 == other.m_op1 - } -} - -impl std::fmt::Debug for AstNodeStar { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Star( {:?} )", self.m_op1) - } -} diff --git a/src/parser/ast_node/ast_node_union.rs b/src/parser/ast_node/ast_node_union.rs deleted file mode 100644 index 5f7a4f6..0000000 --- a/src/parser/ast_node/ast_node_union.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::parser::ast_node::ast_node::AstNode; - -pub(crate) struct AstNodeUnion { - m_op1: Box, - m_op2: Box, -} - -impl AstNodeUnion { - pub(crate) fn new(p0: AstNode, p1: AstNode) -> AstNodeUnion { - AstNodeUnion { - m_op1: Box::new(p0), - m_op2: Box::new(p1), - } - } - - pub(crate) fn get_op1(&self) -> &AstNode { - &self.m_op1 - } - - pub(crate) fn get_op2(&self) -> &AstNode { - &self.m_op2 - } -} - -impl PartialEq for AstNodeUnion { - fn eq(&self, other: &Self) -> bool { - self.m_op1 == other.m_op1 && self.m_op2 == other.m_op2 - } -} - -impl std::fmt::Debug for AstNodeUnion { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Union( {:?} {:?} )", self.m_op1, self.m_op2) - } -} diff --git a/src/parser/ast_node/mod.rs b/src/parser/ast_node/mod.rs deleted file mode 100644 index e203118..0000000 --- a/src/parser/ast_node/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod ast_node; -pub mod ast_node_concat; -pub mod ast_node_group; -pub mod ast_node_literal; -pub mod ast_node_optional; -pub mod ast_node_plus; -pub mod ast_node_star; -pub mod ast_node_union; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 43fc47f..146fa7a 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,3 +1 @@ -// Keep ASTNode private and they will be used by parser in the future -pub(crate) mod ast_node; -mod regex_parser; +pub(crate) mod regex_parser;