meta_language/parser_registry.rs
1use std::collections::HashMap;
2use std::fmt;
3use std::sync::Arc;
4
5use crate::language_parser::{BuiltInLanguageParser, LanguageParser};
6use crate::{LinkNetwork, ParseConfiguration};
7
8/// A pluggable dispatch table mapping language keys to [`LanguageParser`]s.
9///
10/// The registry starts with the [`BuiltInLanguageParser`] as a fallback, so an
11/// unmodified registry behaves exactly like [`LinkNetwork::parse`]: `lino` is
12/// handled by the links-notation parser and every other key is routed through
13/// the tree-sitter adapter with a lossless text fallback.
14///
15/// Users register parsers for new language keys or override an existing one.
16/// Following TXL's grammar-override model and SWC's plugin lesson, a user
17/// registration *shadows* the built-in dispatch for the same key rather than
18/// forking the pipeline: any key without an explicit registration still falls
19/// through to the built-in set.
20///
21/// Language keys are matched case-insensitively, mirroring the built-in
22/// dispatch (`LINO`, `Lino`, and `lino` resolve to the same parser).
23///
24/// # Examples
25///
26/// ```
27/// use std::sync::Arc;
28/// use meta_language::{
29/// LanguageParser, LinkNetwork, ParseConfiguration, ParserRegistry,
30/// };
31///
32/// #[derive(Debug)]
33/// struct ShoutParser;
34///
35/// impl LanguageParser for ShoutParser {
36/// fn parse_source(
37/// &self,
38/// text: &str,
39/// language: &str,
40/// configuration: ParseConfiguration,
41/// ) -> LinkNetwork {
42/// LinkNetwork::parse_lossless_text(&text.to_uppercase(), language, configuration)
43/// }
44/// }
45///
46/// let registry = ParserRegistry::new().with_parser("shout", Arc::new(ShoutParser));
47/// let network = registry.parse("hi", "shout", ParseConfiguration::default());
48/// assert_eq!(network.reconstruct_text(), "HI");
49/// ```
50#[derive(Clone)]
51pub struct ParserRegistry {
52 parsers: HashMap<String, Arc<dyn LanguageParser>>,
53 fallback: Arc<dyn LanguageParser>,
54}
55
56impl ParserRegistry {
57 /// Creates a registry backed by the [`BuiltInLanguageParser`] fallback and
58 /// no user registrations.
59 #[must_use]
60 pub fn new() -> Self {
61 Self {
62 parsers: HashMap::new(),
63 fallback: Arc::new(BuiltInLanguageParser),
64 }
65 }
66
67 /// Registers `parser` for `language`, shadowing any prior registration or
68 /// built-in dispatch for the same (case-insensitive) key.
69 ///
70 /// Returns `&mut Self` so registrations can be chained.
71 pub fn register(
72 &mut self,
73 language: impl Into<String>,
74 parser: Arc<dyn LanguageParser>,
75 ) -> &mut Self {
76 let key: String = language.into();
77 self.parsers.insert(normalize(&key), parser);
78 self
79 }
80
81 /// Builder-style variant of [`register`](Self::register) that consumes and
82 /// returns the registry.
83 #[must_use]
84 pub fn with_parser(
85 mut self,
86 language: impl Into<String>,
87 parser: Arc<dyn LanguageParser>,
88 ) -> Self {
89 self.register(language, parser);
90 self
91 }
92
93 /// Returns the parser explicitly registered for `language`, if any.
94 ///
95 /// Keys served by the built-in fallback report `None`; use
96 /// [`parse`](Self::parse) to dispatch including the fallback.
97 #[must_use]
98 pub fn parser_for(&self, language: &str) -> Option<&Arc<dyn LanguageParser>> {
99 self.parsers.get(&normalize(language))
100 }
101
102 /// Whether `language` has an explicit (non-fallback) registration.
103 #[must_use]
104 pub fn is_registered(&self, language: &str) -> bool {
105 self.parsers.contains_key(&normalize(language))
106 }
107
108 /// The number of explicit registrations, excluding the built-in fallback.
109 #[must_use]
110 pub fn len(&self) -> usize {
111 self.parsers.len()
112 }
113
114 /// Whether the registry holds no explicit registrations.
115 ///
116 /// An empty registry still parses every key through the built-in fallback.
117 #[must_use]
118 pub fn is_empty(&self) -> bool {
119 self.parsers.is_empty()
120 }
121
122 /// Parses `text` for `language`, dispatching to a registered parser when
123 /// one shadows the key and otherwise to the built-in fallback.
124 #[must_use]
125 pub fn parse(
126 &self,
127 text: &str,
128 language: &str,
129 configuration: ParseConfiguration,
130 ) -> LinkNetwork {
131 self.parsers.get(&normalize(language)).map_or_else(
132 || self.fallback.parse_source(text, language, configuration),
133 |parser| parser.parse_source(text, language, configuration),
134 )
135 }
136}
137
138impl Default for ParserRegistry {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl LinkNetwork {
145 /// Parses source text through a pluggable [`ParserRegistry`].
146 ///
147 /// Dispatch honors user registrations, which shadow the built-in set for
148 /// the same language key; keys without an explicit registration fall
149 /// through to the same built-in dispatch [`LinkNetwork::parse`] uses.
150 #[must_use]
151 pub fn parse_with_registry(
152 registry: &ParserRegistry,
153 text: &str,
154 language: &str,
155 configuration: ParseConfiguration,
156 ) -> Self {
157 registry.parse(text, language, configuration)
158 }
159}
160
161impl fmt::Debug for ParserRegistry {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 let mut keys: Vec<&str> = self.parsers.keys().map(String::as_str).collect();
164 keys.sort_unstable();
165 f.debug_struct("ParserRegistry")
166 .field("registered", &keys)
167 .finish_non_exhaustive()
168 }
169}
170
171/// Normalizes a language key for case-insensitive lookup, matching the
172/// built-in dispatch's `eq_ignore_ascii_case` handling.
173fn normalize(language: &str) -> String {
174 language.to_ascii_lowercase()
175}