Skip to main content

telltale_language/
extensions.rs

1//! DSL Extension System for Telltale
2//!
3//! This module provides a clean, composable system for extending choreographic DSL syntax.
4//! Extensions can add new grammar rules, custom statement parsers, and protocol behaviors
5//! while maintaining compatibility with the core choreographic infrastructure.
6
7use crate::ast::{LocalType, Role};
8use crate::compiler::projection::ProjectionError;
9use std::any::{Any, TypeId};
10use std::collections::BTreeMap;
11use std::fmt::Debug;
12
13/// Core Pest rule names reserved by the built-in choreography grammar.
14///
15/// Extension grammar rules must not reuse these names. Extension-owned rules
16/// are also required to start with `<extension_id>_` so composed grammars remain
17/// collision-resistant by construction.
18pub const RESERVED_RULE_NAMES: &[&str] = &[
19    "WHITESPACE",
20    "COMMENT",
21    "choreography",
22    "top_level_decl",
23    "module_decl",
24    "import_decl",
25    "protocol_decl",
26    "where_block",
27    "proof_bundle_decl",
28    "profile_decl",
29    "agreement_profile_decl",
30    "type_decl",
31    "effect_decl",
32    "role_set_decl",
33    "topology_decl",
34    "fragment_decl",
35    "operation_decl",
36    "guest_runtime_decl",
37    "roles_decl",
38    "role_list",
39    "role_decl",
40    "protocol_body",
41    "block_protocol",
42    "statement",
43    "begin_stmt",
44    "await_stmt",
45    "resolve_stmt",
46    "invalidate_stmt",
47    "authority_let_in_stmt",
48    "authority_let_stmt",
49    "observe_let_in_stmt",
50    "observe_let_stmt",
51    "let_in_stmt",
52    "let_stmt",
53    "case_stmt",
54    "timeout_stmt",
55    "authority_expr",
56    "duration",
57    "time_unit",
58    "call_stmt",
59    "publish_authority_stmt",
60    "publish_stmt",
61    "materialize_stmt",
62    "handoff_stmt",
63    "dependent_work_stmt",
64    "continue_stmt",
65    "send_stmt",
66    "broadcast_stmt",
67    "role_ref",
68    "role_index",
69    "choice_stmt",
70    "par_stmt",
71    "guard",
72    "loop_stmt",
73    "rec_stmt",
74    "branch_body",
75    "message",
76    "type_spec",
77    "payload",
78    "ident",
79    "integer",
80    "string",
81];
82
83/// Documentation for an extension
84#[derive(Debug, Clone)]
85pub struct ExtensionDocumentation {
86    pub overview: String,
87    pub syntax_guide: String,
88    pub use_cases: Vec<String>,
89    pub limitations: Vec<String>,
90    pub see_also: Vec<String>,
91}
92
93impl Default for ExtensionDocumentation {
94    fn default() -> Self {
95        Self {
96            overview: "No documentation provided".to_string(),
97            syntax_guide: "No syntax guide provided".to_string(),
98            use_cases: vec![],
99            limitations: vec![],
100            see_also: vec![],
101        }
102    }
103}
104
105/// Example usage for an extension
106#[derive(Debug, Clone)]
107pub struct ExtensionExample {
108    pub title: String,
109    pub description: String,
110    pub code: String,
111    pub expected_output: Option<String>,
112}
113
114/// Trait for adding new grammar rules to the choreographic DSL
115pub trait GrammarExtension: Send + Sync + Debug {
116    /// Return the Pest grammar rules this extension provides
117    fn grammar_rules(&self) -> &'static str;
118
119    /// List of statement rule names this extension handles
120    fn statement_rules(&self) -> Vec<&'static str>;
121
122    /// Priority for conflict resolution (higher = more precedence)
123    fn priority(&self) -> u32 {
124        100
125    }
126
127    /// Extension identifier for debugging and registration
128    fn extension_id(&self) -> &'static str;
129}
130
131/// Trait for self-documenting extensions
132pub trait DocumentedGrammarExtension: GrammarExtension {
133    /// Documentation for this extension
134    fn documentation(&self) -> ExtensionDocumentation {
135        ExtensionDocumentation::default()
136    }
137
138    /// Examples showing how to use this extension
139    fn examples(&self) -> Vec<ExtensionExample> {
140        vec![]
141    }
142
143    /// Grammar rules with human-readable descriptions
144    fn rule_descriptions(&self) -> std::collections::HashMap<String, String> {
145        std::collections::HashMap::new()
146    }
147}
148
149/// Trait for parsing custom protocol statements
150pub trait StatementParser: Send + Sync + Debug {
151    /// Check if this parser can handle the given rule name
152    fn can_parse(&self, rule_name: &str) -> bool;
153
154    /// Return all rules this parser supports
155    fn supported_rules(&self) -> Vec<String>;
156
157    /// Parse a statement into a protocol extension
158    ///
159    /// # Arguments
160    /// * `rule_name` - The grammar rule name being parsed
161    /// * `content` - The matched content as a string
162    /// * `context` - Parsing context with declared roles
163    ///
164    /// # Returns
165    /// A boxed protocol extension representing the parsed statement
166    fn parse_statement(
167        &self,
168        rule_name: &str,
169        content: &str,
170        context: &ParseContext,
171    ) -> Result<Box<dyn ProtocolExtension>, ParseError>;
172}
173
174/// Trait for custom protocol behaviors that can be projected and validated
175pub trait ProtocolExtension: Send + Sync + Debug {
176    /// Unique identifier for this protocol extension type
177    fn type_name(&self) -> &'static str;
178
179    /// Check if this protocol mentions a specific role
180    fn mentions_role(&self, role: &Role) -> bool;
181
182    /// Validate this protocol against declared roles
183    fn validate(&self, roles: &[Role]) -> Result<(), ExtensionValidationError>;
184
185    /// Project this protocol to a local type for a specific role
186    fn project(
187        &self,
188        role: &Role,
189        context: &ProjectionContext,
190    ) -> Result<LocalType, ProjectionError>;
191
192    /// Generate code for this protocol extension
193    fn generate_code(&self, context: &CodegenContext) -> proc_macro2::TokenStream;
194
195    /// For trait object safety and downcasting
196    fn as_any(&self) -> &dyn Any;
197    fn as_any_mut(&mut self) -> &mut dyn Any;
198    fn type_id(&self) -> TypeId;
199    fn clone_box(&self) -> Box<dyn ProtocolExtension>;
200}
201
202impl Clone for Box<dyn ProtocolExtension> {
203    fn clone(&self) -> Self {
204        self.clone_box()
205    }
206}
207
208/// Registry for managing DSL extensions with conflict resolution
209#[derive(Debug, Default)]
210pub struct ExtensionRegistry {
211    grammar_extensions: BTreeMap<String, Box<dyn GrammarExtension>>,
212    statement_parsers: BTreeMap<String, Box<dyn StatementParser>>,
213    rule_to_parser: BTreeMap<String, String>,
214    /// Track rule conflicts for resolution
215    rule_conflicts: BTreeMap<String, Vec<String>>,
216    /// Extension dependencies
217    extension_dependencies: BTreeMap<String, Vec<String>>,
218    /// Extension version information for compatibility checking
219    extension_versions: BTreeMap<String, String>,
220}
221
222impl ExtensionRegistry {
223    /// Create a new empty extension registry
224    pub fn new() -> Self {
225        Self::default()
226    }
227
228    /// Register a grammar extension with conflict detection
229    pub fn register_grammar<T: GrammarExtension + 'static>(
230        &mut self,
231        extension: T,
232    ) -> Result<(), ParseError> {
233        let id = extension.extension_id().to_string();
234        let rules = extension.statement_rules();
235        let priority = extension.priority();
236        validate_extension_grammar(&id, extension.grammar_rules(), &rules)?;
237
238        // Check for conflicts and resolve by priority
239        for rule in &rules {
240            if let Some(existing_id) = self.rule_to_parser.get(*rule) {
241                let existing_priority = self
242                    .grammar_extensions
243                    .get(existing_id)
244                    .map(|e| e.priority())
245                    .unwrap_or(0);
246
247                if priority > existing_priority {
248                    // New extension wins, record conflict
249                    self.rule_conflicts
250                        .entry((*rule).to_string())
251                        .or_default()
252                        .push(existing_id.clone());
253                    self.rule_to_parser.insert((*rule).to_string(), id.clone());
254                } else if priority == existing_priority {
255                    // Equal priority - this is a conflict
256                    return Err(ParseError::PriorityConflict {
257                        extension1: existing_id.clone(),
258                        extension2: id.clone(),
259                        priority1: existing_priority,
260                        priority2: priority,
261                        rule: (*rule).to_string(),
262                    });
263                }
264                // Lower priority - existing extension wins
265            } else {
266                self.rule_to_parser.insert((*rule).to_string(), id.clone());
267            }
268        }
269
270        self.grammar_extensions
271            .insert(id.clone(), Box::new(extension));
272        // Set default version if not specified
273        self.extension_versions
274            .entry(id)
275            .or_insert_with(|| "0.1.0".to_string());
276        Ok(())
277    }
278
279    /// Register a statement parser
280    pub fn register_parser<T: StatementParser + 'static>(&mut self, parser: T, parser_id: String) {
281        self.statement_parsers.insert(parser_id, Box::new(parser));
282    }
283
284    /// Get all grammar rules from registered extensions
285    pub fn compose_grammar(&self, base_grammar: &str) -> String {
286        let mut composed = base_grammar.to_string();
287
288        // Sort extensions by priority (highest first)
289        let mut extensions: Vec<_> = self.grammar_extensions.iter().collect();
290        extensions.sort_by(|(id_a, ext_a), (id_b, ext_b)| {
291            std::cmp::Reverse(ext_a.priority())
292                .cmp(&std::cmp::Reverse(ext_b.priority()))
293                .then_with(|| id_a.cmp(id_b))
294        });
295
296        for (_, extension) in extensions {
297            composed.push('\n');
298            composed.push_str(extension.grammar_rules());
299        }
300
301        composed
302    }
303
304    /// Find parser for a given rule name
305    pub fn find_parser(&self, rule_name: &str) -> Option<&dyn StatementParser> {
306        if let Some(parser_id) = self.rule_to_parser.get(rule_name) {
307            self.statement_parsers.get(parser_id).map(|p| p.as_ref())
308        } else {
309            None
310        }
311    }
312
313    /// Check if a rule is handled by an extension
314    pub fn can_handle(&self, rule_name: &str) -> bool {
315        self.rule_to_parser.contains_key(rule_name)
316    }
317
318    /// Check if any extensions are registered
319    pub fn has_extensions(&self) -> bool {
320        !self.grammar_extensions.is_empty() || !self.statement_parsers.is_empty()
321    }
322
323    /// Get all grammar extensions
324    pub fn grammar_extensions(&self) -> impl Iterator<Item = &dyn GrammarExtension> {
325        let mut ordered: Vec<_> = self.grammar_extensions.iter().collect();
326        ordered.sort_by_key(|(id_a, _)| *id_a);
327        ordered.into_iter().map(|(_, e)| e.as_ref())
328    }
329
330    /// Check if a specific extension is registered
331    pub fn has_extension(&self, extension_id: &str) -> bool {
332        self.grammar_extensions.contains_key(extension_id)
333    }
334
335    /// Get parser for a rule name
336    pub fn get_parser_for_rule(&self, rule_name: &str) -> Option<&str> {
337        self.rule_to_parser.get(rule_name).map(String::as_str)
338    }
339
340    /// Get statement parser by ID
341    pub fn get_statement_parser(&self, parser_id: &str) -> Option<&dyn StatementParser> {
342        self.statement_parsers.get(parser_id).map(|p| p.as_ref())
343    }
344
345    /// Get the number of registered statement parsers.
346    pub fn statement_parser_count(&self) -> usize {
347        self.statement_parsers.len()
348    }
349
350    /// Get the registered extension statement rules in stable order.
351    pub fn statement_rules(&self) -> Vec<&str> {
352        let mut rules: Vec<_> = self.rule_to_parser.keys().map(String::as_str).collect();
353        rules.sort_unstable();
354        rules
355    }
356
357    /// Add dependency between extensions
358    pub fn add_dependency(&mut self, dependent: &str, required: &str) {
359        self.extension_dependencies
360            .entry(dependent.to_string())
361            .or_default()
362            .push(required.to_string());
363    }
364
365    /// Validate all extension dependencies are satisfied
366    pub fn validate_dependencies(&self) -> Result<(), ParseError> {
367        for (dependent, requirements) in &self.extension_dependencies {
368            for required in requirements {
369                if !self.grammar_extensions.contains_key(required) {
370                    return Err(ParseError::MissingDependency {
371                        extension: dependent.clone(),
372                        dependency: required.clone(),
373                    });
374                }
375            }
376        }
377        Ok(())
378    }
379
380    /// Get all rule conflicts for debugging
381    pub fn get_conflicts(&self) -> &BTreeMap<String, Vec<String>> {
382        &self.rule_conflicts
383    }
384
385    /// Get detailed conflict information with resolution suggestions
386    pub fn get_detailed_conflicts(&self) -> Vec<String> {
387        let mut details = Vec::new();
388        let unknown_ext = "unknown".to_string();
389
390        let mut conflicts: Vec<_> = self.rule_conflicts.iter().collect();
391        conflicts.sort_by_key(|(rule_a, _)| *rule_a);
392
393        for (rule, conflicting_extensions) in conflicts {
394            if !conflicting_extensions.is_empty() {
395                let active_extension = self.rule_to_parser.get(rule).unwrap_or(&unknown_ext);
396                let active_priority = self
397                    .grammar_extensions
398                    .get(active_extension)
399                    .map(|e| e.priority())
400                    .unwrap_or(0);
401
402                let mut conflicting_extensions = conflicting_extensions.clone();
403                conflicting_extensions.sort();
404
405                for conflicting in &conflicting_extensions {
406                    let conflicting_priority = self
407                        .grammar_extensions
408                        .get(conflicting)
409                        .map(|e| e.priority())
410                        .unwrap_or(0);
411
412                    details.push(format!(
413                        "Rule '{}': Extension '{}' (priority {}) overrode '{}' (priority {}). \
414                         To resolve: 1) Adjust priorities, 2) Use different rule names, or 3) Merge functionality.",
415                        rule, active_extension, active_priority, conflicting, conflicting_priority
416                    ));
417                }
418            }
419        }
420
421        details
422    }
423
424    /// Check extension compatibility
425    pub fn check_compatibility(&self, extension_ids: &[&str]) -> Result<(), ParseError> {
426        // Check for direct conflicts between the specified extensions
427        let mut rules_used = BTreeMap::new();
428
429        for &extension_id in extension_ids {
430            if let Some(extension) = self.grammar_extensions.get(extension_id) {
431                for rule in extension.statement_rules() {
432                    if let Some(existing) = rules_used.get(rule) {
433                        if existing != &extension_id {
434                            return Err(ParseError::IncompatibleExtensions {
435                                details: format!(
436                                    "Extensions '{}' and '{}' both define rule '{}'. Use different rule names or register extensions with different priorities.",
437                                    existing, extension_id, rule
438                                ),
439                            });
440                        }
441                    }
442                    rules_used.insert(rule.to_string(), extension_id);
443                }
444            }
445        }
446        Ok(())
447    }
448
449    /// Create a registry with built-in extensions
450    pub fn with_builtin_extensions() -> Self {
451        let mut registry = Self::new();
452
453        // Built-in registration is static and covered by extension tests.
454        if registry
455            .register_grammar(timeout::TimeoutGrammarExtension)
456            .is_ok()
457        {
458            registry.register_parser(timeout::TimeoutStatementParser, "timeout".to_string());
459        }
460
461        registry
462    }
463
464    /// Create a minimal registry for 3rd party integration
465    pub fn for_third_party() -> Self {
466        Self::new()
467    }
468
469    /// Generate basic documentation for all registered extensions
470    pub fn generate_docs(&self) -> String {
471        let mut docs = String::from("# Extension Documentation\n\n");
472
473        let mut entries: Vec<_> = self.grammar_extensions.iter().collect();
474        entries.sort_by_key(|(id_a, _)| *id_a);
475
476        for (id, extension) in entries {
477            docs.push_str(&format!("## {}\n\n", id));
478            docs.push_str(&format!("**Priority:** {}\n\n", extension.priority()));
479            docs.push_str(&format!(
480                "**Rules:** {}\n\n",
481                extension.statement_rules().join(", ")
482            ));
483
484            if let Some(version) = self.extension_versions.get(id) {
485                docs.push_str(&format!("**Version:** {}\n\n", version));
486            }
487
488            docs.push_str("**Grammar:**\n```\n");
489            docs.push_str(extension.grammar_rules());
490            docs.push_str("\n```\n\n");
491        }
492
493        docs
494    }
495}
496
497fn validate_extension_grammar(
498    extension_id: &str,
499    grammar_rules: &str,
500    statement_rules: &[&str],
501) -> Result<(), ParseError> {
502    if !is_pest_identifier(extension_id) {
503        return Err(ParseError::RegistrationFailed {
504            extension: extension_id.to_string(),
505            rule: extension_id.to_string(),
506            details: "extension ID must be a Pest identifier".to_string(),
507        });
508    }
509
510    let prefix = format!("{extension_id}_");
511    let parsed_rules = collect_pest_rule_names(grammar_rules);
512    let mut seen = BTreeMap::<String, usize>::new();
513
514    for rule in &parsed_rules {
515        let count = seen.entry(rule.clone()).or_insert(0);
516        *count += 1;
517
518        if RESERVED_RULE_NAMES.contains(&rule.as_str()) {
519            return Err(ParseError::RegistrationFailed {
520                extension: extension_id.to_string(),
521                rule: rule.clone(),
522                details: "rule name is reserved by the core choreography grammar".to_string(),
523            });
524        }
525
526        if !rule.starts_with(&prefix) {
527            return Err(ParseError::RegistrationFailed {
528                extension: extension_id.to_string(),
529                rule: rule.clone(),
530                details: format!("rule name must start with `{prefix}`"),
531            });
532        }
533    }
534
535    if let Some((duplicate, _)) = seen.iter().find(|(_, count)| **count > 1) {
536        return Err(ParseError::RegistrationFailed {
537            extension: extension_id.to_string(),
538            rule: duplicate.clone(),
539            details: "extension grammar defines the same Pest rule more than once".to_string(),
540        });
541    }
542
543    for rule in statement_rules {
544        if RESERVED_RULE_NAMES.contains(rule) {
545            return Err(ParseError::RegistrationFailed {
546                extension: extension_id.to_string(),
547                rule: (*rule).to_string(),
548                details: "statement rule name is reserved by the core choreography grammar"
549                    .to_string(),
550            });
551        }
552
553        if !rule.starts_with(&prefix) {
554            return Err(ParseError::RegistrationFailed {
555                extension: extension_id.to_string(),
556                rule: (*rule).to_string(),
557                details: format!("statement rule must start with `{prefix}`"),
558            });
559        }
560
561        if !parsed_rules.iter().any(|parsed| parsed == rule) {
562            return Err(ParseError::RegistrationFailed {
563                extension: extension_id.to_string(),
564                rule: (*rule).to_string(),
565                details: "statement rule is not defined by extension grammar".to_string(),
566            });
567        }
568    }
569
570    Ok(())
571}
572
573fn collect_pest_rule_names(grammar: &str) -> Vec<String> {
574    let mut rules = Vec::new();
575
576    for line in grammar.lines() {
577        let trimmed = line.trim_start();
578        if trimmed.is_empty() || trimmed.starts_with("//") {
579            continue;
580        }
581
582        let Some(first) = trimmed.chars().next() else {
583            continue;
584        };
585        if !(first == '_' || first.is_ascii_alphabetic()) {
586            continue;
587        }
588
589        let name_len = trimmed
590            .char_indices()
591            .take_while(|(_, ch)| *ch == '_' || ch.is_ascii_alphanumeric())
592            .last()
593            .map_or(0, |(idx, ch)| idx + ch.len_utf8());
594
595        let (name, rest) = trimmed.split_at(name_len);
596        let rest = rest.trim_start();
597        if rest.starts_with('=') && is_pest_identifier(name) {
598            rules.push(name.to_string());
599        }
600    }
601
602    rules
603}
604
605fn is_pest_identifier(value: &str) -> bool {
606    let mut chars = value.chars();
607    let Some(first) = chars.next() else {
608        return false;
609    };
610    (first == '_' || first.is_ascii_alphabetic())
611        && chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
612}
613
614/// Context provided during statement parsing
615#[derive(Debug)]
616pub struct ParseContext<'a> {
617    /// Roles declared in the choreography
618    pub declared_roles: &'a [Role],
619    /// Original input string for error reporting
620    pub input: &'a str,
621}
622
623/// Context provided during projection
624#[derive(Debug)]
625pub struct ProjectionContext<'a> {
626    /// All roles in the choreography
627    pub all_roles: &'a [Role],
628    /// Current role being projected
629    pub current_role: &'a Role,
630}
631
632/// Context provided during code generation
633#[derive(Debug)]
634pub struct CodegenContext<'a> {
635    /// The choreography being generated
636    pub choreography_name: &'a str,
637    /// All roles in the choreography
638    pub roles: &'a [Role],
639    /// Namespace for generated code
640    pub namespace: Option<&'a str>,
641}
642
643impl<'a> Default for CodegenContext<'a> {
644    fn default() -> Self {
645        Self {
646            choreography_name: "Default",
647            roles: &[],
648            namespace: None,
649        }
650    }
651}
652
653/// Errors that can occur during extension parsing
654#[derive(Debug, thiserror::Error)]
655pub enum ParseError {
656    #[error("Syntax error: {message}")]
657    Syntax { message: String },
658
659    #[error("Unknown role '{role}' used in extension")]
660    UnknownRole { role: String },
661
662    #[error("Invalid extension syntax: {details}")]
663    InvalidSyntax { details: String },
664
665    #[error("Extension conflict: {message}")]
666    Conflict { message: String },
667
668    #[error("Extension priority conflict: Extension '{extension1}' (priority {priority1}) conflicts with '{extension2}' (priority {priority2}) for rule '{rule}'. Consider adjusting priorities or using different rule names.")]
669    PriorityConflict {
670        extension1: String,
671        extension2: String,
672        priority1: u32,
673        priority2: u32,
674        rule: String,
675    },
676
677    #[error("Missing dependency: Extension '{extension}' requires '{dependency}' which is not registered. Please register the required extension first.")]
678    MissingDependency {
679        extension: String,
680        dependency: String,
681    },
682
683    #[error("Extension registration failed: Extension '{extension}' with rule '{rule}' cannot be registered. {details}")]
684    RegistrationFailed {
685        extension: String,
686        rule: String,
687        details: String,
688    },
689
690    #[error("Incompatible extensions: {details}")]
691    IncompatibleExtensions { details: String },
692}
693
694/// Validation errors for protocol extensions
695#[derive(Debug, thiserror::Error)]
696pub enum ExtensionValidationError {
697    #[error("Role '{role}' not declared")]
698    UndeclaredRole { role: String },
699
700    #[error("Invalid protocol structure: {reason}")]
701    InvalidStructure { reason: String },
702
703    #[error("Extension validation failed: {message}")]
704    ExtensionFailed { message: String },
705}
706
707/// Convenience macro for registering extensions
708#[macro_export]
709macro_rules! register_extension {
710    ($registry:expr, $extension:expr) => {{
711        let ext = $extension;
712        let id = ext.extension_id().to_string();
713        $registry.register_grammar(ext);
714    }};
715}
716
717/// Utility trait for easy extension registration
718pub trait RegisterExtension {
719    fn register_all(registry: &mut ExtensionRegistry);
720}
721
722pub mod discovery;
723/// Built-in extensions
724pub mod timeout;
725
726#[cfg(test)]
727mod tests {
728    use super::*;
729
730    #[derive(Debug)]
731    struct MockGrammarExtension;
732
733    impl GrammarExtension for MockGrammarExtension {
734        fn grammar_rules(&self) -> &'static str {
735            "mock_timeout_stmt = { \"timeout\" ~ integer ~ protocol_body }"
736        }
737
738        fn statement_rules(&self) -> Vec<&'static str> {
739            vec!["mock_timeout_stmt"]
740        }
741
742        fn extension_id(&self) -> &'static str {
743            "mock_timeout"
744        }
745    }
746
747    #[test]
748    fn test_extension_registry() {
749        let mut registry = ExtensionRegistry::new();
750
751        // Register extension
752        registry
753            .register_grammar(MockGrammarExtension)
754            .expect("extension registration should succeed");
755
756        // Test rule mapping
757        assert!(registry.can_handle("mock_timeout_stmt"));
758        assert!(!registry.can_handle("unknown_rule"));
759
760        // Test grammar composition
761        let base = "basic_rule = { \"test\" }";
762        let composed = registry.compose_grammar(base);
763        assert!(composed.contains("basic_rule"));
764        assert!(composed.contains("mock_timeout_stmt"));
765    }
766
767    #[test]
768    fn test_enhanced_error_messages() {
769        use crate::extensions::ParseError;
770
771        // Test priority conflict error
772        let err = ParseError::PriorityConflict {
773            extension1: "ext1".to_string(),
774            extension2: "ext2".to_string(),
775            priority1: 100,
776            priority2: 100,
777            rule: "test_rule".to_string(),
778        };
779        assert!(err.to_string().contains("Consider adjusting priorities"));
780
781        // Test missing dependency error
782        let err = ParseError::MissingDependency {
783            extension: "dependent_ext".to_string(),
784            dependency: "required_ext".to_string(),
785        };
786        assert!(err
787            .to_string()
788            .contains("Please register the required extension first"));
789
790        // Test incompatible extensions error
791        let err = ParseError::IncompatibleExtensions {
792            details: "Test incompatibility".to_string(),
793        };
794        assert!(err.to_string().contains("Incompatible extensions"));
795    }
796
797    #[test]
798    fn test_extension_rule_prefix_is_required() {
799        #[derive(Debug)]
800        struct BadExt;
801        impl GrammarExtension for BadExt {
802            fn grammar_rules(&self) -> &'static str {
803                "rule1 = { \"test\" }"
804            }
805            fn statement_rules(&self) -> Vec<&'static str> {
806                vec!["rule1"]
807            }
808            fn extension_id(&self) -> &'static str {
809                "bad_ext"
810            }
811        }
812
813        let mut registry = ExtensionRegistry::new();
814        let err = registry
815            .register_grammar(BadExt)
816            .expect_err("unprefixed rule must be rejected");
817        assert!(err.to_string().contains("bad_ext_"));
818    }
819
820    #[test]
821    fn test_documentation_system() {
822        let mut registry = ExtensionRegistry::new();
823
824        registry
825            .extension_versions
826            .insert("mock_timeout".to_string(), "1.0.0".to_string());
827        registry
828            .register_grammar(MockGrammarExtension)
829            .expect("grammar extension should register");
830
831        // Test documentation generation
832        let docs = registry.generate_docs();
833        assert!(docs.contains("# Extension Documentation"));
834        assert!(docs.contains("mock_timeout"));
835        assert!(docs.contains("**Priority:** 100"));
836        assert!(docs.contains("**Version:** 1.0.0"));
837
838        assert_eq!(
839            registry.extension_versions.get("mock_timeout"),
840            Some(&"1.0.0".to_string())
841        );
842    }
843
844    #[test]
845    fn test_compose_grammar_is_stable_for_equal_priorities() {
846        #[derive(Debug)]
847        struct AlphaExt;
848        impl GrammarExtension for AlphaExt {
849            fn grammar_rules(&self) -> &'static str {
850                "alpha_ext_stmt = { \"alpha\" }"
851            }
852            fn statement_rules(&self) -> Vec<&'static str> {
853                vec!["alpha_ext_stmt"]
854            }
855            fn priority(&self) -> u32 {
856                100
857            }
858            fn extension_id(&self) -> &'static str {
859                "alpha_ext"
860            }
861        }
862
863        #[derive(Debug)]
864        struct BetaExt;
865        impl GrammarExtension for BetaExt {
866            fn grammar_rules(&self) -> &'static str {
867                "beta_ext_stmt = { \"beta\" }"
868            }
869            fn statement_rules(&self) -> Vec<&'static str> {
870                vec!["beta_ext_stmt"]
871            }
872            fn priority(&self) -> u32 {
873                100
874            }
875            fn extension_id(&self) -> &'static str {
876                "beta_ext"
877            }
878        }
879
880        let mut registry = ExtensionRegistry::new();
881        registry.register_grammar(BetaExt).unwrap();
882        registry.register_grammar(AlphaExt).unwrap();
883
884        let composed = registry.compose_grammar("base = { \"x\" }");
885        let alpha_idx = composed.find("alpha_ext_stmt").unwrap();
886        let beta_idx = composed.find("beta_ext_stmt").unwrap();
887        assert!(alpha_idx < beta_idx);
888    }
889
890    #[test]
891    fn test_reserved_core_rule_is_rejected() {
892        #[derive(Debug)]
893        struct BadCoreCollisionExt;
894        impl GrammarExtension for BadCoreCollisionExt {
895            fn grammar_rules(&self) -> &'static str {
896                "send_stmt = { \"shadow\" }"
897            }
898            fn statement_rules(&self) -> Vec<&'static str> {
899                vec!["send_stmt"]
900            }
901            fn extension_id(&self) -> &'static str {
902                "send"
903            }
904        }
905
906        let mut registry = ExtensionRegistry::new();
907        let err = registry
908            .register_grammar(BadCoreCollisionExt)
909            .expect_err("core rule collision must be rejected");
910        assert!(err.to_string().contains("reserved"));
911    }
912
913    #[test]
914    fn test_parse_context() {
915        use proc_macro2::Span;
916        let roles = vec![
917            Role::new(proc_macro2::Ident::new("Alice", Span::call_site())).unwrap(),
918            Role::new(proc_macro2::Ident::new("Bob", Span::call_site())).unwrap(),
919        ];
920
921        let context = ParseContext {
922            declared_roles: &roles,
923            input: "test input",
924        };
925
926        assert_eq!(context.declared_roles.len(), 2);
927        assert_eq!(context.input, "test input");
928    }
929}