Skip to content

Commit

Permalink
get append to work in venndb
Browse files Browse the repository at this point in the history
  • Loading branch information
GlenDC committed Apr 5, 2024
1 parent 61a2732 commit 13920c1
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 60 deletions.
68 changes: 68 additions & 0 deletions src/field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! Struct Field Info

use crate::{
errors::Errors,
parse_attrs::{FieldAttrs, FieldKind},
};
use quote::format_ident;
use syn::Ident;

/// A field of a `#![derive(VennDB)]` struct with attributes and some other
/// notable metadata appended.
pub struct StructField<'a> {
/// The original parsed field
field: &'a syn::Field,
/// The parsed attributes of the field
attrs: FieldAttrs,
/// The field name. This is contained optionally inside `field`,
/// but is duplicated non-optionally here to indicate that all field that
/// have reached this point must have a field name, and it no longer
/// needs to be unwrapped.
name: &'a syn::Ident,
}

pub enum FieldInfo<'a> {
Key(KeyField<'a>),
}

pub struct KeyField<'a> {
pub name: &'a Ident,
pub ty: &'a syn::Type,
}

impl<'a> KeyField<'a> {
pub fn name(&'a self) -> &'a Ident {
self.name
}

pub fn ty(&'a self) -> &'a syn::Type {
self.ty
}

pub fn method_name(&self) -> Ident {
format_ident!("get_by_{}", self.name)
}

pub fn map_name(&self) -> Ident {
format_ident!("map_{}", self.name)
}
}

impl<'a> StructField<'a> {
/// Attempts to parse a field of a `#[derive(VennDB)]` struct, pulling out the
/// fields required for code generation.
pub fn new(_errors: &Errors, field: &'a syn::Field, attrs: FieldAttrs) -> Option<Self> {
let name = field.ident.as_ref().expect("missing ident for named field");
Some(StructField { field, attrs, name })
}

/// Return the method name for this struct field.
pub fn info(&self) -> Option<FieldInfo> {
self.attrs.kind.as_ref().map(|kind| match kind {
FieldKind::Key => FieldInfo::Key(KeyField {
name: self.name,
ty: &self.field.ty,
}),
})
}
}
263 changes: 263 additions & 0 deletions src/generate_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
use crate::field::{FieldInfo, StructField};
use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;

/// Generate the venndb logic
pub fn generate_db(
name: &Ident,
name_db: &Ident,
vis: &syn::Visibility,
fields: &[StructField],
) -> TokenStream {
let fields: Vec<_> = fields.iter().filter_map(StructField::info).collect();

let db_struct = generate_db_struct(name, name_db, vis, &fields[..]);
let db_struct_methods = generate_db_struct_methods(name, name_db, vis, &fields[..]);

let db_query = generate_query_struct(name, name_db, vis, &fields[..]);
let db_query_methods = generate_query_struct_methods(name, name_db, vis, &fields[..]);

quote! {
#db_struct

#db_struct_methods

#db_query

#db_query_methods
}
}

fn generate_db_struct(
name: &Ident,
name_db: &Ident,
vis: &syn::Visibility,
fields: &[FieldInfo],
) -> TokenStream {
let db_fields: Vec<_> = fields
.iter()
.map(|info| match info {
FieldInfo::Key(field) => {
let field_name = field.map_name();
let ty: &syn::Type = field.ty();
quote! {
#field_name: std::collections::HashMap<#ty, usize>,
}
}
})
.collect();

let db_doc = format!(
"An in-memory database for storing instances of [`{}`], generated by `#[derive(VennDB)]`.",
name
);
quote! {
#[doc=#db_doc]
#vis struct #name_db {
rows: Vec<#name>,
#(#db_fields)*
}
}
}

fn generate_db_struct_methods(
name: &Ident,
name_db: &Ident,
vis: &syn::Visibility,
fields: &[FieldInfo],
) -> TokenStream {
let method_new = generate_db_struct_method_new(name, name_db, vis, fields);
let method_with_capacity = generate_db_struct_method_with_capacity(name, name_db, vis, fields);
let key_methods = generate_db_struct_methods_key(name, name_db, vis, fields);
let method_append = generate_db_struct_method_append(name, name_db, vis, fields);

quote! {
impl #name_db {
#method_new

#method_with_capacity

/// Return the number of rows in the database.
#vis fn len(&self) -> usize {
self.rows.len()
}

/// Return the capacity of the database,
/// which is automatically grown as needed.
#vis fn capacity(&self) -> usize {
self.rows.capacity()
}

/// Return `true` if the database is empty.
#vis fn is_empty(&self) -> bool {
self.rows.is_empty()
}

#key_methods

#method_append
}

impl Default for #name_db {
fn default() -> Self {
Self::new()
}
}
}
}

pub fn generate_db_struct_method_new(
name: &Ident,
_name_db: &Ident,
vis: &syn::Visibility,
fields: &[FieldInfo],
) -> TokenStream {
let method_doc = format!(
"Construct a new empty database for storing instances of [`{}`].",
name
);

let db_fields_initialisers: Vec<_> = fields
.iter()
.map(|info| match info {
FieldInfo::Key(field) => {
let name = field.map_name();
quote! {
#name: std::collections::HashMap::new(),
}
}
})
.collect();

quote! {
#[doc=#method_doc]
#vis fn new() -> Self {
Self {
rows: Vec::new(),
#(#db_fields_initialisers)*
}
}
}
}

pub fn generate_db_struct_method_append(
name: &Ident,
_name_db: &Ident,
vis: &syn::Visibility,
fields: &[FieldInfo],
) -> TokenStream {
let method_doc = format!("Append a new instance of [`{}`] to the database.", name);

let db_field_inserts: Vec<_> = fields
.iter()
.map(|info| match info {
FieldInfo::Key(field) => {
let map_name = field.map_name();
let field_name = field.name();

quote! {
self.#map_name.insert(data.#field_name.clone(), index);
}
}
})
.collect();

quote! {
#[doc=#method_doc]
#vis fn append(&mut self, data: #name) {
let index = self.rows.len();

if index == self.rows.capacity() {
// TODO: double the bitvecs in size...
}

#(#db_field_inserts)*

self.rows.push(data);
}
}
}

pub fn generate_db_struct_method_with_capacity(
name: &Ident,
_name_db: &Ident,
vis: &syn::Visibility,
fields: &[FieldInfo],
) -> TokenStream {
let method_doc = format!(
"Construct a new empty database for storing instances of [`{}`] with a given capacity.",
name
);

let db_fields_initialisers_with_capacity: Vec<_> = fields
.iter()
.map(|info| match info {
FieldInfo::Key(field) => {
let name = field.map_name();
quote! {
#name: std::collections::HashMap::with_capacity(capacity),
}
}
})
.collect();

quote! {
#[doc=#method_doc]
#vis fn with_capacity(capacity: usize) -> Self {
Self {
rows: Vec::new(),
#(#db_fields_initialisers_with_capacity)*
}
}
}
}

pub fn generate_db_struct_methods_key(
name: &Ident,
_name_db: &Ident,
vis: &syn::Visibility,
fields: &[FieldInfo],
) -> TokenStream {
let db_key_methods: Vec<_> = fields
.iter()
.map(|info| match info {
FieldInfo::Key(field) => {
let map_name = field.map_name();
let ty = field.ty();
let method_name = field.method_name();
quote! {
#vis fn #method_name<Q>(&self, key: &Q) -> std::option::Option<&#name>
where
#ty: std::borrow::Borrow<Q>,
Q: std::hash::Hash + std::cmp::Eq + ?std::marker::Sized,
{
self.#map_name.get(key).and_then(|index| self.rows.get(*index))
}
}
}
})
.collect();

quote! {
#(#db_key_methods)*
}
}

fn generate_query_struct(
_name: &Ident,
_name_db: &Ident,
_vis: &syn::Visibility,
_fields: &[FieldInfo],
) -> TokenStream {
TokenStream::new() // TOOD
}

fn generate_query_struct_methods(
_name: &Ident,
_name_db: &Ident,
_vis: &syn::Visibility,
_fields: &[FieldInfo],
) -> TokenStream {
TokenStream::new() // TOOD
}
Loading

0 comments on commit 13920c1

Please sign in to comment.