use std::collections::BTreeMap;
use plsql_core::Span;
use serde::{Deserialize, Serialize};
use crate::tokens::{TokenTape, TriviaTable};
pub trait Spanned {
fn span(&self) -> Span;
}
#[derive(
Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
)]
#[serde(transparent)]
pub struct CstNodeId(pub u32);
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SourceMap {
inner: BTreeMap<u32, Span>,
}
impl SourceMap {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, node: CstNodeId, span: Span) {
self.inner.insert(node.0, span);
}
#[must_use]
pub fn get(&self, node: CstNodeId) -> Option<&Span> {
self.inner.get(&node.0)
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ConcreteSyntaxTree {
pub root: CstNodeId,
pub token_tape: TokenTape,
pub trivia: TriviaTable,
pub source_map: SourceMap,
}
impl ConcreteSyntaxTree {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn reconstruct(&self) -> String {
self.token_tape.reconstruct(&self.trivia)
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SourceFile {
pub span: Span,
pub declarations: Vec<AstDecl>,
}
impl Spanned for SourceFile {
fn span(&self) -> Span {
self.span
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AstDecl {
PackageSpec { name: String, span: Span },
PackageBody { name: String, span: Span },
Procedure { name: String, span: Span },
Function { name: String, span: Span },
Trigger { name: String, span: Span },
View { name: String, span: Span },
TypeSpec { name: String, span: Span },
TypeBody { name: String, span: Span },
Ddl {
kind: String,
span: Span,
#[serde(default)]
antlr_rule_path: Option<String>,
},
Unknown {
span: Span,
#[serde(default)]
antlr_rule_path: Option<String>,
},
}
impl Spanned for AstDecl {
fn span(&self) -> Span {
match self {
Self::PackageSpec { span, .. }
| Self::PackageBody { span, .. }
| Self::Procedure { span, .. }
| Self::Function { span, .. }
| Self::Trigger { span, .. }
| Self::View { span, .. }
| Self::TypeSpec { span, .. }
| Self::TypeBody { span, .. }
| Self::Ddl { span, .. }
| Self::Unknown { span, .. } => *span,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AstStatement {
Null { span: Span },
Assignment {
target: String,
rhs_text: String,
span: Span,
},
If { cond_text: String, span: Span },
Loop { header_text: String, span: Span },
Raise {
exception: Option<String>,
span: Span,
},
Return {
value_text: Option<String>,
span: Span,
},
ExecuteImmediate {
sql_text: String,
has_using: bool,
span: Span,
},
Sql {
verb: String,
raw_text: String,
span: Span,
},
Call { callee: String, span: Span },
Unknown { span: Span },
}
impl Spanned for AstStatement {
fn span(&self) -> Span {
match self {
Self::Null { span }
| Self::Assignment { span, .. }
| Self::If { span, .. }
| Self::Loop { span, .. }
| Self::Raise { span, .. }
| Self::Return { span, .. }
| Self::ExecuteImmediate { span, .. }
| Self::Sql { span, .. }
| Self::Call { span, .. }
| Self::Unknown { span } => *span,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AstExpr {
Literal { text: String, span: Span },
Name { path: String, span: Span },
Bind { name: String, span: Span },
Substitution {
name: String,
sticky: bool,
span: Span,
},
Call {
callee: String,
args_text: String,
span: Span,
},
Binary {
op: String,
lhs_text: String,
rhs_text: String,
span: Span,
},
Unary {
op: String,
operand_text: String,
span: Span,
},
Unknown { text: String, span: Span },
}
impl Spanned for AstExpr {
fn span(&self) -> Span {
match self {
Self::Literal { span, .. }
| Self::Name { span, .. }
| Self::Bind { span, .. }
| Self::Substitution { span, .. }
| Self::Call { span, .. }
| Self::Binary { span, .. }
| Self::Unary { span, .. }
| Self::Unknown { span, .. } => *span,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AstTypeDecl {
Object {
name: String,
attributes_text: String,
span: Span,
},
Collection {
name: String,
element_text: String,
is_varray: bool,
span: Span,
},
Record {
name: String,
fields_text: String,
span: Span,
},
Unknown { text: String, span: Span },
}
impl Spanned for AstTypeDecl {
fn span(&self) -> Span {
match self {
Self::Object { span, .. }
| Self::Collection { span, .. }
| Self::Record { span, .. }
| Self::Unknown { span, .. } => *span,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Ast {
pub root: SourceFile,
pub source_map: SourceMap,
#[serde(default)]
pub body_statements: Vec<Vec<AstStatement>>,
}
impl Ast {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use plsql_core::{FileId, Position};
fn span(offset: u32, len: u32) -> Span {
Span::new(
FileId::new(0),
Position::new(1, 1, offset),
Position::new(1, 1, offset + len),
)
}
#[test]
fn source_map_insert_and_get() {
let mut sm = SourceMap::new();
let id = CstNodeId(42);
let s = span(10, 5);
sm.insert(id, s);
assert_eq!(sm.get(id), Some(&s));
assert_eq!(sm.get(CstNodeId(99)), None);
}
#[test]
fn source_map_len() {
let mut sm = SourceMap::new();
assert!(sm.is_empty());
sm.insert(CstNodeId(0), span(0, 1));
sm.insert(CstNodeId(1), span(1, 1));
assert_eq!(sm.len(), 2);
assert!(!sm.is_empty());
}
#[test]
fn cst_default_has_empty_source_map() {
let cst = ConcreteSyntaxTree::new();
assert!(cst.source_map.is_empty());
}
#[test]
fn ast_default_has_empty_source_map() {
let ast = Ast::new();
assert!(ast.source_map.is_empty());
}
#[test]
fn source_map_serializes_round_trip() {
let mut sm = SourceMap::new();
sm.insert(CstNodeId(1), span(0, 10));
sm.insert(CstNodeId(5), span(20, 30));
let json = serde_json::to_string(&sm).unwrap();
let back: SourceMap = serde_json::from_str(&json).unwrap();
assert_eq!(back.len(), 2);
assert_eq!(back.get(CstNodeId(1)), Some(&span(0, 10)));
}
#[test]
fn source_file_is_spanned() {
let s = span(0, 100);
let sf = SourceFile {
span: s,
declarations: Vec::new(),
};
assert_eq!(sf.span(), s);
}
#[test]
fn ast_decl_all_variants_are_spanned() {
let s = span(10, 20);
let decls = vec![
AstDecl::PackageSpec {
name: "pkg".into(),
span: s,
},
AstDecl::PackageBody {
name: "pkg".into(),
span: s,
},
AstDecl::Procedure {
name: "p".into(),
span: s,
},
AstDecl::Function {
name: "f".into(),
span: s,
},
AstDecl::Trigger {
name: "t".into(),
span: s,
},
AstDecl::View {
name: "v".into(),
span: s,
},
AstDecl::TypeSpec {
name: "ty".into(),
span: s,
},
AstDecl::TypeBody {
name: "ty".into(),
span: s,
},
AstDecl::Ddl {
kind: "CREATE".into(),
span: s,
antlr_rule_path: None,
},
AstDecl::Unknown {
span: s,
antlr_rule_path: None,
},
];
for decl in &decls {
assert_eq!(
decl.span(),
s,
"Spanned::span() returned wrong span for variant"
);
}
}
#[test]
fn spanned_trait_is_object_safe() {
fn take_spanned(node: &dyn Spanned) -> Span {
node.span()
}
let s = span(0, 50);
let sf = SourceFile {
span: s,
declarations: Vec::new(),
};
assert_eq!(take_spanned(&sf), s);
}
}