use crate::lexer::{QuoteForm, Span};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NameId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExprId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SelectId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SelectCoreId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FromTermId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(pub u32);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Name {
pub text: Vec<u8>,
pub folded: Vec<u8>,
pub quote: QuoteForm,
pub span: Span,
}
impl Name {
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.text).unwrap_or("")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal {
Null,
Boolean(bool),
Integer(Vec<u8>),
Float(Vec<u8>),
String(Vec<u8>),
Blob(Vec<u8>),
CurrentDate,
CurrentTime,
CurrentTimestamp,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnaryOp {
Negate,
Identity,
BitNot,
Not,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinaryOp {
Or,
And,
Equal,
NotEqual,
Less,
LessEqual,
Greater,
GreaterEqual,
Add,
Subtract,
Multiply,
Divide,
Modulo,
Concat,
BitAnd,
BitOr,
ShiftLeft,
ShiftRight,
Extract,
ExtractText,
Match,
Regexp,
L2Distance,
CosineDistance,
NegativeInnerProduct,
L1Distance,
HammingDistance,
JaccardDistance,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PatternOp {
Like,
Glob,
Regexp,
Match,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InRhs {
List(Vec<ExprId>),
Select(SelectId),
Table {
database: Option<NameId>,
table: NameId,
arguments: Option<Vec<ExprId>>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RaiseAction {
Ignore,
Rollback,
Abort,
Fail,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Parameter {
index: u32,
name: Option<NameId>,
},
Column {
database: Option<NameId>,
table: Option<NameId>,
column: NameId,
},
Star {
table: Option<NameId>,
},
Unary {
op: UnaryOp,
operand: ExprId,
},
Binary {
op: BinaryOp,
left: ExprId,
right: ExprId,
},
Collate {
operand: ExprId,
collation: NameId,
},
Cast {
operand: ExprId,
declared: NameId,
},
Pattern {
negated: bool,
op: PatternOp,
operand: ExprId,
pattern: ExprId,
escape: Option<ExprId>,
},
Between {
negated: bool,
operand: ExprId,
low: ExprId,
high: ExprId,
},
In {
negated: bool,
operand: ExprId,
rhs: InRhs,
},
IsNull {
negated: bool,
operand: ExprId,
},
Is {
negated: bool,
distinct_from: bool,
left: ExprId,
right: ExprId,
},
Case {
operand: Option<ExprId>,
branches: Vec<(ExprId, ExprId)>,
otherwise: Option<ExprId>,
},
Function {
name: NameId,
distinct: bool,
arguments: Option<Vec<ExprId>>,
order_by: Vec<OrderTerm>,
filter: Option<ExprId>,
over: Option<WindowId>,
},
Exists {
negated: bool,
select: SelectId,
},
Subquery(SelectId),
RowValue(Vec<ExprId>),
Raise {
action: RaiseAction,
message: Option<Vec<u8>>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum SortOrder {
#[default]
Ascending,
Descending,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NullOrder {
First,
Last,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OrderTerm {
pub expr: ExprId,
pub order: SortOrder,
pub nulls: Option<NullOrder>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResultColumn {
pub expr: ExprId,
pub alias: Option<NameId>,
pub alias_was_explicit: bool,
pub span: Span,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum JoinKind {
Comma,
Inner,
Cross,
Left,
Right,
Full,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum JoinConstraint {
None,
On(ExprId),
Using(Vec<NameId>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FromSource {
Table {
database: Option<NameId>,
name: NameId,
arguments: Option<Vec<ExprId>>,
indexed_by: IndexHint,
},
Subquery(SelectId),
Join(Vec<FromTermId>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IndexHint {
None,
NotIndexed,
IndexedBy(NameId),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FromTerm {
pub source: FromSource,
pub alias: Option<NameId>,
pub join: JoinKind,
pub natural: bool,
pub constraint: JoinConstraint,
pub span: Span,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FrameUnit {
Rows,
Range,
Groups,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FrameBound {
UnboundedPreceding,
Preceding(ExprId),
CurrentRow,
Following(ExprId),
UnboundedFollowing,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FrameExclude {
NoOthers,
CurrentRow,
Group,
Ties,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Window {
pub base: Option<NameId>,
pub partition_by: Vec<ExprId>,
pub order_by: Vec<OrderTerm>,
pub unit: Option<FrameUnit>,
pub start: Option<FrameBound>,
pub end: Option<FrameBound>,
pub exclude: FrameExclude,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SelectBody {
Select {
distinct: bool,
all: bool,
columns: Vec<ResultColumn>,
from: Vec<FromTermId>,
filter: Option<ExprId>,
group_by: Vec<ExprId>,
having: Option<ExprId>,
windows: Vec<(NameId, WindowId)>,
},
Values(Vec<Vec<ExprId>>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SelectCore {
pub body: SelectBody,
pub span: Span,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompoundOp {
Union,
UnionAll,
Intersect,
Except,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CommonTableExpr {
pub name: NameId,
pub columns: Vec<NameId>,
pub materialized: Option<bool>,
pub select: SelectId,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct With {
pub recursive: bool,
pub ctes: Vec<CommonTableExpr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Select {
pub with: With,
pub first: SelectCoreId,
pub compounds: Vec<(CompoundOp, SelectCoreId)>,
pub order_by: Vec<OrderTerm>,
pub limit: Option<ExprId>,
pub offset: Option<ExprId>,
pub span: Span,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConflictAction {
Rollback,
Abort,
Fail,
Ignore,
Replace,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ColumnConstraint {
PrimaryKey {
order: SortOrder,
on_conflict: Option<ConflictAction>,
autoincrement: bool,
},
NotNull(Option<ConflictAction>),
Null,
Unique(Option<ConflictAction>),
Check(ExprId),
Default(ExprId),
Collate(NameId),
References(ForeignKeyClause),
Generated {
expr: ExprId,
stored: bool,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ForeignKeyClause {
pub table: NameId,
pub columns: Vec<NameId>,
pub actions: Vec<ForeignKeyAction>,
pub deferrable: Option<bool>,
pub initially_deferred: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ForeignKeyAction {
OnDelete(ReferentialAction),
OnUpdate(ReferentialAction),
Match(NameId),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReferentialAction {
SetNull,
SetDefault,
Cascade,
Restrict,
NoAction,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ColumnDef {
pub name: NameId,
pub declared_type: Option<Vec<u8>>,
pub constraints: Vec<(Option<NameId>, ColumnConstraint)>,
pub span: Span,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IndexedColumn {
pub expr: ExprId,
pub collation: Option<NameId>,
pub order: SortOrder,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TableConstraint {
PrimaryKey {
columns: Vec<IndexedColumn>,
on_conflict: Option<ConflictAction>,
autoincrement: bool,
},
Unique {
columns: Vec<IndexedColumn>,
on_conflict: Option<ConflictAction>,
},
Check {
expr: ExprId,
on_conflict: Option<ConflictAction>,
},
ForeignKey {
columns: Vec<NameId>,
clause: ForeignKeyClause,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CreateTableBody {
Columns {
columns: Vec<ColumnDef>,
constraints: Vec<(Option<NameId>, TableConstraint)>,
without_rowid: bool,
strict: bool,
},
AsSelect(SelectId),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Upsert {
pub target: Vec<IndexedColumn>,
pub target_filter: Option<ExprId>,
pub assignments: Vec<(Vec<NameId>, ExprId)>,
pub do_update: bool,
pub filter: Option<ExprId>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InsertSource {
Select(SelectId),
DefaultValues,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Insert {
pub with: With,
pub on_conflict: Option<ConflictAction>,
pub database: Option<NameId>,
pub table: NameId,
pub alias: Option<NameId>,
pub columns: Vec<NameId>,
pub source: InsertSource,
pub upserts: Vec<Upsert>,
pub returning: Vec<ResultColumn>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Update {
pub with: With,
pub on_conflict: Option<ConflictAction>,
pub target: FromTermId,
pub assignments: Vec<(Vec<NameId>, ExprId)>,
pub from: Vec<FromTermId>,
pub filter: Option<ExprId>,
pub returning: Vec<ResultColumn>,
pub order_by: Vec<OrderTerm>,
pub limit: Option<ExprId>,
pub offset: Option<ExprId>,
pub limited_at: Option<(Limited, crate::lexer::Span)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Limited {
OrderBy,
Limit,
}
impl Limited {
pub fn word(self) -> &'static str {
match self {
Limited::OrderBy => "ORDER",
Limited::Limit => "LIMIT",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Delete {
pub with: With,
pub target: FromTermId,
pub filter: Option<ExprId>,
pub returning: Vec<ResultColumn>,
pub order_by: Vec<OrderTerm>,
pub limit: Option<ExprId>,
pub offset: Option<ExprId>,
pub limited_at: Option<(Limited, crate::lexer::Span)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ObjectKind {
Table,
Index,
View,
Trigger,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AlterAction {
RenameTo(NameId),
RenameColumn {
from: NameId,
to: NameId,
},
AddColumn(ColumnDef),
DropColumn(NameId),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TriggerTime {
Before,
After,
InsteadOf,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TriggerEvent {
Delete,
Insert,
Update(Vec<NameId>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PragmaValue {
None,
Value(ExprId),
Name(NameId),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Statement {
Empty,
Select(SelectId),
Insert(Box<Insert>),
Update(Box<Update>),
Delete(Box<Delete>),
CreateTable {
temporary: bool,
if_not_exists: bool,
database: Option<NameId>,
name: NameId,
body: CreateTableBody,
},
CreateIndex {
unique: bool,
if_not_exists: bool,
database: Option<NameId>,
name: NameId,
table: NameId,
using: Option<NameId>,
columns: Vec<IndexedColumn>,
settings: Vec<Vec<u8>>,
filter: Option<ExprId>,
},
CreateView {
temporary: bool,
if_not_exists: bool,
database: Option<NameId>,
name: NameId,
columns: Vec<NameId>,
select: SelectId,
},
CreateTrigger {
temporary: bool,
if_not_exists: bool,
database: Option<NameId>,
name: NameId,
time: Option<TriggerTime>,
event: TriggerEvent,
table: NameId,
for_each_row: bool,
when: Option<ExprId>,
body: Vec<Statement>,
},
CreateVirtualTable {
if_not_exists: bool,
database: Option<NameId>,
name: NameId,
module: NameId,
arguments: Vec<Vec<u8>>,
},
Drop {
kind: ObjectKind,
if_exists: bool,
database: Option<NameId>,
name: NameId,
},
AlterTable {
database: Option<NameId>,
table: NameId,
action: AlterAction,
},
Begin {
behaviour: Option<TransactionBehaviour>,
},
Commit,
Rollback {
savepoint: Option<NameId>,
},
Savepoint(NameId),
Release(NameId),
Pragma {
database: Option<NameId>,
name: NameId,
value: PragmaValue,
},
Attach {
file: ExprId,
schema: ExprId,
key: Option<ExprId>,
},
Detach {
schema: ExprId,
},
Vacuum {
database: Option<NameId>,
into: Option<ExprId>,
},
Analyze {
database: Option<NameId>,
name: Option<NameId>,
},
Reindex {
database: Option<NameId>,
name: Option<NameId>,
},
Explain {
query_plan: bool,
inner: Box<Statement>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TransactionBehaviour {
Deferred,
Immediate,
Exclusive,
}
const SPARE_NAME_BUFFERS: usize = 64;
const SPARE_NAME_CAPACITY: usize = 128;
#[derive(Clone, Debug, PartialEq, Eq)]
enum Interned {
One(u32),
Several(Vec<u32>),
}
#[derive(Clone, Debug, Default, Eq)]
pub struct Ast {
names: Vec<Name>,
interned: std::collections::HashMap<u64, Interned>,
spare: Vec<Vec<u8>>,
exprs: Vec<Expr>,
expr_spans: Vec<Span>,
expr_depths: Vec<u32>,
max_expr_depth: u32,
selects: Vec<Select>,
cores: Vec<SelectCore>,
from_terms: Vec<FromTerm>,
windows: Vec<Window>,
bytes: usize,
}
impl PartialEq for Ast {
fn eq(&self, other: &Ast) -> bool {
let Ast {
names,
interned: _,
spare: _,
exprs,
expr_spans,
expr_depths,
max_expr_depth,
selects,
cores,
from_terms,
windows,
bytes,
} = self;
*names == other.names
&& *exprs == other.exprs
&& *expr_spans == other.expr_spans
&& *expr_depths == other.expr_depths
&& *max_expr_depth == other.max_expr_depth
&& *selects == other.selects
&& *cores == other.cores
&& *from_terms == other.from_terms
&& *windows == other.windows
&& *bytes == other.bytes
}
}
impl Ast {
pub fn new() -> Ast {
Ast::default()
}
pub fn clear(&mut self) {
self.recycle_names();
self.interned.clear();
self.exprs.clear();
self.expr_spans.clear();
self.expr_depths.clear();
self.max_expr_depth = 0;
self.selects.clear();
self.cores.clear();
self.from_terms.clear();
self.windows.clear();
self.bytes = 0;
}
pub fn charged_bytes(&self) -> usize {
self.bytes
}
pub fn intern(&mut self, text: Vec<u8>, quote: QuoteForm, span: Span) -> NameId {
let id = self.intern_bytes(&text, quote, span);
Ast::keep_buffer(&mut self.spare, text);
id
}
pub fn intern_bytes(&mut self, text: &[u8], quote: QuoteForm, span: Span) -> NameId {
let hash = self.hash_of(text, quote);
if let Some(index) = self.find_interned(hash, text, quote) {
return NameId(index);
}
let mut folded = Ast::take_buffer(&mut self.spare);
folded.extend(text.iter().map(|byte| byte.to_ascii_lowercase()));
let mut spelling = Ast::take_buffer(&mut self.spare);
spelling.extend_from_slice(text);
self.bytes = self.bytes.saturating_add(
spelling
.len()
.saturating_add(folded.len())
.saturating_add(32),
);
let index = self.names.len() as u32;
self.names.push(Name {
text: spelling,
folded,
quote,
span,
});
self.remember_interned(hash, index);
NameId(index)
}
fn hash_of(&self, text: &[u8], quote: QuoteForm) -> u64 {
use std::hash::BuildHasher;
self.interned.hasher().hash_one((text, quote))
}
fn find_interned(&self, hash: u64, text: &[u8], quote: QuoteForm) -> Option<u32> {
let candidates: &[u32] = match self.interned.get(&hash)? {
Interned::One(index) => core::slice::from_ref(index),
Interned::Several(indexes) => indexes.as_slice(),
};
candidates.iter().copied().find(|index| {
self.names
.get(*index as usize)
.is_some_and(|name| name.quote == quote && name.text == text)
})
}
fn remember_interned(&mut self, hash: u64, index: u32) {
use std::collections::hash_map::Entry;
match self.interned.entry(hash) {
Entry::Vacant(slot) => {
slot.insert(Interned::One(index));
}
Entry::Occupied(mut slot) => match slot.get_mut() {
Interned::Several(indexes) => indexes.push(index),
Interned::One(first) => {
let first = *first;
slot.insert(Interned::Several(vec![first, index]));
}
},
}
}
fn recycle_names(&mut self) {
let spare = &mut self.spare;
for name in self.names.drain(..) {
Ast::keep_buffer(spare, name.text);
Ast::keep_buffer(spare, name.folded);
}
}
fn keep_buffer(spare: &mut Vec<Vec<u8>>, mut buffer: Vec<u8>) {
if spare.len() >= SPARE_NAME_BUFFERS
|| buffer.capacity() == 0
|| buffer.capacity() > SPARE_NAME_CAPACITY
{
return;
}
buffer.clear();
spare.push(buffer);
}
fn take_buffer(spare: &mut Vec<Vec<u8>>) -> Vec<u8> {
spare.pop().unwrap_or_default()
}
pub fn name_count(&self) -> usize {
self.names.len()
}
pub fn max_expr_depth(&self) -> u32 {
self.max_expr_depth
}
pub fn expr_depth(&self, id: ExprId) -> u32 {
self.expr_depths.get(id.0 as usize).copied().unwrap_or(0)
}
pub fn name(&self, id: NameId) -> Option<&Name> {
self.names.get(id.0 as usize)
}
pub fn folded(&self, id: NameId) -> &[u8] {
self.names.get(id.0 as usize).map_or(&[], |n| &n.folded)
}
pub fn text(&self, id: NameId) -> &[u8] {
self.names.get(id.0 as usize).map_or(&[], |n| &n.text)
}
pub fn add_expr(&mut self, expr: Expr, span: Span) -> ExprId {
self.bytes = self
.bytes
.saturating_add(core::mem::size_of::<Expr>().saturating_add(8));
let depth = self.depth_of(&expr);
self.max_expr_depth = self.max_expr_depth.max(depth);
self.exprs.push(expr);
self.expr_spans.push(span);
self.expr_depths.push(depth);
ExprId(self.exprs.len().saturating_sub(1) as u32)
}
fn depth_of(&self, expr: &Expr) -> u32 {
let deepest = |ids: &[ExprId]| -> u32 {
ids.iter().map(|id| self.expr_depth(*id)).max().unwrap_or(0)
};
let children = match expr {
Expr::Literal(_)
| Expr::Parameter { .. }
| Expr::Column { .. }
| Expr::Star { .. }
| Expr::Exists { .. }
| Expr::Subquery(_)
| Expr::Raise { .. } => 0,
Expr::Unary { operand, .. }
| Expr::Collate { operand, .. }
| Expr::Cast { operand, .. }
| Expr::IsNull { operand, .. } => self.expr_depth(*operand),
Expr::Binary { left, right, .. } | Expr::Is { left, right, .. } => {
self.expr_depth(*left).max(self.expr_depth(*right))
}
Expr::Pattern {
operand,
pattern,
escape,
..
} => self
.expr_depth(*operand)
.max(self.expr_depth(*pattern))
.max(escape.map(|id| self.expr_depth(id)).unwrap_or(0)),
Expr::Between {
operand, low, high, ..
} => self
.expr_depth(*operand)
.max(self.expr_depth(*low))
.max(self.expr_depth(*high)),
Expr::In { operand, rhs, .. } => {
let right = match rhs {
InRhs::List(ids) => deepest(ids),
InRhs::Select(_) => 0,
InRhs::Table { arguments, .. } => {
arguments.as_deref().map(deepest).unwrap_or(0)
}
};
self.expr_depth(*operand).max(right)
}
Expr::Case {
operand,
branches,
otherwise,
} => {
let mut deep = operand.map(|id| self.expr_depth(id)).unwrap_or(0);
for (when, then) in branches {
deep = deep.max(self.expr_depth(*when)).max(self.expr_depth(*then));
}
deep.max(otherwise.map(|id| self.expr_depth(id)).unwrap_or(0))
}
Expr::Function {
arguments, filter, ..
} => arguments
.as_deref()
.map(deepest)
.unwrap_or(0)
.max(filter.map(|id| self.expr_depth(id)).unwrap_or(0)),
Expr::RowValue(ids) => deepest(ids),
};
children.saturating_add(1)
}
pub fn expr(&self, id: ExprId) -> Option<&Expr> {
self.exprs.get(id.0 as usize)
}
pub fn expr_span(&self, id: ExprId) -> Span {
self.expr_spans
.get(id.0 as usize)
.copied()
.unwrap_or_default()
}
pub fn expr_count(&self) -> usize {
self.exprs.len()
}
pub fn add_select(&mut self, select: Select) -> SelectId {
self.bytes = self
.bytes
.saturating_add(core::mem::size_of::<Select>().saturating_add(32));
self.selects.push(select);
SelectId(self.selects.len().saturating_sub(1) as u32)
}
pub fn select(&self, id: SelectId) -> Option<&Select> {
self.selects.get(id.0 as usize)
}
pub fn add_core(&mut self, core: SelectCore) -> SelectCoreId {
self.bytes = self
.bytes
.saturating_add(core::mem::size_of::<SelectCore>().saturating_add(64));
self.cores.push(core);
SelectCoreId(self.cores.len().saturating_sub(1) as u32)
}
pub fn core(&self, id: SelectCoreId) -> Option<&SelectCore> {
self.cores.get(id.0 as usize)
}
pub fn add_from_term(&mut self, term: FromTerm) -> FromTermId {
self.bytes = self
.bytes
.saturating_add(core::mem::size_of::<FromTerm>().saturating_add(32));
self.from_terms.push(term);
FromTermId(self.from_terms.len().saturating_sub(1) as u32)
}
pub fn from_term(&self, id: FromTermId) -> Option<&FromTerm> {
self.from_terms.get(id.0 as usize)
}
pub fn from_term_mut(&mut self, id: FromTermId) -> Option<&mut FromTerm> {
self.from_terms.get_mut(id.0 as usize)
}
pub fn add_window(&mut self, window: Window) -> WindowId {
self.bytes = self
.bytes
.saturating_add(core::mem::size_of::<Window>().saturating_add(32));
self.windows.push(window);
WindowId(self.windows.len().saturating_sub(1) as u32)
}
pub fn window(&self, id: WindowId) -> Option<&Window> {
self.windows.get(id.0 as usize)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interning_keeps_the_spelling_and_folds_the_key() {
let mut ast = Ast::new();
let lower = ast.intern(b"abc".to_vec(), QuoteForm::Bare, Span::default());
let upper = ast.intern(b"ABC".to_vec(), QuoteForm::Bare, Span::default());
let again = ast.intern(b"abc".to_vec(), QuoteForm::Bare, Span::default());
assert_eq!(lower, again);
assert_ne!(lower, upper);
assert_eq!(ast.folded(lower), ast.folded(upper));
assert_eq!(ast.text(upper), b"ABC");
}
#[test]
fn an_unknown_id_returns_none_rather_than_panicking() {
let ast = Ast::new();
assert!(ast.expr(ExprId(7)).is_none());
assert!(ast.select(SelectId(7)).is_none());
assert!(ast.name(NameId(7)).is_none());
assert_eq!(ast.expr_span(ExprId(7)), Span::default());
}
#[test]
fn the_arena_charges_for_what_it_holds() {
let mut ast = Ast::new();
let before = ast.charged_bytes();
ast.add_expr(Expr::Literal(Literal::Null), Span::default());
assert!(ast.charged_bytes() > before);
}
#[test]
fn the_borrowed_and_owned_entry_points_intern_the_same_name() {
let mut ast = Ast::new();
let owned = ast.intern(b"col".to_vec(), QuoteForm::Bare, Span::default());
let borrowed = ast.intern_bytes(b"col", QuoteForm::Bare, Span::default());
assert_eq!(owned, borrowed);
assert_eq!(ast.name_count(), 1);
assert_eq!(ast.text(owned), b"col");
assert_eq!(ast.folded(owned), b"col");
}
#[test]
fn the_quote_form_separates_two_names_that_spell_the_same_word() {
let mut ast = Ast::new();
let bare = ast.intern_bytes(b"x", QuoteForm::Bare, Span::default());
let quoted = ast.intern_bytes(b"x", QuoteForm::Double, Span::default());
assert_ne!(bare, quoted);
assert_eq!(ast.name_count(), 2);
assert_eq!(
ast.intern_bytes(b"x", QuoteForm::Bare, Span::default()),
bare
);
assert_eq!(
ast.intern_bytes(b"x", QuoteForm::Double, Span::default()),
quoted
);
}
#[test]
fn a_name_filed_under_another_names_hash_gets_its_own_id() {
let mut ast = Ast::new();
let alpha = ast.intern_bytes(b"alpha", QuoteForm::Bare, Span::default());
let stolen = ast.hash_of(b"gamma", QuoteForm::Bare);
ast.remember_interned(stolen, alpha.0);
let gamma = ast.intern_bytes(b"gamma", QuoteForm::Bare, Span::default());
assert_ne!(gamma, alpha);
assert_eq!(ast.text(gamma), b"gamma");
assert_eq!(ast.text(alpha), b"alpha");
assert_eq!(
ast.intern_bytes(b"gamma", QuoteForm::Bare, Span::default()),
gamma
);
assert_eq!(
ast.intern_bytes(b"alpha", QuoteForm::Bare, Span::default()),
alpha
);
assert_eq!(ast.name_count(), 2);
}
#[test]
fn many_names_each_keep_their_own_id() {
let mut ast = Ast::new();
let spellings: Vec<Vec<u8>> = (0..200)
.map(|nth| format!("column_{nth}").into_bytes())
.collect();
let ids: Vec<NameId> = spellings
.iter()
.map(|text| ast.intern_bytes(text, QuoteForm::Bare, Span::default()))
.collect();
assert_eq!(ast.name_count(), 200);
for (text, id) in spellings.iter().zip(&ids) {
assert_eq!(
ast.intern_bytes(text, QuoteForm::Bare, Span::default()),
*id
);
assert_eq!(ast.text(*id), text.as_slice());
}
let mut sorted = ids.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), 200);
}
#[test]
fn clearing_keeps_the_name_buffers_and_the_names_capacity() {
let mut ast = Ast::new();
for nth in 0..4u32 {
ast.intern_bytes(
format!("c{nth}").as_bytes(),
QuoteForm::Bare,
Span::default(),
);
}
let capacity = ast.names.capacity();
assert!(capacity >= 4);
ast.clear();
assert_eq!(ast.name_count(), 0);
assert_eq!(ast.names.capacity(), capacity);
assert_eq!(ast.spare.len(), 8);
assert!(ast.spare.iter().all(|buffer| buffer.is_empty()));
for nth in 0..4u32 {
ast.intern_bytes(
format!("c{nth}").as_bytes(),
QuoteForm::Bare,
Span::default(),
);
}
assert_eq!(ast.spare.len(), 0);
assert_eq!(ast.name_count(), 4);
assert_eq!(ast.text(NameId(2)), b"c2");
}
#[test]
fn the_free_list_does_not_grow_without_bound() {
let mut ast = Ast::new();
for nth in 0..2_000u32 {
ast.intern_bytes(
format!("column_{nth}").as_bytes(),
QuoteForm::Bare,
Span::default(),
);
}
ast.clear();
assert_eq!(ast.spare.len(), SPARE_NAME_BUFFERS);
let mut ast = Ast::new();
let long = vec![b'z'; SPARE_NAME_CAPACITY.saturating_add(1)];
ast.intern_bytes(&long, QuoteForm::Bare, Span::default());
ast.clear();
assert_eq!(ast.spare.len(), 0);
}
#[test]
fn two_arenas_holding_the_same_names_are_equal() {
let mut one = Ast::new();
let mut two = Ast::new();
for text in [b"alpha".as_slice(), b"beta".as_slice()] {
one.intern_bytes(text, QuoteForm::Bare, Span::default());
two.intern_bytes(text, QuoteForm::Bare, Span::default());
}
assert_eq!(one, two);
two.intern_bytes(b"gamma", QuoteForm::Bare, Span::default());
assert_ne!(one, two);
}
}