Initial commit

This commit is contained in:
Aleksandr 2024-10-15 19:03:39 +03:00
commit 9282b8565d
24 changed files with 599 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]

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,73 @@
//! # Interning arena.
use std::{hash::Hash, num::NonZeroU32};
use hashbrown::hash_map::RawEntryMut;
use crate::collections::HashMap;
use super::wonly;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
struct Mapping<P> {
hash: u64,
key: P,
}
#[derive(Clone)]
pub struct Arena<T, P> {
objects: wonly::Arena<T, P>,
// No raw_entry API on hashset.
bi: HashMap<Mapping<P>, ()>,
}
impl<T, P> Default for Arena<T, P> {
fn default() -> Self {
Self {
objects: wonly::Arena::new(),
bi: HashMap::default(),
}
}
}
impl<T, P> Arena<T, P> {
pub fn get(&self, ptr: P) -> Option<&T>
where
NonZeroU32: From<P>,
{
self.objects.get(ptr)
}
pub fn insert(&mut self, object: T) -> P
where
P: From<NonZeroU32> + Hash + Clone,
NonZeroU32: From<P>,
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(
Mapping {
hash,
key: key.clone(),
},
(),
);
key
}
}
}
pub fn new() -> Self {
Self::default()
}
}

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::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 From<$name> for ::core::num::NonZeroU32 {
fn from(value: $name) -> Self {
value.get()
}
}
impl From<::core::num::NonZeroU32> for $name {
fn from(value: ::core::num::NonZeroU32) -> Self {
Self::new(value)
}
}
impl $crate::ptr::Ptr for $name {
fn get(&self) -> ::core::num::NonZeroU32 {
self.0
}
}
)*};
}
/// Pointer in arena.
pub trait Ptr {
fn get(&self) -> NonZeroU32;
}
pub use crate::_ptrs as define;

View file

@ -0,0 +1,52 @@
//! # Write-only arena, without deletion
use std::{marker::PhantomData, num::NonZeroU32};
#[derive(Clone)]
pub struct Arena<T, P> {
mem: Vec<T>,
_ptr_ty: PhantomData<P>,
}
impl<T, P> Arena<T, P> {
pub fn get(&self, ptr: P) -> Option<&T>
where
P: Into<NonZeroU32>,
{
let index = ptr.into().get() as usize - 1;
self.mem.get(index)
}
pub fn insert(&mut self, obj: T) -> P
where
NonZeroU32: Into<P>,
{
let idx = self.mem.len();
self.mem.push(obj);
let ptr = idx
.checked_add(1)
.and_then(|v| v.try_into().ok())
.and_then(NonZeroU32::new)
.expect("shit happens")
.into();
ptr
}
}
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_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 @@
//! # Type checking.

View file

@ -0,0 +1,3 @@
//! # Type inference.
pub struct 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_arena::ptrs;
ptrs! {
/// 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!");
}