use std::collections::HashMap;
use regast_syntax::AnchorKind;
use crate::{
Disambiguation, IrId, IrKind, IrPool, Value,
value_composite::{alt_values, seq_values, star_values},
value_set::{Span, ValueSet},
};
pub(crate) type ValueTable = Vec<HashMap<Span, Value>>;
pub(crate) fn parse_ir_value(
pool: &IrPool,
root: IrId,
input: &str,
base: usize,
full_len: usize,
disambiguation: Disambiguation,
size_limit: usize,
) -> Result<Option<Value>, usize> {
let boundaries = boundaries(input);
let mut table: ValueTable = (0..pool.len()).map(|_| HashMap::new()).collect();
let mut state_count = 0;
for id in reachable_ir(pool, root) {
let remaining = size_limit.saturating_sub(state_count);
let values = build_values(
pool,
id,
&table,
&boundaries,
input,
base,
full_len,
disambiguation,
remaining,
)
.map_err(|local_states| state_count + local_states)?;
state_count += values.len();
table[id.0 as usize] = values;
}
Ok(table[root.0 as usize]
.get(&(0, boundaries.len() - 1))
.cloned())
}
#[allow(clippy::too_many_arguments)]
fn build_values(
pool: &IrPool,
id: IrId,
table: &ValueTable,
boundaries: &[usize],
input: &str,
base: usize,
full_len: usize,
disambiguation: Disambiguation,
limit: usize,
) -> Result<HashMap<Span, Value>, usize> {
match pool.kind(id).clone() {
IrKind::Zero => Ok(HashMap::new()),
IrKind::One => empty_values(boundaries.len(), limit, |_| true),
IrKind::Anchor(kind, _) => empty_values(boundaries.len(), limit, |position| {
assertion_holds(kind, base + boundaries[position], full_len)
}),
IrKind::Class(set, _) => class_values(pool, set, boundaries, input, limit),
IrKind::Alt(left, right, ..) => {
alt_values(pool, id, table, left, right, disambiguation, limit)
}
IrKind::Seq(left, right, _) => seq_values(
pool,
id,
table,
left,
right,
boundaries.len(),
disambiguation,
limit,
),
IrKind::Star(inner, ..) => star_values(
pool,
id,
table,
inner,
boundaries.len(),
disambiguation,
limit,
),
}
}
fn empty_values(
boundary_count: usize,
limit: usize,
accepts: impl Fn(usize) -> bool,
) -> Result<HashMap<Span, Value>, usize> {
let mut result = ValueSet::new(limit);
for position in (0..boundary_count).filter(|position| accepts(*position)) {
result.insert((position, position), Value::Empty)?;
}
Ok(result.into_inner())
}
fn class_values(
pool: &IrPool,
set: crate::ir::ClassSetId,
boundaries: &[usize],
input: &str,
limit: usize,
) -> Result<HashMap<Span, Value>, usize> {
let mut result = ValueSet::new(limit);
for start in 0..boundaries.len() - 1 {
let c = input[boundaries[start]..boundaries[start + 1]]
.chars()
.next()
.expect("adjacent UTF-8 boundaries contain a scalar");
if pool.get_class_set(set).contains(c) {
result.insert((start, start + 1), Value::Char(c))?;
}
}
Ok(result.into_inner())
}
fn boundaries(input: &str) -> Vec<usize> {
let mut result: Vec<_> = input.char_indices().map(|(position, _)| position).collect();
result.push(input.len());
result
}
fn reachable_ir(pool: &IrPool, root: IrId) -> Vec<IrId> {
let mut seen = vec![false; pool.len()];
let mut stack = vec![(root, false)];
let mut result = Vec::new();
while let Some((id, expanded)) = stack.pop() {
if expanded {
result.push(id);
continue;
}
if seen[id.0 as usize] {
continue;
}
seen[id.0 as usize] = true;
stack.push((id, true));
match pool.kind(id) {
IrKind::Alt(left, right, ..) | IrKind::Seq(left, right, _) => {
stack.push((*right, false));
stack.push((*left, false));
}
IrKind::Star(inner, ..) => stack.push((*inner, false)),
IrKind::Zero | IrKind::One | IrKind::Anchor(..) | IrKind::Class(..) => {}
}
}
result
}
const fn assertion_holds(kind: AnchorKind, position: usize, full_len: usize) -> bool {
match kind {
AnchorKind::Start => position == 0,
AnchorKind::End => position == full_len,
}
}