use crate::offsets::utf16_index;
use crate::registry::{FormatFn, Registry, StepRegistration};
use crate::value::Value;
use regex::Regex;
use std::rc::Rc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParamSpan {
pub start: usize,
pub end: usize,
}
#[derive(Clone)]
pub struct Hit {
pub expression: String,
pub step_def: Rc<StepRegistration>,
pub match_start: usize,
pub match_end: usize,
pub args: Vec<Value>,
pub param_spans: Vec<ParamSpan>,
pub formats: Vec<Option<FormatFn>>,
}
#[derive(Clone)]
pub struct AmbiguityCollision {
pub match_start: usize,
pub match_end: usize,
pub candidates: Vec<Hit>,
}
pub enum ResolvedSteps {
Ok(Vec<Hit>),
Ambiguous(Vec<AmbiguityCollision>),
}
pub fn find_hits(sentence: &str, registry: &Registry) -> Vec<Hit> {
let mut hits = Vec::new();
for step in ®istry.steps {
let Ok(unanchored) = Regex::new(&strip_anchors(step.compiled.regexp_source())) else {
continue;
};
for m in unanchored.find_iter(sentence) {
let matched_text = &sentence[m.start()..m.end()];
let arguments = step.compiled.match_whole(matched_text).unwrap_or_default();
let mut args = Vec::with_capacity(arguments.len());
let mut param_spans = Vec::new();
let mut formats = Vec::with_capacity(arguments.len());
for arg in arguments {
formats.push(registry.formats.get(&arg.parameter_type_name).cloned());
if let Some((gs, ge)) = arg.group {
param_spans.push(ParamSpan {
start: utf16_index(sentence, m.start() + gs),
end: utf16_index(sentence, m.start() + ge),
});
}
args.push(arg.value);
}
hits.push(Hit {
expression: step.expression.clone(),
step_def: step.clone(),
match_start: utf16_index(sentence, m.start()),
match_end: utf16_index(sentence, m.end()),
args,
param_spans,
formats,
});
}
}
hits
}
fn strip_anchors(source: &str) -> String {
let s = source.strip_prefix('^').unwrap_or(source);
let s = s.strip_suffix('$').unwrap_or(s);
s.to_string()
}
pub fn resolve_hits(hits: Vec<Hit>) -> ResolvedSteps {
if hits.is_empty() {
return ResolvedSteps::Ok(Vec::new());
}
let mut sorted = hits;
sorted.sort_by(|a, b| {
a.match_start
.cmp(&b.match_start)
.then_with(|| (b.match_end - b.match_start).cmp(&(a.match_end - a.match_start)))
});
let mut collisions = Vec::new();
let mut i = 0;
while i < sorted.len() {
let here_start = sorted[i].match_start;
let here_len = sorted[i].match_end - sorted[i].match_start;
let mut j = i + 1;
while j < sorted.len()
&& sorted[j].match_start == here_start
&& sorted[j].match_end - sorted[j].match_start == here_len
{
j += 1;
}
if j - i > 1 {
collisions.push(AmbiguityCollision {
match_start: here_start,
match_end: sorted[i].match_end,
candidates: sorted[i..j].to_vec(),
});
}
i = j;
}
if !collisions.is_empty() {
return ResolvedSteps::Ambiguous(collisions);
}
let mut steps = Vec::new();
let mut cursor: isize = -1;
for hit in sorted {
if (hit.match_start as isize) < cursor {
continue;
}
cursor = hit.match_end as isize;
steps.push(hit);
}
ResolvedSteps::Ok(steps)
}