ltk_ritobin 0.6.0

Ritobin text format parser and writer for League Toolkit
Documentation
use std::borrow::Cow;

use indexmap::{Equivalent, IndexMap};
use ltk_meta::{traits::PropertyExt, PropertyValueEnum};

use crate::{
    parse::Span,
    typecheck::{
        diagnostics::{DiagnosticWithSpan, RootKind},
        ir::{IrItem, IrListItem},
    },
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RootKindOrUnknown<'a> {
    Known(RootKind),
    Unknown(Cow<'a, str>),
}

impl std::hash::Hash for RootKindOrUnknown<'_> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        match self {
            RootKindOrUnknown::Known(root_kind) => root_kind.hash(state),
            RootKindOrUnknown::Unknown(cow) => cow.hash(state),
        }
    }
}

impl Equivalent<RootKindOrUnknown<'_>> for RootKind {
    #[inline(always)]
    fn equivalent(&self, key: &RootKindOrUnknown<'_>) -> bool {
        match key {
            RootKindOrUnknown::Known(root_kind) => self == root_kind,
            RootKindOrUnknown::Unknown(_) => false,
        }
    }
}
impl Equivalent<RootKindOrUnknown<'_>> for Cow<'_, str> {
    #[inline(always)]
    fn equivalent(&self, key: &RootKindOrUnknown<'_>) -> bool {
        match key {
            RootKindOrUnknown::Known(_) => false,
            RootKindOrUnknown::Unknown(cow) => self == cow,
        }
    }
}
impl Equivalent<RootKindOrUnknown<'_>> for str {
    #[inline(always)]
    fn equivalent(&self, key: &RootKindOrUnknown<'_>) -> bool {
        match key {
            RootKindOrUnknown::Known(_) => false,
            RootKindOrUnknown::Unknown(cow) => self == cow,
        }
    }
}

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

    #[test]
    fn root_kind_eq() {
        let mut root: IndexMap<RootKindOrUnknown<'static>, ()> = Default::default();

        root.insert(RootKind::Version.into(), ());
        root.insert(RootKind::Entries.into(), ());
        root.insert(RootKindOrUnknown::Unknown("foo".into()), ());
        root.insert(RootKindOrUnknown::Unknown("bar".into()), ());

        assert!(root.swap_remove(&RootKind::Version).is_some());
        assert!(root.swap_remove(&RootKind::Entries).is_some());
        assert!(root
            .swap_remove(&RootKindOrUnknown::Unknown("bar".into()))
            .is_some());

        assert_eq!(root.len(), 1);
    }
}

impl<'a> RootKindOrUnknown<'a> {
    pub fn from_value(src: &'a str, value: &PropertyValueEnum<Span>) -> Self {
        let PropertyValueEnum::String(string) = value else {
            return Self::Unknown(src[*value.meta()].into());
        };

        match string.as_str() {
            "type" => RootKind::Type.into(),
            "version" => RootKind::Version.into(),
            "linked" => RootKind::Linked.into(),
            "entries" => RootKind::Entries.into(),
            _ => Self::Unknown(src[*value.meta()].into()),
        }
    }
}

impl From<RootKind> for RootKindOrUnknown<'_> {
    #[inline(always)]
    fn from(value: RootKind) -> Self {
        Self::Known(value)
    }
}
impl<'a> From<Cow<'a, str>> for RootKindOrUnknown<'a> {
    #[inline(always)]
    fn from(value: Cow<'a, str>) -> Self {
        Self::Unknown(value)
    }
}

#[derive(Debug, Clone)]
pub struct RootEntry {
    pub(crate) key: PropertyValueEnum<Span>,
    pub(crate) type_span: Span,
    pub(crate) value: PropertyValueEnum<Span>,
}

pub struct TypeChecker<'a> {
    pub(crate) ctx: Ctx<'a>,
    pub root: IndexMap<RootKindOrUnknown<'a>, RootEntry>,
    pub(crate) stack: Vec<(u32, IrItem)>,
    pub(crate) list_queue: Vec<IrListItem>,
    pub(crate) depth: u32,
}

impl<'a> TypeChecker<'a> {
    pub fn new(text: &'a str) -> Self {
        Self {
            ctx: Ctx {
                text,
                diagnostics: Vec::new(),
            },
            root: IndexMap::new(),
            stack: Vec::new(),
            list_queue: Vec::new(),
            depth: 0,
        }
    }
}

pub(crate) struct Ctx<'a> {
    pub(crate) text: &'a str,
    pub(crate) diagnostics: Vec<DiagnosticWithSpan>,
}