zirium 0.0.11

Lossless textual MLIR syntax parsing and semantic processing
Documentation
use crate::SyntaxKind;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum FormatBinding {
    Operands,
    Value,
    Results,
    Result,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum FormatLiteral {
    Colon,
    To,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum FormatStep {
    Begin(SyntaxKind),
    Capture(FormatBinding),
    AttributeDictionary,
    Literal(FormatLiteral),
    Type(FormatBinding),
    End(SyntaxKind),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct OperationFormat {
    steps: Box<[FormatStep]>,
}

impl OperationFormat {
    pub(crate) fn parse(description: &str) -> Option<Self> {
        let directives = scan(description)?;
        validate(&directives)?;
        Some(Self {
            steps: directives.into_boxed_slice(),
        })
    }

    pub(crate) fn steps(&self) -> &[FormatStep] {
        &self.steps
    }

    pub(crate) fn captures(&self, binding: FormatBinding) -> bool {
        self.steps.contains(&FormatStep::Capture(binding))
    }

    pub(crate) fn identity_bytes(&self) -> impl Iterator<Item = u8> + '_ {
        self.steps.iter().flat_map(|step| match step {
            FormatStep::Begin(kind) => [0, *kind as u8],
            FormatStep::Capture(binding) => [1, *binding as u8],
            FormatStep::AttributeDictionary => [2, 0],
            FormatStep::Literal(literal) => [3, *literal as u8],
            FormatStep::Type(binding) => [4, *binding as u8],
            FormatStep::End(kind) => [5, *kind as u8],
        })
    }
}

fn scan(description: &str) -> Option<Vec<FormatStep>> {
    let mut raw = Vec::new();
    for word in description.split_ascii_whitespace() {
        let step = match word {
            "$operands" => FormatStep::Capture(FormatBinding::Operands),
            "$value" => FormatStep::Capture(FormatBinding::Value),
            "attr-dict" => FormatStep::AttributeDictionary,
            "`:`" => FormatStep::Literal(FormatLiteral::Colon),
            "`to`" => FormatStep::Literal(FormatLiteral::To),
            "type($operands)" => FormatStep::Type(FormatBinding::Operands),
            "type($value)" => FormatStep::Type(FormatBinding::Value),
            "type($results)" => FormatStep::Type(FormatBinding::Results),
            "type($result)" => FormatStep::Type(FormatBinding::Result),
            _ => return None,
        };
        raw.push(step);
    }

    let operand_program = raw.contains(&FormatStep::Capture(FormatBinding::Operands));
    let mut steps = Vec::with_capacity(raw.len() + 4);
    for step in raw {
        if step == FormatStep::Capture(FormatBinding::Value) {
            steps.push(FormatStep::Begin(SyntaxKind::ArithConstantValue));
        }
        if step == FormatStep::Type(FormatBinding::Operands) {
            steps.push(FormatStep::Begin(SyntaxKind::FunctionType));
        }
        if !operand_program && step == FormatStep::Type(FormatBinding::Result) {
            steps.push(FormatStep::Begin(SyntaxKind::FunctionType));
        }
        steps.push(step);
        if step == FormatStep::Type(FormatBinding::Value) {
            steps.push(FormatStep::End(SyntaxKind::ArithConstantValue));
        }
        if matches!(
            step,
            FormatStep::Type(FormatBinding::Results | FormatBinding::Result)
        ) && steps.contains(&FormatStep::Begin(SyntaxKind::FunctionType))
        {
            steps.push(FormatStep::End(SyntaxKind::FunctionType));
        }
    }
    Some(steps)
}

fn validate(steps: &[FormatStep]) -> Option<()> {
    let directive_count = steps
        .iter()
        .filter(|step| !matches!(step, FormatStep::Begin(_) | FormatStep::End(_)))
        .count();
    if directive_count != 6 {
        return None;
    }
    let operand_capture = steps
        .iter()
        .position(|step| *step == FormatStep::Capture(FormatBinding::Operands));
    let value_capture = steps
        .iter()
        .position(|step| *step == FormatStep::Capture(FormatBinding::Value));
    if operand_capture.is_some() == value_capture.is_some() {
        return None;
    }
    if steps
        .iter()
        .filter(|step| matches!(step, FormatStep::AttributeDictionary))
        .count()
        != 1
    {
        return None;
    }

    if let Some(capture) = operand_capture {
        let attributes = position(steps, FormatStep::AttributeDictionary)?;
        let operand_type = position(steps, FormatStep::Type(FormatBinding::Operands))?;
        let result_type = position(steps, FormatStep::Type(FormatBinding::Results))?;
        let colon = position(steps, FormatStep::Literal(FormatLiteral::Colon))?;
        let to = position(steps, FormatStep::Literal(FormatLiteral::To))?;
        (capture < attributes
            && attributes < colon
            && colon < operand_type
            && operand_type < to
            && to < result_type)
            .then_some(())
    } else {
        let capture = value_capture?;
        let attributes = position(steps, FormatStep::AttributeDictionary)?;
        let value_type = position(steps, FormatStep::Type(FormatBinding::Value))?;
        let result_type = position(steps, FormatStep::Type(FormatBinding::Result))?;
        let colons = steps
            .iter()
            .enumerate()
            .filter_map(|(index, step)| {
                (*step == FormatStep::Literal(FormatLiteral::Colon)).then_some(index)
            })
            .collect::<Vec<_>>();
        (colons.len() == 2
            && capture < colons[0]
            && colons[0] < value_type
            && value_type < attributes
            && attributes < colons[1]
            && colons[1] < result_type)
            .then_some(())
    }
}

fn position(steps: &[FormatStep], needle: FormatStep) -> Option<usize> {
    let mut positions = steps
        .iter()
        .enumerate()
        .filter_map(|(index, step)| (*step == needle).then_some(index));
    let position = positions.next()?;
    positions.next().is_none().then_some(position)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn compiles_supported_formats_and_rejects_invalid_bindings() {
        assert!(
            OperationFormat::parse("$operands attr-dict `:` type($operands) `to` type($results)")
                .is_some()
        );
        assert!(
            OperationFormat::parse("$value `:` type($value) attr-dict `:` type($result)").is_some()
        );
        assert!(OperationFormat::parse("$value `:` type($operands)").is_none());
        assert!(OperationFormat::parse("$operands `to` type($results)").is_none());
    }
}