From 3e4369c6cedd22067f26b685dd9f65bad78acea6 Mon Sep 17 00:00:00 2001 From: Rajdeep Aher Date: Tue, 28 Jan 2025 10:09:15 +0530 Subject: [PATCH 1/3] added regex_engine source files fix name formatting fixed clang formatting formatting issue fixed clang formatting in regex.cc --- changes.txt | Bin 0 -> 218406 bytes regex_engine/ast/ast.cc | 94 +++ regex_engine/ast/ast.hh | 132 +++ regex_engine/lexer/lexer.cc | 90 +++ regex_engine/lexer/lexer.hh | 21 + regex_engine/parser/parser.cc | 526 ++++++++++++ regex_engine/parser/parser.hh | 47 ++ regex_engine/regex/match.hh | 26 + regex_engine/regex/regex.cc | 1439 +++++++++++++++++++++++++++++++++ regex_engine/regex/regex.hh | 35 + regex_engine/tokens/tokens.cc | 63 ++ regex_engine/tokens/tokens.hh | 173 ++++ 12 files changed, 2646 insertions(+) create mode 100644 changes.txt create mode 100644 regex_engine/ast/ast.cc create mode 100644 regex_engine/ast/ast.hh create mode 100644 regex_engine/lexer/lexer.cc create mode 100644 regex_engine/lexer/lexer.hh create mode 100644 regex_engine/parser/parser.cc create mode 100644 regex_engine/parser/parser.hh create mode 100644 regex_engine/regex/match.hh create mode 100644 regex_engine/regex/regex.cc create mode 100644 regex_engine/regex/regex.hh create mode 100644 regex_engine/tokens/tokens.cc create mode 100644 regex_engine/tokens/tokens.hh diff --git a/changes.txt b/changes.txt new file mode 100644 index 0000000000000000000000000000000000000000..57d00205d3d5540bdf6d2c40ad0c7d7855b4fa5e GIT binary patch literal 218406 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 literal 0 HcmV?d00001 diff --git a/regex_engine/ast/ast.cc b/regex_engine/ast/ast.cc new file mode 100644 index 0000000..c8ccddd --- /dev/null +++ b/regex_engine/ast/ast.cc @@ -0,0 +1,94 @@ +#include "ast.hh" +// RE Class Implementation +RE::RE(shared_ptr child, bool capturing, string group_name) + : __capturing__(capturing), group_name(group_name), group_id(-1), + child(child) { + children.push_back(child); +} + +bool RE::is_capturing() const { return __capturing__; } + +// LeafNode Class Implementation +LeafNode::LeafNode() : ASTNode() {} + +bool LeafNode::is_match(const string &ch, int str_i, int str_len) const { + return false; +} + +// Element Class Implementation +Element::Element(const string &match_ch) + : LeafNode(), match(match_ch), min(1), max(1) {} + +bool Element::is_match(const string &ch, int str_i, int str_len) const { + return match == ch; +} + +// WildcardElement Class Implementation +WildcardElement::WildcardElement() : Element("anything") {} + +bool WildcardElement::is_match(const string &ch, int str_i, int str_len) const { + return ch != "\n"; +} + +// SpaceElement Class Implementation +SpaceElement::SpaceElement() : Element("") { match = ""; } + +bool SpaceElement::is_match(const string &ch, int str_i, int str_len) const { + return ch.length() == 1 && isspace(ch[0]); +} + +// RangeElement Class Implementation +RangeElement::RangeElement(const string &match_str, bool is_positive_logic) + : LeafNode(), match(match_str), min(1), max(1), + is_positive_logic(is_positive_logic) {} + +bool RangeElement::is_match(const string &ch, int str_i, int str_len) const { + if (ch.empty()) + return false; + bool ch_in_match = (match.find(ch[0]) != string::npos); + return is_positive_logic ? ch_in_match : !ch_in_match; +} + +// StartElement Class Implementation +StartElement::StartElement() : LeafNode() { + match = ""; + min = 1; + max = 1; +} + +bool StartElement::is_match(const string &ch, int str_i, int str_len) const { + return str_i == 0; +} + +// EndElement Class Implementation +EndElement::EndElement() : LeafNode() { + match = ""; + min = 1; + max = 1; +} + +bool EndElement::is_match(const string &ch, int str_i, int str_len) const { + return str_i == str_len; +} + +// OrNode Class Implementation +OrNode::OrNode(shared_ptr left, shared_ptr right) + : ASTNode() { + this->left = left; + this->right = right; + this->children.push_back(left); + this->children.push_back(right); + this->min = 1; + this->max = 1; +} + +// GroupNode Class Implementation +GroupNode::GroupNode(deque> children, bool capturing, + string group_name, int group_id) + : __capturing__(capturing), group_id(group_id), min(1), max(1) { + this->group_name = + (group_name.empty()) ? "Group" + to_string(this->group_id) : group_name; + this->children = children; +} + +bool GroupNode::is_capturing() const { return __capturing__; } \ No newline at end of file diff --git a/regex_engine/ast/ast.hh b/regex_engine/ast/ast.hh new file mode 100644 index 0000000..8cd9204 --- /dev/null +++ b/regex_engine/ast/ast.hh @@ -0,0 +1,132 @@ +#ifndef RE_AST_HH +#define RE_AST_HH + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// Base class for all AST nodes +class ASTNode { +public: + ASTNode() = default; + virtual ~ASTNode(){}; +}; + +// Regular Expression Node +class RE : public ASTNode { +public: + bool __capturing__; + string group_name; + int group_id; + shared_ptr child; + deque> children; + + RE(shared_ptr child, bool capturing = false, + string group_name = "RegEx"); + bool is_capturing() const; +}; + +// Leaf Node Base Class +class LeafNode : public ASTNode { +public: + LeafNode(); + virtual bool is_match(const string &ch = "", int str_i = -1, + int str_len = -1) const; +}; + +// Element Node +class Element : public LeafNode { +public: + Element(const string &match_ch = ""); + bool is_match(const string &ch = "", int str_i = 0, + int str_len = 0) const override; + string match; + variant min; + variant max; +}; + +// Wildcard Element Node +class WildcardElement : public Element { +public: + WildcardElement(); + bool is_match(const string &ch = "", int str_i = 0, + int str_len = 0) const override; +}; + +// Space Element Node +class SpaceElement : public Element { +public: + SpaceElement(); + bool is_match(const string &ch = "", int str_i = 0, + int str_len = 0) const override; +}; + +// Range Element Node +class RangeElement : public LeafNode { +public: + RangeElement(const string &match_str, bool is_positive_logic = true); + bool is_match(const string &ch = "", int str_i = 0, + int str_len = 0) const override; + string match; + variant min; + variant max; + bool is_positive_logic; +}; + +// Start Element Node +class StartElement : public LeafNode { +public: + StartElement(); + bool is_match(const string &ch = "", int str_i = 0, + int str_len = 0) const override; + string match; + variant min; + variant max; +}; + +// End Element Node +class EndElement : public LeafNode { +public: + EndElement(); + bool is_match(const string &ch = "", int str_i = 0, + int str_len = 0) const override; + string match; + variant min; + variant max; +}; + +// OR Node +class OrNode : public ASTNode { +public: + shared_ptr left; + shared_ptr right; + vector> children; + variant min; + variant max; + + OrNode(shared_ptr left, shared_ptr right); +}; + +// Group Node +class GroupNode : public ASTNode { +public: + bool __capturing__; + string group_name; + int group_id; + deque> children; + variant min; + variant max; + + GroupNode(deque> children, bool capturing = false, + string group_name = "", int group_id = -1); + bool is_capturing() const; +}; + +#endif // RE_AST_HH \ No newline at end of file diff --git a/regex_engine/lexer/lexer.cc b/regex_engine/lexer/lexer.cc new file mode 100644 index 0000000..d0958dd --- /dev/null +++ b/regex_engine/lexer/lexer.cc @@ -0,0 +1,90 @@ +#include "lexer.hh" + +bool Lexer::isDigit(char ch) const { return digits.find(ch) != string::npos; } + +vector Lexer::scan(const string &re) { + vector tokens; + + auto append = [&tokens](Token *elem) { tokens.push_back(elem); }; + + size_t i = 0; + bool escape_found = false; + + while (i < re.size()) { + char ch = re[i]; + + if (escape_found) { + // Handle escape sequences + if (ch == 't') { + append(new ElementToken("\t")); + } else if (ch == 's') { + append(new SpaceToken(ch)); + } else { + string str(1, ch); + append(new ElementToken(str)); + } + } else if (ch == '\\') { + escape_found = true; + ++i; + continue; + } else if (ch == '.') { + append(new Wildcard()); + } else if (ch == '(') { + append(new LeftParenthesis()); + } else if (ch == ')') { + append(new RightParenthesis()); + } else if (ch == '[') { + append(new LeftBracket()); + } else if (ch == '-') { + append(new Dash()); + } else if (ch == ']') { + append(new RightBracket()); + } else if (ch == '{') { + append(new LeftCurlyBrace()); + ++i; + while (i < re.size()) { + ch = re[i]; + if (ch == ',') { + append(new Comma()); + } else if (isDigit(ch)) { + append(new ElementToken(string(1, ch))); + } else if (ch == '}') { + append(new RightCurlyBrace()); + break; + } else { + throw invalid_argument("Bad token at index " + to_string(i)); + } + ++i; + } + } else if (ch == '^') { + if (i == 0) { + append(new Start()); + } else { + append(new Circumflex()); + } + } else if (ch == '$') { + append(new End()); + } else if (ch == '?') { + append(new QuestionMark()); + } else if (ch == '*') { + append(new Asterisk()); + } else if (ch == '+') { + append(new Plus()); + } else if (ch == '|') { + append(new VerticalBar()); + } else if (ch == '}') { + append(new RightCurlyBrace()); + } else { + append(new ElementToken(string(1, ch))); + } + + escape_found = false; + ++i; + } + // for (const auto& token : tokens) { + // cerr << "Token: " << token->char_ << " (Type: " << + // typeid(*token).name() << ")" << endl; + // } + + return tokens; +} \ No newline at end of file diff --git a/regex_engine/lexer/lexer.hh b/regex_engine/lexer/lexer.hh new file mode 100644 index 0000000..0abf62b --- /dev/null +++ b/regex_engine/lexer/lexer.hh @@ -0,0 +1,21 @@ +#ifndef LEXER_H +#define LEXER_H + +#include "../tokens/tokens.hh" +#include +#include +using namespace std; + +class Lexer { +private: + const string digits = "0123456789"; + +public: + Lexer() = default; + + bool isDigit(char ch) const; + + vector scan(const std::string &re); +}; + +#endif \ No newline at end of file diff --git a/regex_engine/parser/parser.cc b/regex_engine/parser/parser.cc new file mode 100644 index 0000000..5166682 --- /dev/null +++ b/regex_engine/parser/parser.cc @@ -0,0 +1,526 @@ +#include "parser.hh" +#include + +using namespace std; + +shared_ptr Parser::parse(const string &re) { + tokens = lxr.scan(re); + i = -1; + next_tkn(); + auto ast = parse_re(); + if (curr_tkn) { + throw runtime_error("Unable to parse the regex."); + } + return ast; +} + +Token *Parser::next_tkn(bool without_consuming) { + if (without_consuming) { + return (i + 1 < tokens.size()) ? tokens[i + 1] : nullptr; + } + i++; + if (i < tokens.size()) { + curr_tkn = tokens[i]; + } else { + curr_tkn = nullptr; + } + return curr_tkn; +} + +function Parser::next_tkn_initializer(const string &re) { + vector tokens = lxr.scan(re); + int i = -1; + + return [tokens, i](bool without_consuming = false) mutable -> Token * { + if (without_consuming) { + return (i + 1 < tokens.size()) ? tokens[i + 1] : nullptr; + } + i++; + return (i < tokens.size()) ? tokens[i] : nullptr; + }; +} + +shared_ptr Parser::parse_re() { return make_shared(parse_re_seq()); } + +shared_ptr +Parser::parse_re_seq(bool capturing, const string &group_name, int group_id) { + bool match_start = false; + bool match_end = false; + + // Debug: Print the current token at the start of the function + // std::cout << "parse_re_seq: Current token = " << (curr_tkn ? + // curr_tkn->char_ : "nullptr") << std::endl; + + // Handle start anchor (^) + if (dc(curr_tkn) || dc(curr_tkn)) { + // cout << "parse_re_seq: Found start anchor (^), setting match_start = + // true" << std::endl; + next_tkn(); + match_start = true; + } + + // Parse the group + // cout << "parse_re_seq: Parsing group..." << std::endl; + shared_ptr node = parse_group(capturing, group_name, group_id); + + // Handle end anchor ($) + if (dc(curr_tkn)) { + // cout << "parse_re_seq: Found end anchor ($), setting match_end = true" << + // std::endl; + next_tkn(); + match_end = true; + } + + // Add StartElement if match_start is true + if (match_start) { + // cout << "parse_re_seq: Adding StartElement to the group" << std::endl; + dynamic_pointer_cast(node)->children.push_front( + make_shared()); + } + + // Add EndElement if match_end is true + if (match_end) { + // cout << "parse_re_seq: Adding EndElement to the group" << std::endl; + dynamic_pointer_cast(node)->children.push_back( + make_shared()); + } + + // Handle alternation (|) + if (dc(curr_tkn)) { + // cout << "parse_re_seq: Found alternation (|), creating OrNode" << + // std::endl; + next_tkn(); + node = make_shared( + node, parse_re_seq(true, group_name, + dynamic_pointer_cast(node)->group_id)); + } + + return node; +} + +shared_ptr +Parser::parse_group(bool capturing, const string &group_name, int group_id) { + if (group_id == -1) { + group_id = groups_counter++; + } + + deque> elements; + + // cout << "Parsing group with group_id: " << group_id << endl; + + while (curr_tkn && !dynamic_cast(curr_tkn) && + !dynamic_cast(curr_tkn) && + !dynamic_cast(curr_tkn)) { + // cout << "Current token: " << (curr_tkn ? curr_tkn->char_ : "nullptr") << + // endl; + + auto new_el = parse_range_el(); + // cout << "Parsed element: " << new_el << endl; + + next_tkn(); + + if (dynamic_cast(curr_tkn)) { + elements.push_back(new_el); + // cout << "EndToken encountered, breaking the loop." << endl; + break; + } + + // Handle quantifiers for any valid AST node + if (dynamic_cast(curr_tkn)) { + // cout << "Quantifier found, handling..." << endl; + + // Downcast new_el to the appropriate derived class + if (auto element = dynamic_pointer_cast(new_el)) { + if (dynamic_cast(curr_tkn)) { + // cout << "ZeroOrOne quantifier." << endl; + element->min = 0; + element->max = 1; + } else if (dynamic_cast(curr_tkn)) { + // cout << "ZeroOrMore quantifier." << endl; + element->min = 0; + element->max = numeric_limits::infinity(); + } else if (dynamic_cast(curr_tkn)) { + // cout << "OneOrMore quantifier." << endl; + element->min = 1; + element->max = numeric_limits::infinity(); + } + } else if (auto range_element = + dynamic_pointer_cast(new_el)) { + if (dynamic_cast(curr_tkn)) { + // cout << "ZeroOrOne quantifier." << endl; + range_element->min = 0; + range_element->max = 1; + } else if (dynamic_cast(curr_tkn)) { + // cout << "ZeroOrMore quantifier." << endl; + range_element->min = 0; + range_element->max = numeric_limits::infinity(); + } else if (dynamic_cast(curr_tkn)) { + // cout << "OneOrMore quantifier." << endl; + range_element->min = 1; + range_element->max = numeric_limits::infinity(); + } + } else if (auto group_node = dynamic_pointer_cast(new_el)) { + if (dynamic_cast(curr_tkn)) { + // cout << "ZeroOrOne quantifier for group." << endl; + group_node->min = 0; + group_node->max = 1; + } else if (dynamic_cast(curr_tkn)) { + // cout << "ZeroOrMore quantifier." << endl; + group_node->min = 0; + group_node->max = numeric_limits::infinity(); + } else if (dynamic_cast(curr_tkn)) { + // cout << "OneOrMore quantifier." << endl; + group_node->min = 1; + group_node->max = numeric_limits::infinity(); + } + } else if (auto or_node = dynamic_pointer_cast(new_el)) { + if (dynamic_cast(curr_tkn)) { + // cout << "ZeroOrOne quantifier." << endl; + or_node->min = 0; + or_node->max = 1; + } else if (dynamic_cast(curr_tkn)) { + // cout << "ZeroOrMore quantifier." << endl; + or_node->min = 0; + or_node->max = numeric_limits::infinity(); + } else if (dynamic_cast(curr_tkn)) { + // cout << "OneOrMore quantifier." << endl; + or_node->min = 1; + or_node->max = numeric_limits::infinity(); + } + } + + next_tkn(); // Consume the quantifier token + } else if (dynamic_cast(curr_tkn)) { + // cout << "Current token: " << (curr_tkn ? curr_tkn->char_ : "nullptr") + // << endl; cout << "LeftCurlyBrace found, parsing curly..." << endl; cout + // << "Current token: " << (curr_tkn ? curr_tkn->char_ : "nullptr") << + // endl; + parse_curly(new_el); + } + + elements.push_back(new_el); + } + + // cout << "Parsed " << elements.size() << " elements in the group." << endl; + + // cout << "Returning GroupNode..." << endl; + return make_shared(elements, capturing, group_name, group_id); +} +// main engine works on this +void Parser::parse_curly(shared_ptr new_el) { + next_tkn(); // past the curly brace + // cout << "Current token: " << (curr_tkn ? curr_tkn->char_ : "nullptr") << + // endl; cases: 2,} 2} 2,3} } + if (dynamic_cast(curr_tkn)) { + throw runtime_error("Empty brace found, Invalid syntax"); + } + // cases: 2,} 2} 2,3} + string val_1, val_2; + + try { + // cout << "Inside the try block" << endl; + // cases: 2,} 2} 2,3} + while (dc(curr_tkn)) { + auto shit = dc(curr_tkn); + val_1 += shit->char_; + next_tkn(); + // cout << "Current token: " << (curr_tkn ? curr_tkn->char_ : "nullptr") + // << endl; + } + // past all the elements, extracted val_1 + // cases: ,} } ,3} + + if (val_1.empty()) + val_1 = "0"; + // cout << "val_1 is " << val_1 << endl; + // cout<<"reached here?"<(new_el); + // cout<<"reached here??"<(new_el); + // cout<<"reached here???"<(new_el); + // cout<(curr_tkn)) { + // cases } + + if (element) { + element->min = stoi(val_1); + element->max = stoi(val_1); + } else if (range_element) { + range_element->min = stoi(val_1); + range_element->max = stoi(val_1); + } else if (gn) { + gn->min = stoi(val_1); + gn->max = stoi(val_1); + } + + next_tkn(); + return; + } + // cases: ,} ,3} + + next_tkn(); // past the comma + // cases: } 3} + + if (dynamic_cast(curr_tkn)) { + val_2 = "inf"; // Use "inf" to represent infinity + // cout << "val_2 is " << val_2 << endl; + + if (element) { + // cout << "Setting element min and max" << endl; + element->min = stoi(val_1); + element->max = numeric_limits::max(); // Use max() for infinity + } else if (range_element) { + // cout << "Setting range_element min and max" << endl; + range_element->min = stoi(val_1); + range_element->max = + numeric_limits::max(); // Use max() for infinity + } else if (gn) { + gn->min = stoi(val_1); + gn->max = numeric_limits::max(); + } + + next_tkn(); // Move past the right curly brace + return; + } + + // cout << "Current token: " << (curr_tkn ? curr_tkn->char_ : "nullptr") << + // endl; + while (dynamic_cast(curr_tkn)) { + auto shit = dynamic_cast(curr_tkn); + val_2 += shit->char_; + next_tkn(); + // cout << "Current token: " << (curr_tkn ? curr_tkn->char_ : "nullptr") + // << endl; + } + if (val_2.empty()) + val_2 = to_string(numeric_limits::infinity()); + // cout << "val_2 is " << val_2 << endl; + if (!dynamic_cast(curr_tkn)) { + throw runtime_error("Invalid curly brace syntax: expected closing brace"); + } + + next_tkn(); // Skip the closing brace + // cout << "Current token: " << (curr_tkn ? curr_tkn->char_ : "nullptr") << + // endl; + + if (element) { + element->min = stoi(val_1); + element->max = stoi(val_2); + } else if (range_element) { + range_element->min = stoi(val_1); + range_element->max = stoi(val_2); + } else if (gn) { + gn->min = stoi(val_1); + gn->max = stoi(val_2); + } + + } catch (const exception &e) { + throw runtime_error("Invalid curly brace syntax."); + } +} + +shared_ptr Parser::parse_range_el() { + if (dynamic_cast(curr_tkn)) { + next_tkn(); + + auto element = parse_inner_el(); + if (dynamic_cast(curr_tkn)) { + return element; + } else { + throw runtime_error("Missing closing ']'."); + } + } else { + // cout << "parse_range_el: reached here" << endl; + return parse_el(); + } +} + +string Parser::get_range_str(char start, char end) { + string result; + for (int i = start; i <= end; ++i) { + result += static_cast(i); + } + return result; +} + +shared_ptr Parser::parse_inner_el() { + string match_str; + bool positive_logic = true; + + if (dc(curr_tkn)) { + positive_logic = false; + next_tkn(); + } + + char prev_char = '\0'; + + while (curr_tkn) { + if (dc(curr_tkn)) + break; + + if (dc(curr_tkn)) { + match_str += dc(curr_tkn)->char_; + // cout << "This is the space token char in parse_inner_el() " << + // dc(curr_tkn)->char_ << endl; + next_tkn(); + continue; + } + + if (!dynamic_cast(curr_tkn)) { + curr_tkn = new ElementToken(dynamic_cast(curr_tkn)->char_); + } + + Token *peek_tkn = next_tkn(true); + + if (peek_tkn && dc(peek_tkn)) { + prev_char = dc(curr_tkn)->char_[0]; + // cout << "Just before consuming 2 tokens " << prev_char << endl; + next_tkn(); + // cout << "Yeah consumed 2 tokens here, now on: " << (curr_tkn ? + // curr_tkn->char_ : "nullptr") << endl; + + Token *next_peek_tkn = next_tkn(true); + + if (dc(next_peek_tkn) || + dc(next_peek_tkn)) { + match_str += prev_char; + match_str += dc(peek_tkn)->char_; + } else { + next_tkn(); + if (!curr_tkn) + throw runtime_error("Missing closing ']'"); + + char end_char = dc(curr_tkn)->char_[0]; + // cout << "end char is" << end_char << endl; + if (prev_char > end_char) { + throw runtime_error("Range values reversed."); + } + match_str += get_range_str(prev_char, end_char); + } + } else { + match_str += dc(curr_tkn)->char_; + next_tkn(); + } + } + + sort(match_str.begin(), match_str.end()); + // cout << "match str after sort " << match_str << endl; + match_str.erase(unique(match_str.begin(), match_str.end()), match_str.end()); + // cout << "match str after an erase " << match_str << endl; + + return make_shared(match_str, positive_logic); +} + +shared_ptr Parser::parse_el() { + string group_name; + + // Debug: Print the current token being processed + // cout << "parse_el: Current token = " << (curr_tkn ? curr_tkn->char_ : + // "nullptr") << std::endl; + + if (dynamic_cast(curr_tkn)) { + // Debug: Handling ElementToken + // cout << "parse_el: Found ElementToken, creating Element node with match = + // " + //<< dynamic_cast(curr_tkn)->char_ << std::endl; + return make_shared(dynamic_cast(curr_tkn)->char_); + } else if (dynamic_cast(curr_tkn)) { + // Debug: Handling Wildcard + // cout << "parse_el: Found Wildcard, creating WildcardElement node" << + // std::endl; + return make_shared(); + } else if (dynamic_cast(curr_tkn)) { + // Debug: Handling SpaceToken + // cout << "parse_el: Found SpaceToken, creating SpaceElement node" << + // std::endl; + return make_shared(); + } else if (dynamic_cast(curr_tkn)) { + // Debug: Handling LeftParenthesis (start of a group) + // cout << "parse_el: Found LeftParenthesis, starting group parsing" << + // std::endl; + + next_tkn(); // Move past the '(' + bool capturing = true; + + // Debug: Check for non-capturing group or named group + if (dynamic_cast(curr_tkn)) { + // cout << "parse_el: Found QuestionMark, checking for non-capturing or + // named group" << std::endl; + next_tkn(); // Move past the '?' + + if (dynamic_cast(curr_tkn) && + dynamic_cast(curr_tkn)->char_ == ":") { + // Debug: Non-capturing group + // cout << "parse_el: Found ':', marking group as non-capturing" << + // std::endl; + capturing = false; + next_tkn(); // Move past the ':' + } else if (dynamic_cast(curr_tkn) && + dynamic_cast(curr_tkn)->char_ == "<") { + // Debug: Named group + // cout << "parse_el: Found '<', parsing named group" << std::endl; + next_tkn(); // Move past the '<' + group_name = parse_group_name(); + // cout << "parse_el: Parsed group name = " << group_name << std::endl; + } else { + // Debug: Invalid group syntax + // cout << "parse_el: Invalid group syntax after '?'" << std::endl; + throw runtime_error("Invalid group syntax."); + } + } + + // Parse the group contents + // cout << "parse_el: Parsing group contents" << std::endl; + auto res = parse_re_seq(capturing, group_name); + + // Debug: Check for closing parenthesis + if (dynamic_cast(curr_tkn)) { + // cout << "parse_el: Found RightParenthesis, group parsing complete" << + // std::endl; + return res; + } else { + // Debug: Missing closing parenthesis + // cout << "parse_el: Missing closing group parenthesis ')'" << std::endl; + throw runtime_error("Missing closing group parenthesis ')'."); + } + } else { + // Debug: Unhandled token + // cout << "parse_el: Unhandled token = " << (curr_tkn ? curr_tkn->char_ : + // "nullptr") << std::endl; + throw runtime_error("Unescaped special character"); + } +} + +string Parser::parse_group_name() { + if (!curr_tkn) { + throw runtime_error("Unterminated named group name."); + } + string group_name; + + while (curr_tkn && dynamic_cast(curr_tkn) && + dynamic_cast(curr_tkn)->char_ != ">") { + group_name += dynamic_cast(curr_tkn)->char_; + next_tkn(); + } + + if (group_name.empty()) + throw runtime_error("Unexpected empty named group name."); + + // consume the > token + if (curr_tkn && dynamic_cast(curr_tkn) && + dynamic_cast(curr_tkn)->char_ == ">") { + next_tkn(); + } else { + throw runtime_error("Expected '>' to terminate named group name."); + } + + return group_name; +} \ No newline at end of file diff --git a/regex_engine/parser/parser.hh b/regex_engine/parser/parser.hh new file mode 100644 index 0000000..0ebe412 --- /dev/null +++ b/regex_engine/parser/parser.hh @@ -0,0 +1,47 @@ +#ifndef PARSER_HH +#define PARSER_HH + +#include "../ast/ast.hh" +#include "../lexer/lexer.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define dc dynamic_cast + +using namespace std; + +class Parser { +public: + Lexer lxr; + vector tokens; + int i = -1; + Token *curr_tkn = nullptr; + + shared_ptr parse(const string &re); + Token *next_tkn(bool without_consuming = false); + function next_tkn_initializer(const string &re); + shared_ptr parse_re(); + shared_ptr parse_re_seq(bool capturing = true, + const string &group_name = "", + int group_id = -1); + int groups_counter = 0; + shared_ptr parse_group(bool capturing = true, + const string &group_name = "", + int group_id = -1); + void parse_curly(shared_ptr new_el); + shared_ptr parse_range_el(); + string get_range_str(char start, char end); + shared_ptr parse_inner_el(); + shared_ptr parse_el(); + string parse_group_name(); +}; + +#endif // PARSER_HH \ No newline at end of file diff --git a/regex_engine/regex/match.hh b/regex_engine/regex/match.hh new file mode 100644 index 0000000..4c9bafa --- /dev/null +++ b/regex_engine/regex/match.hh @@ -0,0 +1,26 @@ +// src/regex/match.hh +#ifndef MATCH_HH +#define MATCH_HH + +#include + +class Match { +public: + // Constructor + Match(int group_id, int start_idx, int end_idx, const std::string &str, + const std::string &name) + : group_id(group_id), start_idx(start_idx), end_idx(end_idx), str(str), + name(name) { + match = str.substr(start_idx, end_idx - start_idx); + } + + // Member variables + int group_id; + int start_idx; + int end_idx; + std::string str; + std::string name; + std::string match; +}; + +#endif // MATCH_HH \ No newline at end of file diff --git a/regex_engine/regex/regex.cc b/regex_engine/regex/regex.cc new file mode 100644 index 0000000..4afcae6 --- /dev/null +++ b/regex_engine/regex/regex.cc @@ -0,0 +1,1439 @@ +#include "regex.hh" +#include "../ast/ast.hh" +#include "../lexer/lexer.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// a function to print the children of an AST in a more visual manner +void RegexEngine::printChildren(const shared_ptr &node) { + if (auto reNode = dynamic_pointer_cast(node)) { + cout << "RE Node Children: " << reNode->children.size() << endl; + for (const auto &child : reNode->children) { + cout << " Child Type: " << typeid(*child).name() << endl; + } + } else if (auto groupNode = dynamic_pointer_cast(node)) { + cout << "GroupNode Children: " << groupNode->children.size() << endl; + for (const auto &child : groupNode->children) { + cout << " Child Type: " << typeid(*child).name() << endl; + } + } else if (auto orNode = dynamic_pointer_cast(node)) { + cout << "OrNode Children: " << orNode->children.size() << endl; + for (const auto &child : orNode->children) { + cout << " Child Type: " << typeid(*child).name() << endl; + } + } else if (auto leafNode = dynamic_pointer_cast(node)) { + cout << "LeafNode has no children." << endl; + } else { + cout << "Unknown node type." << endl; + } +} + +// a function to print an AST in a more visual manner +void RegexEngine::printAST(const shared_ptr &node, int depth) { + if (!node) + return; + + string indent(depth * 2, ' '); + + cout << indent << "Node Type: " << typeid(*node).name() << endl; + + if (auto ele = dynamic_cast(node.get())) { + cout << indent << " Match: " << ele->match << endl; + + if (holds_alternative(ele->min)) { + cout << indent << " Min (int): " << get(ele->min) << endl; + } else if (holds_alternative(ele->min)) { + cout << indent << " Min (double): " << get(ele->min) << endl; + } + + if (holds_alternative(ele->max)) { + cout << indent << " Max (int): " << get(ele->max) << endl; + } else if (holds_alternative(ele->max)) { + cout << indent << " Max (double): " << get(ele->max) << endl; + } + return; + + } else if (auto group = dynamic_cast(node.get())) { + cout << indent << " Group Name: " << group->group_name << endl; + cout << indent << " Group ID: " << group->group_id << endl; + cout << indent << " Capturing: " << (group->is_capturing() ? "Yes" : "No") + << endl; + + if (holds_alternative(group->min)) { + cout << indent << " Min (int): " << get(group->min) << endl; + } else if (holds_alternative(group->min)) { + cout << indent << " Min (double): " << get(group->min) << endl; + } + + if (holds_alternative(group->max)) { + cout << indent << " Max (int): " << get(group->max) << endl; + } else if (holds_alternative(group->max)) { + cout << indent << " Max (double): " << get(group->max) << endl; + } + + } else if (auto range = dynamic_cast(node.get())) { + cout << indent << " Match Range: " << range->match << endl; + cout << indent + << " Positive Logic: " << (range->is_positive_logic ? "Yes" : "No") + << endl; + + if (holds_alternative(range->min)) { + cout << indent << " Min (int): " << get(range->min) << endl; + } else if (holds_alternative(range->min)) { + cout << indent << " Min (double): " << get(range->min) << endl; + } + + if (holds_alternative(range->max)) { + cout << indent << " Max (int): " << get(range->max) << endl; + } else if (holds_alternative(range->max)) { + cout << indent << " Max (double): " << get(range->max) << endl; + } + + } else if (auto reNode = dynamic_cast(node.get())) { + cout << indent << " Group Name: " << reNode->group_name << endl; + cout << indent << " Capturing: " << (reNode->is_capturing() ? "Yes" : "No") + << endl; + } else if (auto orNode = dc(node.get())) { + if (holds_alternative(orNode->min)) { + cout << indent << " Min (int): " << get(orNode->min) << endl; + } else if (holds_alternative(orNode->min)) { + cout << indent << " Min (double): " << get(orNode->min) << endl; + } + + if (holds_alternative(orNode->max)) { + cout << indent << " Max (int): " << get(orNode->max) << endl; + } else if (holds_alternative(orNode->max)) { + cout << indent << " Max (double): " << get(orNode->max) << endl; + } + } + + // Recursively print children for nodes with children + if (auto group = dynamic_cast(node.get())) { + if (!group->children.empty()) { + cout << indent << " Children:" << endl; + for (const auto &child : group->children) { + printAST(child, depth + 1); + } + } + } else if (auto orNode = dynamic_cast(node.get())) { + cout << indent << " Children:" << endl; + if (!orNode->children.empty()) { + for (const auto &child : orNode->children) { + printAST(child, depth + 1); + } + } + + } else if (auto reNode = dynamic_cast(node.get())) { + if (!reNode->children.empty()) { + cout << indent << " Children:" << endl; + for (const auto &child : reNode->children) { + printAST(child, depth + 1); + } + } + } else { + cout << indent << " No children for this node type." << endl; + } +} + +//****************************************************************************************************************************************************** +// + +tuple>> +RegexEngine::match(const string &re, const string &input_string, + bool return_matches, bool continue_after_match) { + /** + * Searches a regular expression within a test string. + * + * This method searches for the specified regular expression within the + * provided test string and returns the result. It can be customized to alter + * the search behavior and the returned value. + * + * + * @param re The regular expression to search (string). + * @param test_string The string to be searched (string). + * @param return_matches If `true`, returns a data structure containing the + * matches, including the whole match and any matched + * groups or subgroups. Default is `false`. + * @param continue_after_match If `true`, the search continues after the first + * match until the entire input string is + * consumed. Default is `false`. + * + * @return A `variant` containing one of the following tuples: + * - `(bool, int)`: + * - `bool`: Indicates whether a match was found. + * - `int`: The index of the last matched character. + * - `(bool, int, vector>)`: + * - `bool`: Indicates whether a match was found. + * - `int`: The index of the last matched character. + * - `vector>`: Contains all matches: + * - The first position represents the whole match. + * - Subsequent positions represent all matched groups and + * subgroups. + * + * @throws runtime_error If an error occurs during regex compilation or + * execution. + */ + + // An inline lambda function to return in the necessary format + auto give_back = [](bool result, int chars_consumed, + const vector> &all_matches, + bool return_matches) { + if (return_matches) { + + return make_tuple(result, chars_consumed, all_matches); + + } else { + + return make_tuple(result, chars_consumed, vector>()); + } + }; + + // cout<<"match: Starting match function"< ast = (prev_re != re) ? parser.parse(normalized_re) : prev_ast; + // cout<<"match: AST parsed successfully"<> all_matches; + + if (result) { + // cout<<"match: Initial match successful"< highest_matched_idx) { + // cout<<"match: New match found, updating highest_matched_idx to + // "<> +RegexEngine::matchInternal(shared_ptr ast, const string &input_string, + int start_str_i) { + /* A more specific match function which works in core */ + + deque matches; + Match *last_match = + nullptr; // used to restore the left match of a or-node (if necessary) + + // `max_matched_idx` represents the "upper limit" of the match. + // It is essential when backtracking in cases with nested quantifiers, + // as it provides a mechanism to signal the group that is causing the + // failure due to excessive greediness to stop earlier if possible. + int max_matched_idx = -1; + + // `str_i` represents the characters matched so far. It is initialized to the + // value of the input parameter `start_str_i` because the match may need to + // start at an index other than 0. For example, this can occur when the + // function is invoked to search for a second match in the test string. + int str_i = start_str_i; + + //***************************************************************************************************************** + //*/ a returm function just like above + auto give_back = [&matches](bool result, int str_i) { + return make_tuple(result, str_i, matches); + }; + + //***************************************************************************************************************** + //*/ + /** + * This lambda function saves matches for capturing groups during regex + * evaluation. It invokes the `match_group` function to attempt a match for + * the specified group (`ast`) in the input string starting from the given + * index. If the match is successful and the group is a capturing group, it + * checks for existing matches with the same group ID in the `matches` list, + * removes them if found, and adds a new `Match` object with the group ID, + * start and end indices, matched substring, and group name. The function + * ensures that the most recent match for each group is saved. It returns a + * tuple containing a boolean indicating match success and the ending index of + * the match. Non-capturing groups do not modify the `matches` list, and + * `max_matched_idx` optionally limits the match extent for backtracking or + * nested quantifiers. + * */ + + auto save_matches = + [&matches, &last_match]( + const function(shared_ptr, const string &, + int)> &match_group, + shared_ptr ast, const string &input_string, int start_idx, + int max_matched_idx = + -1) { // cout<<"save_matches: save_matches called with ast = "<< + // typeid(*ast).name() <<" , start_idx = "<group_id == dc(ast.get())->group_id) { + // cout << "save_matches: Found existing match with group ID "<< + // dc(ast.get())->group_id << ". Removing it." << + // endl; + last_match = &(*it); + // cout << "save_matches: Removed match: group_id=" << + // last_match->group_id<(ast.get()); + // cout<<"save_matches: "<group_id, start_idx, end_idx, + input_string, gn->group_name)); + // cout << "save_matches: Added new match to the front: group_id=" << + // gn->group_id<(shared_ptr, const string &, int)> + match_group; + + match_group = [&](shared_ptr ast, const string &input_string, + int max_matched_idx = -1) { + auto re_node = dynamic_cast(ast.get()); + shared_ptr curr_node = (re_node && !re_node->children.empty()) + ? re_node->children[0] + : nullptr; + + int i = 0; + + //***************************************************************************************************************** + //*/ + vector>> backtrack_stack; + // (child_index, min_matches, matched_times, consumed_lengths): + // - `child_index`: Index of the current child node being processed. + // - `min_matches`: Minimum number of matches required for this node. + // - `matched_times`: Number of times this node has successfully matched so + // far. + // - `consumed_lengths`: List of character counts consumed during each + // match. + + function(int, int, bool)> backtrack; + + backtrack = [&](int str_i, int curr_child_i, + bool recursive = false) -> tuple { + /** + * Determines whether backtracking is possible and provides the state to + * backtrack to. + * + * This function evaluates the current state of the engine to decide if + * backtracking can occur. It returns a flag indicating backtracking + * feasibility and, if possible, the updated string index and child node + * index to backtrack to. + * + * @param str_i The current index being considered in the test string. + * @param curr_child_i The index of the `GroupNode` child currently being + * evaluated. + * + * @return A tuple containing: + * - `bool`: True if backtracking is possible. + * - `int`: The new string index to backtrack to (valid only if + * backtracking is possible). + * - `int`: The new child node index to backtrack to (valid only + * if backtracking is possible). + */ + + // cout << "backtrack: Called with str_i = " << str_i << ", curr_child_i = + // " << curr_child_i << ", recursive = " << recursive << endl; + if (backtrack_stack.empty()) { + // cout << "backtrack: Backtracking not possible. Stack is empty." << + // endl; + return make_tuple(false, str_i, curr_child_i); + } + + auto [popped_child_i, min_, matched_times, chars_consumed_list] = + backtrack_stack.back(); + backtrack_stack.pop_back(); + // cout << "backtrack: Popped state from stack: popped_child_i = " << + // popped_child_i<< ", min_ = " << min_ << ", matched_times = " << + // matched_times<(ast.get())) { + int tmp_str_i = str_i; + auto [result_left, str_i_left] = save_matches( + match_group, curr_node, input_string, str_i, max_matched_idx); + if (!result_left) { + str_i = tmp_str_i; + curr_node = dc(ast.get())->right; + auto [result_right, str_i_right] = save_matches( + match_group, curr_node, input_string, str_i, max_matched_idx); + str_i = str_i_right; + return make_tuple(result_right, str_i); + } + str_i = str_i_left; + return make_tuple(result_left, str_i); + } + + while (i < (dynamic_pointer_cast(ast) + ? dynamic_pointer_cast(ast)->children.size() + : dynamic_pointer_cast(ast) + ? dynamic_pointer_cast(ast)->children.size() + : dynamic_pointer_cast(ast) + ? dynamic_pointer_cast(ast)->children.size() + : 0)) { + // cout<< "size"<<(dynamic_pointer_cast(ast) ? + // dynamic_pointer_cast(ast)->children.size() : + // dynamic_pointer_cast(ast) ? + // dynamic_pointer_cast(ast)->children.size() : + // dynamic_pointer_cast(ast) ? + // dynamic_pointer_cast(ast)->children.size() : 0)<(ast.get()) ? + // dc(ast.get())->children[i] : + // dc(ast.get())->children[i]; cout << "Processing node type: " << + // typeid(*curr_node).name() << endl; printChildren(ast); + + if (auto reNode = dynamic_pointer_cast(ast)) { + curr_node = reNode->children[i]; + } else if (auto groupNode = dynamic_pointer_cast(ast)) { + curr_node = groupNode->children[i]; + } else if (auto orNode = dynamic_pointer_cast(ast)) { + curr_node = orNode->children[i]; + } + + if (dc(curr_node.get())) { + int before_str_i = str_i; + auto orn = dc(curr_node.get()); + // int min_ = get(or-> min); + // int max_ = get(or-> max); + int min_ = 0; + int max_ = 0; + + if (holds_alternative(orn->min)) { + min_ = get(orn->min); + // cout << "isOrNode: min is (int) " << min_ << endl; + } else if (holds_alternative(orn->min)) { + double min_double = get(orn->min); + // cout << "isOrNode: min is (double) " << min_double << endl; + } else { + cout << "Unexpected type in min!" << endl; + } + + if (holds_alternative(orn->max)) { + max_ = get(orn->max); + // cout << "isOrNode: max is (int) " << max_ << endl; + } else if (holds_alternative(orn->max)) { + double max_double = get(orn->max); + if (isinf(max_double)) { + max_ = INT_MAX; + // cout << "isOrNode: max is infinite (treated as INT_MAX)" << endl; + } else { + max_ = static_cast(max_double); + // cout << "isOrNode: max is (double) " << max_ << endl; + } + } else { + cout << "Unexpected type in max!" << endl; + } + + int j = 0; + vector chars_consumed_list; + + bool backtracking = false; + + while (j < max_) { + int tmp_str_i = str_i; + bool save_match_left = + dc(dc(curr_node.get())->children[0].get()); + auto [result_left, str_i_left] = + save_match_left + ? save_matches(match_group, + dc(curr_node.get())->children[0], + input_string, str_i, max_matched_idx) + : match_group(dc(curr_node.get())->children[0], + input_string, max_matched_idx); + + str_i = tmp_str_i; + + bool save_match_right = + dc(dc(curr_node.get())->children[1].get()); + auto [result_right, str_i_right] = + save_match_right + ? save_matches(match_group, + dc(curr_node.get())->children[1], + input_string, str_i, max_matched_idx) + : match_group( + dynamic_cast(curr_node.get())->children[1], + input_string, max_matched_idx); + + if (result_left && result_right) { + bool chose_left = (str_i_left >= str_i_right); + str_i = chose_left ? str_i_left : str_i_right; + if (max_matched_idx != -1 && str_i > max_matched_idx) { + str_i = chose_left ? str_i_right : str_i_left; + } + if (chose_left) { + if (save_match_right) + remove_from_left(); + if (save_match_left) + add_to_left(); + } else { + if (save_match_left && !save_match_right) + remove_from_left(); + } + } else if (result_left && !result_right) { + str_i = str_i_left; + } else if (!result_left && result_right) { + str_i = str_i_right; + } + + bool result = (result_left || result_right); + + if (result && (max_matched_idx == -1 || str_i <= max_matched_idx)) { + if ((str_i - tmp_str_i == 0) && j >= min_) { + max_matched_idx = -1; + break; + } + chars_consumed_list.push_back(str_i - tmp_str_i); + } else { + if (min_ <= j) { + max_matched_idx = -1; + break; + } + if (i > 0 && !dynamic_cast( + dynamic_cast(ast.get()) + ? dynamic_cast(ast.get()) + ->children[i - 1] + .get() + : dynamic_cast(ast.get()) + ->children[i - 1] + .get())) { + str_i = pop(i, str_i); + if (str_i == start_str_i) + return make_tuple(false, str_i); + max_matched_idx = + (max_matched_idx == -1) ? str_i - 1 : max_matched_idx - 1; + } + auto [can_bt, bt_str_i, bt_i] = backtrack(str_i, i, false); + if (can_bt) { + i = bt_i; + str_i = bt_str_i; + backtracking = true; + break; + } else { + return make_tuple(false, str_i); + } + } + j++; + } + if (!backtracking) { + backtrack_stack.emplace_back(i, min_, j, chars_consumed_list); + max_matched_idx = -1; + i++; + } + continue; + } else if (dc(curr_node.get())) { + // cout << "isGroupNode: Inside Group Node, i = " << i << endl; + auto gn = dc(curr_node.get()); + // int min_ = get(gn-> min); + // int max_ = get(gn-> max); + + int min_ = 0; + int max_ = 0; + + if (holds_alternative(gn->min)) { + min_ = get(gn->min); + // cout << "isGroupNode: min is (int) " << min_ << endl; + } else if (holds_alternative(gn->min)) { + double min_double = get(gn->min); + // cout << "isGroupNode: min is (double) " << min_double << endl; + } else { + cout << "Unexpected type in min!" << endl; + } + + if (holds_alternative(gn->max)) { + max_ = get(gn->max); + // cout << "isGroupNode: max is (int) " << max_ << endl; + } else if (holds_alternative(gn->max)) { + double max_double = get(gn->max); + if (isinf(max_double)) { + max_ = INT_MAX; + // cout << "isGroupNode: max is infinite (treated as INT_MAX)" << + // endl; + } else { + max_ = static_cast(max_double); + // cout << "isGroupNode: max is (double) " << max_ << endl; + } + } else { + cout << "Unexpected type in max!" << endl; + } + + int j = 0; + vector chars_consumed_list; + int before_str_i = str_i; + bool backtracking = false; + + // cout << "isGroupNode: Starting inner while loop. j = " << j << ", + // str_i = " << str_i << endl; + + while (j < max_) { + // cout << "isGroupNode: Inner while loop iteration. j = " << j << ", + // str_i = " << str_i << endl; + int tmp_str_i = str_i; + auto [result, new_str_i] = save_matches( + match_group, curr_node, input_string, str_i, max_matched_idx); + // cout << "isGroupNode: Match result: " << result << ", new_str_i = " + // << new_str_i << endl; + + if (result && + (max_matched_idx == -1 || new_str_i <= max_matched_idx)) { + if ((new_str_i - tmp_str_i == 0) && j >= min_) { + max_matched_idx = -1; + //cout<<"isGroupNode: Breaking inner while loop due to zero + // consumption."< 0 && + !dc( + dc(ast.get()) + ? dc(ast.get())->children[i - 1].get() + : dc(ast.get())->children[i - 1].get())) { + str_i = pop(i, str_i); + // cout<<"isGroupNode: Updated str_i after pop = "<(curr_node.get())) { + // cout << "Inside Leaf Node, type: " << typeid(*curr_node).name() << + // endl; cout<<"isLeafNode: Inside Leaf Node, type:"<< + // typeid(*curr_node).name()<(curr_node.get())) { + // cout<<"isLeafNode: isElement:"<match<(ele->min)) { + min_ = get(ele->min); + // cout << "isLeafNode: min (int): " << min_ << endl; + } else if (holds_alternative(ele->min)) { + double min_double = get(ele->min); + // cout << "isLeafNode: min (double): " << min_double << endl; + } else { + cout << "Unexpected type in min!" << endl; + } + + if (holds_alternative(ele->max)) { + max_ = get(ele->max); + // cout << "isLeafNode: max (int): " << max_ << endl; + } else if (holds_alternative(ele->max)) { + double max_double = get(ele->max); + if (isinf(max_double)) { + max_ = INT_MAX; + // cout<< "isLeafNode: max is infinite (treated as + // INT_MAX)"<(max_double); + // cout<<"isLeafNode: max(double): "< chars_consumed_list; + int before_str_i = str_i; + // cout<<"isLeafNode: Starting inner while loop. j = "<< j<<" str_i = + // "<(curr_node.get())) { + if (leaf_node->is_match(input_string.substr(str_i, 1), str_i, + input_string.length()) && + (max_matched_idx == -1 || str_i < max_matched_idx)) { + // cout<<"isLeafNode: Match successful for character: "<< + // input_string[str_i]<(curr_node.get()) && + !dc(curr_node.get())) { + chars_consumed_list.push_back(1); + str_i++; + // cout<< "isLeafNode: Updated str_i = "<(curr_node.get())) { + // cout<<"isLeafNode: isRangeElement:"<match<(ele->min)) { + min_ = get(ele->min); + // cout << "isLeafNode: min (int): " << min_ << endl; + } else if (holds_alternative(ele->min)) { + double min_double = get(ele->min); + // cout << "isLeafNode: min (double): " << min_double << endl; + } else { + cout << "Unexpected type in min!" << endl; + } + + if (holds_alternative(ele->max)) { + max_ = get(ele->max); + // cout << "isLeafNode: max (int): " << max_ << endl; + } else if (holds_alternative(ele->max)) { + double max_double = get(ele->max); + if (isinf(max_double)) { + max_ = INT_MAX; + // cout<< "isLeafNode: max is infinite (treated as + // INT_MAX)"<(max_double); + // cout<<"isLeafNode: max(double): "< chars_consumed_list; + int before_str_i = str_i; + // cout<<"isLeafNode: Starting inner while loop. j = "<< j<<" str_i = + // "<(curr_node.get())) { + if (leaf_node->is_match(input_string.substr(str_i, 1), str_i, + input_string.length()) && + (max_matched_idx == -1 || str_i < max_matched_idx)) { + // cout<<"isLeafNode: Match successful for character: "<< + // input_string[str_i]<(curr_node.get()) && + !dc(curr_node.get())) { + chars_consumed_list.push_back(1); + str_i++; + // cout<< "isLeafNode: Updated str_i = "<(curr_node.get())) { + // cout<<"isLeafNode: isStartElement:"<match<(ele->min)) { + min_ = get(ele->min); + // cout << "isLeafNode: min (int): " << min_ << endl; + } else if (holds_alternative(ele->min)) { + double min_double = get(ele->min); + // cout << "isLeafNode: min (double): " << min_double << endl; + } else { + cout << "Unexpected type in min!" << endl; + } + + if (holds_alternative(ele->max)) { + max_ = get(ele->max); + // cout << "isLeafNode: max (int): " << max_ << endl; + } else if (holds_alternative(ele->max)) { + double max_double = get(ele->max); + if (isinf(max_double)) { + max_ = INT_MAX; + // cout<< "isLeafNode: max is infinite (treated as + // INT_MAX)"<(max_double); + // cout<<"isLeafNode: max(double): "< chars_consumed_list; + int before_str_i = str_i; + // cout<<"isLeafNode: Starting inner while loop. j = "<< j<<" str_i = + // "<(curr_node.get())) { + if (leaf_node->is_match(input_string.substr(str_i, 1), str_i, + input_string.length()) && + (max_matched_idx == -1 || str_i < max_matched_idx)) { + // cout<<"isLeafNode: Match successful for character: "<< + // input_string[str_i]<(curr_node.get()) && + !dc(curr_node.get())) { + chars_consumed_list.push_back(1); + str_i++; + // cout<< "isLeafNode: Updated str_i = "<(curr_node.get())) { + // cout<<"isLeafNode: isEndElement:"<match<(ele->min)) { + min_ = get(ele->min); + // cout << "isLeafNode: min (int): " << min_ << endl; + } else if (holds_alternative(ele->min)) { + double min_double = get(ele->min); + // cout << "isLeafNode: min (double): " << min_double << endl; + } else { + cout << "Unexpected type in min!" << endl; + } + + if (holds_alternative(ele->max)) { + max_ = get(ele->max); + // cout << "isLeafNode: max (int): " << max_ << endl; + } else if (holds_alternative(ele->max)) { + double max_double = get(ele->max); + if (isinf(max_double)) { + max_ = INT_MAX; + // cout<< "isLeafNode: max is infinite (treated as + // INT_MAX)"<(max_double); + // cout<<"isLeafNode: max(double): "< chars_consumed_list; + int before_str_i = str_i; + // cout<<"isLeafNode: Starting inner while loop. j = "<< j<<" str_i = + // "<(curr_node.get())) { + if (leaf_node->is_match(input_string.substr(str_i, 1), str_i, + input_string.length()) && + (max_matched_idx == -1 || str_i < max_matched_idx)) { + // cout<<"isLeafNode: Match successful for character: "<< + // input_string[str_i]<(curr_node.get()) && + !dc(curr_node.get())) { + chars_consumed_list.push_back(1); + str_i++; + // cout<< "isLeafNode: Updated str_i = "< +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +class RegexEngine { +public: + RegexEngine() : prev_re(""), prev_ast(nullptr) {} + + string prev_re; + shared_ptr prev_ast; + void printChildren(const shared_ptr &node); + tuple>> + match(const string &re, const string &input_string, + bool return_matches = false, bool continue_after_match = false); + void printAST(const shared_ptr &node, int depth = 0); + tuple> matchInternal(shared_ptr ast, + const string &input_string, + int start_str_i); +}; + +#endif // REGEX_HH \ No newline at end of file diff --git a/regex_engine/tokens/tokens.cc b/regex_engine/tokens/tokens.cc new file mode 100644 index 0000000..58ff9c7 --- /dev/null +++ b/regex_engine/tokens/tokens.cc @@ -0,0 +1,63 @@ +#include "tokens.hh" + +ElementToken::ElementToken(const std::string &ch) { char_ = ch; } + +WildcardToken::WildcardToken(const std::string &ch) { char_ = ch; } + +Wildcard::Wildcard() : WildcardToken(".") {} + +SpaceToken::SpaceToken(char ch) { char_ = ' '; } + +StartToken::StartToken(const std::string &ch) { char_ = ch; } + +Start::Start() : StartToken("^") {} + +EndToken::EndToken(const std::string &ch) { char_ = ch; } + +End::End() : EndToken("$") {} + +Escape::Escape(const std::string &ch) { char_ = '\\'; } + +Comma::Comma() { char_ = ','; } + +Parenthesis::Parenthesis() {} + +LeftParenthesis::LeftParenthesis() { char_ = '('; } + +RightParenthesis::RightParenthesis() { char_ = ')'; } + +CurlyBrace::CurlyBrace() {} + +LeftCurlyBrace::LeftCurlyBrace() { char_ = '{'; } + +RightCurlyBrace::RightCurlyBrace() { char_ = '}'; } + +Bracket::Bracket() {} + +LeftBracket::LeftBracket() { char_ = '['; } + +RightBracket::RightBracket() { char_ = ']'; } + +Quantifier::Quantifier(const std::string &ch) { char_ = ch; } + +ZeroOrMore::ZeroOrMore(const std::string &ch) : Quantifier(ch) {} + +OneOrMore::OneOrMore(const std::string &ch) : Quantifier(ch) {} + +ZeroOrOne::ZeroOrOne(const std::string &ch) : Quantifier(ch) {} + +Asterisk::Asterisk() : ZeroOrMore("*") {} + +Plus::Plus() : OneOrMore("+") {} + +QuestionMark::QuestionMark() : ZeroOrOne("?") {} + +OrToken::OrToken(const std::string &ch) { char_ = ch; } + +VerticalBar::VerticalBar() : OrToken("|") {} + +NotToken::NotToken(const std::string &ch) { char_ = ch; } + +Circumflex::Circumflex() : NotToken("^") {} + +Dash::Dash() { char_ = "-"; } \ No newline at end of file diff --git a/regex_engine/tokens/tokens.hh b/regex_engine/tokens/tokens.hh new file mode 100644 index 0000000..e991856 --- /dev/null +++ b/regex_engine/tokens/tokens.hh @@ -0,0 +1,173 @@ +#ifndef TOKENS_H +#define TOKENS_H + +#include +#include +#include +#include +using namespace std; + +class Token { +public: + Token() : char_("") {} + virtual ~Token() {} + + std::string char_; +}; + +class ElementToken : public Token { +public: + ElementToken(const std::string &ch); +}; + +class WildcardToken : public Token { +public: + WildcardToken(const std::string &ch); +}; + +class Wildcard : public WildcardToken { +public: + Wildcard(); +}; + +class SpaceToken : public Token { +public: + SpaceToken(char ch); +}; + +class StartToken : public Token { +public: + StartToken(const std::string &ch); +}; + +class Start : public StartToken { +public: + Start(); +}; + +class EndToken : public Token { +public: + EndToken(const std::string &ch); +}; + +class End : public EndToken { +public: + End(); +}; + +class Escape : public Token { +public: + Escape(const std::string &ch); +}; + +class Comma : public Token { +public: + Comma(); +}; + +class Parenthesis : public Token { +public: + Parenthesis(); +}; + +class LeftParenthesis : public Parenthesis { +public: + LeftParenthesis(); +}; + +class RightParenthesis : public Parenthesis { +public: + RightParenthesis(); +}; + +class CurlyBrace : public Token { +public: + CurlyBrace(); +}; + +class LeftCurlyBrace : public CurlyBrace { +public: + LeftCurlyBrace(); +}; + +class RightCurlyBrace : public CurlyBrace { +public: + RightCurlyBrace(); +}; + +class Bracket : public Token { +public: + Bracket(); +}; + +class LeftBracket : public Bracket { +public: + LeftBracket(); +}; + +class RightBracket : public Bracket { +public: + RightBracket(); +}; + +class Quantifier : public Token { +public: + Quantifier(const std::string &ch); +}; + +class ZeroOrMore : public Quantifier { +public: + ZeroOrMore(const std::string &ch); +}; + +class OneOrMore : public Quantifier { +public: + OneOrMore(const std::string &ch); +}; + +class ZeroOrOne : public Quantifier { +public: + ZeroOrOne(const std::string &ch); +}; + +class Asterisk : public ZeroOrMore { +public: + Asterisk(); +}; + +class Plus : public OneOrMore { +public: + Plus(); +}; + +class QuestionMark : public ZeroOrOne { +public: + QuestionMark(); +}; + +class OrToken : public Token { +public: + OrToken(const std::string &ch); +}; + +class VerticalBar : public OrToken { +public: + VerticalBar(); +}; + +class NotToken : public Token { +public: + NotToken(const std::string &ch); +}; + +class Circumflex : public NotToken { +public: + Circumflex(); +}; + +class Dash : public Token { +public: + Dash(); +}; + +#endif // TOKENS_H \ No newline at end of file From dbc39faed488b0cee5b93a8b46b1f7b99f5424a8 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 28 Jan 2025 16:34:38 +0530 Subject: [PATCH 2/3] (feat) : added regex_engine --- regex_engine/regex/regex.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/regex_engine/regex/regex.cc b/regex_engine/regex/regex.cc index 4afcae6..46f94bb 100644 --- a/regex_engine/regex/regex.cc +++ b/regex_engine/regex/regex.cc @@ -542,7 +542,7 @@ RegexEngine::matchInternal(shared_ptr ast, const string &input_string, // dynamic_pointer_cast(ast)->children.size() : // dynamic_pointer_cast(ast) ? // dynamic_pointer_cast(ast)->children.size() : 0)<(ast.get()) ? // dc(ast.get())->children[i] : @@ -744,8 +744,6 @@ RegexEngine::matchInternal(shared_ptr ast, const string &input_string, (max_matched_idx == -1 || new_str_i <= max_matched_idx)) { if ((new_str_i - tmp_str_i == 0) && j >= min_) { max_matched_idx = -1; - //cout<<"isGroupNode: Breaking inner while loop due to zero - // consumption."< Date: Tue, 28 Jan 2025 17:22:06 +0530 Subject: [PATCH 3/3] Delete changes.txt --- changes.txt | Bin 218406 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 changes.txt diff --git a/changes.txt b/changes.txt deleted file mode 100644 index 57d00205d3d5540bdf6d2c40ad0c7d7855b4fa5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 218406 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