1use std::collections::{HashMap, HashSet};
2
3use super::error::RenderError;
4use super::ruleset::RuleSet;
5use super::value::{CopperlaceNumber, CopperlaceValue, StructuredNode};
6
7pub type RenderContext = HashMap<String, String>;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub struct RenderOptions {
17 pub max_recursion_depth: usize,
24}
25
26pub struct RenderState<'a> {
32 pub(crate) ruleset: &'a RuleSet,
33 context: RenderContext,
34 scopes: Vec<IterationScope<'a>>,
35 pub(crate) options: RenderOptions,
36 pub(crate) call_stack: Vec<String>,
37 scoped_call_stack: Vec<usize>,
38 pub(crate) unique_choices: HashMap<String, HashSet<usize>>,
39 pub(crate) rng: rand::rngs::ThreadRng,
40}
41
42#[derive(Clone, Copy)]
43pub(crate) struct LoopMetadata {
44 pub(crate) index: usize,
45 pub(crate) length: usize,
46}
47
48struct IterationScope<'a> {
49 variable_name: String,
50 value: IterationValue<'a>,
51 metadata: LoopMetadata,
52 bindings: RenderContext,
53}
54
55enum ScopedValue<'a> {
56 Text(String),
57 Node(&'a StructuredNode),
58 Runtime(CopperlaceValue),
59 MetadataObject,
60}
61
62pub(crate) enum IterationValue<'a> {
63 Compiled(&'a StructuredNode),
64 Runtime(CopperlaceValue),
65}
66
67impl<'a> RenderState<'a> {
68 pub fn new(ruleset: &'a RuleSet) -> Self {
70 Self::with_context(ruleset, RenderContext::new())
71 }
72
73 pub fn with_context(ruleset: &'a RuleSet, context: RenderContext) -> Self {
75 Self::with_context_and_options(ruleset, context, RenderOptions::default())
76 }
77
78 pub fn with_context_and_options(
80 ruleset: &'a RuleSet,
81 context: RenderContext,
82 options: RenderOptions,
83 ) -> Self {
84 RenderState {
85 ruleset,
86 context,
87 scopes: Vec::new(),
88 options,
89 call_stack: Vec::new(),
90 scoped_call_stack: Vec::new(),
91 unique_choices: HashMap::new(),
92 rng: rand::rngs::ThreadRng::default(),
93 }
94 }
95
96 pub(crate) fn resolve_bound_text(&mut self, name: &str) -> Result<Option<String>, RenderError> {
97 let Some(value) = self.scoped_value(name)? else {
98 return Ok(None);
99 };
100
101 match value {
102 ScopedValue::Text(value) => Ok(Some(value)),
103 ScopedValue::Node(node) => self.render_scoped_node(name, node).map(Some),
104 ScopedValue::Runtime(value) => value.to_rendered_text().map(Some),
105 ScopedValue::MetadataObject => Err(RenderError::UnsupportedValue("object".to_string())),
106 }
107 }
108
109 pub(crate) fn contains_bound_value(&self, name: &str) -> Result<bool, RenderError> {
110 self.scoped_value(name).map(|value| value.is_some())
111 }
112
113 pub(crate) fn bind(
114 &mut self,
115 name: &str,
116 value: String,
117 overwrite: bool,
118 ) -> Result<(), RenderError> {
119 if overwrite && self.is_immutable_loop_name(name) {
120 return Err(RenderError::ImmutableLoopBinding(name.to_string()));
121 }
122
123 if let Some(scope) = self.scopes.last_mut() {
124 scope.bindings.insert(name.to_string(), value);
125 } else {
126 self.context.insert(name.to_string(), value);
127 }
128 Ok(())
129 }
130
131 pub(crate) fn ensure_mutable_binding(&self, name: &str) -> Result<(), RenderError> {
132 if self.is_immutable_loop_name(name) {
133 return Err(RenderError::ImmutableLoopBinding(name.to_string()));
134 }
135 Ok(())
136 }
137
138 pub(crate) fn cache_context_default(&mut self, name: &str, value: String) {
139 self.context.insert(name.to_string(), value);
140 }
141
142 pub(crate) fn iterable_elements(
143 &self,
144 source: &str,
145 ) -> Result<Vec<IterationValue<'a>>, RenderError> {
146 let value = match self.scoped_value(source)? {
147 Some(value) => value,
148 None => {
149 if let Some(node) = self.ruleset.structured_context_node(source) {
150 ScopedValue::Node(node)
151 } else {
152 ScopedValue::Node(self.ruleset.structured_node(source)?)
153 }
154 }
155 };
156
157 match value {
158 ScopedValue::Node(StructuredNode::Array(values)) => values
159 .iter()
160 .map(|entry| {
161 entry
162 .value_node()
163 .map(IterationValue::Compiled)
164 .ok_or_else(|| RenderError::UnsupportedIterationSource {
165 source: source.to_string(),
166 value_type: "array with generated entries".to_string(),
167 })
168 })
169 .collect(),
170 ScopedValue::Node(node) => Err(RenderError::UnsupportedIterationSource {
171 source: source.to_string(),
172 value_type: node.value_type().to_string(),
173 }),
174 ScopedValue::Text(_) => Err(RenderError::UnsupportedIterationSource {
175 source: source.to_string(),
176 value_type: "string".to_string(),
177 }),
178 ScopedValue::MetadataObject => Err(RenderError::UnsupportedIterationSource {
179 source: source.to_string(),
180 value_type: "object".to_string(),
181 }),
182 ScopedValue::Runtime(CopperlaceValue::Array(values)) => {
183 Ok(values.into_iter().map(IterationValue::Runtime).collect())
184 }
185 ScopedValue::Runtime(value) => Err(RenderError::UnsupportedIterationSource {
186 source: source.to_string(),
187 value_type: value.value_type().to_string(),
188 }),
189 }
190 }
191
192 pub(crate) fn push_iteration_scope(
193 &mut self,
194 variable_name: &str,
195 value: IterationValue<'a>,
196 metadata: LoopMetadata,
197 ) {
198 self.scopes.push(IterationScope {
199 variable_name: variable_name.to_string(),
200 value,
201 metadata,
202 bindings: RenderContext::new(),
203 });
204 }
205
206 pub(crate) fn pop_iteration_scope(&mut self) {
207 self.scopes.pop();
208 }
209
210 fn scoped_value(&self, name: &str) -> Result<Option<ScopedValue<'a>>, RenderError> {
211 for scope in self.scopes.iter().rev() {
212 if name == scope.variable_name {
213 return Ok(Some(match &scope.value {
214 IterationValue::Compiled(value) => ScopedValue::Node(value),
215 IterationValue::Runtime(value) => ScopedValue::Runtime(value.clone()),
216 }));
217 }
218 if let Some(path) = name
219 .strip_prefix(&scope.variable_name)
220 .and_then(|suffix| suffix.strip_prefix('.'))
221 {
222 return match &scope.value {
223 IterationValue::Compiled(value) => value
224 .child(path)
225 .map(ScopedValue::Node)
226 .map(Some)
227 .ok_or_else(|| RenderError::UnknownRule(name.to_string())),
228 IterationValue::Runtime(value) => value
229 .child(path)
230 .cloned()
231 .map(ScopedValue::Runtime)
232 .map(Some)
233 .ok_or_else(|| RenderError::UnknownRule(name.to_string())),
234 };
235 }
236
237 if name == "loop" {
238 return Ok(Some(ScopedValue::MetadataObject));
239 }
240 if let Some(field) = name.strip_prefix("loop.") {
241 return self.metadata_value(name, field, scope.metadata).map(Some);
242 }
243
244 if let Some(value) = scope.bindings.get(name) {
245 return Ok(Some(ScopedValue::Text(value.clone())));
246 }
247 }
248
249 Ok(self.context.get(name).cloned().map(ScopedValue::Text))
250 }
251
252 fn metadata_value(
253 &self,
254 full_name: &str,
255 field: &str,
256 metadata: LoopMetadata,
257 ) -> Result<ScopedValue<'a>, RenderError> {
258 let value = match field {
259 "index" => {
260 CopperlaceValue::Number(CopperlaceNumber::Unsigned((metadata.index + 1) as u64))
261 }
262 "index0" => CopperlaceValue::Number(CopperlaceNumber::Unsigned(metadata.index as u64)),
263 "length" => CopperlaceValue::Number(CopperlaceNumber::Unsigned(metadata.length as u64)),
264 "first" => CopperlaceValue::Boolean(metadata.index == 0),
265 "last" => CopperlaceValue::Boolean(metadata.index + 1 == metadata.length),
266 _ => return Err(RenderError::UnknownRule(full_name.to_string())),
267 };
268 Ok(ScopedValue::Runtime(value))
269 }
270
271 fn render_scoped_node(
272 &mut self,
273 name: &str,
274 node: &'a StructuredNode,
275 ) -> Result<String, RenderError> {
276 let identity = node as *const StructuredNode as usize;
277 let existing_calls = self
278 .scoped_call_stack
279 .iter()
280 .filter(|entry| **entry == identity)
281 .count();
282 if self.options.max_recursion_depth == 0 && existing_calls > 0 {
283 let mut cycle = self.call_stack.clone();
284 cycle.push(name.to_string());
285 return Err(RenderError::CircularRuleReference(cycle));
286 }
287 if existing_calls > self.options.max_recursion_depth {
288 return Ok(String::new());
289 }
290
291 self.scoped_call_stack.push(identity);
292 self.call_stack.push(name.to_string());
293 let result = node.generate_text(self);
294 self.call_stack.pop();
295 self.scoped_call_stack.pop();
296 result
297 }
298
299 fn is_immutable_loop_name(&self, name: &str) -> bool {
300 self.scopes.iter().rev().any(|scope| {
301 name == scope.variable_name
302 || name.starts_with(&format!("{}.", scope.variable_name))
303 || name == "loop"
304 || name.starts_with("loop.")
305 })
306 }
307
308 pub(crate) fn used_unique_choice_indices(&self, rule_name: &str) -> Option<&HashSet<usize>> {
309 self.unique_choices.get(rule_name)
310 }
311
312 pub(crate) fn mark_unique_choice_index(&mut self, rule_name: &str, index: usize) {
313 self.unique_choices
314 .entry(rule_name.to_string())
315 .or_default()
316 .insert(index);
317 }
318}