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

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

66
Cargo.lock generated Normal file
View file

@ -0,0 +1,66 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "foldhash"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "shiny"
version = "0.1.0"
[[package]]
name = "shiny-std"
version = "0.1.0"
dependencies = [
"fxhash",
"hashbrown",
]
[[package]]
name = "shiny-typing"
version = "0.1.0"
dependencies = [
"shiny-std",
]

20
Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[package]
name = "shiny"
version = "0.1.0"
edition = "2021"
[workspace]
members = [
"crates/typing",
"crates/std",
]
[workspace.dependencies]
shiny-typing.path = "crates/typing"
shiny-std.path = "crates/std"
fxhash = "0.2.1"
hashbrown = "0.15.0"
[dependencies]

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Aleksandr
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

35
README.md Normal file
View file

@ -0,0 +1,35 @@
# Shiny
Expressive systems language. Inspired by Rust, but different, "shiny" is kinda different than "rust".
# Design
1. Types and layouts are different things. Type defines
**what** data is, **layout** defines how type laid in memory.
2. Typing discipline is structural, making language easier to use
for application programming. Most hugely focus on data and its properties, like
"the field xxx" needs to contain date between 2000.01.01 and 2027.01.01 or
"the token in request is valid". Clearly, these patterns can be implemented without
structural typing by using functions or traits, structural discipline makes it easier to write and
easier to introspect, since this allows to make narrower definition
of predicate in your domain, while passed function logic can be really arbitrary, it's more general,
and you have to deal with that.
3. Reusing pattern matching for types. In Rust, traits are used to constrain types,
in `shiny`, we reuse `pattern matching` to that kind of thing. So, all bounds are encoded
as "types fits in this shape" rather than as "the type implements these traits".
# Some notes
`shiny` has product and sum types, I call these "binary" types - combination of two types. As an example:
`i32 * i32`, alternatively can be written as `(i32, i32)`, also (1) `i32 * i32 * i32 * i32`
(2) `(i32, (i32, (i32, i32)))` and `(i32, i32, i32, i32)` are the same types, but (3) `i32 * (i32 * i32) * i32` are NOT, multiplication
operator here is left-associative, but all (1, 2, 3) types mentioned here are intechangable. Interchangable means here:
If we have constrained type with 1 or 2 or 3 (disambiguation: `or` here means exclusive `or`) **pattern**,
all mentioned types (1, 2, 3) will fulfill the constraint, by applying associativity and commutativity.
Necessary to note, if we have constraint, say, `i32 * str` and type `str * i32 * bool * i32 * str`, what should compiler choose?
Compiler simply would prefer part of the type that can be moved to head with less number of expression
rewrites. Less strict - we prefer types that are in head, rather than types that are in tail of the type.

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

82
flake.lock generated Normal file
View file

@ -0,0 +1,82 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1728492678,
"narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1728786660,
"narHash": "sha256-qY+1e0o6oV5ySlErhj/dsWsPLWjrMKzq4QI7a1t9/Ps=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "174a8d9cec9e2c23877a7b887c52b68ef0421d8b",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

41
flake.nix Normal file
View file

@ -0,0 +1,41 @@
{
description = "Shiny";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
nixpkgs,
flake-utils,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem(system:
let
overlays = [(import rust-overlay)];
pkgs = import nixpkgs { inherit system overlays; };
rust = rec {
package = pkgs.rust-bin.nightly.latest.default;
devPackage = package.override {
extensions = [ "rust-src" "rust-analyzer" ];
};
};
in
{
devShells.default = pkgs.mkShell {
shellHook = ''
export PS1="(shiny) $PS1"
'';
buildInputs = [ rust.devPackage ];
};
}
);
}

3
src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}