Module: Oselvar::Var::Core::Matcher
- Defined in:
- lib/oselvar/var/core/matcher.rb
Overview
Cucumber-expression matching. Port of matcher.ts. cucumber-expressions' regexps are anchored (^...$) and its group offsets are code-point based, so we strip anchors for substring search and convert offsets to UTF-16.
Class Method Summary collapse
-
.find_hits(sentence, registry) ⇒ Object
Every expression match found anywhere in
sentence. -
.resolve_hits(hits) ⇒ Object
Select the best non-overlapping hits, or report ambiguities.
-
.unanchored_pattern(step) ⇒ Object
A compiled, un-anchored pattern from the step's CucumberExpression.
Class Method Details
.find_hits(sentence, registry) ⇒ Object
Every expression match found anywhere in sentence.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/oselvar/var/core/matcher.rb', line 41 def find_hits(sentence, registry) hits = [] registry.steps.each do |step| pattern = unanchored_pattern(step) pos = 0 while pos <= sentence.length m = pattern.match(sentence, pos) break if m.nil? matched_text = m[0] arguments = step.compiled.match(matched_text) || [] args = arguments.map { |arg| arg.value(nil) } formats = arguments.map { |arg| registry.formats[arg.parameter_type.name] } # group.start/.end are code-point offsets within matched_text; add # m.begin(0) for the sentence-absolute code-point index, then to UTF-16. param_spans = arguments.filter_map do |arg| g = arg.group next unless g.start.is_a?(Integer) && g.end.is_a?(Integer) ParamSpan.new( start: Offsets.to_utf16_offset(sentence, m.begin(0) + g.start), end: Offsets.to_utf16_offset(sentence, m.begin(0) + g.end) ) end hits << Hit.new( expression: step.expression, step_def: step, match_start: Offsets.to_utf16_offset(sentence, m.begin(0)), match_end: Offsets.to_utf16_offset(sentence, m.end(0)), args: args, param_spans: param_spans, formats: formats ) pos = matched_text.empty? ? m.begin(0) + 1 : m.end(0) end end hits end |
.resolve_hits(hits) ⇒ Object
Select the best non-overlapping hits, or report ambiguities.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/oselvar/var/core/matcher.rb', line 84 def resolve_hits(hits) return ResolvedSteps.new(kind: 'ok') if hits.empty? # Stable sort by (match_start asc, length desc); the original index # breaks ties so equal-key order follows registration order (Ruby's # sort_by is not stable, Python's sorted is). sorted = hits.each_with_index.sort_by do |h, i| [h.match_start, -(h.match_end - h.match_start), i] end.map(&:first) collisions = [] i = 0 while i < sorted.length here = sorted[i] here_len = here.match_end - here.match_start tied = [here] j = i + 1 while j < sorted.length candidate = sorted[j] if candidate.match_start == here.match_start && candidate.match_end - candidate.match_start == here_len tied << candidate j += 1 else break end end if tied.length > 1 collisions << AmbiguityCollision.new( match_start: here.match_start, match_end: here.match_end, candidates: tied ) end i = j end return ResolvedSteps.new(kind: 'ambiguous', collisions: collisions) unless collisions.empty? steps = [] cursor = -1 sorted.each do |hit| next if hit.match_start < cursor steps << hit cursor = hit.match_end end ResolvedSteps.new(kind: 'ok', steps: steps) end |
.unanchored_pattern(step) ⇒ Object
A compiled, un-anchored pattern from the step's CucumberExpression.
32 33 34 35 36 37 38 |
# File 'lib/oselvar/var/core/matcher.rb', line 32 def unanchored_pattern(step) regexp = step.compiled.instance_variable_get(:@tree_regexp).regexp source = regexp.source source = source[1..] if source.start_with?('^') source = source[0...-1] if source.end_with?('$') Regexp.new(source, regexp.) end |