├── .cargo └── config.toml ├── .envrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .justfile ├── .vscode └── launch.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── crates ├── ast │ ├── Cargo.toml │ ├── src │ │ ├── lexer.rs │ │ ├── lib.rs │ │ ├── peek2.rs │ │ └── syntax.rs │ └── tests │ │ ├── syntax.ks │ │ └── tests.rs ├── derive-macros │ ├── Cargo.toml │ └── src │ │ ├── data.rs │ │ ├── expr_display.rs │ │ └── lib.rs ├── inference │ ├── Cargo.toml │ └── src │ │ ├── checks.rs │ │ ├── default.rs │ │ ├── global_state.rs │ │ └── lib.rs ├── refmap │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── try-hash │ ├── Cargo.toml │ ├── derive │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── src │ │ └── lib.rs │ └── tests │ │ └── tests.rs └── util │ ├── Cargo.toml │ └── src │ ├── display_option.rs │ ├── error.rs │ ├── id.rs │ ├── lib.rs │ ├── pad_adapter.rs │ ├── parc.rs │ ├── recurse_cache.rs │ ├── show_short.rs │ ├── source.rs │ └── tuple.rs ├── examples ├── aoc-2015-1.input.txt ├── aoc-2015-1.ks ├── ast-nested-scope.ks ├── asteroids │ ├── index.html │ ├── js.ks │ └── main.ks ├── big-list.ks ├── bijection.ks ├── bug.ks ├── context-shadow.ks ├── default-number-type.ks ├── embed.rs ├── enumerate.ks ├── exceptions.ks ├── exec-mode.ks ├── extrapolate.ks ├── f.ks ├── fibonacci.ks ├── for-generator.ks ├── generator.ks ├── guess-a-number.ks ├── hash_map.ks ├── hello.ks ├── import-zero.ks ├── inference-bug.ks ├── is.ks ├── late-inferred-context-type.ks ├── leetcode │ ├── 2894.ks │ └── 3373.ks ├── lines.ks ├── lists.ks ├── local-syntax.ks ├── loops.ks ├── macro-hygiene.ks ├── manual-loop.ks ├── match-in-macro.ks ├── monad.ks ├── move.ks ├── mut.ks ├── mutual-recursion.ks ├── names.ks ├── ternary.ks ├── throwing_map.ks ├── traits.ks ├── transitive-mutability.ks ├── transpile-to-javascript.ks ├── ttv.ks ├── tuples.ks ├── type-inspect.ks ├── unsafe.ks ├── unwind.ks ├── variant-types.ks └── zero.ks ├── flake.lock ├── flake.nix ├── src ├── cast.rs ├── cli.rs ├── comments.rs ├── compiler │ ├── builtins.rs │ └── mod.rs ├── contexts.rs ├── executor.rs ├── id.rs ├── interpreter │ ├── autocomplete.rs │ ├── mod.rs │ └── natives.rs ├── ir.rs ├── javascript.rs ├── lib.rs ├── main.rs ├── name.rs ├── place.rs ├── repl_helper.rs ├── rusty.rs ├── scopes │ ├── mod.rs │ └── scope.rs ├── target.rs ├── ty.rs └── value.rs ├── std ├── collections │ ├── _mod.ks │ ├── hash_map.ks │ └── list.ks ├── javascript.ks ├── lib.ks ├── syntax.ks └── time.ks └── tests ├── examples.rs ├── hash_map.output └── simple.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(windows)'] 2 | # rustflags = ["-C", "link-args=/STACK:1073741824"] # 1 GB 3 | rustflags = ["-C", "link-args=/STACK:33554432"] # 32 MB 4 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "Test Suite" 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | name: cargo test 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions-rust-lang/setup-rust-toolchain@v1 13 | with: 14 | rustflags: "" # allow warnings 15 | - run: cargo test --workspace --all-features 16 | env: 17 | RUST_MIN_STACK: 33554432 18 | 19 | # Check formatting with rustfmt 20 | formatting: 21 | name: cargo fmt 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | # Ensure rustfmt is installed and setup problem matcher 26 | - uses: actions-rust-lang/setup-rust-toolchain@v1 27 | with: 28 | components: rustfmt 29 | - name: Rustfmt Check 30 | uses: actions-rust-lang/rustfmt@v1 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build/ 2 | /.direnv/ 3 | /.flock/ 4 | /.logs/ 5 | /dist/ 6 | /target/ 7 | /test.ks 8 | /flamegraph.svg 9 | /perf.data* 10 | -------------------------------------------------------------------------------- /.justfile: -------------------------------------------------------------------------------- 1 | default: 2 | just --list 3 | 4 | testi *ARGS: 5 | RUST_MIN_STACK=33554432 cargo test --workspace --no-default-features -- {{ARGS}} 6 | test *ARGS: 7 | just testi --include-ignored {{ARGS}} 8 | 9 | repl *ARGS: 10 | just run repl {{ARGS}} 11 | 12 | run *ARGS: 13 | cargo run --no-default-features -- {{ARGS}} 14 | 15 | serve: 16 | cd website && zola serve 17 | 18 | asteroids: 19 | mkdir -p dist 20 | just run --to-js examples/asteroids/main.ks > dist/asteroids.js 21 | cp examples/asteroids/index.html dist/index.html 22 | caddy file-server --listen localhost:8000 --root dist 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--no-default-features", 15 | "--bin=kast", 16 | "--package=kast" 17 | ], 18 | "filter": { 19 | "name": "kast", 20 | "kind": "bin" 21 | } 22 | }, 23 | "args": [], 24 | "env": { 25 | "CARGO_MANIFEST_DIR": "${workspaceFolder}", 26 | "RUST_LOG": "kast=trace" 27 | }, 28 | "cwd": "${workspaceFolder}" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | 4 | [workspace.package] 5 | edition = "2024" 6 | version = "0.1.0" 7 | license = "MIT" 8 | repository = "https://github.com/kast-lang/kast" 9 | 10 | [workspace.dependencies] 11 | batbox-time = "0.16" 12 | parking_lot = "0.12" 13 | tokio = { version = "1", features = ["sync"] } 14 | spin = "0.10" 15 | clap = { version = "4.5.18", features = ["derive"] } 16 | eyre = "0.6.12" 17 | color-eyre = "0.6.3" 18 | noisy_float = "0.2.0" 19 | rustyline = "14.0.0" 20 | thiserror = "1.0.63" 21 | tracing = "0.1.40" 22 | tracing-subscriber = "0.3.18" 23 | rand = "0.8.5" 24 | colored = "2.1.0" 25 | futures = "0.3.30" 26 | async-once-cell = "0.5" 27 | async-oneshot = "0.5" 28 | async-notify = "0.3.0" 29 | async-executor = "1.13.1" 30 | async-trait = "0.1.83" 31 | futures-lite = "2" 32 | event-listener = "5" 33 | # https://github.com/rust-random/rand#wasm-support 34 | getrandom = { version = "0.2", features = ["js"] } 35 | ordered-float = { version = "4", features = ["rand"] } 36 | anymap3 = "1.0.1" 37 | linked-hash-map = "0.5" 38 | include_dir = "0.7.4" 39 | console_error_panic_hook = "0.1" 40 | wasm-bindgen = "0.2" 41 | 42 | kast-util = { path = "crates/util" } 43 | kast-ast = { path = "crates/ast" } 44 | derive-macros = { package = "kast-derive-macros", path = "crates/derive-macros" } 45 | 46 | decursion = "0.1" 47 | refmap = { package = "kast-refmap", path = "crates/refmap" } 48 | try-hash = { package = "kast-try-hash", path = "crates/try-hash" } 49 | 50 | [package] 51 | name = "kast" 52 | version.workspace = true 53 | edition.workspace = true 54 | description = "kast programming language" 55 | license.workspace = true 56 | repository.workspace = true 57 | 58 | [profile.release] 59 | debug = true 60 | 61 | [features] 62 | default = ["embed-std"] 63 | embed-std = [] 64 | 65 | [dependencies] 66 | derive-macros.workspace = true 67 | batbox-time.workspace = true 68 | tokio.workspace = true 69 | getrandom.workspace = true 70 | clap.workspace = true 71 | eyre.workspace = true 72 | color-eyre.workspace = true 73 | colored.workspace = true 74 | tracing.workspace = true 75 | tracing-subscriber.workspace = true 76 | kast-util.workspace = true 77 | kast-ast.workspace = true 78 | thiserror.workspace = true 79 | rand.workspace = true 80 | futures.workspace = true 81 | ordered-float.workspace = true 82 | refmap.workspace = true 83 | async-once-cell.workspace = true 84 | async-notify.workspace = true 85 | async-executor.workspace = true 86 | async-trait.workspace = true 87 | futures-lite.workspace = true 88 | decursion.workspace = true 89 | event-listener.workspace = true 90 | try-hash.workspace = true 91 | async-oneshot.workspace = true 92 | anymap3.workspace = true 93 | inference = { package = "kast-inference", path = "crates/inference" } 94 | include_dir.workspace = true 95 | linked-hash-map.workspace = true 96 | 97 | [target.'cfg(target_arch = "wasm32")'.dependencies] 98 | console_error_panic_hook.workspace = true 99 | wasm-bindgen.workspace = true 100 | 101 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 102 | rustyline.workspace = true 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 kuviman 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kast 2 | 3 | An experimental programming language 4 | 5 | **NOT READY YET** 6 | 7 | [See more on the website](https://kast-lang.org) 8 | 9 | ## Bibliography 10 | 11 | - [Pratt Parser](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) 12 | - [Type Inference in Rust](https://rustc-dev-guide.rust-lang.org/type-inference.html) 13 | - [GhostCell - Separating Permissions from Data in Rust](https://plv.mpi-sws.org/rustbelt/ghostcell/) 14 | - [The Simple Essence of Algebraic Subtyping: Principal Type Inference with Subtyping Made Easy](https://infoscience.epfl.ch/server/api/core/bitstreams/afe084e0-0050-4542-99c7-c499d2fe1620/content) 15 | 16 | -------------------------------------------------------------------------------- /crates/ast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kast-ast" 3 | version.workspace = true 4 | edition.workspace = true 5 | description = "ast parser for kast" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [dependencies] 10 | kast-util.workspace = true 11 | tracing.workspace = true 12 | noisy_float.workspace = true 13 | decursion.workspace = true 14 | derive-macros.workspace = true -------------------------------------------------------------------------------- /crates/ast/src/peek2.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::path::{Path, PathBuf}; 3 | 4 | enum Peeked { 5 | None, 6 | One(T), 7 | Two(T, T), 8 | } 9 | 10 | impl Peeked { 11 | fn amount(&self) -> usize { 12 | match self { 13 | Peeked::None => 0, 14 | Peeked::One(_) => 1, 15 | Peeked::Two(_, _) => 2, 16 | } 17 | } 18 | fn push(&mut self, item: T) { 19 | *self = match std::mem::replace(self, Self::None) { 20 | Peeked::None => Peeked::One(item), 21 | Peeked::One(first) => Peeked::Two(first, item), 22 | Peeked::Two(_, _) => unreachable!(), 23 | }; 24 | } 25 | fn pop(&mut self) -> Option { 26 | let result; 27 | (*self, result) = match std::mem::replace(self, Self::None) { 28 | Peeked::None => (Peeked::None, None), 29 | Peeked::One(first) => (Peeked::None, Some(first)), 30 | Peeked::Two(first, second) => (Peeked::One(second), Some(first)), 31 | }; 32 | result 33 | } 34 | fn peek(&self) -> Option<&T> { 35 | match self { 36 | Peeked::None => None, 37 | Peeked::One(first) | Peeked::Two(first, _) => Some(first), 38 | } 39 | } 40 | fn peek2(&self) -> Option<&T> { 41 | match self { 42 | Peeked::Two(_, second) => Some(second), 43 | _ => None, 44 | } 45 | } 46 | } 47 | 48 | pub struct Reader { 49 | peeked: Peeked, 50 | iter: Box>, 51 | current_position: Position, 52 | filename: PathBuf, 53 | progress: usize, 54 | } 55 | 56 | pub enum AdvancePosition { 57 | NextColumn, 58 | NextLine, 59 | SetTo(Position), 60 | } 61 | 62 | pub trait ReadableItem { 63 | fn advance_position(&self) -> AdvancePosition; 64 | } 65 | 66 | impl ReadableItem for char { 67 | fn advance_position(&self) -> AdvancePosition { 68 | match self { 69 | '\n' => AdvancePosition::NextLine, 70 | _ => AdvancePosition::NextColumn, 71 | } 72 | } 73 | } 74 | 75 | impl Reader { 76 | pub fn read(source: SourceFile) -> Self { 77 | Self::new( 78 | source.filename, 79 | source.contents.chars().collect::>(), 80 | ) 81 | } 82 | } 83 | 84 | impl Reader { 85 | pub fn new(filename: PathBuf, iter: impl IntoIterator + 'static) -> Self { 86 | Self { 87 | progress: 0, 88 | peeked: Peeked::None, 89 | iter: Box::new(iter.into_iter()), 90 | current_position: Position { 91 | index: 0, 92 | line: 1, 93 | column: 1, 94 | }, 95 | filename, 96 | } 97 | } 98 | 99 | pub fn filename(&self) -> &Path { 100 | &self.filename 101 | } 102 | 103 | pub fn peek(&mut self) -> Option<&T> { 104 | if self.peeked.amount() == 0 { 105 | if let Some(item) = self.iter.next() { 106 | self.peeked.push(item); 107 | } 108 | } 109 | self.peeked.peek() 110 | } 111 | 112 | pub fn peek2(&mut self) -> Option<&T> { 113 | while self.peeked.amount() < 2 { 114 | match self.iter.next() { 115 | Some(item) => self.peeked.push(item), 116 | None => break, 117 | } 118 | } 119 | self.peeked.peek2() 120 | } 121 | 122 | pub fn next(&mut self) -> Option { 123 | let item = self.peeked.pop().or_else(|| self.iter.next()); 124 | self.progress += 1; 125 | if let Some(item) = &item { 126 | match item.advance_position() { 127 | AdvancePosition::NextLine => { 128 | self.current_position.line += 1; 129 | self.current_position.column = 1; 130 | self.current_position.index += 1; 131 | } 132 | AdvancePosition::NextColumn => { 133 | self.current_position.column += 1; 134 | self.current_position.index += 1; 135 | } 136 | AdvancePosition::SetTo(new_position) => { 137 | self.current_position = new_position; 138 | } 139 | } 140 | } 141 | item 142 | } 143 | 144 | pub fn position(&self) -> Position { 145 | self.current_position 146 | } 147 | 148 | #[allow(dead_code)] 149 | pub fn progress(&self) -> usize { 150 | self.progress 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /crates/ast/src/syntax.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use noisy_float::prelude::*; 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] 6 | pub struct Priority(R64); 7 | 8 | impl Priority { 9 | pub fn new(raw: f64) -> Self { 10 | Self(R64::new(raw)) 11 | } 12 | } 13 | 14 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 15 | pub enum Associativity { 16 | Left, 17 | Right, 18 | } 19 | 20 | #[derive(Debug)] 21 | pub enum SyntaxDefinitionPart { 22 | Keyword(String), 23 | UnnamedBinding, 24 | NamedBinding(String), 25 | } 26 | 27 | #[derive(Debug)] 28 | pub struct SyntaxDefinition { 29 | pub name: String, 30 | pub priority: Priority, 31 | pub associativity: Associativity, 32 | pub parts: Vec, 33 | } 34 | 35 | impl SyntaxDefinition { 36 | pub fn binding_power(&self) -> BindingPower { 37 | BindingPower { 38 | priority: self.priority, 39 | associativity: self.associativity, 40 | } 41 | } 42 | } 43 | 44 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 45 | pub struct BindingPower { 46 | pub priority: Priority, 47 | pub associativity: Associativity, 48 | } 49 | 50 | #[derive(Debug, Eq, PartialEq, Hash, Clone)] 51 | pub enum Edge { 52 | Value, 53 | Keyword(String), 54 | } 55 | impl Edge { 56 | pub fn is_open_bracket(&self) -> bool { 57 | match self { 58 | Self::Keyword(keyword) => is_open_bracket(keyword), 59 | Self::Value => false, 60 | } 61 | } 62 | } 63 | 64 | pub fn is_open_bracket(s: &str) -> bool { 65 | s.chars().any(|c| "([{".contains(c)) 66 | } 67 | 68 | #[derive(Clone, Debug)] 69 | pub struct ParseNode { 70 | pub finish: Option>, 71 | pub next: HashMap, 72 | pub binding_power: Option, 73 | pub is_open_paren: bool, 74 | } 75 | 76 | impl ParseNode { 77 | fn new(is_open_paren: bool, binding_power: Option) -> Self { 78 | Self { 79 | finish: None, 80 | next: HashMap::new(), 81 | binding_power, 82 | is_open_paren, 83 | } 84 | } 85 | pub fn with_power(is_open_paren: bool, binding_power: BindingPower) -> Self { 86 | Self::new(is_open_paren, Some(binding_power)) 87 | } 88 | /// write what is expected after this node (a value or on of the keywords) 89 | pub fn format_possible_continuations(&self) -> impl std::fmt::Display + '_ { 90 | struct Format<'a>(&'a ParseNode); 91 | impl std::fmt::Display for Format<'_> { 92 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 93 | for (i, edge) in self.0.next.keys().enumerate() { 94 | if i != 0 { 95 | write!(f, " or ")?; 96 | } 97 | match edge { 98 | Edge::Value => write!(f, "a value")?, 99 | Edge::Keyword(keyword) => write!(f, "{keyword:?}")?, 100 | } 101 | } 102 | Ok(()) 103 | } 104 | } 105 | Format(self) 106 | } 107 | } 108 | 109 | #[derive(Clone, Debug)] 110 | pub struct Syntax { 111 | pub(crate) keywords: HashSet, 112 | pub(crate) root_without_start_value: ParseNode, 113 | pub(crate) root_with_start_value: ParseNode, 114 | } 115 | 116 | impl Syntax { 117 | pub fn empty() -> Self { 118 | Self { 119 | keywords: HashSet::new(), 120 | root_without_start_value: ParseNode::new(false, None), 121 | root_with_start_value: ParseNode::new(false, None), 122 | } 123 | } 124 | 125 | pub fn insert(&mut self, definition: Parc) -> Result<(), ErrorMessage> { 126 | let binding_power = definition.binding_power(); 127 | let skip; 128 | let mut current_node = match definition.parts[0] { 129 | SyntaxDefinitionPart::Keyword(_) => { 130 | skip = 0; 131 | &mut self.root_without_start_value 132 | } 133 | _ => { 134 | skip = 1; 135 | &mut self.root_with_start_value 136 | } 137 | }; 138 | for part in definition.parts.iter().skip(skip) { 139 | let edge = match part { 140 | SyntaxDefinitionPart::Keyword(keyword) => { 141 | self.keywords.insert(keyword.clone()); 142 | Edge::Keyword(keyword.clone()) 143 | } 144 | SyntaxDefinitionPart::UnnamedBinding | SyntaxDefinitionPart::NamedBinding(_) => { 145 | Edge::Value 146 | } 147 | }; 148 | let is_open_bracket = edge.is_open_bracket(); 149 | let next_node = current_node 150 | .next 151 | .entry(edge) 152 | .or_insert_with(|| ParseNode::with_power(is_open_bracket, binding_power)); 153 | if next_node.binding_power != Some(binding_power) { 154 | return error!("different binding power"); 155 | } 156 | current_node = next_node; 157 | } 158 | if let Some(current) = ¤t_node.finish { 159 | return error!( 160 | "Conficting syntax definitions: {:?} and {:?}", 161 | current.name, definition.name, 162 | ); 163 | } 164 | current_node.finish = Some(definition); 165 | Ok(()) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /crates/ast/tests/syntax.ks: -------------------------------------------------------------------------------- 1 | syntax then -> 0 = _ ";" _; 2 | syntax then -> 0 = _ ";"; 3 | 4 | syntax add <- 20 = lhs "+" rhs; 5 | syntax sub <- 20 = lhs "-" rhs; 6 | syntax mul <- 30 = lhs "*" rhs; 7 | syntax call <- 50 = f args; 8 | 9 | syntax scoped <- 1000 = "(" e ")"; 10 | -------------------------------------------------------------------------------- /crates/ast/tests/tests.rs: -------------------------------------------------------------------------------- 1 | use kast_ast::*; 2 | use kast_util::*; 3 | 4 | fn test_case(source: &str, checker: impl FnOnce(Result, Error>)) { 5 | let syntax = read_syntax(SourceFile { 6 | contents: include_str!("syntax.ks").to_owned(), 7 | filename: "syntax.ks".into(), 8 | }) 9 | .unwrap(); 10 | let source = SourceFile { 11 | contents: source.to_owned(), 12 | filename: "".into(), 13 | }; 14 | checker(parse(&syntax, source)); 15 | } 16 | 17 | enum Checker { 18 | Simple(String), 19 | Complex { 20 | name: String, 21 | values: Tuple, 22 | }, 23 | } 24 | 25 | // I didn't even know that rust is so annoying 26 | // TODO: maybe we can somehow get rid of these parentheses in field matchers? 27 | macro_rules! construct_checker { 28 | ($value:literal) => { 29 | Checker::Simple($value.to_owned()) 30 | }; 31 | ($name:ident { $($field_name:ident: ($($field_matcher:tt)*),)* }) => { 32 | Checker::Complex { 33 | name: stringify!($name).to_owned(), 34 | values: { 35 | let mut values = Tuple::empty(); 36 | $( 37 | values.add_named(stringify!($field_name).to_owned(), construct_checker!($($field_matcher)*)); 38 | )* 39 | values 40 | }, 41 | } 42 | }; 43 | } 44 | 45 | fn check_with(checker: Checker) -> impl FnOnce(Result, Error>) { 46 | fn check(ast: Ast, checker: Checker) { 47 | match (ast, checker) { 48 | (Ast::Simple { token, data }, Checker::Simple(expected)) => { 49 | let raw = token.raw(); 50 | if raw != expected { 51 | panic!("expected {expected:?}, got {raw:?} at {data}"); 52 | } 53 | } 54 | (other, Checker::Simple(expected)) => { 55 | panic!("expected {expected:?}, got {other} at {}", other.data()); 56 | } 57 | ( 58 | Ast::Complex { 59 | definition, 60 | values, 61 | data: span, 62 | }, 63 | Checker::Complex { 64 | name: expected_name, 65 | values: expected_values, 66 | }, 67 | ) => { 68 | if definition.name != expected_name { 69 | panic!( 70 | "expected {expected_name}, got {got} at {span}", 71 | got = definition.name 72 | ); 73 | } 74 | match values.zip(expected_values) { 75 | Err(TupleZipError::DifferentUnnamedAmount(actual, expected)) => { 76 | panic!("expected {expected} unnamed fields, got {actual} at {span}"); 77 | } 78 | Err(TupleZipError::NamedNotPresentInOther(field_name)) => { 79 | panic!( 80 | "field {field_name:?} was not expected in {expected_name} at {span}" 81 | ); 82 | } 83 | Err(TupleZipError::NamedOnlyPresentInOther(field_name)) => { 84 | panic!( 85 | "field {field_name:?} was expected but not present in {expected_name} at {span}" 86 | ); 87 | } 88 | Ok(zipped) => { 89 | for (actual, expected) in zipped.into_field_values() { 90 | check(actual, expected); 91 | } 92 | } 93 | } 94 | } 95 | (other, Checker::Complex { name, .. }) => { 96 | panic!("expected {name} node, got {other} at {}", other.data()); 97 | } 98 | } 99 | } 100 | move |result| { 101 | let ast = result.unwrap().expect("parsed nothing"); 102 | check(ast, checker); 103 | } 104 | } 105 | 106 | macro_rules! check { 107 | ($($matcher:tt)*) => {{ 108 | let checker = construct_checker!($($matcher)*); 109 | check_with(checker) 110 | }}; 111 | } 112 | 113 | #[test] 114 | fn test_add() { 115 | test_case( 116 | "a + b", 117 | check!(add { 118 | lhs: ("a"), 119 | rhs: ("b"), 120 | }), 121 | ); 122 | } 123 | 124 | #[test] 125 | fn test_add_add() { 126 | test_case( 127 | "a + b + c", 128 | check!(add { 129 | lhs: (add { 130 | lhs: ("a"), 131 | rhs: ("b"), 132 | }), 133 | rhs: ("c"), 134 | }), 135 | ); 136 | } 137 | 138 | #[test] 139 | fn test_complex_math() { 140 | test_case( 141 | "a + sin x * c * d + (1 - 2)", 142 | check!(add { 143 | lhs: (add { 144 | lhs: ("a"), 145 | rhs: (mul { 146 | lhs: (mul { 147 | lhs: (call { 148 | f: ("sin"), 149 | args: ("x"), 150 | }), 151 | rhs: ("c"), 152 | }), 153 | rhs: ("d"), 154 | }), 155 | }), 156 | rhs: (scoped { 157 | e: (sub { 158 | lhs: ("1"), 159 | rhs: ("2"), 160 | }), 161 | }), 162 | }), 163 | ); 164 | } 165 | 166 | #[test] 167 | fn test_very_long_file() { 168 | let mut source = String::new(); 169 | for _ in 0..1_000 { 170 | source += "a;" 171 | } 172 | test_case(&source, |result| assert!(result.is_ok())); 173 | } 174 | -------------------------------------------------------------------------------- /crates/derive-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kast-derive-macros" 3 | version.workspace = true 4 | edition.workspace = true 5 | description = "derive macros for kast" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = "2" 14 | quote = "1" 15 | proc-macro2 = "1" 16 | -------------------------------------------------------------------------------- /crates/derive-macros/src/data.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 4 | let input: syn::DeriveInput = syn::parse_macro_input!(input); 5 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 6 | let ident = input.ident; 7 | let data_ty = &input.generics.type_params().next().unwrap().ident; 8 | match input.data { 9 | syn::Data::Enum(data) => { 10 | let variants: Vec<_> = data 11 | .variants 12 | .iter() 13 | .map(|variant| { 14 | let ident = &variant.ident; 15 | quote! { 16 | Self::#ident { data, .. } => data, 17 | } 18 | }) 19 | .collect(); 20 | quote! { 21 | impl #impl_generics #ident #ty_generics #where_clause { 22 | pub fn data(&self) -> & #data_ty { 23 | match self { 24 | #(#variants)* 25 | } 26 | } 27 | pub fn data_mut(&mut self) -> &mut #data_ty { 28 | match self { 29 | #(#variants)* 30 | } 31 | } 32 | } 33 | } 34 | } 35 | _ => panic!("yo"), 36 | } 37 | .into() 38 | } 39 | -------------------------------------------------------------------------------- /crates/derive-macros/src/expr_display.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 4 | let input: syn::DeriveInput = syn::parse_macro_input!(input); 5 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 6 | let ident = input.ident; 7 | let f = syn::Ident::new("f71y26t3t5r4152637687234", proc_macro2::Span::mixed_site()); 8 | 9 | let field_writes = |fields: &syn::Fields| { 10 | fields 11 | .iter() 12 | .map(|field| { 13 | let ident = field.ident.as_ref().unwrap(); 14 | let skip = ident == "data" 15 | || field 16 | .attrs 17 | .iter() 18 | .filter(|attr| attr.path().is_ident("display")) 19 | .any(|attr| { 20 | let name = attr.parse_args::().unwrap(); 21 | name == "skip" 22 | }); 23 | if skip { 24 | return quote!( let _ = #ident; ); 25 | } 26 | if let syn::Type::Path(ty) = &field.ty { 27 | if ty.path.segments[0].ident == "Option" { 28 | return quote! { 29 | write!(#f, "{} = ", stringify!(#ident))?; 30 | match #ident { 31 | Some(value) => { 32 | kast_util::FormatterExt::write(#f, value)?; 33 | writeln!(#f)?; 34 | }, 35 | None => writeln!(#f, "None")?, 36 | }; 37 | }; 38 | } 39 | if ty.path.segments[0].ident == "Vec" { 40 | return quote! { 41 | writeln!(#f, "{} = [", stringify!(#ident))?; 42 | for item in #ident { 43 | write!(#f, " ")?; 44 | kast_util::FormatterExt::write(#f, item)?; 45 | writeln!(#f)?; 46 | } 47 | writeln!(#f, "]")?; 48 | }; 49 | } 50 | } 51 | quote! { 52 | write!(#f, "{} = ", stringify!(#ident))?; 53 | kast_util::FormatterExt::write(#f, #ident)?; 54 | writeln!(#f)?; 55 | } 56 | }) 57 | .collect::>() 58 | }; 59 | 60 | match input.data { 61 | syn::Data::Struct(data) => { 62 | let field_names = data 63 | .fields 64 | .iter() 65 | .map(|field| field.ident.as_ref().unwrap()); 66 | 67 | let field_writes = field_writes(&data.fields); 68 | quote! { 69 | impl #impl_generics std::fmt::Display for #ident #ty_generics #where_clause { 70 | fn fmt(&self, #f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 71 | use std::fmt::Write; 72 | writeln!(#f, "{{")?; 73 | { 74 | let mut #f = pad_adapter::PadAdapter::with_padding(#f, " "); 75 | let #f = &mut #f; 76 | let Self { #(#field_names,)* } = self; 77 | #(#field_writes)* 78 | } 79 | write!(#f, "}}") 80 | } 81 | } 82 | } 83 | } 84 | syn::Data::Enum(data) => { 85 | let variant_names = data.variants.iter().map(|variant| { 86 | let ident = &variant.ident; 87 | quote! { 88 | Self::#ident { .. } => stringify!(#ident), 89 | } 90 | }); 91 | let variant_fields = data.variants.iter().map(|variant| { 92 | let ident = &variant.ident; 93 | let field_names = variant 94 | .fields 95 | .iter() 96 | .map(|field| field.ident.as_ref().unwrap()); 97 | 98 | let field_writes = field_writes(&variant.fields); 99 | quote! { 100 | Self::#ident { #(#field_names,)* } => { 101 | #(#field_writes)* 102 | }, 103 | } 104 | }); 105 | quote! { 106 | impl #impl_generics std::fmt::Display for #ident #ty_generics #where_clause { 107 | fn fmt(&self, #f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 108 | use std::fmt::Write; 109 | let name = match self { 110 | #(#variant_names)* 111 | }; 112 | writeln!(#f, "{name} {{")?; 113 | { 114 | let mut #f = pad_adapter::PadAdapter::with_padding(#f, " "); 115 | let #f = &mut #f; 116 | match self { 117 | #(#variant_fields)* 118 | } 119 | } 120 | write!(#f, "}}") 121 | } 122 | } 123 | } 124 | } 125 | _ => panic!("yo"), 126 | } 127 | .into() 128 | } 129 | -------------------------------------------------------------------------------- /crates/derive-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use quote::quote; 3 | 4 | mod data; 5 | #[proc_macro_derive(Data, attributes(display))] 6 | pub fn derive_data(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 7 | data::derive(input) 8 | } 9 | 10 | mod expr_display; 11 | #[proc_macro_derive(ExprDisplay, attributes(display))] 12 | pub fn derive_expr_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 13 | expr_display::derive(input) 14 | } 15 | -------------------------------------------------------------------------------- /crates/inference/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kast-inference" 3 | version.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | 8 | [dependencies] 9 | rand.workspace = true 10 | eyre.workspace = true 11 | anymap3.workspace = true 12 | try-hash.workspace = true 13 | kast-util.workspace = true 14 | -------------------------------------------------------------------------------- /crates/inference/src/checks.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub type Check = Arc eyre::Result + Send + Sync>; 4 | 5 | #[must_use] 6 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 7 | pub enum CheckResult { 8 | RunAgain, 9 | Completed, 10 | } 11 | 12 | impl Data { 13 | pub fn run_checks(&mut self) -> eyre::Result<()> { 14 | // println!("running checks for {:?}", self.description); 15 | if let Some(value) = &self.value { 16 | let mut completed = Vec::new(); 17 | for (index, check) in self.checks.iter().enumerate() { 18 | match check(value)? { 19 | CheckResult::RunAgain => {} 20 | CheckResult::Completed => { 21 | completed.push(index); 22 | } 23 | } 24 | } 25 | for idx in completed.into_iter().rev() { 26 | self.checks.remove(idx); 27 | } 28 | } 29 | Ok(()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/inference/src/default.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Clone, Default)] 4 | pub enum DefaultValue { 5 | #[default] 6 | None, 7 | Some(Arc), 8 | Conflicted, 9 | } 10 | 11 | impl DefaultValue { 12 | pub fn get(&self) -> Option> { 13 | match self { 14 | DefaultValue::None | DefaultValue::Conflicted => None, 15 | DefaultValue::Some(value) => Some(value.clone()), 16 | } 17 | } 18 | pub fn common(a: Self, b: Self) -> Self { 19 | match (a, b) { 20 | (Self::None, Self::None) => Self::None, 21 | (Self::None, Self::Some(value)) => Self::Some(value), 22 | (Self::Some(value), Self::None) => Self::Some(value), 23 | (Self::Conflicted, _) | (_, Self::Conflicted) => Self::Conflicted, 24 | (Self::Some(a), Self::Some(b)) => match T::make_same(T::clone(&a), T::clone(&b)) { 25 | Ok(value) => Self::Some(Arc::new(value)), 26 | Err(_) => Self::Conflicted, 27 | }, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/inference/src/global_state.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::{ 3 | collections::HashMap, 4 | marker::PhantomData, 5 | sync::{OnceLock, atomic::AtomicUsize}, 6 | }; 7 | 8 | #[derive(Clone)] 9 | struct ConcreteState { 10 | vars: HashMap, 11 | } 12 | 13 | impl Default for ConcreteState { 14 | fn default() -> Self { 15 | Self { 16 | vars: Default::default(), 17 | } 18 | } 19 | } 20 | 21 | #[derive(Default, Clone)] 22 | struct State { 23 | concrete: anymap3::Map, 24 | } 25 | 26 | static STATE: OnceLock> = OnceLock::new(); 27 | fn state() -> &'static Mutex { 28 | STATE.get_or_init(Default::default) 29 | } 30 | 31 | pub struct Snapshot { 32 | state: State, 33 | } 34 | 35 | pub fn snapshot() -> Snapshot { 36 | Snapshot { 37 | state: state().lock().unwrap().clone(), 38 | } 39 | } 40 | 41 | pub fn revert(snapshot: Snapshot) { 42 | *state().lock().unwrap() = snapshot.state; 43 | } 44 | 45 | #[derive(Clone)] 46 | pub struct Id { 47 | pub index: usize, 48 | phantom_data: PhantomData, 49 | } 50 | 51 | impl Id { 52 | pub fn new(value: T) -> Self { 53 | static NEXT_INDEX: AtomicUsize = AtomicUsize::new(0); 54 | let index = NEXT_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst); 55 | let result = Self { 56 | index, 57 | phantom_data: PhantomData, 58 | }; 59 | result.set(value); 60 | result 61 | } 62 | pub fn set(&self, value: T) { 63 | state() 64 | .lock() 65 | .unwrap() 66 | .concrete 67 | .entry::>() 68 | .or_default() 69 | .vars 70 | .insert(self.index, value); 71 | } 72 | pub fn get(&self) -> T { 73 | state() 74 | .lock() 75 | .unwrap() 76 | .concrete 77 | .entry::>() 78 | .or_default() 79 | .vars 80 | .get(&self.index) 81 | .expect("var not exist???") 82 | .clone() 83 | } 84 | pub fn modify(&self, f: impl FnOnce(&mut T) -> R) -> R { 85 | let mut value = self.get(); 86 | let result = f(&mut value); 87 | self.set(value); 88 | result 89 | } 90 | } 91 | 92 | pub fn ptr_eq(a: &Id, b: &Id) -> bool { 93 | a.index == b.index 94 | } 95 | -------------------------------------------------------------------------------- /crates/refmap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kast-refmap" 3 | version.workspace = true 4 | edition.workspace = true 5 | description = "map where keys are references - not values behind references" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /crates/refmap/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::Hash; 3 | use std::rc::Rc; 4 | use std::sync::Arc; 5 | 6 | pub trait Ref: Clone { 7 | fn is_same_ref(&self, other: &Self) -> bool; 8 | fn hash_ref(&self, hasher: &mut impl std::hash::Hasher); 9 | } 10 | 11 | impl Ref for Rc { 12 | fn is_same_ref(&self, other: &Self) -> bool { 13 | Rc::ptr_eq(self, other) 14 | } 15 | fn hash_ref(&self, hasher: &mut impl std::hash::Hasher) { 16 | Rc::as_ptr(self).hash(hasher) 17 | } 18 | } 19 | 20 | impl Ref for Arc { 21 | fn is_same_ref(&self, other: &Self) -> bool { 22 | Arc::ptr_eq(self, other) 23 | } 24 | fn hash_ref(&self, hasher: &mut impl std::hash::Hasher) { 25 | Arc::as_ptr(self).hash(hasher) 26 | } 27 | } 28 | 29 | impl Ref for &'_ T { 30 | fn is_same_ref(&self, other: &Self) -> bool { 31 | std::ptr::eq(*self, *other) 32 | } 33 | fn hash_ref(&self, hasher: &mut impl std::hash::Hasher) { 34 | (*self as *const T).hash(hasher) 35 | } 36 | } 37 | 38 | #[derive(Clone)] 39 | struct RefKey(K); 40 | 41 | impl PartialEq for RefKey { 42 | fn eq(&self, other: &Self) -> bool { 43 | self.0.is_same_ref(&other.0) 44 | } 45 | } 46 | impl Eq for RefKey {} 47 | 48 | impl Hash for RefKey { 49 | fn hash(&self, state: &mut H) { 50 | self.0.hash_ref(state) 51 | } 52 | } 53 | 54 | pub struct RefMap(HashMap, V>); 55 | 56 | impl Clone for RefMap { 57 | fn clone(&self) -> Self { 58 | Self(self.0.clone()) 59 | } 60 | } 61 | 62 | impl Default for RefMap { 63 | fn default() -> Self { 64 | Self::new() 65 | } 66 | } 67 | 68 | impl RefMap { 69 | pub fn new() -> Self { 70 | Self(HashMap::new()) 71 | } 72 | pub fn insert(&mut self, key: K, value: V) { 73 | self.0.insert(RefKey(key), value); 74 | } 75 | pub fn get(&self, key: &K) -> Option<&V> { 76 | self.0.get(&RefKey(key.clone())) 77 | } 78 | pub fn get_mut(&mut self, key: K) -> Option<&mut V> { 79 | self.0.get_mut(&RefKey(key)) 80 | } 81 | pub fn iter(&self) -> impl Iterator { 82 | self.0.iter().map(|(RefKey(key), value)| (key, value)) 83 | } 84 | pub fn iter_mut(&mut self) -> impl Iterator { 85 | self.0.iter_mut().map(|(RefKey(key), value)| (key, value)) 86 | } 87 | } 88 | 89 | impl IntoIterator for RefMap { 90 | type Item = (K, V); 91 | type IntoIter = Box>; // TODO impl or concrete 92 | fn into_iter(self) -> Self::IntoIter { 93 | Box::new(self.0.into_iter().map(|(RefKey(key), value)| (key, value))) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/try-hash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kast-try-hash" 3 | version.workspace = true 4 | edition.workspace = true 5 | description = "Failable Hash trait" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [dependencies] 10 | try-hash-derive = { package = "kast-try-hash-derive", path = "derive" } 11 | -------------------------------------------------------------------------------- /crates/try-hash/derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kast-try-hash-derive" 3 | version.workspace = true 4 | edition.workspace = true 5 | description = "Failable Hash trait (derive macros)" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = "2" 14 | quote = "1" 15 | proc-macro2 = "1" 16 | -------------------------------------------------------------------------------- /crates/try-hash/derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::spanned::Spanned; 5 | 6 | #[proc_macro_derive(TryHash, attributes(try_hash))] 7 | pub fn derive_try_hash(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 8 | let input: syn::DeriveInput = syn::parse_macro_input!(input); 9 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 10 | let ident = input.ident; 11 | let try_hash = quote! { try_hash::TryHash }; 12 | 13 | struct Field { 14 | /// `0` is a valid name originally 15 | name: syn::Member, 16 | /// because `0` is not a valid name anymore 17 | new_name: syn::Ident, 18 | do_try: bool, 19 | } 20 | fn fields(fields: &syn::Fields) -> impl Iterator + '_ { 21 | fields.iter().enumerate().map(|(index, field)| Field { 22 | name: match &field.ident { 23 | Some(ident) => syn::Member::Named(ident.clone()), 24 | None => syn::Member::Unnamed(syn::Index { 25 | index: index as u32, 26 | span: field.span(), 27 | }), 28 | }, 29 | new_name: syn::Ident::new(&format!("__self_{index}"), field.span()), 30 | do_try: field 31 | .attrs 32 | .iter() 33 | .any(|attr| attr.path().is_ident("try_hash")), 34 | }) 35 | } 36 | fn hash_fields(fields: impl IntoIterator) -> TokenStream { 37 | fn hash_field(field: Field) -> TokenStream { 38 | let ident = &field.new_name; 39 | if field.do_try { 40 | quote!( try_hash::TryHash::try_hash(#ident, hasher)?; ) 41 | } else { 42 | quote!( std::hash::Hash::hash(#ident, hasher); ) 43 | } 44 | } 45 | let hash_fields = fields.into_iter().map(hash_field); 46 | quote! { 47 | #( #hash_fields )* 48 | } 49 | } 50 | match input.data { 51 | syn::Data::Struct(data) => { 52 | let field_names = fields(&data.fields).map(|field| field.name); 53 | let field_new_names = fields(&data.fields).map(|field| field.new_name); 54 | let hash_fields = hash_fields(fields(&data.fields)); 55 | quote! { 56 | impl #impl_generics #try_hash for #ident #ty_generics #where_clause { 57 | type Error = Box; 58 | fn try_hash(&self, hasher: &mut impl std::hash::Hasher) -> Result<(), Self::Error> { 59 | let Self { #(#field_names: #field_new_names),* } = self; 60 | #hash_fields; 61 | Ok(()) 62 | } 63 | } 64 | } 65 | } 66 | syn::Data::Enum(data) => { 67 | let variants = data.variants.iter().map(|variant| { 68 | let ident = &variant.ident; 69 | let field_names = fields(&variant.fields).map(|field| field.name); 70 | let field_new_names: Vec<_> = fields(&variant.fields).map(|field| field.new_name).collect(); 71 | let hash_fields = hash_fields(fields(&variant.fields)); 72 | quote! { 73 | Self::#ident { #(#field_names : #field_new_names),* } => { 74 | #hash_fields 75 | } 76 | } 77 | }); 78 | quote! { 79 | impl #impl_generics try_hash::TryHash for #ident #ty_generics #where_clause { 80 | type Error = Box; 81 | fn try_hash(&self, hasher: &mut impl std::hash::Hasher) -> Result<(), Self::Error> { 82 | std::hash::Hash::hash(&std::mem::discriminant(self), hasher); 83 | match self { 84 | #(#variants)* 85 | } 86 | Ok(()) 87 | } 88 | } 89 | } 90 | } 91 | syn::Data::Union(_) => panic!("union no support"), 92 | } 93 | .into() 94 | } 95 | -------------------------------------------------------------------------------- /crates/try-hash/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use try_hash_derive::TryHash; 2 | 3 | pub trait TryHash { 4 | type Error: std::fmt::Display + std::fmt::Debug; 5 | fn try_hash(&self, hasher: &mut impl std::hash::Hasher) -> Result<(), Self::Error>; 6 | } 7 | 8 | impl TryHash for Box { 9 | type Error = T::Error; 10 | fn try_hash(&self, hasher: &mut impl std::hash::Hasher) -> Result<(), Self::Error> { 11 | T::try_hash(self, hasher) 12 | } 13 | } 14 | 15 | impl TryHash for Option { 16 | type Error = T::Error; 17 | fn try_hash(&self, hasher: &mut impl std::hash::Hasher) -> Result<(), Self::Error> { 18 | std::hash::Hash::hash(&std::mem::discriminant(self), hasher); 19 | if let Some(value) = self { 20 | value.try_hash(hasher)?; 21 | } 22 | Ok(()) 23 | } 24 | } 25 | 26 | impl TryHash for Vec { 27 | type Error = T::Error; 28 | fn try_hash(&self, hasher: &mut impl std::hash::Hasher) -> Result<(), Self::Error> { 29 | for elem in self { 30 | elem.try_hash(hasher)?; 31 | } 32 | Ok(()) 33 | } 34 | } 35 | 36 | impl TryHash for std::sync::Arc { 37 | type Error = T::Error; 38 | fn try_hash(&self, hasher: &mut impl std::hash::Hasher) -> Result<(), Self::Error> { 39 | T::try_hash(self, hasher) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/try-hash/tests/tests.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use kast_try_hash as try_hash; 4 | 5 | #[derive(try_hash::TryHash)] 6 | struct FooStruct { 7 | a: i32, 8 | b: i64, 9 | } 10 | 11 | #[derive(try_hash::TryHash)] 12 | enum FooEnum { 13 | A, 14 | B(i32), 15 | C { 16 | #[try_hash] 17 | foo: FooStruct, 18 | idk: String, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /crates/util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kast-util" 3 | version.workspace = true 4 | edition.workspace = true 5 | description = "Utilities for kast" 6 | license.workspace = true 7 | repository.workspace = true 8 | 9 | [dependencies] 10 | thiserror.workspace = true 11 | try-hash.workspace = true 12 | anymap3.workspace = true 13 | -------------------------------------------------------------------------------- /crates/util/src/display_option.rs: -------------------------------------------------------------------------------- 1 | pub fn display_option(option: &Option) -> DisplayOption<'_, T> { 2 | DisplayOption(option) 3 | } 4 | 5 | pub struct DisplayOption<'a, T>(&'a Option); 6 | 7 | impl std::fmt::Display for DisplayOption<'_, T> { 8 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 9 | match self.0 { 10 | Some(value) => value.fmt(f), 11 | None => write!(f, ""), 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /crates/util/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::source::Span; 2 | 3 | #[derive(Debug, thiserror::Error)] 4 | #[error("at {span} - {message}")] 5 | pub struct Error { 6 | pub message: String, 7 | pub span: Span, 8 | } 9 | 10 | #[derive(Debug, thiserror::Error)] 11 | #[error("{0}")] 12 | pub struct ErrorMessage(pub String); 13 | 14 | impl ErrorMessage { 15 | pub fn at(self, span: Span) -> Error { 16 | Error { 17 | message: self.0, 18 | span, 19 | } 20 | } 21 | } 22 | 23 | #[macro_export] 24 | macro_rules! error_fmt { 25 | ($($f:tt)*) => { 26 | ErrorMessage(format!($($f)*)) 27 | }; 28 | } 29 | 30 | #[macro_export] 31 | macro_rules! error { 32 | ($($f:tt)*) => { 33 | Err(error_fmt!($($f)*)) 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /crates/util/src/id.rs: -------------------------------------------------------------------------------- 1 | pub trait Identifiable { 2 | type Id: 'static + Eq + std::hash::Hash; 3 | fn id(&self) -> Self::Id; 4 | } 5 | -------------------------------------------------------------------------------- /crates/util/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod display_option; 2 | mod error; 3 | mod id; 4 | pub mod pad_adapter; 5 | mod parc; 6 | mod recurse_cache; 7 | mod show_short; 8 | mod source; 9 | pub mod tuple; 10 | 11 | pub use crate::display_option::display_option; 12 | pub use crate::error::{Error, ErrorMessage}; 13 | pub use crate::id::*; 14 | pub use crate::parc::Parc; 15 | pub use crate::recurse_cache::*; 16 | pub use crate::show_short::*; 17 | pub use crate::source::{Position, SourceFile, Span}; 18 | pub use crate::tuple::{Tuple, TupleZipError}; 19 | pub use pad_adapter::FormatterExt; 20 | -------------------------------------------------------------------------------- /crates/util/src/pad_adapter.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | pub trait Formatter: std::fmt::Write { 4 | fn alternate(&self) -> bool; 5 | } 6 | 7 | pub trait FormatterExt: Formatter { 8 | fn write(&mut self, value: impl std::fmt::Display) -> std::fmt::Result { 9 | match self.alternate() { 10 | true => write!(self, "{value:#}"), 11 | false => write!(self, "{value}"), 12 | } 13 | } 14 | } 15 | 16 | impl FormatterExt for T {} 17 | 18 | impl Formatter for std::fmt::Formatter<'_> { 19 | fn alternate(&self) -> bool { 20 | std::fmt::Formatter::alternate(self) 21 | } 22 | } 23 | 24 | #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] 25 | pub enum OuterMode { 26 | Surrounded, 27 | Free, 28 | } 29 | 30 | pub struct Moded { 31 | pub normal: T, 32 | pub alternate: T, 33 | } 34 | 35 | /// Include writespace, it will be auto trimmed in beginning/end of lines 36 | pub enum Separator<'a> { 37 | /// a,\nb,\nc 38 | Seq(&'a str), 39 | /// a\n+ b\n+ c 40 | /// differs from seq in before/after newline 41 | BinOp(&'a str), 42 | /// | a | b | c 43 | Before(&'a str), 44 | /// a; b; c; 45 | After(&'a str), 46 | } 47 | 48 | pub fn padded_items( 49 | fmt: &mut dyn Formatter, 50 | items: impl IntoIterator, 51 | outer_mode: OuterMode, 52 | sep: Moded>, 53 | mut write_item: impl FnMut(&mut dyn Formatter, T) -> std::fmt::Result, 54 | ) -> std::fmt::Result { 55 | let alternate = fmt.alternate(); 56 | let sep = match alternate { 57 | false => sep.normal, 58 | true => sep.alternate, 59 | }; 60 | let mut f = PadAdapter::new(fmt); 61 | let f = &mut f; 62 | let mut first = true; 63 | for value in items.into_iter() { 64 | if first && outer_mode == OuterMode::Surrounded { 65 | writeln!(f)?; 66 | } 67 | match sep { 68 | Separator::Seq(s) | Separator::After(s) => { 69 | if !first { 70 | if alternate { 71 | writeln!(f, "{}", s.trim_end())?; 72 | } else { 73 | write!(f, "{s}")?; 74 | } 75 | } 76 | } 77 | Separator::BinOp(s) => { 78 | if !first { 79 | if alternate { 80 | writeln!(f)?; 81 | write!(f, "{}", s.trim_start())?; 82 | } else { 83 | write!(f, "{s}")?; 84 | } 85 | } 86 | } 87 | Separator::Before(s) => { 88 | if alternate { 89 | if !first { 90 | writeln!(f)?; 91 | } 92 | write!(f, "{}", s.trim_start())?; 93 | } else if first { 94 | write!(f, "{}", s.trim_start())?; 95 | } else { 96 | write!(f, "{s}")?; 97 | } 98 | } 99 | } 100 | first = false; 101 | write_item(f, value)?; 102 | } 103 | if let Separator::After(s) = sep { 104 | write!(f, "{}", s.trim_end())?; 105 | } 106 | if outer_mode == OuterMode::Surrounded { 107 | writeln!(f)?; 108 | } 109 | Ok(()) 110 | } 111 | 112 | pub struct PadAdapter<'a, 'b> { 113 | fmt: &'a mut dyn Formatter, 114 | padding: &'b str, 115 | state: State, 116 | } 117 | 118 | impl<'a, 'c> PadAdapter<'a, 'c> { 119 | /// Creates a new pad adapter with default padding. 120 | pub fn new(fmt: &'a mut dyn Formatter) -> Self { 121 | Self { 122 | padding: if fmt.alternate() { " " } else { "" }, 123 | fmt, 124 | state: Default::default(), 125 | } 126 | } 127 | 128 | /// Creates a new pad adapter with the padding. 129 | pub fn with_padding(fmt: &'a mut dyn Formatter, padding: &'c str) -> Self { 130 | Self { 131 | fmt, 132 | padding, 133 | state: Default::default(), 134 | } 135 | } 136 | 137 | pub fn alternate(&self) -> bool { 138 | self.fmt.alternate() 139 | } 140 | } 141 | 142 | impl std::fmt::Write for PadAdapter<'_, '_> { 143 | fn write_str(&mut self, mut s: &str) -> std::fmt::Result { 144 | while !s.is_empty() { 145 | if self.state.on_newline { 146 | self.fmt.write_str(self.padding)?; 147 | } 148 | let split = match s.find('\n') { 149 | Some(pos) => { 150 | self.state.on_newline = true; 151 | pos + 1 152 | } 153 | None => { 154 | self.state.on_newline = false; 155 | s.len() 156 | } 157 | }; 158 | self.fmt.write_str(&s[..split])?; 159 | s = &s[split..]; 160 | } 161 | Ok(()) 162 | } 163 | } 164 | 165 | impl Formatter for PadAdapter<'_, '_> { 166 | fn alternate(&self) -> bool { 167 | PadAdapter::alternate(self) 168 | } 169 | } 170 | 171 | struct State { 172 | on_newline: bool, 173 | } 174 | 175 | impl Default for State { 176 | fn default() -> Self { 177 | Self { on_newline: true } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /crates/util/src/parc.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | #[derive(Default)] 4 | pub struct Parc(Arc); 5 | 6 | impl std::fmt::Debug for Parc { 7 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 8 | let value: &T = self; 9 | value.fmt(f) 10 | } 11 | } 12 | 13 | impl From> for Arc { 14 | fn from(val: Parc) -> Self { 15 | val.0 16 | } 17 | } 18 | 19 | impl From> for Parc { 20 | fn from(value: Arc) -> Self { 21 | Self(value) 22 | } 23 | } 24 | 25 | impl Parc { 26 | pub fn new(value: T) -> Self { 27 | Self(Arc::new(value)) 28 | } 29 | pub fn as_ptr(&self) -> *const T { 30 | Arc::as_ptr(&self.0) 31 | } 32 | } 33 | 34 | impl std::ops::Deref for Parc { 35 | type Target = T; 36 | fn deref(&self) -> &Self::Target { 37 | &self.0 38 | } 39 | } 40 | 41 | impl Clone for Parc { 42 | fn clone(&self) -> Self { 43 | Self(self.0.clone()) 44 | } 45 | } 46 | 47 | impl PartialEq for Parc { 48 | fn eq(&self, other: &Self) -> bool { 49 | Arc::ptr_eq(&self.0, &other.0) 50 | } 51 | } 52 | 53 | impl Eq for Parc {} 54 | 55 | impl PartialOrd for Parc { 56 | fn partial_cmp(&self, other: &Self) -> Option { 57 | Some(self.cmp(other)) 58 | } 59 | } 60 | 61 | impl Ord for Parc { 62 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 63 | let p1: *const () = Arc::as_ptr(&self.0) as _; 64 | let p2: *const () = Arc::as_ptr(&other.0) as _; 65 | p1.cmp(&p2) 66 | } 67 | } 68 | 69 | impl std::hash::Hash for Parc { 70 | fn hash(&self, state: &mut H) { 71 | Arc::as_ptr(&self.0).hash(state) 72 | } 73 | } 74 | 75 | impl std::fmt::Display for Parc { 76 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 77 | T::fmt(self, f) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/util/src/recurse_cache.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::*; 4 | 5 | #[derive(Default)] 6 | pub struct RecurseCache { 7 | cached: anymap3::AnyMap, 8 | } 9 | 10 | impl RecurseCache { 11 | pub fn new() -> Self { 12 | Self::default() 13 | } 14 | } 15 | 16 | impl RecurseCache { 17 | pub fn insert(&mut self, key: &K, value: V) { 18 | // 2 + 2 = √(69 / 2) - 1.87367006224 19 | self.cached 20 | .entry::>() 21 | .or_default() 22 | .insert(key.id(), value); 23 | } 24 | pub fn get(&self, key: &K) -> Option { 25 | self.cached 26 | .get::>() 27 | .and_then(|map| map.get(&key.id())) 28 | .cloned() 29 | } 30 | pub fn remove(&mut self, key: &K) -> Option { 31 | self.cached 32 | .get_mut::>() 33 | .and_then(|map| map.remove(&key.id())) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/util/src/show_short.rs: -------------------------------------------------------------------------------- 1 | pub trait ShowShort { 2 | fn show_short(&self) -> &'static str; 3 | } 4 | -------------------------------------------------------------------------------- /crates/util/src/source.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 4 | pub struct Position { 5 | pub index: usize, 6 | pub line: usize, 7 | pub column: usize, 8 | } 9 | 10 | impl Position { 11 | pub const ZERO: Self = Self { 12 | index: 0, 13 | line: 0, 14 | column: 0, 15 | }; 16 | } 17 | 18 | impl std::fmt::Display for Position { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | write!(f, "{}:{}", self.line, self.column) 21 | } 22 | } 23 | 24 | #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 25 | pub struct Span { 26 | pub start: Position, 27 | pub end: Position, 28 | pub filename: PathBuf, 29 | } 30 | 31 | impl std::fmt::Display for Span { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | match self.filename.to_str() { 34 | Some(filename) => write!(f, "{filename}")?, 35 | None => write!(f, "")?, 36 | } 37 | write!(f, ":{}", self.start)?; 38 | if self.start != self.end { 39 | write!(f, "~{}", self.end)?; 40 | } 41 | Ok(()) 42 | } 43 | } 44 | 45 | pub struct SourceFile { 46 | pub contents: String, 47 | pub filename: PathBuf, 48 | } 49 | -------------------------------------------------------------------------------- /examples/aoc-2015-1.input.txt: -------------------------------------------------------------------------------- 1 | ))((((( 2 | -------------------------------------------------------------------------------- /examples/aoc-2015-1.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let input = read_file "examples/aoc-2015-1.input.txt"; 4 | 5 | for c :: char in chars input { 6 | comptime dbg c; 7 | dbg c; 8 | }; 9 | -------------------------------------------------------------------------------- /examples/ast-nested-scope.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | include_ast `(let x = "hi"; (let x = "yo"); print x); 4 | -------------------------------------------------------------------------------- /examples/asteroids/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Asteroids 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/asteroids/js.ks: -------------------------------------------------------------------------------- 1 | module: 2 | 3 | use std; 4 | 5 | let HtmlElement :: type = builtin "HtmlElement"; 6 | 7 | let document = ( 8 | getElementById: builtin "function(id) { return document.getElementById(id); }" :: string -> HtmlElement, 9 | ); 10 | -------------------------------------------------------------------------------- /examples/asteroids/main.ks: -------------------------------------------------------------------------------- 1 | use std; 2 | 3 | use import "js.ks"; 4 | 5 | let canvas = document.getElementById "canvas"; 6 | 7 | print "hello, this is going to be a game of asteroids" 8 | -------------------------------------------------------------------------------- /examples/big-list.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | dbg <| time.now (); 4 | let start = time.now(); 5 | 6 | let mut i :: int32 = 0; 7 | let mut a :: list[int32] = list[]; 8 | while i < 100 { 9 | list_mut_push(&a, i); 10 | i += 1; 11 | }; 12 | 13 | dbg a; 14 | 15 | dbg (time.now() - start); 16 | -------------------------------------------------------------------------------- /examples/bijection.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | comptime with default_number_type_based_on_dot; 4 | 5 | syntax bijection <- 10 = lhs "<=>" rhs; 6 | impl syntax bijection = macro (.lhs, .rhs) => `( 7 | .into = fn ($lhs) with () { $rhs }, 8 | .from = fn ($rhs) with () { $lhs }, 9 | ); 10 | 11 | let foo = (x, y) <=> (y, x); 12 | let a = 123, "hello"; 13 | dbg &a; 14 | let b = foo.into a; 15 | dbg &b; 16 | let a-again = foo.from b; 17 | dbg &a-again; 18 | -------------------------------------------------------------------------------- /examples/bug.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const f = ( x :: Option[int64] ) => (); 4 | -------------------------------------------------------------------------------- /examples/context-shadow.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let show_current_context = () => ( 4 | print &(current string); 5 | ); 6 | 7 | ( 8 | with "123"; 9 | show_current_context(); 10 | ); 11 | 12 | ( 13 | with "456"; 14 | with "shadow"; 15 | show_current_context(); 16 | ); 17 | 18 | show_current_context(); # TODO This should not compile (no context available) 19 | -------------------------------------------------------------------------------- /examples/default-number-type.ks: -------------------------------------------------------------------------------- 1 | ( 2 | comptime with (.default_number_type = _ => std.int32) :: std.default_number_type; 3 | std.dbg 123; 4 | ); 5 | 6 | ( 7 | comptime with (.default_number_type = _ => std.int64) :: std.default_number_type; 8 | std.dbg 123; 9 | ); 10 | 11 | ( 12 | comptime with ( 13 | .default_number_type = s => ( 14 | if std.contains (.s, .substring=".") then 15 | std.float64 16 | else 17 | std.int32 18 | ), 19 | ) :: std.default_number_type; 20 | 21 | std.dbg 123; 22 | std.dbg 123.0; 23 | ); 24 | 25 | if false then ( 26 | # no default - this is going to fail 27 | std.dbg 123; 28 | ); 29 | -------------------------------------------------------------------------------- /examples/embed.rs: -------------------------------------------------------------------------------- 1 | use kast::*; 2 | 3 | // TODO: Make activation watermark on stream bigger 4 | fn main() -> eyre::Result<()> { 5 | let mut kast = Kast::new("")?; 6 | kast.register_fn("sum_all_ints", |v: Vec| -> i64 { v.into_iter().sum() }); 7 | let result = kast.eval_str_as::("sum_all_ints (list[1, 2, 3, 4, 5])")?; 8 | assert_eq!(result, 15); 9 | Ok(()) 10 | } 11 | -------------------------------------------------------------------------------- /examples/enumerate.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const enumerate = forall[T :: type] { 4 | fn (gen :: () -> ()) -> () with generator_handler[int32, T] { 5 | let outer = current generator_handler[int32, T]; 6 | let _ = current generator_handler[T]; 7 | let mut i = 0; 8 | for item :: T in gen() { 9 | outer.handle (i, item); 10 | i += 1; 11 | }; 12 | } 13 | }; 14 | 15 | for i :: int32, s :: string in enumerate[string] (() => list_iter list["hello", "world"]) { 16 | dbg (i, s); 17 | } -------------------------------------------------------------------------------- /examples/exceptions.ks: -------------------------------------------------------------------------------- 1 | use std; 2 | 3 | let test = fn (x :: int32) { 4 | let result :: Result[ok: string, error: int32] = 5 | try[ok: string, error: int32] ( 6 | if x == 0 then 7 | "hello" 8 | else 9 | throw[int32] x 10 | ); 11 | let value :: string = result catch e { dbg e; "thrown" }; 12 | print <| value; 13 | }; 14 | 15 | test 1; 16 | test 0; 17 | -------------------------------------------------------------------------------- /examples/exec-mode.ks: -------------------------------------------------------------------------------- 1 | std.exec_mode() 2 | -------------------------------------------------------------------------------- /examples/extrapolate.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | syntax extrapolate <- 1 = seq "..."; 4 | impl syntax extrapolate = macro (.seq) => `( 5 | extrapolate-impl list[$seq] 6 | ); 7 | 8 | const Seq = () -> int32 with (); 9 | 10 | let extrapolate-impl = fn(seq :: list[int32]) -> Seq with () { 11 | if list_length &seq != 3 then panic "can only extrapolate 3 for now"; 12 | let a, b, c = list_get (&seq, 0), list_get (&seq, 1), list_get (&seq, 2); 13 | let a, b, c = a^, b^, c^; 14 | let next = a; 15 | let next_step = b - a; 16 | let step_step = (c - b) - (b - a); 17 | fn (()) -> int32 with () { 18 | let cur = next; 19 | next += next_step; 20 | next_step += step_step; 21 | cur 22 | } 23 | }; 24 | 25 | # dbg `(1, 2, 3 ...); 26 | let seq = (1, 2, 4 ...); 27 | 28 | dbg (seq ()); 29 | dbg (seq ()); 30 | dbg (seq ()); 31 | dbg (seq ()); 32 | dbg (seq ()); 33 | dbg (seq ()); 34 | dbg (seq ()); 35 | dbg (seq ()); 36 | dbg (seq ()); 37 | dbg (seq ()); 38 | dbg (seq ()); 39 | dbg (seq ()); 40 | -------------------------------------------------------------------------------- /examples/f.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const Foo = newtype ( 4 | .a = int32, 5 | .b = int64, 6 | ); 7 | dbg Foo; 8 | -------------------------------------------------------------------------------- /examples/fibonacci.ks: -------------------------------------------------------------------------------- 1 | module: 2 | 3 | use std.*; 4 | 5 | let fib = fn (n :: int32) -> int32 { 6 | if n < 2 then 7 | 1 8 | else 9 | fib (n - 1) + fib (n - 2) 10 | }; 11 | -------------------------------------------------------------------------------- /examples/for-generator.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let generator = fn(()) { 4 | print "yielding 1"; 5 | yield "1"; 6 | print "yielding 2"; 7 | yield "2"; 8 | print "yielding stop"; 9 | yield "stop"; 10 | print "yielding 3"; 11 | yield "3"; 12 | }; 13 | 14 | for value in generator() { 15 | print value; 16 | if value == "stop" then ( 17 | break; 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /examples/generator.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | # TODO mark generator with yielding effect 4 | 5 | let generator = fn(void) { 6 | print "yielding 1"; 7 | yield "1"; 8 | print "yielding 2"; 9 | yield "2"; 10 | }; 11 | 12 | for value :: string in generator () { 13 | print &value; 14 | }; 15 | 16 | print "now using generator as value"; 17 | 18 | # TODO types should be inferred 19 | 20 | # let g = generator_value[Yield: string, Resume: void, Finish: void] generator; 21 | # print "calling .next()"; 22 | # dbg <| g.next(); 23 | # print "calling .next()"; 24 | # dbg <| g.next(); 25 | # print "calling .next()"; 26 | # dbg <| g.next(); 27 | # dbg <| g.next(); # this one panics since generator is finished 28 | -------------------------------------------------------------------------------- /examples/guess-a-number.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let run_once = fn (.quit, .first) { 4 | let picked :: int32 = random (.min = 1, .max = 10); 5 | print ( 6 | if first then 7 | "a number has been picked" 8 | else 9 | "a number has been picked again" 10 | ); 11 | print "guess:"; 12 | loop { 13 | let s = read_line (); 14 | if s == "exit" then ( 15 | let _ = unwind quit "quitted"; 16 | ); 17 | let guessed = &s |> parse; 18 | if guessed == picked then ( 19 | break 20 | ) else ( 21 | if picked < guessed then 22 | print "less! guess again:" 23 | else 24 | print "greater! guess again:"; 25 | ) 26 | }; 27 | print "you have guessed the number! congrats!"; 28 | }; 29 | 30 | let main = fn (void) { 31 | let result = unwindable quit ( 32 | run_once (.quit, .first = true); 33 | loop { 34 | run_once (.quit, .first = false); 35 | }; 36 | "not quitted" 37 | ); 38 | print result; 39 | }; 40 | 41 | main (); 42 | -------------------------------------------------------------------------------- /examples/hash_map.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | use std.collections.*; 3 | 4 | let mut map = HashMap.new (); 5 | HashMap.insert (&map, "hello", "world"); 6 | HashMap.insert (&map, "second", "2"); 7 | #dbg map; 8 | dbg <| HashMap.size ↦ 9 | dbg <| HashMap.get (&map, "hello"); 10 | dbg <| HashMap.get (&map, "world"); 11 | 12 | for key :: string, value :: string in HashMap.into_iter map { 13 | print "iterated"; 14 | }; 15 | -------------------------------------------------------------------------------- /examples/hello.ks: -------------------------------------------------------------------------------- 1 | std.print "Hello"; 2 | use std.*; 3 | "World" |> print; 4 | -------------------------------------------------------------------------------- /examples/import-zero.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | dbg import "./zero.ks" 4 | -------------------------------------------------------------------------------- /examples/inference-bug.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const test = fn(T :: type) { 4 | () 5 | }; 6 | 7 | const test2 = fn(T :: type) { 8 | test(T); 9 | comptime dbg T; 10 | test(T, T); 11 | comptime print "hi"; 12 | comptime dbg T; 13 | }; 14 | 15 | comptime dbg test; 16 | comptime dbg test2; 17 | #test2 (); 18 | -------------------------------------------------------------------------------- /examples/is.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let test = (value :: Option[int32]) => ( 4 | if value is :Some nice_value then 5 | dbg nice_value 6 | else 7 | print "None" 8 | ); 9 | 10 | test :Some 69; 11 | test :None; 12 | 13 | let none_is_some = :None is :Some x; 14 | dbg none_is_some; 15 | -------------------------------------------------------------------------------- /examples/late-inferred-context-type.ks: -------------------------------------------------------------------------------- 1 | let context = 123; 2 | with context; 3 | 4 | std.dbg (current std.float64); 5 | 6 | _ = context :: std.float64; 7 | -------------------------------------------------------------------------------- /examples/leetcode/2894.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let assert_eq = fn(actual :: int32, expected :: int32) { 4 | if actual != expected then ( 5 | dbg actual; 6 | dbg expected; 7 | panic "assertion failed"; 8 | ); 9 | }; 10 | 11 | let sum_from_1_to = fn(n :: int32) -> int32 { 12 | (1 + n) * n / 2 13 | }; 14 | 15 | let difference_of_sums = fn(n :: int32, m :: int32) -> int32 { 16 | # num2 = m + 2m + 3m + ... <= n 17 | let num2 = sum_from_1_to(n / m) * m; 18 | let num1 = sum_from_1_to(n) - num2; 19 | num1 - num2 20 | }; 21 | 22 | # assert_eq (difference_of_sums(10, 3), 19); 23 | # assert_eq (difference_of_sums(5, 6), 15); 24 | # assert_eq (difference_of_sums(5, 1), -15); 25 | 26 | let js_code :: string = std.javascript.transpile difference_of_sums; 27 | print "var differenceOfSums="; 28 | print &js_code; 29 | 30 | let test = fn (value :: (int32, int32)) { 31 | print "console.log(differenceOfSums({},"; 32 | print &(std.javascript.transpile value); 33 | print "))"; 34 | }; 35 | 36 | test (10, 3); 37 | test (5, 6); 38 | test (5, 1); 39 | 40 | print "let f=differenceOfSums"; 41 | print "differenceOfSums=(a, b)=>f({},{\"0\":a,\"1\":b})"; 42 | 43 | -------------------------------------------------------------------------------- /examples/leetcode/3373.ks: -------------------------------------------------------------------------------- 1 | use std.prelude.*; 2 | 3 | const HashMap = std.collections.HashMap; 4 | const HashSet = struct ( 5 | const HashSet = forall[T :: type] { HashMap.HashMap[T, ()] }; 6 | const new = forall[T :: type] { 7 | fn (()) -> HashSet[T] { 8 | HashMap.new[T, ()]() 9 | } 10 | }; 11 | const insert = forall[T :: type] { 12 | fn (set :: &HashSet[T], value :: T) { 13 | HashMap.insert[T, ()](set, value, ()); 14 | } 15 | }; 16 | const contains = forall[T :: type] { 17 | fn (set :: &HashSet[T], value :: &T) -> bool { 18 | HashMap.get[T, ()] (set, value) is :Some _ 19 | } 20 | }; 21 | const size = forall[T :: type] { 22 | fn (set :: &HashSet[T]) -> int32 { 23 | HashMap.size[T, ()] set 24 | } 25 | } 26 | ); 27 | 28 | const max = forall[T] { 29 | fn (a :: T, b :: T) -> T { 30 | if a > b then a else b 31 | } 32 | }; 33 | 34 | const List = std.collections.List; 35 | 36 | const VertexId = int32; 37 | const Vertex = type ( 38 | .edges :: list[VertexId], 39 | ); 40 | const Tree = type ( 41 | .vertices :: HashMap.HashMap[VertexId, Vertex], 42 | ); 43 | 44 | let tree_size = fn (tree :: &Tree) -> int32 { 45 | HashMap.size &(tree^).vertices 46 | }; 47 | 48 | const Edge = type (VertexId, VertexId); 49 | 50 | let make_tree = fn(edges :: &list[Edge]) -> Tree { 51 | let mut tree :: Tree = ( 52 | .vertices = HashMap.new (), 53 | ); 54 | let insert_edge = (a :: VertexId, b :: VertexId) => ( 55 | if HashMap.get (&tree.vertices, &a) is :None then ( 56 | HashMap.insert (&tree.vertices, a, ( .edges = list[] )); 57 | ); 58 | let :Some a = HashMap.get (&tree.vertices, &a); 59 | List.push (&(a^).edges, b); 60 | ); 61 | for edge :: &Edge in edges |> List.iter { 62 | let (a :: VertexId, b :: VertexId) = edge^; 63 | insert_edge (a, b); 64 | insert_edge (b, a); 65 | }; 66 | tree 67 | }; 68 | 69 | const TreeData = type ( 70 | .odd :: HashSet.HashSet[VertexId], 71 | .even :: HashSet.HashSet[VertexId], 72 | ); 73 | 74 | let tree_data = fn(tree :: &Tree) -> TreeData { 75 | let mut result :: TreeData = ( .odd = HashSet.new(), .even = HashSet.new() ); 76 | let root :: VertexId = 0; 77 | _ = rec ( 78 | let traverse = fn(current_vertex_id :: VertexId, prev_vertex_id :: VertexId, even :: bool) { 79 | let result_store = if even then ( &result.even ) else ( &result.odd ); 80 | HashSet.insert (result_store, current_vertex_id); 81 | let :Some current_vertex = HashMap.get (&(tree^).vertices, ¤t_vertex_id); 82 | for neighbor_id :: &VertexId in &(current_vertex^).edges |> List.iter { 83 | let neighbor_id = neighbor_id^; 84 | if neighbor_id != prev_vertex_id then ( 85 | traverse(neighbor_id, current_vertex_id, not even); 86 | ); 87 | }; 88 | }; 89 | traverse(root, root, true); 90 | ); 91 | result 92 | }; 93 | 94 | let max_target_nodes = fn(.edges1 :: list[Edge], .edges2 :: list[Edge]) -> list[int32] { 95 | let tree1 = make_tree &edges1; 96 | let tree1_data = &tree1 |> tree_data; 97 | let tree2 = make_tree &edges2; 98 | let tree2_data = &tree2 |> tree_data; 99 | let max_tree2_added_targets = max(HashSet.size &tree2_data.odd, HashSet.size &tree2_data.even); 100 | # dbg max_tree2_added_targets; 101 | # for v in 0..tree_size tree1 102 | let mut vertex_id = 0; 103 | let mut result = list[]; 104 | unwindable _loop ( 105 | loop { 106 | if not (vertex_id < tree_size &tree1) then ( 107 | unwind _loop (); 108 | ); 109 | let current_targets_in_tree_1 = if HashSet.contains(&tree1_data.even, &vertex_id) then 110 | HashSet.size &tree1_data.even 111 | else 112 | HashSet.size &tree1_data.odd; 113 | List.push(&result, current_targets_in_tree_1 + max_tree2_added_targets); 114 | vertex_id += 1; 115 | }; 116 | ); 117 | result 118 | }; 119 | 120 | let test = fn(.edges1 :: list[Edge], .edges2 :: list[Edge], .expected_result :: list[int32]) { 121 | let actual_result = max_target_nodes(.edges1, .edges2); 122 | if actual_result != expected_result then ( 123 | print "expected_result:"; 124 | dbg expected_result; 125 | print "actual_result:"; 126 | dbg actual_result; 127 | panic "test fails"; 128 | ); 129 | # print "test passed"; 130 | }; 131 | 132 | test( 133 | .edges1 = list[(0,1),(0,2),(2,3),(2,4)], 134 | .edges2 = list[(0,1),(0,2),(0,3),(2,7),(1,4),(4,5),(4,6)], 135 | .expected_result = list[8,7,7,8,8], 136 | ); 137 | test( 138 | .edges1 = list[(0,1),(0,2),(0,3),(0,4)], 139 | .edges2 = list[(0,1),(1,2),(2,3)], 140 | .expected_result = list[3,6,6,6,6], 141 | ); 142 | 143 | let max_target_nodes = fn(edges1 :: list[list[int32]], edges2 :: list[list[int32]]) -> list[int32] { 144 | let convert = fn(edges :: list[list[int32]]) -> list[Edge] { 145 | let mut result = list[]; 146 | for edge :: &list[int32] in List.iter &edges { 147 | if List.length edge != 2 then panic "wtf"; 148 | let a, b = List.get(edge, 0), List.get(edge, 1); 149 | let edge :: Edge = a^, b^; 150 | List.push(&result, edge); 151 | }; 152 | result 153 | }; 154 | let edges1 = edges1 |> convert; 155 | let edges2 = edges2 |> convert; 156 | list[] 157 | }; 158 | let js_code :: string = std.javascript.transpile max_target_nodes; 159 | print "var f="; 160 | print &js_code; 161 | 162 | print "maxTargetNodes=(edges1,edges2)=>f({},{edges1,edges2})"; 163 | 164 | print "console.log(maxTargetNodes([[0,1],[0,2],[2,3],[2,4]],[[0,1],[0,2],[0,3],[2,7],[1,4],[4,5],[4,6]]))" 165 | 166 | -------------------------------------------------------------------------------- /examples/lines.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let lines = fn(s) { 4 | let mut line = ""; 5 | for c in chars(s) { 6 | if c == '\n' then ( 7 | yield line; 8 | line = ""; 9 | ) else ( 10 | line = push_char(line, c); 11 | ) 12 | }; 13 | if line != "" then ( 14 | yield line; 15 | ); 16 | }; 17 | 18 | for line in lines(read_file "examples/lines.ks") { 19 | print line; 20 | } 21 | -------------------------------------------------------------------------------- /examples/lists.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | use std.collections.*; 3 | 4 | let x :: list[int32] = list[2, 4, 1, 3]; 5 | 6 | let dbg_list = forall[T] { 7 | fn (x :: list[T]) { 8 | for elem :: &T in List.iter &x { 9 | dbg elem; 10 | }; 11 | } 12 | }; 13 | dbg_list x; 14 | -------------------------------------------------------------------------------- /examples/local-syntax.ks: -------------------------------------------------------------------------------- 1 | const call-both = macro (.a, .b) => `($a(); $b()); 2 | 3 | let hello = () => std.print "hello"; 4 | let world = () => std.print "world"; 5 | 6 | ( 7 | syntax call-1-then-2 <- 20 = "call" a "and" b; 8 | impl syntax call-1-then-2 = call-both; 9 | call hello and world; 10 | ); 11 | 12 | ( 13 | syntax call-2-then-1 <- 20 = "call" b "and" a; 14 | impl syntax call-2-then-1 = call-both; 15 | call hello and world 16 | ); 17 | -------------------------------------------------------------------------------- /examples/loops.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | for c in chars "hello, world" { 4 | # if c == 'l' then ( continue ); 5 | # if c == ' ' then ( break ); 6 | dbg c; 7 | } 8 | -------------------------------------------------------------------------------- /examples/macro-hygiene.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const let_hello = macro _ => `( 4 | let hello = "hello"; 5 | print hello; 6 | ); 7 | const let_named = macro (name :: ast) => `( 8 | let $name = "named"; 9 | print $name; 10 | ); 11 | 12 | let hello = "hi"; 13 | print hello; # hi 14 | let_hello!(); # hello 15 | print hello; # still hi 16 | let_named!(hello); # named 17 | print hello; # named 18 | -------------------------------------------------------------------------------- /examples/manual-loop.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | const loop_handler_type = forall[T] { T -> () with output }; 3 | let loop_handler = c => dbg c; 4 | const yielded_type = _; 5 | comptime dbg yielded_type; 6 | with (loop_handler :: loop_handler_type[yielded_type]); 7 | comptime dbg yielded_type; 8 | 9 | ( 10 | (current loop_handler_type[char])('c'); 11 | comptime print "here"; 12 | ) ::with loop_handler_type[yielded_type]; 13 | comptime print "the end"; 14 | -------------------------------------------------------------------------------- /examples/match-in-macro.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const test = macro _ => `( 4 | match (123 :: int32) { value => value } 5 | ); 6 | 7 | dbg (match (123 :: int32) { value => value }); 8 | dbg test!(); 9 | -------------------------------------------------------------------------------- /examples/monad.ks: -------------------------------------------------------------------------------- 1 | # class Functor f where 2 | # fmap :: (a -> b) -> f a -> f b 3 | # 4 | # instance Functor Maybe where 5 | # fmap _ Nothing = Nothing 6 | # fmap f (Just a) = Just (f a) 7 | 8 | module: 9 | 10 | use std.*; 11 | 12 | const Functor = forall[F :: type -> type] { 13 | .fmap = forall[a :: type, b :: type] { 14 | (a -> b) -> F a -> F b 15 | }, 16 | }; 17 | 18 | const option = T => Option[T]; 19 | 20 | const add-five = forall[F] { 21 | f => (F as Functor).fmap (x => x + 5) 22 | }; 23 | 24 | add-five[option] (:Some (123 :: int32)) 25 | -------------------------------------------------------------------------------- /examples/move.ks: -------------------------------------------------------------------------------- 1 | let x = "hello"; 2 | let y = x; 3 | std.print x; 4 | -------------------------------------------------------------------------------- /examples/mut.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let mut x = "hello"; 4 | let f = () => print x; 5 | f(); 6 | ( 7 | # testing mutating from nested scope 8 | x = "world"; 9 | ); 10 | f(); 11 | -------------------------------------------------------------------------------- /examples/mutual-recursion.ks: -------------------------------------------------------------------------------- 1 | # “Given enough eyeballs, all bugs are shallow.” ― Linus Torvalds 2 | use std.*; 3 | 4 | let rec_scope = rec ( 5 | let f = depth => ( 6 | if depth < 3 then ( 7 | print "inside f"; 8 | dbg depth; 9 | g (depth + 1); 10 | ); 11 | ); 12 | let g = depth => ( 13 | print "inside g"; 14 | dbg depth; 15 | f (depth + 1); 16 | ); 17 | ); 18 | 19 | rec_scope.f (0 :: int32) 20 | 21 | -------------------------------------------------------------------------------- /examples/names.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const mod = struct ( 4 | const inner = struct ( 5 | const id = forall[T] { 6 | (x :: T) => x 7 | }; 8 | ); 9 | ); 10 | 11 | const id = forall[T] { () => () }; 12 | dbg id[int32]; 13 | -------------------------------------------------------------------------------- /examples/ternary.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | syntax ternary -> 13.1 = condition "?" then_case ":" else_case; 4 | impl syntax ternary = macro (.condition, .then_case, .else_case) => `( 5 | if $condition then $then_case else $else_case 6 | ); 7 | 8 | let x :: int32 = true ? 1 : 0; 9 | dbg x; 10 | -------------------------------------------------------------------------------- /examples/throwing_map.ks: -------------------------------------------------------------------------------- 1 | # array.iter().map(|x| x.fallible()?).collect() 2 | 3 | syntax map_impl <- 300 = generator "." "map" map_fn; 4 | 5 | const map_impl = macro (.generator, .map_fn) => `( 6 | const outer_y = _; const inner_y = _; 7 | let map_fn :: inner_y -> outer_y = $map_fn; 8 | dbg map_fn; 9 | let outer = current yields[Yield: outer_y, Resume: void]; 10 | let inner :: yields[Yield: inner_y, Resume: void] = ( 11 | yield: value => ( 12 | outer.yield (map_fn value) 13 | ), 14 | ); 15 | with inner ( $generator ) 16 | ); 17 | 18 | let generator = fn(void) yields[Yield: int32, Resume: void] { 19 | yield (1 :: int32); 20 | yield (2 :: int32); 21 | }; 22 | 23 | let map_fn :: int32 -> string = fn (s) { 24 | print "throwing"; 25 | throw "haha" 26 | }; 27 | 28 | let result = try[ok: void, error: string] ( 29 | for value in (generator()) .map map_fn { 30 | print value; 31 | }; 32 | ); 33 | dbg result; 34 | -------------------------------------------------------------------------------- /examples/traits.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const Foo :: type = ( .a = int32, .b = int32); 4 | const Clone = forall[Self] { 5 | .clone = &Self -> Self, 6 | }; 7 | impl Foo as Clone = ( 8 | .clone = self => ( .a = (self^).a, .b = (self^).b), 9 | ); 10 | let duplicate = forall[T] { 11 | fn (x :: T) -> (T, T) { 12 | ( 13 | (T as Clone).clone(&x), 14 | (_ as Clone).clone(&x), 15 | ) 16 | } 17 | }; 18 | let foo :: (Foo, Foo) = duplicate ( .a = 1, .b = 2 ); 19 | dbg foo; 20 | -------------------------------------------------------------------------------- /examples/transitive-mutability.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let x; 4 | 5 | let mut x :: int32 = 123; 6 | x += 1; 7 | 8 | let foo = (.a = &x, .b = "hi"); 9 | dbg foo; 10 | 11 | let mut a = list[1, 2, 3] :: list[int32]; 12 | dbg &a; 13 | # TODO this should not compile? 14 | let elem1 = list_get (&a, 1); 15 | let elem2 = list_get (&a, 2); 16 | dbg elem1; 17 | elem1^ = 5; 18 | dbg &a; 19 | -------------------------------------------------------------------------------- /examples/transpile-to-javascript.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | comptime with default_number_type_based_on_dot; 4 | 5 | let console = struct ( 6 | # const log = forall[T] { native "console.log" :: T -> () }; 7 | let log = fn(s :: string) -> () { 8 | native "console.log" s 9 | }; 10 | ); 11 | 12 | let g = x => (); 13 | let f = (a, b) => (g a; g b); 14 | let a, b = 12345, 54321; 15 | const main = () => ( 16 | console.log "Hello, world"; 17 | # console.log 123; 18 | ); 19 | 20 | let js_code = javascript.transpile main; 21 | print "main="; 22 | print &js_code; 23 | print "main()" 24 | -------------------------------------------------------------------------------- /examples/ttv.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let commands = ( 4 | .hellopgorley = fn(_args :: list[&string]) -> Option[string] { 5 | :Some "hello, pgorley" 6 | }, 7 | ); 8 | 9 | let is_whitespace = fn(c :: char) -> bool { 10 | c == ' ' 11 | }; 12 | 13 | let split_whitespace = fn(s :: &string) -> () with loop_context[string] { 14 | let cur :: Option[string] = :None; 15 | let finish = () => ( 16 | if cur is :Some cur then ( 17 | yield cur; 18 | ); 19 | cur = :None; 20 | ); 21 | for c :: char in chars s { 22 | if is_whitespace c then ( 23 | finish(); 24 | ) else ( 25 | if cur is :None then ( cur = :Some "" ); 26 | let :Some (ref cur) = cur; 27 | cur^ = push_char (cur^, c); 28 | ) 29 | }; 30 | finish(); 31 | }; 32 | 33 | let handle_chat = fn(s :: &string) -> Option[string] { 34 | for arg :: string in split_whitespace s { 35 | dbg arg; 36 | }; 37 | :None 38 | }; 39 | 40 | if handle_chat "!hellopgorley" is :Some reply then ( 41 | print &reply; 42 | ); 43 | -------------------------------------------------------------------------------- /examples/tuples.ks: -------------------------------------------------------------------------------- 1 | use std.prelude.*; 2 | 3 | let f = forall[T] { fn(x :: T) -> _ { x, x } }; 4 | dbg <| f[int32] (123 :: int32) 5 | -------------------------------------------------------------------------------- /examples/type-inspect.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | const mod = struct ( 4 | const Foo = newtype ( .x = int64 ); 5 | const m = macro (body) => ( 6 | let ty :: type = eval_ast `(typeof $body); 7 | if ty == int32 then ( 8 | `(dbg ( ( .x = 123 ) :: Foo )) 9 | ) else 10 | `(print "not an int32!") 11 | ); 12 | ); 13 | 14 | let x :: int32; 15 | mod.m!(x); 16 | -------------------------------------------------------------------------------- /examples/unsafe.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | # TODO smth 4 | const unsafe :: type = newtype :Unsafe; 5 | 6 | let unsafe_fn = fn (s :: string) with (unsafe | output) { 7 | print &s; 8 | }; 9 | 10 | ( 11 | with :Unsafe :: unsafe; 12 | unsafe_fn "hello"; 13 | ) 14 | -------------------------------------------------------------------------------- /examples/unwind.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let result = unwindable outer ( 4 | unwindable inner ( 5 | print "hi"; 6 | unwind outer "im out"; 7 | print "this will never be printed"; 8 | "this is not the result" 9 | ) 10 | ); 11 | dbg result; 12 | -------------------------------------------------------------------------------- /examples/variant-types.ks: -------------------------------------------------------------------------------- 1 | use std.*; 2 | 3 | let value = :Some "hello" :: Option[string]; 4 | 5 | if value is :Some (ref value) then ( 6 | print &"some"; 7 | print (value :: &string); 8 | ); 9 | 10 | match value { 11 | | :Some _ => ( 12 | print &"some"; 13 | ) 14 | | :None => print &"none" 15 | }; 16 | 17 | const either_type :: type = Either[.left = int32, .right = string]; 18 | let value_left = either_type of :Left 123; 19 | let value_right :: either_type = :Right "right value"; 20 | let check_either = (value :: either_type) => ( 21 | match value { 22 | | :Left value => print &"left" 23 | | :Right value => print &value 24 | } 25 | ); 26 | check_either value_left; 27 | check_either value_right; 28 | 29 | const result_type :: type = Result[.ok = (), .error = string]; 30 | let unwrap = (result :: result_type) => ( 31 | match result { 32 | | :Ok value => ( 33 | print &"unwrapped successfully"; 34 | value 35 | ) 36 | | :Error error => panic error 37 | } 38 | ); 39 | 40 | :Ok () |> unwrap; 41 | :Error "this is going to panic" |> unwrap; 42 | -------------------------------------------------------------------------------- /examples/zero.ks: -------------------------------------------------------------------------------- 1 | 0 :: std.int32 2 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "crane-flake": { 4 | "locked": { 5 | "lastModified": 1748047550, 6 | "narHash": "sha256-t0qLLqb4C1rdtiY8IFRH5KIapTY/n3Lqt57AmxEv9mk=", 7 | "owner": "ipetkov", 8 | "repo": "crane", 9 | "rev": "b718a78696060df6280196a6f992d04c87a16aef", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "ipetkov", 14 | "repo": "crane", 15 | "type": "github" 16 | } 17 | }, 18 | "nix-filter-flake": { 19 | "locked": { 20 | "lastModified": 1731533336, 21 | "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=", 22 | "owner": "numtide", 23 | "repo": "nix-filter", 24 | "rev": "f7653272fd234696ae94229839a99b73c9ab7de0", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "numtide", 29 | "repo": "nix-filter", 30 | "type": "github" 31 | } 32 | }, 33 | "nixpkgs": { 34 | "locked": { 35 | "lastModified": 1748217807, 36 | "narHash": "sha256-P3u2PXxMlo49PutQLnk2PhI/imC69hFl1yY4aT5Nax8=", 37 | "owner": "nixos", 38 | "repo": "nixpkgs", 39 | "rev": "3108eaa516ae22c2360928589731a4f1581526ef", 40 | "type": "github" 41 | }, 42 | "original": { 43 | "owner": "nixos", 44 | "ref": "nixpkgs-unstable", 45 | "repo": "nixpkgs", 46 | "type": "github" 47 | } 48 | }, 49 | "root": { 50 | "inputs": { 51 | "crane-flake": "crane-flake", 52 | "nix-filter-flake": "nix-filter-flake", 53 | "nixpkgs": "nixpkgs", 54 | "rust-overlay": "rust-overlay" 55 | } 56 | }, 57 | "rust-overlay": { 58 | "inputs": { 59 | "nixpkgs": [ 60 | "nixpkgs" 61 | ] 62 | }, 63 | "locked": { 64 | "lastModified": 1748227081, 65 | "narHash": "sha256-RLnN7LBxhEdCJ6+rIL9sbhjBVDaR6jG377M/CLP/fmE=", 66 | "owner": "oxalica", 67 | "repo": "rust-overlay", 68 | "rev": "1cbe817fd8c64a9f77ba4d7861a4839b0b15983e", 69 | "type": "github" 70 | }, 71 | "original": { 72 | "owner": "oxalica", 73 | "repo": "rust-overlay", 74 | "type": "github" 75 | } 76 | } 77 | }, 78 | "root": "root", 79 | "version": 7 80 | } 81 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 4 | rust-overlay = { 5 | url = "github:oxalica/rust-overlay"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | crane-flake.url = "github:ipetkov/crane"; 9 | 10 | # Precisely filter files copied to the nix store 11 | nix-filter-flake.url = "github:numtide/nix-filter"; 12 | }; 13 | 14 | outputs = { self, nixpkgs, nix-filter-flake, rust-overlay, crane-flake }: 15 | let 16 | system = "x86_64-linux"; 17 | pkgs = import nixpkgs { inherit system; overlays = [ (import rust-overlay) ]; }; 18 | crane = crane-flake.mkLib pkgs; 19 | nix-filter = nix-filter-flake.lib; 20 | rust-toolchain = pkgs.rust-bin.stable.latest.default.override { 21 | extensions = [ "rust-src" ]; 22 | targets = [ "wasm32-unknown-unknown" ]; 23 | }; 24 | kast = 25 | let 26 | commonArgs = { 27 | src = nix-filter { 28 | root = ./.; 29 | include = [ 30 | "crates" 31 | "std" 32 | "src" 33 | "Cargo.toml" 34 | "Cargo.lock" 35 | ]; 36 | }; 37 | }; 38 | cargoArtifacts = crane.buildDepsOnly commonArgs; 39 | in 40 | crane.buildPackage (commonArgs // { 41 | inherit cargoArtifacts; 42 | buildInputs = [ 43 | pkgs.makeWrapper 44 | ]; 45 | postFixup = '' 46 | wrapProgram $out/bin/kast --set KAST_STD ${./std} 47 | ''; 48 | }); 49 | in 50 | { 51 | packages.${system} = { 52 | default = kast; 53 | }; 54 | devShells.${system} = { 55 | default = pkgs.mkShell { 56 | packages = with pkgs; [ 57 | rust-toolchain 58 | rust-analyzer 59 | just 60 | cargo-flamegraph 61 | cargo-outdated 62 | nodejs 63 | ]; 64 | shellHook = '' 65 | echo Hello from Kast devshell 66 | ''; 67 | }; 68 | }; 69 | formatter.${system} = pkgs.nixpkgs-fmt; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /src/cast.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(TryHash, PartialEq, Eq, Clone)] 4 | struct Key { 5 | #[try_hash] 6 | value: Value, 7 | #[try_hash] 8 | target: Value, 9 | } 10 | 11 | impl Key { 12 | fn new(value: Value, target: Value) -> eyre::Result { 13 | let err = eyre!("cast key: value = {}, target = {}", value, target); 14 | let key = Self { value, target }; 15 | key.try_hash(&mut std::hash::DefaultHasher::new()) 16 | .map_err(|e| eyre!(e)) 17 | .wrap_err(err)?; 18 | Ok(key) 19 | } 20 | } 21 | 22 | impl std::hash::Hash for Key { 23 | fn hash(&self, state: &mut H) { 24 | self.try_hash(state).unwrap() 25 | } 26 | } 27 | 28 | #[derive(Default)] 29 | pub struct CastMap { 30 | map: HashMap, 31 | } 32 | 33 | impl CastMap { 34 | #[allow(dead_code)] 35 | pub fn new() -> Self { 36 | Self::default() 37 | } 38 | pub fn impl_cast(&mut self, value: Value, target: Value, r#impl: Value) -> eyre::Result<()> { 39 | tracing::trace!("impl {value} as {target} = {impl} :: {}", r#impl.ty()); 40 | self.map.insert(Key::new(value, target)?, r#impl); 41 | Ok(()) 42 | } 43 | #[allow(clippy::only_used_in_recursion)] 44 | pub fn cast_to_ty(&self, value: Value) -> eyre::Result> { 45 | // TODO no clone 46 | Ok(Ok(match value.clone().into_inferred()? { 47 | ValueShape::Unit => TypeShape::Unit.into(), 48 | // TODO check is this useful? 49 | // ValueShape::Ref(place) => TypeShape::Ref( 50 | // self.cast_to_ty(place.claim_value()?)? 51 | // .map_err(|value| eyre!("{value} is not a type"))?, 52 | // ) 53 | // .into(), 54 | 55 | // Cast `TupleValue` (value-tuple with types as fields) into a `TypeShape::Tuple` (type-tuple which is a type itself) 56 | ValueShape::Tuple(tuple) => { 57 | let name = tuple.name.clone(); 58 | let mut field_types = Tuple::::empty(); 59 | for (member, value) in tuple.into_values() { 60 | field_types.add_member( 61 | member, 62 | self.cast_to_ty(value)? 63 | .map_err(|value| eyre!("{value} is not a type"))?, 64 | ); 65 | } 66 | TypeShape::Tuple(TupleType { 67 | name: name.into(), 68 | fields: field_types, 69 | }) 70 | .into() 71 | } 72 | ValueShape::Binding(binding) => { 73 | binding.ty.infer_as(TypeShape::Type)?; 74 | TypeShape::Binding(binding).into() 75 | } 76 | inferred => match inferred.into_type() { 77 | Ok(value) => value, 78 | Err(_e) => return Ok(Err(value)), 79 | }, 80 | })) 81 | } 82 | pub fn cast(&self, value: Value, target: &Value) -> eyre::Result> { 83 | #[allow(clippy::single_match)] 84 | match target.clone().into_inferred()? { 85 | ValueShape::Type(ty) => match ty.inferred() { 86 | Ok(TypeShape::Type) => { 87 | return self 88 | .cast_to_ty(value) 89 | .map(|result| result.map(ValueShape::Type).map(Into::into)); 90 | } 91 | _ => {} 92 | }, 93 | _ => {} 94 | } 95 | let key = Key::new(value.clone(), target.clone())?; 96 | Ok(self.map.get(&key).cloned().ok_or(value)) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | #[derive(clap::Subcommand)] 4 | pub enum Command { 5 | ParseAst, 6 | Repl { 7 | path: Option, 8 | #[clap(long)] 9 | no_stdlib: bool, 10 | #[clap(long)] 11 | prerun: Option, 12 | }, 13 | Run { 14 | path: PathBuf, 15 | #[clap(long)] 16 | no_stdlib: bool, 17 | }, 18 | } 19 | 20 | #[derive(clap::Parser)] 21 | #[clap(args_conflicts_with_subcommands = true)] 22 | pub struct Args { 23 | #[clap(long, default_value = "info")] 24 | pub log: tracing_subscriber::filter::LevelFilter, 25 | pub path: Option, 26 | #[clap(subcommand)] 27 | pub command: Option, 28 | } 29 | 30 | impl Args { 31 | pub fn command(self) -> Command { 32 | match self.command { 33 | Some(command) => command, 34 | None => match self.path { 35 | Some(path) => Command::Run { 36 | path, 37 | no_stdlib: false, 38 | }, 39 | None => Command::Repl { 40 | path: None, 41 | no_stdlib: false, 42 | prerun: None, 43 | }, 44 | }, 45 | } 46 | } 47 | } 48 | 49 | pub fn parse() -> Args { 50 | if cfg!(target_arch = "wasm32") { 51 | return Args { 52 | log: tracing_subscriber::filter::LevelFilter::WARN, 53 | path: None, 54 | command: None, 55 | }; 56 | } 57 | clap::Parser::parse() 58 | } 59 | -------------------------------------------------------------------------------- /src/comments.rs: -------------------------------------------------------------------------------- 1 | // idk why i choosed the horrible language rust to implement this. LUA is my all-time favourite! I 2 | // love that arrays start at index 1 3 | -------------------------------------------------------------------------------- /src/contexts.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(TryHash, PartialEq, Eq, Clone)] 4 | struct Key(#[try_hash] Type); 5 | 6 | impl Key { 7 | fn new(ty: Type) -> eyre::Result { 8 | let err = eyre!("context ty: {ty}"); 9 | let key = Self(ty); 10 | key.try_hash(&mut std::hash::DefaultHasher::new()) 11 | .map_err(|e| eyre!(e)) 12 | .wrap_err(err)?; 13 | Ok(key) 14 | } 15 | } 16 | 17 | impl std::hash::Hash for Key { 18 | fn hash(&self, state: &mut H) { 19 | self.try_hash(state).unwrap() 20 | } 21 | } 22 | 23 | // idk how i ended up with this approach 24 | #[derive(Clone)] 25 | pub struct State { 26 | runtime_contexts: HashMap, 27 | } 28 | 29 | pub fn default_file_system() -> Value { 30 | let mut context = Tuple::empty(); 31 | context.add_named( 32 | "read_file", 33 | ValueShape::NativeFunction(NativeFunction::new( 34 | Name::new(NamePart::Str("native.read_file".into())), 35 | "read_file", 36 | FnType { 37 | arg: TypeShape::String.into(), 38 | contexts: Contexts::empty(), 39 | result: TypeShape::String.into(), 40 | }, 41 | |_kast, _fn_ty, path: Value| { 42 | async move { 43 | let path = path.into_inferred()?.as_str()?.to_owned(); 44 | let contents = std::fs::read_to_string(path)?; 45 | Ok(ValueShape::String(contents).into()) 46 | } 47 | .boxed() 48 | }, 49 | )) 50 | .into(), 51 | ); 52 | let context_ty = TypeShape::Tuple(TupleType { 53 | name: None.into(), 54 | fields: context.as_ref().map(|field: &Value| field.ty()), 55 | }) 56 | .into(); 57 | let context: Value = ValueShape::Tuple(TupleValue::new_unnamed(context, context_ty)).into(); 58 | context 59 | } 60 | 61 | pub fn default_number_type() -> Value { 62 | let mut context = Tuple::empty(); 63 | context.add_named( 64 | "default_number_type", 65 | ValueShape::NativeFunction(NativeFunction::new( 66 | Name::new(NamePart::Str("native.default_number_type".into())), 67 | "default_number_type", 68 | FnType { 69 | arg: TypeShape::String.into(), 70 | contexts: Contexts::empty(), 71 | result: TypeShape::Type.into(), 72 | }, 73 | |_kast, _fn_ty, s: Value| { 74 | async move { 75 | let _s = s.into_inferred()?.as_str()?; 76 | Ok(ValueShape::Type(Type::new_not_inferred("default number type")).into()) 77 | } 78 | .boxed() 79 | }, 80 | )) 81 | .into(), 82 | ); 83 | let context_ty = TypeShape::Tuple(TupleType { 84 | name: None.into(), 85 | fields: context.as_ref().map(|field: &Value| field.ty()), 86 | }) 87 | .into(); 88 | let context: Value = ValueShape::Tuple(TupleValue::new_unnamed(context, context_ty)).into(); 89 | context 90 | } 91 | 92 | pub fn default_output() -> Value { 93 | let write_type = FnType { 94 | arg: TypeShape::Ref(TypeShape::String.into()).into(), 95 | contexts: Contexts::empty(), 96 | result: TypeShape::Unit.into(), 97 | }; 98 | let mut context_type_fields = Tuple::empty(); 99 | context_type_fields.add_named( 100 | "write", 101 | TypeShape::Function(Box::new(write_type.clone())).into(), 102 | ); 103 | let context_type = TypeShape::Tuple(TupleType { 104 | name: Some(Name::new(NamePart::Str("output".into()))).into(), 105 | fields: context_type_fields, 106 | }) 107 | .into(); 108 | let mut context = Tuple::empty(); 109 | context.add_named( 110 | "write", 111 | ValueShape::NativeFunction(NativeFunction::new( 112 | Name::new(NamePart::Str("native.print".into())), 113 | "print", 114 | write_type, 115 | |kast: Kast, _fn_ty, s: Value| { 116 | async move { 117 | let s = s.into_inferred()?; 118 | let s = s.into_ref()?; 119 | let s = s.read_value()?; 120 | let s = s.as_inferred()?; 121 | let s = s.as_str()?; 122 | kast.output.write(s); 123 | Ok(ValueShape::Unit.into()) 124 | } 125 | .boxed() 126 | }, 127 | )) 128 | .into(), 129 | ); 130 | let context: Value = ValueShape::Tuple(TupleValue::new_unnamed(context, context_type)).into(); 131 | context 132 | } 133 | 134 | pub fn default_input() -> Value { 135 | let mut context = Tuple::empty(); 136 | context.add_named( 137 | "read_line", 138 | ValueShape::NativeFunction(NativeFunction::new( 139 | Name::new(NamePart::Str("native.read_line".into())), 140 | "read_line", 141 | FnType { 142 | arg: TypeShape::Unit.into(), 143 | contexts: Contexts::empty(), 144 | result: TypeShape::String.into(), 145 | }, 146 | |kast: Kast, _fn_ty, _arg: Value| { 147 | async move { 148 | let s = kast.input.read_line(); 149 | Ok(ValueShape::String(s).into()) 150 | } 151 | .boxed() 152 | }, 153 | )) 154 | .into(), 155 | ); 156 | let context_ty = TypeShape::Tuple(TupleType { 157 | name: None.into(), 158 | fields: context.as_ref().map(|field: &Value| field.ty()), 159 | }) 160 | .into(); 161 | let context: Value = ValueShape::Tuple(TupleValue::new_unnamed(context, context_ty)).into(); 162 | context 163 | } 164 | 165 | impl State { 166 | pub fn empty() -> Self { 167 | Self { 168 | runtime_contexts: Default::default(), 169 | } 170 | } 171 | pub fn default() -> Self { 172 | let mut contexts = Self::empty(); 173 | contexts.insert_runtime(default_output()).unwrap(); 174 | contexts.insert_runtime(default_input()).unwrap(); 175 | contexts.insert_runtime(default_number_type()).unwrap(); 176 | contexts.insert_runtime(default_file_system()).unwrap(); 177 | contexts 178 | } 179 | pub fn insert_runtime(&mut self, context: Value) -> eyre::Result<()> { 180 | let key = Key::new(context.ty())?; 181 | self.runtime_contexts.insert(key, context); 182 | Ok(()) 183 | } 184 | pub fn get_runtime(&self, ty: Type) -> eyre::Result> { 185 | let key = Key::new(ty)?; 186 | Ok(self.runtime_contexts.get(&key).cloned()) 187 | } 188 | } 189 | 190 | #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] 191 | pub struct ContextsData { 192 | // TODO multiset? 193 | pub types: std::collections::BTreeSet, 194 | pub growable: bool, 195 | } 196 | 197 | #[derive(Debug, Clone, TryHash, PartialEq, Eq, PartialOrd, Ord)] 198 | pub struct Contexts(#[try_hash] pub inference::MaybeNotInferred); 199 | 200 | impl Contexts { 201 | pub fn var(&self) -> &inference::Var { 202 | self.0.var() 203 | } 204 | pub fn single(ty: Type) -> eyre::Result { 205 | let this = inference::MaybeNotInferred::new_set(ContextsData { 206 | types: Default::default(), 207 | growable: true, 208 | }); 209 | ty.var().add_check({ 210 | let this = this.clone(); 211 | move |ty: &TypeShape| -> eyre::Result { 212 | let mut current = this.inferred().unwrap(); 213 | current.extend_with_types([&ty.clone().into()])?; 214 | this.var().set(current)?; 215 | Ok(inference::CheckResult::Completed) 216 | } 217 | })?; 218 | Ok(Self(this)) 219 | } 220 | pub fn remove_injected(contexts: &Self, ty: &Type) -> eyre::Result { 221 | let this = inference::MaybeNotInferred::new_set(ContextsData { 222 | types: Default::default(), 223 | growable: true, 224 | }); 225 | let update = { 226 | let contexts = contexts.clone(); 227 | let ty = ty.clone(); 228 | let this = this.clone(); 229 | move || -> eyre::Result { 230 | // TODO fully_inferred? 231 | if ty.inferred().is_err() { 232 | return Ok(inference::CheckResult::RunAgain); 233 | }; 234 | let Ok(mut contexts) = contexts.0.inferred() else { 235 | return Ok(inference::CheckResult::RunAgain); 236 | }; 237 | let _was_removed = contexts.types.remove(&ty); 238 | this.var().set(contexts)?; 239 | Ok(inference::CheckResult::RunAgain) 240 | } 241 | }; 242 | contexts.var().add_check({ 243 | let update = update.clone(); 244 | move |_| update() 245 | })?; 246 | ty.var().add_check(move |_| update())?; 247 | Ok(Self(this)) 248 | } 249 | pub fn merge<'a>(iter: impl IntoIterator) -> eyre::Result { 250 | let this = inference::MaybeNotInferred::new_set(ContextsData { 251 | types: Default::default(), 252 | growable: true, 253 | }); 254 | let update = { 255 | let this = this.clone(); 256 | move |updated: &ContextsData| -> eyre::Result { 257 | let mut current = this.inferred().unwrap(); 258 | current.extend_with(updated)?; 259 | this.var().set(current)?; 260 | Ok(inference::CheckResult::RunAgain) 261 | } 262 | }; 263 | for elem in iter { 264 | elem.var().add_check(update.clone())?; 265 | } 266 | Ok(Self(this)) 267 | } 268 | pub fn merge_two(a: &Self, b: &Self) -> eyre::Result { 269 | Self::merge([a, b]) 270 | } 271 | } 272 | 273 | impl TryHash for ContextsData { 274 | type Error = eyre::Report; 275 | fn try_hash(&self, hasher: &mut impl std::hash::Hasher) -> Result<(), Self::Error> { 276 | if self.growable { 277 | eyre::bail!("cant hash growable contexts"); 278 | } 279 | for ty in &self.types { 280 | ty.try_hash(hasher)?; 281 | } 282 | Ok(()) 283 | } 284 | } 285 | 286 | impl std::fmt::Display for ContextsData { 287 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 288 | for (index, ty) in self.types.iter().enumerate() { 289 | if index != 0 { 290 | write!(f, " | ")?; 291 | } 292 | write!(f, "{ty}")?; 293 | } 294 | if self.growable { 295 | if !self.types.is_empty() { 296 | write!(f, " | ")?; 297 | } 298 | write!(f, "_")?; 299 | } 300 | Ok(()) 301 | } 302 | } 303 | 304 | impl std::fmt::Display for Contexts { 305 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 306 | self.0.fmt(f) 307 | } 308 | } 309 | 310 | impl Contexts { 311 | pub fn empty() -> Self { 312 | Self::from_list([], false) 313 | } 314 | pub fn empty_growable() -> Self { 315 | Self::from_list([], true) 316 | } 317 | pub fn from_list(list: impl IntoIterator, growable: bool) -> Self { 318 | Self(inference::MaybeNotInferred::new_set(ContextsData { 319 | types: list.into_iter().collect(), 320 | growable, 321 | })) 322 | } 323 | pub fn is_empty(&self) -> bool { 324 | let data = self.0.inferred().unwrap(); 325 | !data.growable && data.types.is_empty() 326 | } 327 | pub fn new_not_inferred() -> Self { 328 | Self(inference::MaybeNotInferred::new_set(ContextsData { 329 | types: Default::default(), 330 | growable: true, 331 | })) 332 | } 333 | } 334 | 335 | impl ContextsData { 336 | pub fn extend_with_types<'a>( 337 | &mut self, 338 | types: impl IntoIterator, 339 | ) -> eyre::Result<()> { 340 | // TODO multiple occurences 341 | for ty in types { 342 | if !self.types.contains(ty) { 343 | if !self.growable { 344 | eyre::bail!("context not listed: {ty}"); 345 | } 346 | self.types.insert(ty.clone()); 347 | } 348 | } 349 | Ok(()) 350 | } 351 | pub fn extend_with(&mut self, that: &Self) -> eyre::Result<()> { 352 | self.extend_with_types(that.types.iter()) 353 | } 354 | } 355 | 356 | impl SubstituteBindings for ContextsData { 357 | type Target = Self; 358 | fn substitute_bindings(self, kast: &Kast, cache: &mut RecurseCache) -> Self { 359 | Self { 360 | types: self 361 | .types 362 | .clone() 363 | .into_iter() 364 | .map(|ty| ty.substitute_bindings(kast, cache)) 365 | .collect(), 366 | growable: self.growable, 367 | } 368 | } 369 | } 370 | 371 | impl SubstituteBindings for Contexts { 372 | type Target = Self; 373 | fn substitute_bindings(self, kast: &Kast, cache: &mut RecurseCache) -> Self { 374 | Self(inference::MaybeNotInferred::new_set( 375 | self.0.inferred().unwrap().substitute_bindings(kast, cache), 376 | )) 377 | } 378 | } 379 | 380 | impl Inferrable for ContextsData { 381 | fn make_same(mut a: Self, mut b: Self) -> eyre::Result { 382 | #![allow(unused_mut)] 383 | a.extend_with(&b)?; 384 | b.extend_with(&a)?; 385 | Ok(Self { 386 | types: a.types, 387 | growable: a.growable && b.growable, 388 | }) 389 | } 390 | } 391 | 392 | impl Inferrable for Contexts { 393 | fn make_same(a: Self, b: Self) -> eyre::Result { 394 | let mut a = a; 395 | a.0.make_same(b.0)?; 396 | Ok(a) 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /src/executor.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | struct ExecutorImpl { 4 | inner: async_executor::Executor<'static>, 5 | } 6 | 7 | impl Drop for ExecutorImpl { 8 | fn drop(&mut self) { 9 | while self.inner.try_tick() {} 10 | if !self.inner.is_empty() { 11 | panic!("executor still has unfinished tasks???"); 12 | } 13 | } 14 | } 15 | 16 | #[derive(Clone)] 17 | pub struct Executor { 18 | inner: Parc, 19 | } 20 | 21 | pub struct Spawned( 22 | Parc, BoxFuture<'static, eyre::Result>>>, 23 | ); 24 | 25 | impl Clone for Spawned { 26 | fn clone(&self) -> Self { 27 | Self(self.0.clone()) 28 | } 29 | } 30 | 31 | impl PartialEq for Spawned { 32 | fn eq(&self, other: &Self) -> bool { 33 | self.0 == other.0 34 | } 35 | } 36 | 37 | impl Eq for Spawned {} 38 | 39 | impl std::hash::Hash for Spawned { 40 | fn hash(&self, state: &mut H) { 41 | self.0.hash(state); 42 | } 43 | } 44 | 45 | impl PartialOrd for Spawned { 46 | fn partial_cmp(&self, other: &Self) -> Option { 47 | Some(self.cmp(other)) 48 | } 49 | } 50 | 51 | impl Ord for Spawned { 52 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 53 | self.0.cmp(&other.0) 54 | } 55 | } 56 | 57 | impl Spawned { 58 | pub async fn get(&self) -> eyre::Result<&T> { 59 | self.0 60 | .get_unpin() 61 | .await 62 | .as_ref() 63 | .map_err(|e| eyre!("Background task resulted in error: {e}")) 64 | } 65 | } 66 | 67 | impl Executor { 68 | pub fn new() -> Self { 69 | Self { 70 | inner: Parc::new(ExecutorImpl { 71 | inner: async_executor::Executor::new(), 72 | }), 73 | } 74 | } 75 | pub fn advance(&self) -> eyre::Result<()> { 76 | while self.inner.inner.try_tick() {} 77 | Ok(()) 78 | } 79 | pub fn spawn( 80 | &self, 81 | f: impl Future> + Send + 'static, 82 | ) -> Spawned { 83 | let (mut sender, receiver) = async_oneshot::oneshot(); 84 | self.inner 85 | .inner 86 | .spawn(async move { 87 | let result = f.await; 88 | if let Err(e) = &result { 89 | tracing::error!("{e:?}"); 90 | } 91 | _ = sender.send(result); 92 | }) 93 | .detach(); 94 | Spawned(Parc::new(async_once_cell::Lazy::new( 95 | async move { receiver.await.expect("dropped sender") }.boxed(), 96 | ))) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/id.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU64, Ordering::SeqCst}; 2 | 3 | #[derive(Debug, Ord, PartialOrd, Copy, Clone, PartialEq, Eq, Hash)] 4 | pub struct Id(u64); 5 | 6 | static NEXT_ID: AtomicU64 = AtomicU64::new(0); 7 | 8 | impl Id { 9 | #[allow(clippy::new_without_default)] 10 | pub fn new() -> Self { 11 | Self(NEXT_ID.fetch_add(1, SeqCst)) 12 | } 13 | } 14 | 15 | impl std::fmt::Display for Id { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | self.0.fmt(f) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/interpreter/autocomplete.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub struct CompletionCandidate { 4 | pub name: String, 5 | pub ty: Type, 6 | } 7 | 8 | impl Kast { 9 | pub fn autocomplete<'a>(&'a self, s: &'a str) -> impl Iterator { 10 | self.scopes.interpreter.inspect(|locals| { 11 | locals 12 | .iter() 13 | .filter_map(move |(name, place)| { 14 | if name.name().contains(s) { 15 | Some(CompletionCandidate { 16 | name: name.name().to_owned(), 17 | ty: place.ty.clone(), 18 | }) 19 | } else { 20 | None 21 | } 22 | }) 23 | .collect::>() 24 | .into_iter() 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! the name of the programming language is kast 2 | #![allow(clippy::type_complexity, clippy::needless_question_mark)] 3 | #![recursion_limit = "256"] 4 | 5 | use async_trait::async_trait; 6 | use eyre::{eyre, Context as _}; 7 | use futures::future::BoxFuture; 8 | use futures::prelude::*; 9 | use inference::Inferrable; 10 | pub use kast_ast as ast; 11 | pub use kast_ast::Token; 12 | pub use kast_util::*; 13 | use ordered_float::OrderedFloat; 14 | use std::collections::HashMap; 15 | use std::path::{Path, PathBuf}; 16 | use std::sync::Mutex; 17 | use try_hash::TryHash; 18 | 19 | mod cast; 20 | mod comments; 21 | mod compiler; 22 | mod contexts; 23 | mod executor; 24 | mod id; 25 | mod interpreter; 26 | mod ir; 27 | pub mod javascript; 28 | mod name; 29 | mod place; 30 | mod rusty; 31 | mod scopes; 32 | mod target; 33 | mod ty; 34 | mod value; 35 | 36 | use self::cast::*; 37 | pub use self::compiler::{Ast, AstData, Hygiene}; 38 | pub use self::contexts::Contexts; 39 | use self::executor::Executor; 40 | pub use self::id::*; 41 | pub use self::ir::Symbol; 42 | use self::ir::*; 43 | pub use self::name::*; 44 | pub use self::place::*; 45 | pub use self::rusty::*; 46 | use self::scopes::*; 47 | pub use self::ty::*; 48 | pub use self::value::*; 49 | pub use inference; 50 | pub use target::*; 51 | 52 | pub enum MaybeBorrowed<'a, T> { 53 | Borrowed(&'a T), 54 | Owned(T), 55 | } 56 | 57 | impl std::ops::Deref for MaybeBorrowed<'_, T> { 58 | type Target = T; 59 | fn deref(&self) -> &Self::Target { 60 | match self { 61 | MaybeBorrowed::Borrowed(value) => value, 62 | MaybeBorrowed::Owned(value) => value, 63 | } 64 | } 65 | } 66 | 67 | pub trait Output: 'static + Sync + Send { 68 | fn write(&self, s: &str); 69 | } 70 | 71 | pub trait Input: 'static + Sync + Send { 72 | fn read_line(&self) -> String; 73 | } 74 | 75 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 76 | enum ExecMode { 77 | Run, 78 | Import, 79 | } 80 | 81 | #[derive(Clone)] 82 | pub struct Kast { 83 | /// Am I a background task? :) 84 | syntax: Parc, 85 | pub interpreter: interpreter::State, 86 | compiler: compiler::State, 87 | spawn_id: Id, 88 | scopes: Scopes, 89 | current_name: Name, 90 | cache: Parc, 91 | pub output: std::sync::Arc, 92 | pub input: std::sync::Arc, 93 | exec_mode: ExecMode, 94 | } 95 | 96 | pub trait SubstituteBindings { 97 | type Target; 98 | fn substitute_bindings(self, kast: &Kast, cache: &mut RecurseCache) -> Self::Target; 99 | } 100 | 101 | // TODO merge into impl SubstituteBindings for MaybeNotInferred 102 | pub fn substitute_bindings_inferrable>( 103 | var: inference::MaybeNotInferred, 104 | kast: &Kast, 105 | cache: &mut RecurseCache, 106 | ) -> inference::MaybeNotInferred { 107 | let inferred = match var.inferred() { 108 | Ok(inferred) => inferred, 109 | Err(_) => { 110 | return var; 111 | } 112 | }; 113 | if let Some(result) = cache.get(var.var()) { 114 | return result; 115 | } 116 | cache.insert(var.var(), var.clone()); 117 | let result: inference::MaybeNotInferred = inferred.substitute_bindings(kast, cache).into(); 118 | cache.insert(var.var(), result.clone()); 119 | result 120 | } 121 | 122 | impl SubstituteBindings for Option { 123 | type Target = Option; 124 | fn substitute_bindings(self, kast: &Kast, cache: &mut RecurseCache) -> Self::Target { 125 | self.map(|value| value.substitute_bindings(kast, cache)) 126 | } 127 | } 128 | 129 | enum ImportMode { 130 | Normal, 131 | OnlyStdSyntax, 132 | FromScratch, 133 | } 134 | 135 | struct Cache { 136 | start: batbox_time::Instant, 137 | executor: Executor, 138 | interpreter: interpreter::Cache, 139 | compiler: compiler::Cache, 140 | imports: Mutex>>, 141 | target_symbol: Symbol, 142 | target_dependent_scopes: Scopes, 143 | } 144 | 145 | impl Default for Cache { 146 | fn default() -> Self { 147 | let target_symbol = Symbol { 148 | name: "target".into(), 149 | span: Span { 150 | start: Position::ZERO, 151 | end: Position::ZERO, 152 | filename: file!().into(), 153 | }, 154 | id: Id::new(), 155 | }; 156 | Self { 157 | start: batbox_time::Instant::now(), 158 | interpreter: interpreter::Cache::new(), 159 | compiler: compiler::Cache::new(), 160 | imports: Default::default(), 161 | executor: Executor::new(), 162 | target_dependent_scopes: { 163 | let scopes = Scopes::new( 164 | Id::new(), // TODO what id? 165 | ScopeType::NonRecursive, 166 | None, 167 | ); 168 | scopes.compiler.insert( 169 | &target_symbol.name, 170 | &target_symbol.span, 171 | ValueShape::Binding(Parc::new(Binding { 172 | symbol: target_symbol.clone(), 173 | ty: TypeShape::Target.into(), 174 | mutability: Mutability::ReadOnly, 175 | compiler_scope: scopes.compiler.clone(), 176 | })) 177 | .into(), 178 | ); 179 | scopes 180 | }, 181 | target_symbol, 182 | } 183 | } 184 | } 185 | 186 | impl Kast { 187 | fn from_scratch(path: impl AsRef, cache: Option>) -> Self { 188 | let spawn_id = Id::new(); 189 | Self { 190 | spawn_id, 191 | syntax: Parc::new(ast::Syntax::empty()), 192 | scopes: Scopes::new(spawn_id, ScopeType::NonRecursive, None), 193 | interpreter: interpreter::State::new(), 194 | compiler: compiler::State::new(), 195 | cache: cache.unwrap_or_default(), 196 | output: std::sync::Arc::new({ 197 | struct DefaultOutput; 198 | impl Output for DefaultOutput { 199 | fn write(&self, s: &str) { 200 | print!("{s}"); 201 | } 202 | } 203 | DefaultOutput 204 | }), 205 | input: std::sync::Arc::new({ 206 | struct DefaultInput; 207 | impl Input for DefaultInput { 208 | fn read_line(&self) -> String { 209 | let mut s = String::new(); 210 | std::io::stdin() 211 | .read_line(&mut s) 212 | .expect("failed to read line"); 213 | let s = s.trim_end_matches("\n").to_owned(); 214 | s 215 | } 216 | } 217 | DefaultInput 218 | }), 219 | exec_mode: ExecMode::Run, 220 | current_name: Name::new(NamePart::File(path.as_ref().to_owned())), 221 | } 222 | } 223 | fn only_std_syntax(path: impl AsRef, cache: Option>) -> eyre::Result { 224 | let mut kast = Self::from_scratch(path, cache); 225 | let syntax = kast 226 | .import_impl( 227 | std_path().join("syntax.ks"), 228 | Some("std.syntax"), 229 | ImportMode::FromScratch, 230 | ) 231 | .wrap_err("failed to import std syntax")? 232 | .into_inferred()? 233 | .into_syntax_module() 234 | .wrap_err("std/syntax.ks must evaluate to syntax")?; 235 | let mut new_syntax: ast::Syntax = (*kast.syntax).clone(); 236 | for definition in &*syntax { 237 | tracing::trace!("std syntax: {}", definition.name); 238 | kast.cache.compiler.register_syntax(definition); 239 | new_syntax 240 | .insert(definition.clone()) 241 | .wrap_err("Failed to add std syntax")?; 242 | } 243 | kast.syntax = Parc::new(new_syntax); 244 | Ok(kast) 245 | } 246 | #[allow(clippy::new_without_default)] 247 | pub fn new(path: impl AsRef) -> eyre::Result { 248 | Self::new_normal(path, None) 249 | } 250 | pub fn new_nostdlib(path: impl AsRef) -> eyre::Result { 251 | Self::only_std_syntax(path, None) 252 | } 253 | fn new_normal(path: impl AsRef, cache: Option>) -> eyre::Result { 254 | let mut kast = Self::only_std_syntax(path, cache)?; 255 | let std_path = std_path().join("lib.ks"); 256 | let std = kast 257 | .import_impl(&std_path, Some("std"), ImportMode::OnlyStdSyntax) 258 | .wrap_err("std lib import failed")?; 259 | kast.add_local( 260 | kast.new_symbol( 261 | "std", 262 | Span { 263 | start: Position::ZERO, 264 | end: Position::ZERO, 265 | filename: std_path, 266 | }, 267 | ), 268 | std, 269 | ); 270 | Ok(kast) 271 | } 272 | 273 | pub fn import(&self, path: impl AsRef) -> eyre::Result { 274 | self.import_impl(path, None, ImportMode::Normal) 275 | } 276 | 277 | // I am conviced that even stone plates offer 278 | // a better DX (developer experience) than rust + cargo 279 | pub fn include(&self, path: impl AsRef) -> eyre::Result> { 280 | let mut path = path.as_ref().to_owned(); 281 | #[cfg(not(target_arch = "wasm32"))] 282 | if !path.starts_with(std_path()) { 283 | path = path.canonicalize()?; 284 | } 285 | let path = path; 286 | let source = SourceFile { 287 | #[cfg(feature = "embed-std")] 288 | contents: { 289 | use include_dir::{include_dir, Dir}; 290 | match path.strip_prefix(std_path()) { 291 | Ok(path) => { 292 | static STD: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/std"); 293 | STD.get_file(path) 294 | .ok_or_else(|| eyre!("file not exist: {path:?}"))? 295 | .contents_utf8() 296 | .ok_or_else(|| eyre!("{path:?} is not utf8"))? 297 | .to_owned() 298 | } 299 | Err(_) => std::fs::read_to_string(&path) 300 | .wrap_err_with(|| eyre!("failed to read {path:?}"))?, 301 | } 302 | }, 303 | #[cfg(not(feature = "embed-std"))] 304 | contents: std::fs::read_to_string(&path)?, 305 | filename: path.clone(), 306 | }; 307 | Ok(ast::parse(&self.syntax, source)?.map(compiler::init_ast)) 308 | } 309 | 310 | fn import_impl( 311 | &self, 312 | path: impl AsRef, 313 | name: Option<&str>, 314 | mode: ImportMode, 315 | ) -> eyre::Result { 316 | let mut path = path.as_ref().to_owned(); 317 | #[cfg(not(target_arch = "wasm32"))] 318 | if !path.starts_with(std_path()) { 319 | path = path.canonicalize()?; 320 | } 321 | let path = path; 322 | 323 | let name = name 324 | .or(path 325 | .file_name() 326 | .and_then(|name| name.to_str()) 327 | .and_then(|s| s.strip_suffix(".ks"))) 328 | .unwrap_or("_"); 329 | 330 | tracing::trace!("importing {path:?}"); 331 | if let Some(value) = self.cache.imports.lock().unwrap().get(&path) { 332 | let value = value.clone().ok_or_else(|| eyre!("recursive imports???"))?; 333 | return Ok(value); 334 | } 335 | tracing::trace!("importing {path:?} for the first time"); 336 | self.cache 337 | .imports 338 | .lock() 339 | .unwrap() 340 | .insert(path.clone(), None); 341 | let mut kast = match mode { 342 | ImportMode::Normal => Self::new_normal(name, Some(self.cache.clone()))?, 343 | ImportMode::OnlyStdSyntax => Self::only_std_syntax(name, Some(self.cache.clone()))?, 344 | ImportMode::FromScratch => Self::from_scratch(name, Some(self.cache.clone())), 345 | }; 346 | let source = self.include(&path)?; 347 | kast.exec_mode = ExecMode::Import; 348 | let value = futures_lite::future::block_on(kast.eval_ast_opt::(&source, None))?; 349 | self.cache 350 | .imports 351 | .lock() 352 | .unwrap() 353 | .insert(path.clone(), Some(value.clone())); 354 | tracing::trace!("{path:?} has been imported"); 355 | Ok(value) 356 | } 357 | 358 | pub fn eval_str_as(&mut self, source: &str) -> eyre::Result { 359 | let value: Value = self.eval_source( 360 | SourceFile { 361 | contents: source.to_owned(), 362 | filename: "".into(), 363 | }, 364 | T::ty(), 365 | )?; 366 | Ok(T::from_value(value)?) 367 | } 368 | 369 | pub fn eval_file(&mut self, path: impl AsRef) -> eyre::Result { 370 | let source = SourceFile { 371 | contents: std::fs::read_to_string(path.as_ref())?, 372 | filename: path.as_ref().into(), 373 | }; 374 | self.eval_source(source, None) 375 | } 376 | 377 | fn spawn_clone(&self) -> Self { 378 | let mut kast = self.clone(); 379 | kast.spawn_id = Id::new(); 380 | kast 381 | } 382 | 383 | pub fn new_symbol(&self, name: impl Into, span: Span) -> Symbol { 384 | self.scopes.compiler.new_symbol(name, span) 385 | } 386 | } 387 | 388 | pub fn std_path() -> PathBuf { 389 | if cfg!(feature = "embed-std") { 390 | return "/embedded-std/".into(); 391 | } 392 | match std::env::var_os("KAST_STD") { 393 | Some(path) => path.into(), 394 | None => match option_env!("CARGO_MANIFEST_DIR") { 395 | Some(path) => Path::new(path).join("std"), 396 | None => panic!("kast standard library not found"), 397 | }, 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // the git has been figured out 2 | mod cli; 3 | mod repl_helper; 4 | 5 | use std::{ 6 | io::{IsTerminal, Read}, 7 | sync::{Arc, Mutex}, 8 | }; 9 | 10 | use kast::*; 11 | 12 | fn run_repl( 13 | helper: impl repl_helper::AnyHelper, 14 | mut handler: impl FnMut(String) -> eyre::Result<()> + 'static, 15 | ) -> eyre::Result<()> { 16 | let mut rustyline = rustyline::Editor::with_config( 17 | rustyline::Config::builder().auto_add_history(true).build(), 18 | )?; 19 | rustyline.set_helper(Some(helper)); 20 | let is_tty = std::io::stdin().is_terminal(); 21 | tracing::debug!("is tty: {is_tty:?}"); 22 | loop { 23 | let s = match is_tty { 24 | true => match rustyline.readline("> ") { 25 | Ok(line) => line, 26 | Err(rustyline::error::ReadlineError::Eof) => break, 27 | Err(e) => return Err(e.into()), 28 | }, 29 | false => { 30 | let mut contents = String::new(); 31 | std::io::stdin() 32 | .lock() 33 | .read_to_string(&mut contents) 34 | .unwrap(); 35 | contents 36 | } 37 | }; 38 | if let Err(e) = handler(s) { 39 | println!("{e:?}"); 40 | }; 41 | if !is_tty { 42 | break; 43 | } 44 | } 45 | Ok(()) 46 | } 47 | 48 | fn main() -> eyre::Result<()> { 49 | color_eyre::config::HookBuilder::new() 50 | .display_env_section(false) 51 | .display_location_section(false) 52 | .install()?; 53 | 54 | let log_level = { 55 | use tracing_subscriber::{filter, fmt, prelude::*, reload}; 56 | let (filter, reload_handle) = reload::Layer::new(filter::LevelFilter::WARN); 57 | tracing_subscriber::registry() 58 | .with(filter) 59 | .with(fmt::Layer::default()) 60 | .init(); 61 | reload_handle 62 | }; 63 | let cli_args = cli::parse(); 64 | let target_log_level = cli_args.log; 65 | let init_log_level = move || { 66 | log_level 67 | .modify(|filter| *filter = target_log_level) 68 | .unwrap(); 69 | }; 70 | 71 | match cli_args.command() { 72 | cli::Command::Run { path, no_stdlib } => { 73 | let mut kast = match no_stdlib { 74 | true => Kast::new_nostdlib(&path), 75 | false => Kast::new(&path), 76 | } 77 | .unwrap(); 78 | init_log_level(); 79 | let value = kast.eval_file(path)?; 80 | match value.clone().inferred() { 81 | Some(ValueShape::Unit) => {} 82 | _ => tracing::info!("evaluated to {value}"), 83 | } 84 | } 85 | cli::Command::ParseAst => { 86 | let syntax = ast::read_syntax(SourceFile { 87 | contents: std::fs::read_to_string(std_path().join("syntax.ks")).unwrap(), 88 | filename: "std/syntax.ks".into(), 89 | })?; 90 | tracing::trace!("{syntax:#?}"); 91 | run_repl((), move |contents| { 92 | let source = SourceFile { 93 | contents, 94 | filename: "".into(), 95 | }; 96 | let ast = ast::parse(&syntax, source)?; 97 | match ast { 98 | None => println!(""), 99 | Some(ast) => println!("{ast:#}"), 100 | } 101 | Ok(()) 102 | })?; 103 | } 104 | cli::Command::Repl { 105 | path, 106 | no_stdlib, 107 | prerun, 108 | } => { 109 | let kast = Arc::new(Mutex::new( 110 | match no_stdlib { 111 | false => Kast::new(""), 112 | true => Kast::new_nostdlib(""), 113 | } 114 | .unwrap(), 115 | )); 116 | init_log_level(); 117 | if let Some(path) = path { 118 | let name = path.file_stem().unwrap().to_str().unwrap(); 119 | let mut kast = kast.lock().unwrap(); 120 | let value = kast.eval_file(&path).expect("Failed to eval file"); 121 | let symbol = kast.new_symbol( 122 | name, 123 | Span { 124 | start: Position::ZERO, 125 | end: Position::ZERO, 126 | filename: path.clone(), 127 | }, 128 | ); 129 | kast.add_local(symbol, value); 130 | } 131 | { 132 | let prerun = 133 | prerun.unwrap_or("use std.*; with default_number_type_based_on_dot;".into()); 134 | kast.lock() 135 | .unwrap() 136 | .eval_source::( 137 | SourceFile { 138 | contents: prerun, 139 | filename: "".into(), 140 | }, 141 | Some(TypeShape::Unit.into()), 142 | ) 143 | .expect("Failed to eval prerun"); 144 | } 145 | let helper = repl_helper::Helper::new(kast.clone()); 146 | run_repl(helper, move |contents| { 147 | let snapshot = kast::inference::global_state::snapshot(); 148 | let source = SourceFile { 149 | contents, 150 | filename: "".into(), 151 | }; 152 | let place_ref: PlaceRef = match kast.lock().unwrap().eval_source(source, None) { 153 | Ok(value) => value, 154 | Err(e) => { 155 | kast::inference::global_state::revert(snapshot); 156 | return Err(e); 157 | } 158 | }; 159 | let place = place_ref.read()?; 160 | let ty = place.ty().unwrap_or_else(|| place_ref.place.ty.clone()); 161 | match ty.inferred() { 162 | Ok(TypeShape::Unit) => {} 163 | _ => println!("{:#} :: {:#}", *place, ty), 164 | } 165 | Ok(()) 166 | })?; 167 | } 168 | } 169 | 170 | Ok(()) 171 | } 172 | -------------------------------------------------------------------------------- /src/name.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Clone, TryHash, PartialEq, Eq, PartialOrd, Ord)] 4 | pub enum NamePart { 5 | File(PathBuf), 6 | Str(String), 7 | Symbol(Symbol), 8 | Instantiate(#[try_hash] Value), 9 | ImplCast { 10 | #[try_hash] 11 | value: Value, 12 | #[try_hash] 13 | target: Value, 14 | }, 15 | } 16 | 17 | impl SubstituteBindings for NamePart { 18 | type Target = Self; 19 | fn substitute_bindings(self, kast: &Kast, cache: &mut RecurseCache) -> Self { 20 | match self { 21 | NamePart::File(_) | NamePart::Str(_) | NamePart::Symbol(_) => self, 22 | NamePart::Instantiate(value) => { 23 | NamePart::Instantiate(value.substitute_bindings(kast, cache)) 24 | } 25 | NamePart::ImplCast { value, target } => NamePart::ImplCast { 26 | value: value.substitute_bindings(kast, cache), 27 | target: target.substitute_bindings(kast, cache), 28 | }, 29 | } 30 | } 31 | } 32 | 33 | impl Inferrable for NamePart { 34 | fn make_same(a: Self, b: Self) -> eyre::Result { 35 | use NamePart::*; 36 | macro_rules! fail { 37 | () => { 38 | eyre::bail!("expected name part {a}, got {b}") 39 | }; 40 | } 41 | Ok(match (a.clone(), b.clone()) { 42 | (File(a), File(b)) if a == b => File(a), 43 | (File(_), _) => fail!(), 44 | (Str(a), Str(b)) if a == b => Str(a), 45 | (Str(_), _) => fail!(), 46 | (Symbol(a), Symbol(b)) if a == b => Symbol(a), 47 | (Symbol(_), _) => fail!(), 48 | (Instantiate(a), Instantiate(b)) => Instantiate(Inferrable::make_same(a, b)?), 49 | (Instantiate { .. }, _) => fail!(), 50 | ( 51 | ImplCast { 52 | value: a_value, 53 | target: a_target, 54 | }, 55 | ImplCast { 56 | value: b_value, 57 | target: b_target, 58 | }, 59 | ) => ImplCast { 60 | value: Inferrable::make_same(a_value, b_value)?, 61 | target: Inferrable::make_same(a_target, b_target)?, 62 | }, 63 | (ImplCast { .. }, _) => fail!(), 64 | }) 65 | } 66 | } 67 | 68 | #[derive(Clone, TryHash, PartialEq, Eq, PartialOrd, Ord)] 69 | pub struct Name(#[try_hash] std::sync::Arc>); 70 | 71 | impl Name { 72 | pub fn short(&self) -> impl std::fmt::Display + '_ { 73 | self.0 74 | .iter() 75 | .rfind(|part| match part { 76 | NamePart::Instantiate(_) => false, 77 | NamePart::ImplCast { .. } => true, 78 | NamePart::File(_) => true, 79 | NamePart::Str(_) => true, 80 | NamePart::Symbol(_) => true, 81 | }) 82 | .unwrap() 83 | } 84 | } 85 | 86 | impl Inferrable for Name { 87 | fn make_same(a: Self, b: Self) -> eyre::Result { 88 | let error_context = format!("name {a} != {b}"); 89 | let parts = |this: Self| -> Vec { (*this.0).clone() }; 90 | Ok(Self(std::sync::Arc::new( 91 | Inferrable::make_same(parts(a), parts(b)).wrap_err(error_context)?, 92 | ))) 93 | } 94 | } 95 | 96 | impl SubstituteBindings for Name { 97 | type Target = Self; 98 | fn substitute_bindings(self, kast: &Kast, cache: &mut RecurseCache) -> Self { 99 | let substituted = self 100 | .0 101 | .iter() 102 | .cloned() 103 | .map(|part| part.substitute_bindings(kast, cache)) 104 | .collect(); 105 | Self(std::sync::Arc::new(substituted)) 106 | } 107 | } 108 | 109 | impl Name { 110 | pub fn new(only_part: NamePart) -> Self { 111 | Self(std::sync::Arc::new(vec![only_part])) 112 | } 113 | pub fn append(&self, part: NamePart) -> Self { 114 | let mut parts: Vec = (*self.0).clone(); 115 | parts.push(part); 116 | Self(parts.into()) 117 | } 118 | } 119 | 120 | impl std::fmt::Debug for Name { 121 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 122 | std::fmt::Display::fmt(&self, f) 123 | } 124 | } 125 | 126 | impl std::fmt::Display for NamePart { 127 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 128 | match self { 129 | NamePart::File(path_buf) => { 130 | if let Some(name) = path_buf.file_name().and_then(std::ffi::OsStr::to_str) { 131 | write!(f, "{name}")?; 132 | } else { 133 | write!(f, "_")?; 134 | } 135 | } 136 | NamePart::Str(s) => write!(f, "{s}")?, 137 | NamePart::Symbol(symbol) => write!(f, "{symbol}")?, 138 | NamePart::Instantiate(value) => write!(f, "[{value}]")?, 139 | NamePart::ImplCast { value, target } => { 140 | write!(f, "(impl {value} as {target})")?; 141 | } 142 | } 143 | Ok(()) 144 | } 145 | } 146 | 147 | impl std::fmt::Display for Name { 148 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 149 | let mut first = true; 150 | for part in self.0.iter() { 151 | if !first && !matches!(part, NamePart::Instantiate { .. }) { 152 | write!(f, ".")?; 153 | } 154 | first = false; 155 | write!(f, "{part}")?; 156 | } 157 | if first { 158 | write!(f, "")?; 159 | } 160 | Ok(()) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/place.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | // TODO impl Drop 4 | pub struct OwnedPlace(Parc); 5 | 6 | impl std::ops::Deref for OwnedPlace { 7 | type Target = Place; 8 | fn deref(&self) -> &Self::Target { 9 | &self.0 10 | } 11 | } 12 | 13 | impl PartialEq for OwnedPlace { 14 | fn eq(&self, other: &Self) -> bool { 15 | *self.0.read().expect("can't read for eq") == *other.0.read().expect("can't read for eq") 16 | } 17 | } 18 | 19 | impl Eq for OwnedPlace {} 20 | 21 | impl PartialOrd for OwnedPlace { 22 | fn partial_cmp(&self, other: &Self) -> Option { 23 | (*self.0.read().unwrap()).partial_cmp(&*other.0.read().unwrap()) 24 | } 25 | } 26 | 27 | impl Ord for OwnedPlace { 28 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 29 | (*self.0.read().unwrap()).cmp(&*other.0.read().unwrap()) 30 | } 31 | } 32 | 33 | impl TryHash for OwnedPlace { 34 | type Error = ::Error; 35 | fn try_hash(&self, state: &mut impl std::hash::Hasher) -> Result<(), Self::Error> { 36 | self.0 37 | .read() 38 | .expect("can't read for hashing") 39 | .try_hash(state) 40 | } 41 | } 42 | 43 | impl Clone for OwnedPlace { 44 | fn clone(&self) -> Self { 45 | Self(Parc::new(Place::new( 46 | self.0.read().expect("can't read for clone").clone(), 47 | self.0.ty.clone(), 48 | self.0.mutability, // TODO is this correct? 49 | ))) 50 | } 51 | } 52 | 53 | impl std::fmt::Display for OwnedPlace { 54 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 55 | self.0.fmt(f) 56 | } 57 | } 58 | 59 | impl OwnedPlace { 60 | pub fn new(value: Value, mutability: Mutability) -> Self { 61 | let ty = value.ty(); 62 | Self::new_impl(PlaceState::Occupied(value), ty, mutability) 63 | } 64 | pub fn new_temp(value: Value) -> Self { 65 | Self::new(value, Mutability::Mutable) 66 | } 67 | fn new_impl(state: PlaceState, ty: Type, mutability: Mutability) -> Self { 68 | Self(Parc::new(Place::new(state, ty, mutability))) 69 | } 70 | pub fn new_uninitialized(ty: Type, mutability: Mutability) -> Self { 71 | Self::new_impl(PlaceState::Unintialized, ty, mutability) 72 | } 73 | pub fn into_value(self) -> eyre::Result { 74 | self.0.take_value() 75 | } 76 | pub fn get_ref(&self) -> PlaceRef { 77 | PlaceRef { 78 | place: self.0.clone(), 79 | } 80 | } 81 | } 82 | 83 | #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 84 | pub struct PlaceRef { 85 | pub place: Parc, 86 | } 87 | 88 | impl PlaceRef { 89 | pub fn new_temp(value: Value) -> Self { 90 | let ty = value.ty(); 91 | Self { 92 | place: Parc::new(Place::new( 93 | PlaceState::Occupied(value), 94 | ty, 95 | Mutability::Mutable, 96 | )), 97 | } 98 | } 99 | } 100 | 101 | #[derive(thiserror::Error, Debug)] 102 | pub enum PlaceError { 103 | #[error("place is uninitialized")] 104 | Unintialized, 105 | #[error("place has been moved out")] 106 | MovedOut, 107 | } 108 | 109 | // nothing is going to work 110 | impl PlaceRef { 111 | pub fn read(&self) -> eyre::Result> { 112 | self.place.read() 113 | } 114 | pub fn write(&self) -> eyre::Result> { 115 | self.place.write() 116 | } 117 | pub fn read_value(&self) -> eyre::Result> { 118 | self.place.read_value() 119 | } 120 | pub fn write_value(&self) -> eyre::Result> { 121 | self.place.write_value() 122 | } 123 | pub fn claim_value(&self, kast: &Kast) -> eyre::Result { 124 | { 125 | let value = self.read_value()?; 126 | if let ValueImpl::Inferrable(_) = value.r#impl { 127 | return Ok(value.clone()); 128 | } 129 | } 130 | Ok(if kast.is_copy(&self.place.ty)? { 131 | self.place.clone_value()? 132 | } else { 133 | self.place.take_value()? 134 | }) 135 | } 136 | pub fn clone_value(&self) -> eyre::Result { 137 | self.place.clone_value() 138 | } 139 | pub fn assign(&self, new_value: Value) -> eyre::Result<()> { 140 | self.place.assign(new_value) 141 | } 142 | } 143 | 144 | impl std::fmt::Display for PlaceRef { 145 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 146 | write!(f, "&{}", self.place.id) 147 | } 148 | } 149 | 150 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 151 | pub enum Mutability { 152 | ReadOnly, 153 | Mutable, 154 | Nested, 155 | } 156 | 157 | impl Mutability { 158 | pub fn check_can_read(&self) -> eyre::Result<()> { 159 | match self { 160 | Self::Nested => Ok(()), 161 | Self::ReadOnly | Self::Mutable => Ok(()), 162 | } 163 | } 164 | pub fn check_can_mutate(&self) -> eyre::Result<()> { 165 | match self { 166 | Self::Nested => Ok(()), 167 | Self::ReadOnly => Err(eyre!("place is readonly")), 168 | Self::Mutable => Ok(()), 169 | } 170 | } 171 | } 172 | 173 | pub struct Place { 174 | pub id: Id, 175 | pub ty: Type, 176 | pub mutability: Mutability, 177 | state: tokio::sync::RwLock, 178 | } 179 | 180 | pub struct PlaceReadGuard<'a, T>(tokio::sync::RwLockReadGuard<'a, T>); 181 | 182 | impl std::ops::Deref for PlaceReadGuard<'_, T> { 183 | type Target = T; 184 | fn deref(&self) -> &Self::Target { 185 | &self.0 186 | } 187 | } 188 | 189 | pub struct PlaceWriteGuard<'a, T>(tokio::sync::RwLockMappedWriteGuard<'a, T>); 190 | 191 | impl std::ops::Deref for PlaceWriteGuard<'_, T> { 192 | type Target = T; 193 | fn deref(&self) -> &Self::Target { 194 | &self.0 195 | } 196 | } 197 | 198 | impl std::ops::DerefMut for PlaceWriteGuard<'_, T> { 199 | fn deref_mut(&mut self) -> &mut Self::Target { 200 | &mut self.0 201 | } 202 | } 203 | 204 | // this wouldnt be so much code if i would code it in assembly 205 | impl Place { 206 | pub fn new(state: PlaceState, ty: Type, mutability: Mutability) -> Self { 207 | Self { 208 | id: Id::new(), 209 | mutability, 210 | ty, 211 | state: tokio::sync::RwLock::new(state), 212 | } 213 | } 214 | fn read(&self) -> eyre::Result> { 215 | Ok(PlaceReadGuard(tokio::sync::RwLockReadGuard::map( 216 | self.state 217 | .try_read() 218 | .map_err(|_| eyre!("already borrowed mutably"))?, 219 | |x| x, 220 | ))) 221 | } 222 | fn write(&self) -> eyre::Result> { 223 | Ok(PlaceWriteGuard(tokio::sync::RwLockWriteGuard::map( 224 | self.state 225 | .try_write() 226 | .map_err(|_| eyre!("already borrowed"))?, 227 | |x| x, 228 | ))) 229 | } 230 | pub fn read_value(&self) -> eyre::Result> { 231 | self.mutability.check_can_read()?; 232 | let guard = self.read()?; 233 | match tokio::sync::RwLockReadGuard::try_map(guard.0, |state| state.get().ok()) { 234 | Ok(result) => Ok(PlaceReadGuard(result)), 235 | Err(guard) => Err(guard.get().unwrap_err().into()), 236 | } 237 | } 238 | pub fn write_value(&self) -> eyre::Result> { 239 | self.mutability.check_can_mutate()?; 240 | let guard = self.write()?; 241 | match tokio::sync::RwLockMappedWriteGuard::try_map(guard.0, |state| state.get_mut().ok()) { 242 | Ok(result) => Ok(PlaceWriteGuard(result)), 243 | Err(guard) => Err(guard.get().unwrap_err().into()), 244 | } 245 | } 246 | pub fn take_value(&self) -> eyre::Result { 247 | self.mutability.check_can_read()?; 248 | Ok(self.write()?.take()?.clone()) 249 | } 250 | pub fn clone_value(&self) -> eyre::Result { 251 | self.mutability.check_can_read()?; 252 | Ok(self.read()?.get()?.clone()) 253 | } 254 | pub fn assign(&self, new_value: Value) -> eyre::Result<()> { 255 | let mut state = self.write()?; 256 | match *state { 257 | PlaceState::Unintialized => {} 258 | PlaceState::Occupied(_) | PlaceState::MovedOut => self.mutability.check_can_mutate()?, 259 | } 260 | state.assign(new_value); 261 | Ok(()) 262 | } 263 | } 264 | 265 | impl std::fmt::Display for Place { 266 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 267 | match &*self.read().expect("Failed to read for Display") { 268 | PlaceState::Unintialized => write!(f, ""), 269 | PlaceState::Occupied(value) => value.fmt(f), 270 | PlaceState::MovedOut => write!(f, ""), 271 | } 272 | } 273 | } 274 | 275 | #[derive(Clone, PartialEq, TryHash, Eq, PartialOrd, Ord)] 276 | pub enum PlaceState { 277 | Unintialized, 278 | // This place is taken 279 | Occupied(#[try_hash] Value), 280 | MovedOut, 281 | } 282 | 283 | impl std::fmt::Display for PlaceState { 284 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 285 | match self { 286 | PlaceState::Unintialized => write!(f, ""), 287 | PlaceState::Occupied(value) => value.fmt(f), 288 | PlaceState::MovedOut => write!(f, ""), 289 | } 290 | } 291 | } 292 | 293 | impl PlaceState { 294 | pub fn ty(&self) -> Option { 295 | match self { 296 | PlaceState::Occupied(value) => Some(value.ty()), 297 | PlaceState::Unintialized | PlaceState::MovedOut => None, 298 | } 299 | } 300 | pub fn assign(&mut self, new_value: Value) { 301 | *self = Self::Occupied(new_value); 302 | } 303 | pub fn get(&self) -> Result<&Value, PlaceError> { 304 | match self { 305 | PlaceState::Unintialized => Err(PlaceError::Unintialized), 306 | PlaceState::Occupied(value) => Ok(value), 307 | PlaceState::MovedOut => Err(PlaceError::MovedOut), 308 | } 309 | } 310 | pub fn get_mut(&mut self) -> eyre::Result<&mut ValueShape> { 311 | match self { 312 | PlaceState::Unintialized => Err(PlaceError::Unintialized)?, 313 | PlaceState::Occupied(value) => Ok(value.mutate()?), 314 | PlaceState::MovedOut => Err(PlaceError::MovedOut)?, 315 | } 316 | } 317 | pub fn take(&mut self) -> Result { 318 | match self { 319 | PlaceState::Unintialized => Err(PlaceError::Unintialized), 320 | PlaceState::MovedOut => Err(PlaceError::MovedOut), 321 | PlaceState::Occupied(_) => { 322 | let PlaceState::Occupied(value) = std::mem::replace(self, PlaceState::MovedOut) 323 | else { 324 | unreachable!() 325 | }; 326 | Ok(value) 327 | } 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/repl_helper.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use colored::Colorize; 3 | 4 | pub struct Helper(Arc>); 5 | 6 | pub trait AnyHelper: rustyline::Helper {} 7 | 8 | impl AnyHelper for () {} 9 | impl AnyHelper for Helper {} 10 | 11 | pub struct CompletionCandidate { 12 | display: String, 13 | replacement: String, 14 | } 15 | 16 | impl rustyline::completion::Candidate for CompletionCandidate { 17 | fn display(&self) -> &str { 18 | &self.display 19 | } 20 | fn replacement(&self) -> &str { 21 | &self.replacement 22 | } 23 | } 24 | 25 | impl rustyline::completion::Completer for Helper { 26 | type Candidate = CompletionCandidate; 27 | fn complete( 28 | &self, 29 | line: &str, 30 | pos: usize, 31 | _ctx: &rustyline::Context<'_>, 32 | ) -> rustyline::Result<(usize, Vec)> { 33 | let start = line[..pos] 34 | .rfind(|c| kast_ast::is_punctuation(c) || c.is_whitespace()) 35 | .map(|i| i + 1) 36 | .unwrap_or(0); 37 | let part = &line[start..pos]; 38 | let kast = self.0.lock().unwrap(); 39 | let completions = kast 40 | .autocomplete(part) 41 | .map(|candidate| CompletionCandidate { 42 | display: format!("{} :: {}", candidate.name, candidate.ty), 43 | replacement: candidate.name, 44 | }) 45 | .collect(); 46 | Ok((start, completions)) 47 | } 48 | } 49 | 50 | impl rustyline::hint::Hinter for Helper { 51 | type Hint = String; 52 | } 53 | 54 | impl rustyline::highlight::Highlighter for Helper { 55 | fn highlight<'l>(&self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> { 56 | match kast_ast::lex(SourceFile { 57 | contents: line.to_owned(), 58 | filename: "".into(), 59 | }) { 60 | Ok(mut reader) => { 61 | let mut result = String::new(); 62 | let mut line = line.chars(); 63 | let mut current_position = 0; 64 | while let Some(token) = reader.next() { 65 | while current_position < token.span.start.index { 66 | current_position += 1; 67 | result.push(line.next().unwrap()); 68 | } 69 | let colored_token = match token.token { 70 | Token::Ident { raw, .. } => raw.underline(), 71 | Token::Punctuation { raw, .. } => raw.normal(), 72 | Token::String(kast_ast::StringToken { raw, .. }) => raw.green(), 73 | Token::Number { raw, .. } => raw.italic(), 74 | Token::Comment { raw, .. } => raw.dimmed(), 75 | Token::Eof => break, 76 | }; 77 | result += &colored_token.to_string(); 78 | while current_position < token.span.end.index { 79 | current_position += 1; 80 | line.next().unwrap(); 81 | } 82 | } 83 | result.into() 84 | } 85 | Err(_e) => line.red().to_string().into(), 86 | } 87 | } 88 | 89 | fn highlight_prompt<'b, 's: 'b, 'p: 'b>( 90 | &'s self, 91 | prompt: &'p str, 92 | _default: bool, 93 | ) -> std::borrow::Cow<'b, str> { 94 | prompt.bold().dimmed().to_string().into() 95 | } 96 | 97 | fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> { 98 | hint.dimmed().to_string().into() 99 | } 100 | 101 | fn highlight_candidate<'c>( 102 | &self, 103 | candidate: &'c str, 104 | completion: rustyline::CompletionType, 105 | ) -> std::borrow::Cow<'c, str> { 106 | let _ = completion; 107 | std::borrow::Cow::Borrowed(candidate) 108 | } 109 | 110 | fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { 111 | let _ = (line, pos, forced); 112 | true 113 | } 114 | } 115 | 116 | impl rustyline::validate::Validator for Helper {} 117 | 118 | impl rustyline::Helper for Helper {} 119 | 120 | impl Helper { 121 | pub fn new(kast: Arc>) -> Self { 122 | Self(kast) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/rusty.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub trait Rusty: Sized { 4 | fn ty() -> Option; 5 | fn from_value(value: Value) -> eyre::Result; 6 | fn into_value(self) -> Value; 7 | } 8 | 9 | impl Rusty for () { 10 | fn ty() -> Option { 11 | Some(TypeShape::Unit.into()) 12 | } 13 | fn from_value(value: Value) -> eyre::Result { 14 | Ok(value.into_inferred()?.into_unit()?) 15 | } 16 | fn into_value(self) -> Value { 17 | ValueShape::Unit.into() 18 | } 19 | } 20 | 21 | impl Rusty for bool { 22 | fn ty() -> Option { 23 | Some(TypeShape::Bool.into()) 24 | } 25 | fn from_value(value: Value) -> eyre::Result { 26 | Ok(value.into_inferred()?.into_bool()?) 27 | } 28 | fn into_value(self) -> Value { 29 | ValueShape::Bool(self).into() 30 | } 31 | } 32 | 33 | impl Rusty for i32 { 34 | fn ty() -> Option { 35 | Some(TypeShape::Int32.into()) 36 | } 37 | fn from_value(value: Value) -> eyre::Result { 38 | Ok(value.into_inferred()?.into_int32()?) 39 | } 40 | fn into_value(self) -> Value { 41 | ValueShape::Int32(self).into() 42 | } 43 | } 44 | 45 | impl Rusty for i64 { 46 | fn ty() -> Option { 47 | Some(TypeShape::Int64.into()) 48 | } 49 | fn from_value(value: Value) -> eyre::Result { 50 | Ok(value.into_inferred()?.into_int64()?) 51 | } 52 | fn into_value(self) -> Value { 53 | ValueShape::Int64(self).into() 54 | } 55 | } 56 | 57 | impl Rusty for f64 { 58 | fn ty() -> Option { 59 | Some(TypeShape::Float64.into()) 60 | } 61 | fn from_value(value: Value) -> eyre::Result { 62 | Ok(value.into_inferred()?.into_float64()?.into()) 63 | } 64 | fn into_value(self) -> Value { 65 | ValueShape::Float64(OrderedFloat(self)).into() 66 | } 67 | } 68 | 69 | impl Rusty for OrderedFloat { 70 | fn ty() -> Option { 71 | Some(TypeShape::Float64.into()) 72 | } 73 | fn from_value(value: Value) -> eyre::Result { 74 | Ok(value.into_inferred()?.into_float64()?) 75 | } 76 | fn into_value(self) -> Value { 77 | ValueShape::Float64(self).into() 78 | } 79 | } 80 | 81 | impl Rusty for char { 82 | fn ty() -> Option { 83 | Some(TypeShape::Char.into()) 84 | } 85 | fn from_value(value: Value) -> eyre::Result { 86 | Ok(value.into_inferred()?.into_char()?) 87 | } 88 | fn into_value(self) -> Value { 89 | ValueShape::Char(self).into() 90 | } 91 | } 92 | 93 | impl Rusty for String { 94 | fn ty() -> Option { 95 | Some(TypeShape::String.into()) 96 | } 97 | fn from_value(value: Value) -> eyre::Result { 98 | Ok(value.into_inferred()?.into_string()?) 99 | } 100 | fn into_value(self) -> Value { 101 | ValueShape::String(self).into() 102 | } 103 | } 104 | 105 | impl Rusty for Vec { 106 | fn ty() -> Option { 107 | Some(TypeShape::List(T::ty().unwrap_or_else(|| Type::new_not_inferred("Vec<_>"))).into()) 108 | } 109 | fn from_value(value: Value) -> eyre::Result { 110 | Ok(value 111 | .into_inferred()? 112 | .into_list()? 113 | .values 114 | .into_iter() 115 | .map(|place| place.into_value().and_then(T::from_value)) 116 | .collect::>()?) 117 | } 118 | fn into_value(self) -> Value { 119 | ValueShape::List(ListValue { 120 | values: self 121 | .into_iter() 122 | .map(T::into_value) 123 | .map(|value| OwnedPlace::new(value, Mutability::Nested)) 124 | .collect(), 125 | element_ty: T::ty().unwrap_or_else(|| Type::new_not_inferred("Vec<_>")), 126 | }) 127 | .into() 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/scopes/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, atomic::AtomicUsize}; 2 | 3 | use super::*; 4 | 5 | mod scope; 6 | 7 | use scope::*; 8 | pub use scope::{Locals, ScopeType}; 9 | 10 | #[derive(Clone, PartialEq, Eq, Hash)] 11 | pub struct InterpreterScope(Parc); 12 | 13 | impl InterpreterScope { 14 | pub fn insert(&self, symbol: &Symbol, value: Value, mutability: Mutability) { 15 | self.0.insert(symbol.clone(), value, mutability); 16 | } 17 | pub fn insert_uninitialized(&self, symbol: &Symbol, ty: Type, mutability: Mutability) { 18 | self.0.insert_uninitialized(symbol.clone(), ty, mutability); 19 | } 20 | /// TODO dont expose Locals? 21 | pub fn inspect(&self, f: impl FnOnce(&Locals) -> R) -> R { 22 | self.0.inspect(f) 23 | } 24 | pub fn syntax_definitions(&self) -> &Mutex>> { 25 | &self.0.syntax_definitions 26 | } 27 | pub fn get(&self, symbol: &Symbol) -> Option { 28 | let (_symbol, value) = self 29 | .0 30 | .lookup(Lookup::Id(symbol.id()), None) 31 | .now_or_never() 32 | .unwrap()?; 33 | Some(value) 34 | } 35 | } 36 | 37 | #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 38 | pub struct CompilerScope(Parc); 39 | 40 | impl CompilerScope { 41 | pub fn id(&self) -> Id { 42 | self.0.id 43 | } 44 | pub fn parent(&self) -> Option { 45 | self.0.parent.clone().map(Self) 46 | } 47 | pub fn insert(&self, name: &str, span: &Span, value: Value) { 48 | tracing::trace!("inserting {name:?} into {:?}", self.0.id); 49 | self.0.insert( 50 | self.new_symbol(name, span.clone()), 51 | value, 52 | Mutability::Nested, // TODO whats the mutability? 53 | ); 54 | } 55 | pub async fn lookup(&self, name: &str, hygiene: Hygiene, spawn_id: Id) -> Option { 56 | tracing::trace!("lookup {name:?} in {:?}", self.0.id); 57 | match hygiene { 58 | Hygiene::DefSite => { 59 | let (_symbol, value) = self.0.lookup(Lookup::Name(name), Some(spawn_id)).await?; 60 | Some(value.clone_value().unwrap()) 61 | } 62 | } 63 | } 64 | pub fn new_symbol(&self, name: impl Into, span: Span) -> Symbol { 65 | Symbol { 66 | name: name.into().into(), 67 | span, 68 | id: Id::new(), 69 | } 70 | } 71 | } 72 | 73 | pub struct Scopes { 74 | pub id: Id, 75 | /// we get values for symbols by id 76 | pub interpreter: InterpreterScope, 77 | /// we get values by names 78 | pub compiler: CompilerScope, 79 | refcount: Arc, 80 | is_weak: bool, 81 | } 82 | 83 | impl Clone for Scopes { 84 | fn clone(&self) -> Self { 85 | self.clone_impl(false) 86 | } 87 | } 88 | 89 | impl Scopes { 90 | fn clone_impl(&self, is_weak: bool) -> Self { 91 | let is_weak = self.is_weak || is_weak; 92 | if !is_weak { 93 | self.refcount 94 | .fetch_add(1, std::sync::atomic::Ordering::SeqCst); 95 | } 96 | Self { 97 | id: self.id, 98 | interpreter: self.interpreter.clone(), 99 | compiler: self.compiler.clone(), 100 | is_weak, 101 | refcount: self.refcount.clone(), 102 | } 103 | } 104 | pub fn new(spawn_id: Id, ty: ScopeType, parent: Option) -> Self { 105 | let (iparent, cparent) = match parent { 106 | Some(parent) => ( 107 | Some(parent.interpreter.0.clone()), 108 | Some(parent.compiler.0.clone()), 109 | ), 110 | None => (None, None), 111 | }; 112 | Self { 113 | id: Id::new(), 114 | interpreter: InterpreterScope(Parc::new(Scope::new(spawn_id, ty, iparent))), 115 | compiler: CompilerScope(Parc::new(Scope::new(spawn_id, ty, cparent))), 116 | is_weak: false, 117 | refcount: Arc::new(AtomicUsize::new(1)), 118 | } 119 | } 120 | pub fn enter_def_site(&self, def_site: CompilerScope) -> Self { 121 | tracing::trace!("entering def site: {:?}", def_site.0.id); 122 | Self { 123 | id: Id::new(), 124 | interpreter: self.interpreter.clone(), 125 | compiler: def_site, 126 | // compiler: CompilerScope(Parc::new(Scope::new(self.interpreter.0.ty, Some(def_site.0)))), 127 | is_weak: true, 128 | refcount: self.refcount.clone(), 129 | } 130 | } 131 | pub fn weak_ref(&self) -> Self { 132 | self.clone_impl(true) 133 | } 134 | } 135 | 136 | impl Drop for Scopes { 137 | fn drop(&mut self) { 138 | if !self.is_weak 139 | && self 140 | .refcount 141 | .fetch_sub(1, std::sync::atomic::Ordering::SeqCst) 142 | == 1 143 | { 144 | self.interpreter.0.close(); 145 | self.compiler.0.close(); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/scopes/scope.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicBool; 2 | 3 | use super::*; 4 | 5 | #[derive(Default)] 6 | pub struct Locals { 7 | // TODO insertion order 8 | id_by_name: HashMap, 9 | by_id: HashMap, 10 | } 11 | 12 | impl Locals { 13 | fn new() -> Self { 14 | Self::default() 15 | } 16 | fn insert_place(&mut self, name: Symbol, place: OwnedPlace) { 17 | self.id_by_name.insert(name.name().to_owned(), name.id()); 18 | self.by_id.insert(name.id(), (name, place)); 19 | } 20 | fn insert(&mut self, name: Symbol, value: Value, mutability: Mutability) { 21 | self.insert_place(name, OwnedPlace::new(value, mutability)); 22 | } 23 | fn insert_uninitialized(&mut self, name: Symbol, ty: Type, mutability: Mutability) { 24 | self.insert_place(name, OwnedPlace::new_uninitialized(ty, mutability)); 25 | } 26 | #[allow(dead_code)] 27 | fn get(&self, lookup: Lookup<'_>) -> Option<&(Symbol, OwnedPlace)> { 28 | let id: Id = match lookup { 29 | Lookup::Name(name) => *self.id_by_name.get(name)?, 30 | Lookup::Id(id) => id, 31 | }; 32 | self.by_id.get(&id) 33 | } 34 | pub fn iter(&self) -> impl Iterator + '_ { 35 | self.by_id.values() 36 | } 37 | } 38 | 39 | #[derive(Debug, Copy, Clone)] 40 | pub enum ScopeType { 41 | NonRecursive, 42 | Recursive, 43 | } 44 | 45 | pub struct Scope { 46 | pub id: Id, 47 | pub spawn_id: Id, 48 | pub parent: Option>, 49 | pub ty: ScopeType, 50 | closed: AtomicBool, 51 | closed_event: event_listener::Event, 52 | pub syntax_definitions: Mutex>>, 53 | locals: Mutex, 54 | } 55 | 56 | impl Drop for Scope { 57 | fn drop(&mut self) { 58 | if !*self.closed.get_mut() { 59 | match self.ty { 60 | ScopeType::Recursive => { 61 | panic!("recursive scope should be closed manually to advance executor") 62 | } 63 | ScopeType::NonRecursive => {} 64 | } 65 | self.close(); 66 | } 67 | } 68 | } 69 | 70 | #[derive(Copy, Clone)] 71 | pub enum Lookup<'a> { 72 | Name(&'a str), 73 | Id(Id), 74 | } 75 | 76 | impl std::fmt::Display for Lookup<'_> { 77 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 78 | match self { 79 | Lookup::Name(name) => write!(f, "{name:?}"), 80 | Lookup::Id(id) => write!(f, "id#{id}"), 81 | } 82 | } 83 | } 84 | 85 | impl Scope { 86 | pub fn new(spawn_id: Id, ty: ScopeType, parent: Option>) -> Self { 87 | let id = Id::new(); 88 | tracing::trace!("new scope {id:?} (ty={ty:?})"); 89 | Self { 90 | id, 91 | spawn_id, 92 | parent, 93 | ty, 94 | closed: AtomicBool::new(false), 95 | closed_event: event_listener::Event::new(), 96 | syntax_definitions: Default::default(), 97 | locals: Mutex::new(Locals::new()), 98 | } 99 | } 100 | pub fn close(&self) { 101 | tracing::trace!("close scope {:?}", self.id); 102 | self.closed.store(true, std::sync::atomic::Ordering::SeqCst); 103 | self.closed_event.notify(usize::MAX); 104 | } 105 | pub fn inspect(&self, f: impl FnOnce(&Locals) -> R) -> R { 106 | f(&self.locals.lock().unwrap()) 107 | } 108 | pub fn insert(&self, name: Symbol, value: Value, mutability: Mutability) { 109 | tracing::trace!("insert into {:?} {:?} = {:?}", self.id, name, value); 110 | self.locals.lock().unwrap().insert(name, value, mutability); 111 | } 112 | pub fn insert_uninitialized(&self, name: Symbol, ty: Type, mutability: Mutability) { 113 | tracing::trace!( 114 | "insert into {:?} {:?} = ", 115 | self.id, 116 | name, 117 | ); 118 | self.locals 119 | .lock() 120 | .unwrap() 121 | .insert_uninitialized(name, ty, mutability); 122 | } 123 | pub fn lookup<'a>( 124 | &'a self, 125 | lookup: Lookup<'a>, 126 | spawn_id: Option, 127 | ) -> BoxFuture<'a, Option<(Symbol, PlaceRef)>> { 128 | tracing::trace!("looking for {lookup} in {:?} (ty={:?})", self.id, self.ty); 129 | async move { 130 | loop { 131 | let was_closed = self.closed.load(std::sync::atomic::Ordering::Relaxed); 132 | if let Some((symbol, place)) = self.locals.lock().unwrap().get(lookup) { 133 | tracing::trace!("found {lookup}"); 134 | // tracing::trace!("found in {:?} {:?} = {:?}", self.id, symbol, place); 135 | return Some((symbol.clone(), place.get_ref())); 136 | } 137 | match self.ty { 138 | ScopeType::NonRecursive => { 139 | tracing::trace!("non recursive not found {lookup}"); 140 | break; 141 | } 142 | ScopeType::Recursive => {} 143 | } 144 | if was_closed { 145 | tracing::trace!("not found in recursive - was closed"); 146 | break; 147 | } 148 | if spawn_id.is_none_or(|spawn_id| spawn_id == self.spawn_id) { 149 | tracing::trace!("not awaiting (same spawn id) - break"); 150 | break; 151 | } 152 | // TODO maybe wait for the name, not entire scope? 153 | self.closed_event.listen().await; 154 | tracing::trace!("continuing searching for {lookup}"); 155 | } 156 | if let Some(parent) = &self.parent { 157 | if let Some(result) = parent.lookup(lookup, spawn_id).await { 158 | return Some(result); 159 | } 160 | } 161 | None 162 | } 163 | .boxed() 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/target.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | pub enum Target { 5 | Interpreter, 6 | JavaScript, 7 | } 8 | 9 | impl Target { 10 | pub fn get_field(&self, field: &tuple::Member<'static>) -> eyre::Result { 11 | let Some(field) = field.as_ref().into_name() else { 12 | eyre::bail!("target has only named fields"); 13 | }; 14 | let field: &str = &field; 15 | Ok(match field { 16 | "name" => { 17 | let name = format!("{self:?}").to_lowercase(); 18 | // println!("name={name:?}"); 19 | ValueShape::String(name).into() 20 | } 21 | _ => eyre::bail!("target doesnt have field {field:?}"), 22 | }) 23 | } 24 | pub fn field_ty(field: &tuple::Member<'static>) -> eyre::Result { 25 | let Some(field) = field.as_ref().into_name() else { 26 | eyre::bail!("target has only named fields"); 27 | }; 28 | let field: &str = &field; 29 | Ok(match field { 30 | "name" => TypeShape::String.into(), 31 | _ => eyre::bail!("target doesnt have field {field:?}"), 32 | }) 33 | } 34 | } 35 | 36 | impl std::fmt::Display for Target { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | write!(f, "{self:?}") 39 | } 40 | } 41 | 42 | #[derive(Clone, derive_macros::ExprDisplay)] 43 | pub struct TargetDependentBranch { 44 | pub condition: Expr, 45 | pub body: T, 46 | } 47 | 48 | impl Kast { 49 | pub async fn select_target_dependent_branch<'a, T: std::fmt::Display>( 50 | &self, 51 | branches: &'a [TargetDependentBranch], 52 | target: Target, 53 | ) -> eyre::Result<&'a T> { 54 | // println!("selecting {target:?}"); 55 | let scopes = Scopes::new( 56 | self.spawn_id, 57 | ScopeType::NonRecursive, 58 | Some(self.cache.target_dependent_scopes.clone()), 59 | ); 60 | scopes.interpreter.insert( 61 | &self.cache.target_symbol, 62 | ValueShape::Target(target).into(), 63 | Mutability::ReadOnly, 64 | ); 65 | for branch in branches { 66 | let condition: Value = self 67 | .with_scopes(scopes.clone()) 68 | .enter_scope() 69 | .eval(&branch.condition) 70 | .await?; 71 | let condition = condition.into_inferred()?.into_bool()?; 72 | if condition { 73 | return Ok(&branch.body); 74 | } 75 | } 76 | eyre::bail!("no matching target branch"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /std/collections/_mod.ks: -------------------------------------------------------------------------------- 1 | module: 2 | 3 | const List = include "./list.ks"; 4 | const HashMap = include "./hash_map.ks"; 5 | -------------------------------------------------------------------------------- /std/collections/hash_map.ks: -------------------------------------------------------------------------------- 1 | module: 2 | 3 | const HashMap = forall[K :: type, V :: type] { 4 | native "HashMap" (K, V) :: type 5 | }; 6 | const new = forall[K :: type, V :: type] { 7 | native "HashMap.new" :: () -> HashMap[K, V] 8 | }; 9 | const insert = forall[K :: type, V :: type] { 10 | native "HashMap.insert" :: (&HashMap[K, V], K, V) -> () 11 | }; 12 | const get = forall[K :: type, V :: type] { 13 | native "HashMap.get" :: (&HashMap[K, V], &K) -> Option[&V] 14 | }; 15 | const size = forall[K :: type, V :: type] { 16 | native "HashMap.size" :: &HashMap[K, V] -> int32 17 | }; 18 | const into_iter = forall[K :: type, V :: type] { 19 | native "HashMap.into_iter" :: HashMap[K, V] -> () # with generator_handler[K, V] 20 | }; 21 | -------------------------------------------------------------------------------- /std/collections/list.ks: -------------------------------------------------------------------------------- 1 | module: 2 | 3 | const push = forall[T] { 4 | cfg_if { 5 | | target.name == "interpreter" => native "list.push" 6 | | target.name == "javascript" => (list_ref, value) => ( 7 | native "$(list_ref).get().push($(value))" 8 | ) 9 | } :: (&list[T], T) -> () 10 | }; 11 | const set = forall[T] { 12 | cfg_if { 13 | | target.name == "interpreter" => native "list.set" 14 | | target.name == "javascript" => (list_ref, index, value) => ( 15 | native "$(list_ref).get()[$(index)]=$(value)" 16 | ) 17 | } :: (&list[T], int32, T) -> () 18 | }; 19 | const length = forall[T] { 20 | cfg_if { 21 | | target.name == "interpreter" => native "list.length" 22 | | target.name == "javascript" => list_ref => ( 23 | native "$(list_ref).get().length" 24 | ) 25 | } :: &list[T] -> int32 26 | }; 27 | const iter = forall[T] { 28 | cfg_if { 29 | | target.name == "interpreter" => native "list.iter" 30 | | target.name == "javascript" => list_ref => ( 31 | native "console.log(123)"; 32 | let handle = (current generator_handler[&T]).handle; 33 | native "(()=>{let list=$(list_ref).get();for(let i=0;ilist[i],set:(v)=>list[i]=v})})()" 34 | ) 35 | } :: &list[T] -> () with generator_handler[&T] 36 | }; 37 | const get = forall[T] { 38 | cfg_if { 39 | | target.name == "interpreter" => native "list.get" 40 | | target.name == "javascript" => (list_ref, index) => ( 41 | native "(()=>{let index=$(index);let list=$(list_ref).get();return{get:()=>list[index],set:(value)=>list[index]=value}})()" 42 | ) 43 | } :: (&list[T], int32) -> &T 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /std/javascript.ks: -------------------------------------------------------------------------------- 1 | module: 2 | 3 | const transpile = forall[T] { native "transpile_to_javascript" :: T -> string }; 4 | -------------------------------------------------------------------------------- /std/lib.ks: -------------------------------------------------------------------------------- 1 | module: 2 | 3 | const @"syntax" = import "./syntax.ks"; 4 | 5 | impl syntax @"syntax".invoke_macro = macro (.@"macro", .arg) => `( 6 | include_ast ($@"macro" !! `($$arg)) 7 | ); 8 | 9 | impl syntax @"syntax".pipe_right = macro (.arg, .f) => `( 10 | let arg = $arg; 11 | let f = $f; 12 | f arg 13 | ); 14 | impl syntax @"syntax".pipe_left = macro (.f, .arg) => `( 15 | let f = $f; 16 | let arg = $arg; 17 | f arg 18 | ); 19 | impl syntax @"syntax".let_infer = macro (.pattern) => `( 20 | (let $pattern = _; $pattern) 21 | ); 22 | 23 | # const type = native "type"; 24 | const ast :: type = native "ast"; 25 | const expr :: type = native "expr"; 26 | 27 | const eval_ast = forall[T] { native "eval_ast" :: ast -> T }; 28 | const compile_ast = forall[T] { native "compile_ast" :: ast -> T }; 29 | 30 | const bool :: type = native "bool"; 31 | 32 | impl syntax @"syntax".@"true" = macro _ => `(native "true"); 33 | impl syntax @"syntax".@"false" = macro _ => `(native "false"); 34 | 35 | # TODO where T: Num or smth 36 | const @"op unary +" = forall[T] { (x :: T,) => x }; 37 | impl syntax @"syntax".@"op unary +" = macro (x,) => `(@"op unary +" $x); 38 | const @"op unary -" = forall[T] { native "unary -" :: T -> T }; 39 | impl syntax @"syntax".@"op unary -" = macro (x,) => `(@"op unary -" $x); 40 | 41 | # nobody's using spacetimedb, but maybe they should 42 | impl syntax @"syntax".@"op binary +" = macro (.lhs, .rhs) => `(@"op binary +" (.lhs = $lhs, .rhs = $rhs)); 43 | impl syntax @"syntax".@"op binary -" = macro (.lhs, .rhs) => `(@"op binary -" (.lhs = $lhs, .rhs = $rhs)); 44 | impl syntax @"syntax".@"op binary *" = macro (.lhs, .rhs) => `(@"op binary *" (.lhs = $lhs, .rhs = $rhs)); 45 | impl syntax @"syntax".@"op binary /" = macro (.lhs, .rhs) => `(@"op binary /" (.lhs = $lhs, .rhs = $rhs)); 46 | impl syntax @"syntax".@"op binary %" = macro (.lhs, .rhs) => `(@"op binary %" (.lhs = $lhs, .rhs = $rhs)); 47 | 48 | impl syntax @"syntax".@"op binary <" = macro (.lhs, .rhs) => `( 49 | @"op binary <"(.lhs = & $lhs, .rhs = & $rhs) 50 | ); 51 | impl syntax @"syntax".@"op binary <=" = macro (.lhs, .rhs) => `( 52 | @"op binary <="(.lhs = & $lhs, .rhs = & $rhs) 53 | ); 54 | impl syntax @"syntax".@"op binary ==" = macro (.lhs, .rhs) => `( 55 | @"op binary =="(.lhs = & $lhs, .rhs = & $rhs) 56 | ); 57 | impl syntax @"syntax".@"op binary !=" = macro (.lhs, .rhs) => `( 58 | @"op binary !="(.lhs = & $lhs, .rhs = & $rhs) 59 | ); 60 | impl syntax @"syntax".@"op binary >=" = macro (.lhs, .rhs) => `( 61 | @"op binary >="(.lhs = & $lhs, .rhs = & $rhs) 62 | ); 63 | impl syntax @"syntax".@"op binary >" = macro (.lhs, .rhs) => `( 64 | @"op binary >"(.lhs = & $lhs, .rhs = & $rhs) 65 | ); 66 | const @"op binary <" = forall[T] { 67 | cfg_if { 68 | | target.name == "interpreter" => native "<" 69 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)<$(rhs))" ) 70 | } :: (.lhs = &T, .rhs = &T) -> bool 71 | }; 72 | const @"op binary <=" = forall[T] { 73 | cfg_if { 74 | | target.name == "interpreter" => native "<=" 75 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)<=$(rhs))" ) 76 | } :: (.lhs = &T, .rhs = &T) -> bool 77 | }; 78 | const @"op binary ==" = forall[T] { 79 | cfg_if { 80 | # Can't have normal target.name == "interpreter" because it will recurse 81 | | native "==" (.lhs = &target.name, .rhs = &"interpreter") => native "==" 82 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)==$(rhs))" ) 83 | } :: (.lhs = &T, .rhs = &T) -> bool 84 | }; 85 | const @"op binary !=" = forall[T] { 86 | cfg_if { 87 | | target.name == "interpreter" => native "!=" 88 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)!=$(rhs))" ) 89 | } :: (.lhs = &T, .rhs = &T) -> bool 90 | }; 91 | const @"op binary >=" = forall[T] { 92 | cfg_if { 93 | | target.name == "interpreter" => native ">=" 94 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)>=$(rhs))" ) 95 | } :: (.lhs = &T, .rhs = &T) -> bool 96 | }; 97 | const @"op binary >" = forall[T] { 98 | cfg_if { 99 | | target.name == "interpreter" => native ">" 100 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)>$(rhs))" ) 101 | } :: (.lhs = &T, .rhs = &T) -> bool 102 | }; 103 | 104 | impl syntax @"syntax".@"op +=" = macro (.target, .value) => `($target = $target + $value); 105 | impl syntax @"syntax".@"op -=" = macro (.target, .value) => `($target = $target - $value); 106 | impl syntax @"syntax".@"op *=" = macro (.target, .value) => `($target = $target * $value); 107 | impl syntax @"syntax".@"op /=" = macro (.target, .value) => `($target = $target / $value); 108 | impl syntax @"syntax".@"op %=" = macro (.target, .value) => `($target = $target % $value); 109 | 110 | const @"op binary +" = forall[T] { 111 | cfg_if { 112 | | target.name == "interpreter" => native "+" 113 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)+$(rhs))" ) 114 | } :: (.lhs = T, .rhs = T) -> T 115 | }; 116 | const @"op binary -" = forall[T] { 117 | cfg_if { 118 | | target.name == "interpreter" => native "-" 119 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)-$(rhs))" ) 120 | } :: (.lhs = T, .rhs = T) -> T 121 | }; 122 | const @"op binary *" = forall[T] { 123 | cfg_if { 124 | | target.name == "interpreter" => native "*" 125 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)*$(rhs))" ) 126 | } :: (.lhs = T, .rhs = T) -> T 127 | }; 128 | const @"op binary /" = forall[T] { 129 | cfg_if { 130 | | target.name == "interpreter" => native "/" 131 | | target.name == "javascript" => (.lhs, .rhs) => ( 132 | # TODO its based on T 133 | native "Math.trunc($(lhs)/$(rhs))" 134 | ) 135 | } :: (.lhs = T, .rhs = T) -> T 136 | }; 137 | const @"op binary %" = forall[T] { 138 | cfg_if { 139 | | target.name == "interpreter" => native "%" 140 | | target.name == "javascript" => (.lhs, .rhs) => ( native "($(lhs)%$(rhs))" ) 141 | } :: (.lhs = T, .rhs = T) -> T 142 | }; 143 | 144 | const @"not" = native "not" :: bool -> bool; 145 | impl syntax @"syntax".@"not" = macro (e,) => `(@"not" $e); 146 | 147 | const int32 :: type = native "int32"; 148 | const int64 :: type = native "int64"; 149 | const float64 :: type = native "float64"; 150 | const char :: type = native "char"; 151 | const string :: type = native "string"; 152 | 153 | const output :: type = native "output"; 154 | # const output :: type = ( 155 | # .write = &string -> (), 156 | # ); 157 | const input :: type = ( 158 | .read_line = () -> string with (), 159 | ); 160 | native "set_native" (.name = "input", .value = input); 161 | 162 | const default_number_type :: type = native "default_number_type"; 163 | let default_number_type_based_on_dot :: default_number_type = ( 164 | # TODO should return Option[type] 165 | .default_number_type = s => ( 166 | if contains (.s, .substring=".") then 167 | float64 168 | else 169 | int32 170 | ), 171 | ); 172 | 173 | # TODO panic should return never 174 | const panic = fn(msg :: string) { 175 | cfg_if { 176 | | target.name == "interpreter" => native "panic" msg 177 | | target.name == "javascript" => native "(()=>{throw $(msg);})()" 178 | } 179 | }; 180 | 181 | const print :: &string -> () with output = line => ( 182 | let output = current output; 183 | output.write line; 184 | output.write &"\n"; 185 | ); 186 | 187 | const read_line :: () -> string with input = () => ( 188 | (current input).read_line () 189 | ); 190 | 191 | const filesystem :: type = native "filesystem"; 192 | 193 | const read_file :: string -> string with filesystem = fn (path) { 194 | (current filesystem).read_file path 195 | }; 196 | 197 | const contains :: (.s = string, .substring = string) -> bool = native "contains"; 198 | 199 | const dbg = forall[T] { 200 | (value :: T) => ( 201 | let output = current output; 202 | output.write <| &(native "dbg" value); 203 | output.write &" :: "; 204 | output.write <| &(native "dbg_type" T); 205 | output.write &"\n"; 206 | ) 207 | }; 208 | 209 | # TODO T: randomizable 210 | const random = forall[T] { 211 | native "random" :: (.min = T, .max = T) -> T 212 | }; 213 | 214 | # TODO `:: type` should be inferred 215 | const Option = forall[T :: type] { newtype :Some T | :None }; 216 | native "set_native" (.name = "Option", .value = Option); 217 | 218 | const Either = forall[.left :: type, .right :: type] { newtype :Left left | :Right right }; 219 | 220 | const Result = forall[.ok :: type, .error :: type] { newtype :Ok ok | :Error error }; 221 | 222 | const TypeName = (.name = string) as type; 223 | 224 | impl int32 as TypeName = (.name = "int32"); 225 | 226 | const Copy = forall[Self :: type] { () }; 227 | 228 | native "set_native" (.name = "Copy", .value = Copy); 229 | 230 | impl int32 as Copy = (); 231 | impl int64 as Copy = (); 232 | impl float64 as Copy = (); 233 | impl bool as Copy = (); 234 | impl char as Copy = (); 235 | impl type as Copy = (); 236 | 237 | # TODO Clone trait 238 | const clone = forall[T] { 239 | fn (x :: &T) -> T { 240 | native "clone" x 241 | } 242 | }; 243 | 244 | const Parse = forall[Self] { 245 | .parse = &string -> Self, 246 | }; 247 | 248 | impl int32 as Parse = ( 249 | .parse = native "parse", 250 | ); 251 | 252 | impl int64 as Parse = ( 253 | .parse = native "parse", 254 | ); 255 | 256 | impl float64 as Parse = ( 257 | .parse = native "parse", 258 | ); 259 | 260 | const parse = forall[T] { 261 | s => (T as Parse).parse s 262 | }; 263 | 264 | const generator_handler = forall[T :: type] { # TODO contexts 265 | (.handle = T -> () with output) :: type 266 | }; 267 | native "set_native" (.name = "generator_handler", .value = generator_handler); 268 | 269 | const loop_context = forall[T] { 270 | ( 271 | .@"break" = T -> () with (), # TODO never 272 | .@"continue" = () -> () with (), 273 | ) :: type 274 | }; 275 | 276 | impl syntax @"syntax".@"loop" = macro (.body) => `( 277 | unwindable for_loop ( 278 | let body = () => ( 279 | const BodyResult = forall[T] { newtype :Break T | :Continue }; 280 | let body_result :: BodyResult[_] = unwindable body ( 281 | with ( 282 | .@"break" = () => unwind body (:Break ()), 283 | .@"continue" = () => unwind body (:Continue), 284 | ) :: loop_context[()]; 285 | $body; 286 | :Continue 287 | ); 288 | match body_result { 289 | | :Break value => unwind for_loop value 290 | | :Continue => () 291 | }; 292 | ); 293 | native "loop" body 294 | ) 295 | ); 296 | 297 | impl syntax @"syntax".@"while" = macro (.cond, .body) => `( 298 | loop { 299 | if $cond then $body else (break); 300 | } 301 | ); 302 | 303 | impl syntax @"syntax".for_loop = macro (.value_pattern, .generator, .body) => `( 304 | unwindable for_loop ( 305 | let handler = $value_pattern => ( 306 | const BodyResult = forall[T] { newtype :Break T | :Continue }; 307 | let body_result :: BodyResult[_] = unwindable body ( 308 | with ( 309 | .@"break" = () => unwind body (:Break ()), 310 | .@"continue" = () => unwind body (:Continue), 311 | ) :: loop_context[()]; 312 | $body; 313 | :Continue 314 | ); 315 | match body_result { 316 | | :Break value => unwind for_loop value 317 | | :Continue => () 318 | }; 319 | ); 320 | with (.handle = handler) :: generator_handler[_]; 321 | $generator; 322 | ) 323 | ); 324 | 325 | impl syntax @"syntax".@"yield" = macro (.value) => `( 326 | (current generator_handler[_]).handle $value 327 | ); 328 | 329 | impl syntax @"syntax".break_without_value = macro _ => `( 330 | break () 331 | ); 332 | 333 | impl syntax @"syntax".break_with_value = macro (.value) => `( 334 | (current loop_context[_]).@"break" $value 335 | ); 336 | 337 | impl syntax @"syntax".@"continue" = macro _ => `( 338 | (current loop_context[()]).@"continue" () # TODO infer loop context arg 339 | ); 340 | 341 | const char_ord :: char -> int32 = native "char_ord"; 342 | const chars :: &string -> () with generator_handler[char] = native "chars"; 343 | const push_char :: (string, char) -> string = native "push_char"; 344 | 345 | const exec_mode = native "exec_mode"; 346 | 347 | #trait Iterator { 348 | # type Item; 349 | # fn next(&mut self) -> Option<::Item>; 350 | #} 351 | 352 | /* 353 | const Iterator = forall[Self] { 354 | .Item = type, 355 | .next = Self -> (Self as Iterator).Item, 356 | }; 357 | 358 | /* 359 | const generator = forall[Self] { 360 | rec ( 361 | let item = type; 362 | let generate = Self -> () with generator_handler[item]; 363 | ) 364 | }; 365 | 366 | impl string as generator = ( 367 | .item = char, 368 | .generate :: string -> () with generator_handler[char] = native "iterate_over_string", 369 | ); 370 | */ 371 | 372 | const collections = include "./collections/_mod.ks"; 373 | const time = include "./time.ks"; 374 | const javascript = include "./javascript.ks"; 375 | 376 | const prelude = ( 377 | # TODO use type, bool, int32, int64, float64, char, string; 378 | # .type = type, 379 | .bool = bool, 380 | .int32 = int32, 381 | .int64 = int64, 382 | .float64 = float64, 383 | .char = char, 384 | .string = string, 385 | 386 | .print = print, 387 | 388 | .Option = Option, 389 | .Either = Either, 390 | .Result = Result, 391 | 392 | .panic = panic, 393 | .dbg = dbg, 394 | ); 395 | -------------------------------------------------------------------------------- /std/syntax.ks: -------------------------------------------------------------------------------- 1 | syntax from scratch 2 | 3 | syntax @"builtin macro then" -> "0" = a ";;" b;; 4 | syntax @"builtin macro syntax_module" -> "0" = "syntax_module" "{" body "}";; 5 | 6 | syntax_module { 7 | syntax @"builtin macro then" -> 0 = a ";" b; 8 | syntax @"builtin macro then" -> 0 = a ";"; 9 | 10 | syntax @"builtin macro struct_def" <- "-1" = "module" ":" body; 11 | 12 | syntax @"builtin macro use" <- 1 = "use" namespace ".*"; 13 | 14 | # syntax return <- 2 = "return" value; 15 | syntax break_with_value <- 2 = "break" value; 16 | syntax break_without_value <- 2 = "break"; 17 | syntax continue <- 2 = "continue"; 18 | syntax yield <- 2 = "yield" value; 19 | 20 | syntax @"while" <- 3 = "while" cond "{" body "}"; 21 | syntax @"loop" <- 3 = "loop" "{" body "}"; 22 | syntax for_loop <- 3 = "for" value_pattern "in" generator "{" body "}"; 23 | syntax @"builtin macro impl_cast" <- 4 = "impl" value "as" target "=" impl; 24 | syntax @"builtin macro impl_syntax" <- 4 = "impl" "syntax" def "=" impl; 25 | # syntax @"builtin macro let_assign" <- 4 = "let" pattern "=" value; 26 | syntax @"builtin macro const_let" <- 4 = "const" pattern "=" value; 27 | syntax @"builtin macro assign" <- 4 = assignee "=" value; 28 | 29 | syntax @"op +=" <- 4 = target "+=" value; 30 | syntax @"op -=" <- 4 = target "-=" value; 31 | syntax @"op *=" <- 4 = target "*=" value; 32 | syntax @"op /=" <- 4 = target "/=" value; 33 | syntax @"op %=" <- 4 = target "%=" value; 34 | 35 | syntax @"builtin macro let" <- 4.1 = "let" pattern; 36 | 37 | syntax @"builtin macro newtype" <- 4.3 = "newtype" def; 38 | 39 | syntax @"builtin macro tuple" <- 4.5 = a "," b; 40 | syntax @"builtin macro tuple" <- 4.5 = a ","; 41 | 42 | syntax @"builtin macro field" <- 4.75 = "." name "=" value; 43 | syntax @"builtin macro field" <- 4.75 = "." name; 44 | 45 | syntax @"builtin macro comptime" <- 4.9 = "comptime" value; 46 | # syntax @"builtin macro comptime" <- 1 = "comptime" value; 47 | 48 | # syntax @"builtin macro with_context" <- 5 = "with" new_context "(" expr ")"; 49 | syntax @"builtin macro with_context" <- 5 = "with" new_context; 50 | syntax @"builtin macro current_context" <- 5 = "current" context_type; 51 | syntax @"builtin macro macro" <- 5 = "macro" def; 52 | syntax @"builtin macro oneof" <- 5 = "oneof" def; 53 | 54 | syntax @"builtin macro merge" <- 5.5 = "|" a; 55 | syntax @"builtin macro merge" <- 5.5 = a "|" b; 56 | 57 | syntax @"builtin macro template_def" <- 6 = "[" arg "]" "=>" body; 58 | syntax @"builtin macro template_def" <- 6 = "forall" "[" arg "]" "{" body "}"; 59 | syntax @"builtin macro template_def" <- 6 = "forall" "[" arg "]" "where" where "{" body "}"; 60 | syntax @"builtin macro function_def" -> 6 = arg "=>" body; 61 | 62 | syntax @"builtin macro type_ascribe" <- 7.1 = value "::" type; 63 | syntax @"builtin macro context_ascribe" <- 7.1 = expr "::" "with" contexts; 64 | syntax @"builtin macro type_ascribe" <- 7.1 = type "of" value; 65 | syntax @"builtin macro type_ascribe" <- 7.1 = type ":>" value; 66 | syntax @"builtin macro type_ascribe" <- 7.1 = value "<:" type; 67 | 68 | syntax @"builtin macro mutable_pattern" <- 7.25 = "mut" pattern; 69 | 70 | syntax @"builtin macro function_type" -> 7.5 = arg "->" result; 71 | syntax @"builtin macro function_type" -> 7.5 = arg "->" result "with" contexts; 72 | syntax @"builtin macro function_def" -> 7.5 = arg "->" returns "=>" body; 73 | 74 | syntax @"builtin macro if" -> 12.9 = cond "then" then "else" else; 75 | # syntax @"builtin macro match" <- 13 = "match" value "(" branches ")"; 76 | syntax @"builtin macro match" <- 13 = "match" value "{" branches "}"; 77 | syntax @"builtin macro target_dependent" <- 13 = "cfg_if" "{" branches "}"; 78 | syntax @"builtin macro if" <- 13 = "if" cond "then" then; 79 | syntax @"builtin macro if" <- 13 = "if" cond "then" then "else" else; 80 | 81 | syntax @"builtin macro function_def" <- 13.5 = "fn" "(" arg ")" "with" contexts "{" body "}"; 82 | syntax @"builtin macro function_def" <- 13.5 = "fn" "(" arg ")" "->" result_type "{" body "}"; 83 | syntax @"builtin macro function_def" <- 13.5 = "fn" "(" arg ")" "->" result_type "with" contexts "{" body "}"; 84 | syntax @"builtin macro function_def" <- 13.5 = "fn" "(" arg ")" "{" body "}"; 85 | 86 | syntax implements <- 14 = type "implements" trait; 87 | 88 | syntax pipe_right <- 15 = arg "|>" f; 89 | syntax pipe_left <- 15 = f "<|" arg; 90 | 91 | syntax try_explicit <- 16 = "try" "[" targ "]" expr; 92 | syntax try_implicit <- 16 = "try" expr; 93 | syntax catch_impl <- 16 = expr "catch" e "{" catch_block "}"; 94 | syntax catch_impl <- 16 = expr "catch" e "(" catch_block ")"; 95 | 96 | syntax @"builtin macro or" <- 17 = lhs "or" rhs; 97 | syntax @"builtin macro and" <- 18 = lhs "and" rhs; 98 | 99 | syntax @"builtin macro is" <- 18.5 = value "is" pattern; 100 | 101 | syntax @"op binary <" <- 19 = lhs "<" rhs; 102 | syntax @"op binary <=" <- 19 = lhs "<=" rhs; 103 | syntax @"op binary ==" <- 19 = lhs "==" rhs; 104 | syntax @"op binary !=" <- 19 = lhs "!=" rhs; 105 | syntax @"op binary >=" <- 19 = lhs ">=" rhs; 106 | syntax @"op binary >" <- 19 = lhs ">" rhs; 107 | 108 | syntax @"builtin macro cast" <- 20 = value "as" target; 109 | #syntax @"builtin macro check_impl" <- 21 = value "impl" trait; 110 | 111 | syntax @"op unary +" <- 25 = "+" _; 112 | syntax @"op unary -" <- 25 = "-" _; 113 | syntax @"op binary +" <- 25 = lhs "+" rhs; 114 | syntax @"op binary -" <- 25 = lhs "-" rhs; 115 | 116 | syntax @"op binary *" <- 40 = lhs "*" rhs; 117 | syntax @"op binary /" <- 40 = lhs "/" rhs; 118 | syntax @"op binary %" <- 40 = lhs "%" rhs; 119 | 120 | # syntax @"op binary ^" -> 60 = lhs "^" rhs; 121 | 122 | syntax @"op postfix ++" <- 100 = x "++"; 123 | syntax @"op prefix ++" <- 100 = "++" x; 124 | syntax @"op postfix --" <- 100 = x "--"; 125 | syntax @"op prefix --" <- 100 = "--" x; 126 | syntax @"not" <- 100 = "not" _; 127 | 128 | syntax @"builtin macro call" <- 100 = f arg; 129 | syntax @"builtin macro ref_pattern" <- 100 = "ref" ident; 130 | 131 | syntax @"builtin macro ref" -> 110 = "&" place; 132 | syntax @"builtin macro deref" <- 115 = ref "^"; 133 | 134 | syntax @"builtin macro typeof" <- 120 = "typeof" expr; 135 | syntax @"builtin macro typeofvalue" <- 120 = "typeofvalue" expr; 136 | syntax @"builtin macro include_ast" <- 120 = "include_ast" ast; 137 | 138 | syntax @"builtin macro native" <- 150 = "native" name; 139 | syntax @"builtin macro import" <- 150 = "import" path; 140 | syntax @"builtin macro include" <- 150 = "include" path; 141 | syntax invoke_macro <- 150 = @"macro" "!" arg; 142 | syntax @"builtin macro call_macro" <- 150 = @"macro" "!!" arg; 143 | 144 | syntax @"builtin macro variant" -> 200 = "`" name; 145 | syntax @"builtin macro variant" -> 200 = "`" name value; 146 | syntax @"builtin macro quote" -> 200 = "`" "(" expr ")"; 147 | # syntax @"builtin macro variant" <- 250 = type ":" name value; 148 | # syntax @"builtin macro variant" <- 250 = type ":" name; 149 | syntax @"builtin macro variant" <- 250 = ":" name value; 150 | syntax @"builtin macro variant" <- 250 = ":" name; 151 | 152 | syntax @"builtin macro field_access" <- 300 = obj "." field; 153 | 154 | syntax @"builtin macro instantiate_template" <- 300 = template "[" arg "]"; 155 | 156 | syntax @"builtin macro struct_def" <- 500 = "rec" "(" body ")"; 157 | syntax @"builtin macro struct_def" <- 500 = "rec" "{" body "}"; 158 | syntax @"builtin macro struct_def" <- 500 = "struct" "(" body ")"; 159 | syntax @"builtin macro struct_def" <- 500 = "struct" "{" body "}"; 160 | syntax @"builtin macro unwindable" <- 500 = "unwindable" name body; 161 | syntax @"builtin macro unwind" <- 500 = "unwind" name value; 162 | syntax @"builtin macro type" <- 500 = "type" expr; 163 | syntax @"builtin macro type" <- 500 = "type"; 164 | 165 | syntax @"builtin macro unquote" -> 500 = "$" expr; 166 | syntax @"builtin macro unquote" -> 500 = "$" "(" expr ")"; 167 | 168 | syntax let_infer <- 500 = "_let" pattern; 169 | 170 | # syntax @"builtin macro function_def" <- 100000 = "{" body "}"; 171 | 172 | syntax @"builtin macro list" <- 100000 = "list" "[" values "]"; 173 | syntax @"builtin macro list" <- 100000 = "list" "[" "]"; 174 | syntax @"builtin macro scope" <- 100000 = "(" _ ")"; 175 | syntax @"builtin macro make_unit" <- 100000 = "(" ")"; 176 | syntax @"builtin macro placeholder" <- 100000 = "_"; 177 | 178 | syntax true <- 100000 = "true"; 179 | syntax false <- 100000 = "false"; 180 | 181 | # const @"postfix ++" = macro (.x :: ast) => `(x += 1); 182 | 183 | } 184 | -------------------------------------------------------------------------------- /std/time.ks: -------------------------------------------------------------------------------- 1 | module: 2 | 3 | const now :: () -> float64 = native "time.now"; 4 | -------------------------------------------------------------------------------- /tests/examples.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity)] 2 | use kast::*; 3 | use std::path::Path; 4 | 5 | #[allow(dead_code)] 6 | struct Case<'a> { 7 | name: &'a str, 8 | comment_lines: Option bool + 'a>>, 9 | input: &'a str, 10 | expect_output: &'a str, 11 | } 12 | 13 | fn try_test(case: Case<'_>) -> eyre::Result { 14 | let mut kast = Kast::new("").unwrap(); 15 | 16 | struct CaptureOutput { 17 | output: std::sync::Arc>, 18 | } 19 | 20 | impl Output for CaptureOutput { 21 | fn write(&self, s: &str) { 22 | *self.output.lock().unwrap() += s; 23 | } 24 | } 25 | 26 | let output = std::sync::Arc::new(std::sync::Mutex::new(String::new())); 27 | kast.output = std::sync::Arc::new(CaptureOutput { 28 | output: output.clone(), 29 | }); 30 | 31 | let path = Path::new("examples").join(case.name).with_extension("ks"); 32 | let value = match case.comment_lines { 33 | Some(f) => { 34 | let source = SourceFile { 35 | contents: { 36 | let mut result = String::new(); 37 | for line in std::fs::read_to_string(&path).unwrap().lines() { 38 | if f(line) { 39 | continue; 40 | } 41 | result += line; 42 | result.push('\n'); 43 | } 44 | result 45 | }, 46 | filename: path, 47 | }; 48 | kast.eval_source(source, None)? 49 | } 50 | None => kast.eval_file(path)?, 51 | }; 52 | 53 | let output: String = output.lock().unwrap().clone(); 54 | assert_eq!(case.expect_output, output); 55 | 56 | Ok(value) 57 | } 58 | 59 | fn test(case: Case<'_>) { 60 | try_test(case).expect("Failed to run the test"); 61 | } 62 | 63 | /// file:///./../examples/hello.ks 64 | #[test] 65 | fn test_hello_world() { 66 | test(Case { 67 | name: "hello", 68 | comment_lines: None, 69 | input: "", 70 | expect_output: "Hello\nWorld\n", 71 | }); 72 | } 73 | 74 | /// file:///./../examples/import-zero.ks 75 | /// file:///./../examples/zero.ks 76 | #[test] 77 | #[should_panic = "assertion `left == right` failed\n left: \"1 :: int32\\n\"\n right: \"0 :: int32\\n\""] 78 | fn test_import_zero_but_expect_1() { 79 | test(Case { 80 | name: "import-zero", 81 | comment_lines: None, 82 | input: "", 83 | expect_output: "1 :: int32\n", 84 | }); 85 | } 86 | 87 | /// file:///./../examples/mut.ks 88 | #[test] 89 | fn test_mut() { 90 | test(Case { 91 | name: "mut", 92 | comment_lines: None, 93 | input: "", 94 | expect_output: "hello\nworld\n", 95 | }); 96 | } 97 | 98 | /// file:///./../examples/default-number-type.ks 99 | #[test] 100 | fn test_default_number_type() { 101 | test(Case { 102 | name: "default-number-type", 103 | comment_lines: None, 104 | input: "", 105 | expect_output: "123 :: int32\n123 :: int64\n123 :: int32\n123 :: float64\n", 106 | }); 107 | } 108 | 109 | /// file:///./../examples/import-zero.ks 110 | /// file:///./../examples/zero.ks 111 | #[test] 112 | fn test_import_zero() { 113 | test(Case { 114 | name: "import-zero", 115 | comment_lines: None, 116 | input: "", 117 | expect_output: "0 :: int32\n", 118 | }); 119 | } 120 | 121 | /// file:///./../examples/mutual-recursion.ks 122 | #[test] 123 | fn test_mutual_recursion() { 124 | test(Case { 125 | name: "mutual-recursion", 126 | comment_lines: None, 127 | input: "", 128 | expect_output: "inside f\n0 :: int32\ninside g\n1 :: int32\ninside f\n2 :: int32\ninside g\n3 :: int32\n", 129 | }); 130 | } 131 | 132 | /// file:///./../examples/context-shadow.ks 133 | // How can I make it not sucking that hard 134 | #[test] 135 | 136 | fn test_context_shadow_with_missing_context() { 137 | let err = try_test(Case { 138 | name: "context-shadow", 139 | comment_lines: None, 140 | input: "", 141 | expect_output: "123\nshadow\n", 142 | }) 143 | .unwrap_err(); 144 | let err = format!("{err:?}"); // debug impl shows the whole chain 145 | assert!(err.contains("string context not available")); 146 | } 147 | 148 | /// file:///./../examples/context-shadow.ks 149 | #[test] 150 | fn test_context_shadow() { 151 | test(Case { 152 | name: "context-shadow", 153 | comment_lines: Some(Box::new(|line| line.contains("should not compile"))), 154 | input: "", 155 | expect_output: "123\nshadow\n", 156 | }); 157 | } 158 | 159 | /// file:///./../examples/is.ks 160 | #[test] 161 | fn test_is() { 162 | test(Case { 163 | name: "is", 164 | comment_lines: None, 165 | input: "", 166 | expect_output: "69 :: int32\nNone\nfalse :: bool\n", 167 | }); 168 | } 169 | 170 | /// file:///./../examples/local-syntax.ks 171 | #[test] 172 | 173 | fn test_local_syntax() { 174 | test(Case { 175 | name: "local-syntax", 176 | comment_lines: None, 177 | input: "", 178 | expect_output: "hello\nworld\nworld\nhello\n", 179 | }); 180 | } 181 | 182 | /// file:///./../examples/macro-hygiene.ks 183 | #[test] 184 | 185 | fn test_macro_hygiene() { 186 | test(Case { 187 | name: "macro-hygiene", 188 | comment_lines: None, 189 | input: "", 190 | expect_output: "hi\nhello\nhi\nnamed\nnamed\n", 191 | }); 192 | } 193 | 194 | /// file:///./../examples/ast-nested-scope.ks 195 | #[test] 196 | 197 | fn ast_nested_scope() { 198 | test(Case { 199 | name: "ast-nested-scope", 200 | comment_lines: None, 201 | input: "", 202 | expect_output: "hi\n", 203 | }); 204 | } 205 | 206 | /// file:///./../examples/hash_map.ks 207 | #[test] 208 | 209 | fn test_hash_map() { 210 | test(Case { 211 | name: "hash_map", 212 | comment_lines: None, 213 | input: "", 214 | expect_output: include_str!("hash_map.output"), 215 | }); 216 | } 217 | 218 | /// file:///./../examples/unsafe.ks 219 | #[test] 220 | 221 | fn test_unsafe() { 222 | test(Case { 223 | name: "unsafe", 224 | comment_lines: None, 225 | input: "", 226 | expect_output: "hello\n", 227 | }); 228 | } 229 | 230 | /// file:///./../examples/unsafe.ks 231 | #[test] 232 | #[ignore = "TODO compile checked contexts"] 233 | #[should_panic = "context is not available"] 234 | fn test_unsafe_without_unsafe_context() { 235 | test(Case { 236 | name: "unsafe", 237 | comment_lines: Some(Box::new(|line| line.trim().starts_with("with"))), 238 | input: "", 239 | expect_output: "", 240 | }); 241 | } 242 | 243 | /// file:///./../examples/fibonacci.ks 244 | #[test] 245 | fn test_fibonacci() { 246 | let name = "fibonacci"; 247 | let mut kast = Kast::new(name).unwrap(); 248 | let module = kast 249 | .import(Path::new("examples").join(name).with_extension("ks")) 250 | .expect("Failed to import the test"); 251 | kast.add_local( 252 | kast.new_symbol( 253 | "test", 254 | Span { 255 | start: Position::ZERO, 256 | end: Position::ZERO, 257 | filename: "test_fibonacci".into(), 258 | }, 259 | ), 260 | module, 261 | ); 262 | let mut test_fib = |n: usize, answer: i32| { 263 | let value = kast 264 | .eval_source::( 265 | SourceFile { 266 | contents: format!("test.fib {n}"), 267 | filename: "".into(), 268 | }, 269 | None, 270 | ) 271 | .unwrap(); 272 | let value = value.into_inferred().unwrap().into_int32().unwrap(); 273 | assert_eq!(answer, value); 274 | }; 275 | test_fib(1, 1); 276 | test_fib(5, 8); 277 | test_fib(10, 89); 278 | } 279 | 280 | /// file:///./../examples/variant-types.ks 281 | #[test] 282 | fn test_variant_types() { 283 | let err = try_test(Case { 284 | name: "variant-types", 285 | comment_lines: None, 286 | input: "", 287 | expect_output: "", 288 | }) 289 | .unwrap_err(); 290 | let err = format!("{err:?}"); // debug impl shows the whole chain 291 | assert!( 292 | err.contains("this is going to panic"), 293 | "this example does unwrap of .Error" 294 | ); 295 | } 296 | 297 | #[test] 298 | #[ignore = "TODO make all examples tested"] 299 | fn test_all_examples_are_tested() { 300 | let this_source = std::fs::read_to_string("tests/examples.rs").unwrap(); 301 | let used_files: std::collections::HashSet = this_source 302 | .lines() 303 | .filter_map(|line| line.strip_prefix("/// file:///./../examples/")) 304 | .map(|s| s.to_owned()) 305 | .collect(); 306 | let mut unused_files = std::collections::BTreeSet::::new(); 307 | for entry in std::fs::read_dir("examples").unwrap() { 308 | let entry = entry.unwrap(); 309 | let file_name = entry.file_name(); 310 | let file_name = file_name.to_str().unwrap(); 311 | if !used_files.contains(file_name) { 312 | unused_files.insert(file_name.to_owned()); 313 | } 314 | } 315 | if !unused_files.is_empty() { 316 | panic!("Untested examples: {unused_files:#?}"); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /tests/hash_map.output: -------------------------------------------------------------------------------- 1 | 2 :: int32 2 | :Option.Some &"world" :: std.Option[type &string] 3 | :Option.None :: std.Option[type &string] 4 | iterated 5 | iterated 6 | -------------------------------------------------------------------------------- /tests/simple.rs: -------------------------------------------------------------------------------- 1 | use kast::*; 2 | 3 | fn test_eq(source: &str, expected_value: Value) { 4 | let source = SourceFile { 5 | contents: source.to_owned(), 6 | filename: "".into(), 7 | }; 8 | let mut kast = Kast::new("").unwrap(); 9 | let value = kast 10 | .eval_source::(source, Some(expected_value.ty())) 11 | .expect("Failed to eval source"); 12 | assert_eq!(value, expected_value); 13 | } 14 | 15 | // cant i write a frontend for LUA instead for kast? I am a LUA pro! 8) 16 | #[test] 17 | fn simple() { 18 | test_eq( 19 | "\"hello, world\"", 20 | ValueShape::String("hello, world".to_owned()).into(), 21 | ); 22 | test_eq( 23 | "const int32 = native \"int32\"; 123 :: int32", 24 | ValueShape::Int32(123).into(), 25 | ); 26 | test_eq("\"hello\" |> std.dbg", ValueShape::Unit.into()); 27 | test_eq("2 + 2", ValueShape::Int32(4).into()); 28 | test_eq("2 + 2", ValueShape::Int64(4).into()); 29 | test_eq( 30 | "use std.*; (:Some 123 :: Option[int32]) is :Some _", 31 | ValueShape::Bool(true).into(), 32 | ); 33 | test_eq( 34 | "use std.*; let foo = (a :: int32, b) => a + b; foo (parse \"123\", parse \"456\")", 35 | ValueShape::Int32(579).into(), 36 | ); 37 | } 38 | --------------------------------------------------------------------------------