glossa_dsl/resolver/lookup_value.rs
1use tap::{Pipe, Tap};
2use tinyvec::TinyVec;
3
4// use super::{ResolverResult, Resolver};
5use crate::{
6 MiniStr,
7 error::{ResolverError, ResolverResult},
8 parsers::context::Context,
9 resolver::{BTreeRawMap, Resolver},
10 template::Template,
11};
12
13impl Resolver {
14 /// Core resolution method
15 ///
16 /// > If the context is empty, you can directly use [Self::try_get].
17 ///
18 /// ## Algorithm
19 ///
20 /// 1. Context sorting for O(log n) parameter lookups
21 /// 2. Recursive template evaluation
22 /// 3. Branch prediction for conditional blocks
23 ///
24 /// ## Example
25 ///
26 /// ```
27 /// use glossa_dsl::{Resolver, error::ResolverError};
28 ///
29 /// let res: Resolver = [
30 /// ("h", "Hello"),
31 /// ("greeting", "{h} {$🐱}"),
32 /// ]
33 /// .try_into()?;
34 ///
35 /// let ctx = [("🐱", "喵 ฅ(°ω°ฅ)")];
36 ///
37 /// let text = res.get_with_context("greeting", &ctx)?;
38 /// assert_eq!(text, "Hello 喵 ฅ(°ω°ฅ)");
39 ///
40 /// # Ok::<(), ResolverError>(())
41 /// ```
42 ///
43 /// See also: [Self::get_with_ctx_map]
44 pub fn get_with_context(
45 &self,
46 var_name: &str,
47 context: &[(&str, &str)],
48 ) -> ResolverResult<MiniStr> {
49 let process = |ctx| self.try_get_template_and_process(var_name, ctx);
50
51 match context.is_empty() {
52 true => return process(&Context::Empty),
53 _ => context
54 .iter()
55 .copied()
56 .collect::<TinyVec<[(&str, &str); 5]>>()
57 .tap_mut(|x| x.sort_unstable_by_key(|&(k, _)| k)),
58 }
59 .as_ref()
60 .pipe(Context::Slice)
61 .pipe_ref(process)
62 }
63
64 /// Similar to [Self::get_with_context], but the context is
65 /// `BTreeMap<MiniStr, MiniStr>` instead of `&[(&str, &str)]`.
66 pub fn get_with_ctx_btree_map(
67 &self,
68 var_name: &str,
69 context_map: &BTreeRawMap,
70 ) -> ResolverResult<MiniStr> {
71 let process = |ctx| self.try_get_template_and_process(var_name, ctx);
72
73 match context_map.is_empty() {
74 true => Context::Empty,
75 _ => context_map.pipe(Context::BTree),
76 }
77 .pipe_ref(process)
78 }
79
80 /// Similar to [Self::get_with_context], but no context.
81 ///
82 /// ## Example
83 ///
84 /// ```
85 /// use glossa_dsl::{Resolver, error::ResolverError};
86 ///
87 /// let res: Resolver = [
88 /// ("🐱", "ฅ(°ω°ฅ)"),
89 /// ("hi", "Hello"),
90 /// ("greeting", "{ hi } { 🐱 }"),
91 /// ]
92 /// .try_into()?;
93 ///
94 /// let text = res.try_get("greeting")?;
95 /// assert_eq!(text, "Hello ฅ(°ω°ฅ)");
96 ///
97 /// # Ok::<(), ResolverError>(())
98 /// ```
99 pub fn try_get(&self, var_name: &str) -> ResolverResult<MiniStr> {
100 let process = |ctx| self.try_get_template_and_process(var_name, ctx);
101 process(&Context::Empty)
102 }
103
104 #[cfg(feature = "std")]
105 /// Similar to [Self::get_with_context], but the context is
106 /// [`&ContextMap`](crate::ContextMap) instead of `&[(&str, &str)]`.
107 ///
108 /// > If the parameter you need to pass is a Context HashMap that owns the
109 /// > data internally (e.g., `HashMap<KString, CompactString>`), instead of
110 /// > `HashMap<&str, &str>`, please use [Self::get_with_ctx_map_buf].
111 ///
112 /// ## Example
113 ///
114 /// ```
115 /// use glossa_dsl::Resolver;
116 ///
117 /// let res: Resolver = [
118 /// ("g", "Good"),
119 /// ("greeting", "{g} { time-period }! { $name }"),
120 /// (
121 /// "time-period",
122 /// "$period ->
123 /// [morning] Morning
124 /// *[other] {$period}",
125 /// ),
126 /// ]
127 /// .try_into()?;
128 ///
129 /// let ctx_map = [("name", "Tom"), ("period", "night")]
130 /// .into_iter()
131 /// .collect();
132 ///
133 /// let text = res.get_with_ctx_map("greeting", &ctx_map)?;
134 /// assert_eq!(text, "Good night! Tom");
135 ///
136 /// # Ok::<(), glossa_dsl::error::ResolverError>(())
137 /// ```
138 pub fn get_with_ctx_map(
139 &self,
140 var_name: &str,
141 context_map: &crate::ContextMap,
142 ) -> ResolverResult<MiniStr> {
143 let process = |ctx| self.try_get_template_and_process(var_name, ctx);
144
145 match context_map.is_empty() {
146 true => Context::Empty,
147 _ => context_map.pipe(Context::Map),
148 }
149 .pipe_ref(process)
150 }
151
152 #[cfg(feature = "std")]
153 /// Similar to [Self::get_with_ctx_map], but the context is
154 /// [`&ContextMapBuf`](crate::ContextMapBuf) instead of
155 /// [`&ContextMap`](crate::ContextMap).
156 ///
157 /// ## Example
158 ///
159 /// ```
160 /// use glossa_dsl::Resolver;
161 ///
162 /// let res: Resolver = [
163 /// ("greeting", "{$hi} { $name }"),
164 /// ]
165 /// .try_into()?;
166 ///
167 /// let ctx_map = [("name", "Tom"), ("hi", "Hello!")]
168 /// .into_iter()
169 /// .map(|(k, v)| (k.into(), v.into()) )
170 /// .collect();
171 ///
172 /// let text = res.get_with_ctx_map("greeting", &ctx_map)?;
173 /// assert_eq!(text, "Hello! Tom");
174 ///
175 /// # Ok::<(), glossa_dsl::error::ResolverError>(())
176 /// ```
177 pub fn get_with_ctx_map_buf(
178 &self,
179 var_name: &str,
180 context_map: &crate::ContextMapBuf,
181 ) -> ResolverResult<MiniStr> {
182 let process = |ctx| self.try_get_template_and_process(var_name, ctx);
183
184 match context_map.is_empty() {
185 true => Context::Empty,
186 _ => context_map.pipe(Context::MapBuf),
187 }
188 .pipe_ref(process)
189 }
190
191 pub(crate) fn try_get_template(&self, key: &str) -> ResolverResult<&Template> {
192 self
193 .0
194 .get(key)
195 .ok_or_else(|| ResolverError::UndefinedVariable(key.into()))
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 #[ignore]
205 #[cfg(feature = "std")]
206 fn test_get_with_ctx_map() -> ResolverResult<()> {
207 let res: Resolver = [
208 ("g", "Good"),
209 ("greeting", "{g} { time-period }! { $name }"),
210 (
211 "time-period",
212 "$period ->
213 [morning] Morning
214 *[other] {$period}",
215 ),
216 ]
217 .try_into()?;
218
219 let ctx_map = [("name", "Tom"), ("period", "night")]
220 .into_iter()
221 .collect();
222
223 let text = res.get_with_ctx_map("greeting", &ctx_map)?;
224 assert_eq!(text, "Good night! Tom");
225 Ok(())
226 }
227
228 #[test]
229 fn test_get_with_btree_map() -> ResolverResult<()> {
230 let res: Resolver = [
231 ("greeting", "Good { time-period }! { $name }"),
232 (
233 "time-period",
234 "$period ->
235 [morning] Morning
236 *[other] {$period}",
237 ),
238 ]
239 .try_into()?;
240
241 let ctx_map = [("name", "Tom"), ("period", "morning")]
242 .into_iter()
243 .map(|(k, v)| (k.into(), v.into()))
244 .collect();
245
246 let text = res.get_with_ctx_btree_map("greeting", &ctx_map)?;
247 assert_eq!(text, "Good Morning! Tom");
248 Ok(())
249 }
250}