#![allow(dead_code)]
use crate::parser::{Expr, TableEntry};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TableShapeKind {
Empty,
PureArray(usize),
Record(Vec<String>),
Mixed {
array_len: usize,
fields: Vec<String>,
},
Dynamic,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TableShape {
pub kind: TableShapeKind,
pub array_capacity: usize,
pub hash_capacity: usize,
pub has_duplicate_keys: bool,
}
impl TableShape {
pub fn analyze(entries: &[TableEntry]) -> Self {
if entries.is_empty() {
return Self {
kind: TableShapeKind::Empty,
array_capacity: 0,
hash_capacity: 0,
has_duplicate_keys: false,
};
}
let mut array_count = 0usize;
let mut field_names = Vec::new();
let mut seen_fields = std::collections::HashSet::new();
let mut has_duplicate_keys = false;
let mut is_pure_array = true;
let mut is_pure_record = true;
for entry in entries {
match &entry.key {
None => {
array_count += 1;
is_pure_record = false;
}
Some(name) => {
is_pure_array = false;
if !seen_fields.insert(name.clone()) {
has_duplicate_keys = true;
}
field_names.push(name.clone());
}
}
}
let kind = if is_pure_array {
TableShapeKind::PureArray(array_count)
} else if is_pure_record {
TableShapeKind::Record(field_names)
} else if array_count > 0 && !seen_fields.is_empty() {
TableShapeKind::Mixed {
array_len: array_count,
fields: field_names,
}
} else {
TableShapeKind::Dynamic
};
Self {
kind,
array_capacity: array_count,
hash_capacity: seen_fields.len(),
has_duplicate_keys,
}
}
pub fn qualifies_for_setlist(&self) -> bool {
match self.kind {
TableShapeKind::PureArray(len) => len > 0 && len <= 250,
TableShapeKind::Mixed { array_len, .. } => array_len > 0 && array_len <= 250,
_ => false,
}
}
}
pub fn analyze_expr_shapes(expr: &Expr, out: &mut Vec<TableShape>) {
match expr {
Expr::Table { entries, .. } => {
out.push(TableShape::analyze(entries));
for entry in entries {
analyze_expr_shapes(&entry.value, out);
}
}
Expr::Call { callee, args, .. } => {
analyze_expr_shapes(callee, out);
for arg in args {
analyze_expr_shapes(arg, out);
}
}
Expr::MethodCall { object, args, .. } => {
analyze_expr_shapes(object, out);
for arg in args {
analyze_expr_shapes(arg, out);
}
}
Expr::Binary { left, right, .. } => {
analyze_expr_shapes(left, out);
analyze_expr_shapes(right, out);
}
Expr::Unary { expr, .. } => {
analyze_expr_shapes(expr, out);
}
Expr::Member { object, .. } => {
analyze_expr_shapes(object, out);
}
Expr::Index { object, index, .. } => {
analyze_expr_shapes(object, out);
analyze_expr_shapes(index, out);
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_table_shape_pure_array() {
let entries = vec![
TableEntry {
key: None,
value: Expr::int(1),
},
TableEntry {
key: None,
value: Expr::int(2),
},
TableEntry {
key: None,
value: Expr::int(3),
},
];
let shape = TableShape::analyze(&entries);
assert_eq!(shape.kind, TableShapeKind::PureArray(3));
assert_eq!(shape.array_capacity, 3);
assert_eq!(shape.hash_capacity, 0);
assert!(shape.qualifies_for_setlist());
}
#[test]
fn test_table_shape_record() {
let entries = vec![
TableEntry {
key: Some("name".to_string()),
value: Expr::string("Neyuki"),
},
TableEntry {
key: Some("version".to_string()),
value: Expr::int(1),
},
];
let shape = TableShape::analyze(&entries);
assert_eq!(
shape.kind,
TableShapeKind::Record(vec!["name".to_string(), "version".to_string()])
);
assert_eq!(shape.array_capacity, 0);
assert_eq!(shape.hash_capacity, 2);
assert!(!shape.qualifies_for_setlist());
}
#[test]
fn test_table_shape_duplicate_keys() {
let entries = vec![
TableEntry {
key: Some("a".to_string()),
value: Expr::int(1),
},
TableEntry {
key: Some("a".to_string()),
value: Expr::int(2),
},
];
let shape = TableShape::analyze(&entries);
assert!(shape.has_duplicate_keys);
assert_eq!(shape.hash_capacity, 1);
}
}