telltale_runtime/compiler/
extension_parser.rs1use crate::compiler::grammar::GrammarCompositionError;
8use crate::compiler::parser::{parse_choreography_str_with_extensions, ParseError};
9use crate::extensions::{ExtensionRegistry, GrammarExtension, StatementParser};
10
11pub struct ExtensionParser {
13 extension_registry: ExtensionRegistry,
14 parse_buffer: String,
16}
17
18impl ExtensionParser {
19 pub fn new() -> Self {
21 Self {
22 extension_registry: ExtensionRegistry::new(),
23 parse_buffer: String::with_capacity(1024), }
25 }
26
27 pub fn register_extension<G, P>(
33 &mut self,
34 grammar_ext: G,
35 statement_parser: P,
36 ) -> Result<(), crate::extensions::ParseError>
37 where
38 G: GrammarExtension + 'static,
39 P: StatementParser + 'static,
40 {
41 let parser_id = grammar_ext.extension_id().to_string();
42 self.extension_registry.register_grammar(grammar_ext)?;
43 self.extension_registry
44 .register_parser(statement_parser, parser_id);
45 Ok(())
46 }
47
48 pub fn parse_with_extensions(
50 &mut self,
51 input: &str,
52 ) -> Result<crate::ast::Choreography, ExtensionParseError> {
53 self.parse_buffer.clear();
55
56 self.parse_buffer.reserve(input.len());
58 parse_choreography_str_with_extensions(input, &self.extension_registry)
59 .map(|(choreography, _)| choreography)
60 .map_err(ExtensionParseError::StandardParseError)
61 }
62
63 pub fn can_handle_statement(&self, statement_type: &str) -> bool {
65 self.extension_registry.can_handle(statement_type)
66 }
67
68 pub fn get_composed_grammar(&self) -> Result<String, GrammarCompositionError> {
70 compose_grammar_from_registry(&self.extension_registry)
71 }
72
73 pub fn extension_stats(&self) -> ExtensionStats {
75 ExtensionStats {
76 grammar_extensions: self.extension_registry.grammar_extensions().count(),
77 statement_parsers: self.extension_registry.statement_parser_count(),
78 }
79 }
80}
81
82impl Default for ExtensionParser {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88fn compose_grammar_from_registry(
89 registry: &ExtensionRegistry,
90) -> Result<String, GrammarCompositionError> {
91 let base_grammar = include_str!("choreography.pest");
92 let extension_rules = registry.compose_grammar("");
93 if extension_rules.trim().is_empty() {
94 return Ok(base_grammar.to_string());
95 }
96
97 let statement_rules: Vec<_> = registry.statement_rules();
98 let mut lines: Vec<String> = base_grammar.lines().map(ToOwned::to_owned).collect();
99 let (stmt_start, stmt_end) = find_statement_rule_bounds(&lines)?;
100 let indent = find_statement_indent(&lines, stmt_start, stmt_end);
101 let insert_lines: Vec<String> = statement_rules
102 .iter()
103 .map(|rule| format!("{indent}| {rule}"))
104 .collect();
105 lines.splice(stmt_end..stmt_end, insert_lines);
106
107 let mut composed = lines.join("\n");
108 composed.push('\n');
109 composed.push_str("// Extension Rules\n");
110 composed.push_str(&extension_rules);
111 Ok(composed)
112}
113
114fn find_statement_rule_bounds(lines: &[String]) -> Result<(usize, usize), GrammarCompositionError> {
115 let start = lines
116 .iter()
117 .position(|line| line.trim_start().starts_with("statement = _{"))
118 .ok_or_else(|| {
119 GrammarCompositionError::InvalidBaseGrammar(
120 "Could not find statement rule in base grammar".to_string(),
121 )
122 })?;
123
124 for (idx, line) in lines.iter().enumerate().skip(start + 1) {
125 if line.trim_start().starts_with('}') {
126 return Ok((start, idx));
127 }
128 }
129
130 Err(GrammarCompositionError::InvalidBaseGrammar(
131 "Could not find end of statement rule in base grammar".to_string(),
132 ))
133}
134
135fn find_statement_indent(lines: &[String], start: usize, end: usize) -> String {
136 for line in lines.iter().take(end).skip(start + 1) {
137 let trimmed = line.trim_start();
138 if trimmed.starts_with('|') {
139 let indent_len = line.len().saturating_sub(trimmed.len());
140 return line[..indent_len].to_string();
141 }
142 }
143 " ".to_string()
144}
145
146#[derive(Debug, Clone)]
148pub struct ExtensionStats {
149 pub grammar_extensions: usize,
150 pub statement_parsers: usize,
151}
152
153#[derive(Debug, thiserror::Error)]
155pub enum ExtensionParseError {
156 #[error("Standard parsing failed: {0}")]
157 StandardParseError(#[from] ParseError),
158
159 #[error("Grammar composition failed: {0}")]
160 GrammarComposition(#[from] GrammarCompositionError),
161
162 #[error("Extension parsing failed: {0}")]
163 ExtensionParsing(String),
164
165 #[error("Unknown extension statement: {0}")]
166 UnknownExtension(String),
167}
168
169pub struct ExtensionParserBuilder {
171 parser: ExtensionParser,
172}
173
174impl ExtensionParserBuilder {
175 pub fn new() -> Self {
176 Self {
177 parser: ExtensionParser::new(),
178 }
179 }
180
181 pub fn with_extension<G, P>(
182 mut self,
183 grammar_ext: G,
184 statement_parser: P,
185 ) -> Result<Self, crate::extensions::ParseError>
186 where
187 G: crate::extensions::GrammarExtension + 'static,
188 P: StatementParser + 'static,
189 {
190 self.parser
191 .register_extension(grammar_ext, statement_parser)?;
192 Ok(self)
193 }
194
195 pub fn build(self) -> ExtensionParser {
196 self.parser
197 }
198}
199
200impl Default for ExtensionParserBuilder {
201 fn default() -> Self {
202 Self::new()
203 }
204}
205
206pub fn create_standard_extension_parser() -> ExtensionParser {
208 ExtensionParserBuilder::new()
209 .build()
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::extensions::{GrammarExtension, ParseContext, StatementParser};
217
218 #[derive(Debug)]
219 struct TestGrammarExtension;
220
221 impl GrammarExtension for TestGrammarExtension {
222 fn grammar_rules(&self) -> &'static str {
223 "test_extension_stmt = { \"test\" ~ ident }"
224 }
225
226 fn statement_rules(&self) -> Vec<&'static str> {
227 vec!["test_extension_stmt"]
228 }
229
230 fn extension_id(&self) -> &'static str {
231 "test_extension"
232 }
233 }
234
235 #[derive(Debug)]
236 struct TestStatementParser;
237
238 impl StatementParser for TestStatementParser {
239 fn can_parse(&self, rule_name: &str) -> bool {
240 rule_name == "test_extension_stmt"
241 }
242
243 fn supported_rules(&self) -> Vec<String> {
244 vec!["test_extension_stmt".to_string()]
245 }
246
247 fn parse_statement(
248 &self,
249 _rule_name: &str,
250 _content: &str,
251 _context: &ParseContext,
252 ) -> Result<Box<dyn crate::extensions::ProtocolExtension>, crate::extensions::ParseError>
253 {
254 Err(crate::extensions::ParseError::Syntax {
256 message: "Test parser - not implemented".to_string(),
257 })
258 }
259 }
260
261 #[test]
262 fn test_extension_parser_creation() {
263 let parser = ExtensionParser::new();
264 let stats = parser.extension_stats();
265 assert_eq!(stats.grammar_extensions, 0);
266 }
267
268 #[test]
269 fn test_extension_registration() {
270 let mut parser = ExtensionParser::new();
271 parser
272 .register_extension(TestGrammarExtension, TestStatementParser)
273 .expect("extension should register");
274
275 let stats = parser.extension_stats();
276 assert_eq!(stats.grammar_extensions, 1);
277 assert!(parser.can_handle_statement("test_extension_stmt"));
278 }
279
280 #[test]
281 fn test_builder_pattern() {
282 let parser = ExtensionParserBuilder::new()
283 .with_extension(TestGrammarExtension, TestStatementParser)
284 .expect("test extension should register")
285 .build();
286
287 assert!(parser.can_handle_statement("test_extension_stmt"));
288 }
289
290 #[test]
291 fn test_standard_parsing() {
292 let mut parser = ExtensionParser::new();
293
294 let input = "protocol TestProtocol =\n roles Alice, Bob\n Alice -> Bob : Message\n";
295
296 let result = parser.parse_with_extensions(input);
297 assert!(result.is_ok(), "Should parse standard choreography");
298 }
299
300 #[test]
301 fn test_composed_grammar_generation() {
302 let mut parser = ExtensionParser::new();
303 parser
304 .register_extension(TestGrammarExtension, TestStatementParser)
305 .expect("extension should register");
306
307 let result = parser.get_composed_grammar();
308 assert!(result.is_ok(), "Should generate composed grammar");
309
310 let grammar = result.unwrap();
311 assert!(
312 grammar.contains("test_extension_stmt"),
313 "Should contain extension rule"
314 );
315 assert!(
316 grammar.contains("choreography"),
317 "Should contain base rules"
318 );
319 }
320}