bend/fun/transform/
resugar_string.rs

1use crate::{
2  fun::{builtins, Name, Num, Pattern, Tag, Term},
3  maybe_grow, AdtEncoding,
4};
5
6impl Term {
7  /// Converts lambda-encoded strings ending with String/nil to string literals.
8  pub fn resugar_strings(&mut self, adt_encoding: AdtEncoding) {
9    match adt_encoding {
10      AdtEncoding::Scott => self.try_resugar_strings_with(Self::resugar_strings_scott),
11      AdtEncoding::NumScott => self.try_resugar_strings_with(Self::resugar_strings_num_scott),
12    }
13  }
14
15  /// Converts encoded strings to string literals using the provided extraction function.
16  fn try_resugar_strings_with(&mut self, extract_fn: fn(&Term) -> Option<(char, &Term)>) {
17    maybe_grow(|| {
18      // Try to resugar nil or cons patterns. If unsuccessful, recurse into child terms.
19      if !self.try_resugar_strings_nil() && !self.try_resugar_strings_cons(extract_fn) {
20        for child in self.children_mut() {
21          child.try_resugar_strings_with(extract_fn);
22        }
23      }
24    })
25  }
26
27  /// Attempts to resugar a nil term (String/nil) to an empty string literal.
28  fn try_resugar_strings_nil(&mut self) -> bool {
29    matches!(self, Term::Ref { nam } if nam == builtins::SNIL).then(|| *self = Term::str("")).is_some()
30  }
31
32  /// Attempts to resugar a cons term to a string literal.
33  fn try_resugar_strings_cons(&mut self, extract_fn: fn(&Term) -> Option<(char, &Term)>) -> bool {
34    self
35      .try_resugar_strings_cons_with(extract_fn)
36      .or_else(|| self.try_resugar_strings_cons_common())
37      .map(|str| *self = Term::str(&str))
38      .is_some()
39  }
40
41  /// Attempts to resugar a cons term using the provided extraction function.
42  fn try_resugar_strings_cons_with(&self, extract_fn: fn(&Term) -> Option<(char, &Term)>) -> Option<String> {
43    extract_fn(self)
44      .and_then(|(head_char, tail)| Self::build_strings_common(tail, head_char.to_string(), extract_fn))
45  }
46
47  /// Attempts to resugar a cons term using the common extraction method.
48  fn try_resugar_strings_cons_common(&self) -> Option<String> {
49    if let Term::App { tag: Tag::Static, fun, arg: tail } = self {
50      if let Term::App { tag: Tag::Static, fun: inner_fun, arg: head } = fun.as_ref() {
51        if let (Term::Ref { nam }, Term::Num { val: Num::U24(head_val) }) =
52          (inner_fun.as_ref(), head.as_ref())
53        {
54          if nam == builtins::SCONS {
55            let head_char = char::from_u32(*head_val).unwrap_or(char::REPLACEMENT_CHARACTER);
56            return Self::build_strings_common(tail, head_char.to_string(), Self::extract_strings_common);
57          }
58        }
59      }
60    }
61    None
62  }
63
64  /// Builds a string from a term structure using the provided extraction function.
65  fn build_strings_common(
66    term: &Term,
67    mut s: String,
68    extract_fn: fn(&Term) -> Option<(char, &Term)>,
69  ) -> Option<String> {
70    maybe_grow(|| {
71      let mut current = term;
72      loop {
73        match current {
74          // If we reach a nil term, we've completed the string
75          Term::Ref { nam } if nam == builtins::SNIL => return Some(s),
76          _ => {
77            // Extract the next character and continue building the string
78            let (head, next) = extract_fn(current).or_else(|| Self::extract_strings_common(current))?;
79            s.push(head);
80            current = next;
81          }
82        }
83      }
84    })
85  }
86
87  /// Extracts a character and the remaining term from a Scott-encoded string term.
88  /// The structure of this function mimics the shape of the AST for easier visualization.
89  fn resugar_strings_scott(term: &Term) -> Option<(char, &Term)> {
90    if let Term::Lam { tag: Tag::Static, pat: outer_pat, bod } = term {
91      if let Pattern::Var(None) = outer_pat.as_ref() {
92        if let Term::Lam { tag: Tag::Static, pat: inner_pat, bod: inner_bod } = bod.as_ref() {
93          if let Pattern::Var(Some(var_lam)) = inner_pat.as_ref() {
94            if let Term::App { tag: Tag::Static, fun, arg: tail } = inner_bod.as_ref() {
95              if let Term::App { tag: Tag::Static, fun: inner_fun, arg: head } = fun.as_ref() {
96                if let (Term::Var { nam: var_app }, Term::Num { val: Num::U24(head_val) }) =
97                  (inner_fun.as_ref(), head.as_ref())
98                {
99                  if var_lam == var_app {
100                    let head_char = char::from_u32(*head_val).unwrap_or(char::REPLACEMENT_CHARACTER);
101                    return Some((head_char, tail));
102                  }
103                }
104              }
105            }
106          }
107        }
108      }
109    }
110    None
111  }
112
113  /// Extracts a character and the remaining term from a NumScott-encoded string term.
114  /// The structure of this function mimics the shape of the AST for easier visualization.
115  fn resugar_strings_num_scott(term: &Term) -> Option<(char, &Term)> {
116    if let Term::Lam { tag: Tag::Static, pat, bod } = term {
117      if let Pattern::Var(Some(var_lam)) = pat.as_ref() {
118        if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_ref() {
119          if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_ref() {
120            if let Term::App { tag: Tag::Static, fun, arg } = fun.as_ref() {
121              if let (
122                Term::Var { nam: var_app },
123                Term::Ref { nam: Name(ref_nam) },
124                Term::Num { val: Num::U24(head_val) },
125              ) = (fun.as_ref(), arg.as_ref(), head.as_ref())
126              {
127                if var_lam == var_app && ref_nam == builtins::SCONS_TAG_REF {
128                  let head_char = char::from_u32(*head_val).unwrap_or(char::REPLACEMENT_CHARACTER);
129                  return Some((head_char, tail));
130                }
131              }
132            }
133          }
134        }
135      }
136    }
137    None
138  }
139
140  /// Extracts a character and the remaining term from a common-encoded string term.
141  /// The structure of this function mimics the shape of the AST for easier visualization.
142  fn extract_strings_common(term: &Term) -> Option<(char, &Term)> {
143    if let Term::App { tag: Tag::Static, fun, arg: tail } = term {
144      if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_ref() {
145        if let (Term::Ref { nam }, Term::Num { val: Num::U24(head_val) }) = (fun.as_ref(), head.as_ref()) {
146          if nam == builtins::SCONS {
147            let head_char = char::from_u32(*head_val).unwrap_or(char::REPLACEMENT_CHARACTER);
148            return Some((head_char, tail));
149          }
150        }
151      }
152    }
153    None
154  }
155}