///|
/// How events not accepted by the next positive occurrence are treated.
pub(all) enum Contiguity {
Strict
SkipNext
SkipAny
} derive(Eq, @debug.Debug)
///|
pub extend Contiguity with Eq::{equal, not_equal}
///|
pub extend Contiguity with @debug.Debug::{to_repr}
///|
/// Selection applied after a match has been emitted.
pub(all) enum AfterMatch {
NoSkip
SkipToNextStart
SkipPastLast
} derive(Eq, @debug.Debug)
///|
pub extend AfterMatch with Eq::{equal, not_equal}
///|
pub extend AfterMatch with @debug.Debug::{to_repr}
///|
/// One named positive requirement. Construction detaches condition arrays.
pub struct Stage {
name : String
condition : Condition
min : Int32
max : Int32
contiguity : Contiguity
forbidden : Condition?
}
///|
pub fn Stage::new(
name : String,
condition : Condition,
min~ : Int32 = 1,
max~ : Int32 = 1,
contiguity~ : Contiguity = SkipNext,
forbidden? : Condition,
) -> Stage raise CepError {
require_label(name, "stage.name")
if min < 0 || max < 1 || min > max || max > 32 {
raise Invalid(
path="stage.occurrences",
reason="expected 0 <= minimum <= maximum <= 32 and maximum >= 1",
)
}
validate_condition_structure(condition)
if forbidden is Some(exclusion) {
validate_condition_structure(exclusion)
}
{
name,
condition: clone_condition(condition),
min,
max,
contiguity,
forbidden: clone_optional_condition(forbidden),
}
}
///|
fn clone_optional_condition(condition : Condition?) -> Condition? {
condition.map(clone_condition)
}
///|
fn clone_stage(stage : Stage) -> Stage {
{
name: stage.name,
condition: clone_condition(stage.condition),
min: stage.min,
max: stage.max,
contiguity: stage.contiguity,
forbidden: clone_optional_condition(stage.forbidden),
}
}
///|
pub fn Stage::name(self : Stage) -> String {
self.name
}
///|
pub fn Stage::condition(self : Stage) -> Condition {
clone_condition(self.condition)
}
///|
pub fn Stage::minimum(self : Stage) -> Int32 {
self.min
}
///|
pub fn Stage::maximum(self : Stage) -> Int32 {
self.max
}
///|
pub fn Stage::contiguity(self : Stage) -> Contiguity {
self.contiguity
}
///|
pub fn Stage::forbidden(self : Stage) -> Condition? {
clone_optional_condition(self.forbidden)
}
///|
pub fn Stage::is_optional(self : Stage) -> Bool {
self.min == 0
}
///|
pub fn Stage::is_repeated(self : Stage) -> Bool {
self.max > 1
}
///|
/// A bounded pattern with a mandatory first stage and unique capture names.
pub struct Pattern {
id : String
stages : Array[Stage]
within : Int64
after_match : AfterMatch
absence : Condition?
}
///|
pub fn Pattern::new(
id : String,
stages : Array[Stage],
within : Int64,
after_match~ : AfterMatch = NoSkip,
absence? : Condition,
) -> Pattern raise CepError {
require_label(id, "pattern.id")
if within < 1L || within > 31536000000L {
raise Invalid(
path="pattern.within",
reason="expected 1..31536000000 event-time units",
)
}
if stages.is_empty() {
raise Invalid(
path="pattern.stages",
reason="at least one stage is required",
)
}
if stages.length() > 32 {
raise Resource(resource="pattern stages", limit=32)
}
if stages[0].min == 0 {
raise Invalid(
path="pattern.stages[0]",
reason="the first stage must consume at least one event",
)
}
let previous : Array[String] = []
let detached : Array[Stage] = []
let mut total = 0
for index, stage in stages {
if previous.contains(stage.name) {
raise Invalid(
path="pattern.stages[\{index}].name",
reason="duplicate capture name \{stage.name}",
)
}
stage.condition.validate(previous)
if stage.forbidden is Some(forbidden) {
forbidden.validate(previous)
}
total += stage.max
if total > 128 {
raise Resource(resource="pattern maximum captures", limit=128)
}
detached.push(clone_stage(stage))
previous.push(stage.name)
}
if absence is Some(condition) {
condition.validate(previous)
}
{
id,
stages: detached,
within,
after_match,
absence: clone_optional_condition(absence),
}
}
///|
fn clone_pattern(pattern : Pattern) -> Pattern {
{
id: pattern.id,
stages: pattern.stages.map(clone_stage),
within: pattern.within,
after_match: pattern.after_match,
absence: clone_optional_condition(pattern.absence),
}
}
///|
pub fn Pattern::id(self : Pattern) -> String {
self.id
}
///|
pub fn Pattern::stages(self : Pattern) -> Array[Stage] {
self.stages.map(clone_stage)
}
///|
pub fn Pattern::stage(self : Pattern, name : String) -> Stage? {
for stage in self.stages {
if stage.name == name {
return Some(clone_stage(stage))
}
}
None
}
///|
pub fn Pattern::stage_at(self : Pattern, index : Int32) -> Stage? {
if index < 0 || index >= self.stages.length() {
None
} else {
Some(clone_stage(self.stages[index]))
}
}
///|
pub fn Pattern::stage_names(self : Pattern) -> Array[String] {
self.stages.map(stage => stage.name)
}
///|
pub fn Pattern::within(self : Pattern) -> Int64 {
self.within
}
///|
pub fn Pattern::after_match(self : Pattern) -> AfterMatch {
self.after_match
}
///|
pub fn Pattern::absence(self : Pattern) -> Condition? {
clone_optional_condition(self.absence)
}
///|
/// Structural classification, not a guarantee on runtime candidate count.
pub(all) enum BranchingRisk {
FixedSequence
BoundedRepetition
CombinatorialSelection
} derive(Eq, @debug.Debug)
///|
pub extend BranchingRisk with Eq::{equal, not_equal}
///|
pub extend BranchingRisk with @debug.Debug::{to_repr}
///|
pub(all) struct PatternAnalysis {
minimum_captures : Int32
maximum_captures : Int32
nullable_stages : Int32
variable_stages : Int32
repeated_stages : Int32
strict_stages : Int32
skip_any_stages : Int32
guarded_stages : Int32
has_absence : Bool
branching_risk : BranchingRisk
} derive(Eq, @debug.Debug)
///|
pub extend PatternAnalysis with Eq::{equal, not_equal}
///|
pub extend PatternAnalysis with @debug.Debug::{to_repr}
///|
pub fn Pattern::analysis(self : Pattern) -> PatternAnalysis {
let mut minimum_captures = 0
let mut maximum_captures = 0
let mut nullable_stages = 0
let mut variable_stages = 0
let mut repeated_stages = 0
let mut strict_stages = 0
let mut skip_any_stages = 0
let mut guarded_stages = 0
for stage in self.stages {
minimum_captures += stage.min
maximum_captures += stage.max
if stage.min == 0 {
nullable_stages += 1
}
if stage.min != stage.max {
variable_stages += 1
}
if stage.max > 1 {
repeated_stages += 1
}
if stage.contiguity == Strict {
strict_stages += 1
}
if stage.contiguity == SkipAny {
skip_any_stages += 1
}
if stage.forbidden is Some(_) {
guarded_stages += 1
}
}
let branching_risk = if skip_any_stages > 0 || variable_stages > 1 {
CombinatorialSelection
} else if repeated_stages > 0 || nullable_stages > 0 {
BoundedRepetition
} else {
FixedSequence
}
{
minimum_captures,
maximum_captures,
nullable_stages,
variable_stages,
repeated_stages,
strict_stages,
skip_any_stages,
guarded_stages,
has_absence: self.absence is Some(_),
branching_risk,
}
}