1use std::ops::Range;
2
3use powerio_core::{Error, Source, SourceBuffer, SourceSpan};
4
5use crate::diagnostics::codes;
6
7const MAX_BYTES: usize = 64 << 20;
8const MAX_TOKENS: usize = 1 << 20;
9const MAX_TOKEN_BYTES: usize = 1 << 20;
10const MAX_NESTING: usize = 64;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum TokenKind {
16 Word,
17 Quoted,
18 OpenBrace,
19 CloseBrace,
20 Semicolon,
21 Directive,
22}
23
24#[derive(Clone, Debug)]
26pub struct Token {
27 kind: TokenKind,
28 buffer: SourceBuffer,
29 range: Range<usize>,
30}
31
32impl Token {
33 #[must_use]
34 pub const fn kind(&self) -> TokenKind {
35 self.kind
36 }
37
38 #[must_use]
40 #[expect(
41 clippy::missing_panics_doc,
42 reason = "private token fields are created only from validated UTF-8 ranges"
43 )]
44 pub fn text(&self) -> &str {
45 std::str::from_utf8(&self.buffer.bytes()[self.range.clone()])
48 .expect("GLM token boundaries remain valid UTF-8")
49 }
50
51 #[must_use]
52 pub fn span(&self) -> SourceSpan {
53 span(&self.buffer, self.range.clone())
54 }
55}
56
57#[derive(Clone, Debug)]
59#[non_exhaustive]
60pub enum Statement {
61 Simple {
63 tokens: Vec<Token>,
64 span: SourceSpan,
65 },
66 Block {
68 header: Vec<Token>,
69 body: Vec<Statement>,
70 span: SourceSpan,
71 },
72 Directive(Token),
74}
75
76#[derive(Clone, Debug)]
79pub struct RawGlm {
80 pub source: Source,
81 pub statements: Vec<Statement>,
82}
83
84pub fn parse_raw_source(source: Source) -> Result<RawGlm, Error> {
89 let result = (|| {
90 let buffer = source.primary_buffer()?;
91 let tokens = lex(&buffer)?;
92 let mut parser = Parser {
93 buffer: &buffer,
94 tokens: tokens.into_iter().peekable(),
95 };
96 parser.statements(0, None)
97 })();
98 match result {
99 Ok(statements) => Ok(RawGlm { source, statements }),
100 Err(error) => Err(error.with_source(source)),
101 }
102}
103
104fn span(buffer: &SourceBuffer, range: Range<usize>) -> SourceSpan {
105 SourceSpan::new(buffer.id().clone(), range.start as u64, range.end as u64)
106 .expect("lexer ranges are ordered")
107}
108
109fn malformed(buffer: &SourceBuffer, range: Range<usize>, message: &str) -> Error {
110 Error::new(&codes::PARSE_GLM_SYNTAX, message).with_span(span(buffer, range))
111}
112
113fn limited(buffer: &SourceBuffer, range: Range<usize>, message: &str) -> Error {
114 Error::new(&codes::PARSE_GLM_LIMIT_EXCEEDED, message).with_span(span(buffer, range))
115}
116
117fn lex(buffer: &SourceBuffer) -> Result<Vec<Token>, Error> {
118 let bytes = buffer.bytes();
119 if bytes.len() > MAX_BYTES {
120 return Err(limited(buffer, 0..bytes.len(), "GLM input exceeds 64 MiB"));
121 }
122 std::str::from_utf8(bytes).map_err(|error| {
123 let start = error.valid_up_to();
124 malformed(
125 buffer,
126 start..start + error.error_len().unwrap_or(1),
127 "GLM source is not UTF-8",
128 )
129 })?;
130 if let Some(offset) = bytes.iter().position(|byte| *byte == 0) {
131 return Err(malformed(
132 buffer,
133 offset..offset + 1,
134 "GLM source contains a NUL byte",
135 ));
136 }
137 let mut tokens = Vec::new();
138 let mut cursor = if buffer.has_utf8_bom() { 3 } else { 0 };
139 let mut line_start = true;
140 while cursor < bytes.len() {
141 let start = cursor;
142 if bytes[cursor].is_ascii_whitespace() {
143 if matches!(bytes[cursor], b'\n' | b'\r') {
144 line_start = true;
145 }
146 cursor += 1;
147 continue;
148 }
149 if bytes[cursor..].starts_with(b"//") {
150 cursor += 2;
151 while cursor < bytes.len() && !matches!(bytes[cursor], b'\n' | b'\r') {
152 cursor += 1;
153 }
154 continue;
155 }
156 if bytes[cursor..].starts_with(b"/*") {
157 cursor += 2;
158 while cursor < bytes.len() && !bytes[cursor..].starts_with(b"*/") {
159 if matches!(bytes[cursor], b'\n' | b'\r') {
160 line_start = true;
161 }
162 cursor += 1;
163 }
164 if cursor == bytes.len() {
165 return Err(malformed(
166 buffer,
167 start..cursor,
168 "unterminated GLM block comment",
169 ));
170 }
171 cursor += 2;
172 continue;
173 }
174 let kind = match bytes[cursor] {
175 b'{' => {
176 cursor += 1;
177 TokenKind::OpenBrace
178 }
179 b'}' => {
180 cursor += 1;
181 TokenKind::CloseBrace
182 }
183 b';' => {
184 cursor += 1;
185 TokenKind::Semicolon
186 }
187 b'#' if line_start => {
188 while cursor < bytes.len() && !matches!(bytes[cursor], b'\n' | b'\r') {
189 cursor += 1;
190 }
191 TokenKind::Directive
192 }
193 b'\'' | b'"' => {
194 cursor = quoted_end(buffer, start)?;
195 TokenKind::Quoted
196 }
197 _ => {
198 while cursor < bytes.len() {
199 if bytes[cursor..].starts_with(b"${") {
200 cursor += 2;
201 while cursor < bytes.len() && bytes[cursor] != b'}' {
202 cursor += 1;
203 }
204 if cursor == bytes.len() {
205 return Err(malformed(
206 buffer,
207 start..cursor,
208 "unterminated GLM variable reference",
209 ));
210 }
211 cursor += 1;
212 } else if bytes[cursor].is_ascii_whitespace()
213 || matches!(bytes[cursor], b'{' | b'}' | b';' | b'\'' | b'"')
214 || bytes[cursor..].starts_with(b"//")
215 || bytes[cursor..].starts_with(b"/*")
216 {
217 break;
218 } else {
219 cursor += 1;
220 }
221 }
222 TokenKind::Word
223 }
224 };
225 line_start = false;
226 if cursor - start > MAX_TOKEN_BYTES || tokens.len() == MAX_TOKENS {
227 return Err(limited(
228 buffer,
229 start..cursor,
230 "GLM token size or count exceeds its limit",
231 ));
232 }
233 tokens.push(Token {
234 kind,
235 buffer: buffer.clone(),
236 range: start..cursor,
237 });
238 }
239 Ok(tokens)
240}
241
242fn quoted_end(buffer: &SourceBuffer, start: usize) -> Result<usize, Error> {
243 let bytes = buffer.bytes();
244 let quote = bytes[start];
245 let mut cursor = start + 1;
246 while cursor < bytes.len() {
247 let byte = bytes[cursor];
248 cursor += 1;
249 if byte == quote {
250 return Ok(cursor);
251 }
252 if byte == b'\\' && cursor < bytes.len() {
253 cursor += 1;
254 }
255 }
256 Err(malformed(
257 buffer,
258 start..bytes.len(),
259 "unterminated GLM quoted token",
260 ))
261}
262
263struct Parser<'a> {
264 buffer: &'a SourceBuffer,
265 tokens: std::iter::Peekable<std::vec::IntoIter<Token>>,
266}
267
268impl Parser<'_> {
269 fn statements(
270 &mut self,
271 depth: usize,
272 opening: Option<usize>,
273 ) -> Result<Vec<Statement>, Error> {
274 let mut output = Vec::new();
275 let mut header = Vec::<Token>::new();
276 if opening.is_some()
277 && self
278 .tokens
279 .peek()
280 .is_some_and(|t| t.kind == TokenKind::CloseBrace)
281 {
282 return Ok(output);
283 }
284 while let Some(token) = self.tokens.next() {
285 match token.kind {
286 TokenKind::Directive => {
287 if !header.is_empty() {
288 return Err(malformed(
289 self.buffer,
290 token.range,
291 "directive interrupts a GLM statement",
292 ));
293 }
294 output.push(Statement::Directive(token));
295 }
296 TokenKind::Word | TokenKind::Quoted => header.push(token),
297 TokenKind::Semicolon => {
298 if let Some(first) = header.first() {
299 output.push(Statement::Simple {
300 span: span(self.buffer, first.range.start..token.range.end),
301 tokens: std::mem::take(&mut header),
302 });
303 }
304 }
305 TokenKind::OpenBrace => {
306 output.push(self.block(std::mem::take(&mut header), token, depth)?);
307 }
308 TokenKind::CloseBrace => {
309 return Err(malformed(
310 self.buffer,
311 token.range,
312 "unexpected GLM closing brace",
313 ));
314 }
315 }
316 if opening.is_some()
317 && self
318 .tokens
319 .peek()
320 .is_some_and(|t| t.kind == TokenKind::CloseBrace)
321 {
322 if let Some(first) = header.first() {
323 return Err(malformed(
324 self.buffer,
325 first.range.clone(),
326 "GLM property is missing its semicolon",
327 ));
328 }
329 return Ok(output);
330 }
331 }
332 if let Some(start) = opening {
333 return Err(malformed(
334 self.buffer,
335 start..self.buffer.bytes().len(),
336 "unterminated GLM block",
337 ));
338 }
339 if let Some(first) = header.first() {
340 return Err(malformed(
341 self.buffer,
342 first.range.clone(),
343 "GLM statement is missing its semicolon or block",
344 ));
345 }
346 Ok(output)
347 }
348
349 fn block(
350 &mut self,
351 header: Vec<Token>,
352 opening: Token,
353 depth: usize,
354 ) -> Result<Statement, Error> {
355 let Some(first) = header.first() else {
356 return Err(malformed(
357 self.buffer,
358 opening.range,
359 "GLM block has no declaration",
360 ));
361 };
362 if depth == MAX_NESTING {
363 return Err(limited(
364 self.buffer,
365 opening.range,
366 "GLM blocks exceed 64 nesting levels",
367 ));
368 }
369 let start = first.range.start;
370 let body = self.statements(depth + 1, Some(opening.range.start))?;
371 let closing = self
373 .tokens
374 .next()
375 .expect("a successful nested parse leaves a brace");
376 let end = if self
377 .tokens
378 .peek()
379 .is_some_and(|t| t.kind == TokenKind::Semicolon)
380 {
381 self.tokens.next().expect("peeked token exists").range.end
382 } else {
383 closing.range.end
384 };
385 Ok(Statement::Block {
386 header,
387 body,
388 span: span(self.buffer, start..end),
389 })
390 }
391}