regast-core 0.1.0

Parse-tree matching backends for regast
Documentation
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,
    }
}