1use std::cmp::Reverse;
4use std::collections::{BTreeMap, BTreeSet};
5use std::error::Error;
6use std::fmt;
7
8use super::{FeatureExpr, GRAMMAR_CONCEPTS, Grammar, GrammarExpr, GrammarRule};
9use crate::{
10 LinkMetadata, LinkNetwork, LinkQuery, LinkType, ParseConfiguration, TranslationRule,
11 TranslationRuleSet, TranslationTemplate, current_concept_id,
12};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum GrammarTranslateError {
17 UnknownConcept {
19 rule: String,
21 concept: String,
23 },
24 NameCollision {
26 language: String,
28 name: String,
30 },
31}
32
33impl fmt::Display for GrammarTranslateError {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::UnknownConcept { rule, concept } => {
37 write!(
38 formatter,
39 "rule `{rule}` references unknown grammar concept `{concept}`"
40 )
41 }
42 Self::NameCollision { language, name } => write!(
43 formatter,
44 "translating grammar surface to `{language}` produced duplicate rule name `{name}`"
45 ),
46 }
47 }
48}
49
50impl Error for GrammarTranslateError {}
51
52pub fn translate_grammar_surface(
66 grammar: &Grammar,
67 target_language: &str,
68 rules: &TranslationRuleSet,
69) -> Result<Grammar, GrammarTranslateError> {
70 let rename_map = translated_rule_names(grammar, target_language, rules)?;
71 let mut translated = Grammar::new();
72
73 if let Some(source_format) = grammar.source_format() {
74 translated.set_source_format(source_format);
75 }
76 if let Some(start) = grammar.start() {
77 translated.set_start(renamed_name(start, &rename_map));
78 }
79
80 for rule in grammar.rules() {
81 let mut translated_rule = GrammarRule::new(
82 renamed_name(rule.name(), &rename_map),
83 rename_expr(rule.expr(), &rename_map),
84 )
85 .with_kind(rule.kind());
86
87 if let Some(concept) = rule.concept() {
88 translated_rule = translated_rule.with_concept(concept);
89 }
90 if let Some(doc) = rule.doc() {
91 translated_rule =
92 translated_rule.with_doc(translate_doc_comment(doc, target_language, rules));
93 }
94
95 translated.add_rule(translated_rule);
96 }
97
98 Ok(translated)
99}
100
101#[must_use]
104pub fn grammar_concept_translation_rules() -> TranslationRuleSet {
105 let grammar_concepts = GRAMMAR_CONCEPTS
106 .iter()
107 .map(|concept| concept.id)
108 .collect::<BTreeSet<_>>();
109 let mut rules = TranslationRuleSet::new("grammar-concepts");
110
111 for surface in GRAMMAR_SURFACE_TRANSLATIONS {
112 debug_assert!(
113 grammar_concepts.contains(surface.concept)
114 || surface.concept.starts_with("grammar.")
115 || surface.concept.starts_with("grammar::concept::"),
116 "grammar translation concept should use a grammar concept namespace"
117 );
118 rules.add_rule(
119 TranslationRule::new(
120 surface.concept,
121 LinkQuery::by_type(LinkType::Concept).with_term(surface.concept),
122 )
123 .with_template("English", surface.english)
124 .with_template("en", surface.english)
125 .with_template("Russian", surface.russian)
126 .with_template("ru", surface.russian),
127 );
128 }
129
130 rules
131}
132
133fn translated_rule_names(
134 grammar: &Grammar,
135 target_language: &str,
136 rules: &TranslationRuleSet,
137) -> Result<BTreeMap<String, String>, GrammarTranslateError> {
138 let mut rename_map = BTreeMap::new();
139 let mut used_names = BTreeMap::<String, String>::new();
140
141 for rule in grammar.rules() {
142 let resolved = resolve_rule_concept(rule, rules);
143 let translated_name = match resolved {
144 Some(ResolvedConcept {
145 concept,
146 explicit: true,
147 }) => translate_concept_surface(&concept, target_language, rules).ok_or_else(|| {
148 GrammarTranslateError::UnknownConcept {
149 rule: rule.name().to_string(),
150 concept,
151 }
152 })?,
153 Some(ResolvedConcept {
154 concept,
155 explicit: false,
156 }) => translate_concept_surface(&concept, target_language, rules)
157 .unwrap_or_else(|| rule.name().to_string()),
158 None => rule.name().to_string(),
159 };
160
161 if let Some(previous_rule) = used_names.insert(translated_name.clone(), rule.name().into())
162 && previous_rule != rule.name()
163 {
164 return Err(GrammarTranslateError::NameCollision {
165 language: target_language.to_string(),
166 name: translated_name,
167 });
168 }
169 rename_map.insert(rule.name().to_string(), translated_name);
170 }
171
172 Ok(rename_map)
173}
174
175#[derive(Clone, Debug, PartialEq, Eq)]
176struct ResolvedConcept {
177 concept: String,
178 explicit: bool,
179}
180
181fn resolve_rule_concept(rule: &GrammarRule, rules: &TranslationRuleSet) -> Option<ResolvedConcept> {
182 rule.concept()
183 .map(|concept| ResolvedConcept {
184 concept: current_concept_id(concept).to_string(),
185 explicit: true,
186 })
187 .or_else(|| infer_concept_from_surface(rule.name(), rules))
188}
189
190fn infer_concept_from_surface(name: &str, rules: &TranslationRuleSet) -> Option<ResolvedConcept> {
191 for rule in rules.rules() {
192 let Some(concept) = concept_id_for_translation_rule(rule) else {
193 continue;
194 };
195 if concept == name
196 || rule
197 .templates()
198 .values()
199 .any(|template| template.source() == name)
200 {
201 return Some(ResolvedConcept {
202 concept: concept.to_string(),
203 explicit: false,
204 });
205 }
206 }
207
208 None
209}
210
211fn concept_id_for_translation_rule(rule: &TranslationRule) -> Option<&str> {
212 rule.query()
213 .term_filter()
214 .or_else(|| concept_like_rule_name(rule.name()))
215}
216
217fn concept_like_rule_name(name: &str) -> Option<&str> {
218 (name.starts_with("grammar.") || name.starts_with("grammar::concept::")).then_some(name)
219}
220
221fn translate_concept_surface(
222 concept: &str,
223 target_language: &str,
224 rules: &TranslationRuleSet,
225) -> Option<String> {
226 render_concept_with_rules(concept, target_language, rules).or_else(|| {
227 rules
228 .rules()
229 .iter()
230 .find(|rule| concept_id_for_translation_rule(rule) == Some(concept))
231 .and_then(|rule| template_for_language(rule.templates(), target_language))
232 .map(ToString::to_string)
233 })
234}
235
236fn render_concept_with_rules(
237 concept: &str,
238 target_language: &str,
239 rules: &TranslationRuleSet,
240) -> Option<String> {
241 let mut network = LinkNetwork::new();
242 let concept_link = network.intern_concept(concept, None);
243 network.insert_link(
244 [concept_link],
245 LinkMetadata::new()
246 .with_link_type(LinkType::Semantic)
247 .with_named(true)
248 .with_term(concept),
249 );
250
251 for language in language_lookup_order(target_language) {
252 let rendered =
253 network.reconstruct_text_as_with_rules(&language, ParseConfiguration::default(), rules);
254 if !rendered.is_empty() {
255 return Some(rendered);
256 }
257 }
258
259 None
260}
261
262fn template_for_language<'a>(
263 templates: &'a BTreeMap<String, TranslationTemplate>,
264 language: &str,
265) -> Option<&'a str> {
266 language_lookup_order(language)
267 .into_iter()
268 .find_map(|candidate| templates.get(&candidate).map(TranslationTemplate::source))
269}
270
271fn language_lookup_order(language: &str) -> Vec<String> {
272 match canonical_language(language) {
273 Some(("English", "en")) => vec!["English".to_string(), "en".to_string()],
274 Some(("Russian", "ru")) => vec!["Russian".to_string(), "ru".to_string()],
275 _ => vec![language.to_string()],
276 }
277}
278
279fn canonical_language(language: &str) -> Option<(&'static str, &'static str)> {
280 match language.to_ascii_lowercase().as_str() {
281 "english" | "en" => Some(("English", "en")),
282 "russian" | "ru" => Some(("Russian", "ru")),
283 _ => None,
284 }
285}
286
287fn renamed_name(name: &str, rename_map: &BTreeMap<String, String>) -> String {
288 rename_map
289 .get(name)
290 .cloned()
291 .unwrap_or_else(|| name.to_string())
292}
293
294fn rename_expr(expr: &GrammarExpr, rename_map: &BTreeMap<String, String>) -> GrammarExpr {
295 match expr {
296 GrammarExpr::NonTerminal(name) => GrammarExpr::NonTerminal(renamed_name(name, rename_map)),
297 GrammarExpr::Choice {
298 ordered,
299 alternatives,
300 } => GrammarExpr::Choice {
301 ordered: *ordered,
302 alternatives: alternatives
303 .iter()
304 .map(|expr| rename_expr(expr, rename_map))
305 .collect(),
306 },
307 GrammarExpr::Sequence(items) => GrammarExpr::Sequence(
308 items
309 .iter()
310 .map(|expr| rename_expr(expr, rename_map))
311 .collect(),
312 ),
313 GrammarExpr::Optional(expr) => {
314 GrammarExpr::Optional(Box::new(rename_expr(expr, rename_map)))
315 }
316 GrammarExpr::ZeroOrMore(expr) => {
317 GrammarExpr::ZeroOrMore(Box::new(rename_expr(expr, rename_map)))
318 }
319 GrammarExpr::OneOrMore(expr) => {
320 GrammarExpr::OneOrMore(Box::new(rename_expr(expr, rename_map)))
321 }
322 GrammarExpr::Repeat { expr, min, max } => GrammarExpr::Repeat {
323 expr: Box::new(rename_expr(expr, rename_map)),
324 min: *min,
325 max: *max,
326 },
327 GrammarExpr::And(expr) => GrammarExpr::And(Box::new(rename_expr(expr, rename_map))),
328 GrammarExpr::Not(expr) => GrammarExpr::Not(Box::new(rename_expr(expr, rename_map))),
329 GrammarExpr::Capture { label, expr } => GrammarExpr::Capture {
330 label: label.clone(),
331 expr: Box::new(rename_expr(expr, rename_map)),
332 },
333 GrammarExpr::Feature(feature) => {
334 let mut copy =
335 GrammarExpr::rewrite_feature(feature, |inner| rename_expr(inner, rename_map));
336 if let GrammarExpr::Feature(renamed) = &mut copy
337 && let FeatureExpr::Call { name, .. } = renamed.as_mut()
338 {
339 *name = renamed_name(name, rename_map);
340 }
341 copy
342 }
343 GrammarExpr::Empty
344 | GrammarExpr::Terminal(_)
345 | GrammarExpr::TerminalInsensitive(_)
346 | GrammarExpr::CharRange(_, _)
347 | GrammarExpr::CharClass { .. }
348 | GrammarExpr::AnyChar => expr.clone(),
349 }
350}
351
352fn translate_doc_comment(doc: &str, target_language: &str, rules: &TranslationRuleSet) -> String {
353 for source_language in doc_source_languages(target_language) {
354 let network = LinkNetwork::parse(doc, source_language, ParseConfiguration::default());
355 let rendered = network.reconstruct_text_as_with_rules(
356 target_language,
357 ParseConfiguration::default(),
358 rules,
359 );
360 if rendered != doc {
361 return rendered;
362 }
363 }
364
365 let replaced = replace_known_concept_surfaces(doc, target_language, rules);
366 if replaced == doc {
367 doc.to_string()
368 } else {
369 replaced
370 }
371}
372
373fn doc_source_languages(target_language: &str) -> Vec<&'static str> {
374 match canonical_language(target_language) {
375 Some(("Russian", _)) => vec!["English"],
376 Some(("English", _)) => vec!["Russian"],
377 _ => vec!["English", "Russian"],
378 }
379}
380
381fn replace_known_concept_surfaces(
382 text: &str,
383 target_language: &str,
384 rules: &TranslationRuleSet,
385) -> String {
386 let mut replacements = rules
387 .rules()
388 .iter()
389 .filter_map(|rule| {
390 template_for_language(rule.templates(), target_language).map(|target| (rule, target))
391 })
392 .flat_map(|(rule, target)| {
393 rule.templates()
394 .values()
395 .filter_map(move |template| replacement_pair(template.source(), target))
396 })
397 .collect::<Vec<_>>();
398
399 replacements.sort_by_key(|replacement| Reverse(replacement.source.len()));
400 replacements.dedup();
401
402 replacements
403 .into_iter()
404 .fold(text.to_string(), |current, pair| {
405 replace_surface(¤t, &pair.source, &pair.target)
406 })
407}
408
409fn replacement_pair(source: &str, target: &str) -> Option<ReplacementPair> {
410 (!source.is_empty() && source != target && !source.contains('{') && !target.contains('{')).then(
411 || ReplacementPair {
412 source: source.to_string(),
413 target: target.to_string(),
414 },
415 )
416}
417
418#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
419struct ReplacementPair {
420 source: String,
421 target: String,
422}
423
424fn replace_surface(text: &str, source: &str, target: &str) -> String {
425 let mut output = String::with_capacity(text.len());
426 let mut index = 0;
427
428 while index < text.len() {
429 if text[index..].starts_with(source)
430 && text.is_char_boundary(index + source.len())
431 && has_surface_boundaries(text, source, index, index + source.len())
432 {
433 output.push_str(target);
434 index += source.len();
435 continue;
436 }
437
438 let character = text[index..]
439 .chars()
440 .next()
441 .expect("index should be inside the string");
442 output.push(character);
443 index += character.len_utf8();
444 }
445
446 output
447}
448
449fn has_surface_boundaries(text: &str, source: &str, start: usize, end: usize) -> bool {
450 let first = source.chars().next();
451 let last = source.chars().next_back();
452 let before = text[..start].chars().next_back();
453 let after = text[end..].chars().next();
454
455 let start_ok = first.is_none_or(|character| !is_word_character(character))
456 || !before.is_some_and(is_word_character);
457 let end_ok = last.is_none_or(|character| !is_word_character(character))
458 || !after.is_some_and(is_word_character);
459
460 start_ok && end_ok
461}
462
463fn is_word_character(character: char) -> bool {
464 character.is_alphanumeric() || character == '_'
465}
466
467#[derive(Clone, Copy, Debug, PartialEq, Eq)]
468struct SurfaceTranslation {
469 concept: &'static str,
470 english: &'static str,
471 russian: &'static str,
472}
473
474const GRAMMAR_SURFACE_TRANSLATIONS: &[SurfaceTranslation] = &[
475 SurfaceTranslation {
476 concept: "grammar.rule",
477 english: "rule",
478 russian: "правило",
479 },
480 SurfaceTranslation {
481 concept: "grammar.sequence",
482 english: "sequence",
483 russian: "последовательность",
484 },
485 SurfaceTranslation {
486 concept: "grammar.ordered-choice",
487 english: "ordered choice",
488 russian: "упорядоченный выбор",
489 },
490 SurfaceTranslation {
491 concept: "grammar.unordered-choice",
492 english: "choice",
493 russian: "выбор",
494 },
495 SurfaceTranslation {
496 concept: "grammar.counted-repetition",
497 english: "repetition",
498 russian: "повторение",
499 },
500 SurfaceTranslation {
501 concept: "grammar.zero-or-more-repetition",
502 english: "zero or more",
503 russian: "ноль или более",
504 },
505 SurfaceTranslation {
506 concept: "grammar.one-or-more-repetition",
507 english: "one or more",
508 russian: "один или более",
509 },
510 SurfaceTranslation {
511 concept: "grammar.optional-expression",
512 english: "optional",
513 russian: "необязательный",
514 },
515 SurfaceTranslation {
516 concept: "grammar.terminal",
517 english: "terminal",
518 russian: "терминал",
519 },
520 SurfaceTranslation {
521 concept: "grammar.nonterminal",
522 english: "non-terminal",
523 russian: "нетерминал",
524 },
525 SurfaceTranslation {
526 concept: "grammar.character-class",
527 english: "character class",
528 russian: "класс символов",
529 },
530 SurfaceTranslation {
531 concept: "grammar.character-range",
532 english: "character range",
533 russian: "диапазон символов",
534 },
535 SurfaceTranslation {
536 concept: "grammar.any-character",
537 english: "any character",
538 russian: "любой символ",
539 },
540 SurfaceTranslation {
541 concept: "grammar.positive-predicate",
542 english: "positive predicate",
543 russian: "положительный предикат",
544 },
545 SurfaceTranslation {
546 concept: "grammar.negative-predicate",
547 english: "negative predicate",
548 russian: "отрицательный предикат",
549 },
550 SurfaceTranslation {
551 concept: "grammar.capture",
552 english: "capture",
553 russian: "захват",
554 },
555 SurfaceTranslation {
556 concept: "grammar.empty-expression",
557 english: "empty",
558 russian: "пусто",
559 },
560 SurfaceTranslation {
561 concept: "grammar.expression",
562 english: "expression",
563 russian: "выражение",
564 },
565 SurfaceTranslation {
566 concept: "grammar.term",
567 english: "term",
568 russian: "слагаемое",
569 },
570 SurfaceTranslation {
571 concept: "grammar.factor",
572 english: "factor",
573 russian: "множитель",
574 },
575 SurfaceTranslation {
576 concept: "grammar.number",
577 english: "number",
578 russian: "число",
579 },
580 SurfaceTranslation {
581 concept: "grammar.digit",
582 english: "digit",
583 russian: "цифра",
584 },
585 SurfaceTranslation {
586 concept: "grammar.letter",
587 english: "letter",
588 russian: "буква",
589 },
590 SurfaceTranslation {
591 concept: "grammar.identifier",
592 english: "identifier",
593 russian: "идентификатор",
594 },
595 SurfaceTranslation {
596 concept: "grammar.statement",
597 english: "statement",
598 russian: "оператор",
599 },
600 SurfaceTranslation {
601 concept: "grammar.item",
602 english: "item",
603 russian: "элемент",
604 },
605 SurfaceTranslation {
606 concept: "grammar.list",
607 english: "list",
608 russian: "список",
609 },
610 SurfaceTranslation {
611 concept: "grammar.value",
612 english: "value",
613 russian: "значение",
614 },
615 SurfaceTranslation {
616 concept: "grammar.name",
617 english: "name",
618 russian: "имя",
619 },
620 SurfaceTranslation {
621 concept: "grammar.object",
622 english: "object",
623 russian: "объект",
624 },
625 SurfaceTranslation {
626 concept: "grammar.member",
627 english: "member",
628 russian: "член",
629 },
630 SurfaceTranslation {
631 concept: "grammar.string",
632 english: "string",
633 russian: "строка",
634 },
635 SurfaceTranslation {
636 concept: "grammar.boolean-value",
637 english: "boolean",
638 russian: "логическое",
639 },
640 SurfaceTranslation {
641 concept: "grammar.null",
642 english: "null",
643 russian: "нуль",
644 },
645 SurfaceTranslation {
646 concept: "grammar::concept::expression",
647 english: "expression",
648 russian: "выражение",
649 },
650 SurfaceTranslation {
651 concept: "grammar::concept::term",
652 english: "term",
653 russian: "слагаемое",
654 },
655 SurfaceTranslation {
656 concept: "grammar::concept::factor",
657 english: "factor",
658 russian: "множитель",
659 },
660];