kbvm 0.1.5

An implementation of the XKB specification
Documentation
use {
    crate::xkb::{
        code_loader::{CodeLoader, CodeType},
        code_map::CodeMap,
        diagnostic::{DiagnosticKind, DiagnosticSink},
        include::parse_include,
        interner::{Interned, Interner},
        kccgst::{
            ast::{
                Compat, CompatmapDecl, ConfigItemType, DirectOrIncluded, Geometry, GeometryDecl,
                Include, Item, ItemType, KeycodeDecl, Keycodes, LoadedInclude, Symbols,
                SymbolsDecl, Types, TypesDecl,
            },
            ast_cache::AstCache,
        },
        meaning::MeaningCache,
        span::SpanExt,
    },
    hashbrown::HashSet,
    kbvm_proc::ad_hoc_display,
};

#[expect(clippy::too_many_arguments)]
pub(crate) fn resolve_includes(
    diagnostics: &mut DiagnosticSink<'_, '_>,
    map: &mut CodeMap,
    cache: &mut AstCache,
    loader: &mut CodeLoader,
    interner: &mut Interner,
    meaning_cache: &mut MeaningCache,
    remaining_runtime: &mut u64,
    item: &mut Item,
    max_includes: u64,
    max_include_depth: u64,
) {
    let mut includer = Includer {
        diagnostics,
        map,
        cache,
        loader,
        meaning_cache,
        remaining_runtime,
        active_includes: Default::default(),
        num_includes: 0,
        max_includes,
        include_depth: 0,
        max_include_depth,
    };
    includer.process_includes(interner, item);
}

struct Includer<'a, 'b, 'c> {
    diagnostics: &'a mut DiagnosticSink<'b, 'c>,
    map: &'a mut CodeMap,
    cache: &'a mut AstCache,
    loader: &'a mut CodeLoader,
    meaning_cache: &'a mut MeaningCache,
    remaining_runtime: &'a mut u64,
    active_includes: HashSet<(CodeType, Interned, Option<Interned>)>,
    num_includes: u64,
    max_includes: u64,
    include_depth: u64,
    max_include_depth: u64,
}

macro_rules! process_ct {
    ($fn:ident, $ty:ident, $decl:ident, $ct:ident) => {
        fn $fn(&mut self, interner: &mut Interner, item: &mut $ty) {
            for decl in &mut item.decls.decls {
                if let DirectOrIncluded::Direct($decl::Include(i)) = &mut decl.val.ty {
                    self.process_include(interner, i, CodeType::$ct);
                }
            }
        }
    };
}

impl Includer<'_, '_, '_> {
    fn process_includes(&mut self, interner: &mut Interner, item: &mut Item) {
        self.include_depth += 1;
        match &mut item.ty {
            ItemType::Composite(c) => {
                for i in &mut c.config_items {
                    self.process_config_item_type(interner, &mut i.val.item.specific);
                }
            }
            ItemType::Config(c) => self.process_config_item_type(interner, c),
        }
        self.include_depth -= 1;
    }

    fn process_config_item_type(&mut self, interner: &mut Interner, ty: &mut ConfigItemType) {
        match ty {
            ConfigItemType::Keycodes(e) => self.process_keycodes_includes(interner, e),
            ConfigItemType::Types(e) => self.process_types_includes(interner, e),
            ConfigItemType::Compat(e) => self.process_compat_includes(interner, e),
            ConfigItemType::Symbols(e) => self.process_symbols_includes(interner, e),
            ConfigItemType::Geometry(e) => self.process_geometry_includes(interner, e),
        }
    }

    process_ct!(process_keycodes_includes, Keycodes, KeycodeDecl, Keycodes);
    process_ct!(process_types_includes, Types, TypesDecl, Types);
    process_ct!(process_compat_includes, Compat, CompatmapDecl, Compat);
    process_ct!(process_symbols_includes, Symbols, SymbolsDecl, Symbols);
    process_ct!(process_geometry_includes, Geometry, GeometryDecl, Geometry);

    fn process_include(&mut self, interner: &mut Interner, include: &mut Include, ty: CodeType) {
        if self.include_depth >= self.max_include_depth {
            self.diagnostics.push(
                self.map,
                DiagnosticKind::MaxIncludeDepthReached,
                ad_hoc_display!("maximum include depth ({}) reached", self.max_include_depth => u64).spanned2(include.path.span),
            );
            return;
        }
        let mut resolved = vec![];
        let mut iter = parse_include(interner, include.path);
        while let Some(i) = iter.next() {
            let i = match i {
                Ok(i) => i,
                Err(e) => {
                    self.diagnostics.push(self.map, e.val.diagnostic_kind(), e);
                    continue;
                }
            };
            self.num_includes += 1;
            if self.num_includes > self.max_includes {
                self.diagnostics.push(
                    self.map,
                    DiagnosticKind::MaxIncludesReached,
                    ad_hoc_display!("maximum number of includes reached").spanned2(i.file.span),
                );
                break;
            }
            let mut span = i.file.span;
            if let Some(map) = i.map {
                span.hi = map.span.hi + 1;
            }
            let key = (ty, i.file.val, i.map.map(|m| m.val));
            if !self.active_includes.insert(key) {
                self.diagnostics.push(
                    self.map,
                    DiagnosticKind::RecursiveInclude,
                    ad_hoc_display!("ignoring recursive include").spanned2(span),
                );
                continue;
            }
            let res = self.cache.get(
                self.diagnostics,
                self.map,
                self.loader,
                iter.interner(),
                self.meaning_cache,
                self.remaining_runtime,
                ty,
                i.file.val,
                i.map.map(|m| m.val),
                span,
            );
            match res {
                Ok(mut item) => {
                    self.process_includes(iter.interner(), &mut item.val);
                    resolved.push(LoadedInclude {
                        mm: i.merge_mode,
                        group: i.group,
                        item,
                    });
                }
                Err(e) => {
                    self.diagnostics.push(self.map, e.val.diagnostic_kind(), e);
                }
            }
            self.active_includes.remove(&key);
        }
        include.loaded = Some(resolved.into_boxed_slice());
    }
}