1use crate::format::{IrError, IslandEntry, MAX_ISLAND_STATE_BYTES};
3use crate::parser::IrModule;
4use crate::schema::{PropField, PropType};
5use crate::slot::{SlotData, SlotValue};
6
7const PREFIX: &str = "{\"v\":1,\"s\":[],\"i\":[";
8const SUFFIX: &str = "]}";
9static NULL: SlotValue = SlotValue::Null;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct IslandState {
18 json: String,
19 count: usize,
20}
21impl IslandState {
22 pub fn as_json(&self) -> &str {
26 &self.json
27 }
28 pub fn is_empty(&self) -> bool {
30 self.count == 0
31 }
32 pub fn len(&self) -> usize {
34 self.count
35 }
36}
37
38pub(crate) struct StateBuilder {
39 encoded: String,
40 count: usize,
41}
42impl StateBuilder {
43 pub(crate) fn new() -> Self {
44 Self {
45 encoded: PREFIX.to_owned(),
46 count: 0,
47 }
48 }
49 pub(crate) fn snapshot(
52 &mut self,
53 module: &IrModule,
54 entry: &IslandEntry,
55 slots: &SlotData,
56 ) -> Result<(usize, String), IrError> {
57 let before = self.encoded.len();
58 let occurrence = self.count;
59 let hash = entry
60 .digest
61 .iter()
62 .map(|byte| format!("{byte:02x}"))
63 .collect::<String>();
64 let result = (|| {
65 if entry.digest.len() != 8 {
66 return Err(invalid(entry.id, &[], "identity digest is not eight bytes"));
67 }
68 let fields = module
69 .schema
70 .objects
71 .get(entry.schema_object as usize)
72 .ok_or_else(|| invalid(entry.id, &[], "missing declared island object"))?;
73 if fields.len() != entry.slot_ids.len() {
74 return Err(invalid(
75 entry.id,
76 &[],
77 "ordered input count differs from declared fields",
78 ));
79 }
80 if occurrence > 0 {
81 self.push(",")?;
82 }
83 self.push("{\"h\":\"")?;
84 self.push(&hash)?;
85 self.push("\",\"p\":")?;
86 self.project(
87 module,
88 entry.id,
89 Task::Object {
90 fields,
91 values: ObjectValues::Inputs(&entry.slot_ids, slots),
92 index: 0,
93 emitted: false,
94 },
95 )?;
96 self.push("}")?;
97 Ok(())
98 })();
99 if let Err(error) = result {
100 self.encoded.truncate(before);
101 return Err(error);
102 }
103 self.count += 1;
104 Ok((occurrence, hash))
105 }
106 pub(crate) fn finish(mut self) -> Result<IslandState, IrError> {
107 let charged = self
108 .encoded
109 .len()
110 .checked_add(SUFFIX.len())
111 .ok_or_else(limit)?;
112 if charged > MAX_ISLAND_STATE_BYTES {
113 return Err(limit());
114 }
115 self.encoded.push_str(SUFFIX);
116 if self.encoded.len() != charged {
118 return Err(limit());
119 }
120 Ok(IslandState {
121 json: self.encoded,
122 count: self.count,
123 })
124 }
125 fn push(&mut self, text: &str) -> Result<(), IrError> {
126 let next = self
127 .encoded
128 .len()
129 .checked_add(text.len())
130 .and_then(|n| n.checked_add(SUFFIX.len()))
131 .ok_or_else(limit)?;
132 if next > MAX_ISLAND_STATE_BYTES {
133 return Err(limit());
134 }
135 self.encoded.push_str(text);
136 Ok(())
137 }
138 fn string(&mut self, text: &str) -> Result<(), IrError> {
139 self.push("\"")?;
140 for ch in text.chars() {
141 match ch {
142 '"' => self.push("\\\"")?,
143 '\\' => self.push("\\\\")?,
144 '\n' => self.push("\\n")?,
145 '\r' => self.push("\\r")?,
146 '\t' => self.push("\\t")?,
147 '\u{08}' => self.push("\\b")?,
148 '\u{0c}' => self.push("\\f")?,
149 '<' => self.push("\\u003c")?,
150 '\0'..='\u{1f}' => {
151 self.push(&format!("\\u{:04x}", ch as u8))?;
152 }
153 _ => {
154 let mut bytes = [0; 4];
155 self.push(ch.encode_utf8(&mut bytes))?;
156 }
157 }
158 }
159 self.push("\"")
160 }
161 fn project<'a>(
162 &mut self,
163 module: &'a IrModule,
164 island: u16,
165 first: Task<'a>,
166 ) -> Result<(), IrError> {
167 let mut tasks = vec![first];
168 let mut path = Vec::new();
169 while let Some(task) = tasks.pop() {
172 match task {
173 Task::PathEnd => {
174 path.pop();
175 }
176 Task::Value(kind, value) => match (kind, value) {
177 (PropType::Optional(_), SlotValue::Null) => self.push("null")?,
178 (PropType::Optional(inner), _) => tasks.push(Task::Value(inner, value)),
179 (PropType::Boolean, SlotValue::Bool(value)) => {
180 self.push(if *value { "true" } else { "false" })?
181 }
182 (PropType::String, SlotValue::Text(value)) => self.string(value)?,
183 (PropType::Number, SlotValue::Number(number)) if number.is_finite() => {
184 self.push(&value.to_text())?
185 }
186 (PropType::Date, SlotValue::Number(number))
187 if number.is_finite()
188 && number.fract() == 0.0
189 && number.abs() <= 8_640_000_000_000_000.0 =>
190 {
191 self.push(&value.to_text())?
192 }
193 (PropType::Enum(members), SlotValue::Text(value))
194 if members.iter().any(|id| {
195 module.strings.get(*id).is_ok_and(|member| member == value)
196 }) =>
197 {
198 self.string(value)?
199 }
200 (PropType::Array(inner), SlotValue::Array(values)) => tasks.push(Task::Array {
201 kind: inner,
202 values,
203 index: 0,
204 }),
205 (PropType::Object(id), SlotValue::Object(values)) => {
206 let fields = module
207 .schema
208 .objects
209 .get(*id as usize)
210 .ok_or_else(|| invalid(island, &path, "missing declared object"))?;
211 tasks.push(Task::Object {
212 fields,
213 values: ObjectValues::Named(values),
214 index: 0,
215 emitted: false,
216 });
217 }
218 _ => return Err(invalid(island, &path, "missing or invalid declared value")),
219 },
220 Task::Array {
221 kind,
222 values,
223 index,
224 } => {
225 if index == 0 {
226 self.push("[")?;
227 }
228 if let Some(value) = values.get(index) {
229 if index > 0 {
230 self.push(",")?;
231 }
232 tasks.push(Task::Array {
233 kind,
234 values,
235 index: index + 1,
236 });
237 path.push(Path::Index(index));
238 tasks.push(Task::PathEnd);
239 tasks.push(Task::Value(kind, value));
240 } else {
241 self.push("]")?;
242 }
243 }
244 Task::Object {
245 fields,
246 values,
247 index,
248 emitted,
249 } => {
250 if index == 0 {
251 self.push("{")?;
252 }
253 if let Some(field) = fields.get(index) {
254 let name = module.strings.get(field.name)?;
255 let value = match values {
256 ObjectValues::Inputs(ids, slots) => slots.get(ids[index]),
257 ObjectValues::Named(entries) => {
258 let mut matches = entries.iter().filter(|(key, _)| key == name);
259 let value = matches.next().map_or(&NULL, |(_, value)| value);
260 if matches.next().is_some() {
261 path.push(Path::Field(name));
262 return Err(invalid(
263 island,
264 &path,
265 "duplicate declared input field",
266 ));
267 }
268 value
269 }
270 };
271 if matches!(field.kind, PropType::Optional(_))
272 && matches!(value, SlotValue::Null)
273 {
274 tasks.push(Task::Object {
275 fields,
276 values,
277 index: index + 1,
278 emitted,
279 });
280 continue;
281 }
282 if emitted {
283 self.push(",")?;
284 }
285 self.string(name)?;
286 self.push(":")?;
287 tasks.push(Task::Object {
288 fields,
289 values,
290 index: index + 1,
291 emitted: true,
292 });
293 path.push(Path::Field(name));
294 tasks.push(Task::PathEnd);
295 tasks.push(Task::Value(&field.kind, value));
296 } else {
297 self.push("}")?;
298 }
299 }
300 }
301 }
302 Ok(())
303 }
304}
305#[derive(Clone, Copy)]
306enum ObjectValues<'a> {
307 Inputs(&'a [u16], &'a SlotData),
308 Named(&'a [(String, SlotValue)]),
309}
310enum Task<'a> {
311 Value(&'a PropType, &'a SlotValue),
312 Array {
313 kind: &'a PropType,
314 values: &'a [SlotValue],
315 index: usize,
316 },
317 Object {
318 fields: &'a [PropField],
319 values: ObjectValues<'a>,
320 index: usize,
321 emitted: bool,
322 },
323 PathEnd,
324}
325enum Path<'a> {
326 Field(&'a str),
327 Index(usize),
328}
329fn invalid(island: u16, path: &[Path<'_>], reason: &'static str) -> IrError {
330 use std::fmt::Write;
331 let mut name = String::from("$");
332 for segment in path {
333 match segment {
334 Path::Field(field) => {
335 let _ = write!(name, "[{field:?}]");
336 }
337 Path::Index(index) => {
338 let _ = write!(name, "[{index}]");
339 }
340 }
341 }
342 IrError::InvalidIslandState(Box::new(crate::format::IslandStateViolation {
343 island,
344 path: name,
345 reason,
346 }))
347}
348fn limit() -> IrError {
349 IrError::IslandStateLimitExceeded {
350 limit: MAX_ISLAND_STATE_BYTES,
351 }
352}