ielr 0.1.1

Table generation backend for LR parser generators
Documentation
/*
 * Copyright 2022 Arnaud Golfouse
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

//! Errors while generating the LR table.

use super::{Lookahead, StateIdx};
use crate::input::{Node, ProdIdx};

/// An error occured while generating the LR table.
#[derive(Clone, Debug)]
pub enum Error {
    /// The algorithm  generated too many states.
    StatesOverflow,
    /// The grammar derives `node ⇒+ node` for some `node`.
    CycleError(CycleError),
    /// There is at least one LR conflict in the grammar.
    Conflict {
        /// LALR tables.
        ///
        /// Those can be used to gather context about the conflict.
        lalr_tables: super::Table,
        /// State in which the conflict occured.
        ///
        /// Used to access a [`State`](super::State) in the
        /// [LALR tables](Self::Conflict::lalr_tables).
        state: StateIdx,
        /// Conflict in [state](Self::Conflict::state) between multiple possible
        /// [`Action`](super::Action).
        conflict: Conflict,
    },
}

impl From<crate::lr::StatesOverflow> for Error {
    fn from(_: crate::lr::StatesOverflow) -> Self {
        Self::StatesOverflow
    }
}

/// The grammar derives `node ⇒+ node` for some `node`.
///
/// Note that this is only one example of such a derivation in the grammar: other may
/// exist.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CycleError {
    /// The node that can derive itself.
    pub node: Node,
    /// The productions to follow in order to derive `node ⇒+ node`.
    ///
    /// This does not contains the productions necessary to nullate some nodes.
    pub path: Vec<ProdIdx>,
}

impl From<CycleError> for Error {
    fn from(err: CycleError) -> Self {
        Self::CycleError(err)
    }
}

/// The table contains a conflict between various possible actions.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Conflict {
    /// The lookahead on which the conflict occurs.
    pub lookahead: Lookahead,
    /// The various possible actions on `lookahead`, that conflict with one another.
    pub contributions: ConflictContributions,
}

/// Possible actions in a [`Conflict`].
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConflictContributions {
    /// `true` if the conflict has a 'shift' contributions.
    ///
    /// Else, the conflict only has 'reduce' contributions.
    pub has_shift: bool,
    /// Reduce contributions of the conflict, in the form of the associated production
    /// to reduce.
    ///
    /// Contains at least one element.
    // NOTE: since those come from a state, the length of this Vec is bounded by both
    // `ProdIdxRaw` and `ItemIdx`
    pub reduces: Vec<ProdIdx>,
}

impl ConflictContributions {
    /// Get the number of contributions to the conflict.
    ///
    /// This is `self.reduces.len()` if `self.has_shift` is false, and
    /// `self.reduces.len() + 1` else.
    #[allow(clippy::len_without_is_empty)]
    pub fn len(&self) -> usize {
        let mut len = self.reduces.len();
        if self.has_shift {
            len += 1;
        }
        len
    }
}

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

    #[test]
    fn conflict_contributions_len() {
        let conflict_contributions = ConflictContributions {
            has_shift: true,
            reduces: vec![
                ProdIdx {
                    lhs: Node(0),
                    index: 0,
                },
                ProdIdx {
                    lhs: Node(0),
                    index: 1,
                },
            ],
        };
        assert_eq!(conflict_contributions.len(), 3);
        let conflict_contributions = ConflictContributions {
            has_shift: false,
            reduces: vec![
                ProdIdx {
                    lhs: Node(0),
                    index: 0,
                },
                ProdIdx {
                    lhs: Node(0),
                    index: 1,
                },
            ],
        };
        assert_eq!(conflict_contributions.len(), 2);
    }
}