Skip to main content

copperlace/render/
nodes.rs

1use rand::distr::Distribution;
2use rand::distr::weighted::WeightedIndex;
3use rand::seq::IndexedRandom;
4
5use super::error::RenderError;
6use super::state::RenderState;
7
8/// A renderable text-generating piece of a compiled rule.
9///
10/// Nodes are produced from config values, template expressions, and template
11/// statements. Text generation is driven by `RenderState`, which carries the
12/// rule table, bound variables, RNG, and rule call stack for cycle detection.
13pub trait TextGeneratorNode {
14    /// Generates text using the supplied render state.
15    fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError>;
16}
17
18/// Literal text node.
19///
20/// `String` is used for plain template spans such as `"Hello "` and for scalar
21/// config values that do not need further expansion. Rendering returns the
22/// string unchanged.
23impl TextGeneratorNode for String {
24    fn generate_text(&self, _state: &mut RenderState) -> Result<String, RenderError> {
25        Ok(self.clone())
26    }
27}
28
29/// Looks up a previously bound variable in the current render context.
30///
31/// This node is useful when a template should require a value that was already
32/// bound by a `BindNode`. In the current parser, normal `{name}` expressions use
33/// `RuleCallNode` instead, because they can mean either a bound variable or a
34/// named rule.
35pub struct VariableNode {
36    name: String,
37}
38
39impl VariableNode {
40    /// Creates a variable node that reads a bound value by name.
41    pub fn new(name: String) -> Self {
42        VariableNode { name }
43    }
44}
45
46impl TextGeneratorNode for VariableNode {
47    fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError> {
48        state
49            .context
50            .get(&self.name)
51            .cloned()
52            .ok_or_else(|| RenderError::UnknownRule(self.name.clone()))
53    }
54}
55
56/// Calls another named rule, or reuses a bound/context value with the same name.
57///
58/// This is the node generated for `{rule}` template expressions. Resolution
59/// order is:
60/// 1. return an existing bound value from the render context;
61/// 2. render and cache a lazy `context` default, if one exists;
62/// 3. render the named rule from `RuleSet`.
63pub struct RuleCallNode {
64    name: String,
65}
66
67impl RuleCallNode {
68    /// Creates a rule call node for a template reference.
69    pub fn new(name: String) -> Self {
70        RuleCallNode { name }
71    }
72}
73
74impl TextGeneratorNode for RuleCallNode {
75    fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError> {
76        if let Some(value) = state.context.get(&self.name) {
77            return Ok(value.clone());
78        }
79
80        if let Some(value) = state
81            .ruleset
82            .render_context_default_with_state(&self.name, state)?
83        {
84            state.context.insert(self.name.clone(), value.clone());
85            return Ok(value);
86        }
87
88        state.ruleset.render_rule_with_state(&self.name, state)
89    }
90}
91
92/// Controls whether a binding expression preserves or overwrites an existing
93/// value in the render context.
94pub enum BindMode {
95    /// Preserve an existing binding and bind only when the name is missing.
96    IfMissing,
97    /// Always render the source and replace any existing binding.
98    Overwrite,
99}
100
101/// Binds the output of a child node into the render context without emitting it.
102///
103/// This is the node generated for `{% alias:rule %}` statements. If `alias` is
104/// not already bound, it renders `rule` and stores the result under `alias`. It
105/// also supports `{% alias:=rule %}` statements, which always render `rule` and
106/// overwrite `alias`. Binding statements always return an empty string so later
107/// `{alias}` references reuse the generated value.
108pub struct BindNode {
109    name: String,
110    node: Box<dyn TextGeneratorNode>,
111    mode: BindMode,
112}
113
114impl BindNode {
115    /// Creates a binding node for a target name, source node, and binding mode.
116    pub fn new(name: String, node: Box<dyn TextGeneratorNode>, mode: BindMode) -> Self {
117        BindNode { name, node, mode }
118    }
119}
120
121impl TextGeneratorNode for BindNode {
122    fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError> {
123        if matches!(self.mode, BindMode::IfMissing) && state.context.contains_key(&self.name) {
124            return Ok(String::new());
125        }
126
127        let value = self.node.generate_text(state)?;
128        state.context.insert(self.name.clone(), value);
129        Ok(String::new())
130    }
131}
132
133/// Applies named processors to a rendered child value from left to right.
134pub struct ProcessorPipelineNode {
135    node: Box<dyn TextGeneratorNode>,
136    processors: Vec<String>,
137}
138
139impl ProcessorPipelineNode {
140    /// Creates a pipeline node that applies processors to the rendered child.
141    pub fn new(node: Box<dyn TextGeneratorNode>, processors: Vec<String>) -> Self {
142        ProcessorPipelineNode { node, processors }
143    }
144}
145
146impl TextGeneratorNode for ProcessorPipelineNode {
147    fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError> {
148        let mut value = self.node.generate_text(state)?;
149        for processor_name in &self.processors {
150            value = state.ruleset.process(processor_name, &value)?;
151        }
152        Ok(value)
153    }
154}
155
156/// Randomly renders one child node from a list of alternatives.
157///
158/// This is produced from text-rendered config arrays. For example,
159/// `mood = [happy, sad]` becomes a choice between two literal nodes. If the
160/// array is empty, rendering returns `RenderError::EmptyChoice`.
161pub struct ChoiceNode {
162    nodes: Vec<Box<dyn TextGeneratorNode>>,
163}
164
165impl ChoiceNode {
166    /// Creates a choice node from renderable alternatives.
167    pub fn new(nodes: Vec<Box<dyn TextGeneratorNode>>) -> Self {
168        ChoiceNode { nodes }
169    }
170}
171
172impl TextGeneratorNode for ChoiceNode {
173    fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError> {
174        let random_node = self
175            .nodes
176            .choose(&mut state.rng)
177            .ok_or(RenderError::EmptyChoice)?;
178        random_node.generate_text(state)
179    }
180}
181
182/// Randomly renders one child node using per-child weights.
183///
184/// Weighted choices are produced from arrays containing at least one weighted
185/// object entry, such as `{ value = "common", weight = 9 }`. Plain entries in
186/// the same array receive weight `1.0`.
187pub struct WeightedChoiceNode {
188    nodes: Vec<Box<dyn TextGeneratorNode>>,
189    distribution: WeightedIndex<f64>,
190}
191
192impl WeightedChoiceNode {
193    /// Creates a weighted choice node from renderable alternatives and weights.
194    pub fn new(entries: Vec<(Box<dyn TextGeneratorNode>, f64)>) -> Result<Self, RenderError> {
195        let (nodes, weights): (Vec<_>, Vec<_>) = entries.into_iter().unzip();
196        let distribution = WeightedIndex::new(weights)
197            .map_err(|error| RenderError::InvalidWeightedChoice(error.to_string()))?;
198        Ok(WeightedChoiceNode {
199            nodes,
200            distribution,
201        })
202    }
203}
204
205impl TextGeneratorNode for WeightedChoiceNode {
206    fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError> {
207        let index = self.distribution.sample(&mut state.rng);
208        self.nodes[index].generate_text(state)
209    }
210}
211
212/// Renders a sequence of child nodes and concatenates their output.
213///
214/// This is produced from string templates after splitting literal text,
215/// `{...}` expressions, and `{% ... %}` statements. For example,
216/// `"Hello {name}"` becomes a `VecNode` containing a literal `"Hello "` and a
217/// `RuleCallNode` for `name`.
218pub struct VecNode {
219    nodes: Vec<Box<dyn TextGeneratorNode>>,
220}
221
222impl VecNode {
223    /// Creates a sequence node that renders children in order.
224    pub fn new(nodes: Vec<Box<dyn TextGeneratorNode>>) -> Self {
225        VecNode { nodes }
226    }
227}
228
229impl TextGeneratorNode for VecNode {
230    fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError> {
231        let mut output = String::new();
232
233        for node in &self.nodes {
234            output.push_str(&node.generate_text(state)?);
235        }
236
237        Ok(output)
238    }
239}
240
241/// Placeholder node for config value types that are not renderable yet.
242///
243/// Object values currently compile to this node unless they are the special
244/// top-level `context` object handled by `RuleSet::from_config`. Rendering this
245/// node returns `RenderError::UnsupportedValue`.
246pub struct UnsupportedValueNode {
247    value_type: String,
248}
249
250impl UnsupportedValueNode {
251    /// Creates a node that reports an unsupported config value type at render time.
252    pub fn new(value_type: String) -> Self {
253        UnsupportedValueNode { value_type }
254    }
255}
256
257impl TextGeneratorNode for UnsupportedValueNode {
258    fn generate_text(&self, _state: &mut RenderState) -> Result<String, RenderError> {
259        Err(RenderError::UnsupportedValue(self.value_type.clone()))
260    }
261}