Initial commit

This commit is contained in:
Aleksandr 2024-10-15 19:03:39 +03:00
commit 85b08fe00b
29 changed files with 750 additions and 0 deletions

8
crates/std/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "shiny-std"
version = "0.1.0"
edition = "2021"
[dependencies]
fxhash.workspace = true
hashbrown.workspace = true

View file

@ -0,0 +1,147 @@
//! # Interning arena.
//!
//! Additionally, if same value was present on the arena, doesn't add new object, returns old.
use std::hash::{BuildHasherDefault, Hash};
use hashbrown::hash_map::{HashMap, RawEntryMut};
use super::{ptr::Ptr, wonly};
// No raw_entry API on hashset.
type Bi<P> = HashMap<Mapping<P>, (), BuildHasherDefault<hasher::NoHash>>;
#[derive(Debug, Default, Clone, Copy)]
struct Mapping<P> {
hash: u64,
key: P,
}
impl<P> Hash for Mapping<P> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
// [`hasher::NoHash`] implementation ensures that
// hasher stores `write_u64` result only once.
state.write_u64(self.hash);
}
}
#[derive(Clone)]
pub struct Arena<T, P> {
objects: wonly::Arena<T, P>,
bi: Bi<P>,
}
impl<T, P> Default for Arena<T, P> {
fn default() -> Self {
Self {
objects: wonly::Arena::new(),
bi: Bi::default(),
}
}
}
impl<T, P> Arena<T, P> {
pub fn modify(&mut self, ptr: P, f: impl FnOnce(&mut T)) -> bool
where
T: Hash + Eq,
P: Ptr,
{
let Some(object) = self.objects.get(ptr.clone()) else {
return false;
};
let hash = fxhash::hash64(object);
let entry = self
.bi
.raw_entry_mut()
.from_hash(hash, |m| self.objects.get(m.key.clone()) == Some(object));
// SAFETY: if we got here, the object is in arena.
let object = unsafe { self.objects.get_mut(ptr.clone()).unwrap_unchecked() };
f(object);
let hash = fxhash::hash64(object);
entry.and_modify(|k, _| *k = Mapping { hash, key: ptr });
true
}
pub fn get(&self, ptr: P) -> Option<&T>
where
P: Ptr,
{
self.objects.get(ptr)
}
pub fn insert(&mut self, object: T) -> P
where
P: Ptr,
T: Hash + Eq,
{
let hash = fxhash::hash64(&object);
match self.bi.raw_entry_mut().from_hash(hash, |m| {
self.objects
.get(m.key.clone())
.map_or(false, |lhs| lhs == &object)
}) {
RawEntryMut::Occupied(occup) => occup.into_key().key.clone(),
RawEntryMut::Vacant(vacant) => {
let key = self.objects.insert(object);
vacant.insert_hashed_nocheck(
hash,
Mapping {
hash,
key: key.clone(),
},
(),
);
key
}
}
}
pub fn new() -> Self {
Self::default()
}
}
mod hasher;
#[cfg(test)]
mod tests {
use super::*;
use crate::arena::ptr::define;
define! {
struct Token;
}
#[test]
fn modification_works() {
// Ensure that when modifying the value,
// pointer is still valid.
let mut arena: Arena<u32, Token> = Arena::new();
let key100to200 = arena.insert(100);
let modified = arena.modify(key100to200, |x| *x = 200);
assert!(modified);
assert_eq!(arena.get(key100to200), Some(&200));
let key100new = arena.insert(100);
assert_ne!(key100to200, key100new);
assert_eq!(arena.get(key100new), Some(&100));
assert_eq!(arena.get(key100to200), Some(&200));
}
#[test]
fn it_interns() {
let mut arena: Arena<u32, Token> = Arena::new();
let key100 = arena.insert(100);
let key100again = arena.insert(100);
assert_eq!(key100, key100again);
}
}

View file

@ -0,0 +1,22 @@
use std::hash::Hasher;
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub struct NoHash(Option<u64>);
impl Hasher for NoHash {
fn write_u64(&mut self, i: u64) {
if self.0.is_some() {
panic!("called write_u64 twice")
} else {
self.0 = Some(i);
}
}
fn write(&mut self, _: &[u8]) {
unreachable!()
}
fn finish(&self) -> u64 {
self.0.unwrap()
}
}

View file

@ -0,0 +1,20 @@
/// Combination of pointers.
#[macro_export]
#[doc(hidden)]
macro_rules! _make_comb {
($(
$item:item
)*) => {$(
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
)]
$item
)*};
}

View file

@ -0,0 +1,8 @@
pub mod intern;
pub mod wonly;
pub mod ptr;
pub use crate::_make_comb as make_comb;
mod macros;

View file

@ -0,0 +1,59 @@
use std::{hash::Hash, num::NonZeroU32};
#[macro_export]
#[doc(hidden)]
macro_rules! _ptrs {
($(
$(#[$outer:meta])*
$vis:vis struct $name:ident;
)*) => {$(
$(#[$outer])*
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
)]
$vis struct $name(::core::num::NonZeroU32);
impl $name {
/// Create instance of the pointer.
pub const fn new(v: ::core::num::NonZeroU32) -> Self {
Self(v)
}
pub const fn get(&self) -> ::core::num::NonZeroU32 {
self.0
}
}
impl ::core::convert::From<$name> for ::core::num::NonZeroU32 {
fn from(value: $name) -> Self {
value.get()
}
}
impl ::core::convert::From<::core::num::NonZeroU32> for $name {
fn from(value: ::core::num::NonZeroU32) -> Self {
Self::new(value)
}
}
impl $crate::arena::ptr::Ptr for $name {
fn get(&self) -> ::core::num::NonZeroU32 {
self.0
}
}
)*};
}
/// Pointer in arena.
pub trait Ptr: Copy + Eq + Into<NonZeroU32> + From<NonZeroU32> + Hash {
fn get(&self) -> NonZeroU32;
}
pub use crate::_ptrs as define;

View file

@ -0,0 +1,60 @@
//! # Write-only arena, without deletion
use std::{marker::PhantomData, num::NonZeroU32};
use super::ptr::Ptr;
#[derive(Clone)]
pub struct Arena<T, P> {
mem: Vec<T>,
_ptr_ty: PhantomData<P>,
}
fn calc_idx(v: NonZeroU32) -> usize {
v.get() as usize - 1
}
impl<T, P> Arena<T, P> {
pub fn get_mut(&mut self, ptr: P) -> Option<&mut T>
where
P: Ptr,
{
self.mem.get_mut(calc_idx(ptr.into()))
}
pub fn get(&self, ptr: P) -> Option<&T>
where
P: Ptr,
{
self.mem.get(calc_idx(ptr.into()))
}
pub fn insert(&mut self, obj: T) -> P
where
P: Ptr,
{
let idx = self.mem.len();
self.mem.push(obj);
idx.checked_add(1)
.and_then(|v| v.try_into().ok())
.and_then(NonZeroU32::new)
.expect("shit happens")
.into()
}
}
impl<T, P> Default for Arena<T, P> {
fn default() -> Self {
Self::new()
}
}
impl<T, P> Arena<T, P> {
pub const fn new() -> Self {
Self {
mem: Vec::new(),
_ptr_ty: PhantomData,
}
}
}

View file

@ -0,0 +1,6 @@
use std::hash::BuildHasherDefault;
use fxhash::FxHasher;
pub type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<FxHasher>>;
pub type HashSet<V> = hashbrown::HashSet<V, BuildHasherDefault<FxHasher>>;

3
crates/std/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod collections;
pub mod arena;

7
crates/typing/Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "shiny-typing"
version = "0.1.0"
edition = "2021"
[dependencies]
shiny-std.workspace = true

33
crates/typing/src/bin.rs Normal file
View file

@ -0,0 +1,33 @@
//! # Binary types
use shiny_std::arena::make_comb;
use crate::ptr;
make_comb! {
/// Type with additionally equipped ID.
pub struct Field {
pub ty: ptr::Ty,
pub id: ptr::Id,
}
/// Product type. Pretty much just `lhs_ty * rhs_ty`.
/// The following properties must be upheld:
///
/// 1. lhs * rhs = rhs * lhs <- commutativity
/// 2. (a * b) * c = a * (b * c) <- associativity
pub struct Prod {
pub lhs: ptr::Ty,
pub rhs: ptr::Ty,
}
/// Sum type. Pretty much just `lhs_ty + rhs_ty`.
/// The following properties must be upheld:
///
/// 1. lhs + rhs = rhs + rhs <- commutativity
/// 2. (a + b) + c = a + (b + c) <- associativity
pub struct Sum {
pub lhs: ptr::Ty,
pub rhs: ptr::Ty,
}
}

View file

@ -0,0 +1 @@
pub struct Ctx {}

View file

@ -0,0 +1,4 @@
//! # Type checking.
pub mod ctx;
pub mod shape;

View file

@ -0,0 +1,7 @@
use crate::ptr;
use super::ctx::Ctx;
pub fn fits(lhs: ptr::Ty, rhs: ptr::Ty) -> bool {
todo!()
}

View file

@ -0,0 +1,15 @@
use shiny_std::arena::intern;
use crate::{bin, int, ptr};
enum Ty {
Prod(bin::Prod),
Sum(bin::Sum),
Field(bin::Field),
Int(int::Int),
}
/// Inference context.
pub struct Ctx {
tys: intern::Arena<Ty, ptr::Ty>,
}

View file

@ -0,0 +1,3 @@
//! # Type inference.
mod ctx;

14
crates/typing/src/int.rs Normal file
View file

@ -0,0 +1,14 @@
//! # Integer types.
/// Sign of the integer.
#[derive(Debug, Clone, Copy)]
pub enum Signedness {
/// The number has sign.
Signed,
/// The number has no sign.
Unsigned,
}
/// Integer.
#[derive(Debug, Clone, Copy)]
pub struct Int(pub Signedness);

10
crates/typing/src/lib.rs Normal file
View file

@ -0,0 +1,10 @@
pub mod bin;
pub mod un;
pub mod int;
pub mod pat;
pub mod ptr;
pub mod check;
pub mod infer;

32
crates/typing/src/pat.rs Normal file
View file

@ -0,0 +1,32 @@
use shiny_arena::make_comb;
use crate::ptr;
make_comb! {
/// Product type pattern.
pub struct Prod {
pub lhs: ptr::Pat,
pub rhs: ptr::Pat,
}
/// Sum type pattern.
pub struct Sum {
pub lhs: ptr::Pat,
pub rhs: ptr::Pat,
}
/// Exact type. Matches only if matched type
/// equals to specified.
pub struct ExactTy(pub ptr::Ty);
/// Field type. matches `{ name : ty_pat }`
pub struct Field {
pub name: ptr::Id,
pub ty: ptr::Pat,
}
/// "Value" pattern. Works like [`ExactTy`], but
/// additionally checks whether value of that type is equal
/// to specified value.
pub struct Value(pub ptr::Value);
}

21
crates/typing/src/ptr.rs Normal file
View file

@ -0,0 +1,21 @@
//! # Pointers to needed information.
//!
//! If values are equal, then their pointers are equal.
//! This means that there's unambiguous mapping from id to value and
//! back.
use shiny_std::arena::ptr::define;
define! {
/// Pointer to a type.
pub struct Ty;
/// Pointer to ID.
pub struct Id;
/// Pointer to a pattern.
pub struct Pat;
/// Pointer to a *complete* value.
pub struct Value;
}

1
crates/typing/src/un.rs Normal file
View file

@ -0,0 +1 @@
//! # Unary types