symbolique 0.1.0

Symbol table pipeline for language servers — parse, link, merge, and resolve symbols across files, built on the laburnum LSP framework.
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! Shared helper and macro for generating symbol writer extension traits.
//!
//! The three symbol pipeline stages (File, Linked, Merged) share identical
//! writer logic — store a shape in `Symbols`, create an index entry in the
//! stage partition. Only the index partition type and method names differ.
//!
//! This module provides:
//! - [`write_shape_and_index`]: generic helper parameterised over the index partition
//! - [`define_symbol_writer!`]: macro that generates a trait + impl for each stage

use {
  crate::{
    core::{Ident, Symbol, SymbolPath, SymbolSortKey, Value},
    partitions::{records::SymbolEntry, symbols::Symbols},
  },
  laburnum::database::{
    HasPartition, Partition, PartitionKey, PartitionWriteContextRef, RecordHandle,
    storage::Partitions,
  },
};

/// Store a shape in the shared `Symbols` CAS partition and create an index
/// entry in a stage-specific index partition.
///
/// Also stages a span interval (ADR0007) so that position-based LSP queries
/// can locate the symbol by byte offset.
pub(super) fn write_shape_and_index<P, V, I, Path, Idx>(
  writer: &mut PartitionWriteContextRef<'_, P>,
  path: Path,
  span: laburnum::Span,
  shape: Symbol<V, I, Path>,
) -> RecordHandle<Symbols<V, I, Path>>
where
  P: Partitions,
  V: Value<I> + 'static,
  I: Ident,
  Path: SymbolPath + 'static,
  Idx: Partition<IndexEntry = SymbolEntry<V, I, Path>> + PartitionKey + 'static,
  P::Stores: HasPartition<Symbols<V, I, Path>> + HasPartition<Idx>,
{
  let handle = writer.store::<Symbols<V, I, Path>>(shape);

  let entry = SymbolEntry::new(span, handle);
  writer.index_entry::<Idx>(SymbolSortKey::from_path(&path), entry);

  // ADR0007: stage span for position-based lookups
  writer.index_span::<Idx>(span, handle.content_hash());

  handle
}

/// Generate a symbol writer extension trait and its implementation for a
/// pipeline stage.
///
/// # Parameters
///
/// - `trait_name`: Name of the generated trait (e.g. `SymboliqueWriteExt`)
/// - `index_partition`: The index partition type (e.g. `FileSymbols`)
/// - `clear_method`: Name of the clear method (e.g. `clear_file_symbols`)
/// - `method_prefix`: Prefix for write methods — the macro appends
///   `definition`, `reference`, `scope`, `keyword`, `value`, `import`
///
/// # Example
///
/// ```ignore
/// define_symbol_writer! {
///   trait_name: SymboliqueWriteExt,
///   index_partition: FileSymbols,
///   clear_method: clear_file_symbols,
///   method_prefix: write_symbol_,
/// }
/// ```
macro_rules! define_symbol_writer {
  (
    trait_name: $trait_name:ident,
    index_partition: $idx:ident,
    clear_method: $clear:ident,
    method_prefix: $prefix:ident,
  ) => {
    paste::paste! {

    pub trait $trait_name<P: Partitions> {
      fn $clear<V, I, Path>(
        &mut self,
        file_prefix: &Path,
      ) where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores: HasPartition<$idx<V, I, Path>>;

      fn [<$prefix definition>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        name: I,
        value: Option<V>,
        visibility: crate::core::SymbolVisibility,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>;

      #[allow(clippy::too_many_arguments)]
      fn [<$prefix reference>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        name: Option<I>,
        target_path: Path,
        is_absolute: bool,
        nameable: bool,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>;

      fn [<$prefix scope>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        value: Option<V>,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>;

      fn [<$prefix keyword>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        name: I,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>;

      fn [<$prefix value>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        value: V,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>;

      fn [<$prefix import>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        import_path: Path,
        alias: Option<I>,
        value: Option<V>,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>;
    }

    impl<P: Partitions> $trait_name<P> for PartitionWriteContextRef<'_, P> {
      fn $clear<V, I, Path>(
        &mut self,
        file_prefix: &Path,
      ) where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores: HasPartition<$idx<V, I, Path>>,
      {
        self.clear_prefix(
          $idx::<V, I, Path>::KEY,
          SymbolSortKey::from_path(file_prefix),
        );
      }

      fn [<$prefix definition>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        name: I,
        value: Option<V>,
        visibility: crate::core::SymbolVisibility,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>,
      {
        let shape = Symbol::Definition { name, value, visibility };
        super::symbol_writer_macro::write_shape_and_index::<P, V, I, Path, $idx<V, I, Path>>(
          self, path, span, shape,
        )
      }

      fn [<$prefix reference>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        name: Option<I>,
        target_path: Path,
        is_absolute: bool,
        nameable: bool,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>,
      {
        let shape = Symbol::Reference { path: path.clone(), name, target_path, is_absolute, nameable };
        super::symbol_writer_macro::write_shape_and_index::<P, V, I, Path, $idx<V, I, Path>>(
          self, path, span, shape,
        )
      }

      fn [<$prefix scope>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        value: Option<V>,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>,
      {
        let shape = Symbol::Scope { value };
        super::symbol_writer_macro::write_shape_and_index::<P, V, I, Path, $idx<V, I, Path>>(
          self, path, span, shape,
        )
      }

      fn [<$prefix keyword>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        name: I,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>,
      {
        let shape = Symbol::Keyword { name };
        super::symbol_writer_macro::write_shape_and_index::<P, V, I, Path, $idx<V, I, Path>>(
          self, path, span, shape,
        )
      }

      fn [<$prefix value>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        value: V,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>,
      {
        let shape = Symbol::Value { value };
        super::symbol_writer_macro::write_shape_and_index::<P, V, I, Path, $idx<V, I, Path>>(
          self, path, span, shape,
        )
      }

      fn [<$prefix import>]<V, I, Path>(
        &mut self,
        path: Path,
        span: laburnum::Span,
        import_path: Path,
        alias: Option<I>,
        value: Option<V>,
      ) -> RecordHandle<Symbols<V, I, Path>>
      where
        V: Value<I> + 'static,
        I: Ident,
        Path: SymbolPath + 'static,
        P::Stores:
          HasPartition<Symbols<V, I, Path>> + HasPartition<$idx<V, I, Path>>,
      {
        let shape = Symbol::Import { path: import_path, alias, value };
        super::symbol_writer_macro::write_shape_and_index::<P, V, I, Path, $idx<V, I, Path>>(
          self, path, span, shape,
        )
      }
    }

    } // paste!
  };
}

pub(super) use define_symbol_writer;