plotnik_lib/engine/
materializer.rs1use super::effect_stream::{CapturedNode, EffectStream};
4use super::value::Value;
5use crate::ir::{DataFieldId, EffectOp, VariantTagId};
6use std::collections::BTreeMap;
7
8enum Container<'tree> {
10 Array(Vec<Value<'tree>>),
11 Object(BTreeMap<DataFieldId, Value<'tree>>),
12 Variant(VariantTagId),
13}
14
15pub struct Materializer<'a, 'tree> {
16 current: Option<Value<'tree>>,
18 stack: Vec<Container<'tree>>,
20 nodes: std::slice::Iter<'a, CapturedNode<'tree>>,
22}
23
24impl<'a, 'tree> Materializer<'a, 'tree> {
25 fn new(stream: &'a EffectStream<'tree>) -> Self {
27 Self {
28 current: None,
29 stack: Vec::new(),
30 nodes: stream.nodes().iter(),
31 }
32 }
33
34 fn finish(mut self) -> Value<'tree> {
36 self.current.take().unwrap_or(Value::Null)
37 }
38
39 pub fn materialize(stream: &'a EffectStream<'tree>) -> Value<'tree> {
41 let mut materializer = Materializer::new(stream);
42
43 for op in stream.ops() {
44 materializer.apply_op(*op);
45 }
46
47 materializer.finish()
48 }
49
50 fn apply_op(&mut self, op: EffectOp) {
52 match op {
53 EffectOp::CaptureNode => {
54 let node = *self.nodes.next().expect("mismatched node capture");
55 self.current = Some(Value::Node(node));
56 }
57 EffectOp::StartObject => {
58 self.stack.push(Container::Object(BTreeMap::new()));
59 }
60 EffectOp::EndObject => match self.stack.pop() {
61 Some(Container::Object(obj)) => self.current = Some(Value::Object(obj)),
62 _ => panic!("invalid EndObject operation"),
63 },
64 EffectOp::Field(id) => {
65 let value = self.current.take().unwrap_or(Value::Null);
66 if let Some(Container::Object(map)) = self.stack.last_mut() {
67 map.insert(id, value);
68 } else {
69 panic!("invalid Field operation without object on stack");
70 }
71 }
72 EffectOp::StartArray => {
73 self.stack.push(Container::Array(Vec::new()));
74 }
75 EffectOp::EndArray => match self.stack.pop() {
76 Some(Container::Array(arr)) => self.current = Some(Value::Array(arr)),
77 _ => panic!("invalid EndArray operation"),
78 },
79 EffectOp::PushElement => {
80 let value = self.current.take().unwrap_or(Value::Null);
81 if let Some(Container::Array(arr)) = self.stack.last_mut() {
82 arr.push(value);
83 } else {
84 panic!("invalid PushElement operation without array on stack");
85 }
86 }
87 EffectOp::ClearCurrent => {
88 self.current = None;
89 }
90 EffectOp::StartVariant(tag) => {
91 self.stack.push(Container::Variant(tag));
92 }
93 EffectOp::EndVariant => {
94 let value = self.current.take().unwrap_or(Value::Null);
95 match self.stack.pop() {
96 Some(Container::Variant(tag)) => {
97 self.current = Some(Value::Variant {
98 tag,
99 value: Box::new(value),
100 });
101 }
102 _ => panic!("invalid EndVariant operation"),
103 }
104 }
105 EffectOp::ToString => {
106 if let Some(Value::Node(node)) = self.current.take() {
107 self.current = Some(Value::String(node.text().to_string()));
108 } else {
109 panic!("invalid ToString operation without a node");
110 }
111 }
112 }
113 }
114}
115
116#[cfg(test)]
117mod materializer_tests;