commit 9282b8565d5c241f60a94595c04fbd945ed784d2 Author: Aleksandr Date: Tue Oct 15 19:03:39 2024 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..178a70b --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..030771d --- /dev/null +++ b/Cargo.toml @@ -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] + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1f09d4 --- /dev/null +++ b/README.md @@ -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. + diff --git a/crates/std/Cargo.toml b/crates/std/Cargo.toml new file mode 100644 index 0000000..f391d34 --- /dev/null +++ b/crates/std/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "shiny-std" +version = "0.1.0" +edition = "2021" + +[dependencies] +fxhash.workspace = true +hashbrown.workspace = true diff --git a/crates/std/src/arena/intern.rs b/crates/std/src/arena/intern.rs new file mode 100644 index 0000000..f15c190 --- /dev/null +++ b/crates/std/src/arena/intern.rs @@ -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

{ + hash: u64, + key: P, +} + +#[derive(Clone)] +pub struct Arena { + objects: wonly::Arena, + // No raw_entry API on hashset. + bi: HashMap, ()>, +} + +impl Default for Arena { + fn default() -> Self { + Self { + objects: wonly::Arena::new(), + bi: HashMap::default(), + } + } +} + +impl Arena { + pub fn get(&self, ptr: P) -> Option<&T> + where + NonZeroU32: From

, + { + self.objects.get(ptr) + } + + pub fn insert(&mut self, object: T) -> P + where + P: From + Hash + Clone, + NonZeroU32: From

, + 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() + } +} diff --git a/crates/std/src/arena/macros.rs b/crates/std/src/arena/macros.rs new file mode 100644 index 0000000..66571a6 --- /dev/null +++ b/crates/std/src/arena/macros.rs @@ -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 + )*}; +} diff --git a/crates/std/src/arena/mod.rs b/crates/std/src/arena/mod.rs new file mode 100644 index 0000000..b084e6c --- /dev/null +++ b/crates/std/src/arena/mod.rs @@ -0,0 +1,8 @@ +pub mod intern; +pub mod wonly; + +pub mod ptr; + +pub use crate::_make_comb as make_comb; + +mod macros; diff --git a/crates/std/src/arena/ptr.rs b/crates/std/src/arena/ptr.rs new file mode 100644 index 0000000..8864c83 --- /dev/null +++ b/crates/std/src/arena/ptr.rs @@ -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; diff --git a/crates/std/src/arena/wonly.rs b/crates/std/src/arena/wonly.rs new file mode 100644 index 0000000..dbeb6b1 --- /dev/null +++ b/crates/std/src/arena/wonly.rs @@ -0,0 +1,52 @@ +//! # Write-only arena, without deletion + +use std::{marker::PhantomData, num::NonZeroU32}; + +#[derive(Clone)] +pub struct Arena { + mem: Vec, + _ptr_ty: PhantomData

, +} + +impl Arena { + pub fn get(&self, ptr: P) -> Option<&T> + where + P: Into, + { + let index = ptr.into().get() as usize - 1; + + self.mem.get(index) + } + + pub fn insert(&mut self, obj: T) -> P + where + NonZeroU32: Into

, + { + 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 Default for Arena { + fn default() -> Self { + Self::new() + } +} + +impl Arena { + pub const fn new() -> Self { + Self { + mem: Vec::new(), + _ptr_ty: PhantomData, + } + } +} diff --git a/crates/std/src/collections.rs b/crates/std/src/collections.rs new file mode 100644 index 0000000..f3783f5 --- /dev/null +++ b/crates/std/src/collections.rs @@ -0,0 +1,6 @@ +use std::hash::BuildHasherDefault; + +use fxhash::FxHasher; + +pub type HashMap = hashbrown::HashMap>; +pub type HashSet = hashbrown::HashSet>; diff --git a/crates/std/src/lib.rs b/crates/std/src/lib.rs new file mode 100644 index 0000000..dc94b8d --- /dev/null +++ b/crates/std/src/lib.rs @@ -0,0 +1,3 @@ +pub mod collections; + +pub mod arena; diff --git a/crates/typing/Cargo.toml b/crates/typing/Cargo.toml new file mode 100644 index 0000000..5a34e8a --- /dev/null +++ b/crates/typing/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "shiny-typing" +version = "0.1.0" +edition = "2021" + +[dependencies] +shiny-std.workspace = true diff --git a/crates/typing/src/bin.rs b/crates/typing/src/bin.rs new file mode 100644 index 0000000..6d11510 --- /dev/null +++ b/crates/typing/src/bin.rs @@ -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, + } +} diff --git a/crates/typing/src/check/mod.rs b/crates/typing/src/check/mod.rs new file mode 100644 index 0000000..670bde8 --- /dev/null +++ b/crates/typing/src/check/mod.rs @@ -0,0 +1 @@ +//! # Type checking. diff --git a/crates/typing/src/infer/mod.rs b/crates/typing/src/infer/mod.rs new file mode 100644 index 0000000..f663c4c --- /dev/null +++ b/crates/typing/src/infer/mod.rs @@ -0,0 +1,3 @@ +//! # Type inference. + +pub struct Ctx {} diff --git a/crates/typing/src/int.rs b/crates/typing/src/int.rs new file mode 100644 index 0000000..dd50a90 --- /dev/null +++ b/crates/typing/src/int.rs @@ -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); diff --git a/crates/typing/src/lib.rs b/crates/typing/src/lib.rs new file mode 100644 index 0000000..63d3b24 --- /dev/null +++ b/crates/typing/src/lib.rs @@ -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; diff --git a/crates/typing/src/pat.rs b/crates/typing/src/pat.rs new file mode 100644 index 0000000..d24f0d1 --- /dev/null +++ b/crates/typing/src/pat.rs @@ -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); +} diff --git a/crates/typing/src/ptr.rs b/crates/typing/src/ptr.rs new file mode 100644 index 0000000..df54d3d --- /dev/null +++ b/crates/typing/src/ptr.rs @@ -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; +} diff --git a/crates/typing/src/un.rs b/crates/typing/src/un.rs new file mode 100644 index 0000000..bb6fefa --- /dev/null +++ b/crates/typing/src/un.rs @@ -0,0 +1 @@ +//! # Unary types diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..55ce606 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ba9ead9 --- /dev/null +++ b/flake.nix @@ -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 ]; + }; + } + ); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}