1use std::cmp::Reverse;
4use std::collections::{BTreeMap, BTreeSet};
5use std::error::Error;
6use std::fmt;
7
8use super::{Grammar, GrammarExpr, GrammarRule, GRAMMAR_CONCEPTS};
9use crate::{
10 LinkMetadata, LinkNetwork, LinkQuery, LinkType, ParseConfiguration, TranslationRule,
11 TranslationRuleSet, TranslationTemplate,
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 {
163 if previous_rule != rule.name() {
164 return Err(GrammarTranslateError::NameCollision {
165 language: target_language.to_string(),
166 name: translated_name,
167 });
168 }
169 }
170 rename_map.insert(rule.name().to_string(), translated_name);
171 }
172
173 Ok(rename_map)
174}
175
176#[derive(Clone, Debug, PartialEq, Eq)]
177struct ResolvedConcept {
178 concept: String,
179 explicit: bool,
180}
181
182fn resolve_rule_concept(rule: &GrammarRule, rules: &TranslationRuleSet) -> Option<ResolvedConcept> {
183 rule.concept()
184 .map(|concept| ResolvedConcept {
185 concept: concept.to_string(),
186 explicit: true,
187 })
188 .or_else(|| infer_concept_from_surface(rule.name(), rules))
189}
190
191fn infer_concept_from_surface(name: &str, rules: &TranslationRuleSet) -> Option<ResolvedConcept> {
192 for rule in rules.rules() {
193 let Some(concept) = concept_id_for_translation_rule(rule) else {
194 continue;
195 };
196 if concept == name
197 || rule
198 .templates()
199 .values()
200 .any(|template| template.source() == name)
201 {
202 return Some(ResolvedConcept {
203 concept: concept.to_string(),
204 explicit: false,
205 });
206 }
207 }
208
209 None
210}
211
212fn concept_id_for_translation_rule(rule: &TranslationRule) -> Option<&str> {
213 rule.query()
214 .term_filter()
215 .or_else(|| concept_like_rule_name(rule.name()))
216}
217
218fn concept_like_rule_name(name: &str) -> Option<&str> {
219 (name.starts_with("grammar.") || name.starts_with("grammar::concept::")).then_some(name)
220}
221
222fn translate_concept_surface(
223 concept: &str,
224 target_language: &str,
225 rules: &TranslationRuleSet,
226) -> Option<String> {
227 render_concept_with_rules(concept, target_language, rules).or_else(|| {
228 rules
229 .rules()
230 .iter()
231 .find(|rule| concept_id_for_translation_rule(rule) == Some(concept))
232 .and_then(|rule| template_for_language(rule.templates(), target_language))
233 .map(ToString::to_string)
234 })
235}
236
237fn render_concept_with_rules(
238 concept: &str,
239 target_language: &str,
240 rules: &TranslationRuleSet,
241) -> Option<String> {
242 let mut network = LinkNetwork::new();
243 let concept_link = network.intern_concept(concept, None);
244 network.insert_link(
245 [concept_link],
246 LinkMetadata::new()
247 .with_link_type(LinkType::Semantic)
248 .with_named(true)
249 .with_term(concept),
250 );
251
252 for language in language_lookup_order(target_language) {
253 let rendered =
254 network.reconstruct_text_as_with_rules(&language, ParseConfiguration::default(), rules);
255 if !rendered.is_empty() {
256 return Some(rendered);
257 }
258 }
259
260 None
261}
262
263fn template_for_language<'a>(
264 templates: &'a BTreeMap<String, TranslationTemplate>,
265 language: &str,
266) -> Option<&'a str> {
267 language_lookup_order(language)
268 .into_iter()
269 .find_map(|candidate| templates.get(&candidate).map(TranslationTemplate::source))
270}
271
272fn language_lookup_order(language: &str) -> Vec<String> {
273 match canonical_language(language) {
274 Some(("English", "en")) => vec!["English".to_string(), "en".to_string()],
275 Some(("Russian", "ru")) => vec!["Russian".to_string(), "ru".to_string()],
276 _ => vec![language.to_string()],
277 }
278}
279
280fn canonical_language(language: &str) -> Option<(&'static str, &'static str)> {
281 match language.to_ascii_lowercase().as_str() {
282 "english" | "en" => Some(("English", "en")),
283 "russian" | "ru" => Some(("Russian", "ru")),
284 _ => None,
285 }
286}
287
288fn renamed_name(name: &str, rename_map: &BTreeMap<String, String>) -> String {
289 rename_map
290 .get(name)
291 .cloned()
292 .unwrap_or_else(|| name.to_string())
293}
294
295fn rename_expr(expr: &GrammarExpr, rename_map: &BTreeMap<String, String>) -> GrammarExpr {
296 match expr {
297 GrammarExpr::NonTerminal(name) => GrammarExpr::NonTerminal(renamed_name(name, rename_map)),
298 GrammarExpr::Choice {
299 ordered,
300 alternatives,
301 } => GrammarExpr::Choice {
302 ordered: *ordered,
303 alternatives: alternatives
304 .iter()
305 .map(|expr| rename_expr(expr, rename_map))
306 .collect(),
307 },
308 GrammarExpr::Sequence(items) => GrammarExpr::Sequence(
309 items
310 .iter()
311 .map(|expr| rename_expr(expr, rename_map))
312 .collect(),
313 ),
314 GrammarExpr::Optional(expr) => {
315 GrammarExpr::Optional(Box::new(rename_expr(expr, rename_map)))
316 }
317 GrammarExpr::ZeroOrMore(expr) => {
318 GrammarExpr::ZeroOrMore(Box::new(rename_expr(expr, rename_map)))
319 }
320 GrammarExpr::OneOrMore(expr) => {
321 GrammarExpr::OneOrMore(Box::new(rename_expr(expr, rename_map)))
322 }
323 GrammarExpr::Repeat { expr, min, max } => GrammarExpr::Repeat {
324 expr: Box::new(rename_expr(expr, rename_map)),
325 min: *min,
326 max: *max,
327 },
328 GrammarExpr::And(expr) => GrammarExpr::And(Box::new(rename_expr(expr, rename_map))),
329 GrammarExpr::Not(expr) => GrammarExpr::Not(Box::new(rename_expr(expr, rename_map))),
330 GrammarExpr::Capture { label, expr } => GrammarExpr::Capture {
331 label: label.clone(),
332 expr: Box::new(rename_expr(expr, rename_map)),
333 },
334 GrammarExpr::Empty
335 | GrammarExpr::Terminal(_)
336 | GrammarExpr::TerminalInsensitive(_)
337 | GrammarExpr::CharRange(_, _)
338 | GrammarExpr::CharClass { .. }
339 | GrammarExpr::AnyChar => expr.clone(),
340 }
341}
342
343fn translate_doc_comment(doc: &str, target_language: &str, rules: &TranslationRuleSet) -> String {
344 for source_language in doc_source_languages(target_language) {
345 let network = LinkNetwork::parse(doc, source_language, ParseConfiguration::default());
346 let rendered = network.reconstruct_text_as_with_rules(
347 target_language,
348 ParseConfiguration::default(),
349 rules,
350 );
351 if rendered != doc {
352 return rendered;
353 }
354 }
355
356 let replaced = replace_known_concept_surfaces(doc, target_language, rules);
357 if replaced == doc {
358 doc.to_string()
359 } else {
360 replaced
361 }
362}
363
364fn doc_source_languages(target_language: &str) -> Vec<&'static str> {
365 match canonical_language(target_language) {
366 Some(("Russian", _)) => vec!["English"],
367 Some(("English", _)) => vec!["Russian"],
368 _ => vec!["English", "Russian"],
369 }
370}
371
372fn replace_known_concept_surfaces(
373 text: &str,
374 target_language: &str,
375 rules: &TranslationRuleSet,
376) -> String {
377 let mut replacements = rules
378 .rules()
379 .iter()
380 .filter_map(|rule| {
381 template_for_language(rule.templates(), target_language).map(|target| (rule, target))
382 })
383 .flat_map(|(rule, target)| {
384 rule.templates()
385 .values()
386 .filter_map(move |template| replacement_pair(template.source(), target))
387 })
388 .collect::<Vec<_>>();
389
390 replacements.sort_by_key(|replacement| Reverse(replacement.source.len()));
391 replacements.dedup();
392
393 replacements
394 .into_iter()
395 .fold(text.to_string(), |current, pair| {
396 replace_surface(¤t, &pair.source, &pair.target)
397 })
398}
399
400fn replacement_pair(source: &str, target: &str) -> Option<ReplacementPair> {
401 (!source.is_empty() && source != target && !source.contains('{') && !target.contains('{')).then(
402 || ReplacementPair {
403 source: source.to_string(),
404 target: target.to_string(),
405 },
406 )
407}
408
409#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
410struct ReplacementPair {
411 source: String,
412 target: String,
413}
414
415fn replace_surface(text: &str, source: &str, target: &str) -> String {
416 let mut output = String::with_capacity(text.len());
417 let mut index = 0;
418
419 while index < text.len() {
420 if text[index..].starts_with(source)
421 && text.is_char_boundary(index + source.len())
422 && has_surface_boundaries(text, source, index, index + source.len())
423 {
424 output.push_str(target);
425 index += source.len();
426 continue;
427 }
428
429 let character = text[index..]
430 .chars()
431 .next()
432 .expect("index should be inside the string");
433 output.push(character);
434 index += character.len_utf8();
435 }
436
437 output
438}
439
440fn has_surface_boundaries(text: &str, source: &str, start: usize, end: usize) -> bool {
441 let first = source.chars().next();
442 let last = source.chars().next_back();
443 let before = text[..start].chars().next_back();
444 let after = text[end..].chars().next();
445
446 let start_ok = first.map_or(true, |character| !is_word_character(character))
447 || !before.is_some_and(is_word_character);
448 let end_ok = last.map_or(true, |character| !is_word_character(character))
449 || !after.is_some_and(is_word_character);
450
451 start_ok && end_ok
452}
453
454fn is_word_character(character: char) -> bool {
455 character.is_alphanumeric() || character == '_'
456}
457
458#[derive(Clone, Copy, Debug, PartialEq, Eq)]
459struct SurfaceTranslation {
460 concept: &'static str,
461 english: &'static str,
462 russian: &'static str,
463}
464
465const GRAMMAR_SURFACE_TRANSLATIONS: &[SurfaceTranslation] = &[
466 SurfaceTranslation {
467 concept: "grammar.rule",
468 english: "rule",
469 russian: "правило",
470 },
471 SurfaceTranslation {
472 concept: "grammar.sequence",
473 english: "sequence",
474 russian: "последовательность",
475 },
476 SurfaceTranslation {
477 concept: "grammar.ordered-choice",
478 english: "ordered choice",
479 russian: "упорядоченный выбор",
480 },
481 SurfaceTranslation {
482 concept: "grammar.unordered-choice",
483 english: "choice",
484 russian: "выбор",
485 },
486 SurfaceTranslation {
487 concept: "grammar.repetition",
488 english: "repetition",
489 russian: "повторение",
490 },
491 SurfaceTranslation {
492 concept: "grammar.zero-or-more",
493 english: "zero or more",
494 russian: "ноль или более",
495 },
496 SurfaceTranslation {
497 concept: "grammar.one-or-more",
498 english: "one or more",
499 russian: "один или более",
500 },
501 SurfaceTranslation {
502 concept: "grammar.optional",
503 english: "optional",
504 russian: "необязательный",
505 },
506 SurfaceTranslation {
507 concept: "grammar.terminal",
508 english: "terminal",
509 russian: "терминал",
510 },
511 SurfaceTranslation {
512 concept: "grammar.non-terminal",
513 english: "non-terminal",
514 russian: "нетерминал",
515 },
516 SurfaceTranslation {
517 concept: "grammar.char-class",
518 english: "character class",
519 russian: "класс символов",
520 },
521 SurfaceTranslation {
522 concept: "grammar.char-range",
523 english: "character range",
524 russian: "диапазон символов",
525 },
526 SurfaceTranslation {
527 concept: "grammar.any-char",
528 english: "any character",
529 russian: "любой символ",
530 },
531 SurfaceTranslation {
532 concept: "grammar.positive-predicate",
533 english: "positive predicate",
534 russian: "положительный предикат",
535 },
536 SurfaceTranslation {
537 concept: "grammar.negative-predicate",
538 english: "negative predicate",
539 russian: "отрицательный предикат",
540 },
541 SurfaceTranslation {
542 concept: "grammar.capture",
543 english: "capture",
544 russian: "захват",
545 },
546 SurfaceTranslation {
547 concept: "grammar.empty",
548 english: "empty",
549 russian: "пусто",
550 },
551 SurfaceTranslation {
552 concept: "grammar.expression",
553 english: "expression",
554 russian: "выражение",
555 },
556 SurfaceTranslation {
557 concept: "grammar.term",
558 english: "term",
559 russian: "слагаемое",
560 },
561 SurfaceTranslation {
562 concept: "grammar.factor",
563 english: "factor",
564 russian: "множитель",
565 },
566 SurfaceTranslation {
567 concept: "grammar.number",
568 english: "number",
569 russian: "число",
570 },
571 SurfaceTranslation {
572 concept: "grammar.digit",
573 english: "digit",
574 russian: "цифра",
575 },
576 SurfaceTranslation {
577 concept: "grammar.letter",
578 english: "letter",
579 russian: "буква",
580 },
581 SurfaceTranslation {
582 concept: "grammar.identifier",
583 english: "identifier",
584 russian: "идентификатор",
585 },
586 SurfaceTranslation {
587 concept: "grammar.statement",
588 english: "statement",
589 russian: "оператор",
590 },
591 SurfaceTranslation {
592 concept: "grammar.item",
593 english: "item",
594 russian: "элемент",
595 },
596 SurfaceTranslation {
597 concept: "grammar.list",
598 english: "list",
599 russian: "список",
600 },
601 SurfaceTranslation {
602 concept: "grammar.value",
603 english: "value",
604 russian: "значение",
605 },
606 SurfaceTranslation {
607 concept: "grammar.name",
608 english: "name",
609 russian: "имя",
610 },
611 SurfaceTranslation {
612 concept: "grammar.object",
613 english: "object",
614 russian: "объект",
615 },
616 SurfaceTranslation {
617 concept: "grammar.member",
618 english: "member",
619 russian: "член",
620 },
621 SurfaceTranslation {
622 concept: "grammar.string",
623 english: "string",
624 russian: "строка",
625 },
626 SurfaceTranslation {
627 concept: "grammar.boolean",
628 english: "boolean",
629 russian: "логическое",
630 },
631 SurfaceTranslation {
632 concept: "grammar.null",
633 english: "null",
634 russian: "нуль",
635 },
636 SurfaceTranslation {
637 concept: "grammar::concept::expression",
638 english: "expression",
639 russian: "выражение",
640 },
641 SurfaceTranslation {
642 concept: "grammar::concept::term",
643 english: "term",
644 russian: "слагаемое",
645 },
646 SurfaceTranslation {
647 concept: "grammar::concept::factor",
648 english: "factor",
649 russian: "множитель",
650 },
651];