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 dep_aliases = BTreeMap::new();
63 for (alias, dep) in &doc.dep_aliases {
64 dep_aliases.insert(alias.clone(), Rc::new(reverse_document(dep)?));
65 }
66 let mut imports = Vec::with_capacity(doc.imports.len());
67 for dep in &doc.imports {
68 imports.push(Rc::new(reverse_document(dep)?));
69 }
70 Ok(Document {
71 name: reverse_name(&doc.name),
72 system_code: doc.system_code.clone(),
73 aliases: doc.aliases.clone(),
74 stages,
75 dep_aliases,
76 imports,
77 tests: Tests {
80 data: doc
81 .tests
82 .data
83 .iter()
84 .map(|(a, b)| (b.clone(), a.clone()))
85 .collect(),
86 },
87 metadata: reverse_metadata(&doc.metadata),
89 })
90}
91
92fn reverse_metadata(metadata: &[(String, String)]) -> Vec<(String, String)> {
93 let src = metadata
94 .iter()
95 .find(|(k, _)| k == "source_script")
96 .map(|(_, v)| v.clone());
97 let dst = metadata
98 .iter()
99 .find(|(k, _)| k == "destination_script")
100 .map(|(_, v)| v.clone());
101 let mut out = metadata.to_vec();
102 for (k, v) in out.iter_mut() {
103 match k.as_str() {
104 "source_script" => {
105 if let Some(d) = &dst {
106 *v = d.clone();
107 }
108 }
109 "destination_script" => {
110 if let Some(s) = &src {
111 *v = s.clone();
112 }
113 }
114 _ => {}
115 }
116 }
117 out
118}
119
120pub fn reverse_stage(stage: &Stage) -> Result<Stage> {
124 let mut items: Vec<StageItem> = Vec::with_capacity(stage.items.len());
125 for item in stage.items.iter().rev() {
126 items.push(reverse_stage_item(item)?);
127 }
128 Ok(Stage {
129 name: stage.name.clone(),
130 items,
131 reverse_run: toggle(stage.reverse_run),
132 })
133}
134
135fn toggle(run: Option<bool>) -> Option<bool> {
136 run.map(|b| !b)
137}
138
139fn reverse_stage_item(item: &StageItem) -> Result<StageItem> {
140 Ok(match item {
141 StageItem::Parallel(rules) => {
142 let mut out = Vec::with_capacity(rules.len());
143 for rule in rules.iter().rev() {
144 out.push(reverse_sub(rule)?);
145 }
146 StageItem::Parallel(out)
147 }
148 StageItem::Sequential(rules) => {
149 let mut out = Vec::with_capacity(rules.len());
150 for rule in rules.iter().rev() {
151 out.push(reverse_sub(rule)?);
152 }
153 StageItem::Sequential(out)
154 }
155 StageItem::Run {
156 dep,
157 stage,
158 reverse_run,
159 } => StageItem::Run {
160 dep: dep.clone(),
161 stage: stage.clone(),
162 reverse_run: toggle(*reverse_run),
163 },
164 StageItem::Funcall(funcall) => StageItem::Funcall(reverse_funcall(funcall)?),
165 })
166}
167
168fn reverse_funcall(funcall: &Funcall) -> Result<Funcall> {
171 let name = funcall.name.reverse_name().ok_or_else(|| {
172 Error::Unsupported(
173 "funcall upcase has no reverse (the Ruby reverse_function map omits it)".into(),
174 )
175 })?;
176 Ok(Funcall {
177 name,
178 kwargs: funcall.kwargs.clone(),
179 reverse_run: toggle(funcall.reverse_run),
180 })
181}
182
183pub fn reverse_sub(sub: &Sub) -> Result<Sub> {
185 let (xfrom, xto) = match &sub.to {
186 SubTo::Upcase => (item_downcase(&sub.from)?, SubTo::Downcase),
189 SubTo::Downcase => (item_upcase(&sub.from)?, SubTo::Upcase),
190 SubTo::Item(to) => {
191 let (f, t) = reverse_transfer(sub.from.clone(), to.clone())?;
192 (f, SubTo::Item(t))
193 }
194 };
195
196 let mut rrun = toggle(sub.reverse_run);
201 if rrun.is_none()
202 && sub.before.is_none()
203 && sub.not_before.is_none()
204 && sub.after.is_none()
205 && sub.not_after.is_none()
206 && is_empty_from(&xfrom)
207 {
208 rrun = Some(true);
209 }
210
211 let mut reversed = Sub {
212 before: sub.before.clone(),
215 not_before: sub.not_before.clone(),
216 after: sub.after.clone(),
217 not_after: sub.not_after.clone(),
218 from: xfrom,
219 to: xto,
220 group_count: 0,
221 reverse_run: rrun,
222 };
223 reversed.group_count = assign_group_ids(&mut reversed);
226 Ok(reversed)
227}
228
229fn is_empty_from(item: &Item) -> bool {
231 match item {
232 Item::None | Item::Prim(Prim::None_) => true,
233 Item::Str(s) => s.is_empty(),
234 _ => false,
235 }
236}
237
238pub fn item_downcase(item: &Item) -> Result<Item> {
246 item_case(item, false)
247}
248
249pub fn item_upcase(item: &Item) -> Result<Item> {
251 item_case(item, true)
252}
253
254fn item_case(item: &Item, up: bool) -> Result<Item> {
255 let apply = |s: &str| {
256 if up {
257 s.to_uppercase()
258 } else {
259 s.to_lowercase()
260 }
261 };
262 Ok(match item {
263 Item::Str(s) => Item::Str(apply(s)),
264 Item::None => Item::None,
265 Item::Prim(_) | Item::Alias { .. } | Item::CaptureRef(_) => item.clone(),
267 Item::CaptureGroup { id, inner } => Item::CaptureGroup {
268 id: *id,
269 inner: Box::new(item_case(inner, up)?),
270 },
271 Item::Concat(parts) => Item::Concat(
272 parts
273 .iter()
274 .map(|p| item_case(p, up))
275 .collect::<Result<Vec<_>>>()?,
276 ),
277 Item::Range(lo, hi) => {
280 let mut entries = Vec::new();
281 let mut c = *lo;
282 loop {
283 entries.push(Item::Str(apply(&c.to_string())));
284 if c == *hi {
285 break;
286 }
287 match char::from_u32(c as u32 + 1) {
288 Some(n) => c = n,
289 None => break,
290 }
291 }
292 Item::Set(entries)
293 }
294 Item::Set(entries) => Item::Set(
295 entries
296 .iter()
297 .map(|e| item_case(e, up))
298 .collect::<Result<Vec<_>>>()?,
299 ),
300 Item::Maybe(_) | Item::Some(_) => {
302 return Err(Error::Unsupported(
303 "repeat item in a to-upcase/downcase sub (the Ruby reverse raises NoMethodError)"
304 .into(),
305 ))
306 }
307 Item::Func(_) => {
309 return Err(Error::Unsupported(
310 "function item in a sub `from` (the Ruby adapter rejects it)".into(),
311 ))
312 }
313 })
314}
315
316fn boundary_like(item: &Item) -> bool {
323 matches!(
324 item,
325 Item::Prim(Prim::Boundary | Prim::NonWordBoundary | Prim::LineStart | Prim::LineEnd)
326 )
327}
328
329fn group_plus_compact(prefix: Vec<Item>, last: Item) -> Item {
334 let mut children = prefix;
335 match last {
336 Item::Concat(parts) => children.extend(parts),
337 other => children.push(other),
338 }
339 let kept: Vec<Item> = children
340 .into_iter()
341 .filter(|c| match c {
342 Item::None | Item::Prim(Prim::None_) => false,
343 Item::Str(s) => !s.is_empty(),
344 _ => true,
345 })
346 .collect();
347 match kept.len() {
348 0 => Item::None,
349 1 => kept.into_iter().next().unwrap(),
350 _ => Item::Concat(kept),
351 }
352}
353
354fn reverse_transfer(from: Item, to: Item) -> Result<(Item, Item)> {
360 let mut from = from;
361 let mut to = to;
362
363 match &from {
364 Item::Concat(parts) => {
365 let first = parts.first().cloned();
367 let last = parts.last().cloned();
368
369 if let Some(f) = &first {
371 if boundary_like(f) {
372 to = group_plus_compact(vec![f.clone()], to);
373 let mut rest = parts.clone();
374 rest.remove(0);
375 from = compact_children(rest);
376 }
377 }
378 if let Some(l) = &last {
383 if boundary_like(l) {
384 match &from {
385 Item::Concat(prev) => {
386 let mut shorter = prev.clone();
387 shorter.pop();
388 to = group_plus_compact(vec![to], l.clone());
389 from = compact_children(shorter);
390 }
391 _ => {
392 return Err(Error::Unsupported(
393 "boundary hoist consumed the whole from group \
394 (the Ruby reverse raises NoMethodError)"
395 .into(),
396 ))
397 }
398 }
399 }
400 }
401 }
402 single => {
403 if boundary_like(single) {
407 to = if matches!(single, Item::Prim(Prim::LineEnd)) {
411 group_plus_compact(vec![to], single.clone())
412 } else {
413 group_plus_compact(vec![single.clone()], to)
414 };
415 from = Item::Prim(Prim::None_);
416 }
417 }
418 }
419
420 let mut state = TransferState::default();
422 let visited_from = transfer_visit(&from, Side::From, &mut state)?;
423 let visited_to = transfer_visit(&to, Side::To, &mut state)?;
424 Ok((visited_to, visited_from))
426}
427
428fn compact_children(children: Vec<Item>) -> Item {
430 let kept: Vec<Item> = children
431 .into_iter()
432 .filter(|c| match c {
433 Item::None | Item::Prim(Prim::None_) => false,
434 Item::Str(s) => !s.is_empty(),
435 _ => true,
436 })
437 .collect();
438 match kept.len() {
439 0 => Item::None,
440 1 => kept.into_iter().next().unwrap(),
441 _ => Item::Concat(kept),
442 }
443}
444
445#[derive(Clone, Copy, PartialEq)]
446enum Side {
447 From,
448 To,
449}
450
451#[derive(Default)]
452struct TransferState {
453 #[allow(dead_code)]
456 left: Vec<Item>,
457 right: std::collections::BTreeSet<usize>,
460}
461
462fn transfer_visit(node: &Item, side: Side, state: &mut TransferState) -> Result<Item> {
466 Ok(match node {
467 Item::Alias { name, .. } if name == "kor_maybedash" => {
473 state.left.push(node.clone());
474 Item::CaptureRef(state.left.len())
475 }
476 Item::Str(_) | Item::None | Item::Prim(_) | Item::Alias { .. } | Item::Range(_, _) => {
477 node.clone()
478 }
479 Item::Set(entries) => Item::Set(
480 entries
481 .iter()
482 .map(|e| transfer_visit(e, side, state))
483 .collect::<Result<Vec<_>>>()?,
484 ),
485 Item::Concat(parts) => Item::Concat(
486 parts
487 .iter()
488 .map(|p| transfer_visit(p, side, state))
489 .collect::<Result<Vec<_>>>()?,
490 ),
491 Item::Maybe(inner) => Item::Maybe(Box::new(transfer_visit(inner, side, state)?)),
493 Item::Some(inner) => Item::Some(Box::new(transfer_visit(inner, side, state)?)),
494 Item::CaptureRef(id) => match side {
495 Side::From => node.clone(),
497 Side::To => {
498 if state.right.contains(id) {
499 node.clone()
500 } else {
501 state.right.insert(*id);
502 match state.left.get(id.wrapping_sub(1)) {
503 Some(group) => group.clone(),
504 None => {
505 return Err(Error::MapLogic(
506 "Capture count doesn't match".into(),
507 ))
508 }
509 }
510 }
511 }
512 },
513 Item::CaptureGroup { inner, .. } => {
514 state.left.push(node.clone());
515 let out = Item::CaptureRef(state.left.len());
516 transfer_visit(inner, side, state)?;
518 out
519 }
520 Item::Func(_) => {
521 return Err(Error::Unsupported(
522 "Type Item::Func unhandled in reverse_transfer_visit (the Ruby raises \
523 \"Type ... unhandled!\")"
524 .into(),
525 ))
526 }
527 })
528}