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 language = language.filter(|l| doc.languages.iter().any(|d| d == l));
210 let stage = doc
211 .imported_stages()
212 .remove(stage_name)
213 .ok_or_else(|| Error::MapLogic(format!("stage {stage_name:?} not found in {}", doc.name)))?;
214 if stage.reverse_run == Some(true) {
217 return Ok(input.to_string());
218 }
219 let mut s = input.to_string();
220 for item in &stage.items {
221 let skipped = match item {
223 StageItem::Funcall(f) => f.reverse_run == Some(true),
224 StageItem::Run { reverse_run, .. } => *reverse_run == Some(true),
225 _ => false,
226 };
227 if skipped {
228 continue;
229 }
230 s = match item {
231 StageItem::Parallel(rules) => apply_parallel(doc, rules, &s, language)?,
232 StageItem::Sequential(rules) => {
233 let mut out = s;
234 for rule in rules {
235 if rule.reverse_run == Some(true) {
236 continue;
237 }
238 if !rule.guard_applies(language) {
241 continue;
242 }
243 out = apply_sub(doc, rule, &out)?;
244 }
245 out
246 }
247 StageItem::Run { dep, stage, .. } => match dep {
248 Some(alias) => {
249 let dep_doc = doc.dep_aliases.get(alias).ok_or_else(|| {
250 Error::MapLogic(format!("dep alias {alias:?} not found in {}", doc.name))
251 })?;
252 execute_stage(dep_doc, stage, &s, language)?
256 }
257 None => execute_stage(doc, stage, &s, language)?,
258 },
259 StageItem::Funcall(funcall) => funcall.name.apply(s, &funcall.kwargs)?,
260 };
261 }
262 Ok(s)
263}
264
265fn apply_parallel(doc: &Document, rules: &[Sub], s: &str, language: Option<&str>) -> Result<String> {
275 let live: Vec<&Sub> = rules
278 .iter()
279 .filter(|r| r.reverse_run != Some(true))
280 .collect();
281 let applicable = guard_filter(&live, language);
284 if let Some(tree) = build_tree(&applicable, doc)? {
285 return tree_replace(&tree, s);
286 }
287
288 let chars: Vec<char> = s.chars().collect();
289 let mut order: Vec<usize> = (0..applicable.len()).collect();
291 order.sort_by_key(|&i| (std::cmp::Reverse(applicable[i].max_length()), i));
292
293 let mut out = String::with_capacity(s.len());
294 let mut copied = 0usize; let mut pos = 0usize;
296 while pos < chars.len() {
297 let mut matched: Option<(usize, String)> = None;
298 for &i in &order {
299 let rule = applicable[i];
300 let m = Matcher::new(&chars, doc);
301 if let Some((end, replacement)) = m.match_rule(rule, pos)? {
302 matched = Some((end, replacement));
303 break;
304 }
305 }
306 match matched {
307 Some((end, replacement)) => {
308 out.extend(&chars[copied..pos]);
309 out.push_str(&replacement);
310 copied = end;
311 pos = std::cmp::max(end, pos + 1);
314 }
315 None => pos += 1,
316 }
317 }
318 out.extend(&chars[copied..]);
319 Ok(out)
320}
321
322type Trie = std::collections::BTreeMap<char, TrieNode>;
324#[derive(Default)]
325struct TrieNode {
326 children: Trie,
327 replacement: Option<String>,
328}
329
330fn build_tree(rules: &[&Sub], doc: &Document) -> Result<Option<TrieNode>> {
336 let m = Matcher::new(&[], doc);
337 let mut map: std::collections::BTreeMap<String, String> = std::collections::BTreeMap::new();
338 for rule in rules {
339 if rule.before.is_some()
340 || rule.not_before.is_some()
341 || rule.after.is_some()
342 || rule.not_after.is_some()
343 {
344 return Ok(None);
345 }
346 let to = match &rule.to {
347 SubTo::Item(item) => match m.build_par_str(item) {
348 Ok(s) => s,
349 Err(_) => return Ok(None),
350 },
351 SubTo::Upcase | SubTo::Downcase => String::new(),
356 };
357 let _ = to;
358 match m.build_par_from(&rule.from) {
359 Ok(keys) => {
360 for key in keys {
361 map.insert(key, to.clone());
362 }
363 }
364 Err(_) => return Ok(None),
365 }
366 }
367 let mut root = TrieNode::default();
368 for (from, to) in map {
369 let chars: Vec<char> = from.chars().collect();
370 if chars.is_empty() {
371 continue;
374 }
375 let mut node = &mut root;
376 for c in chars {
377 node = node.children.entry(c).or_default();
378 }
379 node.replacement = Some(to);
380 }
381 Ok(Some(root))
382}
383
384fn tree_replace(root: &TrieNode, s: &str) -> Result<String> {
386 let chars: Vec<char> = s.chars().collect();
387 let len = chars.len();
388 let mut out = String::with_capacity(s.len());
389 let mut i = 0usize;
390 while i < len {
391 let mut branch = &root.children;
392 let mut matched: Option<(usize, &String)> = None;
393 let mut j = 0usize;
394 while j < len - i {
395 let cc = chars[i + j];
396 match branch.get(&cc) {
397 Some(node) => {
398 branch = &node.children;
399 if let Some(repl) = &node.replacement {
400 matched = Some((j + 1, repl));
401 }
402 j += 1;
403 }
404 None => break,
405 }
406 }
407 match matched {
408 Some((mlen, repl)) => {
409 out.push_str(repl);
410 i += mlen;
411 }
412 None => {
413 out.push(chars[i]);
414 i += 1;
415 }
416 }
417 }
418 Ok(out)
419}
420
421fn apply_sub(doc: &Document, rule: &Sub, s: &str) -> Result<String> {
423 let chars: Vec<char> = s.chars().collect();
424 let mut out = String::with_capacity(s.len());
425 let mut copied = 0usize; let mut pos = 0usize;
427 while pos <= chars.len() {
430 let m = Matcher::new(&chars, doc);
431 match m.match_rule(rule, pos)? {
432 Some((end, replacement)) => {
433 out.extend(&chars[copied..pos]);
434 out.push_str(&replacement);
435 copied = end;
436 pos = std::cmp::max(end, pos + 1);
437 }
438 None => pos += 1,
439 }
440 }
441 out.extend(&chars[copied..]);
442 Ok(out)
443}
444
445struct Matcher<'a> {
452 chars: &'a [char],
453 doc: &'a Document,
454 groups: RefCell<Vec<Option<(usize, usize)>>>,
455 error: RefCell<Option<Error>>,
456}
457
458impl<'a> Matcher<'a> {
459 fn new(chars: &'a [char], doc: &'a Document) -> Matcher<'a> {
460 Matcher {
461 chars,
462 doc,
463 groups: RefCell::new(Vec::new()),
464 error: RefCell::new(None),
465 }
466 }
467
468 fn match_rule(&self, rule: &Sub, pos: usize) -> Result<Option<(usize, String)>> {
471 let slots = rule.group_count + 1;
472 self.groups.borrow_mut().resize_with(slots, || None);
473
474 if let Some(before) = &rule.before {
476 if !self.lookbehind(before, pos)? {
477 return Ok(None);
478 }
479 }
480 if let Some(nb) = &rule.not_before {
484 if self.lookbehind(nb, pos)? {
485 return Ok(None);
486 }
487 }
488
489 let mut end_slot: Option<usize> = None;
490 let matched = {
491 let slot_ref = &mut end_slot;
492 self.match_item(&rule.from, pos, &mut |m, end| {
493 if let Some(na) = &rule.not_after {
495 if m.lookahead(na, end)? {
496 return Ok(false);
497 }
498 }
499 if let Some(a) = &rule.after {
501 if !m.lookahead(a, end)? {
502 return Ok(false);
503 }
504 }
505 *slot_ref = Some(end);
506 Ok(true)
507 })
508 };
509
510 if let Some(e) = self.error.borrow_mut().take() {
511 return Err(e);
512 }
513 if !matched {
514 return Ok(None);
515 }
516 let end = end_slot.unwrap_or(pos);
517
518 let replacement = match &rule.to {
519 SubTo::Upcase => self.chars[pos..end].iter().collect::<String>().to_uppercase(),
520 SubTo::Downcase => self.chars[pos..end].iter().collect::<String>().to_lowercase(),
521 SubTo::Item(to) => {
522 let collapsed = to.first_string();
523 let built = self.build_replacement(&collapsed)?;
524 self.decode_replacement(&built, pos, end)
525 }
526 };
527 Ok(Some((end, replacement)))
528 }
529
530 fn lookbehind(&self, item: &Item, pos: usize) -> Result<bool> {
532 for start in (0..=pos).rev() {
533 let ok = self.match_item(item, start, &mut |_m, end| Ok(end == pos));
534 if let Some(e) = self.error.borrow_mut().take() {
535 return Err(e);
536 }
537 if ok {
538 return Ok(true);
539 }
540 }
541 Ok(false)
542 }
543
544 fn lookahead(&self, item: &Item, pos: usize) -> Result<bool> {
546 let ok = self.match_item(item, pos, &mut |_m, _end| Ok(true));
547 if let Some(e) = self.error.borrow_mut().take() {
548 return Err(e);
549 }
550 Ok(ok)
551 }
552
553 fn match_item(
556 &self,
557 item: &Item,
558 pos: usize,
559 k: &mut dyn FnMut(&Matcher, usize) -> Result<bool>,
560 ) -> bool {
561 match item {
562 Item::None => self.call(k, pos),
563 Item::Str(s) => {
564 let pat: Vec<char> = s.chars().collect();
565 if pos + pat.len() <= self.chars.len()
566 && self.chars[pos..pos + pat.len()] == pat[..]
567 {
568 self.call(k, pos + pat.len())
569 } else {
570 false
571 }
572 }
573 Item::Prim(prim) => match prim.matches_zero_width(self.chars, pos) {
574 Some(ok) => ok && self.call(k, pos),
575 None => {
576 if pos < self.chars.len() && prim.matches_char(self.chars[pos]).unwrap_or(false)
577 {
578 self.call(k, pos + 1)
579 } else {
580 false
581 }
582 }
583 },
584 Item::Concat(parts) => self.match_seq(parts, pos, k),
585 Item::Set(entries) => {
586 for entry in entries {
587 if self.match_item(entry, pos, k) {
588 return true;
589 }
590 }
591 false
592 }
593 Item::Range(lo, hi) => {
594 if pos < self.chars.len() && *lo <= self.chars[pos] && self.chars[pos] <= *hi {
595 self.call(k, pos + 1)
596 } else {
597 false
598 }
599 }
600 Item::Maybe(inner) => {
601 let extended = self.match_item(inner, pos, k);
602 if extended {
603 return true;
604 }
605 self.call(k, pos)
606 }
607 Item::Some(inner) => self.match_some(inner, pos, 1, k),
608 Item::CaptureGroup { id, inner } => {
609 let snapshot = self.groups.borrow().clone();
610 let id = *id;
611 let ok = self.match_item(inner, pos, &mut |m, end| {
612 m.groups.borrow_mut()[id] = Some((pos, end));
613 match k(m, end) {
614 Ok(true) => Ok(true),
615 failed => {
616 *m.groups.borrow_mut() = snapshot.clone();
617 failed
618 }
619 }
620 });
621 if !ok {
622 *self.groups.borrow_mut() = snapshot;
623 }
624 ok
625 }
626 Item::CaptureRef(n) => {
627 let text = self
628 .groups
629 .borrow()
630 .get(*n)
631 .and_then(|g| g.as_ref())
632 .map(|(s, e)| self.chars[*s..*e].iter().collect::<String>());
633 match text {
634 Some(t) => {
635 let pat: Vec<char> = t.chars().collect();
636 if pos + pat.len() <= self.chars.len()
637 && self.chars[pos..pos + pat.len()] == pat[..]
638 {
639 self.call(k, pos + pat.len())
640 } else {
641 false
642 }
643 }
644 None => false,
646 }
647 }
648 Item::Alias { map, name } => match self.resolve_alias(map.as_deref(), name) {
649 Ok(Some(resolved)) => self.match_item(&resolved, pos, k),
650 Ok(None) => false,
651 Err(e) => {
652 self.record_error(e);
653 false
654 }
655 },
656 Item::Func(_) => {
657 self.record_error(Error::MapLogic("function item in match position".into()));
658 false
659 }
660 }
661 }
662
663 fn match_seq(
664 &self,
665 parts: &[Item],
666 pos: usize,
667 k: &mut dyn FnMut(&Matcher, usize) -> Result<bool>,
668 ) -> bool {
669 match parts.split_first() {
670 None => self.call(k, pos),
671 Some((first, rest)) => {
672 let rest: Vec<Item> = rest.to_vec();
673 self.match_item(first, pos, &mut |m, end| Ok(m.match_seq(&rest, end, k)))
674 }
675 }
676 }
677
678 fn match_some(
679 &self,
680 inner: &Item,
681 pos: usize,
682 count: usize,
683 k: &mut dyn FnMut(&Matcher, usize) -> Result<bool>,
684 ) -> bool {
685 let extended = self.match_item(inner, pos, &mut |m, end| {
689 if end > pos {
690 Ok(m.match_some(inner, end, count + 1, k))
691 } else {
692 Ok(false)
693 }
694 });
695 if extended {
696 return true;
697 }
698 if count >= 1 {
699 self.call(k, pos)
700 } else {
701 false
702 }
703 }
704
705 fn call(&self, k: &mut dyn FnMut(&Matcher, usize) -> Result<bool>, pos: usize) -> bool {
706 match k(self, pos) {
707 Ok(true) => true,
708 Ok(false) => false,
709 Err(e) => {
710 self.record_error(e);
711 false
712 }
713 }
714 }
715
716 fn record_error(&self, e: Error) {
717 let mut slot = self.error.borrow_mut();
718 if slot.is_none() {
719 *slot = Some(e);
720 }
721 }
722
723 fn resolve_alias(&self, map: Option<&str>, name: &str) -> Result<Option<Item>> {
724 if map.is_none() {
727 if let Some(prim) = Prim::from_name(name) {
728 return Ok(Some(Item::Prim(prim)));
729 }
730 }
731 match map {
732 Some(alias) => {
733 let doc = self
734 .doc
735 .dep_aliases
736 .get(alias)
737 .ok_or_else(|| Error::MapLogic(format!("dep alias {alias:?} not found")))?;
738 doc.lookup_alias(name).map(Some).ok_or_else(|| {
739 Error::MapLogic(format!("alias {name:?} of dep {alias:?} not found"))
740 })
741 }
742 None => self.doc.lookup_alias(name).map(Some).ok_or_else(|| {
743 Error::MapLogic(format!("alias {name:?} not found in {}", self.doc.name))
744 }),
745 }
746 }
747
748 fn build_replacement(&self, item: &Item) -> Result<String> {
750 match item {
751 Item::Str(s) => Ok(s.clone()),
752 Item::None => Ok(String::new()),
753 Item::Concat(parts) => {
754 let mut out = String::new();
755 for p in parts {
756 out.push_str(&self.build_replacement(p)?);
757 }
758 Ok(out)
759 }
760 Item::CaptureRef(n) => Ok(format!("\\{n}")),
761 Item::Maybe(inner) => Ok(format!("{}?", self.build_replacement(inner)?)),
764 Item::Some(inner) => Ok(format!("{}+", self.build_replacement(inner)?)),
765 Item::CaptureGroup { inner, .. } => {
766 Ok(format!("({})", self.build_replacement(inner)?))
767 }
768 Item::Prim(prim) => match prim {
769 Prim::None_ => Ok(String::new()),
770 Prim::Space => Ok(" ".to_string()),
771 other => Err(Error::MapLogic(format!(
772 "can't use {} in a string context",
773 other.name()
774 ))),
775 },
776 Item::Alias { map, name } => {
777 if map.is_none() {
778 if let Some(prim) = Prim::from_name(name) {
779 return self.build_replacement(&Item::Prim(prim));
780 }
781 }
782 match self.resolve_alias(map.as_deref(), name)? {
783 Some(resolved) => self.build_replacement(&resolved),
784 None => Err(Error::MapLogic(format!("alias {name:?} not found"))),
785 }
786 }
787 Item::Set(_) | Item::Range(_, _) => {
791 Err(Error::MapLogic("can't use Any in a string context".into()))
792 }
793 Item::Func(_) => Err(Error::MapLogic(
794 "function item in replacement position".into(),
795 )),
796 }
797 }
798
799 fn decode_replacement(&self, built: &str, start: usize, end: usize) -> String { let chars: Vec<char> = built.chars().collect();
803 let mut out = String::with_capacity(built.len());
804 let mut i = 0usize;
805 while i < chars.len() {
806 if chars[i] == '\\' && i + 1 < chars.len() {
807 let next = chars[i + 1];
808 match next {
809 '0' | '1'..='9' => {
810 let n = next.to_digit(10).unwrap() as usize;
811 if n == 0 {
812 out.extend(&self.chars[start..end]);
813 } else if let Some(Some((gs, ge))) = self.groups.borrow().get(n) {
814 out.extend(&self.chars[*gs..*ge]);
815 }
816 i += 2;
817 continue;
818 }
819 '\\' => {
820 out.push('\\');
821 i += 2;
822 continue;
823 }
824 '&' => {
825 out.extend(&self.chars[start..end]);
826 i += 2;
827 continue;
828 }
829 _ => {
830 out.push(next);
832 i += 2;
833 continue;
834 }
835 }
836 }
837 out.push(chars[i]);
838 i += 1;
839 }
840 out
841 }
842}
843
844impl<'a> Matcher<'a> {
845 fn build_par_from(&self, item: &Item) -> Result<Vec<String>> {
855 self.build_par_from_depth(item, 0)
856 }
857
858 fn build_par_from_depth(&self, item: &Item, depth: usize) -> Result<Vec<String>> {
859 if depth > 64 {
860 return Err(Error::MapLogic("alias expansion too deep".into()));
861 }
862 match item {
863 Item::Str(s) => Ok(vec![s.clone()]),
864 Item::None => Ok(vec![String::new()]),
865 Item::Prim(prim) => match prim {
866 Prim::None_ => Ok(vec![String::new()]),
867 Prim::Space => Ok(vec![" ".to_string()]),
868 other => Err(Error::MapLogic(format!(
869 "can't use {} in a par context",
870 other.name()
871 ))),
872 },
873 Item::Range(lo, hi) => {
877 let mut keys = Vec::new();
878 let mut c = *lo;
879 loop {
880 keys.push(c.to_string());
881 if c == *hi {
882 break;
883 }
884 match char::from_u32(c as u32 + 1) {
885 Some(n) => c = n,
886 None => break,
887 }
888 }
889 Ok(keys)
890 }
891 Item::Set(entries) => {
892 let mut keys = Vec::new();
893 for entry in entries {
894 match entry {
895 Item::Str(s) => keys.push(s.clone()),
896 _ => {
899 return Err(Error::MapLogic(
900 "can't parallelize nested any() entries".into(),
901 ))
902 }
903 }
904 }
905 Ok(keys)
906 }
907 Item::Concat(parts) => {
909 let mut acc: Vec<String> = vec![String::new()];
910 for part in parts {
911 let next = self.build_par_from_depth(part, depth)?;
912 let mut joined = Vec::new();
913 for a in &acc {
914 for b in &next {
915 joined.push(format!("{a}{b}"));
916 }
917 }
918 acc = joined;
919 }
920 Ok(acc)
921 }
922 Item::Alias { map, name } => {
923 if map.is_none() {
924 if let Some(prim) = Prim::from_name(name) {
925 return self.build_par_from_depth(&Item::Prim(prim), depth);
926 }
927 }
928 match self.resolve_alias(map.as_deref(), name)? {
929 Some(resolved) => self.build_par_from_depth(&resolved, depth + 1),
930 None => Ok(vec![]),
931 }
932 }
933 Item::Maybe(_) | Item::Some(_) => Err(Error::MapLogic(
935 "can't use a MaybeSome in a par context".into(),
936 )),
937 Item::CaptureGroup { .. } => Err(Error::MapLogic(
938 "can't use a CaptureGroup in a par context".into(),
939 )),
940 Item::CaptureRef(_) => Err(Error::MapLogic(
941 "Can't use CaptureRef in parallel mode".into(),
942 )),
943 Item::Func(_) => Ok(vec![]),
945 }
946 }
947
948 fn build_par_str(&self, item: &Item) -> Result<String> {
951 let collapsed = item.first_string();
952 self.build_par_str_inner(&collapsed, 0)
953 }
954
955 fn build_par_str_inner(&self, item: &Item, depth: usize) -> Result<String> {
956 if depth > 64 {
957 return Err(Error::MapLogic("alias expansion too deep".into()));
958 }
959 match item {
960 Item::Str(s) => Ok(s.clone()),
961 Item::None => Ok(String::new()),
962 Item::Prim(prim) => match prim {
963 Prim::None_ => Ok(String::new()),
964 Prim::Space => Ok(" ".to_string()),
965 other => Err(Error::MapLogic(format!(
966 "can't use {} in a par context",
967 other.name()
968 ))),
969 },
970 Item::Concat(parts) => {
971 let mut out = String::new();
972 for p in parts {
973 out.push_str(&self.build_par_str_inner(p, depth)?);
974 }
975 Ok(out)
976 }
977 Item::Alias { map, name } => {
978 if map.is_none() {
979 if let Some(prim) = Prim::from_name(name) {
980 return self.build_par_str_inner(&Item::Prim(prim), depth);
981 }
982 }
983 match self.resolve_alias(map.as_deref(), name)? {
984 Some(resolved) => self.build_par_str_inner(&resolved, depth + 1),
985 None => Err(Error::MapLogic(format!("alias {name:?} not found"))),
986 }
987 }
988 Item::Set(_) | Item::Range(_, _) => Err(Error::MapLogic(
991 "parallel replacement is not a single string".into(),
992 )),
993 Item::Maybe(_) | Item::Some(_) => Err(Error::MapLogic(
994 "can't use a MaybeSome in a par context".into(),
995 )),
996 Item::CaptureGroup { .. } => Err(Error::MapLogic(
997 "can't use a CaptureGroup in a par context".into(),
998 )),
999 Item::CaptureRef(_) => Err(Error::MapLogic(
1000 "Can't use CaptureRef in parallel mode".into(),
1001 )),
1002 Item::Func(_) => Err(Error::MapLogic(
1003 "function item in a par context".into(),
1004 )),
1005 }
1006 }
1007}