use plsql_core::Span;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum TokenKind {
StringLiteral,
NumericLiteral,
QuotedIdentifier,
Keyword,
BuiltIn,
Identifier,
Semicolon,
Slash,
Dot,
Comma,
LParen,
RParen,
Assign,
Arrow,
Concat,
Operator,
IncludeDirective,
StatementTerminator,
Unknown,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Token {
pub kind: TokenKind,
pub span: Span,
pub text: String,
}
impl Token {
#[must_use]
pub fn new(kind: TokenKind, span: Span, text: impl Into<String>) -> Self {
Self {
kind,
span,
text: text.into(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Trivia {
Whitespace(String),
LineComment(String),
BlockComment(String),
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct TriviaTable {
pub leading: Vec<Vec<Trivia>>,
}
impl TriviaTable {
#[must_use]
pub fn new() -> Self {
Self {
leading: Vec::new(),
}
}
pub fn push(&mut self, token_index: usize, trivia: Trivia) {
while self.leading.len() <= token_index {
self.leading.push(Vec::new());
}
self.leading[token_index].push(trivia);
}
#[must_use]
pub fn get(&self, token_index: usize) -> &[Trivia] {
self.leading.get(token_index).map_or(&[], |v| v.as_slice())
}
#[must_use]
pub fn total_count(&self) -> usize {
self.leading.iter().map(Vec::len).sum()
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct TokenTape {
pub tokens: Vec<Token>,
}
impl TokenTape {
#[must_use]
pub fn new() -> Self {
Self { tokens: Vec::new() }
}
#[must_use]
pub fn len(&self) -> usize {
self.tokens.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tokens.is_empty()
}
pub fn push(&mut self, token: Token) {
self.tokens.push(token);
}
#[must_use]
pub fn reconstruct(&self, trivia: &TriviaTable) -> String {
let mut out = String::new();
for (i, token) in self.tokens.iter().enumerate() {
for t in trivia.get(i) {
match t {
Trivia::Whitespace(s) | Trivia::LineComment(s) | Trivia::BlockComment(s) => {
out.push_str(s)
}
}
}
out.push_str(&token.text);
}
for t in trivia.get(self.tokens.len()) {
match t {
Trivia::Whitespace(s) | Trivia::LineComment(s) | Trivia::BlockComment(s) => {
out.push_str(s)
}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use plsql_core::{FileId, Position};
fn span(start: u32, len: u32) -> Span {
Span::new(
FileId::new(0),
Position::new(1, 1, start),
Position::new(1, 1, start + len),
)
}
#[test]
fn empty_tape_reconstructs_to_empty_string() {
let tape = TokenTape::new();
let trivia = TriviaTable::new();
assert_eq!(tape.reconstruct(&trivia), "");
}
#[test]
fn single_token_no_trivia() {
let mut tape = TokenTape::new();
tape.push(Token::new(TokenKind::Keyword, span(0, 6), "SELECT"));
let trivia = TriviaTable::new();
assert_eq!(tape.reconstruct(&trivia), "SELECT");
}
#[test]
fn reconstruct_with_leading_and_inter_token_trivia() {
let mut tape = TokenTape::new();
tape.push(Token::new(TokenKind::Keyword, span(2, 6), "SELECT"));
tape.push(Token::new(TokenKind::Identifier, span(9, 4), "name"));
tape.push(Token::new(TokenKind::Keyword, span(14, 4), "FROM"));
tape.push(Token::new(TokenKind::Identifier, span(19, 5), "users"));
tape.push(Token::new(TokenKind::Semicolon, span(24, 1), ";"));
let mut trivia = TriviaTable::new();
trivia.push(0, Trivia::Whitespace(" ".to_string()));
trivia.push(1, Trivia::Whitespace(" ".to_string()));
trivia.push(2, Trivia::Whitespace(" ".to_string()));
trivia.push(3, Trivia::Whitespace(" ".to_string()));
assert_eq!(tape.reconstruct(&trivia), " SELECT name FROM users;");
}
#[test]
fn reconstruct_preserves_comments() {
let mut tape = TokenTape::new();
tape.push(Token::new(TokenKind::Keyword, span(12, 6), "SELECT"));
tape.push(Token::new(TokenKind::NumericLiteral, span(19, 1), "1"));
tape.push(Token::new(TokenKind::Semicolon, span(20, 1), ";"));
let mut trivia = TriviaTable::new();
trivia.push(0, Trivia::LineComment("-- pick one\n".to_string()));
trivia.push(1, Trivia::Whitespace(" ".to_string()));
assert_eq!(tape.reconstruct(&trivia), "-- pick one\nSELECT 1;");
}
#[test]
fn trivia_table_total_count() {
let mut trivia = TriviaTable::new();
trivia.push(0, Trivia::Whitespace(" ".to_string()));
trivia.push(2, Trivia::Whitespace(" ".to_string()));
trivia.push(2, Trivia::LineComment("-- x".to_string()));
assert_eq!(trivia.total_count(), 3);
}
#[test]
fn trivia_table_get_out_of_bounds_returns_empty() {
let trivia = TriviaTable::new();
assert_eq!(trivia.get(999), &[] as &[Trivia]);
}
}