wgsl-parser 0.5.0

A zero-copy recursive-descent parser for WebGPU shading language
Documentation
use std::ops::{Deref, DerefMut};

use gramatika::{
	Lexer as _, Parse, ParseStreamer, PartialLexer, Span, Spanned, SpannedError, Substr,
	Token as _, TokenStream,
};

use crate::{expr::Expr, parser::ErrorRecoveringParseStream, utils, Text, Token};

#[cfg(test)]
use crate::utils::WithErrors;

use super::{
	chunk::{ChunkKind, Lexer as ChunkLexer},
	directive::{DirectiveKind, DirectiveLexer},
	Chunk, Directive,
};

#[derive(DebugLisp, Default)]
pub struct PreParsed {
	pub sections: Vec<Section>,
}

#[derive(DebugLisp)]
pub enum Section {
	Text(Text),
	Branch(Box<Branch>),
	Definition(Text),
}

#[derive(DebugLisp)]
pub struct Branch {
	pub directive: Directive,
	pub condition: Option<Expr>,
	pub body: Vec<Section>,
	pub else_: Option<Box<Branch>>,
}

impl Deref for PreParsed {
	type Target = Vec<Section>;

	fn deref(&self) -> &Self::Target {
		&self.sections
	}
}

impl DerefMut for PreParsed {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.sections
	}
}

pub struct ParseStream {
	inner: gramatika::ParseStream<Chunk, ChunkLexer>,
	errors: Vec<SpannedError>,
}

impl ParseStream {
	pub fn into_inner(self) -> (Substr, Vec<SpannedError>) {
		(self.inner.source(), self.errors)
	}

	pub fn source(&self) -> Substr {
		self.inner.source()
	}
}

impl<S> From<S> for ParseStream
where S: Into<Substr>
{
	fn from(input: S) -> Self {
		let input = input.into();
		Self {
			inner: gramatika::ParseStream::from(input),
			errors: vec![],
		}
	}
}

#[cfg(test)]
impl WithErrors for ParseStream {
	type Output = SpannedError;

	fn get(&self) -> &[Self::Output] {
		&self.errors
	}
}

impl ErrorRecoveringParseStream for ParseStream {
	fn push_error(&mut self, error: SpannedError) {
		self.errors.push(error);
	}
}

impl ParseStreamer for ParseStream {
	type Token = Chunk;

	fn is_empty(&mut self) -> bool {
		self.inner.is_empty()
	}
	fn peek(&mut self) -> Option<&Self::Token> {
		self.inner.peek()
	}
	fn prev(&mut self) -> Option<&Self::Token> {
		self.inner.prev()
	}
	fn check_kind(&mut self, kind: <Self::Token as gramatika::Token>::Kind) -> bool {
		self.inner.check_kind(kind)
	}
	fn check(&mut self, compare: Self::Token) -> bool {
		self.inner.check(compare)
	}
	fn consume(&mut self, compare: Self::Token) -> gramatika::Result<Self::Token> {
		self.inner.consume(compare)
	}
	fn consume_kind(
		&mut self,
		kind: <Self::Token as gramatika::Token>::Kind,
	) -> gramatika::Result<Self::Token> {
		self.inner.consume_kind(kind)
	}
	fn consume_as(
		&mut self,
		kind: <Self::Token as gramatika::Token>::Kind,
		convert: gramatika::TokenCtor<Self::Token>,
	) -> gramatika::Result<Self::Token> {
		self.inner.consume_as(kind, convert)
	}
	fn upgrade_last(
		&mut self,
		kind: <Self::Token as gramatika::Token>::Kind,
		convert: gramatika::TokenCtor<Self::Token>,
	) -> gramatika::Result<Self::Token> {
		self.inner.upgrade_last(kind, convert)
	}
	fn upgrade(
		&mut self,
		token: Self::Token,
		convert: gramatika::TokenCtor<Self::Token>,
	) -> gramatika::Result<Self::Token> {
		self.inner.upgrade(token, convert)
	}
	fn discard(&mut self) {
		self.inner.discard()
	}
}

impl Iterator for ParseStream {
	type Item = Chunk;

	fn next(&mut self) -> Option<Self::Item> {
		self.inner.next()
	}
}

impl Parse for PreParsed {
	type Stream = ParseStream;

	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
		let sections = input.parse_seq(|input| !input.is_empty());
		Ok(Self { sections })
	}
}

impl Parse for Section {
	type Stream = ParseStream;

	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
		match input.peek().cloned() {
			Some(Chunk::Definition(_, _)) => {
				let (substr, span) = input.next().unwrap().as_inner();
				Ok(Section::Definition(Text(substr, span)))
			}
			Some(Chunk::Line(_, _)) => Ok(Section::Text(parse_text(input)?)),
			Some(Chunk::BranchStart(_, _)) => Ok(Section::Branch(Box::new(input.parse()?))),
			Some(other) => {
				let span = other.span();
				Err(SpannedError {
					message: "Unexpected input".into(),
					source: input.source(),
					span: Some(span),
				})
			}
			None => Err(SpannedError {
				message: "Unexpected end of input".into(),
				source: input.source(),
				span: input.prev().map(|token| token.span()),
			}),
		}
	}
}

fn parse_text(input: &mut ParseStream) -> gramatika::Result<Text> {
	let (mut text, mut span) = input.consume_kind(ChunkKind::Line)?.as_inner();

	while let Some(Chunk::Line(next_text, next_span)) = input.peek() {
		text = utils::join_substrs(&text, next_text);
		span = span.through(*next_span);
		input.next();
	}

	Ok(Text(text, span))
}

impl Parse for Branch {
	type Stream = ParseStream;

	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
		let (line, line_span) = match input.next() {
			Some(Chunk::BranchStart(line, span) | Chunk::BranchFork(line, span)) => (line, span),
			Some(other) => {
				return Err(SpannedError {
					message: "Expected `#if ...`, `#ifdef ...` `#ifndef ...` or `#else ...`".into(),
					source: input.source(),
					span: Some(other.span()),
				});
			}
			None => {
				return Err(SpannedError {
					message: "Unexpected end of input".into(),
					source: input.source(),
					span: input.prev().map(|token| token.span()),
				});
			}
		};

		let mut lexer = DirectiveLexer::from_remaining(line.clone(), line_span.start);
		let directive = lexer.scan_token().ok_or_else(|| SpannedError {
			message: "Unexpected end of line".into(),
			source: lexer.source(),
			span: Some(Span {
				start: line_span.end,
				end: line_span.end,
			}),
		})?;

		let (remaining, position) = lexer.stop();
		let condition_lexer = TokenStream::<Token>::from_remaining(remaining, position);
		let condition = crate::ParseStream::new(condition_lexer)
			.parse::<Expr>()
			.ok();

		let body = input.parse_seq(|input| {
			!input.check_kind(ChunkKind::BranchEnd) && !input.check_kind(ChunkKind::BranchFork)
		});

		let else_ = if input.check_kind(ChunkKind::BranchFork) {
			Some(Box::new(input.parse()?))
		} else {
			None
		};

		if !matches!(
			directive.kind(),
			DirectiveKind::Else
				| DirectiveKind::ElseIf
				| DirectiveKind::ElseIfDef
				| DirectiveKind::ElseIfNDef
		) {
			input.consume_kind(ChunkKind::BranchEnd)?;
		}

		Ok(Self {
			directive,
			condition,
			body,
			else_,
		})
	}
}

impl Spanned for Section {
	fn span(&self) -> Span {
		match self {
			Section::Definition(inner) | Section::Text(inner) => inner.span(),
			Section::Branch(inner) => inner.span(),
		}
	}
}

impl Spanned for Branch {
	fn span(&self) -> Span {
		let start = self.directive.span();
		let end = self
			.else_
			.as_ref()
			.map(|branch| branch.span())
			.or_else(|| self.body.last().map(|last| last.span()))
			// FIXME: Expr span starts at 1:1
			.or_else(|| self.condition.as_ref().map(|cond| cond.span()));

		match end {
			Some(end) => start.through(end),
			None => start,
		}
	}
}