diff --git a/tooling/pipeline/src/services/xeger/xeger.cc b/tooling/pipeline/src/services/xeger/xeger.cc index b04ebefb..34322d03 100644 --- a/tooling/pipeline/src/services/xeger/xeger.cc +++ b/tooling/pipeline/src/services/xeger/xeger.cc @@ -16,8 +16,8 @@ std::string GenerateStringFromRegex(std::string_view pattern, std::mt19937& rng, const int unbounded_repeat_cap) { - xeger_internal::Parser parser(pattern); - const xeger_internal::NodePtr ast = parser.Parse(); - xeger_internal::XegerGenerator generator(rng, unbounded_repeat_cap); - return generator.Run(*ast); + xeger_internal::Parser parser(pattern); + const xeger_internal::NodePtr ast = parser.Parse(); + xeger_internal::XegerGenerator generator(rng, unbounded_repeat_cap); + return generator.Run(*ast); } diff --git a/tooling/pipeline/src/services/xeger/xeger_generator.cc b/tooling/pipeline/src/services/xeger/xeger_generator.cc index 2fdfb578..e812d898 100644 --- a/tooling/pipeline/src/services/xeger/xeger_generator.cc +++ b/tooling/pipeline/src/services/xeger/xeger_generator.cc @@ -3,108 +3,108 @@ * @brief Visitor that walks a regex AST and emits a random matching string. */ -#include "services/xeger/xeger_internal.h" - #include #include #include #include #include +#include "services/xeger/xeger_internal.h" + namespace xeger_internal { XegerGenerator::XegerGenerator(std::mt19937& rng, const int unbounded_cap) : rng_(rng), unbounded_cap_(unbounded_cap) {} std::string XegerGenerator::Run(const Node& root) { - out_.clear(); - root.Accept(*this); - return out_; + out_.clear(); + root.Accept(*this); + return out_; } void XegerGenerator::Visit(const Literal& node) { out_ += node.ch; } void XegerGenerator::Visit(const AnyChar&) { - static constexpr int kPrintableLow = ' '; // empty space - character 32 - static constexpr int kPrintableHigh = '~'; // tilde - character 126 + static constexpr int kPrintableLow = ' '; // empty space - character 32 + static constexpr int kPrintableHigh = '~'; // tilde - character 126 - std::uniform_int_distribution dist(kPrintableLow, kPrintableHigh); + std::uniform_int_distribution dist(kPrintableLow, kPrintableHigh); - // assert that the rand number is in the char range - bool inCharRange = dist.min() >= std::numeric_limits::min() && - dist.max() <= std::numeric_limits::max(); - assert(inCharRange && "Random number is out of char range"); + // assert that the rand number is in the char range + bool inCharRange = dist.min() >= std::numeric_limits::min() && + dist.max() <= std::numeric_limits::max(); + assert(inCharRange && "Random number is out of char range"); - out_ += static_cast(dist(rng_)); + out_ += static_cast(dist(rng_)); } void XegerGenerator::Visit(const CharClass& node) { - out_ += PickFromClass(node); + out_ += PickFromClass(node); } void XegerGenerator::Visit(const Concat& node) { - for (const NodePtr& part : node.parts) { - part->Accept(*this); - } + for (const NodePtr& part : node.parts) { + part->Accept(*this); + } } void XegerGenerator::Visit(const Alternation& node) { - std::uniform_int_distribution dist(0, node.options.size() - 1); - node.options[dist(rng_)]->Accept(*this); + std::uniform_int_distribution dist(0, node.options.size() - 1); + node.options[dist(rng_)]->Accept(*this); } void XegerGenerator::Visit(const Repeat& node) { - const int lo = node.min; - const int hi = (node.max < 0) ? node.min + unbounded_cap_ : node.max; - std::uniform_int_distribution dist(lo, hi); - const int count = dist(rng_); - for (int i = 0; i < count; ++i) { - node.child->Accept(*this); - } + const int lo = node.min; + const int hi = (node.max < 0) ? node.min + unbounded_cap_ : node.max; + std::uniform_int_distribution dist(lo, hi); + const int count = dist(rng_); + for (int i = 0; i < count; ++i) { + node.child->Accept(*this); + } } bool XegerGenerator::InRanges(const CharClass& node, const char c) { - for (const auto& [lo, hi] : node.ranges) { - if (c >= lo && c <= hi) { - return true; - } - } - return false; + for (const auto& [lo, hi] : node.ranges) { + if (c >= lo && c <= hi) { + return true; + } + } + return false; } char XegerGenerator::PickFromClass(const CharClass& node) { - if (!node.negated) { - int total = 0; - for (const auto& [lo, hi] : node.ranges) { - total += hi - lo + 1; - } - std::uniform_int_distribution dist(0, total - 1); - int k = dist(rng_); - for (const auto& [lo, hi] : node.ranges) { - const int width = hi - lo + 1; - if (k < width) { - return static_cast(lo + k); + if (!node.negated) { + int total = 0; + for (const auto& [lo, hi] : node.ranges) { + total += hi - lo + 1; } - k -= width; - } - return node.ranges.back().second; // unreachable - } + std::uniform_int_distribution dist(0, total - 1); + int k = dist(rng_); + for (const auto& [lo, hi] : node.ranges) { + const int width = hi - lo + 1; + if (k < width) { + return static_cast(lo + k); + } + k -= width; + } + return node.ranges.back().second; // unreachable + } - // Negated class: draw random printable characters until one falls - // outside the forbidden set. - static constexpr int kPrintableLow = 32; - static constexpr int kPrintableHigh = 126; - static constexpr int kMaxAttempts = 1000; - std::uniform_int_distribution dist(kPrintableLow, kPrintableHigh); - for (int attempt = 0; attempt < kMaxAttempts; ++attempt) { - const char c = static_cast(dist(rng_)); - if (!InRanges(node, c)) { - return c; - } - } - throw std::runtime_error( - "GenerateStringFromRegex: negated class rules out every printable " - "character"); + // Negated class: draw random printable characters until one falls + // outside the forbidden set. + static constexpr int kPrintableLow = 32; + static constexpr int kPrintableHigh = 126; + static constexpr int kMaxAttempts = 1000; + std::uniform_int_distribution dist(kPrintableLow, kPrintableHigh); + for (int attempt = 0; attempt < kMaxAttempts; ++attempt) { + const char c = static_cast(dist(rng_)); + if (!InRanges(node, c)) { + return c; + } + } + throw std::runtime_error( + "GenerateStringFromRegex: negated class rules out every printable " + "character"); } } // namespace xeger_internal diff --git a/tooling/pipeline/src/services/xeger/xeger_parser.cc b/tooling/pipeline/src/services/xeger/xeger_parser.cc index 180b6f04..c77e626f 100644 --- a/tooling/pipeline/src/services/xeger/xeger_parser.cc +++ b/tooling/pipeline/src/services/xeger/xeger_parser.cc @@ -3,8 +3,6 @@ * @brief Recursive-descent parser that turns a regex pattern into an AST. */ -#include "services/xeger/xeger_internal.h" - #include #include #include @@ -14,31 +12,33 @@ #include #include +#include "services/xeger/xeger_internal.h" + namespace xeger_internal { Parser::Parser(std::string_view pattern) : src_(StripAnchors(pattern)) {} NodePtr Parser::Parse() { - NodePtr node = ParseAlternation(); - if (!AtEnd()) { - throw std::runtime_error( - std::format("GenerateStringFromRegex: unexpected '{}' at position {}", - Peek(), pos_)); - } - return node; + NodePtr node = ParseAlternation(); + if (!AtEnd()) { + throw std::runtime_error( + std::format("GenerateStringFromRegex: unexpected '{}' at position {}", + Peek(), pos_)); + } + return node; } std::string Parser::StripAnchors(std::string_view pattern) { - if (!pattern.empty() && pattern.front() == kAnchorStart) { - pattern.remove_prefix(1); - } - const bool has_trailing_dollar = - !pattern.empty() && pattern.back() == kAnchorEnd && - (pattern.size() < 2 || pattern[pattern.size() - 2] != kEscape); - if (has_trailing_dollar) { - pattern.remove_suffix(1); - } - return std::string(pattern); + if (!pattern.empty() && pattern.front() == kAnchorStart) { + pattern.remove_prefix(1); + } + const bool has_trailing_dollar = + !pattern.empty() && pattern.back() == kAnchorEnd && + (pattern.size() < 2 || pattern[pattern.size() - 2] != kEscape); + if (has_trailing_dollar) { + pattern.remove_suffix(1); + } + return std::string(pattern); } bool Parser::AtEnd() const { return pos_ >= src_.size(); } @@ -46,262 +46,263 @@ char Parser::Peek() const { return src_[pos_]; } char Parser::Advance() { return src_[pos_++]; } bool Parser::Match(const char c) { - if (!AtEnd() && Peek() == c) { - ++pos_; - return true; - } - return false; + if (!AtEnd() && Peek() == c) { + ++pos_; + return true; + } + return false; } void Parser::Expect(const char c) { - if (!Match(c)) { - throw std::runtime_error(std::format( - "GenerateStringFromRegex: expected '{}' at position {}", c, pos_)); - } + if (!Match(c)) { + throw std::runtime_error(std::format( + "GenerateStringFromRegex: expected '{}' at position {}", c, pos_)); + } } NodePtr Parser::ParseAlternation() { - std::vector options; - options.push_back(ParseConcat()); - while (Match(kAlternation)) { - options.push_back(ParseConcat()); - } - if (options.size() == 1) { - return std::move(options.front()); - } - auto alternation = std::make_unique(); - alternation->options = std::move(options); - return alternation; + std::vector options; + options.push_back(ParseConcat()); + while (Match(kAlternation)) { + options.push_back(ParseConcat()); + } + if (options.size() == 1) { + return std::move(options.front()); + } + auto alternation = std::make_unique(); + alternation->options = std::move(options); + return alternation; } NodePtr Parser::ParseConcat() { - auto concat = std::make_unique(); - while (!AtEnd() && Peek() != kAlternation && Peek() != kGroupClose) { - concat->parts.push_back(ParseRepeat()); - } - if (concat->parts.size() == 1) { - return std::move(concat->parts.front()); - } - return concat; + auto concat = std::make_unique(); + while (!AtEnd() && Peek() != kAlternation && Peek() != kGroupClose) { + concat->parts.push_back(ParseRepeat()); + } + if (concat->parts.size() == 1) { + return std::move(concat->parts.front()); + } + return concat; } NodePtr Parser::ParseRepeat() { - NodePtr atom = ParseAtom(); - if (AtEnd()) { - return atom; - } - - int lo = 0; - int hi = -1; - switch (Peek()) { - case kStar: - Advance(); - lo = 0; - hi = -1; - break; - case kPlus: - Advance(); - lo = 1; - hi = -1; - break; - case kOptional: - Advance(); - lo = 0; - hi = 1; - break; - case kBraceOpen: - return ParseBrace(std::move(atom)); - default: + NodePtr atom = ParseAtom(); + if (AtEnd()) { return atom; - } - auto repeat = std::make_unique(); - repeat->child = std::move(atom); - repeat->min = lo; - repeat->max = hi; - return repeat; + } + + int lo = 0; + int hi = -1; + switch (Peek()) { + case kStar: + Advance(); + lo = 0; + hi = -1; + break; + case kPlus: + Advance(); + lo = 1; + hi = -1; + break; + case kOptional: + Advance(); + lo = 0; + hi = 1; + break; + case kBraceOpen: + return ParseBrace(std::move(atom)); + default: + return atom; + } + auto repeat = std::make_unique(); + repeat->child = std::move(atom); + repeat->min = lo; + repeat->max = hi; + return repeat; } NodePtr Parser::ParseBrace(NodePtr atom) { - Expect(kBraceOpen); - std::string lower_digits; - std::string upper_digits; - bool has_comma = false; - while (!AtEnd() && std::isdigit(static_cast(Peek())) != 0) { - lower_digits += Advance(); - } - if (Match(kBraceComma)) { - has_comma = true; - while (!AtEnd() && - std::isdigit(static_cast(Peek())) != 0) { - upper_digits += Advance(); - } - } - Expect(kBraceClose); + Expect(kBraceOpen); + std::string lower_digits; + std::string upper_digits; + bool has_comma = false; + while (!AtEnd() && std::isdigit(static_cast(Peek())) != 0) { + lower_digits += Advance(); + } + if (Match(kBraceComma)) { + has_comma = true; + while (!AtEnd() && + std::isdigit(static_cast(Peek())) != 0) { + upper_digits += Advance(); + } + } + Expect(kBraceClose); - auto repeat = std::make_unique(); - repeat->child = std::move(atom); - repeat->min = lower_digits.empty() ? 0 : std::stoi(lower_digits); - if (!has_comma) { - repeat->max = repeat->min; - } else if (upper_digits.empty()) { - repeat->max = -1; - } else { - repeat->max = std::stoi(upper_digits); - } - if (repeat->max != -1 && repeat->max < repeat->min) { - throw std::runtime_error( - "GenerateStringFromRegex: repetition max is smaller than min"); - } - return repeat; + auto repeat = std::make_unique(); + repeat->child = std::move(atom); + repeat->min = lower_digits.empty() ? 0 : std::stoi(lower_digits); + if (!has_comma) { + repeat->max = repeat->min; + } else if (upper_digits.empty()) { + repeat->max = -1; + } else { + repeat->max = std::stoi(upper_digits); + } + if (repeat->max != -1 && repeat->max < repeat->min) { + throw std::runtime_error( + "GenerateStringFromRegex: repetition max is smaller than min"); + } + return repeat; } NodePtr Parser::ParseAtom() { - if (AtEnd()) { - throw std::runtime_error( - "GenerateStringFromRegex: pattern ended unexpectedly"); - } - const char c = Peek(); - switch (c) { - case kGroupOpen: { - Advance(); - // Non-capturing group "(?:...)" -- capture semantics don't apply - // here (nothing back-references a group), so it's treated exactly - // like a plain group once the "?:" marker is consumed. - if (!AtEnd() && Peek() == kNonCapturingMarker) { - if (pos_ + 1 < src_.size() && src_[pos_ + 1] == kNonCapturingColon) { - pos_ += 2; - } else { - throw std::runtime_error(std::format( - "GenerateStringFromRegex: unsupported group syntax at " - "position {} (only plain '(...)' and non-capturing " - "'(?:...)' groups are supported)", - pos_)); - } + if (AtEnd()) { + throw std::runtime_error( + "GenerateStringFromRegex: pattern ended unexpectedly"); + } + const char c = Peek(); + switch (c) { + case kGroupOpen: { + Advance(); + // Non-capturing group "(?:...)" -- capture semantics don't apply + // here (nothing back-references a group), so it's treated exactly + // like a plain group once the "?:" marker is consumed. + if (!AtEnd() && Peek() == kNonCapturingMarker) { + if (pos_ + 1 < src_.size() && + src_[pos_ + 1] == kNonCapturingColon) { + pos_ += 2; + } else { + throw std::runtime_error(std::format( + "GenerateStringFromRegex: unsupported group syntax at " + "position {} (only plain '(...)' and non-capturing " + "'(?:...)' groups are supported)", + pos_)); + } + } + NodePtr inner = ParseAlternation(); + Expect(kGroupClose); + return inner; } - NodePtr inner = ParseAlternation(); - Expect(kGroupClose); - return inner; - } - case kClassOpen: - return ParseCharClass(); - case kAnyChar: - Advance(); - return std::make_unique(); - case kEscape: - Advance(); - return ParseEscape(); - case kStar: - case kPlus: - case kOptional: - case kBraceOpen: - throw std::runtime_error(std::format( - "GenerateStringFromRegex: quantifier with no preceding item at " - "position {}", - pos_)); - default: - Advance(); - return std::make_unique(c); - } + case kClassOpen: + return ParseCharClass(); + case kAnyChar: + Advance(); + return std::make_unique(); + case kEscape: + Advance(); + return ParseEscape(); + case kStar: + case kPlus: + case kOptional: + case kBraceOpen: + throw std::runtime_error(std::format( + "GenerateStringFromRegex: quantifier with no preceding item at " + "position {}", + pos_)); + default: + Advance(); + return std::make_unique(c); + } } NodePtr Parser::MakeClass(std::vector> ranges, const bool negated) { - auto char_class = std::make_unique(); - char_class->ranges = std::move(ranges); - char_class->negated = negated; - return char_class; + auto char_class = std::make_unique(); + char_class->ranges = std::move(ranges); + char_class->negated = negated; + return char_class; } NodePtr Parser::ParseEscape() { - if (AtEnd()) { - throw std::runtime_error( - "GenerateStringFromRegex: a backslash at the very end has nothing " - "to escape"); - } - const char c = Advance(); - switch (c) { - case 'd': - return MakeClass({{'0', '9'}}, false); - case 'D': - return MakeClass({{'0', '9'}}, true); - case 'w': - return MakeClass({{'a', 'z'}, {'A', 'Z'}, {'0', '9'}, {'_', '_'}}, - false); - case 'W': - return MakeClass({{'a', 'z'}, {'A', 'Z'}, {'0', '9'}, {'_', '_'}}, - true); - case 's': - return MakeClass({{' ', ' '}, {'\t', '\t'}, {'\n', '\n'}, {'\r', '\r'}}, - false); - case 'S': - return MakeClass({{' ', ' '}, {'\t', '\t'}, {'\n', '\n'}, {'\r', '\r'}}, - true); - case 'n': - return std::make_unique('\n'); - case 't': - return std::make_unique('\t'); - case 'r': - return std::make_unique('\r'); - default: - // \. \* \( \\ etc. -- the literal character itself. - return std::make_unique(c); - } + if (AtEnd()) { + throw std::runtime_error( + "GenerateStringFromRegex: a backslash at the very end has nothing " + "to escape"); + } + const char c = Advance(); + switch (c) { + case 'd': + return MakeClass({{'0', '9'}}, false); + case 'D': + return MakeClass({{'0', '9'}}, true); + case 'w': + return MakeClass({{'a', 'z'}, {'A', 'Z'}, {'0', '9'}, {'_', '_'}}, + false); + case 'W': + return MakeClass({{'a', 'z'}, {'A', 'Z'}, {'0', '9'}, {'_', '_'}}, + true); + case 's': + return MakeClass( + {{' ', ' '}, {'\t', '\t'}, {'\n', '\n'}, {'\r', '\r'}}, false); + case 'S': + return MakeClass( + {{' ', ' '}, {'\t', '\t'}, {'\n', '\n'}, {'\r', '\r'}}, true); + case 'n': + return std::make_unique('\n'); + case 't': + return std::make_unique('\t'); + case 'r': + return std::make_unique('\r'); + default: + // \. \* \( \\ etc. -- the literal character itself. + return std::make_unique(c); + } } NodePtr Parser::ParseCharClass() { - Expect(kClassOpen); - auto char_class = std::make_unique(); - if (Match(kClassNegate)) { - char_class->negated = true; - } - if (AtEnd()) { - throw std::runtime_error( - "GenerateStringFromRegex: character class '[' is never closed"); - } + Expect(kClassOpen); + auto char_class = std::make_unique(); + if (Match(kClassNegate)) { + char_class->negated = true; + } + if (AtEnd()) { + throw std::runtime_error( + "GenerateStringFromRegex: character class '[' is never closed"); + } - while (!AtEnd() && Peek() != kClassClose) { - char lo = ClassChar(); - const bool is_range = Peek() == kClassRange && pos_ + 1 < src_.size() && - src_[pos_ + 1] != kClassClose; - if (is_range) { - Advance(); - char hi = ClassChar(); - if (hi < lo) { - std::swap(lo, hi); + while (!AtEnd() && Peek() != kClassClose) { + char lo = ClassChar(); + const bool is_range = Peek() == kClassRange && pos_ + 1 < src_.size() && + src_[pos_ + 1] != kClassClose; + if (is_range) { + Advance(); + char hi = ClassChar(); + if (hi < lo) { + std::swap(lo, hi); + } + char_class->ranges.emplace_back(lo, hi); + } else { + char_class->ranges.emplace_back(lo, lo); } - char_class->ranges.emplace_back(lo, hi); - } else { - char_class->ranges.emplace_back(lo, lo); - } - } - Expect(kClassClose); - if (char_class->ranges.empty()) { - throw std::runtime_error( - "GenerateStringFromRegex: empty character class []"); - } - return char_class; + } + Expect(kClassClose); + if (char_class->ranges.empty()) { + throw std::runtime_error( + "GenerateStringFromRegex: empty character class []"); + } + return char_class; } char Parser::ClassChar() { - const char c = Advance(); - if (c != kEscape) { - return c; - } - if (AtEnd()) { - throw std::runtime_error( - "GenerateStringFromRegex: backslash at end of character class"); - } - const char escaped = Advance(); - switch (escaped) { - case 'n': - return '\n'; - case 't': - return '\t'; - case 'r': - return '\r'; - default: - return escaped; - } + const char c = Advance(); + if (c != kEscape) { + return c; + } + if (AtEnd()) { + throw std::runtime_error( + "GenerateStringFromRegex: backslash at end of character class"); + } + const char escaped = Advance(); + switch (escaped) { + case 'n': + return '\n'; + case 't': + return '\t'; + case 'r': + return '\r'; + default: + return escaped; + } } } // namespace xeger_internal