# symbolique

Generic symbol table pipeline for compilers and language servers, built on laburnum's partition system.

## Pipeline

Symbols flow through 4 stages, each backed by a laburnum partition:

```text
     Symbols (CAS)             ← Content-addressed symbol storage (shared across all stages)
         │
    FileSymbols                ← Stage 1: Parse - per-file symbol index
         │
   LinkedSymbols               ← Stage 2: Link - cross-file import resolution
         │
   MergedSymbols               ← Stage 3: Merge - combined namespace
         │
  SymbolResolution             ← Stage 4: Resolve - reference → definition mapping
```

Each stage writes index entries pointing to shared `Symbol` records in the CAS partition.

## Integration with Laburnum

The pipeline stages map to laburnum's task-based incremental computation model:

| Stage   | Partition          | Writer Trait                | Invalidation                     |
| ------- | ------------------ | --------------------------- | -------------------------------- |
| Parse   | `FileSymbols`      | `SymboliqueWriteExt`        | When file content changes        |
| Link    | `LinkedSymbols`    | `LinkedSymboliqueWriteExt`  | When imported file changes       |
| Merge   | `MergedSymbols`    | `MergedSymboliqueWriteExt`  | When any file in package changes |
| Resolve | `SymbolResolution` | `ResolutionWriteExt`        | When any linked table changes    |

All writes go through `PartitionWriteContextRef` using the writer extension traits.

## Type Parameters

All partition types are generic over four parameters:

| Parameter | Trait             | Purpose                         |
| --------- | ----------------- | ------------------------------- |
| `V`       | `Value<I>`        | Literal values in your language |
| `I`       | `Ident`           | Identifier representation       |
| `P`       | `SymbolPath`      | Path representation (default: `String`) |
| `S`       | `Visibility`      | Definition visibility (default: `SymbolVisibility`) |

Default implementations are provided:

- `StringIdent` - String-based identifiers
- `DefaultValue` - Common literal types (String, Integer, Float, Boolean)
- `SymbolVisibility` - Public/private visibility

## Visibility

`Symbol::Definition` carries a visibility value of type `S: Visibility`. The
`Visibility` trait has one behaviour method, `is_visible_from`, which decides
whether a definition is reachable from a given reference site:

```rust,ignore
pub trait Visibility:
  Copy + Debug + Clone + PartialEq + Eq + Hash + Send + Sync + 'static
{
    fn is_visible_from<P: SymbolPath>(
        &self,
        defined_at: &P,
        referenced_from: &P,
    ) -> bool {
        let _ = (defined_at, referenced_from);
        true // default: always visible
    }
}
```

The built-in `SymbolVisibility` implements `Public` (always visible) and
`Private` (visible within the defining scope and its descendants). Languages
needing richer access control (`pub(crate)`, `protected`, region-scoped
privacy) define their own `Visibility` type and override `is_visible_from`.

## Symbol Types

The `Symbol` enum represents 6 patterns:

| Variant      | Purpose                          | Example                     |
| ------------ | -------------------------------- | --------------------------- |
| `Definition` | Named entities                   | Functions, variables, types |
| `Reference`  | Names referring to other symbols | Variable usage, path access |
| `Scope`      | Anonymous containers             | Blocks, namespaces          |
| `Keyword`    | Reserved words                   | `if`, `let`, `fn`           |
| `Value`      | Literal values                   | `42`, `"hello"`             |
| `Import`     | Imported symbols                 | `use math::add`             |

## Writing Symbols

Each stage has a writer extension trait on `PartitionWriteContextRef`. Writers store the `Symbol` record in the CAS partition and create a `SymbolEntry` index entry in the stage's partition.

```rust,ignore
use symbolique::{SymboliqueWriteExt, SymbolVisibility};

// In an on_file_version hook or similar task:
writer.write_symbol_definition(
    "math.add".to_string(),    // path
    span,                      // laburnum::Span
    ident,                     // I: Ident
    None,                      // value: Option<V>
    SymbolVisibility::Public,
);

writer.write_symbol_reference(
    "main.ref_to_add".to_string(), // path
    span,                          // laburnum::Span
    Some(ident),                   // name: Option<I>
    "math.add".to_string(),        // target_path
    false,                         // is_absolute
    true,                          // nameable
);
```

The linked and merged stages follow the same pattern with `LinkedSymboliqueWriteExt` and `MergedSymboliqueWriteExt`.

## Resolution

The resolution stage maps references to their targets using `ResolutionWriteExt`:

```rust,ignore
use symbolique::ResolutionWriteExt;

writer.write_resolution(
    reference_path,   // P - the reference's path
    target_path,      // P - the resolved target's path
    target_handle,    // RecordHandle<Symbols<V, I, P>> - handle to the target symbol
);
```

## Custom Resolution

Implement `SymboliqueResolver` for your language's resolution behavior:

```rust,ignore
use symbolique::{SymboliqueResolver, Resolution, SymbolEntry, Value, Ident, SymbolPath, Visibility};

struct MyResolver;

impl<V, I, P, S> SymboliqueResolver<V, I, P, S> for MyResolver
where
    V: Value<I>,
    I: Ident + std::hash::Hash,
    P: SymbolPath,
    S: Visibility,
{
    fn resolve(
        &self,
        path: &P,
        from_path: &P,
        from_scope: SymbolEntry<V, I, P, S>,
    ) -> Resolution<V, I, P, S> {
        // Walk scopes (from `from_scope`) to find the target for `path`.
        // Once a candidate definition is found, enforce visibility:
        //   if target_visibility.is_visible_from(&target_path, from_path) { .. }
        // Return Resolution::Resolved(target_entry) on success
        // Return Resolution::Deferred for multi-pass resolution
        // Return Resolution::Failed(msg) on error
        todo!()
    }
}
```

The `Resolution` enum has these variants:

- `Resolved(SymbolEntry)` - Successfully resolved
- `Deferred` - Process in next pass (for multi-pass resolution)
- `Phantom(PhantomSymbol)` - Resolved to external/synthetic symbol
- `WrongKind { found, expected_kinds }` - Found but wrong type
- `Failed(String)` - Resolution failed with error message

## Key Types

### SymbolEntry

The index entry type used across all stages. Pairs a span with a content-addressed handle to the symbol record:

```rust,ignore
pub struct SymbolEntry<V, I, P> {
    pub span: laburnum::Span,
    pub symbol: RecordHandle<Symbols<V, I, P>>,
}
```

### ResolutionEntry

The index entry for the resolution stage. Maps a reference to its resolved target:

```rust,ignore
pub struct ResolutionEntry<V, I, P> {
    pub target_path: P,
    pub target: RecordHandle<Symbols<V, I, P>>,
}
```
