plotnik_lib/engine/
materializer.rs1use crate::bytecode::{StringsView, TypeData, TypeId, TypeKind, TypesView};
4
5use super::effect::RuntimeEffect;
6use super::value::{NodeHandle, Value};
7
8pub trait Materializer<'t> {
10 type Output;
11
12 fn materialize(&self, effects: &[RuntimeEffect<'t>], result_type: TypeId) -> Self::Output;
13}
14
15pub struct ValueMaterializer<'a> {
17 source: &'a str,
18 types: TypesView<'a>,
19 strings: StringsView<'a>,
20}
21
22impl<'a> ValueMaterializer<'a> {
23 pub fn new(source: &'a str, types: TypesView<'a>, strings: StringsView<'a>) -> Self {
24 Self {
25 source,
26 types,
27 strings,
28 }
29 }
30
31 fn resolve_member_name(&self, idx: u16) -> String {
32 let member = self.types.get_member(idx as usize);
33 self.strings.get(member.name()).to_owned()
34 }
35
36 fn resolve_member_type(&self, idx: u16) -> TypeId {
37 self.types.get_member(idx as usize).type_id()
38 }
39
40 fn is_void_type(&self, type_id: TypeId) -> bool {
41 self.types
42 .get(type_id)
43 .is_some_and(|def| matches!(def.classify(), TypeData::Primitive(TypeKind::Void)))
44 }
45
46 fn builder_for_type(&self, type_id: TypeId) -> Builder {
48 let def = self
49 .types
50 .get(type_id)
51 .unwrap_or_else(|| panic!("unknown type_id {}", type_id.0));
52
53 match def.classify() {
54 TypeData::Composite {
55 kind: TypeKind::Struct,
56 ..
57 } => Builder::Object(vec![]),
58 TypeData::Composite {
59 kind: TypeKind::Enum,
60 ..
61 } => Builder::Scalar(None),
62 TypeData::Wrapper {
63 kind: TypeKind::ArrayZeroOrMore | TypeKind::ArrayOneOrMore,
64 ..
65 } => Builder::Array(vec![]),
66 _ => Builder::Scalar(None),
67 }
68 }
69}
70
71enum Builder {
73 Scalar(Option<Value>),
74 Array(Vec<Value>),
75 Object(Vec<(String, Value)>),
76 Tagged {
77 tag: String,
78 payload_type: TypeId,
79 fields: Vec<(String, Value)>,
80 },
81}
82
83impl Builder {
84 fn build(self) -> Value {
85 match self {
86 Builder::Scalar(v) => v.unwrap_or(Value::Null),
87 Builder::Array(arr) => Value::Array(arr),
88 Builder::Object(fields) => Value::Object(fields),
89 Builder::Tagged { tag, fields, .. } => Value::Tagged {
90 tag,
91 data: Some(Box::new(Value::Object(fields))),
92 },
93 }
94 }
95
96 fn kind(&self) -> &'static str {
97 match self {
98 Builder::Scalar(_) => "Scalar",
99 Builder::Array(_) => "Array",
100 Builder::Object(_) => "Object",
101 Builder::Tagged { .. } => "Tagged",
102 }
103 }
104}
105
106impl<'t> Materializer<'t> for ValueMaterializer<'_> {
107 type Output = Value;
108
109 fn materialize(&self, effects: &[RuntimeEffect<'t>], result_type: TypeId) -> Value {
110 let mut stack: Vec<Builder> = vec![];
112
113 let result_builder = self.builder_for_type(result_type);
115 stack.push(result_builder);
116
117 let mut pending: Option<Value> = None;
119
120 for (effect_idx, effect) in effects.iter().enumerate() {
121 match effect {
122 RuntimeEffect::Node(n) => {
123 pending = Some(Value::Node(NodeHandle::from_node(*n, self.source)));
124 }
125 RuntimeEffect::Text(n) => {
126 let text = n
127 .utf8_text(self.source.as_bytes())
128 .expect("invalid UTF-8")
129 .to_owned();
130 pending = Some(Value::String(text));
131 }
132 RuntimeEffect::Null => {
133 pending = Some(Value::Null);
134 }
135 RuntimeEffect::Arr => {
136 stack.push(Builder::Array(vec![]));
137 }
138 RuntimeEffect::Push => {
139 let val = pending.take().unwrap_or(Value::Null);
140 let Some(Builder::Array(arr)) = stack.last_mut() else {
141 panic!(
142 "effect {effect_idx}: Push expects Array on stack, found {:?}",
143 stack.last().map(|b| b.kind())
144 );
145 };
146 arr.push(val);
147 }
148 RuntimeEffect::EndArr => {
149 let top = stack.pop();
150 let Some(Builder::Array(arr)) = top else {
151 panic!(
152 "effect {effect_idx}: EndArr expects Array on stack, found {:?}",
153 top.as_ref().map(|b| b.kind())
154 );
155 };
156 pending = Some(Value::Array(arr));
157 }
158 RuntimeEffect::Obj => {
159 stack.push(Builder::Object(vec![]));
160 }
161 RuntimeEffect::Set(idx) => {
162 let field_name = self.resolve_member_name(*idx);
163 let val = pending.take().unwrap_or(Value::Null);
164 match stack.last_mut() {
165 Some(Builder::Object(obj)) => obj.push((field_name, val)),
166 Some(Builder::Tagged { fields, .. }) => fields.push((field_name, val)),
167 other => panic!(
168 "effect {effect_idx}: Set expects Object/Tagged on stack, found {:?}",
169 other.map(|b| b.kind())
170 ),
171 }
172 }
173 RuntimeEffect::EndObj => {
174 let top = stack.pop();
175 let Some(Builder::Object(fields)) = top else {
176 panic!(
177 "effect {effect_idx}: EndObj expects Object on stack, found {:?}",
178 top.as_ref().map(|b| b.kind())
179 );
180 };
181 if !fields.is_empty() {
182 pending = Some(Value::Object(fields));
184 } else if pending.is_none() {
185 if stack.len() > 1 {
191 pending = Some(Value::Object(vec![]));
192 }
193 }
195 }
197 RuntimeEffect::Enum(idx) => {
198 let tag = self.resolve_member_name(*idx);
199 let payload_type = self.resolve_member_type(*idx);
200 stack.push(Builder::Tagged {
201 tag,
202 payload_type,
203 fields: vec![],
204 });
205 }
206 RuntimeEffect::EndEnum => {
207 let top = stack.pop();
208 let Some(Builder::Tagged {
209 tag,
210 payload_type,
211 fields,
212 }) = top
213 else {
214 panic!(
215 "effect {effect_idx}: EndEnum expects Tagged on stack, found {:?}",
216 top.as_ref().map(|b| b.kind())
217 );
218 };
219 let data = if self.is_void_type(payload_type) {
221 None
222 } else {
223 Some(Box::new(pending.take().unwrap_or(Value::Object(fields))))
226 };
227 pending = Some(Value::Tagged { tag, data });
228 }
229 RuntimeEffect::Clear => {
230 pending = None;
231 }
232 }
233 }
234
235 pending
237 .or_else(|| stack.pop().map(Builder::build))
238 .unwrap_or(Value::Null)
239 }
240}