use serde::Serialize;
use crate::ast::{Keyword, SourceStore, Span};
pub const WIRE_SCHEMA_VERSION: u32 = 2;
use crate::ast::dialect::{SupportEvidence, SupportTier};
use crate::dialect::BuiltinDialect;
use crate::error::{Found, ParseError, ParseErrorKind};
use crate::parser::{Parsed, Recovered};
use crate::tokenizer::{Operator, Punctuation, Token, TokenKind, TriviaKind, TriviaRange};
#[derive(Serialize)]
#[serde(bound(serialize = "Parsed<S>: Serialize"))]
pub struct ParseDocument<'a, S: SourceStore> {
pub dialect: &'static str,
#[serde(flatten)]
pub parsed: &'a Parsed<S>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trivia: Option<Vec<BindingTrivia>>,
pub resolver: ResolverMetadata,
}
impl<'a, S: SourceStore> ParseDocument<'a, S> {
pub fn new(parsed: &'a Parsed<S>, dialect: BuiltinDialect) -> Self {
Self {
dialect: dialect.name(),
parsed,
trivia: binding_trivia(parsed.source(), parsed.trivia()),
resolver: ResolverMetadata::new(),
}
}
}
#[derive(Serialize)]
#[serde(bound(serialize = "ParseDocument<'a, S>: Serialize"))]
pub struct RecoveredDocument<'a, S: SourceStore> {
#[serde(flatten)]
pub parsed: ParseDocument<'a, S>,
pub errors: Vec<ParseDiagnostic>,
}
impl<'a, S: SourceStore> RecoveredDocument<'a, S> {
pub fn new(recovered: &'a Recovered<S>, dialect: BuiltinDialect) -> Self {
Self {
parsed: ParseDocument::new(recovered.parsed(), dialect),
errors: recovered
.errors()
.iter()
.map(ParseDiagnostic::from)
.collect(),
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct ResolverMetadata {
pub dynamic_base: u32,
pub keyword_symbols: Vec<KeywordSymbol>,
}
impl ResolverMetadata {
pub fn new() -> Self {
Self {
dynamic_base: Keyword::ALL.len() as u32 + 1,
keyword_symbols: Keyword::ALL
.iter()
.copied()
.map(|keyword| KeywordSymbol {
symbol: keyword.symbol().as_u32(),
text: keyword.as_str(),
})
.collect(),
}
}
}
impl Default for ResolverMetadata {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, Serialize)]
pub struct KeywordSymbol {
pub symbol: u32,
pub text: &'static str,
}
#[derive(Clone, Debug, Serialize)]
pub struct DialectInfo {
pub name: &'static str,
pub aliases: &'static [&'static str],
pub tier: SupportTier,
pub evidence: SupportEvidence,
}
pub fn supported_dialects() -> Vec<DialectInfo> {
BuiltinDialect::ALL
.iter()
.copied()
.map(|dialect| DialectInfo {
name: dialect.name(),
aliases: dialect.aliases(),
tier: dialect.support_tier(),
evidence: dialect.support_evidence(),
})
.collect()
}
#[derive(Clone, Debug, Serialize)]
pub struct TokenizeDocument {
pub source: String,
pub dialect: &'static str,
pub tokens: Vec<BindingToken>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trivia: Option<Vec<BindingTrivia>>,
}
impl TokenizeDocument {
pub fn new(source: &str, dialect: BuiltinDialect, tokens: &[Token]) -> Self {
Self {
source: source.to_owned(),
dialect: dialect.name(),
tokens: tokens
.iter()
.copied()
.map(|token| BindingToken::new(source, token))
.collect(),
trivia: None,
}
}
pub fn with_trivia(
source: &str,
dialect: BuiltinDialect,
tokens: &[Token],
trivia: &[TriviaRange],
) -> Self {
Self {
trivia: Some(
trivia
.iter()
.copied()
.map(|range| BindingTrivia::new(source, range))
.collect(),
),
..Self::new(source, dialect, tokens)
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct BindingToken {
#[serde(flatten)]
pub kind: BindingTokenKind,
pub span: SourceSpan,
pub text: String,
}
impl BindingToken {
fn new(source: &str, token: Token) -> Self {
Self {
kind: BindingTokenKind::from(token.kind),
span: SourceSpan::from_span(token.span),
text: slice_bytes(source, token.span),
}
}
}
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "kind")]
#[non_exhaustive]
pub enum BindingTokenKind {
Word,
Keyword {
keyword: &'static str,
},
Number,
String,
QuotedIdent,
Parameter,
PositionalColumn,
Variable,
StageReference,
Operator {
operator: &'static str,
},
Punctuation {
punctuation: &'static str,
},
Unknown,
}
impl From<TokenKind> for BindingTokenKind {
fn from(kind: TokenKind) -> Self {
match kind {
TokenKind::Word => Self::Word,
TokenKind::Keyword(keyword) => Self::Keyword {
keyword: keyword.as_str(),
},
TokenKind::Number => Self::Number,
TokenKind::String => Self::String,
TokenKind::QuotedIdent => Self::QuotedIdent,
TokenKind::Parameter => Self::Parameter,
TokenKind::PositionalColumn => Self::PositionalColumn,
TokenKind::Variable => Self::Variable,
TokenKind::StageReference => Self::StageReference,
TokenKind::Operator(operator) => Self::Operator {
operator: operator_name(operator),
},
TokenKind::Punctuation(punctuation) => Self::Punctuation {
punctuation: punctuation_name(punctuation),
},
TokenKind::Unknown => Self::Unknown,
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct BindingTrivia {
pub kind: &'static str,
pub span: SourceSpan,
pub text: String,
}
impl BindingTrivia {
fn new(source: &str, trivia: TriviaRange) -> Self {
Self {
kind: trivia_kind_name(trivia.kind()),
span: SourceSpan::from_span(trivia.span()),
text: slice_bytes(source, trivia.span()),
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct ParseDiagnostic {
pub message: String,
pub kind: &'static str,
pub span: Option<SourceSpan>,
#[serde(skip_serializing_if = "Option::is_none")]
pub span_start: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub span_end: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expected: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub found: Option<String>,
}
impl From<&ParseError> for ParseDiagnostic {
fn from(error: &ParseError) -> Self {
let span = SourceSpan::from_diagnostic_span(error.span);
Self {
message: error.to_string(),
kind: parse_error_kind(error.kind),
span,
span_start: span.map(|span| span.start),
span_end: span.map(|span| span.end),
expected: Some(error.expected.to_string()),
found: found_text(&error.found),
}
}
}
#[derive(Clone, Copy, Debug, Serialize)]
pub struct SourceSpan {
pub start: u32,
pub end: u32,
}
impl SourceSpan {
pub fn from_span(span: Span) -> Self {
Self {
start: span.start(),
end: span.end(),
}
}
fn from_diagnostic_span(span: Span) -> Option<Self> {
if span.is_synthetic() {
None
} else {
Some(Self::from_span(span))
}
}
}
pub type DiagnosticSpan = SourceSpan;
fn parse_error_kind(kind: ParseErrorKind) -> &'static str {
match kind {
ParseErrorKind::Syntax => "syntax",
ParseErrorKind::RecursionLimitExceeded => "recursion_limit_exceeded",
ParseErrorKind::Lexical(lex_kind) => lex_kind.machine_kind(),
}
}
fn found_text(found: &Found) -> Option<String> {
match found {
Found::EndOfInput => Some("end of input".to_owned()),
Found::Text(text) => Some(text.to_string()),
}
}
fn binding_trivia(source: &str, trivia: &[TriviaRange]) -> Option<Vec<BindingTrivia>> {
if trivia.is_empty() {
None
} else {
Some(
trivia
.iter()
.copied()
.map(|range| BindingTrivia::new(source, range))
.collect(),
)
}
}
fn operator_name(operator: Operator) -> &'static str {
match operator {
Operator::Plus => "Plus",
Operator::Minus => "Minus",
Operator::Star => "Star",
Operator::Slash => "Slash",
Operator::SlashSlash => "SlashSlash",
Operator::Percent => "Percent",
Operator::Eq => "Eq",
Operator::EqEq => "EqEq",
Operator::Lt => "Lt",
Operator::LtEq => "LtEq",
Operator::Gt => "Gt",
Operator::GtEq => "GtEq",
Operator::NotEq => "NotEq",
Operator::LtEqGt => "LtEqGt",
Operator::Concat => "Concat",
Operator::AmpAmp => "AmpAmp",
Operator::Bang => "Bang",
Operator::Pipe => "Pipe",
Operator::Amp => "Amp",
Operator::Caret => "Caret",
Operator::CaretAt => "CaretAt",
Operator::Tilde => "Tilde",
Operator::ShiftLeft => "ShiftLeft",
Operator::ShiftRight => "ShiftRight",
Operator::Hash => "Hash",
Operator::Arrow => "Arrow",
Operator::ColonEquals => "ColonEquals",
Operator::AtGt => "AtGt",
Operator::LtAt => "LtAt",
Operator::MinusGt => "MinusGt",
Operator::MinusGtGt => "MinusGtGt",
Operator::PipeArrow => "PipeArrow",
Operator::Question => "Question",
Operator::QuestionPipe => "QuestionPipe",
Operator::QuestionAmp => "QuestionAmp",
Operator::AtQuestion => "AtQuestion",
Operator::AtAt => "AtAt",
Operator::HashGt => "HashGt",
Operator::HashGtGt => "HashGtGt",
Operator::HashMinus => "HashMinus",
Operator::Custom => "Custom",
}
}
fn punctuation_name(punctuation: Punctuation) -> &'static str {
match punctuation {
Punctuation::LParen => "LParen",
Punctuation::RParen => "RParen",
Punctuation::Comma => "Comma",
Punctuation::Semicolon => "Semicolon",
Punctuation::Dot => "Dot",
Punctuation::LBracket => "LBracket",
Punctuation::RBracket => "RBracket",
Punctuation::LBrace => "LBrace",
Punctuation::RBrace => "RBrace",
Punctuation::Colon => "Colon",
Punctuation::DoubleColon => "DoubleColon",
Punctuation::At => "At",
}
}
fn trivia_kind_name(kind: TriviaKind) -> &'static str {
match kind {
TriviaKind::LineComment => "LineComment",
TriviaKind::BlockComment => "BlockComment",
TriviaKind::Whitespace => "Whitespace",
}
}
fn slice_bytes(source: &str, span: Span) -> String {
String::from_utf8_lossy(&source.as_bytes()[span.start() as usize..span.end() as usize])
.into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tokenizer::{LexError, LexErrorKind};
const ALL_LEX_KINDS: &[LexErrorKind] = &[
LexErrorKind::UnterminatedString,
LexErrorKind::UnterminatedQuotedIdent,
LexErrorKind::UnterminatedBlockComment,
LexErrorKind::UnterminatedDollarQuote,
LexErrorKind::StrayByte,
LexErrorKind::InvalidEscapeSequence,
LexErrorKind::NulByteInString,
LexErrorKind::NulByteInIdentifier,
LexErrorKind::NulByteInComment,
LexErrorKind::ZeroLengthDelimitedIdentifier,
LexErrorKind::TrailingJunkAfterNumber,
LexErrorKind::MalformedBlobLiteral,
LexErrorKind::SourceTooLong,
];
#[test]
fn widened_lexical_faults_surface_distinct_diagnostic_kinds() {
let mut seen = std::collections::HashSet::new();
for &lex_kind in ALL_LEX_KINDS {
let parse_error = ParseError::from(LexError::new(lex_kind, Span::new(1, 4)));
let diagnostic = ParseDiagnostic::from(&parse_error);
assert_eq!(
diagnostic.kind,
lex_kind.machine_kind(),
"diagnostic kind must be the lexical machine kind for {lex_kind:?}"
);
assert_ne!(diagnostic.kind, "syntax", "for {lex_kind:?}");
assert!(
seen.insert(diagnostic.kind),
"duplicate diagnostic kind {:?}",
diagnostic.kind
);
let span = diagnostic.span.expect("real span");
assert_eq!((span.start, span.end), (1, 4));
}
}
#[test]
fn ordinary_syntax_error_still_maps_to_syntax() {
let err = ParseError::new(Span::new(0, 1), "expression", "`,`");
assert_eq!(ParseDiagnostic::from(&err).kind, "syntax");
}
}