phase 2: syntax plugin interface and registry
Add the Lexer/Parser/SyntaxPlugin traits and the SyntaxRegistry per SPEC.md §6.3. - `Lexer<Token>` and `Parser<Token>` are typed building blocks: each syntax owns its own token alphabet (vimwiki today, markdown later). - `TokenStream<T>` is a `Vec<T>` newtype so the eager-vs-lazy choice (§6.6) can flip later without breaking callers. - `SyntaxPlugin` is type-erased: it exposes id / display_name / file_extensions / parse(text) so the registry can hold heterogeneous plugins behind a single trait object. Implementations chain their Lexer + Parser inside parse(). - `SyntaxPlugin: Send + Sync` so the LSP server can stash plugins in an Arc and share them across handler tasks. - `SyntaxRegistry` stores `Arc<dyn SyntaxPlugin>`, supports lookup by id and by file extension, iteration in registration order. Tests cover TokenStream round-trip, full lex→parse chain through a mock plugin, registry lookup hits and misses, and a compile-time assert_send_sync::<SyntaxRegistry>(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
//! Registry of syntax plugins. See SPEC.md §6.3.
|
||||
//!
|
||||
//! Plugins are stored behind `Arc<dyn SyntaxPlugin>` so the LSP layer can
|
||||
//! hand a cheap clone to per-document tasks without re-locking the registry.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::SyntaxPlugin;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct SyntaxRegistry {
|
||||
plugins: Vec<Arc<dyn SyntaxPlugin>>,
|
||||
}
|
||||
|
||||
impl SyntaxRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Register a plugin owned directly. The most common shape — implementer
|
||||
/// hands over a value, registry takes ownership.
|
||||
pub fn register<P>(&mut self, plugin: P)
|
||||
where
|
||||
P: SyntaxPlugin + 'static,
|
||||
{
|
||||
self.plugins.push(Arc::new(plugin));
|
||||
}
|
||||
|
||||
/// Register a pre-arc'd plugin. Useful when the same plugin instance
|
||||
/// also needs to be held elsewhere.
|
||||
pub fn register_arc(&mut self, plugin: Arc<dyn SyntaxPlugin>) {
|
||||
self.plugins.push(plugin);
|
||||
}
|
||||
|
||||
/// Look up a plugin by its `id`.
|
||||
pub fn get(&self, id: &str) -> Option<&dyn SyntaxPlugin> {
|
||||
self.plugins
|
||||
.iter()
|
||||
.find(|p| p.id() == id)
|
||||
.map(|p| p.as_ref())
|
||||
}
|
||||
|
||||
/// Look up a plugin by file extension. `ext` should include the leading
|
||||
/// dot (e.g. `".wiki"`) — matches the plugin's `file_extensions()`.
|
||||
pub fn detect_from_extension(&self, ext: &str) -> Option<&dyn SyntaxPlugin> {
|
||||
self.plugins
|
||||
.iter()
|
||||
.find(|p| p.file_extensions().iter().any(|e| *e == ext))
|
||||
.map(|p| p.as_ref())
|
||||
}
|
||||
|
||||
/// Iterate over registered plugins in registration order.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &dyn SyntaxPlugin> + '_ {
|
||||
self.plugins.iter().map(|p| p.as_ref())
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.plugins.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.plugins.is_empty()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user