1use crate::offsets::utf16_index;
7use crate::registry::{FormatFn, Registry, StepRegistration};
8use crate::value::Value;
9use regex::Regex;
10use std::rc::Rc;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct ParamSpan {
15 pub start: usize,
16 pub end: usize,
17}
18
19#[derive(Clone)]
22pub struct Hit {
23 pub expression: String,
24 pub step_def: Rc<StepRegistration>,
25 pub match_start: usize,
26 pub match_end: usize,
27 pub args: Vec<Value>,
28 pub param_spans: Vec<ParamSpan>,
29 pub formats: Vec<Option<FormatFn>>,
30}
31
32#[derive(Clone)]
34pub struct AmbiguityCollision {
35 pub match_start: usize,
36 pub match_end: usize,
37 pub candidates: Vec<Hit>,
38}
39
40pub enum ResolvedSteps {
42 Ok(Vec<Hit>),
44 Ambiguous(Vec<AmbiguityCollision>),
46}
47
48pub fn find_hits(sentence: &str, registry: &Registry) -> Vec<Hit> {
52 let mut hits = Vec::new();
53 for step in ®istry.steps {
54 let Ok(unanchored) = Regex::new(&strip_anchors(step.compiled.regexp_source())) else {
55 continue;
56 };
57 for m in unanchored.find_iter(sentence) {
58 let matched_text = &sentence[m.start()..m.end()];
59 let arguments = step.compiled.match_whole(matched_text).unwrap_or_default();
60
61 let mut args = Vec::with_capacity(arguments.len());
62 let mut param_spans = Vec::new();
63 let mut formats = Vec::with_capacity(arguments.len());
64 for arg in arguments {
65 formats.push(registry.formats.get(&arg.parameter_type_name).cloned());
66 if let Some((gs, ge)) = arg.group {
67 param_spans.push(ParamSpan {
68 start: utf16_index(sentence, m.start() + gs),
69 end: utf16_index(sentence, m.start() + ge),
70 });
71 }
72 args.push(arg.value);
73 }
74
75 hits.push(Hit {
76 expression: step.expression.clone(),
77 step_def: step.clone(),
78 match_start: utf16_index(sentence, m.start()),
79 match_end: utf16_index(sentence, m.end()),
80 args,
81 param_spans,
82 formats,
83 });
84 }
85 }
86 hits
87}
88
89fn strip_anchors(source: &str) -> String {
92 let s = source.strip_prefix('^').unwrap_or(source);
93 let s = s.strip_suffix('$').unwrap_or(s);
94 s.to_string()
95}
96
97pub fn resolve_hits(hits: Vec<Hit>) -> ResolvedSteps {
100 if hits.is_empty() {
101 return ResolvedSteps::Ok(Vec::new());
102 }
103 let mut sorted = hits;
104 sorted.sort_by(|a, b| {
106 a.match_start
107 .cmp(&b.match_start)
108 .then_with(|| (b.match_end - b.match_start).cmp(&(a.match_end - a.match_start)))
109 });
110
111 let mut collisions = Vec::new();
112 let mut i = 0;
113 while i < sorted.len() {
114 let here_start = sorted[i].match_start;
115 let here_len = sorted[i].match_end - sorted[i].match_start;
116 let mut j = i + 1;
117 while j < sorted.len()
118 && sorted[j].match_start == here_start
119 && sorted[j].match_end - sorted[j].match_start == here_len
120 {
121 j += 1;
122 }
123 if j - i > 1 {
124 collisions.push(AmbiguityCollision {
125 match_start: here_start,
126 match_end: sorted[i].match_end,
127 candidates: sorted[i..j].to_vec(),
128 });
129 }
130 i = j;
131 }
132 if !collisions.is_empty() {
133 return ResolvedSteps::Ambiguous(collisions);
134 }
135
136 let mut steps = Vec::new();
137 let mut cursor: isize = -1;
138 for hit in sorted {
139 if (hit.match_start as isize) < cursor {
140 continue;
141 }
142 cursor = hit.match_end as isize;
143 steps.push(hit);
144 }
145 ResolvedSteps::Ok(steps)
146}