use crate::diagnostic::{Diagnostic, DiagnosticCode};
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use zerodds_idl_cst::{
Checkpoint, CstKind, GreenNode, Span, SyntaxNode, Token, TokenKind, TreeBuilder, lex,
};
#[derive(Debug, Clone)]
pub struct Parse {
root: Arc<GreenNode>,
diagnostics: Vec<Diagnostic>,
placed: Vec<(u32, Span)>,
}
impl Parse {
#[must_use]
pub fn syntax(&self) -> Arc<SyntaxNode> {
Arc::new(SyntaxNode::root(self.root.clone()))
}
#[must_use]
pub fn green(&self) -> &Arc<GreenNode> {
&self.root
}
#[must_use]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
#[must_use]
pub fn has_errors(&self) -> bool {
self.diagnostics.iter().any(Diagnostic::is_error)
}
pub fn prepend_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
let mut all = diagnostics;
all.append(&mut self.diagnostics);
self.diagnostics = all;
}
#[must_use]
pub fn text(&self) -> String {
self.root.text()
}
#[must_use]
pub fn source_span(&self, tree: Span) -> Span {
let start = self.source_offset(tree.start, false);
let end = if tree.end > tree.start {
self.source_offset(tree.end, true).max(start)
} else {
start
};
Span::new(start, end)
}
fn source_offset(&self, offset: u32, end: bool) -> u32 {
let probe = if end {
offset.saturating_sub(1)
} else {
offset
};
let Some(index) = self
.placed
.partition_point(|(start, _)| *start <= probe)
.checked_sub(1)
else {
return self
.placed
.first()
.map_or(offset, |(_, source)| source.start);
};
let (start, source) = self.placed[index];
source
.start
.saturating_add(offset.saturating_sub(start))
.min(source.end)
}
}
#[must_use]
pub fn parse(source: &str) -> Parse {
parse_with(source, crate::Dialect::ALL_VENDORS)
}
#[must_use]
pub fn parse_with(source: &str, dialect: crate::Dialect) -> Parse {
let mut parser = Parser::new(source);
parser.dialect = dialect;
parser.parse_file()
}
#[must_use]
pub fn parse_preprocessed(preprocessed: &crate::preprocess::Preprocessed) -> Parse {
parse_preprocessed_with(preprocessed, crate::Dialect::ALL_VENDORS)
}
#[must_use]
pub fn parse_preprocessed_with(
preprocessed: &crate::preprocess::Preprocessed,
dialect: crate::Dialect,
) -> Parse {
let mut parser = Parser::over(&preprocessed.text, preprocessed.tokens.clone());
parser.dialect = dialect;
let mut parse = parser.parse_file();
parse.prepend_diagnostics(preprocessed.diagnostics.clone());
parse
}
pub struct Parser<'a> {
pub(crate) source: &'a str,
pub(crate) tokens: Vec<Token>,
pub(crate) at: usize,
pub(crate) builder: TreeBuilder,
pub(crate) diagnostics: Vec<Diagnostic>,
pub(crate) dialect: crate::Dialect,
steps: usize,
tree_len: u32,
placed: Vec<(u32, Span)>,
}
const MAX_STEPS: usize = 4_000_000;
impl<'a> Parser<'a> {
#[must_use]
pub fn new(source: &'a str) -> Self {
Self {
source,
tokens: lex(source),
at: 0,
builder: TreeBuilder::new(),
diagnostics: Vec::new(),
dialect: crate::Dialect::ALL_VENDORS,
steps: 0,
tree_len: 0,
placed: Vec::new(),
}
}
#[must_use]
pub fn over(source: &'a str, tokens: Vec<Token>) -> Self {
Self {
source,
tokens,
at: 0,
builder: TreeBuilder::new(),
diagnostics: Vec::new(),
dialect: crate::Dialect::ALL_VENDORS,
steps: 0,
tree_len: 0,
placed: Vec::new(),
}
}
#[must_use]
pub fn parse_file(mut self) -> Parse {
self.builder.start(CstKind::File);
while !self.at_eof() {
let before = self.at;
self.parse_definition();
if self.at == before {
self.error_token("expected a declaration");
}
if self.steps > MAX_STEPS {
break;
}
}
self.eat_eof();
self.builder.finish();
let root = self
.builder
.build()
.unwrap_or_else(|| Arc::new(GreenNode::new(CstKind::File, Vec::new())));
Parse {
root,
diagnostics: self.diagnostics,
placed: self.placed,
}
}
#[must_use]
pub fn current(&self) -> TokenKind {
self.tokens.get(self.at).map_or(TokenKind::Eof, |t| t.kind)
}
#[must_use]
pub fn peek(&self, ahead: usize) -> TokenKind {
self.tokens
.get(self.at + ahead)
.map_or(TokenKind::Eof, |t| t.kind)
}
#[must_use]
pub fn at_eof(&self) -> bool {
self.current() == TokenKind::Eof
}
#[must_use]
pub fn current_span(&self) -> Span {
self.tokens
.get(self.at)
.map_or_else(|| Span::empty_at(self.source_end()), |t| t.span)
}
#[must_use]
pub fn current_text(&self) -> &str {
self.tokens
.get(self.at)
.and_then(|t| t.span.slice(self.source))
.unwrap_or("")
}
#[allow(clippy::cast_possible_truncation)]
fn source_end(&self) -> u32 {
self.source.len() as u32
}
pub fn bump(&mut self) {
self.steps += 1;
let Some(token) = self.tokens.get(self.at) else {
return;
};
let text = token.span.slice(self.source).unwrap_or("");
let kind = token.kind;
let span = token.span;
self.emit_leading_trivia();
self.emit(kind, text, span);
self.at += 1;
}
pub(crate) fn emit(&mut self, kind: TokenKind, text: &str, source: Span) {
if !text.is_empty() {
self.placed.push((self.tree_len, source));
}
self.builder.token(kind, text);
#[allow(clippy::cast_possible_truncation)]
let len = text.len() as u32;
self.tree_len = self.tree_len.saturating_add(len);
}
fn emit_leading_trivia(&mut self) {
let Some(token) = self.tokens.get(self.at) else {
return;
};
let leading: Vec<(zerodds_idl_cst::TriviaKind, Span)> = token
.leading
.iter()
.map(|trivia| (trivia.kind, trivia.span))
.collect();
for (kind, span) in leading {
let text = span.slice(self.source).unwrap_or("");
if kind == zerodds_idl_cst::TriviaKind::LineComment
&& crate::suffix::is_suffix_comment(text)
{
self.diagnose_stray_suffix(span);
}
self.emit(trivia_token(kind), text, span);
}
}
pub(crate) fn eat_eof(&mut self) {
self.emit_leading_trivia();
}
pub(crate) fn eat_adjacent_string_literals(&mut self, kind: TokenKind) {
loop {
let next = self.current();
if next == kind {
self.bump();
} else if matches!(next, TokenKind::StringLiteral | TokenKind::WStringLiteral) {
self.diagnose(
crate::diagnostic::DiagnosticCode::UnexpectedToken,
"a string literal and a wide string literal are not concatenated; \
adjacent literals have to be of one width",
"OMG IDL 4.2 §7.2.6.3, §7.2.6.4",
);
self.bump();
} else {
return;
}
}
}
pub fn eat(&mut self, kind: TokenKind) -> bool {
if self.current() == kind {
self.bump();
true
} else {
false
}
}
pub fn expect(&mut self, kind: TokenKind, what: &str) -> bool {
if self.eat(kind) {
return true;
}
let span = Span::empty_at(self.current_span().start);
self.diagnostics.push(Diagnostic::error(
DiagnosticCode::MissingToken,
span,
format!("expected {what}"),
"OMG IDL 4.2 §7.4",
));
false
}
pub fn start(&mut self, kind: CstKind) {
self.builder.start(kind);
}
pub fn finish(&mut self) {
self.builder.finish();
}
#[must_use]
pub fn checkpoint(&self) -> Checkpoint {
self.builder.checkpoint()
}
pub fn start_at(&mut self, checkpoint: Checkpoint, kind: CstKind) {
self.builder.start_at(checkpoint, kind);
}
#[must_use]
pub fn tokens_left(&self) -> usize {
self.tokens.len().saturating_sub(self.at)
}
pub fn split_shift_right(&mut self) {
self.steps += 1;
let Some(token) = self.tokens.get(self.at).cloned() else {
return;
};
if token.kind != TokenKind::GreaterGreater {
return;
}
self.emit_leading_trivia();
self.emit(
TokenKind::Greater,
">",
Span::new(token.span.start, token.span.start.saturating_add(1)),
);
let rest = Token {
kind: TokenKind::Greater,
span: Span::new(token.span.start.saturating_add(1), token.span.end),
leading: Vec::new(),
value: token.value,
};
if let Some(slot) = self.tokens.get_mut(self.at) {
*slot = rest;
}
}
pub fn diagnose(&mut self, code: DiagnosticCode, message: &str, spec_reference: &'static str) {
self.diagnostics.push(Diagnostic::error(
code,
self.current_span(),
message.to_string(),
spec_reference,
));
}
pub(crate) fn diagnose_vendor(
&mut self,
flag: crate::dialect::VendorFlag,
message: &str,
spec_reference: &'static str,
) {
let span = self.current_span();
let diagnostic = if self.dialect.admits(flag) {
Diagnostic::warning(
DiagnosticCode::VendorExtension,
span,
message.to_string(),
spec_reference,
)
} else {
Diagnostic::error(
DiagnosticCode::VendorExtension,
span,
message.to_string(),
spec_reference,
)
};
self.diagnostics.push(diagnostic);
}
pub fn error_token(&mut self, message: &str) {
self.diagnose(DiagnosticCode::UnexpectedToken, message, "OMG IDL 4.2 §7.4");
self.start(CstKind::Error);
self.bump();
self.finish();
}
pub fn recover_to_declaration(&mut self) {
if self.at_eof() || self.at_recovery_point() {
return;
}
self.start(CstKind::Error);
let mut depth = 0i32;
while !self.at_eof() {
match self.current() {
TokenKind::LBrace => depth += 1,
TokenKind::RBrace => {
if depth == 0 {
break;
}
depth -= 1;
}
TokenKind::Semicolon if depth == 0 => {
self.bump();
break;
}
_ if depth == 0 && self.at_recovery_point() => break,
_ => {}
}
self.bump();
if self.steps > MAX_STEPS {
break;
}
}
self.finish();
}
#[must_use]
pub fn at_recovery_point(&self) -> bool {
matches!(
self.current(),
TokenKind::Module
| TokenKind::Const
| TokenKind::Typedef
| TokenKind::Struct
| TokenKind::Union
| TokenKind::Enum
| TokenKind::Bitmask
| TokenKind::Bitset
| TokenKind::Native
| TokenKind::Exception
| TokenKind::Interface
| TokenKind::Valuetype
| TokenKind::Eventtype
| TokenKind::Component
| TokenKind::Home
| TokenKind::Connector
| TokenKind::Porttype
| TokenKind::Local
| TokenKind::Abstract
| TokenKind::Custom
| TokenKind::Typeid
| TokenKind::Typeprefix
| TokenKind::Import
| TokenKind::At
)
}
#[must_use]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
}
pub(crate) const fn trivia_token(kind: zerodds_idl_cst::TriviaKind) -> TokenKind {
match kind {
zerodds_idl_cst::TriviaKind::Whitespace | zerodds_idl_cst::TriviaKind::Newline => {
TokenKind::Whitespace
}
zerodds_idl_cst::TriviaKind::LineComment => TokenKind::LineComment,
zerodds_idl_cst::TriviaKind::BlockComment => TokenKind::BlockComment,
zerodds_idl_cst::TriviaKind::Directive => TokenKind::Directive,
}
}