use super::*;
use alloc::borrow::ToOwned;
use alloc::collections::{BTreeMap, BTreeSet};
type Result<T> = core::result::Result<T, CompileError>;
enum SingleDirection {
Forward,
Reverse,
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub(crate) struct SpecialConstructCounts {
pub(crate) num_compounds: usize,
pub(crate) num_quantifiers_opt: usize,
pub(crate) num_quantifiers_kleene: usize,
pub(crate) num_quantifiers_kleene_plus: usize,
pub(crate) num_segments: usize,
pub(crate) num_unicode_sets: usize,
pub(crate) num_function_calls: usize,
pub(crate) max_left_placeholders: u32,
pub(crate) max_right_placeholders: u32,
pub(crate) max_backref_num: u32,
}
impl SpecialConstructCounts {
pub(crate) fn num_total(&self) -> usize {
self.num_compounds
+ self.num_quantifiers_opt
+ self.num_quantifiers_kleene
+ self.num_quantifiers_kleene_plus
+ self.num_segments
+ self.num_unicode_sets
+ self.num_function_calls
+ self.max_left_placeholders as usize
+ self.max_right_placeholders as usize
+ self.max_backref_num as usize
}
fn combine(&mut self, other: Self) {
let Self {
num_compounds,
num_quantifiers_opt,
num_quantifiers_kleene,
num_quantifiers_kleene_plus,
num_segments,
num_unicode_sets,
num_function_calls,
max_left_placeholders,
max_right_placeholders,
max_backref_num,
} = other;
self.num_compounds += num_compounds;
self.num_quantifiers_opt += num_quantifiers_opt;
self.num_quantifiers_kleene += num_quantifiers_kleene;
self.num_quantifiers_kleene_plus += num_quantifiers_kleene_plus;
self.num_segments += num_segments;
self.num_unicode_sets += num_unicode_sets;
self.num_function_calls += num_function_calls;
self.max_left_placeholders = self.max_left_placeholders.max(max_left_placeholders);
self.max_right_placeholders = self.max_right_placeholders.max(max_right_placeholders);
self.max_backref_num = self.max_backref_num.max(max_backref_num);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct Pass1Data {
pub(crate) counts: SpecialConstructCounts,
pub(crate) used_variables: BTreeSet<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct DirectedPass1Result<'p> {
pub(crate) data: Pass1Data,
pub(crate) groups: rule_group_agg::RuleGroups<'p>,
pub(crate) filter: Option<parse::FilterSet>,
}
#[derive(Debug, Clone)]
pub(crate) struct Pass1Result<'p> {
pub(crate) forward_result: DirectedPass1Result<'p>,
pub(crate) reverse_result: DirectedPass1Result<'p>,
pub(crate) variable_definitions: BTreeMap<String, &'p [parse::Element]>,
}
#[derive(Debug, Clone)]
pub(crate) struct Pass1<'p> {
direction: Direction,
forward_data: Pass1Data,
reverse_data: Pass1Data,
variable_data: BTreeMap<String, Pass1Data>,
forward_filter: Option<parse::FilterSet>,
reverse_filter: Option<parse::FilterSet>,
forward_rule_group_agg: rule_group_agg::ForwardRuleGroupAggregator<'p>,
reverse_rule_group_agg: rule_group_agg::ReverseRuleGroupAggregator<'p>,
variable_definitions: BTreeMap<String, &'p [parse::Element]>,
target_disallowed_variables: BTreeSet<String>,
}
impl<'p> Pass1<'p> {
pub(super) fn run(direction: Direction, rules: &'p [parse::Rule]) -> Result<Pass1Result<'p>> {
let mut s = Self {
direction,
forward_data: Pass1Data::default(),
reverse_data: Pass1Data::default(),
variable_data: BTreeMap::new(),
variable_definitions: BTreeMap::new(),
target_disallowed_variables: BTreeSet::new(),
forward_filter: None,
reverse_filter: None,
forward_rule_group_agg: rule_group_agg::ForwardRuleGroupAggregator::new(),
reverse_rule_group_agg: rule_group_agg::ReverseRuleGroupAggregator::new(),
};
let rules = s.validate_global_filters(rules);
for rule in rules {
s.forward_rule_group_agg.push(rule);
s.reverse_rule_group_agg.push(rule);
match rule {
parse::Rule::GlobalFilter(_) | parse::Rule::GlobalInverseFilter(_) => {
return Err(CompileErrorKind::UnexpectedGlobalFilter.into());
}
parse::Rule::Transform(..) => {}
parse::Rule::VariableDefinition(name, definition) => {
s.validate_variable_definition(name, definition)?;
}
parse::Rule::Conversion(hr1, dir, hr2) => {
s.validate_conversion(hr1, *dir, hr2)?;
}
}
}
Pass1ResultGenerator::generate(s)
}
fn validate_global_filters<'a>(&mut self, rules: &'a [parse::Rule]) -> &'a [parse::Rule] {
let rules = match rules {
[parse::Rule::GlobalFilter(filter), rest @ ..] => {
self.forward_filter = Some(filter.clone());
rest
}
_ => rules,
};
match rules {
[rest @ .., parse::Rule::GlobalInverseFilter(filter)] => {
self.reverse_filter = Some(filter.clone());
rest
}
_ => rules,
}
}
fn validate_variable_definition(
&mut self,
name: &String,
definition: &'p [parse::Element],
) -> Result<()> {
if self.variable_definitions.contains_key(name) {
return Err(CompileErrorKind::DuplicateVariable.into());
}
self.variable_definitions.insert(name.clone(), definition);
let mut data = Pass1Data::default();
data.counts.num_compounds = 1;
let mut validator = VariableDefinitionValidator::new(
|s| self.variable_definitions.contains_key(s),
&mut data,
&self.target_disallowed_variables,
definition,
);
validator.validate()?;
if validator.used_target_disallowed_construct {
self.target_disallowed_variables.insert(name.clone());
}
self.variable_data.insert(name.clone(), data);
Ok(())
}
fn validate_conversion(
&mut self,
source: &parse::HalfRule,
dir: Direction,
target: &parse::HalfRule,
) -> Result<()> {
#[cfg(feature = "datagen")]
if dir == Direction::Forward && (!target.ante.is_empty() || !target.post.is_empty()) {
log::warn!("forward conversion rule has ignored context on target side");
}
#[cfg(feature = "datagen")]
if dir == Direction::Reverse && (!source.ante.is_empty() || !source.post.is_empty()) {
log::warn!("reverse conversion rule has ignored context on target side");
}
if self.direction.permits(Direction::Forward) && dir.permits(Direction::Forward) {
self.validate_conversion_one_direction(source, target, SingleDirection::Forward)?;
}
if self.direction.permits(Direction::Reverse) && dir.permits(Direction::Reverse) {
self.validate_conversion_one_direction(target, source, SingleDirection::Reverse)?;
}
Ok(())
}
fn validate_conversion_one_direction(
&mut self,
source: &parse::HalfRule,
target: &parse::HalfRule,
dir: SingleDirection,
) -> Result<()> {
let data = match dir {
SingleDirection::Forward => &mut self.forward_data,
SingleDirection::Reverse => &mut self.reverse_data,
};
let mut source_validator = SourceValidator::new(
|s| self.variable_definitions.contains_key(s),
data,
&source.ante,
&source.key,
&source.post,
);
source_validator.validate()?;
let num_source_segments = source_validator.num_segments;
let mut target_validator = TargetValidator::new(
|s| self.variable_definitions.contains_key(s),
&mut self.target_disallowed_variables,
data,
&target.key,
num_source_segments,
);
target_validator.validate()?;
Ok(())
}
}
struct SourceValidator<'a, 'p, F: Fn(&str) -> bool> {
is_variable_defined: F,
data: &'a mut Pass1Data,
ante: &'p [parse::Element],
key: &'p [parse::Element],
post: &'p [parse::Element],
num_segments: u32,
}
impl<'a, 'p, F: Fn(&str) -> bool> SourceValidator<'a, 'p, F> {
fn new(
is_variable_defined: F,
data: &'a mut Pass1Data,
ante: &'p [parse::Element],
key: &'p [parse::Element],
post: &'p [parse::Element],
) -> Self {
Self {
is_variable_defined,
data,
ante,
key,
post,
num_segments: 0,
}
}
fn validate(&mut self) -> Result<()> {
let sections = [self.ante, self.key, self.post];
let sections = match sections {
[[parse::Element::AnchorStart, ante @ ..], key, post] => [ante, key, post],
[[], [parse::Element::AnchorStart, key @ ..], post] => [&[], key, post],
_ => sections,
};
let sections = match sections {
[ante, key, [post @ .., parse::Element::AnchorEnd]] => [ante, key, post],
[ante, [key @ .., parse::Element::AnchorEnd], []] => [ante, key, &[]],
_ => sections,
};
sections
.iter()
.try_for_each(|s| self.validate_section(s, true))
}
fn validate_section(&mut self, section: &[parse::Element], top_level: bool) -> Result<()> {
section
.iter()
.try_for_each(|element| self.validate_element(element, top_level))
}
fn validate_element(&mut self, element: &parse::Element, top_level: bool) -> Result<()> {
match element {
parse::Element::Literal(_) => {}
parse::Element::VariableRef(name) => {
if !(self.is_variable_defined)(name) {
return Err(CompileErrorKind::UnknownVariable.into());
}
self.data.used_variables.insert(name.clone());
}
parse::Element::Quantifier(kind, inner) => {
self.validate_element(inner, false)?;
match *kind {
parse::QuantifierKind::ZeroOrOne => self.data.counts.num_quantifiers_opt += 1,
parse::QuantifierKind::ZeroOrMore => {
self.data.counts.num_quantifiers_kleene += 1
}
parse::QuantifierKind::OneOrMore => {
self.data.counts.num_quantifiers_kleene_plus += 1
}
}
}
parse::Element::Segment(inner) => {
self.validate_section(inner, false)?;
self.num_segments += 1;
self.data.counts.num_segments += 1;
}
parse::Element::UnicodeSet(_) => {
self.data.counts.num_unicode_sets += 1;
}
parse::Element::Cursor(_, _) => {
if !top_level {
return Err(CompileErrorKind::InvalidCursor.into());
}
}
parse::Element::AnchorStart => {
return Err(CompileErrorKind::AnchorStartNotAtStart.into());
}
parse::Element::AnchorEnd => {
return Err(CompileErrorKind::AnchorEndNotAtEnd.into());
}
elt => {
return Err(
CompileErrorKind::UnexpectedElementInSource(elt.kind().debug_str()).into(),
);
}
}
Ok(())
}
}
struct TargetValidator<'a, 'p, F: Fn(&str) -> bool> {
is_variable_defined: F,
target_disallowed_variables: &'a mut BTreeSet<String>,
data: &'a mut Pass1Data,
replacer: &'p [parse::Element],
num_segments: u32,
encountered_cursor: bool,
}
impl<'a, 'p, F: Fn(&str) -> bool> TargetValidator<'a, 'p, F> {
fn new(
is_variable_defined: F,
target_disallowed_variables: &'a mut BTreeSet<String>,
data: &'a mut Pass1Data,
replacer: &'p [parse::Element],
num_segments: u32,
) -> Self {
Self {
is_variable_defined,
target_disallowed_variables,
data,
replacer,
num_segments,
encountered_cursor: false,
}
}
fn validate(&mut self) -> Result<()> {
let section = self.replacer;
let section = match section {
[parse::Element::Cursor(pre, post)] => {
self.encounter_cursor()?;
if *pre != 0 && *post != 0 {
return Err(CompileErrorKind::InvalidCursor.into());
}
self.update_cursor_data(*pre, *post);
return Ok(());
}
_ => section,
};
let section = match section {
[parse::Element::Cursor(pre, post), rest @ ..] => {
self.encounter_cursor()?;
if *pre != 0 {
return Err(CompileErrorKind::InvalidCursor.into());
}
self.update_cursor_data(*pre, *post);
rest
}
_ => section,
};
let section = match section {
[rest @ .., parse::Element::Cursor(pre, post)] => {
self.encounter_cursor()?;
if *post != 0 {
return Err(CompileErrorKind::InvalidCursor.into());
}
self.update_cursor_data(*pre, *post);
rest
}
_ => section,
};
self.validate_section(section, true)
}
fn validate_section(&mut self, section: &[parse::Element], top_level: bool) -> Result<()> {
section
.iter()
.try_for_each(|element| self.validate_element(element, top_level))
}
fn validate_element(&mut self, element: &parse::Element, top_level: bool) -> Result<()> {
match element {
parse::Element::Literal(_) => {}
parse::Element::VariableRef(name) => {
if !(self.is_variable_defined)(name) {
return Err(CompileErrorKind::UnknownVariable.into());
}
if self.target_disallowed_variables.contains(name) {
return Err(CompileErrorKind::SourceOnlyVariable.into());
}
self.data.used_variables.insert(name.clone());
}
parse::Element::BackRef(num) => {
if *num > self.num_segments {
return Err(CompileErrorKind::BackReferenceOutOfRange.into());
}
self.data.counts.max_backref_num = self.data.counts.max_backref_num.max(*num);
}
parse::Element::FunctionCall(_, inner) => {
self.validate_section(inner, false)?;
self.data.counts.num_function_calls += 1;
}
&parse::Element::Cursor(pre, post) => {
self.encounter_cursor()?;
if !top_level || pre != 0 || post != 0 {
return Err(CompileErrorKind::InvalidCursor.into());
}
}
parse::Element::AnchorStart => {
}
parse::Element::AnchorEnd => {
}
elt => {
return Err(
CompileErrorKind::UnexpectedElementInTarget(elt.kind().debug_str()).into(),
);
}
}
Ok(())
}
fn encounter_cursor(&mut self) -> Result<()> {
if self.encountered_cursor {
return Err(CompileErrorKind::DuplicateCursor.into());
}
self.encountered_cursor = true;
Ok(())
}
fn update_cursor_data(&mut self, left: u32, right: u32) {
self.data.counts.max_left_placeholders = self.data.counts.max_left_placeholders.max(left);
self.data.counts.max_right_placeholders =
self.data.counts.max_right_placeholders.max(right);
}
}
struct VariableDefinitionValidator<'a, 'p, F: Fn(&str) -> bool> {
is_variable_defined: F,
target_disallowed_variables: &'a BTreeSet<String>,
data: &'a mut Pass1Data,
definition: &'p [parse::Element],
used_target_disallowed_construct: bool,
}
impl<'a, 'p, F: Fn(&str) -> bool> VariableDefinitionValidator<'a, 'p, F> {
fn new(
is_variable_defined: F,
data: &'a mut Pass1Data,
target_disallowed_variables: &'a BTreeSet<String>,
definition: &'p [parse::Element],
) -> Self {
Self {
is_variable_defined,
data,
target_disallowed_variables,
definition,
used_target_disallowed_construct: false,
}
}
fn validate(&mut self) -> Result<()> {
self.validate_section(self.definition)
}
fn validate_section(&mut self, section: &[parse::Element]) -> Result<()> {
section
.iter()
.try_for_each(|element| self.validate_element(element))
}
fn validate_element(&mut self, element: &parse::Element) -> Result<()> {
match element {
parse::Element::Literal(_) => {}
parse::Element::VariableRef(name) => {
if !(self.is_variable_defined)(name) {
return Err(CompileErrorKind::UnknownVariable.into());
}
if self.target_disallowed_variables.contains(name) {
self.used_target_disallowed_construct = true;
}
self.data.used_variables.insert(name.clone());
}
parse::Element::Quantifier(kind, inner) => {
self.used_target_disallowed_construct = true;
match *kind {
parse::QuantifierKind::ZeroOrOne => self.data.counts.num_quantifiers_opt += 1,
parse::QuantifierKind::ZeroOrMore => {
self.data.counts.num_quantifiers_kleene += 1
}
parse::QuantifierKind::OneOrMore => {
self.data.counts.num_quantifiers_kleene_plus += 1
}
}
self.validate_element(inner)?;
}
parse::Element::UnicodeSet(_) => {
self.used_target_disallowed_construct = true;
self.data.counts.num_unicode_sets += 1;
}
elt => {
return Err(CompileErrorKind::UnexpectedElementInVariableDefinition(
elt.kind().debug_str(),
)
.into());
}
}
Ok(())
}
}
struct Pass1ResultGenerator {
current_vars: BTreeSet<String>,
transitive_var_dependencies: BTreeMap<String, BTreeSet<String>>,
}
impl Pass1ResultGenerator {
fn generate(pass: Pass1) -> Result<Pass1Result> {
let generator = Self {
current_vars: BTreeSet::new(),
transitive_var_dependencies: BTreeMap::new(),
};
generator.generate_result(pass)
}
fn generate_result(mut self, pass: Pass1) -> Result<Pass1Result> {
let forward_data =
self.generate_result_one_direction(&pass.forward_data, &pass.variable_data)?;
let reverse_data =
self.generate_result_one_direction(&pass.reverse_data, &pass.variable_data)?;
let variable_definitions = pass
.variable_definitions
.into_iter()
.filter(|(var, _)| {
forward_data.used_variables.contains(var)
|| reverse_data.used_variables.contains(var)
})
.collect();
let forward_rule_groups = pass.forward_rule_group_agg.finalize();
let reverse_rule_groups = pass.reverse_rule_group_agg.finalize();
Ok(Pass1Result {
forward_result: DirectedPass1Result {
data: forward_data,
filter: pass.forward_filter,
groups: forward_rule_groups,
},
reverse_result: DirectedPass1Result {
data: reverse_data,
filter: pass.reverse_filter,
groups: reverse_rule_groups,
},
variable_definitions,
})
}
fn generate_result_one_direction(
&mut self,
seed_data: &Pass1Data,
var_data_map: &BTreeMap<String, Pass1Data>,
) -> Result<Pass1Data> {
let seed_vars = &seed_data.used_variables;
let mut used_variables = seed_vars.clone();
for var in seed_vars {
self.visit_var(var, var_data_map)?;
#[expect(clippy::indexing_slicing)] let deps = self.transitive_var_dependencies[var].clone();
used_variables.extend(deps);
}
let mut combined_counts = seed_data.counts;
for var in &used_variables {
let var_data = var_data_map
.get(var)
.ok_or(CompileErrorKind::Internal("unexpected unknown variable"))?;
combined_counts.combine(var_data.counts);
}
Ok(Pass1Data {
used_variables,
counts: combined_counts,
})
}
fn visit_var(&mut self, name: &str, var_data_map: &BTreeMap<String, Pass1Data>) -> Result<()> {
if self.transitive_var_dependencies.contains_key(name) {
return Ok(());
}
if self.current_vars.contains(name) {
return Err(CompileErrorKind::Internal("unexpected cyclic variable").into());
}
self.current_vars.insert(name.to_owned());
let var_data = var_data_map
.get(name)
.ok_or(CompileErrorKind::Internal("unexpected unknown variable"))?;
let mut transitive_dependencies = var_data.used_variables.clone();
var_data
.used_variables
.iter()
.try_for_each(|var| -> Result<()> {
self.visit_var(var, var_data_map)?;
#[expect(clippy::indexing_slicing)] let deps = self.transitive_var_dependencies[var].clone();
transitive_dependencies.extend(deps);
Ok(())
})?;
self.current_vars.remove(name);
self.transitive_var_dependencies
.insert(name.to_owned(), transitive_dependencies);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::ops::Deref;
enum ExpectedOutcome {
Pass,
Fail,
}
use ExpectedOutcome::*;
fn parse(s: &str) -> Vec<parse::Rule> {
match parse::parse(s) {
Ok(rules) => rules,
Err(e) => panic!("unexpected error parsing rules {s:?}: {:?}", e.explain(s)),
}
}
fn pass1data_from_parts(
var_deps: &[&'static str],
counts: SpecialConstructCounts,
) -> Pass1Data {
let mut data = Pass1Data {
counts,
..Default::default()
};
for &var in var_deps {
data.used_variables.insert(var.into());
}
data
}
#[test]
fn test_pass1_computed_data() {
let source = r"
:: [a-z] ;
$used_both = [a-z] ; # only transitively used by reverse direction
$used_rev = $used_both $used_both+ ;
$unused = a+ b+ .? $used_both $used_rev ; # unused
$unused2 = $unused ; # unused
:: [:L:] Bidi-Dependency/One ;
$used_fwd = [just a set] ;
($used_both [a-z]) > &[a-z] Forward-Dependency($1) ;
$used_fwd > ;
< $used_rev+? ;
$literal1 = a ;
$literal2 = b ;
$literal1 <> $literal2 ;
:: AnotherForwardDependency () ;
:: ([set] Backward-Dependency) ;
:: YetAnother-ForwardDependency (AnotherBackwardDependency) ;
&Many(&Backwardz(&Deps($2))) < (a(bc)d)+ ;
:: ([a-z]) ;
";
let rules = parse(source);
let result = Pass1::run(Direction::Both, &rules)
.map_err(|e| e.explain(source))
.expect("pass1 failed");
{
let vars_with_data: BTreeSet<_> = result
.variable_definitions
.keys()
.map(Deref::deref)
.collect();
let expected_vars_with_data =
BTreeSet::from(["used_both", "used_rev", "used_fwd", "literal1", "literal2"]);
assert_eq!(expected_vars_with_data, vars_with_data);
}
{
let fwd_counts = SpecialConstructCounts {
num_compounds: 4,
num_unicode_sets: 3,
num_function_calls: 1,
num_segments: 1,
max_backref_num: 1,
..Default::default()
};
let fwd_data = pass1data_from_parts(
&["used_both", "used_fwd", "literal1", "literal2"],
fwd_counts,
);
let rev_counts = SpecialConstructCounts {
num_compounds: 4,
num_unicode_sets: 1,
num_quantifiers_kleene_plus: 3,
num_quantifiers_opt: 1,
num_segments: 2,
num_function_calls: 3,
max_backref_num: 2,
..Default::default()
};
let rev_data = pass1data_from_parts(
&["used_both", "used_rev", "literal1", "literal2"],
rev_counts,
);
assert_eq!(fwd_data, result.forward_result.data);
assert_eq!(rev_data, result.reverse_result.data);
let actual_definition_keys: BTreeSet<_> = result
.variable_definitions
.keys()
.map(Deref::deref)
.collect();
let expected_definition_keys =
BTreeSet::from(["used_both", "used_fwd", "used_rev", "literal1", "literal2"]);
assert_eq!(expected_definition_keys, actual_definition_keys);
}
}
#[test]
fn test_pass1_validate_conversion() {
let sources = [
(Pass, r"^ a > ;"),
(Pass, r"^ a > ^ b;"),
(Pass, r"^ a < ^ b;"),
(Pass, r"^ a <> ^ b;"),
(Pass, r"^ { a > ;"),
(Pass, r"{ ^ a > ;"),
(Fail, r"a { ^ a > ;"),
(Fail, r"a ^ a > ;"),
(Fail, r"a ^ > ;"),
(Fail, r"< a ^ ;"),
(Fail, r"a } ^ > ;"),
(Fail, r"a } ^ a > ;"),
(Fail, r"(^) a > ;"),
(Fail, r"^+ a > ;"),
(Pass, r"a $ > ;"),
(Pass, r"a $ > $;"),
(Pass, r"a $ <> a$;"),
(Pass, r"a } $ > ;"),
(Pass, r"a $ } > ;"),
(Fail, r"a $ } a > ;"),
(Fail, r"< $ a ;"),
(Fail, r"a $ a > ;"),
(Fail, r"$ a > ;"),
(Fail, r"$ { a > ;"),
(Fail, r"a $ { a > ;"),
(Fail, r"a ($) > ;"),
(Fail, r"a $+ > ;"),
(Pass, r"a | b <> c | d ;"),
(Fail, r"a | b | <> | c | d ;"),
(Fail, r"a > | c | d ;"),
(Pass, r"a > | c d ;"),
(Pass, r"a > | ;"),
(Fail, r"a > || ;"),
(Fail, r"a|? > ;"),
(Fail, r"a(|) > ;"),
(Fail, r"a > &Remove(|) ;"),
(Pass, r"a > |@ ;"),
(Pass, r"a > @| ;"),
(Fail, r"a > @|@ ;"),
(Fail, r"a > @|@| ;"),
(Pass, r"a > xa @@@| ;"),
(Pass, r"a > |@@ xa ;"),
(Fail, r"a > x @| a ;"),
(Fail, r"a > x |@ a ;"),
(Fail, r"a > x @|@ a ;"),
(Pass, r"[a-z] > a ;"),
(Fail, r"[a-z] < a ;"),
(Pass, r". > a ;"),
(Fail, r". < a ;"),
(Fail, r"(a) <> $1 ;"),
(Pass, r"(a) > $1 ;"),
(Pass, r"(a()) > $1 $2;"),
(Pass, r"(a()) > $2;"),
(Fail, r"(a) > $2;"),
(Pass, r"(a) } (abc) > $2;"),
(Fail, r"a > $a;"),
(Pass, r"a+*? } b? > a;"),
(Fail, r"a > a+;"),
(Fail, r"a > a*;"),
(Fail, r"a > a?;"),
(Pass, r"a > &Remove();"),
(Fail, r"a < &Remove();"),
(Pass, r"a (.*)> &[a-z] Latin-Greek/BGN(abc &[a]Remove($1));"),
];
for (expected_outcome, source) in sources {
match (
expected_outcome,
Pass1::run(Direction::Both, &parse(source)),
) {
(Fail, Ok(_)) => {
panic!("unexpected successful pass1 validation for rules {source:?}")
}
(Pass, Err(e)) => {
panic!(
"unexpected error in pass1 validation for rules {source:?}: {:?}",
e.explain(source)
)
}
_ => {}
}
}
}
#[test]
fn test_pass1_validate_variable_definition() {
let sources = [
(Fail, r"$a = &Remove() ;"),
(Fail, r"$a = (abc) ;"),
(Fail, r"$a = | ;"),
(Fail, r"$a = ^ ;"),
(Fail, r"$a = $ ;"),
(Fail, r"$a = $1 ;"),
(Fail, r"$var = [a-z] ; a > $var ;"),
(Fail, r"$var = a+ ; a > $var ;"),
(Pass, r"$var = [a-z] ; $var > a ;"),
(Pass, r"$var = a+ ; $var > a ;"),
(Pass, r"$b = 'hello'; $var = a+*? [a-z] $b ;"),
];
for (expected_outcome, source) in sources {
match (
expected_outcome,
Pass1::run(Direction::Both, &parse(source)),
) {
(Fail, Ok(_)) => {
panic!("unexpected successful pass1 validation for rules {source:?}")
}
(Pass, Err(e)) => {
panic!(
"unexpected error in pass1 validation for rules {source:?}: {:?}",
e.explain(source)
)
}
_ => {}
}
}
}
#[test]
fn test_pass1_validate_global_filters() {
let sources = [
(Pass, r":: [a-z];"),
(Pass, r":: ([a-z]);"),
(Pass, r":: [a-z] ; :: ([a-z]);"),
(Fail, r":: [a-z] ; :: [a-z] ;"),
(Fail, r":: ([a-z]) ; :: ([a-z]) ;"),
(Fail, r":: ([a-z]) ; :: [a-z] ;"),
(Pass, r":: [a-z] ; :: Remove ; :: ([a-z]) ;"),
(Fail, r":: Remove ; :: [a-z] ;"),
(Fail, r":: ([a-z]) ; :: Remove ;"),
];
for (expected_outcome, source) in sources {
let rules = parse(source);
let result = Pass1::run(Direction::Both, &rules);
match (expected_outcome, result) {
(Fail, Ok(_)) => {
panic!("unexpected successful pass1 validation for rules {source:?}")
}
(Pass, Err(e)) => {
panic!(
"unexpected error in pass1 validation for rules {source:?}: {:?}",
e.explain(source)
)
}
_ => {}
}
}
}
}