Skip to main content

telltale_runtime/compiler/
extension_parser.rs

1//! Extension-aware parser that integrates with the existing parser
2//!
3//! This module provides a parser that can handle both standard choreographic
4//! constructs and extension-defined syntax by leveraging the grammar composition system.
5
6use crate::ast::{Protocol, Role};
7use crate::compiler::grammar::{GrammarComposer, GrammarCompositionError};
8use crate::compiler::parser::{parse_choreography_str, ParseError};
9use crate::extensions::StatementParser;
10
11/// Extension-aware parser that can handle both core and extension syntax
12pub struct ExtensionParser {
13    grammar_composer: GrammarComposer,
14    /// Pre-allocated buffer for parsing operations to reduce allocations
15    parse_buffer: String,
16    /// Pre-allocated HashMap for annotations to reduce allocations
17    annotation_cache: std::collections::HashMap<String, String>,
18}
19
20impl ExtensionParser {
21    /// Create a new extension parser
22    pub fn new() -> Self {
23        Self {
24            grammar_composer: GrammarComposer::new(),
25            parse_buffer: String::with_capacity(1024), // Pre-allocate for common case
26            annotation_cache: std::collections::HashMap::with_capacity(16), // Pre-allocate for annotations
27        }
28    }
29
30    /// Register an extension with both grammar and parsing support
31    ///
32    /// Note: Grammar extensions are fully supported and registered with the grammar composer.
33    /// Statement parsers are accepted but not currently stored. Extension parsing passes
34    /// through the standard parser - full extension statement dispatch is a planned feature.
35    ///
36    /// # Errors
37    ///
38    /// Returns an error if the grammar extension cannot be registered.
39    pub fn register_extension<G, P>(
40        &mut self,
41        grammar_ext: G,
42        _statement_parser: P,
43    ) -> Result<(), crate::extensions::ParseError>
44    where
45        G: crate::extensions::GrammarExtension + 'static,
46        P: StatementParser + 'static,
47    {
48        // Register grammar extension (this also registers the grammar rules for rule mapping)
49        self.grammar_composer.register_extension(grammar_ext)?;
50
51        // Statement parser registration is accepted but not stored.
52        // Full extension statement dispatch will require extending GrammarComposer
53        // to track parsers alongside grammar rules.
54        Ok(())
55    }
56
57    /// Parse a choreography string with extension support (optimized)
58    pub fn parse_with_extensions(
59        &mut self,
60        input: &str,
61    ) -> Result<crate::ast::Choreography, ExtensionParseError> {
62        // Clear and reuse buffers to reduce allocations
63        self.parse_buffer.clear();
64        self.annotation_cache.clear();
65
66        // Reserve capacity based on input size for efficient parsing
67        self.parse_buffer.reserve(input.len());
68
69        // Hybrid approach: parse with standard parser, then post-process
70        // to handle any extension statements
71
72        let mut choreography =
73            parse_choreography_str(input).map_err(ExtensionParseError::StandardParseError)?;
74
75        // Post-process to identify and parse extension statements (optimized)
76        choreography.protocol =
77            self.process_extensions_optimized(choreography.protocol, input, &choreography.roles)?;
78
79        Ok(choreography)
80    }
81
82    /// Process a protocol tree to identify and dispatch extension statements.
83    ///
84    /// Returns the protocol unchanged. Extension statement dispatch is a reserved
85    /// extension point: the grammar composer identifies extension statements,
86    /// but this method does not yet transform them.
87    fn process_extensions_optimized(
88        &mut self,
89        protocol: Protocol,
90        _input: &str,
91        _roles: &[Role],
92    ) -> Result<Protocol, ExtensionParseError> {
93        Ok(protocol)
94    }
95
96    /// Check if an extension can handle a given statement
97    pub fn can_handle_statement(&self, statement_type: &str) -> bool {
98        self.grammar_composer.has_extension_rule(statement_type)
99    }
100
101    /// Get the composed grammar for debugging
102    pub fn get_composed_grammar(&mut self) -> Result<String, GrammarCompositionError> {
103        self.grammar_composer.compose()
104    }
105
106    /// Get statistics about registered extensions
107    pub fn extension_stats(&self) -> ExtensionStats {
108        ExtensionStats {
109            grammar_extensions: self.grammar_composer.extension_count(),
110            // Statement parsers are not stored separately (see register_extension doc)
111            statement_parsers: 0,
112        }
113    }
114}
115
116impl Default for ExtensionParser {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122/// Statistics about registered extensions
123#[derive(Debug, Clone)]
124pub struct ExtensionStats {
125    pub grammar_extensions: usize,
126    pub statement_parsers: usize,
127}
128
129/// Errors that can occur during extension-aware parsing
130#[derive(Debug, thiserror::Error)]
131pub enum ExtensionParseError {
132    #[error("Standard parsing failed: {0}")]
133    StandardParseError(#[from] ParseError),
134
135    #[error("Grammar composition failed: {0}")]
136    GrammarComposition(#[from] GrammarCompositionError),
137
138    #[error("Extension parsing failed: {0}")]
139    ExtensionParsing(String),
140
141    #[error("Unknown extension statement: {0}")]
142    UnknownExtension(String),
143}
144
145/// Builder for extension parsers
146pub struct ExtensionParserBuilder {
147    parser: ExtensionParser,
148}
149
150impl ExtensionParserBuilder {
151    pub fn new() -> Self {
152        Self {
153            parser: ExtensionParser::new(),
154        }
155    }
156
157    pub fn with_extension<G, P>(
158        mut self,
159        grammar_ext: G,
160        statement_parser: P,
161    ) -> Result<Self, crate::extensions::ParseError>
162    where
163        G: crate::extensions::GrammarExtension + 'static,
164        P: StatementParser + 'static,
165    {
166        self.parser
167            .register_extension(grammar_ext, statement_parser)?;
168        Ok(self)
169    }
170
171    pub fn build(self) -> ExtensionParser {
172        self.parser
173    }
174}
175
176impl Default for ExtensionParserBuilder {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181
182/// Helper function to create an extension parser with common extensions
183pub fn create_standard_extension_parser() -> ExtensionParser {
184    ExtensionParserBuilder::new()
185        // Add standard extensions here as they're developed
186        .build()
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use crate::extensions::{GrammarExtension, ParseContext, StatementParser};
193
194    #[derive(Debug)]
195    struct TestGrammarExtension;
196
197    impl GrammarExtension for TestGrammarExtension {
198        fn grammar_rules(&self) -> &'static str {
199            "test_stmt = { \"test\" ~ ident }"
200        }
201
202        fn statement_rules(&self) -> Vec<&'static str> {
203            vec!["test_stmt"]
204        }
205
206        fn extension_id(&self) -> &'static str {
207            "test_extension"
208        }
209    }
210
211    #[derive(Debug)]
212    struct TestStatementParser;
213
214    impl StatementParser for TestStatementParser {
215        fn can_parse(&self, rule_name: &str) -> bool {
216            rule_name == "test_stmt"
217        }
218
219        fn supported_rules(&self) -> Vec<String> {
220            vec!["test_stmt".to_string()]
221        }
222
223        fn parse_statement(
224            &self,
225            _rule_name: &str,
226            _content: &str,
227            _context: &ParseContext,
228        ) -> Result<Box<dyn crate::extensions::ProtocolExtension>, crate::extensions::ParseError>
229        {
230            // This would normally parse the statement content
231            Err(crate::extensions::ParseError::Syntax {
232                message: "Test parser - not implemented".to_string(),
233            })
234        }
235    }
236
237    #[test]
238    fn test_extension_parser_creation() {
239        let parser = ExtensionParser::new();
240        let stats = parser.extension_stats();
241        assert_eq!(stats.grammar_extensions, 0);
242    }
243
244    #[test]
245    fn test_extension_registration() {
246        let mut parser = ExtensionParser::new();
247        parser
248            .register_extension(TestGrammarExtension, TestStatementParser)
249            .expect("extension should register");
250
251        let stats = parser.extension_stats();
252        assert_eq!(stats.grammar_extensions, 1);
253        assert!(parser.can_handle_statement("test_stmt"));
254    }
255
256    #[test]
257    fn test_builder_pattern() {
258        let parser = ExtensionParserBuilder::new()
259            .with_extension(TestGrammarExtension, TestStatementParser)
260            .expect("test extension should register")
261            .build();
262
263        assert!(parser.can_handle_statement("test_stmt"));
264    }
265
266    #[test]
267    fn test_standard_parsing() {
268        let mut parser = ExtensionParser::new();
269
270        let input = "protocol TestProtocol =\n  roles Alice, Bob\n  Alice -> Bob : Message\n";
271
272        let result = parser.parse_with_extensions(input);
273        assert!(result.is_ok(), "Should parse standard choreography");
274    }
275
276    #[test]
277    fn test_composed_grammar_generation() {
278        let mut parser = ExtensionParser::new();
279        parser
280            .register_extension(TestGrammarExtension, TestStatementParser)
281            .expect("extension should register");
282
283        let result = parser.get_composed_grammar();
284        assert!(result.is_ok(), "Should generate composed grammar");
285
286        let grammar = result.unwrap();
287        assert!(
288            grammar.contains("test_stmt"),
289            "Should contain extension rule"
290        );
291        assert!(
292            grammar.contains("choreography"),
293            "Should contain base rules"
294        );
295    }
296}