1use crate::ast::{LocalType, Role};
8use crate::compiler::projection::ProjectionError;
9use std::any::{Any, TypeId};
10use std::collections::BTreeMap;
11use std::fmt::Debug;
12
13pub 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#[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#[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
114pub trait GrammarExtension: Send + Sync + Debug {
116 fn grammar_rules(&self) -> &'static str;
118
119 fn statement_rules(&self) -> Vec<&'static str>;
121
122 fn priority(&self) -> u32 {
124 100
125 }
126
127 fn extension_id(&self) -> &'static str;
129}
130
131pub trait DocumentedGrammarExtension: GrammarExtension {
133 fn documentation(&self) -> ExtensionDocumentation {
135 ExtensionDocumentation::default()
136 }
137
138 fn examples(&self) -> Vec<ExtensionExample> {
140 vec![]
141 }
142
143 fn rule_descriptions(&self) -> std::collections::HashMap<String, String> {
145 std::collections::HashMap::new()
146 }
147}
148
149pub trait StatementParser: Send + Sync + Debug {
151 fn can_parse(&self, rule_name: &str) -> bool;
153
154 fn supported_rules(&self) -> Vec<String>;
156
157 fn parse_statement(
167 &self,
168 rule_name: &str,
169 content: &str,
170 context: &ParseContext,
171 ) -> Result<Box<dyn ProtocolExtension>, ParseError>;
172}
173
174pub trait ProtocolExtension: Send + Sync + Debug {
176 fn type_name(&self) -> &'static str;
178
179 fn mentions_role(&self, role: &Role) -> bool;
181
182 fn validate(&self, roles: &[Role]) -> Result<(), ExtensionValidationError>;
184
185 fn project(
187 &self,
188 role: &Role,
189 context: &ProjectionContext,
190 ) -> Result<LocalType, ProjectionError>;
191
192 fn generate_code(&self, context: &CodegenContext) -> proc_macro2::TokenStream;
194
195 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#[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 rule_conflicts: BTreeMap<String, Vec<String>>,
216 extension_dependencies: BTreeMap<String, Vec<String>>,
218 extension_versions: BTreeMap<String, String>,
220}
221
222impl ExtensionRegistry {
223 pub fn new() -> Self {
225 Self::default()
226 }
227
228 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 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 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 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 } 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 self.extension_versions
274 .entry(id)
275 .or_insert_with(|| "0.1.0".to_string());
276 Ok(())
277 }
278
279 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 pub fn compose_grammar(&self, base_grammar: &str) -> String {
286 let mut composed = base_grammar.to_string();
287
288 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 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 pub fn can_handle(&self, rule_name: &str) -> bool {
315 self.rule_to_parser.contains_key(rule_name)
316 }
317
318 pub fn has_extensions(&self) -> bool {
320 !self.grammar_extensions.is_empty() || !self.statement_parsers.is_empty()
321 }
322
323 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 pub fn has_extension(&self, extension_id: &str) -> bool {
332 self.grammar_extensions.contains_key(extension_id)
333 }
334
335 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 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 pub fn statement_parser_count(&self) -> usize {
347 self.statement_parsers.len()
348 }
349
350 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 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 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 pub fn get_conflicts(&self) -> &BTreeMap<String, Vec<String>> {
382 &self.rule_conflicts
383 }
384
385 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 pub fn check_compatibility(&self, extension_ids: &[&str]) -> Result<(), ParseError> {
426 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 pub fn with_builtin_extensions() -> Self {
451 let mut registry = Self::new();
452
453 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 pub fn for_third_party() -> Self {
466 Self::new()
467 }
468
469 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#[derive(Debug)]
616pub struct ParseContext<'a> {
617 pub declared_roles: &'a [Role],
619 pub input: &'a str,
621}
622
623#[derive(Debug)]
625pub struct ProjectionContext<'a> {
626 pub all_roles: &'a [Role],
628 pub current_role: &'a Role,
630}
631
632#[derive(Debug)]
634pub struct CodegenContext<'a> {
635 pub choreography_name: &'a str,
637 pub roles: &'a [Role],
639 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#[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#[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#[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
717pub trait RegisterExtension {
719 fn register_all(registry: &mut ExtensionRegistry);
720}
721
722pub mod discovery;
723pub 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 registry
753 .register_grammar(MockGrammarExtension)
754 .expect("extension registration should succeed");
755
756 assert!(registry.can_handle("mock_timeout_stmt"));
758 assert!(!registry.can_handle("unknown_rule"));
759
760 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 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 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 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 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}