///|
/// 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 = arg_group.values().map(v => v.unwrap_or(""))
(entry.transformer.call(values), entry.type_)
}
(None, Some(_)) =>
(AnonymousVal(arg_group.values()[0].unwrap_or("")), Anonymous)
}
params.push({ value, type_, raw, group: arg_group, })
}
Some({ params, })
}