Module: Lich::Gemstone::Combat::Definitions::PatternGate
- Defined in:
- documented/gemstone/combat/defs/pattern_gate.rb
Constant Summary collapse
- MIN_LITERAL =
Build a gate for a list of patterns. Returns [union_regex, always_scan] where union_regex matches iff some pattern's literal is present, and always_scan lists patterns whose literal was too short to be a useful gate (they must be tried on every line).
4
Class Method Summary collapse
-
.build(patterns) ⇒ Array
private
Builds a gate for a list of patterns that pre-filters lines before expensive regex matching.
-
.longest_literal(regex) ⇒ Object
Longest guaranteed-literal run in a regex source, or nil when no safe literal exists.
-
.rejects?(gate, always_scan, line) ⇒ Boolean
Convenience: true when the line can't possibly match any gated pattern (no literal present and no ungated patterns exist).
Class Method Details
.build(patterns) ⇒ Array
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Builds a gate for a list of patterns that pre-filters lines before expensive regex matching.
Extracts the longest literal substring from each pattern using longest_literal. Patterns whose literal is at least MIN_LITERAL (4) characters long are added to a Regexp.union gate; shorter or literal-free patterns are returned in the always_scan list and must be tried on every line.
71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'documented/gemstone/combat/defs/pattern_gate.rb', line 71 def build(patterns) literals = [] always_scan = [] patterns.each do |pattern| literal = longest_literal(pattern) if literal && literal.length >= MIN_LITERAL literals << literal else always_scan << pattern end end [literals.empty? ? nil : Regexp.union(literals.uniq).freeze, always_scan.freeze] end |
.longest_literal(regex) ⇒ Object
Longest guaranteed-literal run in a regex source, or nil when no
safe literal exists. Character classes, escapes and then entire
parenthesized groups (innermost-out, so nesting works) are removed
wholesale - text inside a group may be optional or one alternation
branch, so it is never guaranteed. What survives is top-level text
that every match must contain; the longest metachar-free fragment
of it is the gate literal. A source with a top-level | is a pure
alternation with no guaranteed text - returns nil (always scan).
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'documented/gemstone/combat/defs/pattern_gate.rb', line 33 def longest_literal(regex) source = regex.source.dup source.gsub!(/\\[A-Za-z]/, "\x00") # escape sequences (\d, \w, \b...) source.gsub!(/\[[^\]]*\]/, "\x00") # character classes # Remove groups innermost-first so nested groups collapse cleanly nil while source.gsub!(/\((?:\?(?:<[a-zA-Z_]+>|:|=|!))?[^()]*\)/, "\x00") return nil if source.include?('|') # top-level alternation fragments = source.split(/[\\(){}?*+.^$\x00]/) # A fragment followed by ? or * in the original is optional; the # split above already breaks on those metachars, but the char # BEFORE ? belongs to the fragment - trim it to stay conservative. longest = fragments.max_by(&:length).to_s longest = longest[0..-2] if source =~ /#{Regexp.escape(longest)}[?*]/ longest.empty? ? nil : longest end |
.rejects?(gate, always_scan, line) ⇒ Boolean
Convenience: true when the line can't possibly match any gated pattern (no literal present and no ungated patterns exist).
87 88 89 |
# File 'documented/gemstone/combat/defs/pattern_gate.rb', line 87 def rejects?(gate, always_scan, line) always_scan.empty? && (gate.nil? || !gate.match?(line)) end |