///|
/// A step pattern written as a regular expression, for example
/// `^I have (\d+) cukes$`.
///
/// Each top-level capture group gives one parameter. The registry finds the
/// parameter type from the source of the group, for example `\d+` gives
/// `{int}`. A group with no registered type gives an `AnonymousVal`, and a
/// group that did not match gives `NullVal`.
pub(all) struct RegularExpression {
priv tree_regexp : TreeRegexp
priv registry : ParamTypeRegistry
}
///|
/// Compile a regular expression. Raises an error when the regex is not
/// valid.
pub fn RegularExpression::new(
regexp : String,
registry? : ParamTypeRegistry = ParamTypeRegistry::default(),
) -> RegularExpression raise ExpressionError {
let tree_regexp = TreeRegexp::new(regexp, on_error=detail => {
ValidationError(
position=0,
message="Invalid regular expression /\{regexp}/: \{detail}",
)
})
{ tree_regexp, registry, }
}
///|
/// The source of the regular expression.
pub fn RegularExpression::source(self : RegularExpression) -> String {
self.tree_regexp.source
}
///|
/// The regex. This is the same as `source`.
pub fn RegularExpression::regexp(self : RegularExpression) -> String {
self.tree_regexp.source
}
///|
/// Match the regular expression against a text.
///
/// Returns `None` if the text does not match. Raises an
/// `AmbiguousParameterTypeError` when a group matches more than one
/// parameter type and none of them is preferential, and the error of a
/// transformer.
pub fn RegularExpression::match_(
self : RegularExpression,
text : String,
) -> Match? raise {
guard self.tree_regexp.match_(text) is Some(group) else { return None }
let builders = self.tree_regexp.group_builder.children
let params : Array[Param] = []
for i, builder in builders {
let entry = self.registry.lookup_by_regexp(
builder.source,
self.tree_regexp.source,
text,
)
let arg_group = group.children[i]
let raw = arg_group.value.unwrap_or("")
let (value, type_) = match (entry, arg_group.value) {
(_, None) => (NullVal, entry.map(e => e.type_).unwrap_or(Anonymous))
(Some(entry), Some(_)) => {
let values = typed_values(entry, builder.source, arg_group.values())
(entry.transformer.call_with_missing(values), entry.type_)
}
(None, Some(_)) =>
(AnonymousVal(arg_group.values()[0].unwrap_or("")), Anonymous)
}
params.push({ value, type_, raw, group: arg_group, })
}
Some({ params, })
}
///|
/// The values for the transformer of `entry`, when the group has the
/// regexp `source`.
///
/// A typed parameter type decodes the groups of all its regexps. The group
/// has only the groups of one regexp, so put them at the offset of that
/// regexp, and give `None` for the groups of the other regexps. Other
/// transformers get `values` unchanged.
fn typed_values(
entry : ParamTypeEntry,
source : String,
values : Array[String?],
) -> Array[String?] {
guard entry.transformer.with_missing is Some(_) && entry.patterns.length() > 1 else {
return values
}
let counts = entry.patterns.map(p => {
match create_group_builder(p.to_string()) {
Some(builder) => builder.children.length()
None => 0
}
})
let total = counts.fold(init=0, (a, b) => a + b)
guard total > 0 else { return values }
let result : Array[String?] = Array::make(total, None)
let mut offset = 0
for i, pattern in entry.patterns {
if pattern.to_string() == source {
for j in 0..