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

use std::{
  fmt::{self, Debug, Display},
  hash::Hash,
};

/// Trait for identifier types used in the symbol table.
///
/// `Ident` is a marker trait that ensures identifier types have the necessary
/// properties for use in symbol tables: they must be debuggable, cloneable,
/// comparable for equality, hashable, and thread-safe.
///
/// This trait allows different identifier representations:
/// - **Owned strings** - Using `StringIdent` for simplicity
/// - **Interned strings** - Using string interning crates for memory efficiency
/// - **Custom types** - Any type meeting the trait requirements
///
/// # Example
///
/// ```rust
/// use symbolique::StringIdent;
/// let ident = StringIdent::from("my_variable");
/// ```
///
/// # Why a Marker Trait?
///
/// The trait has no required methods because different identifier types
/// may have different ways of accessing their string content. For example,
/// `StringIdent` provides `as_str()`, while interned types require an interner
/// to resolve to a string. This design allows maximum flexibility.
pub trait Ident:
  Debug + Clone + PartialEq + Eq + PartialOrd + Ord + Hash + Send + Sync + 'static
{
  /// Returns true if the identifier is empty.
  ///
  /// Useful when you want an unnameable identifier.
  fn is_none(&self) -> bool;

  /// Returns true if path resolution should stop at this identifier.
  fn is_end_of_path(&self) -> bool {
    false
  }

  /// Returns the span associated with this identifier, if any.
  ///
  /// Most identifier implementations don't store spans, so this defaults to
  /// None. Override this method if your identifier type carries source
  /// location information.
  fn span(&self) -> Option<laburnum::Span> {
    None
  }
}

/// A simple owned-string identifier implementation.
///
/// `StringIdent` stores identifiers as owned `String` values. This is the
/// simplest identifier type but may use more memory than interned alternatives
/// when many symbols share the same names.
///
/// # When to Use
///
/// Use `StringIdent` when:
/// - Simplicity is more important than memory efficiency
/// - The number of unique identifiers is relatively small
/// - You don't want to manage a string interner
///
/// For large codebases with many repeated identifiers, consider using
/// `laburnum::Ident` or a string interning crate instead.
///
/// # Example
///
/// ```rust
/// # use symbolique::StringIdent;
/// let ident = StringIdent::new("my_function");
/// assert_eq!(ident.as_str(), "my_function");
///
/// // Can be created from various string types
/// let from_string = StringIdent::from(String::from("variable"));
/// let from_str = StringIdent::from("constant");
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StringIdent(String);

impl StringIdent {
  /// Creates a new identifier from any type convertible to `String`.
  ///
  /// # Arguments
  ///
  /// * `s` - Any value that implements `Into<String>`
  ///
  /// # Example
  ///
  /// ```rust
  /// # use symbolique::StringIdent;
  /// let ident = StringIdent::new("my_variable");
  /// let ident2 = StringIdent::new(String::from("my_function"));
  /// ```
  pub fn new(s: impl Into<String>) -> Self {
    StringIdent(s.into())
  }

  /// Returns the identifier as a string slice.
  ///
  /// This method is specific to `StringIdent` and not part of the `Ident`
  /// trait, allowing direct access to the underlying string without an
  /// interner.
  ///
  /// # Example
  ///
  /// ```rust
  /// # use symbolique::StringIdent;
  /// let ident = StringIdent::from("example");
  /// assert_eq!(ident.as_str(), "example");
  /// ```
  pub fn as_str(&self) -> &str {
    &self.0
  }
}

impl Ident for StringIdent {
  fn is_none(&self) -> bool {
    false
  }

  fn span(&self) -> Option<laburnum::Span> {
    None
  }
}

impl Display for StringIdent {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "{}", self.0)
  }
}

impl From<String> for StringIdent {
  fn from(s: String) -> Self {
    StringIdent(s)
  }
}

impl From<&str> for StringIdent {
  fn from(s: &str) -> Self {
    StringIdent(s.to_string())
  }
}

impl AsRef<str> for StringIdent {
  fn as_ref(&self) -> &str {
    &self.0
  }
}

// Implement symbolique::Ident for laburnum::Ident
impl Ident for laburnum::Ident {
  fn is_none(&self) -> bool {
    self.0 == 0
  }

  fn span(&self) -> Option<laburnum::Span> {
    None
  }
}

// Implement symbolique::Ident for Spanned<laburnum::Ident>
impl Ident for laburnum::Spanned<laburnum::Ident> {
  fn is_none(&self) -> bool {
    self.0.is_none()
  }

  fn is_end_of_path(&self) -> bool {
    false
  }

  fn span(&self) -> Option<laburnum::Span> {
    Some(self.1)
  }
}

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

  #[test]
  fn string_ident_new() {
    let ident = StringIdent::new("foo");
    assert_eq!(ident.as_str(), "foo");
  }

  #[test]
  fn string_ident_from_string() {
    let ident = StringIdent::from(String::from("bar"));
    assert_eq!(ident.as_str(), "bar");
  }

  #[test]
  fn string_ident_from_str() {
    let ident = StringIdent::from("baz");
    assert_eq!(ident.as_str(), "baz");
  }

  #[test]
  fn string_ident_as_str() {
    let ident = StringIdent::new("hello");
    assert_eq!(ident.as_str(), "hello");
  }

  #[test]
  fn string_ident_display() {
    let ident = StringIdent::new("world");
    assert_eq!(format!("{}", ident), "world");
  }

  #[test]
  fn string_ident_is_none_returns_false() {
    let ident = StringIdent::new("anything");
    assert!(!ident.is_none());
  }

  #[test]
  fn string_ident_equality() {
    let a = StringIdent::new("same");
    let b = StringIdent::new("same");
    assert_eq!(a, b);
  }

  #[test]
  fn string_ident_ordering() {
    let a = StringIdent::new("aaa");
    let b = StringIdent::new("bbb");
    assert!(a < b);
  }

  #[test]
  fn laburnum_ident_is_none_when_zero() {
    let zero = laburnum::Ident(0);
    assert!(zero.is_none());

    let nonzero = laburnum::Ident(1);
    assert!(!nonzero.is_none());
  }
}