1use crate::builder::assign_group_ids;
23use crate::document::{Document, Stage, StageItem, Sub, SubTo, Tests};
24use crate::error::{Error, Result};
25use crate::item::Item;
26use crate::stdlib::{Funcall, Prim};
27use std::collections::BTreeMap;
28use std::rc::Rc;
29
30pub fn reverse_name(name: &str) -> String {
38 let mut parts: Vec<&str> = name.split('-').collect();
39 if parts.len() >= 4 {
40 parts.swap(2, 3);
41 let swapped = parts.join("-");
42 if swapped != name {
43 return swapped;
44 }
45 }
46 let stripped = name.replace("-reverse", "");
47 if stripped != name {
48 stripped
49 } else {
50 format!("{name}-reverse")
51 }
52}
53
54pub fn reverse_document(doc: &Document) -> Result<Document> {
58 let mut stages = BTreeMap::new();
59 for (name, stage) in &doc.stages {
60 stages.insert(name.clone(), reverse_stage(stage)?);
61 }
62 let mut rulesets = BTreeMap::new();
65 for (name, stage) in &doc.rulesets {
66 rulesets.insert(name.clone(), reverse_stage(stage)?);
67 }
68 let mut dep_aliases = BTreeMap::new();
69 for (alias, dep) in &doc.dep_aliases {
70 dep_aliases.insert(alias.clone(), Rc::new(reverse_document(dep)?));
71 }
72 let mut imports = Vec::with_capacity(doc.imports.len());
73 for dep in &doc.imports {
74 imports.push(Rc::new(reverse_document(dep)?));
75 }
76 Ok(Document {
77 name: reverse_name(&doc.name),
78 system_code: doc.system_code.clone(),
79 aliases: doc.aliases.clone(),
80 stages,
81 rulesets,
82 dep_aliases,
83 imports,
84 tests: Tests {
87 data: doc
88 .tests
89 .data
90 .iter()
91 .map(|(a, b)| (b.clone(), a.clone()))
92 .collect(),
93 },
94 metadata: reverse_metadata(&doc.metadata),
96 languages: doc.languages.clone(),
98 })
99}
100
101fn reverse_metadata(metadata: &[(String, String)]) -> Vec<(String, String)> {
102 let src = metadata
103 .iter()
104 .find(|(k, _)| k == "source_script")
105 .map(|(_, v)| v.clone());
106 let dst = metadata
107 .iter()
108 .find(|(k, _)| k == "destination_script")
109 .map(|(_, v)| v.clone());
110 let mut out = metadata.to_vec();
111 for (k, v) in out.iter_mut() {
112 match k.as_str() {
113 "source_script" => {
114 if let Some(d) = &dst {
115 *v = d.clone();
116 }
117 }
118 "destination_script" => {
119 if let Some(s) = &src {
120 *v = s.clone();
121 }
122 }
123 _ => {}
124 }
125 }
126 out
127}
128
129pub fn reverse_stage(stage: &Stage) -> Result<Stage> {
133 let mut items: Vec<StageItem> = Vec::with_capacity(stage.items.len());
134 for item in stage.items.iter().rev() {
135 items.push(reverse_stage_item(item)?);
136 }
137 Ok(Stage {
138 name: stage.name.clone(),
139 items,
140 reverse_run: toggle(stage.reverse_run),
141 })
142}
143
144fn toggle(run: Option<bool>) -> Option<bool> {
145 run.map(|b| !b)
146}
147
148fn reverse_stage_item(item: &StageItem) -> Result<StageItem> {
149 Ok(match item {
150 StageItem::Parallel(rules) => {
151 let mut out = Vec::with_capacity(rules.len());
152 for rule in rules.iter().rev() {
153 out.push(reverse_sub(rule)?);
154 }
155 StageItem::Parallel(out)
156 }
157 StageItem::Sequential(rules) => {
158 let mut out = Vec::with_capacity(rules.len());
159 for rule in rules.iter().rev() {
160 out.push(reverse_sub(rule)?);
161 }
162 StageItem::Sequential(out)
163 }
164 StageItem::Run {
165 dep,
166 stage,
167 reverse_run,
168 } => StageItem::Run {
169 dep: dep.clone(),
170 stage: stage.clone(),
171 reverse_run: toggle(*reverse_run),
172 },
173 StageItem::Funcall(funcall) => StageItem::Funcall(reverse_funcall(funcall)?),
174 })
175}
176
177fn reverse_funcall(funcall: &Funcall) -> Result<Funcall> {
180 let name = funcall.name.reverse_name().ok_or_else(|| {
181 Error::Unsupported(
182 "funcall upcase has no reverse (the Ruby reverse_function map omits it)".into(),
183 )
184 })?;
185 Ok(Funcall {
186 name,
187 kwargs: funcall.kwargs.clone(),
188 reverse_run: toggle(funcall.reverse_run),
189 })
190}
191
192pub fn reverse_sub(sub: &Sub) -> Result<Sub> {
194 let (xfrom, xto) = match &sub.to {
195 SubTo::Upcase => (item_downcase(&sub.from)?, SubTo::Downcase),
198 SubTo::Downcase => (item_upcase(&sub.from)?, SubTo::Upcase),
199 SubTo::Item(to) => {
200 let (f, t) = reverse_transfer(sub.from.clone(), to.clone())?;
201 (f, SubTo::Item(t))
202 }
203 };
204
205 let mut rrun = toggle(sub.reverse_run);
210 if rrun.is_none()
211 && sub.before.is_none()
212 && sub.not_before.is_none()
213 && sub.after.is_none()
214 && sub.not_after.is_none()
215 && is_empty_from(&xfrom)
216 {
217 rrun = Some(true);
218 }
219
220 let mut reversed = Sub {
221 before: sub.before.clone(),
224 not_before: sub.not_before.clone(),
225 after: sub.after.clone(),
226 not_after: sub.not_after.clone(),
227 from: xfrom,
228 to: xto,
229 group_count: 0,
230 reverse_run: rrun,
231 when: sub.when.clone(),
234 };
235 reversed.group_count = assign_group_ids(&mut reversed);
238 Ok(reversed)
239}
240
241fn is_empty_from(item: &Item) -> bool {
243 match item {
244 Item::None | Item::Prim(Prim::None_) => true,
245 Item::Str(s) => s.is_empty(),
246 _ => false,
247 }
248}
249
250pub fn item_downcase(item: &Item) -> Result<Item> {
258 item_case(item, false)
259}
260
261pub fn item_upcase(item: &Item) -> Result<Item> {
263 item_case(item, true)
264}
265
266fn item_case(item: &Item, up: bool) -> Result<Item> {
267 let apply = |s: &str| {
268 if up {
269 s.to_uppercase()
270 } else {
271 s.to_lowercase()
272 }
273 };
274 Ok(match item {
275 Item::Str(s) => Item::Str(apply(s)),
276 Item::None => Item::None,
277 Item::Prim(_) | Item::Alias { .. } | Item::CaptureRef(_) => item.clone(),
279 Item::CaptureGroup { id, inner } => Item::CaptureGroup {
280 id: *id,
281 inner: Box::new(item_case(inner, up)?),
282 },
283 Item::Concat(parts) => Item::Concat(
284 parts
285 .iter()
286 .map(|p| item_case(p, up))
287 .collect::<Result<Vec<_>>>()?,
288 ),
289 Item::Range(lo, hi) => {
292 let mut entries = Vec::new();
293 let mut c = *lo;
294 loop {
295 entries.push(Item::Str(apply(&c.to_string())));
296 if c == *hi {
297 break;
298 }
299 match char::from_u32(c as u32 + 1) {
300 Some(n) => c = n,
301 None => break,
302 }
303 }
304 Item::Set(entries)
305 }
306 Item::Set(entries) => Item::Set(
307 entries
308 .iter()
309 .map(|e| item_case(e, up))
310 .collect::<Result<Vec<_>>>()?,
311 ),
312 Item::Maybe(_) | Item::Some(_) => {
314 return Err(Error::Unsupported(
315 "repeat item in a to-upcase/downcase sub (the Ruby reverse raises NoMethodError)"
316 .into(),
317 ))
318 }
319 Item::Func(_) => {
321 return Err(Error::Unsupported(
322 "function item in a sub `from` (the Ruby adapter rejects it)".into(),
323 ))
324 }
325 })
326}
327
328fn boundary_like(item: &Item) -> bool {
335 matches!(
336 item,
337 Item::Prim(Prim::Boundary | Prim::NonWordBoundary | Prim::LineStart | Prim::LineEnd)
338 )
339}
340
341fn group_plus_compact(prefix: Vec<Item>, last: Item) -> Item {
346 let mut children = prefix;
347 match last {
348 Item::Concat(parts) => children.extend(parts),
349 other => children.push(other),
350 }
351 let kept: Vec<Item> = children
352 .into_iter()
353 .filter(|c| match c {
354 Item::None | Item::Prim(Prim::None_) => false,
355 Item::Str(s) => !s.is_empty(),
356 _ => true,
357 })
358 .collect();
359 match kept.len() {
360 0 => Item::None,
361 1 => kept.into_iter().next().unwrap(),
362 _ => Item::Concat(kept),
363 }
364}
365
366fn reverse_transfer(from: Item, to: Item) -> Result<(Item, Item)> {
372 let mut from = from;
373 let mut to = to;
374
375 match &from {
376 Item::Concat(parts) => {
377 let first = parts.first().cloned();
379 let last = parts.last().cloned();
380
381 if let Some(f) = &first {
383 if boundary_like(f) {
384 to = group_plus_compact(vec![f.clone()], to);
385 let mut rest = parts.clone();
386 rest.remove(0);
387 from = compact_children(rest);
388 }
389 }
390 if let Some(l) = &last {
395 if boundary_like(l) {
396 match &from {
397 Item::Concat(prev) => {
398 let mut shorter = prev.clone();
399 shorter.pop();
400 to = group_plus_compact(vec![to], l.clone());
401 from = compact_children(shorter);
402 }
403 _ => {
404 return Err(Error::Unsupported(
405 "boundary hoist consumed the whole from group \
406 (the Ruby reverse raises NoMethodError)"
407 .into(),
408 ))
409 }
410 }
411 }
412 }
413 }
414 single => {
415 if boundary_like(single) {
419 to = if matches!(single, Item::Prim(Prim::LineEnd)) {
423 group_plus_compact(vec![to], single.clone())
424 } else {
425 group_plus_compact(vec![single.clone()], to)
426 };
427 from = Item::Prim(Prim::None_);
428 }
429 }
430 }
431
432 let mut state = TransferState::default();
434 let visited_from = transfer_visit(&from, Side::From, &mut state)?;
435 let visited_to = transfer_visit(&to, Side::To, &mut state)?;
436 Ok((visited_to, visited_from))
438}
439
440fn compact_children(children: Vec<Item>) -> Item {
442 let kept: Vec<Item> = children
443 .into_iter()
444 .filter(|c| match c {
445 Item::None | Item::Prim(Prim::None_) => false,
446 Item::Str(s) => !s.is_empty(),
447 _ => true,
448 })
449 .collect();
450 match kept.len() {
451 0 => Item::None,
452 1 => kept.into_iter().next().unwrap(),
453 _ => Item::Concat(kept),
454 }
455}
456
457#[derive(Clone, Copy, PartialEq)]
458enum Side {
459 From,
460 To,
461}
462
463#[derive(Default)]
464struct TransferState {
465 #[allow(dead_code)]
468 left: Vec<Item>,
469 right: std::collections::BTreeSet<usize>,
472}
473
474fn transfer_visit(node: &Item, side: Side, state: &mut TransferState) -> Result<Item> {
478 Ok(match node {
479 Item::Alias { name, .. } if name == "kor_maybedash" => {
485 state.left.push(node.clone());
486 Item::CaptureRef(state.left.len())
487 }
488 Item::Str(_) | Item::None | Item::Prim(_) | Item::Alias { .. } | Item::Range(_, _) => {
489 node.clone()
490 }
491 Item::Set(entries) => Item::Set(
492 entries
493 .iter()
494 .map(|e| transfer_visit(e, side, state))
495 .collect::<Result<Vec<_>>>()?,
496 ),
497 Item::Concat(parts) => Item::Concat(
498 parts
499 .iter()
500 .map(|p| transfer_visit(p, side, state))
501 .collect::<Result<Vec<_>>>()?,
502 ),
503 Item::Maybe(inner) => Item::Maybe(Box::new(transfer_visit(inner, side, state)?)),
505 Item::Some(inner) => Item::Some(Box::new(transfer_visit(inner, side, state)?)),
506 Item::CaptureRef(id) => match side {
507 Side::From => node.clone(),
509 Side::To => {
510 if state.right.contains(id) {
511 node.clone()
512 } else {
513 state.right.insert(*id);
514 match state.left.get(id.wrapping_sub(1)) {
515 Some(group) => group.clone(),
516 None => {
517 return Err(Error::MapLogic(
518 "Capture count doesn't match".into(),
519 ))
520 }
521 }
522 }
523 }
524 },
525 Item::CaptureGroup { inner, .. } => {
526 state.left.push(node.clone());
527 let out = Item::CaptureRef(state.left.len());
528 transfer_visit(inner, side, state)?;
530 out
531 }
532 Item::Func(_) => {
533 return Err(Error::Unsupported(
534 "Type Item::Func unhandled in reverse_transfer_visit (the Ruby raises \
535 \"Type ... unhandled!\")"
536 .into(),
537 ))
538 }
539 })
540}