1use crate::document::{Document, StageItem, Sub, SubTo};
25use crate::error::{Error, Result};
26use crate::item::Item;
27use crate::stdlib::Prim;
28use std::cell::RefCell;
29use std::collections::{BTreeMap, BTreeSet};
30use std::path::PathBuf;
31use std::rc::Rc;
32
33pub struct Runtime {
36 load_path: Vec<PathBuf>,
37 cache: RefCell<BTreeMap<String, Rc<Document>>>,
38 loading: RefCell<Vec<String>>,
40}
41
42impl Runtime {
43 pub fn new(load_path: Vec<PathBuf>) -> Runtime {
44 Runtime {
45 load_path,
46 cache: RefCell::new(BTreeMap::new()),
47 loading: RefCell::new(Vec::new()),
48 }
49 }
50
51 pub fn load(&self, map_id: &str) -> Result<Rc<Document>> {
53 if let Some(doc) = self.cache.borrow().get(map_id) {
54 return Ok(doc.clone());
55 }
56 {
57 let loading = self.loading.borrow();
58 if loading.iter().any(|m| m == map_id) {
59 let mut chain = loading.clone();
60 chain.push(map_id.to_string());
61 return Err(Error::DependencyCycle(chain));
62 }
63 }
64 self.loading.borrow_mut().push(map_id.to_string());
65 let result = self.load_uncached(map_id);
66 self.loading.borrow_mut().pop();
67 result
68 }
69
70 fn load_uncached(&self, map_id: &str) -> Result<Rc<Document>> {
71 let path = crate::locate::locate(&self.load_path, map_id)?;
72 let source = crate::locate::read_map(&path)?;
73 let stem = path
74 .file_stem()
75 .and_then(|s| s.to_str())
76 .unwrap_or(map_id)
77 .to_string();
78 let tree = crate::grammar::parse_isc(&source).map_err(|(offset, expected)| Error::Parse {
79 map: stem.clone(),
80 offset,
81 expected,
82 })?;
83
84 let targets = crate::builder::dependency_targets(&tree);
86 let mut imports: Vec<Rc<Document>> = Vec::new();
87 let mut dep_docs: BTreeMap<String, Rc<Document>> = BTreeMap::new();
88 for (target, alias) in targets {
89 let dep = self.load(&target)?;
90 match alias {
91 Some(name) => {
92 dep_docs.insert(name, dep);
93 }
94 None => imports.push(dep),
95 }
96 }
97
98 let doc = Rc::new(crate::builder::build(&tree, &stem, &imports, &dep_docs)?);
99 self.cache
100 .borrow_mut()
101 .insert(map_id.to_string(), doc.clone());
102 Ok(doc)
103 }
104
105 pub fn transliterate(&self, map_id: &str, input: &str) -> Result<String> {
107 self.transliterate_stage(map_id, "main", input, None)
108 }
109
110 pub fn transliterate_lang(
114 &self,
115 map_id: &str,
116 input: &str,
117 language: Option<&str>,
118 ) -> Result<String> {
119 self.transliterate_stage(map_id, "main", input, language)
120 }
121
122 pub fn transliterate_stage(
123 &self,
124 map_id: &str,
125 stage: &str,
126 input: &str,
127 language: Option<&str>,
128 ) -> Result<String> {
129 let doc = self.load(map_id)?;
130 execute_stage(&doc, stage, input, language)
131 }
132
133 pub fn run_tests(&self, map_id: &str) -> Result<(usize, usize, Vec<String>)> {
136 let doc = self.load(map_id)?;
137 let mut passed = 0usize;
138 let mut failed = 0usize;
139 let mut failures = Vec::new();
140 for (input, expected) in &doc.tests.data {
141 match self.transliterate(map_id, input) {
142 Ok(actual) if &actual == expected => passed += 1,
143 Ok(actual) => {
144 failed += 1;
145 failures.push(format!("{input:?} -> {actual:?} (expected {expected:?})"));
146 }
147 Err(e) => {
148 failed += 1;
149 failures.push(format!("{input:?} -> ERROR {e}"));
150 }
151 }
152 }
153 Ok((passed, failed, failures))
154 }
155}
156
157pub fn execute_stage_pub(
163 doc: &Document,
164 stage_name: &str,
165 input: &str,
166 language: Option<&str>,
167) -> Result<String> {
168 execute_stage(doc, stage_name, input, language)
169}
170
171fn guard_filter<'a>(rules: &[&'a Sub], language: Option<&str>) -> Vec<&'a Sub> {
178 let applicable: Vec<&Sub> = rules
179 .iter()
180 .copied()
181 .filter(|r| r.guard_applies(language))
182 .collect();
183 let guarded_froms: BTreeSet<String> = applicable
184 .iter()
185 .filter(|r| !r.when.is_empty())
186 .map(|r| r.from_key())
187 .collect();
188 if guarded_froms.is_empty() {
189 return applicable;
190 }
191 applicable
194 .into_iter()
195 .filter(|r| !r.when.is_empty() || !guarded_froms.contains(&r.from_key()))
196 .collect()
197}
198
199fn execute_stage(
200 doc: &Document,
201 stage_name: &str,
202 input: &str,
203 language: Option<&str>,
204) -> Result<String> {
205 let stage = doc
206 .imported_stages()
207 .remove(stage_name)
208 .ok_or_else(|| Error::MapLogic(format!("stage {stage_name:?} not found in {}", doc.name)))?;
209 if stage.reverse_run == Some(true) {
212 return Ok(input.to_string());
213 }
214 let mut s = input.to_string();
215 for item in &stage.items {
216 let skipped = match item {
218 StageItem::Funcall(f) => f.reverse_run == Some(true),
219 StageItem::Run { reverse_run, .. } => *reverse_run == Some(true),
220 _ => false,
221 };
222 if skipped {
223 continue;
224 }
225 s = match item {
226 StageItem::Parallel(rules) => apply_parallel(doc, rules, &s, language)?,
227 StageItem::Sequential(rules) => {
228 let mut out = s;
229 for rule in rules {
230 if rule.reverse_run == Some(true) {
231 continue;
232 }
233 if !rule.guard_applies(language) {
236 continue;
237 }
238 out = apply_sub(doc, rule, &out)?;
239 }
240 out
241 }
242 StageItem::Run { dep, stage, .. } => match dep {
243 Some(alias) => {
244 let dep_doc = doc.dep_aliases.get(alias).ok_or_else(|| {
245 Error::MapLogic(format!("dep alias {alias:?} not found in {}", doc.name))
246 })?;
247 execute_stage(dep_doc, stage, &s, language)?
251 }
252 None => execute_stage(doc, stage, &s, language)?,
253 },
254 StageItem::Funcall(funcall) => funcall.name.apply(s, &funcall.kwargs)?,
255 };
256 }
257 Ok(s)
258}
259
260fn apply_parallel(doc: &Document, rules: &[Sub], s: &str, language: Option<&str>) -> Result<String> {
270 let live: Vec<&Sub> = rules
273 .iter()
274 .filter(|r| r.reverse_run != Some(true))
275 .collect();
276 let applicable = guard_filter(&live, language);
279 if let Some(tree) = build_tree(&applicable, doc)? {
280 return tree_replace(&tree, s);
281 }
282
283 let chars: Vec<char> = s.chars().collect();
284 let mut order: Vec<usize> = (0..applicable.len()).collect();
286 order.sort_by_key(|&i| (std::cmp::Reverse(applicable[i].max_length()), i));
287
288 let mut out = String::with_capacity(s.len());
289 let mut copied = 0usize; let mut pos = 0usize;
291 while pos < chars.len() {
292 let mut matched: Option<(usize, String)> = None;
293 for &i in &order {
294 let rule = applicable[i];
295 let m = Matcher::new(&chars, doc);
296 if let Some((end, replacement)) = m.match_rule(rule, pos)? {
297 matched = Some((end, replacement));
298 break;
299 }
300 }
301 match matched {
302 Some((end, replacement)) => {
303 out.extend(&chars[copied..pos]);
304 out.push_str(&replacement);
305 copied = end;
306 pos = std::cmp::max(end, pos + 1);
309 }
310 None => pos += 1,
311 }
312 }
313 out.extend(&chars[copied..]);
314 Ok(out)
315}
316
317type Trie = std::collections::BTreeMap<char, TrieNode>;
319#[derive(Default)]
320struct TrieNode {
321 children: Trie,
322 replacement: Option<String>,
323}
324
325fn build_tree(rules: &[&Sub], doc: &Document) -> Result<Option<TrieNode>> {
331 let m = Matcher::new(&[], doc);
332 let mut map: std::collections::BTreeMap<String, String> = std::collections::BTreeMap::new();
333 for rule in rules {
334 if rule.before.is_some()
335 || rule.not_before.is_some()
336 || rule.after.is_some()
337 || rule.not_after.is_some()
338 {
339 return Ok(None);
340 }
341 let to = match &rule.to {
342 SubTo::Item(item) => match m.build_par_str(item) {
343 Ok(s) => s,
344 Err(_) => return Ok(None),
345 },
346 SubTo::Upcase | SubTo::Downcase => String::new(),
351 };
352 let _ = to;
353 match m.build_par_from(&rule.from) {
354 Ok(keys) => {
355 for key in keys {
356 map.insert(key, to.clone());
357 }
358 }
359 Err(_) => return Ok(None),
360 }
361 }
362 let mut root = TrieNode::default();
363 for (from, to) in map {
364 let chars: Vec<char> = from.chars().collect();
365 if chars.is_empty() {
366 continue;
369 }
370 let mut node = &mut root;
371 for c in chars {
372 node = node.children.entry(c).or_default();
373 }
374 node.replacement = Some(to);
375 }
376 Ok(Some(root))
377}
378
379fn tree_replace(root: &TrieNode, s: &str) -> Result<String> {
381 let chars: Vec<char> = s.chars().collect();
382 let len = chars.len();
383 let mut out = String::with_capacity(s.len());
384 let mut i = 0usize;
385 while i < len {
386 let mut branch = &root.children;
387 let mut matched: Option<(usize, &String)> = None;
388 let mut j = 0usize;
389 while j < len - i {
390 let cc = chars[i + j];
391 match branch.get(&cc) {
392 Some(node) => {
393 branch = &node.children;
394 if let Some(repl) = &node.replacement {
395 matched = Some((j + 1, repl));
396 }
397 j += 1;
398 }
399 None => break,
400 }
401 }
402 match matched {
403 Some((mlen, repl)) => {
404 out.push_str(repl);
405 i += mlen;
406 }
407 None => {
408 out.push(chars[i]);
409 i += 1;
410 }
411 }
412 }
413 Ok(out)
414}
415
416fn apply_sub(doc: &Document, rule: &Sub, s: &str) -> Result<String> {
418 let chars: Vec<char> = s.chars().collect();
419 let mut out = String::with_capacity(s.len());
420 let mut copied = 0usize; let mut pos = 0usize;
422 while pos <= chars.len() {
425 let m = Matcher::new(&chars, doc);
426 match m.match_rule(rule, pos)? {
427 Some((end, replacement)) => {
428 out.extend(&chars[copied..pos]);
429 out.push_str(&replacement);
430 copied = end;
431 pos = std::cmp::max(end, pos + 1);
432 }
433 None => pos += 1,
434 }
435 }
436 out.extend(&chars[copied..]);
437 Ok(out)
438}
439
440struct Matcher<'a> {
447 chars: &'a [char],
448 doc: &'a Document,
449 groups: RefCell<Vec<Option<(usize, usize)>>>,
450 error: RefCell<Option<Error>>,
451}
452
453impl<'a> Matcher<'a> {
454 fn new(chars: &'a [char], doc: &'a Document) -> Matcher<'a> {
455 Matcher {
456 chars,
457 doc,
458 groups: RefCell::new(Vec::new()),
459 error: RefCell::new(None),
460 }
461 }
462
463 fn match_rule(&self, rule: &Sub, pos: usize) -> Result<Option<(usize, String)>> {
466 let slots = rule.group_count + 1;
467 self.groups.borrow_mut().resize_with(slots, || None);
468
469 if let Some(before) = &rule.before {
471 if !self.lookbehind(before, pos)? {
472 return Ok(None);
473 }
474 }
475 if let Some(nb) = &rule.not_before {
479 if self.lookbehind(nb, pos)? {
480 return Ok(None);
481 }
482 }
483
484 let mut end_slot: Option<usize> = None;
485 let matched = {
486 let slot_ref = &mut end_slot;
487 self.match_item(&rule.from, pos, &mut |m, end| {
488 if let Some(na) = &rule.not_after {
490 if m.lookahead(na, end)? {
491 return Ok(false);
492 }
493 }
494 if let Some(a) = &rule.after {
496 if !m.lookahead(a, end)? {
497 return Ok(false);
498 }
499 }
500 *slot_ref = Some(end);
501 Ok(true)
502 })
503 };
504
505 if let Some(e) = self.error.borrow_mut().take() {
506 return Err(e);
507 }
508 if !matched {
509 return Ok(None);
510 }
511 let end = end_slot.unwrap_or(pos);
512
513 let replacement = match &rule.to {
514 SubTo::Upcase => self.chars[pos..end].iter().collect::<String>().to_uppercase(),
515 SubTo::Downcase => self.chars[pos..end].iter().collect::<String>().to_lowercase(),
516 SubTo::Item(to) => {
517 let collapsed = to.first_string();
518 let built = self.build_replacement(&collapsed)?;
519 self.decode_replacement(&built, pos, end)
520 }
521 };
522 Ok(Some((end, replacement)))
523 }
524
525 fn lookbehind(&self, item: &Item, pos: usize) -> Result<bool> {
527 for start in (0..=pos).rev() {
528 let ok = self.match_item(item, start, &mut |_m, end| Ok(end == pos));
529 if let Some(e) = self.error.borrow_mut().take() {
530 return Err(e);
531 }
532 if ok {
533 return Ok(true);
534 }
535 }
536 Ok(false)
537 }
538
539 fn lookahead(&self, item: &Item, pos: usize) -> Result<bool> {
541 let ok = self.match_item(item, pos, &mut |_m, _end| Ok(true));
542 if let Some(e) = self.error.borrow_mut().take() {
543 return Err(e);
544 }
545 Ok(ok)
546 }
547
548 fn match_item(
551 &self,
552 item: &Item,
553 pos: usize,
554 k: &mut dyn FnMut(&Matcher, usize) -> Result<bool>,
555 ) -> bool {
556 match item {
557 Item::None => self.call(k, pos),
558 Item::Str(s) => {
559 let pat: Vec<char> = s.chars().collect();
560 if pos + pat.len() <= self.chars.len()
561 && self.chars[pos..pos + pat.len()] == pat[..]
562 {
563 self.call(k, pos + pat.len())
564 } else {
565 false
566 }
567 }
568 Item::Prim(prim) => match prim.matches_zero_width(self.chars, pos) {
569 Some(ok) => ok && self.call(k, pos),
570 None => {
571 if pos < self.chars.len() && prim.matches_char(self.chars[pos]).unwrap_or(false)
572 {
573 self.call(k, pos + 1)
574 } else {
575 false
576 }
577 }
578 },
579 Item::Concat(parts) => self.match_seq(parts, pos, k),
580 Item::Set(entries) => {
581 for entry in entries {
582 if self.match_item(entry, pos, k) {
583 return true;
584 }
585 }
586 false
587 }
588 Item::Range(lo, hi) => {
589 if pos < self.chars.len() && *lo <= self.chars[pos] && self.chars[pos] <= *hi {
590 self.call(k, pos + 1)
591 } else {
592 false
593 }
594 }
595 Item::Maybe(inner) => {
596 let extended = self.match_item(inner, pos, k);
597 if extended {
598 return true;
599 }
600 self.call(k, pos)
601 }
602 Item::Some(inner) => self.match_some(inner, pos, 1, k),
603 Item::CaptureGroup { id, inner } => {
604 let snapshot = self.groups.borrow().clone();
605 let id = *id;
606 let ok = self.match_item(inner, pos, &mut |m, end| {
607 m.groups.borrow_mut()[id] = Some((pos, end));
608 match k(m, end) {
609 Ok(true) => Ok(true),
610 failed => {
611 *m.groups.borrow_mut() = snapshot.clone();
612 failed
613 }
614 }
615 });
616 if !ok {
617 *self.groups.borrow_mut() = snapshot;
618 }
619 ok
620 }
621 Item::CaptureRef(n) => {
622 let text = self
623 .groups
624 .borrow()
625 .get(*n)
626 .and_then(|g| g.as_ref())
627 .map(|(s, e)| self.chars[*s..*e].iter().collect::<String>());
628 match text {
629 Some(t) => {
630 let pat: Vec<char> = t.chars().collect();
631 if pos + pat.len() <= self.chars.len()
632 && self.chars[pos..pos + pat.len()] == pat[..]
633 {
634 self.call(k, pos + pat.len())
635 } else {
636 false
637 }
638 }
639 None => false,
641 }
642 }
643 Item::Alias { map, name } => match self.resolve_alias(map.as_deref(), name) {
644 Ok(Some(resolved)) => self.match_item(&resolved, pos, k),
645 Ok(None) => false,
646 Err(e) => {
647 self.record_error(e);
648 false
649 }
650 },
651 Item::Func(_) => {
652 self.record_error(Error::MapLogic("function item in match position".into()));
653 false
654 }
655 }
656 }
657
658 fn match_seq(
659 &self,
660 parts: &[Item],
661 pos: usize,
662 k: &mut dyn FnMut(&Matcher, usize) -> Result<bool>,
663 ) -> bool {
664 match parts.split_first() {
665 None => self.call(k, pos),
666 Some((first, rest)) => {
667 let rest: Vec<Item> = rest.to_vec();
668 self.match_item(first, pos, &mut |m, end| Ok(m.match_seq(&rest, end, k)))
669 }
670 }
671 }
672
673 fn match_some(
674 &self,
675 inner: &Item,
676 pos: usize,
677 count: usize,
678 k: &mut dyn FnMut(&Matcher, usize) -> Result<bool>,
679 ) -> bool {
680 let extended = self.match_item(inner, pos, &mut |m, end| {
684 if end > pos {
685 Ok(m.match_some(inner, end, count + 1, k))
686 } else {
687 Ok(false)
688 }
689 });
690 if extended {
691 return true;
692 }
693 if count >= 1 {
694 self.call(k, pos)
695 } else {
696 false
697 }
698 }
699
700 fn call(&self, k: &mut dyn FnMut(&Matcher, usize) -> Result<bool>, pos: usize) -> bool {
701 match k(self, pos) {
702 Ok(true) => true,
703 Ok(false) => false,
704 Err(e) => {
705 self.record_error(e);
706 false
707 }
708 }
709 }
710
711 fn record_error(&self, e: Error) {
712 let mut slot = self.error.borrow_mut();
713 if slot.is_none() {
714 *slot = Some(e);
715 }
716 }
717
718 fn resolve_alias(&self, map: Option<&str>, name: &str) -> Result<Option<Item>> {
719 if map.is_none() {
722 if let Some(prim) = Prim::from_name(name) {
723 return Ok(Some(Item::Prim(prim)));
724 }
725 }
726 match map {
727 Some(alias) => {
728 let doc = self
729 .doc
730 .dep_aliases
731 .get(alias)
732 .ok_or_else(|| Error::MapLogic(format!("dep alias {alias:?} not found")))?;
733 doc.lookup_alias(name).map(Some).ok_or_else(|| {
734 Error::MapLogic(format!("alias {name:?} of dep {alias:?} not found"))
735 })
736 }
737 None => self.doc.lookup_alias(name).map(Some).ok_or_else(|| {
738 Error::MapLogic(format!("alias {name:?} not found in {}", self.doc.name))
739 }),
740 }
741 }
742
743 fn build_replacement(&self, item: &Item) -> Result<String> {
745 match item {
746 Item::Str(s) => Ok(s.clone()),
747 Item::None => Ok(String::new()),
748 Item::Concat(parts) => {
749 let mut out = String::new();
750 for p in parts {
751 out.push_str(&self.build_replacement(p)?);
752 }
753 Ok(out)
754 }
755 Item::CaptureRef(n) => Ok(format!("\\{n}")),
756 Item::Maybe(inner) => Ok(format!("{}?", self.build_replacement(inner)?)),
759 Item::Some(inner) => Ok(format!("{}+", self.build_replacement(inner)?)),
760 Item::CaptureGroup { inner, .. } => {
761 Ok(format!("({})", self.build_replacement(inner)?))
762 }
763 Item::Prim(prim) => match prim {
764 Prim::None_ => Ok(String::new()),
765 Prim::Space => Ok(" ".to_string()),
766 other => Err(Error::MapLogic(format!(
767 "can't use {} in a string context",
768 other.name()
769 ))),
770 },
771 Item::Alias { map, name } => {
772 if map.is_none() {
773 if let Some(prim) = Prim::from_name(name) {
774 return self.build_replacement(&Item::Prim(prim));
775 }
776 }
777 match self.resolve_alias(map.as_deref(), name)? {
778 Some(resolved) => self.build_replacement(&resolved),
779 None => Err(Error::MapLogic(format!("alias {name:?} not found"))),
780 }
781 }
782 Item::Set(_) | Item::Range(_, _) => {
786 Err(Error::MapLogic("can't use Any in a string context".into()))
787 }
788 Item::Func(_) => Err(Error::MapLogic(
789 "function item in replacement position".into(),
790 )),
791 }
792 }
793
794 fn decode_replacement(&self, built: &str, start: usize, end: usize) -> String { let chars: Vec<char> = built.chars().collect();
798 let mut out = String::with_capacity(built.len());
799 let mut i = 0usize;
800 while i < chars.len() {
801 if chars[i] == '\\' && i + 1 < chars.len() {
802 let next = chars[i + 1];
803 match next {
804 '0' | '1'..='9' => {
805 let n = next.to_digit(10).unwrap() as usize;
806 if n == 0 {
807 out.extend(&self.chars[start..end]);
808 } else if let Some(Some((gs, ge))) = self.groups.borrow().get(n) {
809 out.extend(&self.chars[*gs..*ge]);
810 }
811 i += 2;
812 continue;
813 }
814 '\\' => {
815 out.push('\\');
816 i += 2;
817 continue;
818 }
819 '&' => {
820 out.extend(&self.chars[start..end]);
821 i += 2;
822 continue;
823 }
824 _ => {
825 out.push(next);
827 i += 2;
828 continue;
829 }
830 }
831 }
832 out.push(chars[i]);
833 i += 1;
834 }
835 out
836 }
837}
838
839impl<'a> Matcher<'a> {
840 fn build_par_from(&self, item: &Item) -> Result<Vec<String>> {
850 self.build_par_from_depth(item, 0)
851 }
852
853 fn build_par_from_depth(&self, item: &Item, depth: usize) -> Result<Vec<String>> {
854 if depth > 64 {
855 return Err(Error::MapLogic("alias expansion too deep".into()));
856 }
857 match item {
858 Item::Str(s) => Ok(vec![s.clone()]),
859 Item::None => Ok(vec![String::new()]),
860 Item::Prim(prim) => match prim {
861 Prim::None_ => Ok(vec![String::new()]),
862 Prim::Space => Ok(vec![" ".to_string()]),
863 other => Err(Error::MapLogic(format!(
864 "can't use {} in a par context",
865 other.name()
866 ))),
867 },
868 Item::Range(lo, hi) => {
872 let mut keys = Vec::new();
873 let mut c = *lo;
874 loop {
875 keys.push(c.to_string());
876 if c == *hi {
877 break;
878 }
879 match char::from_u32(c as u32 + 1) {
880 Some(n) => c = n,
881 None => break,
882 }
883 }
884 Ok(keys)
885 }
886 Item::Set(entries) => {
887 let mut keys = Vec::new();
888 for entry in entries {
889 match entry {
890 Item::Str(s) => keys.push(s.clone()),
891 _ => {
894 return Err(Error::MapLogic(
895 "can't parallelize nested any() entries".into(),
896 ))
897 }
898 }
899 }
900 Ok(keys)
901 }
902 Item::Concat(parts) => {
904 let mut acc: Vec<String> = vec![String::new()];
905 for part in parts {
906 let next = self.build_par_from_depth(part, depth)?;
907 let mut joined = Vec::new();
908 for a in &acc {
909 for b in &next {
910 joined.push(format!("{a}{b}"));
911 }
912 }
913 acc = joined;
914 }
915 Ok(acc)
916 }
917 Item::Alias { map, name } => {
918 if map.is_none() {
919 if let Some(prim) = Prim::from_name(name) {
920 return self.build_par_from_depth(&Item::Prim(prim), depth);
921 }
922 }
923 match self.resolve_alias(map.as_deref(), name)? {
924 Some(resolved) => self.build_par_from_depth(&resolved, depth + 1),
925 None => Ok(vec![]),
926 }
927 }
928 Item::Maybe(_) | Item::Some(_) => Err(Error::MapLogic(
930 "can't use a MaybeSome in a par context".into(),
931 )),
932 Item::CaptureGroup { .. } => Err(Error::MapLogic(
933 "can't use a CaptureGroup in a par context".into(),
934 )),
935 Item::CaptureRef(_) => Err(Error::MapLogic(
936 "Can't use CaptureRef in parallel mode".into(),
937 )),
938 Item::Func(_) => Ok(vec![]),
940 }
941 }
942
943 fn build_par_str(&self, item: &Item) -> Result<String> {
946 let collapsed = item.first_string();
947 self.build_par_str_inner(&collapsed, 0)
948 }
949
950 fn build_par_str_inner(&self, item: &Item, depth: usize) -> Result<String> {
951 if depth > 64 {
952 return Err(Error::MapLogic("alias expansion too deep".into()));
953 }
954 match item {
955 Item::Str(s) => Ok(s.clone()),
956 Item::None => Ok(String::new()),
957 Item::Prim(prim) => match prim {
958 Prim::None_ => Ok(String::new()),
959 Prim::Space => Ok(" ".to_string()),
960 other => Err(Error::MapLogic(format!(
961 "can't use {} in a par context",
962 other.name()
963 ))),
964 },
965 Item::Concat(parts) => {
966 let mut out = String::new();
967 for p in parts {
968 out.push_str(&self.build_par_str_inner(p, depth)?);
969 }
970 Ok(out)
971 }
972 Item::Alias { map, name } => {
973 if map.is_none() {
974 if let Some(prim) = Prim::from_name(name) {
975 return self.build_par_str_inner(&Item::Prim(prim), depth);
976 }
977 }
978 match self.resolve_alias(map.as_deref(), name)? {
979 Some(resolved) => self.build_par_str_inner(&resolved, depth + 1),
980 None => Err(Error::MapLogic(format!("alias {name:?} not found"))),
981 }
982 }
983 Item::Set(_) | Item::Range(_, _) => Err(Error::MapLogic(
986 "parallel replacement is not a single string".into(),
987 )),
988 Item::Maybe(_) | Item::Some(_) => Err(Error::MapLogic(
989 "can't use a MaybeSome in a par context".into(),
990 )),
991 Item::CaptureGroup { .. } => Err(Error::MapLogic(
992 "can't use a CaptureGroup in a par context".into(),
993 )),
994 Item::CaptureRef(_) => Err(Error::MapLogic(
995 "Can't use CaptureRef in parallel mode".into(),
996 )),
997 Item::Func(_) => Err(Error::MapLogic(
998 "function item in a par context".into(),
999 )),
1000 }
1001 }
1002}