use super::reader::ReadMode;
use super::reader::Reader;
use crate::compiling_rules::{Region, RegionMap, Rules};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
#[derive(PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum RegionReaction {
Begin(bool),
End(bool),
Pass,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RegionHandler {
region_stack: Vec<Region>,
region_map: RegionMap,
}
impl RegionHandler {
pub fn new(rules: &Rules) -> Self {
RegionHandler {
region_stack: vec![rules.region_tree.clone()],
region_map: rules.region_tree.clone().generate_region_map(),
}
}
#[inline]
pub fn get_region(&self) -> Option<&Region> {
self.region_stack.last()
}
#[inline]
pub fn is_region_closed(&self, reader: &Reader) -> Result<(), ((usize, usize), usize, Region)> {
if let Some(region) = self.region_stack.last() {
if !region.allow_unclosed_region {
let pos = reader.get_position();
return Err((pos, reader.get_index(), region.clone()));
}
}
Ok(())
}
pub fn handle_region(&mut self, reader: &Reader) -> RegionReaction {
if let Some(region) = self.get_region() {
for interp_region in region.interp.iter() {
if let Some(mut begin_region) = self.match_region_by_begin(reader) {
if begin_region.name == *interp_region.name {
let tokenize = begin_region.tokenize;
if let Some(reference_name) = &begin_region.references {
match self.region_map.get(reference_name) {
Some(target_region) => {
begin_region.interp = target_region.interp.clone();
}
None => {
panic!("Could not find region with id '{}'", reference_name);
}
}
}
self.region_stack.push(begin_region);
return RegionReaction::Begin(tokenize);
}
}
}
if let Some(end_region) = self.match_region_by_end(reader) {
if end_region.name == region.name {
let tokenize = end_region.tokenize;
self.region_stack.pop();
return RegionReaction::End(tokenize);
}
}
}
RegionReaction::Pass
}
#[inline]
fn match_region_by(
&self,
reader: &Reader,
cb: impl Fn(&Region) -> &String,
candidates: &[Region],
read_mode: ReadMode,
) -> Option<Region> {
let predicate = |candidate: &Region| match reader
.get_history_or_future(cb(candidate).len(), &read_mode)
{
Some(code_chunk) => &code_chunk == cb(candidate),
None => false,
};
self.get_region_by(predicate, candidates)
}
#[inline]
fn match_region_by_begin(&self, reader: &Reader) -> Option<Region> {
let region = self.get_region().unwrap();
self.match_region_by(
reader,
|candidate: &Region| &candidate.begin,
®ion.interp,
ReadMode::Future,
)
}
#[inline]
fn match_region_by_end(&self, reader: &Reader) -> Option<Region> {
let region = self.get_region().unwrap();
if !region.global {
self.match_region_by(
reader,
|candidate: &Region| &candidate.end,
&[region.clone()],
ReadMode::History,
)
} else {
None
}
}
#[inline]
fn get_region_by(&self, cb: impl Fn(&Region) -> bool, candidates: &[Region]) -> Option<Region> {
for region in candidates.iter() {
if cb(region) {
return Some(region.clone());
}
}
None
}
}
#[cfg(test)]
mod test {
use super::Reader;
use super::{RegionHandler, RegionReaction};
use crate::compiling_rules::Region;
use crate::reg;
#[test]
fn match_region() {
let lines = vec!["begin", "\\begin", "end"];
let expected = vec![(0, String::from("begin")), (15, String::from("end"))];
let code = lines.join(" ");
let mut reader = Reader::new(&code);
let region = reg![reg!(module as "Module literal" => {
begin: "begin",
end: "end"
})];
let mut rh = RegionHandler {
region_stack: vec![region.clone()],
region_map: region.generate_region_map(),
};
let mut result = vec![];
let mut is_escaped = false;
while let Some(letter) = reader.next() {
if is_escaped {
is_escaped = !is_escaped && letter == '\\';
continue;
}
if let Some(begin) = rh.match_region_by_begin(&reader) {
rh.region_stack.push(begin.clone());
result.push((reader.get_index(), begin.begin));
}
if let Some(end) = rh.match_region_by_end(&reader) {
result.push((reader.get_index(), end.end));
}
is_escaped = !is_escaped && letter == '\\';
}
assert_eq!(expected, result);
}
#[test]
fn handle_region() {
let lines = vec!["'My name is \\\\\\'{name}.\\\\'"];
let expected = vec![0, 16, 21, 25];
let code = lines.join("\n");
let region = reg![reg!(string as "String literal" => {
begin: "'",
end: "'"
} => [
reg!(interp as "Interpolation" => {
begin: "{",
end: "}"
})
])];
let mut reader = Reader::new(&code);
let mut rh = RegionHandler {
region_stack: vec![region.clone()],
region_map: region.generate_region_map(),
};
let mut result = vec![];
let mut is_escaped = false;
while let Some(letter) = reader.next() {
if is_escaped {
is_escaped = !is_escaped && letter == '\\';
continue;
}
let region_mutated = rh.handle_region(&reader);
if let RegionReaction::Begin(_) | RegionReaction::End(_) = region_mutated {
result.push(reader.get_index());
}
is_escaped = !is_escaped && letter == '\\';
}
assert_eq!(expected, result);
}
}