1use std::collections::{BTreeMap, BTreeSet};
11
12use crate::decorators::{
13 DecoratorError, DecoratorLevel, DecoratorSet, decorator_record, record_field,
14};
15use crate::language_catalog::{native_grammar, native_grammars};
16use crate::native_grammar_parser::{native_grammar_text, native_parser};
17use crate::{ConceptRecord, FeatureParseOptions, SyntaxTree, concept_records, parse_grammar_links};
18
19const NATIVE_SOURCE_PREFIX: &str = "native:";
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct NativeRuleConcept {
24 pub rule: String,
26 pub concept: Option<String>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum NativeConceptProblemKind {
33 RuleWithoutConcept,
35 ConceptWithoutRecord,
37 RuleWithoutAlias,
39 AliasWithoutRule,
41 NamespaceMismatch,
43}
44
45impl NativeConceptProblemKind {
46 #[must_use]
48 pub const fn as_str(self) -> &'static str {
49 match self {
50 Self::RuleWithoutConcept => "rule-without-concept",
51 Self::ConceptWithoutRecord => "concept-without-record",
52 Self::RuleWithoutAlias => "rule-without-alias",
53 Self::AliasWithoutRule => "alias-without-rule",
54 Self::NamespaceMismatch => "namespace-mismatch",
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct NativeConceptProblem {
62 pub kind: NativeConceptProblemKind,
64 pub grammar: String,
66 pub rule: String,
68 pub concept: Option<String>,
70}
71
72#[must_use]
74pub fn native_grammar_ids() -> Vec<&'static str> {
75 let mut ids: Vec<&'static str> = native_grammars()
76 .iter()
77 .map(|grammar| grammar.id.as_str())
78 .collect();
79 ids.sort_unstable();
80 ids
81}
82
83#[must_use]
86pub fn native_grammar_language(id: &str) -> Option<&'static str> {
87 let file = native_grammar(id)?.file.as_str();
88 let name = file.rsplit('/').next().unwrap_or(file);
89 Some(name.strip_suffix(".lino").unwrap_or(name))
90}
91
92#[must_use]
95pub fn native_grammar_source(id: &str) -> Option<String> {
96 native_grammar_language(id).map(|language| format!("{NATIVE_SOURCE_PREFIX}{language}"))
97}
98
99#[must_use]
102pub fn native_grammar_rule_concepts(id: &str) -> Vec<NativeRuleConcept> {
103 native_grammar_text(id)
104 .and_then(|text| parse_grammar_links(text).ok())
105 .map(|grammar| {
106 grammar
107 .rules()
108 .iter()
109 .map(|rule| NativeRuleConcept {
110 rule: rule.name.clone(),
111 concept: rule.concept.clone(),
112 })
113 .collect()
114 })
115 .unwrap_or_default()
116}
117
118fn namespace_language<'a>(concept: &'a str, languages: &BTreeSet<&str>) -> Option<&'a str> {
121 let parts: Vec<&str> = concept.split('.').collect();
122 (parts.len() > 2 && languages.contains(parts[1])).then(|| parts[1])
123}
124
125#[must_use]
131pub fn check_native_grammar_concepts() -> Vec<NativeConceptProblem> {
132 check_native_grammar_concepts_in(
133 concept_records(),
134 &native_grammar_ids(),
135 &native_grammar_rule_concepts,
136 )
137}
138
139#[must_use]
142pub fn check_native_grammar_concepts_in(
143 records: &[ConceptRecord],
144 grammars: &[&str],
145 rule_concepts: &dyn Fn(&str) -> Vec<NativeRuleConcept>,
146) -> Vec<NativeConceptProblem> {
147 let by_id: BTreeMap<&str, &ConceptRecord> = records
148 .iter()
149 .map(|record| (record.id.as_str(), record))
150 .collect();
151 let languages: BTreeSet<&str> = grammars
152 .iter()
153 .filter_map(|id| native_grammar_language(id))
154 .collect();
155 let mut problems = Vec::new();
156 for &grammar in grammars {
157 let (Some(source), Some(language)) = (
158 native_grammar_source(grammar),
159 native_grammar_language(grammar),
160 ) else {
161 continue;
162 };
163 let problem = |kind, rule: &str, concept: Option<&str>| NativeConceptProblem {
164 kind,
165 grammar: grammar.to_owned(),
166 rule: rule.to_owned(),
167 concept: concept.map(str::to_owned),
168 };
169 let mut named = BTreeMap::new();
170 for NativeRuleConcept { rule, concept } in rule_concepts(grammar) {
171 let Some(concept) = concept else {
172 problems.push(problem(
173 NativeConceptProblemKind::RuleWithoutConcept,
174 &rule,
175 None,
176 ));
177 continue;
178 };
179 let Some(record) = by_id.get(concept.as_str()) else {
180 problems.push(problem(
181 NativeConceptProblemKind::ConceptWithoutRecord,
182 &rule,
183 Some(&concept),
184 ));
185 named.insert(rule, concept);
186 continue;
187 };
188 if !record
189 .source_aliases
190 .iter()
191 .any(|alias| alias.source == source && alias.name == rule)
192 {
193 problems.push(problem(
194 NativeConceptProblemKind::RuleWithoutAlias,
195 &rule,
196 Some(&concept),
197 ));
198 }
199 if namespace_language(&concept, &languages).is_some_and(|owner| owner != language) {
200 problems.push(problem(
201 NativeConceptProblemKind::NamespaceMismatch,
202 &rule,
203 Some(&concept),
204 ));
205 }
206 named.insert(rule, concept);
207 }
208 for record in records {
209 for alias in &record.source_aliases {
210 if alias.source == source && named.get(&alias.name) != Some(&record.id) {
211 problems.push(problem(
212 NativeConceptProblemKind::AliasWithoutRule,
213 &alias.name,
214 Some(&record.id),
215 ));
216 }
217 }
218 }
219 }
220 problems
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct SharedRule {
226 pub rule: String,
228 pub concept: String,
230 pub languages: Vec<String>,
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct SpecificRule {
237 pub rule: String,
239 pub concept: String,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
246pub struct GrammarConceptReuse {
247 pub grammar: String,
249 pub language: String,
251 pub rules: usize,
253 pub shared: Vec<SharedRule>,
255 pub specific: Vec<SpecificRule>,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
261pub struct ConceptReuse {
262 pub concept: String,
264 pub languages: Vec<String>,
266 pub shared: bool,
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct NativeConceptReuse {
273 pub grammars: Vec<GrammarConceptReuse>,
275 pub concepts: Vec<ConceptReuse>,
277}
278
279#[must_use]
286pub fn native_grammar_concept_reuse() -> NativeConceptReuse {
287 let grammars = native_grammar_ids();
288 let language_of: BTreeMap<String, &str> = grammars
289 .iter()
290 .filter_map(|id| Some((native_grammar_source(id)?, native_grammar_language(id)?)))
291 .collect();
292 let mut languages: BTreeMap<&str, Vec<String>> = BTreeMap::new();
293 for record in concept_records() {
294 let named: BTreeSet<&str> = record
295 .source_aliases
296 .iter()
297 .filter_map(|alias| language_of.get(&alias.source).copied())
298 .collect();
299 if !named.is_empty() {
300 languages.insert(&record.id, named.into_iter().map(str::to_owned).collect());
301 }
302 }
303 let report = grammars
304 .iter()
305 .map(|&grammar| {
306 let mut shared = Vec::new();
307 let mut specific = Vec::new();
308 for NativeRuleConcept { rule, concept } in native_grammar_rule_concepts(grammar) {
309 let concept = concept.unwrap_or_default();
310 match languages.get(concept.as_str()) {
311 Some(named) if named.len() > 1 => shared.push(SharedRule {
312 rule,
313 concept,
314 languages: named.clone(),
315 }),
316 _ => specific.push(SpecificRule { rule, concept }),
317 }
318 }
319 GrammarConceptReuse {
320 grammar: grammar.to_owned(),
321 language: native_grammar_language(grammar)
322 .unwrap_or_default()
323 .to_owned(),
324 rules: shared.len() + specific.len(),
325 shared,
326 specific,
327 }
328 })
329 .collect();
330 let concepts = languages
331 .into_iter()
332 .map(|(concept, named)| ConceptReuse {
333 concept: concept.to_owned(),
334 shared: named.len() > 1,
335 languages: named,
336 })
337 .collect();
338 NativeConceptReuse {
339 grammars: report,
340 concepts,
341 }
342}
343
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346pub enum ConstructTranslationRelation {
347 Translated,
349 Untranslatable,
351 Ambiguous,
353 Unknown,
355}
356
357impl ConstructTranslationRelation {
358 #[must_use]
360 pub const fn as_str(self) -> &'static str {
361 match self {
362 Self::Translated => "translated",
363 Self::Untranslatable => "untranslatable",
364 Self::Ambiguous => "ambiguous",
365 Self::Unknown => "unknown",
366 }
367 }
368
369 #[must_use]
371 pub fn parse(name: &str) -> Option<Self> {
372 [
373 Self::Translated,
374 Self::Untranslatable,
375 Self::Ambiguous,
376 Self::Unknown,
377 ]
378 .into_iter()
379 .find(|relation| relation.as_str() == name)
380 }
381}
382
383#[derive(Debug, Clone, PartialEq, Eq)]
385pub struct ConstructTranslation {
386 pub relation: ConstructTranslationRelation,
388 pub concept: Option<String>,
390 pub rules: Vec<String>,
392}
393
394#[must_use]
400pub fn translate_native_construct(from: &str, rule: &str, to: &str) -> ConstructTranslation {
401 translate_native_construct_in(concept_records(), from, rule, to)
402}
403
404#[must_use]
406pub fn translate_native_construct_in(
407 records: &[ConceptRecord],
408 from: &str,
409 rule: &str,
410 to: &str,
411) -> ConstructTranslation {
412 let unknown = ConstructTranslation {
413 relation: ConstructTranslationRelation::Unknown,
414 concept: None,
415 rules: Vec::new(),
416 };
417 let (Some(source), Some(target)) = (native_grammar_source(from), native_grammar_source(to))
418 else {
419 return unknown;
420 };
421 let meanings: Vec<&ConceptRecord> = records
422 .iter()
423 .filter(|record| {
424 record
425 .source_aliases
426 .iter()
427 .any(|alias| alias.source == source && alias.name == rule)
428 })
429 .collect();
430 let record = match meanings.as_slice() {
431 [] => return unknown,
432 [record] => record,
433 _ => {
434 return ConstructTranslation {
435 relation: ConstructTranslationRelation::Ambiguous,
436 ..unknown
437 };
438 }
439 };
440 let rules: Vec<String> = record
441 .source_aliases
442 .iter()
443 .filter(|alias| alias.source == target)
444 .map(|alias| alias.name.clone())
445 .collect();
446 ConstructTranslation {
447 relation: relation_of(&rules),
448 concept: Some(record.id.clone()),
449 rules,
450 }
451}
452
453const fn relation_of(rules: &[String]) -> ConstructTranslationRelation {
454 match rules.len() {
455 1 => ConstructTranslationRelation::Translated,
456 0 => ConstructTranslationRelation::Untranslatable,
457 _ => ConstructTranslationRelation::Ambiguous,
458 }
459}
460
461pub fn translate_native_construct_decorated(
473 from: &str,
474 rule: &str,
475 to: &str,
476 decorators: &DecoratorSet,
477) -> Result<ConstructTranslation, DecoratorError> {
478 let found = translate_native_construct(from, rule, to);
479 if !decorators.has(DecoratorLevel::ConceptMapping) {
480 return Ok(found);
481 }
482 let joined = found.rules.join(" ");
483 let record = decorator_record([
484 ("from", from),
485 ("to", to),
486 ("rule", rule),
487 ("relation", found.relation.as_str()),
488 ("concept", found.concept.as_deref().unwrap_or_default()),
489 ("rules", &joined),
490 ]);
491 let Some(decorated) = decorators.decorate(DecoratorLevel::ConceptMapping, &record) else {
492 return Ok(ConstructTranslation {
493 relation: ConstructTranslationRelation::Unknown,
494 concept: None,
495 rules: Vec::new(),
496 });
497 };
498 let rules: Vec<String> = record_field(&decorated, "rules")
499 .split(' ')
500 .filter(|name| !name.is_empty())
501 .map(str::to_owned)
502 .collect();
503 let relation = record_field(&decorated, "relation");
504 let relation =
505 if relation != found.relation.as_str() || record_field(&decorated, "rules") == joined {
506 ConstructTranslationRelation::parse(relation).ok_or_else(|| {
507 DecoratorError::new(format!("a decorator set the unknown relation {relation:?}"))
508 })?
509 } else {
510 relation_of(&rules)
511 };
512 let concept = record_field(&decorated, "concept");
513 Ok(ConstructTranslation {
514 relation,
515 concept: (!concept.is_empty()).then(|| concept.to_owned()),
516 rules,
517 })
518}
519
520#[derive(Debug, Clone, PartialEq, Eq)]
523pub struct ConstructTree {
524 pub kind: String,
526 pub concept: String,
528 pub children: Vec<Self>,
530}
531
532#[must_use]
538pub fn native_construct_tree(id: &str, source: &str) -> Option<ConstructTree> {
539 let concepts: BTreeMap<String, String> = native_grammar_rule_concepts(id)
540 .into_iter()
541 .filter_map(|NativeRuleConcept { rule, concept }| Some((rule, concept?)))
542 .collect();
543 let outcome = native_parser(id)?
544 .parse_tree(source.as_bytes(), &FeatureParseOptions::default())
545 .ok()?;
546 let tree = outcome.tree?;
547 if !matches!(tree, SyntaxTree::Node { .. }) {
548 return None;
549 }
550 constructs(&tree, &concepts)?.into_iter().next()
551}
552
553fn constructs(
554 node: &SyntaxTree,
555 concepts: &BTreeMap<String, String>,
556) -> Option<Vec<ConstructTree>> {
557 let (kind, children) = match node {
558 SyntaxTree::Node { kind, children, .. } => {
559 let mut inner = Vec::new();
560 for child in children {
561 inner.extend(constructs(child, concepts)?);
562 }
563 (Some(kind), inner)
564 }
565 SyntaxTree::Token {
566 kind,
567 trivia: false,
568 ..
569 } => (kind.as_ref(), Vec::new()),
570 SyntaxTree::Token { .. } | SyntaxTree::Embed { .. } => (None, Vec::new()),
571 SyntaxTree::Error { .. } | SyntaxTree::Missing { .. } => return None,
572 };
573 match kind.and_then(|kind| Some((kind, concepts.get(kind)?))) {
574 Some((kind, concept)) => Some(vec![ConstructTree {
575 kind: kind.clone(),
576 concept: concept.clone(),
577 children,
578 }]),
579 None => Some(children),
580 }
581}
582
583#[derive(Debug, Clone, PartialEq, Eq)]
585pub struct ConstructTranslationProblem {
586 pub kind: String,
588 pub concept: Option<String>,
590 pub relation: ConstructTranslationRelation,
592}
593
594#[derive(Debug, Clone, PartialEq, Eq)]
596pub struct ConstructTreeTranslation {
597 pub tree: Option<ConstructTree>,
600 pub problems: Vec<ConstructTranslationProblem>,
602}
603
604#[must_use]
607pub fn translate_native_construct_tree(
608 tree: &ConstructTree,
609 from: &str,
610 to: &str,
611) -> ConstructTreeTranslation {
612 fn translate(
613 node: &ConstructTree,
614 from: &str,
615 to: &str,
616 problems: &mut Vec<ConstructTranslationProblem>,
617 ) -> Option<ConstructTree> {
618 let translation = translate_native_construct(from, &node.kind, to);
619 let children: Vec<Option<ConstructTree>> = node
620 .children
621 .iter()
622 .map(|child| translate(child, from, to, problems))
623 .collect();
624 if translation.relation != ConstructTranslationRelation::Translated {
625 problems.push(ConstructTranslationProblem {
626 kind: node.kind.clone(),
627 concept: translation.concept,
628 relation: translation.relation,
629 });
630 return None;
631 }
632 Some(ConstructTree {
633 kind: translation.rules[0].clone(),
634 concept: translation.concept.unwrap_or_default(),
635 children: children.into_iter().collect::<Option<Vec<_>>>()?,
636 })
637 }
638 let mut problems = Vec::new();
639 let translated = translate(tree, from, to, &mut problems);
640 ConstructTreeTranslation {
641 tree: if problems.is_empty() {
642 translated
643 } else {
644 None
645 },
646 problems,
647 }
648}