use std::ops::Range;
use powerio_core::{Error, Source, SourceBuffer, SourceSpan};
use crate::diagnostics::codes;
const MAX_BYTES: usize = 64 << 20;
const MAX_TOKENS: usize = 1 << 20;
const MAX_TOKEN_BYTES: usize = 1 << 20;
const MAX_NESTING: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TokenKind {
Word,
Quoted,
OpenBrace,
CloseBrace,
Semicolon,
Directive,
}
#[derive(Clone, Debug)]
pub struct Token {
kind: TokenKind,
buffer: SourceBuffer,
range: Range<usize>,
}
impl Token {
#[must_use]
pub const fn kind(&self) -> TokenKind {
self.kind
}
#[must_use]
#[expect(
clippy::missing_panics_doc,
reason = "private token fields are created only from validated UTF-8 ranges"
)]
pub fn text(&self) -> &str {
std::str::from_utf8(&self.buffer.bytes()[self.range.clone()])
.expect("GLM token boundaries remain valid UTF-8")
}
#[must_use]
pub fn span(&self) -> SourceSpan {
span(&self.buffer, self.range.clone())
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Statement {
Simple {
tokens: Vec<Token>,
span: SourceSpan,
},
Block {
header: Vec<Token>,
body: Vec<Statement>,
span: SourceSpan,
},
Directive(Token),
}
#[derive(Clone, Debug)]
pub struct RawGlm {
pub source: Source,
pub statements: Vec<Statement>,
}
pub fn parse_raw_source(source: Source) -> Result<RawGlm, Error> {
let result = (|| {
let buffer = source.primary_buffer()?;
let tokens = lex(&buffer)?;
let mut parser = Parser {
buffer: &buffer,
tokens: tokens.into_iter().peekable(),
};
parser.statements(0, None)
})();
match result {
Ok(statements) => Ok(RawGlm { source, statements }),
Err(error) => Err(error.with_source(source)),
}
}
fn span(buffer: &SourceBuffer, range: Range<usize>) -> SourceSpan {
SourceSpan::new(buffer.id().clone(), range.start as u64, range.end as u64)
.expect("lexer ranges are ordered")
}
fn malformed(buffer: &SourceBuffer, range: Range<usize>, message: &str) -> Error {
Error::new(&codes::PARSE_GLM_SYNTAX, message).with_span(span(buffer, range))
}
fn limited(buffer: &SourceBuffer, range: Range<usize>, message: &str) -> Error {
Error::new(&codes::PARSE_GLM_LIMIT_EXCEEDED, message).with_span(span(buffer, range))
}
fn lex(buffer: &SourceBuffer) -> Result<Vec<Token>, Error> {
let bytes = buffer.bytes();
if bytes.len() > MAX_BYTES {
return Err(limited(buffer, 0..bytes.len(), "GLM input exceeds 64 MiB"));
}
std::str::from_utf8(bytes).map_err(|error| {
let start = error.valid_up_to();
malformed(
buffer,
start..start + error.error_len().unwrap_or(1),
"GLM source is not UTF-8",
)
})?;
if let Some(offset) = bytes.iter().position(|byte| *byte == 0) {
return Err(malformed(
buffer,
offset..offset + 1,
"GLM source contains a NUL byte",
));
}
let mut tokens = Vec::new();
let mut cursor = if buffer.has_utf8_bom() { 3 } else { 0 };
let mut line_start = true;
while cursor < bytes.len() {
let start = cursor;
if bytes[cursor].is_ascii_whitespace() {
if matches!(bytes[cursor], b'\n' | b'\r') {
line_start = true;
}
cursor += 1;
continue;
}
if bytes[cursor..].starts_with(b"//") {
cursor += 2;
while cursor < bytes.len() && !matches!(bytes[cursor], b'\n' | b'\r') {
cursor += 1;
}
continue;
}
if bytes[cursor..].starts_with(b"/*") {
cursor += 2;
while cursor < bytes.len() && !bytes[cursor..].starts_with(b"*/") {
if matches!(bytes[cursor], b'\n' | b'\r') {
line_start = true;
}
cursor += 1;
}
if cursor == bytes.len() {
return Err(malformed(
buffer,
start..cursor,
"unterminated GLM block comment",
));
}
cursor += 2;
continue;
}
let kind = match bytes[cursor] {
b'{' => {
cursor += 1;
TokenKind::OpenBrace
}
b'}' => {
cursor += 1;
TokenKind::CloseBrace
}
b';' => {
cursor += 1;
TokenKind::Semicolon
}
b'#' if line_start => {
while cursor < bytes.len() && !matches!(bytes[cursor], b'\n' | b'\r') {
cursor += 1;
}
TokenKind::Directive
}
b'\'' | b'"' => {
cursor = quoted_end(buffer, start)?;
TokenKind::Quoted
}
_ => {
while cursor < bytes.len() {
if bytes[cursor..].starts_with(b"${") {
cursor += 2;
while cursor < bytes.len() && bytes[cursor] != b'}' {
cursor += 1;
}
if cursor == bytes.len() {
return Err(malformed(
buffer,
start..cursor,
"unterminated GLM variable reference",
));
}
cursor += 1;
} else if bytes[cursor].is_ascii_whitespace()
|| matches!(bytes[cursor], b'{' | b'}' | b';' | b'\'' | b'"')
|| bytes[cursor..].starts_with(b"//")
|| bytes[cursor..].starts_with(b"/*")
{
break;
} else {
cursor += 1;
}
}
TokenKind::Word
}
};
line_start = false;
if cursor - start > MAX_TOKEN_BYTES || tokens.len() == MAX_TOKENS {
return Err(limited(
buffer,
start..cursor,
"GLM token size or count exceeds its limit",
));
}
tokens.push(Token {
kind,
buffer: buffer.clone(),
range: start..cursor,
});
}
Ok(tokens)
}
fn quoted_end(buffer: &SourceBuffer, start: usize) -> Result<usize, Error> {
let bytes = buffer.bytes();
let quote = bytes[start];
let mut cursor = start + 1;
while cursor < bytes.len() {
let byte = bytes[cursor];
cursor += 1;
if byte == quote {
return Ok(cursor);
}
if byte == b'\\' && cursor < bytes.len() {
cursor += 1;
}
}
Err(malformed(
buffer,
start..bytes.len(),
"unterminated GLM quoted token",
))
}
struct Parser<'a> {
buffer: &'a SourceBuffer,
tokens: std::iter::Peekable<std::vec::IntoIter<Token>>,
}
impl Parser<'_> {
fn statements(
&mut self,
depth: usize,
opening: Option<usize>,
) -> Result<Vec<Statement>, Error> {
let mut output = Vec::new();
let mut header = Vec::<Token>::new();
if opening.is_some()
&& self
.tokens
.peek()
.is_some_and(|t| t.kind == TokenKind::CloseBrace)
{
return Ok(output);
}
while let Some(token) = self.tokens.next() {
match token.kind {
TokenKind::Directive => {
if !header.is_empty() {
return Err(malformed(
self.buffer,
token.range,
"directive interrupts a GLM statement",
));
}
output.push(Statement::Directive(token));
}
TokenKind::Word | TokenKind::Quoted => header.push(token),
TokenKind::Semicolon => {
if let Some(first) = header.first() {
output.push(Statement::Simple {
span: span(self.buffer, first.range.start..token.range.end),
tokens: std::mem::take(&mut header),
});
}
}
TokenKind::OpenBrace => {
output.push(self.block(std::mem::take(&mut header), token, depth)?);
}
TokenKind::CloseBrace => {
return Err(malformed(
self.buffer,
token.range,
"unexpected GLM closing brace",
));
}
}
if opening.is_some()
&& self
.tokens
.peek()
.is_some_and(|t| t.kind == TokenKind::CloseBrace)
{
if let Some(first) = header.first() {
return Err(malformed(
self.buffer,
first.range.clone(),
"GLM property is missing its semicolon",
));
}
return Ok(output);
}
}
if let Some(start) = opening {
return Err(malformed(
self.buffer,
start..self.buffer.bytes().len(),
"unterminated GLM block",
));
}
if let Some(first) = header.first() {
return Err(malformed(
self.buffer,
first.range.clone(),
"GLM statement is missing its semicolon or block",
));
}
Ok(output)
}
fn block(
&mut self,
header: Vec<Token>,
opening: Token,
depth: usize,
) -> Result<Statement, Error> {
let Some(first) = header.first() else {
return Err(malformed(
self.buffer,
opening.range,
"GLM block has no declaration",
));
};
if depth == MAX_NESTING {
return Err(limited(
self.buffer,
opening.range,
"GLM blocks exceed 64 nesting levels",
));
}
let start = first.range.start;
let body = self.statements(depth + 1, Some(opening.range.start))?;
let closing = self
.tokens
.next()
.expect("a successful nested parse leaves a brace");
let end = if self
.tokens
.peek()
.is_some_and(|t| t.kind == TokenKind::Semicolon)
{
self.tokens.next().expect("peeked token exists").range.end
} else {
closing.range.end
};
Ok(Statement::Block {
header,
body,
span: span(self.buffer, start..end),
})
}
}