1mod declarations;
10mod group;
11mod names;
12mod normalize;
13mod reconcile;
14mod rename;
15
16use group::{
17 Prepared, edition_alternatives, group_fingerprint, group_key, merge_group, name_label,
18 normalize_samples, prepare_sources, required_failures, source_of,
19};
20use normalize::normalize;
21pub use rename::{rename_grammar_rule, restore_source_names};
22
23use std::collections::{BTreeMap, BTreeSet};
24use std::error::Error;
25use std::fmt;
26
27use serde::Serialize;
28
29use super::{Grammar, GrammarRule};
30use crate::decorators::{DecoratorLevel, DecoratorSet, decorator_record, record_field};
31
32pub const GRAMMAR_MERGE_METHOD: &str = "recursive-structural-bisimulation";
34
35#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct GrammarMergeSource {
38 pub id: String,
40 pub language: String,
42 pub edition: String,
44 pub precedence: u32,
46 pub grammar: Grammar,
48}
49
50impl GrammarMergeSource {
51 #[must_use]
53 pub fn new(id: impl Into<String>, language: impl Into<String>, grammar: Grammar) -> Self {
54 Self {
55 id: id.into(),
56 language: language.into(),
57 edition: String::new(),
58 precedence: 0,
59 grammar,
60 }
61 }
62
63 #[must_use]
65 pub fn with_edition(mut self, edition: impl Into<String>) -> Self {
66 self.edition = edition.into();
67 self
68 }
69
70 #[must_use]
72 pub const fn with_precedence(mut self, precedence: u32) -> Self {
73 self.precedence = precedence;
74 self
75 }
76}
77
78#[derive(Clone, Debug, Default)]
80pub struct GrammarMergeOptions<'a> {
81 pub samples: BTreeMap<String, Vec<String>>,
84 pub required_equivalences: Vec<(String, String)>,
86 pub previous: Option<&'a GrammarMergeResult>,
89 pub decorators: DecoratorSet,
94 pub reconcile: bool,
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
105#[serde(rename_all = "kebab-case")]
106pub enum GrammarMergeDecisionKind {
107 Merged,
109 Reconciled,
111 KeptUnique,
113 RenamedForCollision,
115 HomonymKeptDistinct,
117 Uncertain,
119 DeclarationConflict,
122}
123
124impl GrammarMergeDecisionKind {
125 #[must_use]
127 pub const fn as_str(self) -> &'static str {
128 match self {
129 Self::Merged => "merged",
130 Self::Reconciled => "reconciled",
131 Self::KeptUnique => "kept-unique",
132 Self::RenamedForCollision => "renamed-for-collision",
133 Self::HomonymKeptDistinct => "homonym-kept-distinct",
134 Self::Uncertain => "uncertain",
135 Self::DeclarationConflict => "declaration-conflict",
136 }
137 }
138
139 #[must_use]
141 pub fn parse(text: &str) -> Option<Self> {
142 [
143 Self::Merged,
144 Self::Reconciled,
145 Self::KeptUnique,
146 Self::RenamedForCollision,
147 Self::HomonymKeptDistinct,
148 Self::Uncertain,
149 Self::DeclarationConflict,
150 ]
151 .into_iter()
152 .find(|kind| kind.as_str() == text)
153 }
154}
155
156#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
158pub struct GrammarMergeDecision {
159 pub kind: GrammarMergeDecisionKind,
161 pub name: String,
163 pub members: Vec<String>,
165 pub basis: String,
167 pub definition: Option<String>,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub concept: Option<String>,
172}
173
174#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
176#[serde(rename_all = "kebab-case")]
177pub enum GrammarMergeNominationBasis {
178 NameSimilarity,
180 IdenticalSamples,
182}
183
184impl GrammarMergeNominationBasis {
185 #[must_use]
187 pub const fn as_str(self) -> &'static str {
188 match self {
189 Self::NameSimilarity => "name-similarity",
190 Self::IdenticalSamples => "identical-samples",
191 }
192 }
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
197#[serde(rename_all = "kebab-case")]
198pub enum GrammarMergeNominationOutcome {
199 Proven,
201 Reconciled,
203 Unproven,
205}
206
207#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
209pub struct GrammarMergeNomination {
210 pub basis: GrammarMergeNominationBasis,
212 pub members: [String; 2],
214 pub outcome: GrammarMergeNominationOutcome,
216}
217
218#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
220#[serde(rename_all = "kebab-case")]
221pub enum GrammarMergeAlternativeReason {
222 StartRule,
224 DistinctMeaning,
226 UncertainMatch,
228 Edition,
230 DeclarationConflict,
232}
233
234impl GrammarMergeAlternativeReason {
235 #[must_use]
237 pub const fn as_str(self) -> &'static str {
238 match self {
239 Self::StartRule => "start-rule",
240 Self::DistinctMeaning => "distinct-meaning",
241 Self::UncertainMatch => "uncertain-match",
242 Self::Edition => "edition",
243 Self::DeclarationConflict => "declaration-conflict",
244 }
245 }
246}
247
248#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
250pub struct GrammarMergeAlternative {
251 pub reason: GrammarMergeAlternativeReason,
253 pub name: String,
255 pub options: Vec<String>,
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
261#[serde(rename_all = "kebab-case")]
262pub enum GrammarMergeFailureKind {
263 UnresolvedRequiredEquivalence,
265}
266
267#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
269#[serde(rename_all = "kebab-case")]
270pub enum GrammarMergeFailureReason {
271 UnknownRule,
273 DifferentLanguageOrEdition,
275 NotProven,
277}
278
279impl GrammarMergeFailureReason {
280 #[must_use]
282 pub const fn as_str(self) -> &'static str {
283 match self {
284 Self::UnknownRule => "unknown-rule",
285 Self::DifferentLanguageOrEdition => "different-language-or-edition",
286 Self::NotProven => "not-proven",
287 }
288 }
289}
290
291#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
293pub struct GrammarMergeFailure {
294 pub kind: GrammarMergeFailureKind,
296 pub members: [String; 2],
298 pub reason: GrammarMergeFailureReason,
300}
301
302#[derive(Clone, Debug, PartialEq, Eq)]
304pub struct MergedGrammarGroup {
305 pub key: String,
307 pub language: String,
309 pub edition: String,
311 pub fingerprint: String,
313 pub sources: Vec<String>,
315 pub grammar: Grammar,
317 pub identities: BTreeMap<String, String>,
319 pub decisions: Vec<GrammarMergeDecision>,
321 pub nominations: Vec<GrammarMergeNomination>,
323 pub alternatives: Vec<GrammarMergeAlternative>,
325}
326
327#[derive(Clone, Debug, PartialEq, Eq)]
329pub struct GrammarMergeResult {
330 pub groups: Vec<MergedGrammarGroup>,
332 pub alternatives: Vec<GrammarMergeAlternative>,
334 pub failures: Vec<GrammarMergeFailure>,
336 pub reused: Vec<String>,
338 pub recomputed: Vec<String>,
340}
341
342impl GrammarMergeResult {
343 #[must_use]
345 pub const fn status(&self) -> &'static str {
346 if self.failures.is_empty() {
347 "complete"
348 } else {
349 "incomplete"
350 }
351 }
352
353 #[must_use]
355 pub fn group(&self, key: &str) -> Option<&MergedGrammarGroup> {
356 self.groups.iter().find(|group| group.key == key)
357 }
358}
359
360#[derive(Clone, Debug, PartialEq, Eq)]
362pub struct GrammarMergeError {
363 message: String,
364 failures: Vec<GrammarMergeFailure>,
365 concatenated: Vec<String>,
366}
367
368impl GrammarMergeError {
369 fn new(message: impl Into<String>) -> Self {
370 Self {
371 message: message.into(),
372 failures: Vec::new(),
373 concatenated: Vec::new(),
374 }
375 }
376
377 #[must_use]
379 pub fn failures(&self) -> &[GrammarMergeFailure] {
380 &self.failures
381 }
382
383 #[must_use]
385 pub fn concatenated(&self) -> &[String] {
386 &self.concatenated
387 }
388}
389
390impl fmt::Display for GrammarMergeError {
391 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
392 formatter.write_str(&self.message)
393 }
394}
395
396impl Error for GrammarMergeError {}
397
398#[derive(Clone, Copy, Debug, PartialEq, Eq)]
400pub enum GrammarRenameErrorKind {
401 InvalidName,
403 UnknownRule,
405 Collision,
407}
408
409impl GrammarRenameErrorKind {
410 #[must_use]
412 pub const fn as_str(self) -> &'static str {
413 match self {
414 Self::InvalidName => "invalid-name",
415 Self::UnknownRule => "unknown-rule",
416 Self::Collision => "collision",
417 }
418 }
419}
420
421#[derive(Clone, Debug, PartialEq, Eq)]
423pub struct GrammarRenameError {
424 kind: GrammarRenameErrorKind,
425 message: String,
426}
427
428impl GrammarRenameError {
429 fn new(kind: GrammarRenameErrorKind, message: impl Into<String>) -> Self {
430 Self {
431 kind,
432 message: message.into(),
433 }
434 }
435
436 #[must_use]
438 pub const fn kind(&self) -> GrammarRenameErrorKind {
439 self.kind
440 }
441}
442
443impl fmt::Display for GrammarRenameError {
444 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
445 formatter.write_str(&self.message)
446 }
447}
448
449impl Error for GrammarRenameError {}
450
451#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
453pub struct RuleAlias {
454 pub canonical: String,
456 pub original: String,
458}
459
460#[derive(Clone, Debug, PartialEq, Eq)]
462pub struct RenamedGrammar {
463 pub grammar: Grammar,
465 pub aliases: Vec<RuleAlias>,
467}
468
469pub fn merge_grammars(
476 sources: &[GrammarMergeSource],
477 options: &GrammarMergeOptions<'_>,
478) -> Result<GrammarMergeResult, GrammarMergeError> {
479 let prepared = prepare_sources(sources)?;
480 let samples = normalize_samples(&options.samples);
481 let required: BTreeSet<(String, String)> = options
482 .required_equivalences
483 .iter()
484 .map(|(first, second)| {
485 if first <= second {
486 (first.clone(), second.clone())
487 } else {
488 (second.clone(), first.clone())
489 }
490 })
491 .collect();
492 let previous_groups: BTreeMap<&str, &MergedGrammarGroup> = options
493 .previous
494 .map(|previous| {
495 previous
496 .groups
497 .iter()
498 .map(|group| (group.key.as_str(), group))
499 .collect()
500 })
501 .unwrap_or_default();
502
503 let mut grouped: BTreeMap<(&str, &str), Vec<&Prepared<'_>>> = BTreeMap::new();
504 for source in &prepared {
505 grouped
506 .entry((source.language, source.edition))
507 .or_default()
508 .push(source);
509 }
510
511 let mut groups = Vec::new();
512 let mut reused = Vec::new();
513 let mut recomputed = Vec::new();
514 for ((language, edition), entry) in grouped {
515 let key = group_key(language, edition);
516 let ids: BTreeSet<&str> = entry.iter().map(|source| source.id).collect();
517 let group_required: Vec<&(String, String)> = required
518 .iter()
519 .filter(|(first, second)| {
520 ids.contains(source_of(first)) || ids.contains(source_of(second))
521 })
522 .collect();
523 let fingerprint = group_fingerprint(
524 language,
525 edition,
526 &entry,
527 &group_required,
528 &samples,
529 &options.decorators,
530 options.reconcile,
531 )?;
532 let previous = previous_groups.get(key.as_str()).copied();
533 if let Some(previous) = previous.filter(|previous| previous.fingerprint == fingerprint) {
534 groups.push(previous.clone());
535 reused.push(key);
536 continue;
537 }
538 let empty = BTreeMap::new();
539 let previous_identities = previous.map_or(&empty, |previous| &previous.identities);
540 let mut group = merge_group(
541 language,
542 edition,
543 &entry,
544 fingerprint,
545 &samples,
546 previous_identities,
547 options.reconcile,
548 )?;
549 group.decisions = decorate_decisions(group.decisions, &options.decorators)?;
550 groups.push(group);
551 recomputed.push(key);
552 }
553
554 let alternatives = edition_alternatives(&groups);
555 let failures = required_failures(&required, &groups);
556 Ok(GrammarMergeResult {
557 groups,
558 alternatives,
559 failures,
560 reused,
561 recomputed,
562 })
563}
564
565fn decorate_decisions(
566 decisions: Vec<GrammarMergeDecision>,
567 decorators: &DecoratorSet,
568) -> Result<Vec<GrammarMergeDecision>, GrammarMergeError> {
569 if !decorators.has(DecoratorLevel::MergeDecision) {
570 return Ok(decisions);
571 }
572 let mut kept = Vec::with_capacity(decisions.len());
573 for mut decision in decisions {
574 let record = decorator_record([
575 ("kind", decision.kind.as_str()),
576 ("name", &decision.name),
577 ("members", &decision.members.join(" ")),
578 ("basis", &decision.basis),
579 (
580 "definition",
581 decision.definition.as_deref().unwrap_or_default(),
582 ),
583 ]);
584 let Some(decorated) = decorators.decorate(DecoratorLevel::MergeDecision, &record) else {
585 continue;
586 };
587 let kind = record_field(&decorated, "kind");
588 decision.kind = GrammarMergeDecisionKind::parse(kind).ok_or_else(|| {
589 GrammarMergeError::new(format!(
590 "a decorator set the unknown decision kind {kind:?}"
591 ))
592 })?;
593 record_field(&decorated, "basis").clone_into(&mut decision.basis);
594 kept.push(decision);
595 }
596 Ok(kept)
597}
598
599pub fn assert_merge_complete(
601 result: &GrammarMergeResult,
602) -> Result<&GrammarMergeResult, GrammarMergeError> {
603 if result.failures.is_empty() {
604 return Ok(result);
605 }
606 let detail = result
607 .failures
608 .iter()
609 .map(|failure| {
610 format!(
611 "{} = {} ({})",
612 failure.members[0],
613 failure.members[1],
614 failure.reason.as_str()
615 )
616 })
617 .collect::<Vec<_>>()
618 .join("; ");
619 Err(GrammarMergeError {
620 message: format!("unresolved required equivalence: {detail}"),
621 failures: result.failures.clone(),
622 concatenated: Vec::new(),
623 })
624}
625
626#[must_use]
629pub fn shared_rule_decisions(group: &MergedGrammarGroup) -> Vec<&GrammarMergeDecision> {
630 group
631 .decisions
632 .iter()
633 .filter(|decision| {
634 matches!(
635 decision.kind,
636 GrammarMergeDecisionKind::Merged | GrammarMergeDecisionKind::Reconciled
637 ) && decision
638 .members
639 .iter()
640 .map(|member| source_of(member))
641 .collect::<BTreeSet<_>>()
642 .len()
643 > 1
644 })
645 .collect()
646}
647
648pub fn assert_merge_shares(
651 result: &GrammarMergeResult,
652) -> Result<&GrammarMergeResult, GrammarMergeError> {
653 let concatenated: Vec<&MergedGrammarGroup> = result
654 .groups
655 .iter()
656 .filter(|group| group.sources.len() > 1 && shared_rule_decisions(group).is_empty())
657 .collect();
658 if concatenated.is_empty() {
659 return Ok(result);
660 }
661 let detail = concatenated
662 .iter()
663 .map(|group| {
664 format!(
665 "{} ({}: {} rules)",
666 group.key,
667 group.sources.join(", "),
668 group.grammar.rules().len()
669 )
670 })
671 .collect::<Vec<_>>()
672 .join("; ");
673 Err(GrammarMergeError {
674 message: format!("the merge only concatenates its sources, sharing no rule: {detail}"),
675 failures: Vec::new(),
676 concatenated: concatenated.iter().map(|group| group.key.clone()).collect(),
677 })
678}
679
680pub fn normalized_rule_definition(rule: &GrammarRule) -> Result<String, GrammarMergeError> {
682 Ok(format!(
683 "{}:{}",
684 rule.kind.as_str(),
685 normalize(&rule.expr, &name_label)?.text
686 ))
687}