1use std::collections::BTreeMap;
2
3use powerio_core::{Error, SourceSpan};
4
5use super::{GlmProject, Statement, Token, TokenKind};
6use crate::diagnostics::codes;
7
8const MAX_OBJECTS: usize = 1 << 20;
9const MAX_PROPERTIES: usize = 1 << 20;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
14pub struct ObjectKey(usize);
15
16impl ObjectKey {
17 #[must_use]
18 pub const fn ordinal(self) -> usize {
19 self.0
20 }
21}
22
23#[derive(Clone, Debug)]
25#[non_exhaustive]
26pub enum GlmPropertyValue<'a> {
27 Tokens(&'a [Token]),
28 InlineObject(ObjectKey),
29}
30
31#[derive(Clone, Debug)]
32pub struct GlmProperty<'a> {
33 pub name: &'a str,
34 pub value: GlmPropertyValue<'a>,
35 pub span: &'a SourceSpan,
36}
37
38#[derive(Debug)]
41pub struct GlmObject<'a> {
42 key: ObjectKey,
43 class_name: &'a str,
44 explicit_id: Option<u32>,
45 name: Option<String>,
46 span: &'a SourceSpan,
47 nested_parent: Option<ObjectKey>,
48 properties: Vec<GlmProperty<'a>>,
49 other_statements: Vec<&'a Statement>,
50}
51
52impl<'a> GlmObject<'a> {
53 #[must_use]
54 pub const fn key(&self) -> ObjectKey {
55 self.key
56 }
57 #[must_use]
58 pub const fn class_name(&self) -> &'a str {
59 self.class_name
60 }
61 #[must_use]
62 pub const fn explicit_id(&self) -> Option<u32> {
63 self.explicit_id
64 }
65 #[must_use]
66 pub fn name(&self) -> Option<&str> {
67 self.name.as_deref()
68 }
69 #[must_use]
70 pub const fn span(&self) -> &'a SourceSpan {
71 self.span
72 }
73 #[must_use]
74 pub fn properties(&self) -> &[GlmProperty<'a>] {
75 &self.properties
76 }
77 #[must_use]
79 pub fn other_statements(&self) -> &[&'a Statement] {
80 &self.other_statements
81 }
82}
83
84#[derive(Debug)]
90pub struct ObjectIndex<'a> {
91 project: &'a GlmProject,
92 objects: Vec<GlmObject<'a>>,
93 names: BTreeMap<String, ObjectKey>,
94 ids: BTreeMap<u32, ObjectKey>,
95 parents: Vec<Option<ObjectKey>>,
96 property_count: usize,
97}
98
99impl GlmProject {
100 pub fn index_objects(&self) -> Result<ObjectIndex<'_>, Error> {
101 let mut index = ObjectIndex {
102 project: self,
103 objects: Vec::new(),
104 names: BTreeMap::new(),
105 ids: BTreeMap::new(),
106 parents: Vec::new(),
107 property_count: 0,
108 };
109 for statement in self.statements() {
110 if let Statement::Block { header, body, span } = statement {
111 match header.first().map(Token::text) {
112 Some("object") => {
113 index.declaration(header, body, span, None)?;
114 }
115 Some("namespace") => {
116 return Err(self.fail(
117 span,
118 "GLM namespaces require explicit qualification before object indexing",
119 ));
120 }
121 _ => {}
122 }
123 }
124 }
125 index.resolve_parents()?;
126 Ok(index)
127 }
128}
129
130impl<'a> ObjectIndex<'a> {
131 pub(super) fn fail(&self, span: &SourceSpan, message: impl Into<String>) -> Error {
132 self.project.fail(span, message)
133 }
134
135 #[must_use]
136 pub fn objects(&self) -> &[GlmObject<'a>] {
137 &self.objects
138 }
139
140 #[must_use]
141 pub fn get(&self, key: ObjectKey) -> Option<&GlmObject<'a>> {
142 self.objects.get(key.0)
143 }
144
145 #[must_use]
146 pub fn parent(&self, key: ObjectKey) -> Option<ObjectKey> {
147 self.parents.get(key.0).copied().flatten()
148 }
149
150 pub fn resolve_reference(&self, reference: &str) -> Result<Option<ObjectKey>, Error> {
154 if reference == "root" {
155 return Ok(None);
156 }
157 let found = if let Some((class, numeric)) = reference.split_once(':') {
158 let id = numeric.parse::<u32>().map_err(|_| {
159 self.reference_error(reference, "unsupported symbolic or relative reference")
160 })?;
161 let key = self.ids.get(&id).copied().ok_or_else(|| {
162 self.reference_error(reference, "explicit object ID does not exist")
163 })?;
164 let actual = self.objects[key.0]
165 .class_name
166 .rsplit('.')
167 .next()
168 .unwrap_or("");
169 if class != "id" && class != actual {
170 return Err(
171 self.reference_error(reference, "explicit object ID has a different class")
172 );
173 }
174 Some(key)
175 } else {
176 self.names.get(reference).copied()
177 };
178 found
179 .map(Some)
180 .ok_or_else(|| self.reference_error(reference, "object name does not exist"))
181 }
182
183 pub fn resolve_property(&self, key: ObjectKey, name: &str) -> Result<Option<ObjectKey>, Error> {
186 let object = self
187 .objects
188 .get(key.0)
189 .ok_or_else(|| self.reference_error(name, "owner key does not belong to this index"))?;
190 let mut properties = object.properties.iter().filter(|p| p.name == name);
191 let Some(property) = properties.next() else {
192 return Ok(None);
193 };
194 if properties.next().is_some() {
195 return Err(self.project.fail(property.span, format!("repeated object reference property {name} requires deferred-assignment ordering")));
196 }
197 match &property.value {
198 GlmPropertyValue::InlineObject(key) => Ok(Some(*key)),
199 GlmPropertyValue::Tokens(tokens) => {
200 let reference =
201 literal(tokens).map_err(|message| self.project.fail(property.span, message))?;
202 self.resolve_reference(&reference)
203 .map_err(|error| self.project.fail(property.span, error.to_string()))
204 }
205 }
206 }
207
208 fn reference_error(&self, reference: &str, message: &str) -> Error {
209 Error::new(&codes::PARSE_GLM_OBJECT, format!("{message}: {reference}"))
210 .with_source(self.project.source().clone())
211 }
212
213 fn declaration(
214 &mut self,
215 header: &'a [Token],
216 body: &'a [Statement],
217 span: &'a SourceSpan,
218 parent: Option<ObjectKey>,
219 ) -> Result<Option<ObjectKey>, Error> {
220 if header.len() != 2 || header[0].text() != "object" || header[1].kind() != TokenKind::Word
221 {
222 return Err(self
223 .project
224 .fail(span, "expected object class or object class:numeric_id"));
225 }
226 let (class, first, count) =
227 declaration_ids(header[1].text()).map_err(|m| self.project.fail(span, m))?;
228 if count > MAX_OBJECTS - self.objects.len() {
229 return Err(self
230 .project
231 .fail(span, "expanded GLM object count exceeds its limit"));
232 }
233 let mut last = None;
234 for ordinal in 0..count {
235 if self.objects.len() == MAX_OBJECTS {
236 return Err(self
237 .project
238 .fail(span, "expanded GLM object count exceeds its limit"));
239 }
240 let id = first.map(|start| start + ordinal as u32);
241 let key = ObjectKey(self.objects.len());
242 if let Some(id) = id
243 && self.ids.insert(id, key).is_some()
244 {
245 return Err(self.project.fail(
246 span,
247 "duplicate GLM explicit ID; numeric keys are global across classes",
248 ));
249 }
250 self.objects.push(GlmObject {
251 key,
252 class_name: class,
253 explicit_id: id,
254 name: None,
255 span,
256 nested_parent: parent,
257 properties: Vec::new(),
258 other_statements: Vec::new(),
259 });
260 last = Some(key);
261 self.body(key, body)?;
262 }
263 Ok(last)
264 }
265
266 fn body(&mut self, owner: ObjectKey, body: &'a [Statement]) -> Result<(), Error> {
267 for statement in body {
268 match statement {
269 Statement::Simple { tokens, span }
270 if tokens.len() < 2
271 && tokens
272 .first()
273 .is_some_and(|t| matches!(t.text(), "name" | "parent" | "root")) =>
274 {
275 return Err(self
276 .project
277 .fail(span, "GLM identity or parent property is missing its value"));
278 }
279 Statement::Simple { tokens, span } if tokens.len() >= 2 => {
280 let name = tokens[0].text();
281 if name == "name" {
282 let value =
283 literal(&tokens[1..]).map_err(|m| self.project.fail(span, m))?;
284 if self.objects[owner.0].name.is_some() || self.names.contains_key(&value) {
285 return Err(self
286 .project
287 .fail(span, "duplicate or reassigned GLM object name"));
288 }
289 self.names.insert(value.clone(), owner);
290 self.objects[owner.0].name = Some(value);
291 }
292 self.property(
293 owner,
294 GlmProperty {
295 name,
296 value: GlmPropertyValue::Tokens(&tokens[1..]),
297 span,
298 },
299 )?;
300 }
301 Statement::Block { header, body, span }
302 if header.first().is_some_and(|t| t.text() == "object") =>
303 {
304 self.declaration(header, body, span, Some(owner))?;
305 }
306 Statement::Block { header, body, span }
307 if header.get(1).is_some_and(|t| t.text() == "object") =>
308 {
309 let target = self
310 .declaration(&header[1..], body, span, None)?
311 .ok_or_else(|| self.project.fail(span, "inline object count is zero"))?;
312 self.property(
313 owner,
314 GlmProperty {
315 name: header[0].text(),
316 value: GlmPropertyValue::InlineObject(target),
317 span,
318 },
319 )?;
320 }
321 _ => self.objects[owner.0].other_statements.push(statement),
322 }
323 }
324 Ok(())
325 }
326
327 fn property(&mut self, owner: ObjectKey, property: GlmProperty<'a>) -> Result<(), Error> {
328 if self.property_count == MAX_PROPERTIES {
329 return Err(self.project.fail(
330 property.span,
331 "expanded GLM property count exceeds its limit",
332 ));
333 }
334 self.property_count += 1;
335 self.objects[owner.0].properties.push(property);
336 Ok(())
337 }
338
339 fn resolve_parents(&mut self) -> Result<(), Error> {
340 for object in &self.objects {
341 let explicit = object.properties.iter().any(|p| p.name == "parent");
342 let root = object.properties.iter().any(|p| p.name == "root");
343 if explicit && root {
344 return Err(self.project.fail(
345 object.span,
346 "combined root/parent assignments require deferred-assignment ordering",
347 ));
348 }
349 let parent = if explicit {
350 self.resolve_property(object.key, "parent")?
351 } else if root {
352 None
353 } else {
354 object.nested_parent
355 };
356 self.parents.push(parent);
357 }
358 let mut state = vec![0u8; self.objects.len()];
359 for start in 0..state.len() {
360 if state[start] != 0 {
361 continue;
362 }
363 let mut next = Some(ObjectKey(start));
364 while let Some(key) = next {
365 match state[key.0] {
366 2 => break,
367 1 => {
368 return Err(self.project.fail(
369 self.objects[key.0].span,
370 "GLM parent references form a cycle",
371 ));
372 }
373 _ => {
374 state[key.0] = 1;
375 next = self.parents[key.0];
376 }
377 }
378 }
379 next = Some(ObjectKey(start));
380 while let Some(key) = next {
381 if state[key.0] == 2 {
382 break;
383 }
384 state[key.0] = 2;
385 next = self.parents[key.0];
386 }
387 }
388 Ok(())
389 }
390}
391
392fn declaration_ids(text: &str) -> Result<(&str, Option<u32>, usize), &'static str> {
393 let (class, ids) = text
394 .split_once(':')
395 .map_or((text, None), |(c, ids)| (c, Some(ids)));
396 if class.is_empty()
397 || !class
398 .bytes()
399 .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'.'))
400 {
401 return Err("invalid GLM class spelling");
402 }
403 let Some(ids) = ids else {
404 return Ok((class, None, 1));
405 };
406 if let Some(count) = ids.strip_prefix("..") {
407 let count = count
408 .parse::<usize>()
409 .map_err(|_| "invalid GLM object count")?;
410 return Ok((class, None, count));
411 }
412 if let Some((first, last)) = ids.split_once("..") {
413 let first = first
414 .parse::<u32>()
415 .map_err(|_| "invalid first GLM object ID")?;
416 let last = last
417 .parse::<u32>()
418 .map_err(|_| "invalid last GLM object ID")?;
419 let count = u64::from(last)
420 .checked_sub(u64::from(first))
421 .and_then(|n| usize::try_from(n + 1).ok())
422 .ok_or("invalid or oversized GLM object range")?;
423 return Ok((class, Some(first), count));
424 }
425 Ok((
426 class,
427 Some(ids.parse().map_err(|_| "invalid GLM object ID")?),
428 1,
429 ))
430}
431
432fn literal(tokens: &[Token]) -> Result<String, &'static str> {
433 let [token] = tokens else {
434 return Err("object names/references require one literal value");
435 };
436 let value = token.text();
437 let value = if token.kind() == TokenKind::Quoted {
438 value
439 .strip_prefix('"')
440 .and_then(|v| v.strip_suffix('"'))
441 .ok_or("object string literals require double quotes")?
442 } else {
443 value
444 };
445 if value.is_empty() || value.contains(['\\', '$', '\n', '\r']) {
446 return Err("object names/references require a nonempty resolved literal without escapes");
447 }
448 Ok(value.to_owned())
449}