├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── Makefile ├── README.md ├── examples ├── dummy.let └── hello.let ├── lib └── stdio.let ├── sources ├── compiler │ ├── backend │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── lib.rs │ │ │ ├── prelude │ │ │ └── mod.rs │ │ │ └── steps │ │ │ ├── codegen │ │ │ ├── context │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── model │ │ │ │ ├── decls │ │ │ │ └── mod.rs │ │ │ │ ├── import.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── scope │ │ │ ├── context │ │ │ ├── env.rs │ │ │ ├── mod.rs │ │ │ ├── stack.rs │ │ │ └── symbol.rs │ │ │ ├── mod.rs │ │ │ └── model │ │ │ ├── decls │ │ │ └── mod.rs │ │ │ ├── expr │ │ │ ├── lit.rs │ │ │ └── mod.rs │ │ │ ├── import.rs │ │ │ ├── mod.rs │ │ │ ├── pat │ │ │ └── mod.rs │ │ │ └── typ │ │ │ └── mod.rs │ ├── bmi │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── builder │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── cargo │ │ │ └── mod.rs │ │ │ ├── context │ │ │ └── mod.rs │ │ │ └── lib.rs │ ├── cli │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── args.rs │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ └── prelude.rs │ └── frontend │ │ ├── Cargo.toml │ │ └── src │ │ ├── ast │ │ ├── decls │ │ │ ├── class.rs │ │ │ ├── effect.rs │ │ │ ├── function.rs │ │ │ └── mod.rs │ │ ├── expr │ │ │ ├── list.rs │ │ │ ├── lit.rs │ │ │ ├── mod.rs │ │ │ ├── ops.rs │ │ │ └── tuple.rs │ │ ├── import.rs │ │ ├── mod.rs │ │ ├── pat │ │ │ └── mod.rs │ │ └── typ │ │ │ └── mod.rs │ │ ├── lexer │ │ ├── location.rs │ │ ├── mod.rs │ │ ├── stream.rs │ │ └── token.rs │ │ ├── lib.rs │ │ ├── parser │ │ ├── grammar.rs │ │ ├── mod.rs │ │ └── sourcemap.rs │ │ └── prelude │ │ └── mod.rs ├── runtime │ ├── Cargo.toml │ └── src │ │ ├── concurrency │ │ ├── mod.rs │ │ ├── process │ │ │ ├── context.rs │ │ │ ├── handle.rs │ │ │ └── mod.rs │ │ ├── process_group │ │ │ ├── command.rs │ │ │ ├── mod.rs │ │ │ ├── request.rs │ │ │ └── response.rs │ │ └── signal.rs │ │ ├── data │ │ ├── atom.rs │ │ ├── exc.rs │ │ ├── mod.rs │ │ └── pid.rs │ │ ├── lib.rs │ │ ├── prelude │ │ └── mod.rs │ │ ├── thread │ │ ├── continuation.rs │ │ ├── coroutine.rs │ │ ├── int.rs │ │ ├── mod.rs │ │ └── task.rs │ │ └── traits │ │ ├── class.rs │ │ ├── func.rs │ │ └── mod.rs └── utils │ ├── ast-core │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ └── ast-macros │ ├── Cargo.toml │ └── src │ ├── codegen │ ├── mod.rs │ ├── model.rs │ ├── visit.rs │ └── visit_many.rs │ ├── lib.rs │ └── parser │ ├── mod.rs │ ├── model │ ├── assoc.rs │ └── mod.rs │ └── visit.rs └── www ├── LICENSE.txt ├── assets ├── languages │ └── letlang.tmLanguage.json └── style.css ├── config.yaml ├── content ├── _index.md ├── book │ └── _index.md ├── lep │ └── _index.md └── spec │ ├── _index.md │ ├── expressions │ ├── _index.md │ ├── cond.md │ ├── containers.md │ ├── destructuring.md │ ├── do.md │ ├── effects.md │ ├── exceptions.md │ ├── function-call.md │ ├── lazy-assert.md │ ├── match.md │ ├── operators.md │ ├── pipeline.md │ ├── receive.md │ ├── spawn.md │ └── typecheck.md │ ├── lexical-structure │ ├── _index.md │ ├── comments.md │ ├── identfiers.md │ ├── input-format.md │ ├── keywords.md │ ├── tokens.md │ └── whitespace.md │ ├── linkage │ ├── _index.md │ ├── archive-format.md │ ├── binary-module-interface.md │ ├── compilation-unit.md │ └── executable.md │ ├── modules-imports.md │ ├── notation.md │ ├── pattern-matching.md │ ├── runtime │ ├── _index.md │ ├── actor.md │ ├── classes.md │ └── iocap.md │ └── symbols │ ├── _index.md │ ├── class.md │ ├── effect.md │ └── function.md ├── data ├── menu.yml ├── meta.yml └── news.yml ├── netlify.toml ├── package-lock.json ├── package.json ├── static ├── css │ ├── fontawesome.min.css │ └── style.css ├── img │ └── favicon.svg ├── js │ ├── alpine.min.js │ ├── el-transition.js │ ├── fontawesome.min.js │ ├── moment.min.js │ └── news.js ├── schemas │ └── bmi.json └── webfonts │ ├── fa-brands-400.eot │ ├── fa-brands-400.svg │ ├── fa-brands-400.ttf │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.svg │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ ├── fa-solid-900.eot │ ├── fa-solid-900.svg │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ └── fa-solid-900.woff2 ├── tailwind.config.js └── templates ├── layouts ├── _default │ └── home.html ├── base.html ├── book │ ├── base.html │ ├── section.html │ └── single.html ├── lep │ ├── base.html │ ├── section.html │ └── single.html └── spec │ ├── base.html │ ├── section.html │ └── single.html ├── partials ├── footer.html ├── nav.html └── sidenav-menu.html └── shortcodes └── nav.html /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.lltarget/ 3 | /www/node_modules/ 4 | /www/public/ 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | resolver = "2" 4 | members = [ 5 | "sources/utils/ast-core", 6 | "sources/utils/ast-macros", 7 | "sources/compiler/bmi", 8 | "sources/compiler/frontend", 9 | "sources/compiler/backend", 10 | "sources/compiler/builder", 11 | "sources/compiler/cli", 12 | "sources/runtime", 13 | ] 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 David Delassus 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LETLANGC := cargo run --bin letlangc -- 2 | 3 | .PHONY: dummy 4 | dummy: 5 | @$(LETLANGC) --type=exe --rpath ./sources/runtime ./examples/dummy.let 6 | @./dummy.exe 7 | 8 | .PHONY: hello 9 | hello: 10 | @$(LETLANGC) --type=lib --rpath ./sources/runtime ./lib/stdio.let 11 | @$(LETLANGC) --type=exe --rpath ./sources/runtime ./examples/hello.let -L. -lstdio 12 | @./hello.exe 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Letlang 2 | 3 | ![status: WIP](https://img.shields.io/badge/status-WIP-red?style=flat-square) 4 | ![version: 0.0.0](https://img.shields.io/badge/version-v0.0.0-brightgreen?style=flat-square) 5 | ![license: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square) 6 | 7 | ## Contributing 8 | 9 | If you want to contribute, please make a pull request. 10 | 11 | **Requirements:** 12 | 13 | - Rust & Cargo 1.77+ (nightly) 14 | 15 | **Build:** 16 | 17 | ``` 18 | $ cargo build 19 | ``` 20 | 21 | **Run example:** 22 | 23 | ``` 24 | $ make dummy 25 | ``` 26 | 27 | ## License 28 | 29 | **Letlang**'s source code is distributed under the terms of the 30 | [MIT License](./LICENSE.txt) 31 | 32 | The website's content is distributed under the terms of the 33 | [CC BY NC SA 4.0 License](./www/LICENSE.txt). 34 | -------------------------------------------------------------------------------- /examples/dummy.let: -------------------------------------------------------------------------------- 1 | module dummy::main; 2 | 3 | let pub main: func[() -> @ok] { 4 | () -> @ok, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/hello.let: -------------------------------------------------------------------------------- 1 | module hello::main; 2 | 3 | from std::io import { println }; 4 | 5 | let pub main: func[() -> @ok] { 6 | () -> { 7 | println("hello world"); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /lib/stdio.let: -------------------------------------------------------------------------------- 1 | module std::io; 2 | 3 | let __println: effect[(str) -> @ok]; 4 | 5 | let pub println: func[(str) -> @ok] { 6 | (msg) -> perform __println(msg), 7 | }; 8 | -------------------------------------------------------------------------------- /sources/compiler/backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "letlang-backend" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ast-core = { package = "letlang-ast-core", path = "../../utils/ast-core" } 8 | ast-macros = { package = "letlang-ast-macros", path = "../../utils/ast-macros" } 9 | llfront = { package = "letlang-frontend", path = "../frontend" } 10 | llbmi = { package = "letlang-bmi", path = "../bmi" } 11 | 12 | nonempty = "0.8" 13 | ariadne = "0.3" 14 | 15 | syn = { version = "2.0", features = ["visit"] } 16 | quote = "1.0" 17 | proc-macro2 = { version = "1.0", features = ["span-locations"] } 18 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod prelude; 2 | pub mod steps; 3 | 4 | use proc_macro2::TokenStream; 5 | use quote::quote; 6 | 7 | use self::prelude::*; 8 | use llfront::{AST, SourceLocation}; 9 | use llbmi::BinaryModuleInterface; 10 | 11 | 12 | pub fn compile_lib<'source>( 13 | ast: AST>, 14 | ) -> Result<(BinaryModuleInterface, String)> { 15 | let (root_env, ast) = steps::scope::transform(&ast)?; 16 | let (bmi, code) = steps::codegen::eval(&ast)?; 17 | Ok((bmi, format!("{code}"))) 18 | } 19 | 20 | pub fn compile_exe<'source>( 21 | ast: AST>, 22 | ) -> Result<(BinaryModuleInterface, String)> { 23 | let (root_env, ast) = steps::scope::transform(&ast)?; 24 | let (bmi, code) = steps::codegen::eval(&ast)?; 25 | 26 | let code = quote!{ 27 | #code 28 | 29 | pub fn main() { 30 | use llruntime::*; 31 | let entrypoint = symbol_main::Object::new(); 32 | LLProcessGroup::new(0).run(entrypoint).unwrap(); 33 | } 34 | }; 35 | 36 | Ok((bmi, format!("{code}"))) 37 | } 38 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/prelude/mod.rs: -------------------------------------------------------------------------------- 1 | use ariadne::{Report, ReportKind, Label, Source}; 2 | use llfront::SourceLocation; 3 | 4 | #[derive(Debug, Clone, PartialEq)] 5 | pub enum CompilationErrorKind { 6 | ClauseArrity { expected: usize, got: usize } 7 | } 8 | 9 | #[derive(Debug, Clone, PartialEq)] 10 | pub struct CompilationError<'source> { 11 | pub location: SourceLocation<'source>, 12 | pub kind: CompilationErrorKind, 13 | } 14 | 15 | #[derive(Debug, Clone, PartialEq)] 16 | pub struct OwnedCompilationError { 17 | pub report: String, 18 | } 19 | 20 | pub type Result<'source, T> = std::result::Result>; 21 | 22 | impl std::fmt::Display for CompilationErrorKind { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | match self { 25 | Self::ClauseArrity { expected, got } => { 26 | write!(f, "Invalid clause arity, expected {expected} arguments, got {got}") 27 | } 28 | } 29 | } 30 | } 31 | 32 | impl<'source> std::fmt::Display for CompilationError<'source> { 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 34 | let filename = self.location.filename.display().to_string(); 35 | let code = self.location.code; 36 | let span = self.location.span.clone(); 37 | 38 | let mut message: Vec = vec![]; 39 | 40 | Report::build(ReportKind::Error, &filename, span.start) 41 | .with_code(200) 42 | .with_label(Label::new((&filename, span))) 43 | .with_message(format!("Compilation Error: {}", self.kind)) 44 | .finish() 45 | .write((&filename, Source::from(code)), &mut message) 46 | .expect("unable to write error report"); 47 | 48 | let message = String::from_utf8_lossy(&message).into_owned(); 49 | 50 | write!(f, "{}", message) 51 | } 52 | } 53 | 54 | impl<'source> std::fmt::Display for OwnedCompilationError { 55 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 56 | write!(f, "{}", self.report) 57 | } 58 | } 59 | 60 | impl<'source> std::error::Error for CompilationError<'source> {} 61 | impl std::error::Error for OwnedCompilationError {} 62 | 63 | impl<'source> CompilationError<'source> { 64 | pub fn to_owned(&self) -> OwnedCompilationError { 65 | OwnedCompilationError { report: format!("{}", self) } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/codegen/context/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::steps::scope::SymbolKind; 2 | 3 | pub struct CodeGenerator { 4 | pub bmi_modpath: Vec, 5 | pub bmi_syms: Vec<(String, SymbolKind)>, 6 | } 7 | 8 | impl CodeGenerator { 9 | pub fn new() -> Self { 10 | Self { 11 | bmi_modpath: vec![], 12 | bmi_syms: vec![], 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use proc_macro2::TokenStream; 3 | 4 | use ast_core::*; 5 | use llfront::{ast::*, SourceLocation}; 6 | 7 | use crate::{ 8 | prelude::*, 9 | steps::scope::{EnvRef, SymbolKind}, 10 | }; 11 | 12 | mod context; 13 | mod model; 14 | 15 | use self::{ 16 | context::CodeGenerator, 17 | model::*, 18 | }; 19 | 20 | pub fn eval<'source>( 21 | /* bmi */ 22 | ast: &AST<(EnvRef, SourceLocation<'source>)> 23 | ) -> Result<'source, (llbmi::BinaryModuleInterface, TokenStream)> { 24 | let mut context = CodeGenerator::new(/* bmi */); 25 | let (crate_name, code) = context.visit(&ast.0, ())?; 26 | 27 | let bmi = llbmi::BinaryModuleInterface { 28 | crate_name, 29 | module: NonEmpty::from_vec(context.bmi_modpath).unwrap(), 30 | symbols: context.bmi_syms.into_iter().map(|(name, sym)| { 31 | match sym { 32 | SymbolKind::Class { type_arity, .. } => { 33 | llbmi::Symbol::Class { name, type_arity } 34 | }, 35 | SymbolKind::Function { type_arity, call_arity } => { 36 | llbmi::Symbol::Function { name, type_arity, call_arity } 37 | }, 38 | SymbolKind::Effect { type_arity, call_arity } => { 39 | llbmi::Symbol::Effect { name, type_arity, call_arity } 40 | }, 41 | _ => { 42 | unreachable!() 43 | }, 44 | } 45 | }).collect(), 46 | }; 47 | 48 | Ok((bmi, code)) 49 | } 50 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/codegen/model/decls/mod.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | use proc_macro2::TokenStream; 4 | use quote::{quote, format_ident}; 5 | 6 | use llfront::{ast::*, SourceLocation}; 7 | use crate::{ 8 | steps::codegen::CodeGenerator, 9 | steps::scope::{EnvRef, SymbolKind}, 10 | prelude::*, 11 | }; 12 | 13 | 14 | model!{ 15 | impl<'source> Interpreter for CodeGenerator { 16 | type InputData = NamedDeclaration<(EnvRef, SourceLocation<'source>)>; 17 | type InputMeta = (EnvRef, SourceLocation<'source>); 18 | 19 | type Output = TokenStream; 20 | 21 | type Error = CompilationError<'source>; 22 | 23 | visit { 24 | let (env, location) = node.get_meta(); 25 | let data = node.get_data(); 26 | 27 | let sym_name = &data.name.get_data().0; 28 | let sym_kind = { 29 | env.borrow() 30 | .get(sym_name.clone()) 31 | .unwrap() 32 | }; 33 | 34 | if data.public { 35 | self.bmi_syms.push((sym_name.clone(), sym_kind.clone())); 36 | } 37 | 38 | let visibility_tokens = if data.public { 39 | quote!{pub} 40 | } 41 | else { 42 | quote!{} 43 | }; 44 | 45 | let mut type_params_tokens = vec![]; 46 | let mut type_params_names = vec![]; 47 | 48 | for type_param_node in data.type_params.iter() { 49 | let name = type_param_node.get_data().0.clone(); 50 | let tokens = quote!{#name : LLClassInstance,}; 51 | type_params_tokens.push(tokens); 52 | 53 | let tokens = quote!{#name,}; 54 | type_params_names.push(tokens); 55 | } 56 | 57 | let decl_tokens = visit!(&data.declaration, sym_kind.clone()); 58 | 59 | let sym_mod_name = format_ident!("symbol_{sym_name}"); 60 | let sym_mod_tokens = quote!{ 61 | #[allow(non_snake_case)] 62 | #visibility_tokens mod #sym_mod_name { 63 | extern crate llruntime; 64 | use llruntime::*; 65 | #[allow(unused_imports)] 66 | use super::*; 67 | 68 | pub struct Object { 69 | #(#type_params_tokens)* 70 | } 71 | 72 | impl Object { 73 | pub fn new(#(#type_params_names)*) -> Box { 74 | Box::new(Self { #(#type_params_names)* }) 75 | } 76 | } 77 | 78 | #decl_tokens 79 | } 80 | }; 81 | 82 | let (loc_start, loc_end) = (location.span.start, location.span.end); 83 | let tokens = quote!{ 84 | sourcemap_begin!(#loc_start, #loc_end); 85 | #sym_mod_tokens 86 | sourcemap_end!(#loc_start, #loc_end); 87 | }; 88 | 89 | Ok(tokens) 90 | } 91 | } 92 | } 93 | 94 | model!{ 95 | impl<'source> Interpreter for CodeGenerator { 96 | type InputData = Declaration<(EnvRef, SourceLocation<'source>)>; 97 | type InputMeta = (EnvRef, SourceLocation<'source>); 98 | type InputExtra = SymbolKind; 99 | 100 | type Output = TokenStream; 101 | 102 | type Error = CompilationError<'source>; 103 | 104 | visit { 105 | let (env, location) = node.get_meta(); 106 | let data = node.get_data(); 107 | let sym_kind = extra; 108 | 109 | let tokens = match data { 110 | Declaration::Class(class) => { 111 | todo!("codegen class"); 112 | }, 113 | Declaration::Effect(effect) => { 114 | todo!("codegen effect"); 115 | }, 116 | Declaration::Function(func) => { 117 | let (type_arity, call_arity) = match sym_kind { 118 | SymbolKind::Function { type_arity, call_arity } => (type_arity, call_arity), 119 | _ => unreachable!(), 120 | }; 121 | 122 | quote!{ 123 | impl LLFunction for Object { 124 | fn call( 125 | &self, 126 | ctx: LLProcessContext, 127 | type_params: Vec, 128 | call_params: Vec, 129 | ) -> LLContinuation { 130 | async fn code_block( 131 | co: LLCoroutine, 132 | ctx: LLProcessContext, 133 | type_params: Vec, 134 | call_params: Vec, 135 | ) -> LLValue { 136 | ctx.assert_type_arity(&co, #type_arity, type_params.len()).await; 137 | ctx.assert_func_arity(&co, #call_arity, call_params.len()).await; 138 | 139 | todo!("function body") 140 | } 141 | 142 | LLContinuation::new_boxed( 143 | |co| code_block(co, ctx, type_params, call_params) 144 | ) 145 | } 146 | } 147 | } 148 | }, 149 | }; 150 | 151 | let (loc_start, loc_end) = (location.span.start, location.span.end); 152 | Ok(quote!{ 153 | sourcemap_begin!(#loc_start, #loc_end); 154 | #tokens 155 | sourcemap_end!(#loc_start, #loc_end); 156 | }) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/codegen/model/import.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | use llfront::{ast::*, SourceLocation}; 7 | use crate::{ 8 | steps::codegen::CodeGenerator, 9 | steps::scope::{EnvRef, SymbolKind}, 10 | prelude::*, 11 | }; 12 | 13 | 14 | model!{ 15 | impl<'source> Interpreter for CodeGenerator { 16 | type InputData = Import<(EnvRef, SourceLocation<'source>)>; 17 | type InputMeta = (EnvRef, SourceLocation<'source>); 18 | 19 | type Output = TokenStream; 20 | 21 | type Error = CompilationError<'source>; 22 | 23 | visit { 24 | todo!("codegen import"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/codegen/model/mod.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | 6 | use llfront::{ast::*, SourceLocation}; 7 | use crate::{ 8 | steps::codegen::CodeGenerator, 9 | steps::scope::EnvRef, 10 | prelude::*, 11 | }; 12 | 13 | mod import; 14 | mod decls; 15 | 16 | pub use self::{ 17 | import::*, 18 | decls::*, 19 | }; 20 | 21 | model!{ 22 | impl<'source> Interpreter for CodeGenerator { 23 | type InputData = Module<(EnvRef, SourceLocation<'source>)>; 24 | type InputMeta = (EnvRef, SourceLocation<'source>); 25 | 26 | type Output = (String, TokenStream); 27 | 28 | type Error = CompilationError<'source>; 29 | 30 | visit { 31 | let (_, location) = node.get_meta(); 32 | 33 | let data = node.get_data(); 34 | 35 | self.bmi_modpath = data.path.iter() 36 | .map(|ident_node| ident_node.get_data().0.clone()) 37 | .collect::>(); 38 | 39 | let mod_path = self.bmi_modpath.join("_"); 40 | let crate_name = format!("lldep_{mod_path}"); 41 | 42 | let imports_tokens = visit_many!(data.imports); 43 | let decls_tokens = visit_many!(data.declarations); 44 | 45 | let (loc_start, loc_end) = (location.span.start, location.span.end); 46 | let tokens = quote!{ 47 | macro_rules! sourcemap_begin { 48 | ($start:expr, $end:expr) => {}; 49 | } 50 | macro_rules! sourcemap_end { 51 | ($start:expr, $end:expr) => {}; 52 | } 53 | 54 | sourcemap_begin!(#loc_start, #loc_end); 55 | #(#imports_tokens)* 56 | #(#decls_tokens)* 57 | sourcemap_end!(#loc_start, #loc_end); 58 | }; 59 | 60 | Ok((crate_name, tokens)) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod scope; 2 | pub mod codegen; 3 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/context/env.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | rc::Rc, 4 | cell::RefCell, 5 | }; 6 | 7 | use super::symbol::SymbolKind; 8 | 9 | pub type EnvRef = Rc>; 10 | 11 | pub struct Environment { 12 | locals: HashMap, 13 | parent: Option, 14 | } 15 | 16 | impl Environment { 17 | pub fn new(parent: Option) -> EnvRef { 18 | Rc::new(RefCell::new(Self { 19 | locals: HashMap::new(), 20 | parent, 21 | })) 22 | } 23 | 24 | pub fn lookup(&self, symbol_name: String) -> Option { 25 | match (self.locals.get(&symbol_name), &self.parent) { 26 | (Some(symbol_kind), _) => Some(symbol_kind.clone()), 27 | (None, Some(env)) => env.borrow().lookup(symbol_name), 28 | (None, None) => None, 29 | } 30 | } 31 | 32 | pub fn get(&self, symbol_name: String) -> Option { 33 | self.locals.get(&symbol_name).map(|sym| sym.clone()) 34 | } 35 | 36 | pub fn insert(&mut self, symbol_name: String, symbol_kind: SymbolKind) { 37 | self.locals.insert(symbol_name, symbol_kind); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/context/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod symbol; 2 | pub mod env; 3 | pub mod stack; 4 | 5 | pub use self::{env::EnvRef, symbol::SymbolKind}; 6 | use self::stack::EnvironmentStack; 7 | 8 | pub struct ScopeBuilder { 9 | pub(crate) env_stack: EnvironmentStack, 10 | } 11 | 12 | impl ScopeBuilder { 13 | pub fn new(/* bmi */) -> Self { 14 | let mut env_stack = EnvironmentStack::new(); 15 | env_stack.enter_scope(); 16 | // TODO: add bmi 17 | Self { env_stack } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/context/stack.rs: -------------------------------------------------------------------------------- 1 | use super::env::{Environment, EnvRef}; 2 | 3 | pub struct EnvironmentStack { 4 | stack: Vec, 5 | } 6 | 7 | impl EnvironmentStack { 8 | pub fn new() -> Self { 9 | Self { stack: vec![] } 10 | } 11 | 12 | pub fn enter_scope(&mut self) { 13 | let parent = self.stack.last().map(|e| e.clone()); 14 | let new_scope = Environment::new(parent); 15 | self.stack.push(new_scope); 16 | } 17 | 18 | pub fn get_scope(&self) -> EnvRef { 19 | self.stack.last().map(|e| e.clone()).expect("expected at least one env") 20 | } 21 | 22 | pub fn leave_scope(&mut self) { 23 | self.stack.pop(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/context/symbol.rs: -------------------------------------------------------------------------------- 1 | use super::EnvRef; 2 | 3 | #[derive(Clone)] 4 | pub enum SymbolKind { 5 | Module { 6 | env: EnvRef, 7 | }, 8 | Class { 9 | type_arity: usize, 10 | local_index: Option, 11 | }, 12 | Effect { 13 | type_arity: usize, 14 | call_arity: usize, 15 | }, 16 | Function { 17 | type_arity: usize, 18 | call_arity: usize, 19 | }, 20 | LocalVariable, 21 | } 22 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/mod.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use llfront::{ast::*, SourceLocation}; 3 | 4 | use crate::prelude::*; 5 | 6 | mod context; 7 | mod model; 8 | 9 | use self::context::ScopeBuilder; 10 | pub use self::context::{EnvRef, SymbolKind}; 11 | 12 | pub fn transform<'source>( 13 | /* bmi */ 14 | ast: &AST> 15 | ) -> Result<'source, (EnvRef, AST<(EnvRef, SourceLocation<'source>)>)> { 16 | let mut context = ScopeBuilder::new(/* bmi */); 17 | let root_env = context.env_stack.get_scope(); 18 | 19 | let node = context.visit(&ast.0, ())?; 20 | 21 | Ok((root_env.clone(), AST(node))) 22 | } 23 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/model/decls/mod.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use ast_core::*; 3 | use ast_macros::*; 4 | 5 | use llfront::{ast::*, SourceLocation}; 6 | use crate::{ 7 | steps::scope::context::{ScopeBuilder, EnvRef, SymbolKind}, 8 | prelude::* 9 | }; 10 | 11 | model!{ 12 | impl<'source> Transformer for ScopeBuilder { 13 | type InputData = NamedDeclaration>; 14 | type InputMeta = SourceLocation<'source>; 15 | 16 | type OutputData = NamedDeclaration<(EnvRef, SourceLocation<'source>)>; 17 | type OutputMeta = (EnvRef, SourceLocation<'source>); 18 | 19 | type Error = CompilationError<'source>; 20 | 21 | visit { 22 | let env = self.env_stack.get_scope(); 23 | 24 | let data = node.get_data(); 25 | let sym_kind = match data.declaration.get_data() { 26 | Declaration::Class(_) => SymbolKind::Class { 27 | type_arity: data.type_params.len(), 28 | local_index: None, 29 | }, 30 | Declaration::Effect(effect) => SymbolKind::Effect { 31 | type_arity: data.type_params.len(), 32 | call_arity: effect.params.len(), 33 | }, 34 | Declaration::Function(func) => SymbolKind::Function { 35 | type_arity: data.type_params.len(), 36 | call_arity: func.params.len(), 37 | }, 38 | }; 39 | 40 | env.borrow_mut().insert(data.name.get_data().0.clone(), sym_kind); 41 | 42 | let public = data.public; 43 | let name = visit!(&data.name); 44 | let type_params = visit_many!(data.type_params); 45 | 46 | self.env_stack.enter_scope(); 47 | let decl_scope = self.env_stack.get_scope(); 48 | 49 | for (idx, type_param) in data.type_params.iter().enumerate() { 50 | decl_scope.borrow_mut().insert( 51 | type_param.get_data().0.clone(), 52 | SymbolKind::Class { type_arity: 0, local_index: Some(idx) }, 53 | ); 54 | } 55 | 56 | let declaration = visit!(&data.declaration); 57 | self.env_stack.leave_scope(); 58 | 59 | Ok(Node::new( 60 | (env.clone(), node.get_meta().clone()), 61 | NamedDeclaration { public, name, type_params, declaration }, 62 | )) 63 | } 64 | } 65 | } 66 | 67 | 68 | model!{ 69 | impl<'source> Transformer for ScopeBuilder { 70 | type InputData = Declaration>; 71 | type InputMeta = SourceLocation<'source>; 72 | 73 | type OutputData = Declaration<(EnvRef, SourceLocation<'source>)>; 74 | type OutputMeta = (EnvRef, SourceLocation<'source>); 75 | 76 | type Error = CompilationError<'source>; 77 | 78 | visit { 79 | let env = self.env_stack.get_scope(); 80 | 81 | let data = match node.get_data() { 82 | Declaration::Class(_) => { 83 | todo!("decl class"); 84 | }, 85 | Declaration::Effect(_) => { 86 | todo!("decl effect"); 87 | }, 88 | Declaration::Function(func) => { 89 | let params = visit_many!(func.params); 90 | let return_type = visit!(&func.return_type); 91 | 92 | let func_arity = params.len(); 93 | 94 | let mut clauses = vec![]; 95 | 96 | for clause_node in func.clauses.iter() { 97 | self.env_stack.enter_scope(); 98 | 99 | let clause_arity = clause_node.get_data().patterns.len(); 100 | if clause_arity != func_arity { 101 | return Err(CompilationError { 102 | location: clause_node.get_meta().clone(), 103 | kind: CompilationErrorKind::ClauseArrity { 104 | expected: func_arity, 105 | got: clause_arity, 106 | }, 107 | }); 108 | } 109 | 110 | let clause = visit!(clause_node); 111 | self.env_stack.leave_scope(); 112 | clauses.push(clause); 113 | } 114 | 115 | let clauses = NonEmpty::from_vec(clauses).unwrap(); 116 | 117 | Declaration::Function(Function { 118 | tailrec: func.tailrec, 119 | params, 120 | return_type, 121 | clauses, 122 | }) 123 | }, 124 | }; 125 | 126 | Ok(Node::new( 127 | (env.clone(), node.get_meta().clone()), 128 | data, 129 | )) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/model/expr/lit.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | 4 | use llfront::{ast::*, SourceLocation}; 5 | use crate::{ 6 | steps::scope::context::{ScopeBuilder, EnvRef}, 7 | prelude::* 8 | }; 9 | 10 | model!{ 11 | impl<'source> Transformer for ScopeBuilder { 12 | type InputData = Literal; 13 | type InputMeta = SourceLocation<'source>; 14 | 15 | type OutputData = Literal; 16 | type OutputMeta = (EnvRef, SourceLocation<'source>); 17 | 18 | type Error = CompilationError<'source>; 19 | 20 | visit { 21 | let env = self.env_stack.get_scope(); 22 | 23 | Ok(Node::new( 24 | (env.clone(), node.get_meta().clone()), 25 | node.get_data().clone(), 26 | )) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/model/expr/mod.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | 4 | use llfront::{ast::*, SourceLocation}; 5 | use crate::{ 6 | steps::scope::context::{ScopeBuilder, EnvRef, SymbolKind}, 7 | prelude::* 8 | }; 9 | 10 | mod lit; 11 | 12 | pub use self::{ 13 | lit::*, 14 | }; 15 | 16 | model!{ 17 | impl<'source> Transformer for ScopeBuilder { 18 | type InputData = Expression>; 19 | type InputMeta = SourceLocation<'source>; 20 | 21 | type OutputData = Expression<(EnvRef, SourceLocation<'source>)>; 22 | type OutputMeta = (EnvRef, SourceLocation<'source>); 23 | 24 | type Error = CompilationError<'source>; 25 | 26 | visit { 27 | let env = self.env_stack.get_scope(); 28 | 29 | let data = match node.get_data() { 30 | Expression::Literal(lit_node) => Expression::Literal(visit!(lit_node)), 31 | _ => todo!("expr"), 32 | }; 33 | 34 | Ok(Node::new( 35 | (env.clone(), node.get_meta().clone()), 36 | data, 37 | )) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/model/import.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | 4 | use llfront::{ast::*, SourceLocation}; 5 | use crate::{ 6 | steps::scope::context::{ScopeBuilder, EnvRef}, 7 | prelude::* 8 | }; 9 | 10 | model!{ 11 | impl<'source> Transformer for ScopeBuilder { 12 | type InputData = Import>; 13 | type InputMeta = SourceLocation<'source>; 14 | 15 | type OutputData = Import<(EnvRef, SourceLocation<'source>)>; 16 | type OutputMeta = (EnvRef, SourceLocation<'source>); 17 | 18 | type Error = CompilationError<'source>; 19 | 20 | visit { 21 | match node.get_data() { 22 | Import::Symbol { path, symbols } => todo!("import symbols"), 23 | Import::Module { path, alias } => todo!("import alias"), 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/model/mod.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | 4 | use llfront::{ast::*, SourceLocation}; 5 | use crate::{ 6 | steps::scope::context::{ScopeBuilder, EnvRef, SymbolKind}, 7 | prelude::* 8 | }; 9 | 10 | mod import; 11 | mod decls; 12 | mod typ; 13 | mod expr; 14 | mod pat; 15 | 16 | pub use self::{ 17 | import::*, 18 | decls::*, 19 | typ::*, 20 | expr::*, 21 | pat::*, 22 | }; 23 | 24 | model!{ 25 | impl<'source> Transformer for ScopeBuilder { 26 | type InputData = Module>; 27 | type InputMeta = SourceLocation<'source>; 28 | 29 | type OutputData = Module<(EnvRef, SourceLocation<'source>)>; 30 | type OutputMeta = (EnvRef, SourceLocation<'source>); 31 | 32 | type Error = CompilationError<'source>; 33 | 34 | visit { 35 | let root_env = self.env_stack.get_scope(); 36 | 37 | let mut mod_env = root_env; 38 | for path_segment in node.get_data().path.iter() { 39 | self.env_stack.enter_scope(); 40 | let new_env = self.env_stack.get_scope(); 41 | 42 | mod_env.borrow_mut().insert( 43 | path_segment.get_data().0.clone(), 44 | SymbolKind::Module { env: new_env.clone() }, 45 | ); 46 | 47 | mod_env = new_env; 48 | } 49 | 50 | let path = visit_many!(node.get_data().path).try_into().unwrap(); 51 | let imports = visit_many!(node.get_data().imports); 52 | let declarations = visit_many!(node.get_data().declarations); 53 | 54 | for _ in node.get_data().path.iter() { 55 | self.env_stack.leave_scope(); 56 | } 57 | 58 | Ok(Node::new( 59 | (mod_env.clone(), node.get_meta().clone()), 60 | Module { path, imports, declarations }, 61 | )) 62 | } 63 | } 64 | } 65 | 66 | model!{ 67 | impl<'source> Transformer for ScopeBuilder { 68 | type InputData = Identifier; 69 | type InputMeta = SourceLocation<'source>; 70 | 71 | type OutputData = Identifier; 72 | type OutputMeta = (EnvRef, SourceLocation<'source>); 73 | 74 | type Error = CompilationError<'source>; 75 | 76 | visit { 77 | let env = self.env_stack.get_scope(); 78 | 79 | Ok(Node::new( 80 | (env.clone(), node.get_meta().clone()), 81 | node.get_data().clone(), 82 | )) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/model/pat/mod.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | 4 | use llfront::{ast::*, SourceLocation}; 5 | use crate::{ 6 | steps::scope::context::{ScopeBuilder, EnvRef, SymbolKind}, 7 | prelude::* 8 | }; 9 | 10 | model!{ 11 | impl<'source> Transformer for ScopeBuilder { 12 | type InputData = Clause>; 13 | type InputMeta = SourceLocation<'source>; 14 | 15 | type OutputData = Clause<(EnvRef, SourceLocation<'source>)>; 16 | type OutputMeta = (EnvRef, SourceLocation<'source>); 17 | 18 | type Error = CompilationError<'source>; 19 | 20 | visit { 21 | let env = self.env_stack.get_scope(); 22 | 23 | let data = node.get_data(); 24 | 25 | let patterns = visit_many!(data.patterns); 26 | let guard = match &data.guard { 27 | Some(expr) => Some(visit!(expr)), 28 | None => None, 29 | }; 30 | let body = visit_many!(data.body).try_into().unwrap(); 31 | 32 | Ok(Node::new( 33 | (env.clone(), node.get_meta().clone()), 34 | Clause { patterns, guard, body }, 35 | )) 36 | } 37 | } 38 | } 39 | 40 | 41 | model!{ 42 | impl<'source> Transformer for ScopeBuilder { 43 | type InputData = Pattern>; 44 | type InputMeta = SourceLocation<'source>; 45 | 46 | type OutputData = Pattern<(EnvRef, SourceLocation<'source>)>; 47 | type OutputMeta = (EnvRef, SourceLocation<'source>); 48 | 49 | type Error = CompilationError<'source>; 50 | 51 | visit { 52 | todo!("pattern") 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sources/compiler/backend/src/steps/scope/model/typ/mod.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | use ast_macros::*; 3 | 4 | use llfront::{ast::*, SourceLocation}; 5 | use crate::{ 6 | steps::scope::context::{ScopeBuilder, EnvRef, SymbolKind}, 7 | prelude::* 8 | }; 9 | 10 | model!{ 11 | impl<'source> Transformer for ScopeBuilder { 12 | type InputData = Type>; 13 | type InputMeta = SourceLocation<'source>; 14 | 15 | type OutputData = Type<(EnvRef, SourceLocation<'source>)>; 16 | type OutputMeta = (EnvRef, SourceLocation<'source>); 17 | 18 | type Error = CompilationError<'source>; 19 | 20 | visit { 21 | let env = self.env_stack.get_scope(); 22 | 23 | let data = match node.get_data() { 24 | Type::Literal(lit_node) => Type::Literal(visit!(lit_node)), 25 | _ => todo!("type"), 26 | }; 27 | 28 | Ok(Node::new( 29 | (env.clone(), node.get_meta().clone()), 30 | data, 31 | )) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sources/compiler/bmi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "letlang-bmi" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1.0", features=["derive"] } 8 | nonempty = { version = "0.8", features=["serialize"] } 9 | -------------------------------------------------------------------------------- /sources/compiler/bmi/src/lib.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 5 | pub enum Symbol { 6 | Class { 7 | name: String, 8 | type_arity: usize, 9 | }, 10 | Function { 11 | name: String, 12 | type_arity: usize, 13 | call_arity: usize, 14 | }, 15 | Effect { 16 | name: String, 17 | type_arity: usize, 18 | call_arity: usize, 19 | }, 20 | } 21 | 22 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 23 | pub struct BinaryModuleInterface { 24 | pub crate_name: String, 25 | pub module: NonEmpty, 26 | pub symbols: Vec, 27 | } 28 | -------------------------------------------------------------------------------- /sources/compiler/builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "letlang-builder" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | llfront = { package = "letlang-frontend", path = "../frontend" } 8 | llback = { package = "letlang-backend", path = "../backend" } 9 | llbmi = { package = "letlang-bmi", path = "../bmi" } 10 | 11 | serde = { version = "1.0", features=["derive"] } 12 | serde_json = "1.0" 13 | toml = "0.8" 14 | 15 | nonempty = { version = "0.8", features=["serialize"] } 16 | ar = "0.9" 17 | 18 | anyhow = "1.0" 19 | -------------------------------------------------------------------------------- /sources/compiler/builder/src/cargo/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::PathBuf; 3 | use serde::Serialize; 4 | 5 | #[derive(Serialize)] 6 | pub struct CargoManifest { 7 | pub package: CargoPackage, 8 | pub dependencies: HashMap, 9 | pub bin: Vec, 10 | pub workspace: CargoWorkspace, 11 | } 12 | 13 | #[derive(Serialize)] 14 | pub struct CargoWorkspace {} 15 | 16 | #[derive(Serialize)] 17 | pub struct CargoPackage { 18 | pub name: String, 19 | pub version: String, 20 | pub edition: String, 21 | } 22 | 23 | #[derive(Serialize)] 24 | pub struct CargoBin { 25 | pub name: String, 26 | pub path: PathBuf, 27 | } 28 | 29 | #[derive(Serialize)] 30 | #[serde(untagged)] 31 | pub enum CargoDependency { 32 | Local { 33 | package: String, 34 | path: PathBuf, 35 | }, 36 | Remote { 37 | package: String, 38 | version: String, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /sources/compiler/builder/src/context/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | path::PathBuf, 4 | fs::{create_dir_all, File}, 5 | io::{copy, prelude::*}, 6 | process::{Command, Stdio}, 7 | str, 8 | }; 9 | 10 | use anyhow::{Context, Result}; 11 | use ar::Archive; 12 | 13 | use crate::cargo::*; 14 | use llfront::*; 15 | use llbmi::BinaryModuleInterface; 16 | 17 | const ERROR_TARGET_DIR: &str = "Failed to create target directory"; 18 | const ERROR_DEP_ARCHIVE: &str = "Failed to extract dependency archive"; 19 | const ERROR_BUILD_ARTIFACT: &str = "Failed to build artifact"; 20 | const ERROR_WRITE_MANIFEST: &str = "Failed to create artifact's manifest"; 21 | const ERROR_WRITE_SOURCE: &str = "Failed to write generated code"; 22 | const ERROR_OPEN_EXE: &str = "Failed to open generated executable"; 23 | const ERROR_OPEN_OUTPUT: &str = "Failed to open output path"; 24 | const ERROR_WRITE_EXE: &str = "Failed to copy generated executable"; 25 | 26 | #[derive(PartialEq)] 27 | pub enum BuildType { 28 | Lib, 29 | Exe, 30 | } 31 | 32 | pub struct BuildContext { 33 | pub build_type: BuildType, 34 | pub target_path: PathBuf, 35 | pub runtime_path: PathBuf, 36 | pub input: PathBuf, 37 | pub output: PathBuf, 38 | pub dependencies: Vec, 39 | } 40 | 41 | pub struct BuildArtifact { 42 | pub bmi: BinaryModuleInterface, 43 | pub code: String, 44 | } 45 | 46 | impl BuildContext { 47 | pub fn extract_dependencies(&self) -> Result> { 48 | let mut res = Vec::new(); 49 | 50 | create_dir_all(&self.target_path).context(ERROR_TARGET_DIR)?; 51 | 52 | for dependency in self.dependencies.iter() { 53 | let depfile = File::open(dependency).context(ERROR_DEP_ARCHIVE)?; 54 | let mut depar = Archive::new(depfile); 55 | 56 | while let Some(entry) = depar.next_entry() { 57 | let mut entry = entry.context(ERROR_DEP_ARCHIVE)?; 58 | let filename = str::from_utf8(entry.header().identifier()).context(ERROR_DEP_ARCHIVE)?; 59 | let filepath = self.target_path.join(filename); 60 | let mut file = File::create(&filepath).context(ERROR_DEP_ARCHIVE)?; 61 | copy(&mut entry, &mut file).context(ERROR_DEP_ARCHIVE)?; 62 | 63 | if filepath.extension() == Some("bmi".as_ref()) { 64 | let file = File::open(&filepath).context(ERROR_DEP_ARCHIVE)?; 65 | let bmi = serde_json::from_reader(file).context(ERROR_DEP_ARCHIVE)?; 66 | res.push(bmi); 67 | } 68 | } 69 | } 70 | 71 | Ok(res) 72 | } 73 | 74 | pub fn build(&self, _deps: &[BinaryModuleInterface]) -> Result { 75 | let src_file = &SourceFile::from_file(&self.input).context(ERROR_BUILD_ARTIFACT)?; 76 | 77 | let ast: AST = src_file.try_into() 78 | .map_err(|e: SyntaxError| e.to_owned()) 79 | .context(ERROR_BUILD_ARTIFACT)?; 80 | 81 | let res = match self.build_type { 82 | BuildType::Lib => llback::compile_lib(ast), 83 | BuildType::Exe => llback::compile_exe(ast), 84 | }; 85 | 86 | let (bmi, code) = res 87 | .map_err(|e: llback::prelude::CompilationError| e.to_owned()) 88 | .context(ERROR_BUILD_ARTIFACT)?; 89 | 90 | Ok(BuildArtifact { bmi, code }) 91 | } 92 | 93 | pub fn write( 94 | &self, 95 | artifact: BuildArtifact, 96 | deps: &[BinaryModuleInterface], 97 | ) -> Result<()> { 98 | let crate_path = self.target_path.join(&artifact.bmi.crate_name); 99 | create_dir_all(&crate_path).context(ERROR_TARGET_DIR)?; 100 | 101 | // Create crate's Cargo.toml 102 | let mut manifest = CargoManifest { 103 | package: CargoPackage { 104 | name: artifact.bmi.crate_name.clone(), 105 | version: "0.1.0".to_string(), 106 | edition: "2021".to_string(), 107 | }, 108 | dependencies: HashMap::new(), 109 | bin: Vec::new(), 110 | workspace: CargoWorkspace {}, 111 | }; 112 | 113 | for dep in deps.iter() { 114 | manifest.dependencies.insert( 115 | dep.crate_name.clone(), 116 | CargoDependency::Local { 117 | package: dep.crate_name.clone(), 118 | path: self.target_path.join(&dep.crate_name), 119 | } 120 | ); 121 | } 122 | 123 | manifest.dependencies.insert( 124 | "llruntime".to_string(), 125 | CargoDependency::Local { 126 | package: "letlang-runtime".to_string(), 127 | path: self.runtime_path.clone(), 128 | } 129 | ); 130 | 131 | if self.build_type == BuildType::Exe { 132 | manifest.bin.push(CargoBin { 133 | name: { 134 | self.output.file_name() 135 | .expect("output should have a filename") 136 | .to_string_lossy() 137 | .to_string() 138 | }, 139 | path: PathBuf::from("src/main.rs"), 140 | }); 141 | } 142 | 143 | { 144 | let manifest_path = crate_path.join("Cargo.toml"); 145 | let mut manifest_file = File::create(&manifest_path).context(ERROR_WRITE_MANIFEST)?; 146 | let manifest_data = toml::to_string(&manifest).context(ERROR_WRITE_MANIFEST)?; 147 | manifest_file.write_all(manifest_data.as_bytes()).context(ERROR_WRITE_MANIFEST)?; 148 | } 149 | 150 | // Write code to source file 151 | let src_path = crate_path.join("src"); 152 | create_dir_all(&src_path).context(ERROR_WRITE_SOURCE)?; 153 | 154 | let source_path = match self.build_type { 155 | BuildType::Lib => src_path.join("lib.rs"), 156 | BuildType::Exe => src_path.join("main.rs"), 157 | }; 158 | 159 | { 160 | let mut source_file = File::create(&source_path).context(ERROR_WRITE_SOURCE)?; 161 | source_file.write_all(artifact.code.as_bytes()).context(ERROR_WRITE_SOURCE)?; 162 | } 163 | 164 | // Format source code 165 | Command::new("rustfmt") 166 | .arg("--edition=2021") 167 | .arg(&source_path) 168 | .stdout(Stdio::null()) 169 | .stderr(Stdio::null()) 170 | .status().context(ERROR_WRITE_SOURCE)? 171 | .exit_ok().context(ERROR_WRITE_SOURCE)?; 172 | 173 | // Call cargo build 174 | Command::new("cargo") 175 | .arg("build") 176 | .arg("--release") 177 | .current_dir(&crate_path) 178 | .status().context(ERROR_BUILD_ARTIFACT)? 179 | .exit_ok().context(ERROR_BUILD_ARTIFACT)?; 180 | 181 | match self.build_type { 182 | BuildType::Lib => { 183 | todo!("write lib") 184 | }, 185 | BuildType::Exe => { 186 | let mut src_file = { 187 | let exe_dir = crate_path.join("target").join("release"); 188 | let exe_name = self.output.file_name() 189 | .expect("output should have a filename"); 190 | let exe_path = exe_dir.join(exe_name).with_extension(std::env::consts::EXE_EXTENSION); 191 | 192 | File::open(&exe_path).context(ERROR_OPEN_EXE)? 193 | }; 194 | let mut tgt_file = { 195 | let exe_path = self.output.with_extension(std::env::consts::EXE_EXTENSION); 196 | File::create(&exe_path).context(ERROR_OPEN_OUTPUT)? 197 | }; 198 | 199 | copy(&mut src_file, &mut tgt_file).context(ERROR_WRITE_EXE)?; 200 | }, 201 | } 202 | 203 | Ok(()) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /sources/compiler/builder/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(exit_status_error)] 2 | 3 | pub mod cargo; 4 | pub mod context; 5 | -------------------------------------------------------------------------------- /sources/compiler/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "letlang-cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "letlangc" 8 | path = "src/main.rs" 9 | 10 | [dependencies] 11 | llfront = { package = "letlang-frontend", path = "../frontend" } 12 | llback = { package = "letlang-backend", path = "../backend" } 13 | llbuild = { package = "letlang-builder", path = "../builder" } 14 | 15 | clap = { version = "4.4", features = ["derive"] } 16 | anyhow = "1.0" 17 | -------------------------------------------------------------------------------- /sources/compiler/cli/src/args.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::OsString, 3 | path::{absolute, PathBuf}, 4 | fs::canonicalize, 5 | }; 6 | 7 | use clap::{Parser, ValueEnum, ValueHint}; 8 | use anyhow::{bail, Result}; 9 | 10 | use llbuild::context::{BuildType, BuildContext}; 11 | 12 | trait ToAbsolute { 13 | fn as_absolute(&self) -> Self; 14 | fn as_canonical(&self) -> Self; 15 | } 16 | 17 | impl ToAbsolute for PathBuf { 18 | fn as_absolute(&self) -> PathBuf { 19 | match absolute(&self) { 20 | Ok(path) => path, 21 | Err(err) => panic!("ERROR: Could not absolutize path: {}\n{:?}", self.display(), err), 22 | } 23 | } 24 | 25 | fn as_canonical(&self) -> PathBuf { 26 | match canonicalize(&self) { 27 | Ok(path) => path, 28 | Err(err) => panic!("ERROR: Could not canonicalize path: {}\n{:?}", self.display(), err), 29 | } 30 | } 31 | } 32 | 33 | #[derive(ValueEnum, Debug, Clone)] 34 | pub enum UnitType { 35 | Lib, 36 | Exe, 37 | } 38 | 39 | impl From for BuildType { 40 | fn from(value: UnitType) -> Self { 41 | match value { 42 | UnitType::Lib => BuildType::Lib, 43 | UnitType::Exe => BuildType::Exe, 44 | } 45 | } 46 | } 47 | 48 | #[derive(Parser, Debug)] 49 | #[command(author, version, about, long_about = None)] 50 | pub struct Args { 51 | /// Type of build ("lib" to build a static library, "exe" to build an executable) 52 | #[arg(short = 't', long = "type", value_enum, default_value = "lib")] 53 | unit_type: UnitType, 54 | 55 | /// Path to Letlang source code 56 | #[arg(value_name = "FILE", value_hint = ValueHint::FilePath)] 57 | input: PathBuf, 58 | 59 | /// Path to build artifact 60 | #[arg(short, value_name = "FILE")] 61 | output: Option, 62 | 63 | /// Path to store extracted dependencies 64 | #[arg(short = 'b', value_name = "DIR", value_hint = ValueHint::DirPath)] 65 | build_dir: Option, 66 | 67 | /// Path to the Letlang runtime Cargo project 68 | #[arg(long = "rpath", value_name = "DIR", value_hint = ValueHint::DirPath)] 69 | runtime_path: PathBuf, 70 | 71 | /// Add folder to library paths, this is where the compiler will look for dependencies 72 | #[arg(short = 'L', value_name = "DIR", value_hint = ValueHint::DirPath)] 73 | library_path: Vec, 74 | 75 | /// Add a dependency 76 | #[arg(short = 'l', value_name = "LIBNAME")] 77 | link_lib: Vec, 78 | } 79 | 80 | impl Args { 81 | pub fn to_build_context(self) -> Result { 82 | let input = self.input.as_canonical(); 83 | let output = self.output.unwrap_or_else(|| { 84 | let unit_name = input.file_name().expect("unable to get input filename"); 85 | 86 | match self.unit_type { 87 | UnitType::Lib => { 88 | let mut name = OsString::from("lib"); 89 | name.push(unit_name); 90 | PathBuf::from(name).with_extension("lla") 91 | }, 92 | UnitType::Exe => { 93 | PathBuf::from(unit_name).with_extension("") 94 | }, 95 | } 96 | }).as_absolute(); 97 | 98 | let build_dir = self.build_dir 99 | .unwrap_or_else(|| PathBuf::from(".lltarget")) 100 | .as_absolute(); 101 | 102 | let runtime_path = self.runtime_path.as_canonical(); 103 | 104 | let library_paths: Vec = self.library_path 105 | .into_iter() 106 | .map(|p| p.as_canonical()) 107 | .collect(); 108 | 109 | let dependency_names = self.link_lib; 110 | 111 | if !input.exists() { 112 | bail!("No such file or directory: {}", input.display()); 113 | } 114 | else if !input.is_file() { 115 | bail!("Expected a file at: {}", input.display()); 116 | } 117 | 118 | for library_path in library_paths.iter() { 119 | if !library_path.exists() { 120 | bail!("No such file or directory: {}", library_path.display()); 121 | } 122 | else if !library_path.is_dir() { 123 | bail!("Expected a directory at: {}", library_path.display()); 124 | } 125 | } 126 | 127 | let mut dependency_paths = Vec::with_capacity(dependency_names.len()); 128 | 129 | for dependency_name in dependency_names.iter() { 130 | let libname = format!("lib{dependency_name}.lla"); 131 | 132 | let deppaths: Vec = library_paths 133 | .iter() 134 | .map(|libdir| libdir.join(&libname)) 135 | .filter(|p| p.is_file()) 136 | .collect(); 137 | 138 | match deppaths.as_slice() { 139 | [] => { 140 | bail!("Could not find {libname} in any library path."); 141 | }, 142 | [deppath] => { 143 | dependency_paths.push(deppath.clone()) 144 | }, 145 | _ => { 146 | bail!("Multiple {libname} were found in library paths."); 147 | }, 148 | }; 149 | } 150 | 151 | Ok(BuildContext { 152 | build_type: self.unit_type.into(), 153 | target_path: build_dir, 154 | runtime_path, 155 | input, 156 | output, 157 | dependencies: dependency_paths, 158 | }) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /sources/compiler/cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(absolute_path)] 2 | pub mod prelude; 3 | pub mod args; 4 | -------------------------------------------------------------------------------- /sources/compiler/cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use letlang_cli::args::Args; 2 | 3 | use clap::Parser; 4 | use anyhow::Result; 5 | 6 | fn main() { 7 | let args = Args::parse(); 8 | 9 | if let Err(err) = build(args) { 10 | eprintln!("{err}"); 11 | std::process::exit(1); 12 | } 13 | } 14 | 15 | fn build(args: Args) -> Result<()> { 16 | let ctx = args.to_build_context()?; 17 | let deps = ctx.extract_dependencies()?; 18 | let artifact = ctx.build(&deps)?; 19 | ctx.write(artifact, &deps)?; 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /sources/compiler/cli/src/prelude.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::Error as IOError, 3 | str::Utf8Error, 4 | }; 5 | 6 | pub type Result = std::result::Result; 7 | 8 | pub struct CliError { 9 | pub message: String, 10 | } 11 | 12 | impl From for CliError { 13 | fn from(value: IOError) -> Self { 14 | Self { message: format!("{value}") } 15 | } 16 | } 17 | 18 | impl From for CliError { 19 | fn from(value: Utf8Error) -> Self { 20 | Self { message: format!("{value}") } 21 | } 22 | } 23 | 24 | impl From for CliError { 25 | fn from(value: llfront::OwnedSyntaxError) -> Self { 26 | Self { message: value.report } 27 | } 28 | } 29 | 30 | impl From> for CliError { 31 | fn from(value: Box) -> Self { 32 | Self { message: format!("{value}") } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sources/compiler/frontend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "letlang-frontend" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ast-core = { package = "letlang-ast-core", path = "../../utils/ast-core" } 8 | 9 | nonempty = "0.8" 10 | ariadne = "0.3" 11 | 12 | logos = "0.13" 13 | parse_int = "0.6" 14 | snailquote = "0.3" 15 | 16 | peg = "0.8" 17 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/decls/class.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | 3 | use crate::ast::{Type, Clause}; 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub struct Class { 7 | pub cons_param: Node, M>, 8 | pub clauses: Vec, M>>, 9 | } 10 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/decls/effect.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | 3 | use crate::ast::Type; 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub struct Effect { 7 | pub params: Vec, M>>, 8 | pub return_type: Node, M>, 9 | } 10 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/decls/function.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use ast_core::*; 3 | 4 | use crate::ast::{Type, Clause}; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub struct Function { 8 | pub tailrec: bool, 9 | pub params: Vec, M>>, 10 | pub return_type: Node, M>, 11 | pub clauses: NonEmpty, M>>, 12 | } 13 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/decls/mod.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | 3 | use crate::ast::Identifier; 4 | 5 | mod class; 6 | mod effect; 7 | mod function; 8 | 9 | pub use self::{ 10 | class::*, 11 | effect::*, 12 | function::*, 13 | }; 14 | 15 | #[derive(Debug, Clone, PartialEq)] 16 | pub struct NamedDeclaration { 17 | pub public: bool, 18 | pub name: Node, 19 | pub type_params: Vec>, 20 | pub declaration: Node, M>, 21 | } 22 | 23 | #[derive(Debug, Clone, PartialEq)] 24 | pub enum Declaration { 25 | Class(Class), 26 | Effect(Effect), 27 | Function(Function), 28 | } 29 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/expr/list.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | 3 | use crate::ast::Expression; 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub enum ListItem { 7 | Value(Node, M>), 8 | Expansion(Node, M>), 9 | } 10 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/expr/lit.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq)] 2 | pub enum Literal { 3 | Atom(String), 4 | Boolean(bool), 5 | Number(f64), 6 | String(String), 7 | } 8 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/expr/mod.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use ast_core::*; 3 | 4 | use crate::ast::{Identifier, Type, Pattern, Clause}; 5 | 6 | mod lit; 7 | mod tuple; 8 | mod list; 9 | mod ops; 10 | 11 | pub use self::{ 12 | lit::*, 13 | tuple::*, 14 | list::*, 15 | ops::*, 16 | }; 17 | 18 | #[derive(Debug, Clone, PartialEq)] 19 | pub enum Expression { 20 | Variable(Node), 21 | Literal(Node), 22 | Tuple(Vec, M>>), 23 | NamedTuple(Vec, M>>), 24 | List(Vec, M>>), 25 | ListHeadTail { 26 | heads: Vec, M>>, 27 | tail: Node, M>, 28 | }, 29 | UnaryOperation(UnaryOperator, Node, M>), 30 | BinaryOperation { 31 | lhs: Node, M>, 32 | op: BinaryOperator, 33 | rhs: Node, M>, 34 | }, 35 | MemberAccess { 36 | lhs: Node, M>, 37 | rhs: Node, 38 | }, 39 | TypeCheck { 40 | lhs: Node, M>, 41 | rhs: Node, M>, 42 | negated: bool, 43 | }, 44 | PatternBind { 45 | lhs: Node, M>, 46 | rhs: Node, M>, 47 | }, 48 | Let { 49 | bindings: Vec<(Node, Node, M>)>, 50 | guard: Option, M>>, 51 | body: NonEmpty, M>>, 52 | }, 53 | Match(Node, M>, NonEmpty, M>>), 54 | Receive { 55 | clauses: NonEmpty, M>>, 56 | after: Option<(Node, NonEmpty, M>>)>, 57 | }, 58 | Cond { 59 | branches: NonEmpty<( 60 | Node, M>, 61 | NonEmpty, M>>, 62 | )>, 63 | else_branch: NonEmpty, M>>, 64 | }, 65 | TailrecRecurse(Vec, M>>), 66 | TailrecFinal(Node, M>), 67 | FunctionCall { 68 | name: Node, 69 | type_params: Vec, M>>, 70 | call_params: Vec, M>>, 71 | }, 72 | EffectCall { 73 | name: Node, 74 | type_params: Vec, M>>, 75 | call_params: Vec, M>>, 76 | }, 77 | ExceptionThrow(Node, M>), 78 | ProcessSpawn { 79 | name: Node, 80 | type_params: Vec, M>>, 81 | call_params: Vec, M>>, 82 | }, 83 | Block(CodeBlock), 84 | } 85 | 86 | #[derive(Debug, Clone, PartialEq)] 87 | pub struct CodeBlock { 88 | pub body: NonEmpty, M>>, 89 | pub effect_handlers: Vec<( 90 | Node, 91 | Vec, M>>, 92 | NonEmpty, M>>, 93 | )>, 94 | pub exception_handlers: Vec, M>>, 95 | } 96 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/expr/ops.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq)] 2 | pub enum UnaryOperator { 3 | LogicNot, 4 | BitNot, 5 | ArithmeticNegation, 6 | } 7 | 8 | #[derive(Debug, Clone, PartialEq)] 9 | pub enum BinaryOperator { 10 | LogicAnd, 11 | LogicOr, 12 | BitAnd, 13 | BitOr, 14 | BitXor, 15 | BitLShift, 16 | BitRShift, 17 | ArithmeticAdd, 18 | ArithmeticSub, 19 | ArithmeticMul, 20 | ArithmeticDiv, 21 | ArithmeticMod, 22 | ArithmeticPow, 23 | CompareLT, 24 | CompareLTE, 25 | CompareEQ, 26 | CompareNE, 27 | CompareGTE, 28 | CompareGT, 29 | StringConcatenation, 30 | ListConcatenation, 31 | ListContains, 32 | ListNotContains, 33 | } 34 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/expr/tuple.rs: -------------------------------------------------------------------------------- 1 | use ast_core::*; 2 | 3 | use crate::ast::{Identifier, Expression}; 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub enum TupleItem { 7 | Value(Node, M>), 8 | Expansion(Node, M>), 9 | } 10 | 11 | #[derive(Debug, Clone, PartialEq)] 12 | pub enum NamedTupleItem { 13 | Pair(Node, Node, M>), 14 | Expansion(Node, M>), 15 | } 16 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/import.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use ast_core::*; 3 | 4 | use crate::ast::Identifier; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub enum Import { 8 | Symbol { 9 | path: NonEmpty>, 10 | symbols: NonEmpty<(Node, Option>)>, 11 | }, 12 | Module { 13 | path: NonEmpty>, 14 | alias: Option>, 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use ast_core::*; 3 | 4 | mod import; 5 | mod decls; 6 | mod typ; 7 | mod expr; 8 | mod pat; 9 | 10 | pub use self::{ 11 | import::*, 12 | decls::*, 13 | typ::*, 14 | expr::*, 15 | pat::*, 16 | }; 17 | 18 | #[derive(Debug, Clone, PartialEq)] 19 | pub struct AST(pub Node, M>); 20 | 21 | #[derive(Debug, Clone, PartialEq)] 22 | pub struct Module { 23 | pub path: NonEmpty>, 24 | pub imports: Vec, M>>, 25 | pub declarations: Vec, M>>, 26 | } 27 | 28 | #[derive(Debug, Clone, PartialEq)] 29 | pub struct Identifier(pub String); 30 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/pat/mod.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use ast_core::*; 3 | 4 | use crate::ast::{Identifier, Expression, Literal}; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub struct Clause { 8 | pub patterns: Vec, M>>, 9 | pub guard: Option, M>>, 10 | pub body: NonEmpty, M>>, 11 | } 12 | 13 | #[derive(Debug, Clone, PartialEq)] 14 | pub enum Pattern { 15 | Ignore, 16 | Binding(Node), 17 | Literal(Node), 18 | Tuple { 19 | items: Vec, M>>, 20 | strict: bool, 21 | }, 22 | NamedTuple { 23 | items: Vec<(Node, Node, M>)>, 24 | strict: bool, 25 | }, 26 | List { 27 | items: Vec, M>>, 28 | strict: bool, 29 | }, 30 | ListHeadTail { 31 | heads: Vec, M>>, 32 | tail: Node, M>, 33 | }, 34 | Eval(Node, M>), 35 | } 36 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/ast/typ/mod.rs: -------------------------------------------------------------------------------- 1 | use nonempty::NonEmpty; 2 | use ast_core::*; 3 | 4 | use crate::ast::{Identifier, Literal}; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub enum Type { 8 | Reference { 9 | name: NonEmpty>, 10 | type_params: Vec, M>>, 11 | }, 12 | Literal(Node), 13 | Tuple(Vec, M>>), 14 | NamedTuple(Vec<(Node, Node, M>)>), 15 | OneOf { 16 | lhs: Node, M>, 17 | rhs: Node, M>, 18 | }, 19 | AllOf { 20 | lhs: Node, M>, 21 | rhs: Node, M>, 22 | }, 23 | Not(Node, M>), 24 | } 25 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/lexer/location.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter, Result}; 2 | 3 | pub struct TokenLocation(pub usize, pub usize); 4 | 5 | impl Display for TokenLocation { 6 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 7 | write!(f, "[{};{}]", self.0, self.1) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use logos::{Logos, Span}; 3 | 4 | use crate::prelude::*; 5 | 6 | mod location; 7 | mod token; 8 | mod stream; 9 | 10 | pub use self::{ 11 | location::*, 12 | token::*, 13 | stream::*, 14 | }; 15 | 16 | pub fn lex<'source>( 17 | filename: &'source PathBuf, 18 | code: &'source str, 19 | ) -> Result<'source, TokenStream<'source>> { 20 | let tokens = Token::lexer(code) 21 | .spanned() 22 | .map(|(res, span)| match res { 23 | Ok(token) => Ok((token, span)), 24 | Err(_) => Err(SyntaxError { 25 | kind: SyntaxErrorKind::InvalidToken(&code[span.clone()]), 26 | location: SourceLocation { 27 | filename, 28 | code, 29 | span: span.clone(), 30 | }, 31 | }), 32 | }) 33 | .collect::, Span)>>>()?; 34 | 35 | Ok(TokenStream { 36 | size: code.len(), 37 | tokens, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/lexer/stream.rs: -------------------------------------------------------------------------------- 1 | use logos::Span; 2 | use peg::Parse; 3 | 4 | use crate::lexer::{Token, TokenLocation}; 5 | 6 | pub struct TokenStream<'source> { 7 | pub size: usize, 8 | pub tokens: Vec<(Token<'source>, Span)>, 9 | } 10 | 11 | impl<'source> TokenStream<'source> { 12 | pub fn span(&self, tok_begin: usize, tok_end: usize) -> (usize, usize) { 13 | let TokenLocation(begin, _) = self.position_repr(tok_begin); 14 | let TokenLocation(_, end) = self.position_repr(tok_end); 15 | (begin, end) 16 | } 17 | } 18 | 19 | impl<'source> peg::Parse for TokenStream<'source> { 20 | type PositionRepr = TokenLocation; 21 | 22 | fn start(&self) -> usize { 23 | 0 24 | } 25 | 26 | fn is_eof(&self, pos: usize) -> bool { 27 | pos >= self.tokens.len() 28 | } 29 | 30 | fn position_repr(&self, pos: usize) -> Self::PositionRepr { 31 | match self.tokens.get(pos) { 32 | Some((_token, span)) => TokenLocation(span.start, span.end), 33 | None => TokenLocation(self.size, self.size), 34 | } 35 | } 36 | } 37 | 38 | impl<'source> peg::ParseElem<'source> for TokenStream<'source> { 39 | type Element = &'source Token<'source>; 40 | 41 | fn parse_elem(&'source self, pos: usize) -> peg::RuleResult { 42 | match self.tokens.get(pos) { 43 | Some((token, _)) => peg::RuleResult::Matched(pos + 1, token), 44 | None => peg::RuleResult::Failed, 45 | } 46 | } 47 | } 48 | 49 | impl<'source> peg::ParseLiteral for TokenStream<'source> { 50 | fn parse_string_literal(&self, pos: usize, literal: &str) -> peg::RuleResult<()> { 51 | match self.tokens.get(pos) { 52 | Some((Token::Keyword(sym), _)) if sym == &literal => peg::RuleResult::Matched(pos + 1, ()), 53 | _ => peg::RuleResult::Failed, 54 | } 55 | } 56 | } 57 | 58 | impl<'source> peg::ParseSlice<'source> for TokenStream<'source> { 59 | type Slice = Vec<&'source Token<'source>>; 60 | 61 | fn parse_slice(&'source self, begin: usize, end: usize) -> Self::Slice { 62 | self.tokens[begin..end] 63 | .into_iter() 64 | .map(|(token, _)| token) 65 | .collect() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/lexer/token.rs: -------------------------------------------------------------------------------- 1 | use logos::{Lexer, Logos}; 2 | 3 | #[derive(Logos, Debug, Clone, PartialEq)] 4 | #[logos(skip r"[ \t\r\n\f]+")] 5 | #[logos(skip r"#.*\n?")] 6 | pub enum Token<'source> { 7 | #[regex(r"[_a-zA-Z][_0-9a-zA-Z]*")] 8 | Ident(&'source str), 9 | 10 | #[regex(r#""([^"\\]|\\.)*""#, string)] 11 | String(String), 12 | 13 | #[regex(r"@(('(?:[^']|\\')+')|([_a-zA-Z0-9]+))")] 14 | Atom(&'source str), 15 | 16 | #[token("true", |_| true)] 17 | #[token("false", |_| false)] 18 | Bool(bool), 19 | 20 | #[regex(r"0b_*[01][_01]*", int)] 21 | #[regex(r"0o_*[0-7][_0-7]*", int)] 22 | #[regex(r"[0-9][_0-9]*", int, priority = 2)] 23 | #[regex(r"0x_*[0-9a-fA-F][_0-9a-fA-F]*", int)] 24 | #[regex(r"((\d+\.?\d*)|(\.\d+))(([eE][+-]?)?\d+)?", float)] 25 | Number(f64), 26 | 27 | #[token("::")] 28 | ModSep, 29 | 30 | #[token("(")] 31 | ParenBegin, 32 | 33 | #[token(")")] 34 | ParenEnd, 35 | 36 | #[token("[")] 37 | BracketBegin, 38 | 39 | #[token("]")] 40 | BracketEnd, 41 | 42 | #[token("{")] 43 | BraceBegin, 44 | 45 | #[token("}")] 46 | BraceEnd, 47 | 48 | #[token("${")] 49 | DollarBraceBegin, 50 | 51 | #[token(",")] 52 | Comma, 53 | 54 | #[token(".")] 55 | Dot, 56 | 57 | #[token(":")] 58 | Colon, 59 | 60 | #[token(";")] 61 | Semicolon, 62 | 63 | #[token("->")] 64 | Arrow, 65 | 66 | #[token(":=")] 67 | Match, 68 | 69 | #[token("+")] 70 | Add, 71 | 72 | #[token("-")] 73 | Sub, 74 | 75 | #[token("*")] 76 | Mul, 77 | 78 | #[token("/")] 79 | Div, 80 | 81 | #[token("%")] 82 | Mod, 83 | 84 | #[token("**")] 85 | Pow, 86 | 87 | #[token("<")] 88 | LT, 89 | 90 | #[token("<=")] 91 | LTE, 92 | 93 | #[token("=")] 94 | Eq, 95 | 96 | #[token("!=")] 97 | Ne, 98 | 99 | #[token(">=")] 100 | GTE, 101 | 102 | #[token(">")] 103 | GT, 104 | 105 | #[token("!")] 106 | #[token("¬")] 107 | Negation, 108 | 109 | #[token("~")] 110 | BitNot, 111 | 112 | #[token("&")] 113 | BitAnd, 114 | 115 | #[token("|")] 116 | BitOr, 117 | 118 | #[token("^")] 119 | BitXor, 120 | 121 | #[token("<<")] 122 | BitLShift, 123 | 124 | #[token(">>")] 125 | BitRShift, 126 | 127 | #[token("not")] 128 | LogicNot, 129 | 130 | #[token("and")] 131 | LogicAnd, 132 | 133 | #[token("or")] 134 | LogicOr, 135 | 136 | #[token("<>")] 137 | StringConcat, 138 | 139 | #[token("++")] 140 | ListConcat, 141 | 142 | #[token("in")] 143 | ListContains, 144 | 145 | #[token("is")] 146 | TypeContains, 147 | 148 | #[token("|>")] 149 | Pipe, 150 | 151 | #[token("_")] 152 | PatternIgnore, 153 | 154 | #[token("...")] 155 | Ellipsis, 156 | 157 | #[token("module")] 158 | #[token("from")] 159 | #[token("import")] 160 | #[token("as")] 161 | #[token("let")] 162 | #[token("pub")] 163 | #[token("class")] 164 | #[token("func")] 165 | #[token("tailrec")] 166 | #[token("effect")] 167 | #[token("extern")] 168 | #[token("when")] 169 | #[token("with")] 170 | #[token("do")] 171 | #[token("intercept")] 172 | #[token("catch")] 173 | #[token("perform")] 174 | #[token("throw")] 175 | #[token("spawn")] 176 | #[token("receive")] 177 | #[token("after")] 178 | #[token("match")] 179 | #[token("cond")] 180 | #[token("else")] 181 | #[token("recurse")] 182 | #[token("final")] 183 | Keyword(&'source str), 184 | } 185 | 186 | fn int<'a>(lex: &mut Lexer<'a, Token<'a>>) -> Result { 187 | parse_int::parse::(lex.slice()) 188 | .map(|i| i as f64) 189 | .map_err(|_| ()) 190 | } 191 | 192 | fn float<'a>(lex: &mut Lexer<'a, Token<'a>>) -> Result { 193 | lex.slice().parse().map_err(|_| ()) 194 | } 195 | 196 | fn string<'a>(lex: &mut Lexer<'a, Token<'a>>) -> Result { 197 | snailquote::unescape(lex.slice()).map_err(|_| ()) 198 | } 199 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod prelude; 2 | pub mod lexer; 3 | pub mod parser; 4 | pub mod ast; 5 | 6 | use std::{ 7 | io::Error as IOError, 8 | fs, 9 | path::{Path, PathBuf}, 10 | }; 11 | 12 | pub use self::{ 13 | prelude::{SourceLocation, SyntaxError, OwnedSyntaxError}, 14 | ast::AST, 15 | }; 16 | 17 | pub struct SourceFile { 18 | filename: PathBuf, 19 | code: String, 20 | } 21 | 22 | impl SourceFile { 23 | pub fn from_file>(filename: P) -> Result { 24 | let filename = filename.as_ref().to_path_buf(); 25 | let code = fs::read_to_string(&filename)?; 26 | Ok(Self { filename, code }) 27 | } 28 | } 29 | 30 | impl<'source> TryFrom<&'source SourceFile> for AST> { 31 | type Error = SyntaxError<'source>; 32 | 33 | fn try_from(value: &'source SourceFile) -> Result { 34 | let tokens = lexer::lex(&value.filename, &value.code)?; 35 | let ast = parser::parse(&value.filename, &value.code, tokens)?; 36 | Ok(ast) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | mod sourcemap; 2 | mod grammar; 3 | 4 | use std::path::PathBuf; 5 | use crate::{ 6 | ast::AST, 7 | lexer::{TokenStream, TokenLocation}, 8 | prelude::*, 9 | }; 10 | use self::{ 11 | sourcemap::SourceMapLookup, 12 | grammar::unit_parser, 13 | }; 14 | 15 | pub fn parse<'source>( 16 | filename: &'source PathBuf, 17 | code: &'source str, 18 | token_stream: TokenStream<'source> 19 | ) -> Result<'source, AST>> { 20 | let srcmap = SourceMapLookup { 21 | filename, 22 | code, 23 | token_stream: &token_stream, 24 | }; 25 | 26 | let ast = unit_parser::unit(&token_stream, &srcmap) 27 | .map_err(|err| { 28 | let TokenLocation(begin, end) = err.location; 29 | 30 | SyntaxError { 31 | location: SourceLocation { 32 | filename, 33 | code, 34 | span: begin..end, 35 | }, 36 | kind: SyntaxErrorKind::UnexpectedToken(err.expected.to_string()), 37 | } 38 | })?; 39 | 40 | Ok(ast) 41 | } 42 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/parser/sourcemap.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use crate::{lexer::TokenStream, SourceLocation}; 3 | 4 | pub struct SourceMapLookup<'source, 'tokens> { 5 | pub filename: &'source PathBuf, 6 | pub code: &'source str, 7 | pub token_stream: &'tokens TokenStream<'source>, 8 | } 9 | 10 | impl<'source, 'tokens> SourceMapLookup<'source, 'tokens> { 11 | pub fn token_span(&self, tok_begin: usize, tok_end: usize) -> SourceLocation<'source> { 12 | let tok_end = tok_end.saturating_sub(1); 13 | let (char_begin, char_end) = self.token_stream.span(tok_begin, tok_end); 14 | 15 | SourceLocation { 16 | filename: self.filename, 17 | code: self.code, 18 | span: char_begin..char_end, 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sources/compiler/frontend/src/prelude/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::ops::Range; 3 | 4 | use ariadne::{Report, ReportKind, Label, Source}; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub struct SourceLocation<'source> { 8 | pub filename: &'source PathBuf, 9 | pub code: &'source str, 10 | pub span: Range, 11 | } 12 | 13 | #[derive(Debug, Clone, PartialEq)] 14 | pub enum SyntaxErrorKind<'source> { 15 | InvalidToken(&'source str), 16 | UnexpectedToken(String), 17 | } 18 | 19 | #[derive(Debug, Clone, PartialEq)] 20 | pub struct SyntaxError<'source> { 21 | pub kind: SyntaxErrorKind<'source>, 22 | pub location: SourceLocation<'source>, 23 | } 24 | 25 | #[derive(Debug, Clone, PartialEq)] 26 | pub struct OwnedSyntaxError { 27 | pub report: String, 28 | } 29 | 30 | pub type Result<'source, T> = std::result::Result>; 31 | 32 | impl<'source> std::fmt::Display for SyntaxErrorKind<'source> { 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 34 | match self { 35 | SyntaxErrorKind::InvalidToken(tok) => write!(f, "Invalid token: {tok}"), 36 | SyntaxErrorKind::UnexpectedToken(tok) => write!(f, "Unexpected token: {tok}"), 37 | } 38 | } 39 | } 40 | 41 | impl<'source> std::fmt::Display for SyntaxError<'source> { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | let filename = self.location.filename.display().to_string(); 44 | let code = self.location.code; 45 | let span = self.location.span.clone(); 46 | 47 | let mut message: Vec = vec![]; 48 | 49 | Report::build(ReportKind::Error, &filename, span.start) 50 | .with_code(match self.kind { 51 | SyntaxErrorKind::InvalidToken(..) => 101, 52 | SyntaxErrorKind::UnexpectedToken(..) => 102, 53 | }) 54 | .with_label(Label::new((&filename, span))) 55 | .with_message(format!("Syntax Error: {}", self.kind)) 56 | .finish() 57 | .write((&filename, Source::from(code)), &mut message) 58 | .expect("unable to write error report"); 59 | 60 | let message = String::from_utf8_lossy(&message).into_owned(); 61 | 62 | write!(f, "{}", message) 63 | } 64 | } 65 | 66 | impl<'source> std::fmt::Display for OwnedSyntaxError { 67 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 68 | write!(f, "{}", self.report) 69 | } 70 | } 71 | 72 | impl<'source> std::error::Error for SyntaxError<'source> {} 73 | impl std::error::Error for OwnedSyntaxError {} 74 | 75 | impl<'source> SyntaxError<'source> { 76 | pub fn to_owned(&self) -> OwnedSyntaxError { 77 | OwnedSyntaxError { report: format!("{}", self) } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /sources/runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "letlang-runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "llruntime" 8 | 9 | [dependencies] 10 | genawaiter = "0.99" 11 | async-trait = "0.1" 12 | siphasher = "1.0" 13 | tokio = { version = "1.35", features = ["full"] } 14 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/mod.rs: -------------------------------------------------------------------------------- 1 | mod signal; 2 | mod process; 3 | mod process_group; 4 | 5 | pub use self::{ 6 | signal::*, 7 | process::*, 8 | process_group::*, 9 | }; 10 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/process/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data::{ 3 | LLPid, 4 | LLValue, 5 | LLException, 6 | }, 7 | thread::{ 8 | LLInterrupt, 9 | LLCoroutine, 10 | }, 11 | concurrency::signal::LLSignalReceiver, 12 | traits::LLClassInstance, 13 | }; 14 | 15 | use std::sync::Arc; 16 | use tokio::sync::Mutex; 17 | 18 | #[derive(Clone)] 19 | pub struct LLProcessContext(Arc>); 20 | 21 | struct ContextData { 22 | pid: LLPid, 23 | mbox_rx: LLSignalReceiver, 24 | } 25 | 26 | impl LLProcessContext { 27 | pub fn new(pid: LLPid, mbox_rx: LLSignalReceiver) -> Self { 28 | let data = ContextData { pid, mbox_rx }; 29 | Self(Arc::new(Mutex::new(data))) 30 | } 31 | 32 | pub async fn pid(&self) -> LLPid { 33 | self.0.lock().await.pid.clone() 34 | } 35 | 36 | pub async fn throw( 37 | &self, 38 | co: &LLCoroutine, 39 | exc: LLException, 40 | ) { 41 | let exc_info: LLValue = exc.into(); 42 | co.yield_(LLInterrupt::Exception(exc_info)).await; 43 | } 44 | 45 | pub async fn assert_type_arity( 46 | &self, 47 | co: &LLCoroutine, 48 | expected: usize, 49 | got: usize, 50 | ) { 51 | if expected != got { 52 | self.throw(co, LLException::TypeArity { expected, got }).await; 53 | } 54 | } 55 | 56 | pub async fn assert_func_arity( 57 | &self, 58 | co: &LLCoroutine, 59 | expected: usize, 60 | got: usize, 61 | ) { 62 | if expected != got { 63 | self.throw(co, LLException::FuncArity { expected, got }).await; 64 | } 65 | } 66 | 67 | pub async fn assert_class( 68 | &self, 69 | co: &LLCoroutine, 70 | val: &LLValue, 71 | class: &LLClassInstance, 72 | ) { 73 | if !class.has(self.clone(), co, val).await { 74 | self.throw(co, LLException::TypeError).await; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/process/handle.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data::LLPid, 3 | concurrency::LLSignalSender, 4 | }; 5 | 6 | pub struct LLProcessHandle { 7 | pub links: Vec, 8 | pub mbox_tx: LLSignalSender, 9 | } 10 | 11 | impl LLProcessHandle { 12 | pub fn new(mbox_tx: LLSignalSender) -> Self { 13 | Self { links: vec![], mbox_tx } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/process/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data::LLPid, 3 | concurrency::signal::LLChannel, 4 | }; 5 | 6 | mod context; 7 | mod handle; 8 | 9 | pub use self::{ 10 | context::*, 11 | handle::*, 12 | }; 13 | 14 | pub struct LLProcess; 15 | 16 | impl LLProcess { 17 | pub fn new(pid: LLPid) -> (LLProcessContext, LLProcessHandle) { 18 | let (tx, rx) = LLChannel::new(); 19 | (LLProcessContext::new(pid, rx), LLProcessHandle::new(tx)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/process_group/command.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | prelude::*, 3 | data::LLPid, 4 | concurrency::signal::LLSignal, 5 | traits::{ 6 | LLFunctionInstance, 7 | LLClassInstance, 8 | }, 9 | }; 10 | use super::{ 11 | request::{Request, RequestSender}, 12 | response::ResponseChannel, 13 | }; 14 | 15 | pub enum Command { 16 | Spawn(Vec, LLFunctionInstance), 17 | SendTo(LLPid, LLSignal), 18 | Link(LLPid, LLPid), 19 | Unlink(LLPid, LLPid), 20 | Exited(LLPid, Result<()>), 21 | } 22 | 23 | impl Command { 24 | pub async fn send(self, sender: &mut RequestSender) -> Result<()> { 25 | let (resp_tx, mut resp_rx) = ResponseChannel::new(); 26 | let req = Request { respond_to: resp_tx, command: self }; 27 | sender.send(req).await?; 28 | resp_rx.receive().await 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/process_group/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | prelude::*, 3 | data::{ 4 | LLPid, 5 | LLValue, 6 | LLAtom, 7 | }, 8 | concurrency::{ 9 | signal::LLSignal, 10 | process::{ 11 | LLProcess, 12 | LLProcessHandle, 13 | }, 14 | }, 15 | thread::LLTask, 16 | traits::{ 17 | LLFunctionInstance, 18 | LLClassInstance, 19 | }, 20 | }; 21 | 22 | use std::sync::atomic::{AtomicU64, Ordering}; 23 | use std::collections::HashMap; 24 | 25 | mod command; 26 | mod request; 27 | mod response; 28 | 29 | use self::{ 30 | command::*, 31 | request::*, 32 | }; 33 | 34 | pub struct LLProcessGroup { 35 | group_id: u64, 36 | last_local_id: AtomicU64, 37 | process_registry: HashMap, 38 | request_tx: RequestSender, 39 | request_rx: RequestReceiver, 40 | } 41 | 42 | 43 | impl LLProcessGroup { 44 | pub fn new(group_id: u64) -> Self { 45 | let (tx, rx) = RequestChannel::new(); 46 | Self { 47 | group_id, 48 | last_local_id: AtomicU64::new(0), 49 | process_registry: HashMap::new(), 50 | request_tx: tx, 51 | request_rx: rx, 52 | } 53 | } 54 | 55 | fn make_pid(&self) -> LLPid { 56 | let local_id = self.last_local_id.fetch_add(1, Ordering::SeqCst); 57 | LLPid::new(self.group_id, local_id) 58 | } 59 | 60 | pub fn run(self, main: LLFunctionInstance) -> Result<()> { 61 | let rt = tokio::runtime::Runtime::new()?; 62 | rt.block_on(async { 63 | self.serve(main).await 64 | }) 65 | } 66 | 67 | async fn serve(mut self, main: LLFunctionInstance) -> Result<()> { 68 | let mut req_tx = self.request_tx.clone(); 69 | 70 | let server_handle = tokio::spawn(async move { 71 | while let Some(req) = self.request_rx.receive().await { 72 | let res = self.handle_command(req.command).await; 73 | req.respond_to.send(res).unwrap(); 74 | 75 | if self.process_registry.is_empty() { 76 | break; 77 | } 78 | } 79 | }); 80 | 81 | Command::Spawn(vec![], main).send(&mut req_tx).await?; 82 | 83 | server_handle.await.map_err(|_| RuntimeError::ProcessGroupCrashed)?; 84 | 85 | Ok(()) 86 | } 87 | 88 | async fn handle_command(&mut self, cmd: Command) -> Result<()> { 89 | match cmd { 90 | Command::Spawn(type_params, func) => { 91 | self.handle_command_spawn(type_params, func) 92 | }, 93 | Command::SendTo(pid, signal) => { 94 | self.handle_command_send_to(pid, signal).await 95 | }, 96 | Command::Link(source, target) => { 97 | self.handle_command_link(source, target) 98 | }, 99 | Command::Unlink(source, target) => { 100 | self.handle_command_unlink(source, target) 101 | }, 102 | Command::Exited(pid, res) => { 103 | self.handle_command_exited(pid, res).await 104 | }, 105 | } 106 | } 107 | 108 | fn handle_command_spawn( 109 | &mut self, 110 | type_params: Vec, 111 | func: LLFunctionInstance, 112 | ) -> Result<()> { 113 | let mut group_req_tx = self.request_tx.clone(); 114 | 115 | let pid = self.make_pid(); 116 | let (ctx, handle) = LLProcess::new(pid.clone()); 117 | self.process_registry.insert(pid.clone(), handle); 118 | 119 | tokio::spawn(async move { 120 | let task_handle = tokio::spawn(async move { 121 | let task = LLTask { 122 | context: ctx, 123 | type_params, 124 | func, 125 | }; 126 | task.run().await.unwrap(); 127 | }); 128 | 129 | let res = task_handle.await.map_err(|_| RuntimeError::ProcessCrashed); 130 | Command::Exited(pid.clone(), res).send(&mut group_req_tx).await.unwrap(); 131 | }); 132 | 133 | Ok(()) 134 | } 135 | 136 | async fn handle_command_send_to( 137 | &mut self, 138 | target: LLPid, 139 | signal: LLSignal, 140 | ) -> Result<()> { 141 | match self.process_registry.get_mut(&target) { 142 | None => Err(RuntimeError::ProcessNotFound), 143 | Some(handle) => handle.mbox_tx.send(signal).await, 144 | } 145 | } 146 | 147 | fn handle_command_link( 148 | &mut self, 149 | source: LLPid, 150 | target: LLPid, 151 | ) -> Result<()> { 152 | match self.process_registry.get_mut(&target) { 153 | None => Err(RuntimeError::ProcessNotFound), 154 | Some(handle) => { 155 | handle.links.push(source); 156 | Ok(()) 157 | }, 158 | } 159 | } 160 | 161 | fn handle_command_unlink( 162 | &mut self, 163 | source: LLPid, 164 | target: LLPid, 165 | ) -> Result<()> { 166 | match self.process_registry.get_mut(&target) { 167 | None => Err(RuntimeError::ProcessNotFound), 168 | Some(handle) => { 169 | handle.links.retain(|pid| *pid != source); 170 | Ok(()) 171 | }, 172 | } 173 | } 174 | 175 | async fn handle_command_exited( 176 | &mut self, 177 | pid: LLPid, 178 | res: Result<()>, 179 | ) -> Result<()> { 180 | let reason = match res { 181 | Ok(_) => LLValue::Atom(LLAtom::new("@normal")), 182 | Err(err) => { 183 | LLValue::Tuple(Box::new([ 184 | LLValue::Atom(LLAtom::new("@error")), 185 | LLValue::String(format!("{}", err)), 186 | ])) 187 | }, 188 | }; 189 | 190 | let signal = LLSignal::Exited(pid.clone(), reason); 191 | 192 | let handle = self.process_registry.remove(&pid) 193 | .ok_or(RuntimeError::ProcessNotFound)?; 194 | 195 | for link in handle.links.iter() { 196 | if let Some(handle) = self.process_registry.get_mut(&link) { 197 | handle.mbox_tx.send(signal.clone()).await?; 198 | } 199 | } 200 | 201 | Ok(()) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/process_group/request.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use super::{ 3 | command::Command, 4 | response::ResponseSender, 5 | }; 6 | 7 | use tokio::sync::mpsc; 8 | 9 | pub struct Request { 10 | pub respond_to: ResponseSender, 11 | pub command: Command, 12 | } 13 | 14 | pub struct RequestChannel; 15 | 16 | #[derive(Clone)] 17 | pub struct RequestSender(mpsc::Sender); 18 | 19 | pub struct RequestReceiver(mpsc::Receiver); 20 | 21 | impl RequestChannel { 22 | pub fn new() -> (RequestSender, RequestReceiver) { 23 | let (tx, rx) = mpsc::channel(32); 24 | (RequestSender(tx), RequestReceiver(rx)) 25 | } 26 | } 27 | 28 | impl RequestSender { 29 | pub async fn send(&self, req: Request) -> Result<()> { 30 | let Self(tx) = self; 31 | tx.send(req).await?; 32 | Ok(()) 33 | } 34 | } 35 | 36 | impl RequestReceiver { 37 | pub async fn receive(&mut self) -> Option { 38 | let Self(rx) = self; 39 | rx.recv().await 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/process_group/response.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use tokio::sync::oneshot; 3 | 4 | pub struct ResponseChannel; 5 | 6 | pub struct ResponseSender(oneshot::Sender>); 7 | 8 | pub struct ResponseReceiver(oneshot::Receiver>); 9 | 10 | 11 | impl ResponseChannel { 12 | pub fn new() -> (ResponseSender, ResponseReceiver) { 13 | let (tx, rx) = oneshot::channel(); 14 | (ResponseSender(tx), ResponseReceiver(rx)) 15 | } 16 | } 17 | 18 | impl ResponseSender { 19 | pub fn send(self, resp: Result<()>) -> Result<()> { 20 | let Self(tx) = self; 21 | tx.send(resp).map_err(|_| RuntimeError::ChannelSendError)?; 22 | Ok(()) 23 | } 24 | } 25 | 26 | impl ResponseReceiver { 27 | pub async fn receive(&mut self) -> Result<()> { 28 | let Self(rx) = self; 29 | rx.await? 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sources/runtime/src/concurrency/signal.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | prelude::*, 3 | data::{LLValue, LLPid}, 4 | }; 5 | 6 | use tokio::sync::mpsc; 7 | 8 | #[derive(Clone)] 9 | pub enum LLSignal { 10 | Message(LLPid, LLValue), 11 | Exited(LLPid, LLValue), 12 | } 13 | 14 | #[derive(Clone)] 15 | pub struct LLSignalSender(mpsc::Sender); 16 | 17 | pub struct LLSignalReceiver(mpsc::Receiver); 18 | 19 | pub struct LLChannel; 20 | 21 | impl LLChannel { 22 | pub fn new() -> (LLSignalSender, LLSignalReceiver) { 23 | let (tx, rx) = mpsc::channel(32); 24 | (LLSignalSender(tx), LLSignalReceiver(rx)) 25 | } 26 | } 27 | 28 | impl LLSignalSender { 29 | pub async fn send(&self, signal: LLSignal) -> Result<()> { 30 | let Self(tx) = self; 31 | tx.send(signal).await?; 32 | Ok(()) 33 | } 34 | } 35 | 36 | impl LLSignalReceiver { 37 | pub async fn receive(&mut self) -> Result { 38 | let Self(rx) = self; 39 | rx.recv().await.ok_or(RuntimeError::ChannelReceiveError) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sources/runtime/src/data/atom.rs: -------------------------------------------------------------------------------- 1 | use siphasher::sip::SipHasher; 2 | 3 | #[derive(Clone)] 4 | pub struct LLAtom { 5 | repr: &'static str, 6 | hash: u64, 7 | } 8 | 9 | impl LLAtom { 10 | pub fn new(repr: &'static str) -> Self { 11 | let hasher = SipHasher::new(); 12 | let hash = hasher.hash(repr.as_bytes()); 13 | 14 | Self { repr, hash } 15 | } 16 | 17 | pub fn new_explicit(repr: &'static str, hash: u64) -> Self { 18 | Self { repr, hash } 19 | } 20 | } 21 | 22 | impl PartialEq for LLAtom { 23 | fn eq(&self, other: &Self) -> bool { 24 | self.hash == other.hash 25 | } 26 | } 27 | 28 | impl Eq for LLAtom {} 29 | 30 | impl std::fmt::Display for LLAtom { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 | write!(f, "{}", self.repr) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sources/runtime/src/data/exc.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{LLValue, LLAtom}; 2 | 3 | #[derive(Clone)] 4 | pub enum LLException { 5 | TypeArity { expected: usize, got: usize }, 6 | FuncArity { expected: usize, got: usize }, 7 | NoClauseMatched, 8 | TypeError, 9 | } 10 | 11 | impl From for LLValue { 12 | fn from(value: LLException) -> Self { 13 | match value { 14 | LLException::TypeArity { expected, got } => { 15 | LLValue::Tuple(Box::new([ 16 | LLValue::Atom(LLAtom::new("@type_arity")), 17 | LLValue::Tuple(Box::new([ 18 | LLValue::Atom(LLAtom::new("@expected")), 19 | LLValue::Number(expected as f64), 20 | ])), 21 | LLValue::Tuple(Box::new([ 22 | LLValue::Atom(LLAtom::new("@got")), 23 | LLValue::Number(got as f64), 24 | ])), 25 | ])) 26 | }, 27 | LLException::FuncArity { expected, got } => { 28 | LLValue::Tuple(Box::new([ 29 | LLValue::Atom(LLAtom::new("@func_arity")), 30 | LLValue::Tuple(Box::new([ 31 | LLValue::Atom(LLAtom::new("@expected")), 32 | LLValue::Number(expected as f64), 33 | ])), 34 | LLValue::Tuple(Box::new([ 35 | LLValue::Atom(LLAtom::new("@got")), 36 | LLValue::Number(got as f64), 37 | ])), 38 | ])) 39 | }, 40 | LLException::NoClauseMatched => { 41 | LLValue::Atom(LLAtom::new("@no_clause_matched")) 42 | }, 43 | LLException::TypeError => { 44 | LLValue::Atom(LLAtom::new("@type_error")) 45 | }, 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sources/runtime/src/data/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | mod atom; 4 | mod pid; 5 | mod exc; 6 | 7 | pub use self::{ 8 | atom::*, 9 | pid::*, 10 | exc::*, 11 | }; 12 | 13 | #[derive(Clone)] 14 | pub enum LLValue { 15 | Atom(LLAtom), 16 | Bool(bool), 17 | Number(f64), 18 | String(String), 19 | Tuple(Box<[LLValue]>), 20 | List(Vec), 21 | NamedTuple(HashMap), 22 | TailRecRecurse(Vec), 23 | TailRecFinal(Box), 24 | Pid(LLPid), 25 | } 26 | 27 | impl std::fmt::Display for LLValue { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 | match self { 30 | Self::Atom(val) => { 31 | write!(f, "{val}") 32 | }, 33 | Self::Bool(val) => { 34 | write!(f, "{val}") 35 | }, 36 | Self::Number(val) => { 37 | write!(f, "{val}") 38 | }, 39 | Self::String(val) => { 40 | write!(f, "{:?}", val) 41 | }, 42 | Self::Tuple(items) => { 43 | let inner = items.iter() 44 | .map(|val| format!("{val}")) 45 | .fold(String::new(), |a, b| format!("{a}, {b}")); 46 | write!(f, "({inner})") 47 | }, 48 | Self::List(items) => { 49 | let inner = items.iter() 50 | .map(|val| format!("{val}")) 51 | .fold(String::new(), |a, b| format!("{a}, {b}")); 52 | 53 | write!(f, "[{inner}]") 54 | }, 55 | Self::NamedTuple(items) => { 56 | let inner = items.iter() 57 | .map(|(key, val)| format!("{key}: {val}")) 58 | .fold(String::new(), |a, b| format!("{a}, {b}")); 59 | 60 | write!(f, "{{{inner}}}") 61 | }, 62 | Self::TailRecRecurse(items) => { 63 | let inner = items.iter() 64 | .map(|val| format!("{val}")) 65 | .fold(String::new(), |a, b| format!("{a}, {b}")); 66 | 67 | write!(f, "recurse[{inner}]") 68 | }, 69 | Self::TailRecFinal(val) => { 70 | write!(f, "final[{val}]") 71 | }, 72 | Self::Pid(val) => { 73 | write!(f, "{val}") 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /sources/runtime/src/data/pid.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, PartialEq, Eq, Hash)] 2 | pub struct LLPid { 3 | group_id: u64, 4 | local_id: u64, 5 | } 6 | 7 | impl LLPid { 8 | pub fn new(group_id: u64, local_id: u64) -> Self { 9 | Self { group_id, local_id } 10 | } 11 | } 12 | 13 | impl std::fmt::Display for LLPid { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | write!(f, "Pid({}:{})", self.group_id, self.local_id) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sources/runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod prelude; 2 | mod data; 3 | mod thread; 4 | mod concurrency; 5 | mod traits; 6 | 7 | pub use self::{ 8 | data::*, 9 | thread::*, 10 | concurrency::*, 11 | traits::*, 12 | }; 13 | 14 | pub use async_trait::async_trait; 15 | -------------------------------------------------------------------------------- /sources/runtime/src/prelude/mod.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::{oneshot, mpsc}; 2 | 3 | #[derive(Debug)] 4 | pub enum RuntimeError { 5 | ChannelReceiveError, 6 | ChannelSendError, 7 | ProcessGroupCrashed, 8 | ProcessCrashed, 9 | ProcessNotFound, 10 | UncaughtException, 11 | IOError(std::io::Error), 12 | } 13 | 14 | pub type Result = std::result::Result; 15 | 16 | unsafe impl Sync for RuntimeError {} 17 | unsafe impl Send for RuntimeError {} 18 | 19 | impl std::fmt::Display for RuntimeError { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | match self { 22 | Self::ChannelReceiveError => write!(f, "Channel receive error"), 23 | Self::ChannelSendError => write!(f, "Channel send error"), 24 | Self::ProcessGroupCrashed => write!(f, "Process group crashed"), 25 | Self::ProcessCrashed => write!(f, "Process crashed"), 26 | Self::ProcessNotFound => write!(f, "Process not found"), 27 | Self::UncaughtException => write!(f, "Uncaught exception"), 28 | Self::IOError(e) => write!(f, "IO error: {}", e), 29 | } 30 | } 31 | } 32 | 33 | impl std::error::Error for RuntimeError {} 34 | 35 | impl From> for RuntimeError { 36 | fn from(_: mpsc::error::SendError) -> Self { 37 | Self::ChannelSendError 38 | } 39 | } 40 | 41 | impl From for RuntimeError { 42 | fn from(_: oneshot::error::RecvError) -> Self { 43 | Self::ChannelReceiveError 44 | } 45 | } 46 | 47 | impl From for RuntimeError { 48 | fn from(e: std::io::Error) -> Self { 49 | Self::IOError(e) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sources/runtime/src/thread/continuation.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data::LLValue, 3 | thread::LLInterrupt, 4 | }; 5 | 6 | use genawaiter::{ 7 | GeneratorState, 8 | sync::Gen, 9 | }; 10 | 11 | use std::{ 12 | pin::Pin, 13 | future::Future, 14 | }; 15 | 16 | pub type LLContinuation = Gen< 17 | LLInterrupt, 18 | LLValue, 19 | Pin + Send + 'static)>>, 20 | >; 21 | 22 | pub type LLContinuationState = GeneratorState; 23 | -------------------------------------------------------------------------------- /sources/runtime/src/thread/coroutine.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data::LLValue, 3 | thread::LLInterrupt, 4 | }; 5 | 6 | use genawaiter::sync::Co; 7 | 8 | pub type LLCoroutine = Co; 9 | -------------------------------------------------------------------------------- /sources/runtime/src/thread/int.rs: -------------------------------------------------------------------------------- 1 | use crate::data::LLValue; 2 | 3 | #[derive(Clone)] 4 | pub enum LLInterrupt { 5 | Effect { id: String, args: Vec }, 6 | Exception(LLValue), 7 | } 8 | -------------------------------------------------------------------------------- /sources/runtime/src/thread/mod.rs: -------------------------------------------------------------------------------- 1 | mod int; 2 | mod coroutine; 3 | mod continuation; 4 | mod task; 5 | 6 | pub use self::{ 7 | int::*, 8 | coroutine::*, 9 | continuation::*, 10 | task::*, 11 | }; 12 | -------------------------------------------------------------------------------- /sources/runtime/src/thread/task.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | prelude::*, 3 | data::LLValue, 4 | thread::LLInterrupt, 5 | concurrency::LLProcessContext, 6 | traits::{ 7 | LLFunctionInstance, 8 | LLClassInstance, 9 | }, 10 | }; 11 | 12 | use genawaiter::GeneratorState; 13 | 14 | pub struct LLTask { 15 | pub context: LLProcessContext, 16 | pub type_params: Vec, 17 | pub func: LLFunctionInstance, 18 | } 19 | 20 | impl LLTask { 21 | pub async fn run(self) -> Result<()> { 22 | let task_args: Vec = vec![]; 23 | let mut block = self.func.call( 24 | self.context.clone(), 25 | self.type_params, 26 | task_args, 27 | ); 28 | 29 | let ignored = LLValue::Bool(bool::default()); 30 | let mut state = block.resume_with(ignored); 31 | 32 | loop { 33 | match &state { 34 | GeneratorState::Yielded(LLInterrupt::Effect { id, args}) => { 35 | state = block.resume_with(todo!()); 36 | }, 37 | GeneratorState::Yielded(LLInterrupt::Exception(exc)) => { 38 | eprintln!( 39 | "[FATAL] Uncaught xception in task {}: {}", 40 | self.context.pid().await, 41 | exc, 42 | ); 43 | return Err(RuntimeError::UncaughtException); 44 | }, 45 | GeneratorState::Complete(val) => { 46 | eprintln!( 47 | "Task {} terminated with: {}", 48 | self.context.pid().await, 49 | val, 50 | ); 51 | break; 52 | }, 53 | } 54 | } 55 | 56 | Ok(()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sources/runtime/src/traits/class.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data::LLValue, 3 | concurrency::LLProcessContext, 4 | thread::LLCoroutine, 5 | }; 6 | 7 | use async_trait::async_trait; 8 | 9 | pub type LLClassInstance = Box; 10 | 11 | #[async_trait] 12 | pub trait LLClass: Sync + Send { 13 | async fn has( 14 | &self, 15 | ctx: LLProcessContext, 16 | co: &LLCoroutine, 17 | val: &LLValue, 18 | ) -> bool; 19 | } 20 | -------------------------------------------------------------------------------- /sources/runtime/src/traits/func.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data::LLValue, 3 | concurrency::LLProcessContext, 4 | thread::LLContinuation, 5 | traits::LLClassInstance, 6 | }; 7 | 8 | pub type LLFunctionInstance = Box; 9 | 10 | pub trait LLFunction: Sync + Send { 11 | fn call( 12 | &self, 13 | ctx: LLProcessContext, 14 | type_params: Vec, 15 | call_params: Vec, 16 | ) -> LLContinuation; 17 | } 18 | -------------------------------------------------------------------------------- /sources/runtime/src/traits/mod.rs: -------------------------------------------------------------------------------- 1 | mod class; 2 | mod func; 3 | 4 | pub use self::{ 5 | class::*, 6 | func::*, 7 | }; 8 | -------------------------------------------------------------------------------- /sources/utils/ast-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "letlang-ast-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /sources/utils/ast-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq)] 2 | pub struct Node { 3 | meta: M, 4 | data: Box, 5 | } 6 | 7 | impl Node { 8 | pub fn new(meta: M, data: T) -> Self { 9 | Self { 10 | meta, 11 | data: Box::new(data), 12 | } 13 | } 14 | 15 | pub fn get_meta(&self) -> &M { 16 | &self.meta 17 | } 18 | 19 | pub fn get_data(&self) -> &T { 20 | self.data.as_ref() 21 | } 22 | } 23 | 24 | pub trait Model { 25 | type Result; 26 | type Extra; 27 | 28 | fn visit(&mut self, node: &Node, extra: Self::Extra) -> Self::Result; 29 | } 30 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "letlang-ast-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | quote = "1.0" 11 | syn = "2.0" 12 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod model; 2 | pub mod visit; 3 | pub mod visit_many; 4 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/codegen/model.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::parser::model::ModelInfo; 5 | 6 | 7 | pub fn generate(model_info: ModelInfo) -> TokenStream { 8 | let tokens = match model_info { 9 | ModelInfo::Transformer { 10 | generics, 11 | context, 12 | input_data, 13 | input_meta, 14 | input_extra, 15 | output_data, 16 | output_meta, 17 | error, 18 | body, 19 | } => { 20 | let input_extra = match input_extra { 21 | Some(typ) => quote!{#typ}, 22 | None => quote!{()} 23 | }; 24 | 25 | quote!{ 26 | impl #generics Model<#input_data, #input_meta> for #context { 27 | type Result = std::result::Result< 28 | Node<#output_data, #output_meta>, 29 | #error 30 | >; 31 | type Extra = #input_extra; 32 | 33 | fn visit( 34 | &mut self, 35 | node: &Node<#input_data, #input_meta>, 36 | extra: Self::Extra, 37 | ) -> Self::Result { 38 | #body 39 | } 40 | } 41 | } 42 | }, 43 | ModelInfo::Interpreter { 44 | generics, 45 | context, 46 | input_data, 47 | input_meta, 48 | input_extra, 49 | output, 50 | error, 51 | body, 52 | } => { 53 | let input_extra = match input_extra { 54 | Some(typ) => quote!{#typ}, 55 | None => quote!{()} 56 | }; 57 | 58 | quote!{ 59 | impl #generics Model<#input_data, #input_meta> for #context { 60 | type Result = std::result::Result<#output, #error>; 61 | type Extra = #input_extra; 62 | 63 | fn visit( 64 | &mut self, 65 | node: &Node<#input_data, #input_meta>, 66 | extra: Self::Extra, 67 | ) -> Self::Result { 68 | #body 69 | } 70 | } 71 | } 72 | }, 73 | }; 74 | 75 | TokenStream::from(tokens) 76 | } 77 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/codegen/visit.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::Expr; 4 | 5 | pub fn generate(child_node: Expr, extra: Option) -> TokenStream { 6 | let extra = match extra { 7 | Some(toks) => quote!{#toks}, 8 | None => quote!{()}, 9 | }; 10 | 11 | let tokens = quote!{ 12 | self.visit(#child_node, #extra)? 13 | }; 14 | 15 | TokenStream::from(tokens) 16 | } 17 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/codegen/visit_many.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::Expr; 4 | 5 | pub fn generate(iterator: Expr, extra: Option) -> TokenStream { 6 | let extra = match extra { 7 | Some(toks) => quote!{#toks}, 8 | None => quote!{()}, 9 | }; 10 | 11 | let tokens = quote!{ 12 | { 13 | let it = &#iterator; 14 | let mut outputs = Vec::with_capacity(it.len()); 15 | 16 | for child_node in it.iter() { 17 | let output = self.visit(child_node, #extra)?; 18 | outputs.push(output); 19 | } 20 | 21 | outputs 22 | } 23 | }; 24 | 25 | TokenStream::from(tokens) 26 | } 27 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::parse_macro_input; 3 | 4 | mod parser; 5 | mod codegen; 6 | 7 | #[proc_macro] 8 | pub fn model(input: TokenStream) -> TokenStream { 9 | let model_info = parse_macro_input!(input as parser::model::ModelInfo); 10 | codegen::model::generate(model_info) 11 | } 12 | 13 | #[proc_macro] 14 | pub fn visit(input: TokenStream) -> TokenStream { 15 | let visit_args = parse_macro_input!(input as parser::visit::VisitArgs); 16 | codegen::visit::generate(visit_args.first, visit_args.extra) 17 | } 18 | 19 | #[proc_macro] 20 | pub fn visit_many(input: TokenStream) -> TokenStream { 21 | let visit_args = parse_macro_input!(input as parser::visit::VisitArgs); 22 | codegen::visit_many::generate(visit_args.first, visit_args.extra) 23 | } 24 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod model; 2 | pub mod visit; 3 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/parser/model/assoc.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream, ParseBuffer, Result}; 2 | use syn::*; 3 | 4 | pub trait AssociatedTypeKeyword { 5 | fn kw() -> &'static str; 6 | } 7 | 8 | pub struct AssociatedType { 9 | pub typ: Type, 10 | _marker: std::marker::PhantomData 11 | } 12 | 13 | pub struct OptionalAssociatedType( 14 | pub Option>, 15 | ); 16 | 17 | impl AssociatedType { 18 | fn peek(input: &ParseBuffer) -> bool { 19 | input.fork().parse::().is_ok() 20 | } 21 | } 22 | 23 | impl Parse for AssociatedType { 24 | fn parse(input: &ParseBuffer) -> Result { 25 | let kw = T::kw(); 26 | 27 | input.parse::()?; 28 | 29 | let tok: Ident = input.parse()?; 30 | if tok != kw { 31 | return Err(Error::new(tok.span(), format!("expected `{kw}`"))); 32 | } 33 | input.parse::()?; 34 | let typ: Type = input.parse()?; 35 | input.parse::()?; 36 | 37 | Ok(Self { typ, _marker: std::marker::PhantomData{} }) 38 | } 39 | } 40 | 41 | impl Parse for OptionalAssociatedType { 42 | fn parse(input: ParseStream) -> Result { 43 | if AssociatedType::::peek(input) { 44 | let data = input.parse::>()?; 45 | Ok(Self(Some(data))) 46 | } 47 | else { 48 | Ok(Self(None)) 49 | } 50 | } 51 | } 52 | 53 | pub struct AssociatedInputData; 54 | pub struct AssociatedInputMeta; 55 | pub struct AssociatedInputExtra; 56 | pub struct AssociatedOutputData; 57 | pub struct AssociatedOutputMeta; 58 | pub struct AssociatedOutput; 59 | pub struct AssociatedError; 60 | 61 | impl AssociatedTypeKeyword for AssociatedInputData { 62 | fn kw() -> &'static str { "InputData" } 63 | } 64 | 65 | impl AssociatedTypeKeyword for AssociatedInputMeta { 66 | fn kw() -> &'static str { "InputMeta" } 67 | } 68 | 69 | impl AssociatedTypeKeyword for AssociatedInputExtra { 70 | fn kw() -> &'static str { "InputExtra" } 71 | } 72 | 73 | impl AssociatedTypeKeyword for AssociatedOutputData { 74 | fn kw() -> &'static str { "OutputData" } 75 | } 76 | 77 | impl AssociatedTypeKeyword for AssociatedOutputMeta { 78 | fn kw() -> &'static str { "OutputMeta" } 79 | } 80 | 81 | impl AssociatedTypeKeyword for AssociatedOutput { 82 | fn kw() -> &'static str { "Output" } 83 | } 84 | 85 | impl AssociatedTypeKeyword for AssociatedError { 86 | fn kw() -> &'static str { "Error" } 87 | } 88 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/parser/model/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream, Result}; 2 | use syn::*; 3 | 4 | mod assoc; 5 | use self::assoc::*; 6 | 7 | pub enum ModelInfo { 8 | Transformer { 9 | generics: Generics, 10 | context: Type, 11 | input_data: Type, 12 | input_meta: Type, 13 | input_extra: Option, 14 | output_data: Type, 15 | output_meta: Type, 16 | error: Type, 17 | body: Expr, 18 | }, 19 | Interpreter { 20 | generics: Generics, 21 | context: Type, 22 | input_data: Type, 23 | input_meta: Type, 24 | input_extra: Option, 25 | output: Type, 26 | error: Type, 27 | body: Expr, 28 | } 29 | } 30 | 31 | impl Parse for ModelInfo { 32 | fn parse(input: ParseStream) -> Result { 33 | input.parse::()?; 34 | 35 | let generics: Generics = input.parse()?; 36 | 37 | let model_type: Ident = input.parse()?; 38 | match model_type { 39 | t if t == "Transformer" => Self::parse_transformer(input, generics), 40 | t if t == "Interpreter" => Self::parse_interpreter(input, generics), 41 | _ => Err(Error::new(model_type.span(), format!("invalid model type: {model_type}"))), 42 | } 43 | } 44 | } 45 | 46 | impl ModelInfo { 47 | fn parse_transformer(input: ParseStream, generics: Generics) -> Result { 48 | input.parse::()?; 49 | 50 | let context: Type = input.parse()?; 51 | let info; braced!(info in input); 52 | 53 | let input_data: AssociatedType = info.parse()?; 54 | let input_meta: AssociatedType = info.parse()?; 55 | let input_extra: OptionalAssociatedType = info.parse()?; 56 | let output_data: AssociatedType = info.parse()?; 57 | let output_meta: AssociatedType = info.parse()?; 58 | let error: AssociatedType = info.parse()?; 59 | 60 | let tok: Ident = info.parse()?; 61 | if tok != "visit" { 62 | return Err(Error::new(tok.span(), "expected `visit`")); 63 | } 64 | 65 | let body: Expr = info.parse()?; 66 | 67 | Ok(Self::Transformer { 68 | generics, 69 | context, 70 | input_data: input_data.typ, 71 | input_meta: input_meta.typ, 72 | input_extra: input_extra.0.map(|t| t.typ), 73 | output_data: output_data.typ, 74 | output_meta: output_meta.typ, 75 | error: error.typ, 76 | body, 77 | }) 78 | } 79 | 80 | fn parse_interpreter(input: ParseStream, generics: Generics) -> Result { 81 | input.parse::()?; 82 | 83 | let context: Type = input.parse()?; 84 | let info; braced!(info in input); 85 | 86 | let input_data: AssociatedType = info.parse()?; 87 | let input_meta: AssociatedType = info.parse()?; 88 | let input_extra: OptionalAssociatedType = info.parse()?; 89 | let output: AssociatedType = info.parse()?; 90 | let error: AssociatedType = info.parse()?; 91 | 92 | let tok: Ident = info.parse()?; 93 | if tok != "visit" { 94 | return Err(Error::new(tok.span(), "expected `visit`")); 95 | } 96 | 97 | let body: Expr = info.parse()?; 98 | 99 | Ok(Self::Interpreter { 100 | generics, 101 | context, 102 | input_data: input_data.typ, 103 | input_meta: input_meta.typ, 104 | input_extra: input_extra.0.map(|t| t.typ), 105 | output: output.typ, 106 | error: error.typ, 107 | body, 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /sources/utils/ast-macros/src/parser/visit.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream, Result}; 2 | use syn::*; 3 | 4 | pub struct VisitArgs { 5 | pub first: Expr, 6 | pub extra: Option, 7 | } 8 | 9 | impl Parse for VisitArgs { 10 | fn parse(input: ParseStream) -> Result { 11 | let first: Expr = input.parse()?; 12 | 13 | let extra = if !input.is_empty() { 14 | input.parse::()?; 15 | Some(input.parse::()?) 16 | } 17 | else { 18 | None 19 | }; 20 | 21 | Ok(Self { first, extra }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /www/assets/languages/letlang.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Letlang", 4 | "patterns": [ 5 | { 6 | "include": "#comments" 7 | }, 8 | { 9 | "include": "#literals" 10 | }, 11 | { 12 | "include": "#keywords" 13 | }, 14 | { 15 | "include": "#operators" 16 | }, 17 | { 18 | "include": "#builtins" 19 | } 20 | ], 21 | "repository": { 22 | "comments": { 23 | "patterns": [{ 24 | "name": "comment.line.number-sign.letlang", 25 | "begin": "#", 26 | "end": "\\n" 27 | }] 28 | }, 29 | "literals": { 30 | "patterns": [ 31 | {"include": "#booleans"}, 32 | {"include": "#numbers"}, 33 | {"include": "#strings"}, 34 | {"include": "#atoms"} 35 | ] 36 | }, 37 | "booleans": { 38 | "patterns": [{ 39 | "name": "constant.language.letlang", 40 | "match": "\\b(true|false)\\b" 41 | }] 42 | }, 43 | "numbers": { 44 | "patterns": [ 45 | { 46 | "name": "constant.numeric.letlang", 47 | "match": "\\b(0b_*[01][_01]*)\\b" 48 | }, 49 | { 50 | "name": "constant.numeric.letlang", 51 | "match": "\\b(0o_*[0-7][_0-7]*)\\b" 52 | }, 53 | { 54 | "name": "constant.numeric.letlang", 55 | "match": "\\b([0-9][_0-9]*)\\b" 56 | }, 57 | { 58 | "name": "constant.numeric.letlang", 59 | "match": "\\b(0x_*[0-9a-fA-F][_0-9a-fA-F]*)\\b" 60 | }, 61 | { 62 | "name": "constant.numeric.letlang", 63 | "match": "\\b(((\\d+\\.?\\d*)|(\\.\\d+))(([eE][+-]?)?\\d+)?)\\b" 64 | } 65 | ] 66 | }, 67 | "strings": { 68 | "name": "string.quoted.double.letlang", 69 | "begin": "\"", 70 | "end": "\"", 71 | "patterns": [ 72 | { 73 | "name": "constant.character.escape.letlang", 74 | "match": "\\\\." 75 | } 76 | ] 77 | }, 78 | "atoms": { 79 | "patterns": [{ 80 | "name": "constant.language.letlang", 81 | "begin": "@(('(?:[^']|\\\\')+')|([_a-zA-Z][_a-zA-Z0-9]*))", 82 | "end": "\\b" 83 | }] 84 | }, 85 | "keywords": { 86 | "patterns": [ 87 | {"include": "#kw-visibility"}, 88 | {"include": "#kw-statements"}, 89 | {"include": "#kw-controlflow"} 90 | ] 91 | }, 92 | "kw-visibility": { 93 | "patterns": [{ 94 | "name": "storage.modifier.letlang", 95 | "match": "\\b(pub)\\b" 96 | }] 97 | }, 98 | "kw-statements": { 99 | "patterns": [{ 100 | "name": "storage.type.letlang", 101 | "match": "\\b(module|from|import|as|let|class|func|tailrec|effect|extern|when|with)\\b" 102 | }] 103 | }, 104 | "kw-controlflow": { 105 | "patterns": [{ 106 | "name": "keyword.control.letlang", 107 | "match": "\\b(do|intercept|catch|perform|throw|spawn|receive|after|match|cond|else|recurse|final)\\b" 108 | }] 109 | }, 110 | "operators": { 111 | "patterns": [ 112 | { 113 | "name": "keyword.other.letlang", 114 | "match": "\\;|\\-\\>|\\,|\\:|\\:\\:|\\:\\=|\\$" 115 | }, 116 | { 117 | "name": "keyword.operator.letlang", 118 | "match": "\\!|\\||\\&|\\~|\\^|\\<\\<|\\>\\>" 119 | }, 120 | { 121 | "name": "keyword.operator.letlang", 122 | "match": "\\b(or|and|not)\\b" 123 | }, 124 | { 125 | "name": "keyword.operator.letlang", 126 | "match": "\\=|\\!\\=|\\<|\\<\\=|\\>\\=|\\>" 127 | }, 128 | { 129 | "name": "keyword.operator.letlang", 130 | "match": "\\+|\\-|\\*|\\/|\\%|\\*\\*" 131 | }, 132 | { 133 | "name": "keyword.operator.letlang", 134 | "match": "\\|\\>" 135 | }, 136 | { 137 | "name": "keyword.operator.letlang", 138 | "match": "\\b(in|is)\\b" 139 | }, 140 | { 141 | "name": "keyword.operator.letlang", 142 | "match": "\\{|\\}|\\(|\\)|\\[|\\]" 143 | } 144 | ] 145 | }, 146 | "builtins": { 147 | "patterns": [{ 148 | "name": "support.class.letlang", 149 | "match": "\\b(bool|number|int|string|atom|list)\\b" 150 | }] 151 | } 152 | }, 153 | "scopeName": "source.letlang" 154 | } 155 | -------------------------------------------------------------------------------- /www/assets/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | * { 7 | @apply transition-all duration-150 ease-in-out; 8 | } 9 | } 10 | 11 | @layer components { 12 | .markdown h1 { 13 | @apply first:mt-0 my-6; 14 | @apply text-5xl font-semibold; 15 | } 16 | 17 | .markdown h2 { 18 | @apply first:mt-0 my-6; 19 | @apply text-4xl font-semibold; 20 | } 21 | 22 | .markdown h3 { 23 | @apply first:mt-0 my-6; 24 | @apply text-3xl font-semibold; 25 | } 26 | 27 | .markdown h4 { 28 | @apply first:mt-0 my-6; 29 | @apply text-2xl font-semibold; 30 | } 31 | 32 | .markdown h5 { 33 | @apply first:mt-0 my-6; 34 | @apply text-xl font-semibold; 35 | } 36 | 37 | .markdown h6 { 38 | @apply first:mt-0 my-6; 39 | @apply text-lg font-semibold; 40 | } 41 | 42 | .markdown p { 43 | @apply first:mt-0 my-3; 44 | @apply text-base; 45 | } 46 | 47 | .markdown a { 48 | @apply text-indigo-500 hover:text-red-600; 49 | } 50 | 51 | .markdown ul { 52 | @apply my-3 ml-6; 53 | @apply list-disc list-inside; 54 | } 55 | 56 | .markdown ol { 57 | @apply my-3 ml-6; 58 | @apply list-decimal list-inside; 59 | } 60 | 61 | .markdown pre { 62 | @apply p-3 my-6 mx-3; 63 | @apply overflow-x-auto; 64 | @apply shadow-md; 65 | } 66 | 67 | .markdown :not(pre) > code { 68 | @apply px-1 py-0.5; 69 | @apply bg-gray-200 text-red-500; 70 | } 71 | 72 | .markdown hr { 73 | @apply h-1; 74 | @apply bg-gray-300; 75 | } 76 | 77 | .markdown table { 78 | @apply w-full; 79 | @apply border border-collapse border-gray-300; 80 | @apply shadow-md; 81 | } 82 | 83 | .markdown table.table-hoverable tr { 84 | @apply hover:bg-gray-100; 85 | } 86 | 87 | .markdown table th { 88 | @apply px-4 py-2; 89 | @apply bg-gray-100; 90 | @apply border border-gray-300; 91 | } 92 | 93 | .markdown table td { 94 | @apply px-4 py-2; 95 | @apply border border-gray-300; 96 | } 97 | 98 | .markdown blockquote { 99 | @apply px-4 py-2 my-6; 100 | @apply border-l-4 border-gray-300; 101 | @apply bg-gray-100; 102 | @apply shadow-md; 103 | } 104 | 105 | .markdown blockquote p { 106 | @apply first:mt-0 last:mb-0; 107 | } 108 | 109 | .grammkit-diagram { 110 | @apply my-6; 111 | @apply flex flex-col items-stretch; 112 | @apply border border-emerald-500; 113 | @apply shadow-sm hover:shadow-md; 114 | } 115 | 116 | .grammkit-diagram .grammkit-diagram-title { 117 | @apply px-2 py-1; 118 | @apply text-white text-lg font-mono; 119 | @apply bg-emerald-500; 120 | } 121 | 122 | .grammkit-diagram .grammkit-diagram-svg { 123 | @apply overflow-x-auto; 124 | } 125 | 126 | .grammkit-diagram .grammkit-diagram-src > details { 127 | @apply bg-gray-100; 128 | @apply transition duration-150 ease-in-out; 129 | } 130 | 131 | .grammkit-diagram .grammkit-diagram-src > details > summary { 132 | @apply p-3; 133 | @apply text-indigo-500 hover:text-red-600; 134 | @apply hover:cursor-pointer; 135 | } 136 | } 137 | 138 | @layer utilities { 139 | .leading-16 { 140 | line-height: 64px; 141 | } 142 | 143 | .left-4\/5 { 144 | left: 80%; 145 | } 146 | 147 | table th, 148 | table td { 149 | @apply text-center; 150 | } 151 | 152 | table.table-col0-minw th:first-child, 153 | table.table-col0-minw td:first-child { 154 | @apply w-0; 155 | @apply whitespace-nowrap; 156 | } 157 | 158 | table.table-col0-left th:first-child, 159 | table.table-col0-left td:first-child { 160 | @apply text-left; 161 | } 162 | 163 | table.table-col1-left th:nth-child(2), 164 | table.table-col1-left td:nth-child(2) { 165 | @apply text-left; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /www/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | title: Letlang 3 | baseURL: "https://letlang.dev/" 4 | 5 | highlighter: 6 | theme: monokai 7 | extraLanguages: 8 | - id: letlang 9 | scopeName: source.letlang 10 | aliases: [letlang] 11 | grammarPath: assets/languages/letlang.tmLanguage.json 12 | 13 | plugins: 14 | - '../contrib/grammkit' 15 | -------------------------------------------------------------------------------- /www/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Homepage 3 | --- 4 | 5 |
6 | 7 | ![status: WIP](https://img.shields.io/badge/status-WIP-red?style=flat-square) 8 | ![version: 0.0.0](https://img.shields.io/badge/version-v0.0.0-brightgreen?style=flat-square) 9 | ![license: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square) 10 | 11 |
12 | 13 | **Letlang** is a general purpose functional programming language. 14 | 15 | Implemented in [Rust](https://www.rust-lang.org), it compiles to Rust, allowing 16 | you to target any platform supported by [LLVM](https://llvm.org). 17 | 18 |
19 |
Work in progress
20 | 21 |
22 | 23 | **Letlang** is in a very early stage. 24 | 25 | The documentation and code examples you may find on this website are **NOT** 26 | definitive and may be subject to change. 27 | 28 |
29 |
30 | 31 | --- 32 | 33 | {% include "nav.html" %} 34 | 35 | --- 36 | 37 |
38 | 39 |
40 |
Dynamic Type System
41 |
42 | 43 | ```letlang 44 | let even: class[int] { 45 | (n) -> n % 2 = 0, 46 | }; 47 | 48 | let odd: class[int & !even]; 49 | ``` 50 | 51 |
52 | 53 | A value does not have a single type, instead the type definition determines what 54 | value it contains. 55 | 56 | `42` is a `number`, an `int`, an `even` but not an `odd`. 57 | 58 |
59 |
60 | 61 |
62 |
Pattern Matching
63 |
64 | 65 | ```letlang 66 | (@ok, val) := (@ok, 42); 67 | 68 | (@ok, res) := (@error, @oops); # error 69 | 70 | (a, b) := (1, 2); 71 | ``` 72 | 73 |
74 | 75 | The `:=` operator is a **pattern matching** operator, used to bind values to new 76 | variables, and extract values from complex data structures. 77 | 78 |
79 |
80 | 81 |
82 |
Lazy Constraints
83 |
84 | 85 | ```letlang 86 | let (x: int, y: int) with x > y { 87 | x := 0; 88 | y := 1; # error 89 | }; 90 | ``` 91 | 92 |
93 | 94 | `let` expressions create a new scope where future bindings are constrained to a 95 | type and a guard expression. 96 | 97 |
98 |
99 | 100 |
101 |
Actor Based Concurrency
102 |
103 | 104 | ```letlang 105 | let task: func[() -> @ok] { 106 | () -> @ok, 107 | }; 108 | 109 | let pub main: func[() -> @ok] { 110 | () -> { 111 | (@ok, proc_id) := spawn task(); 112 | # do something 113 | @ok; 114 | }, 115 | }; 116 | ``` 117 | 118 |
119 | 120 | Run any function in a new **Letlang** *process* (concurrent task), inspired by 121 | [Erlang](https://www.erlang.org)'s design, and backed by [Tokio](https://tokio.rs). 122 | 123 | Your program will live as long as there is a running *process* even if the 124 | `main()` function returned. 125 | 126 | Every *process* have a unique identifier (`pid`) used for communication and 127 | monitoring. 128 | 129 |
130 |
131 | 132 |
133 |
Message Passing
134 |
135 | 136 | ```letlang 137 | (@ok, proc_id) := spawn task(std::proc::self()); 138 | # send a signal to this process on exit 139 | std::proc::link(proc_id); 140 | 141 | # blocks until signal is received 142 | receive { 143 | (@message, proc_id, msg) -> { 144 | # message signal received 145 | }, 146 | (@exited, proc_id, reason) -> { 147 | # exit signal received 148 | }, 149 | }; 150 | ``` 151 | 152 | ```letlang 153 | @ok := std::proc::send(proc_id, "hello"); 154 | ``` 155 | 156 |
157 | 158 | Each *process* has a mailbox where signals can be queued. There are 2 kinds of 159 | signals: **message** and **exited**. 160 | 161 | Processes can send messages to each others. When a process exits (or crashes), 162 | an **exited** signal is sent to every linked process. 163 | 164 |
165 |
166 | 167 |
168 |
Effect Handlers
169 |
170 | 171 | ```letlang 172 | let log: effect[(string) -> @ok]; 173 | ``` 174 | 175 | ```letlang 176 | do { 177 | @ok := perform log("hello"); 178 | } 179 | intercept log { 180 | (msg) -> std::io::println(msg), 181 | }; 182 | ``` 183 | 184 |
185 | 186 | Delegate the handling of side effects to the caller, or let them bubble up to 187 | the **Letlang** runtime. 188 | 189 |
190 |
191 | 192 |
193 | -------------------------------------------------------------------------------- /www/content/book/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Handbook 3 | layout: book 4 | --- 5 | 6 | # Handbook 7 | 8 |
9 |
10 |
Work in progress
11 |
-------------------------------------------------------------------------------- /www/content/lep/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Letlang Enhancement Proposals 3 | layout: lep 4 | --- 5 | 6 | # Letlang Enhancement Proposals 7 | 8 |
9 |
10 |
Work in progress
11 |
12 | -------------------------------------------------------------------------------- /www/content/spec/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Specification 3 | layout: spec 4 | --- 5 | 6 | # Preface 7 | 8 | This book specifies the semantics of the Letlang programming language, as well 9 | as the requirements for its implementation. 10 | 11 | The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, 12 | “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in those documents are to be 13 | interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). 14 | 15 | ## Enhancement Proposals 16 | 17 | To get an history of changes made to this specification, please consult this 18 | [page](./lep). 19 | -------------------------------------------------------------------------------- /www/content/spec/expressions/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Expressions 3 | layout: spec 4 | weight: 7 5 | --- 6 | 7 | # Expressions 8 | 9 | ### Syntax 10 | 11 | {% apply grammkit %} 12 | expression 13 | = ("(" expression ")") 14 | / destructuring_expression 15 | / typecheck_expression 16 | / pipe_expression 17 | / throw_expression 18 | / binary_operation_expression 19 | / unary_binary_expression 20 | / access_expression 21 | / expression_term 22 | 23 | destructuring_expression = pattern ":=" expression 24 | 25 | typecheck_expression = expression "is" "not"? type_expression 26 | 27 | pipe_expression = expression "|>" function_call_expression 28 | 29 | throw_expression = "throw" expression 30 | 31 | binary_operation_expression 32 | = expression ( 33 | logic_binary_operator 34 | / bitwise_arithmetic_binary_operator 35 | / inclusion_binary_operator 36 | / comparison_binary_operator 37 | / bitwise_shift_binary_operator 38 | / concatenation_binary_operator 39 | / arithmetic_binary_operator 40 | ) expression 41 | 42 | unary_operation_expression 43 | = ( 44 | arithmetic_unary_operator 45 | / logic_unary_operator 46 | / bitwise_arithmetic_unary_operator 47 | ) expression 48 | 49 | access_expression = expression "." identifier 50 | 51 | expression_term 52 | = literal_expression 53 | / tuple_expression 54 | / namedtuple_expression 55 | / list_expression 56 | / list_headtail_expression 57 | / let_expression 58 | / match_expression 59 | / cond_expression 60 | / receive_expression 61 | / tailrec_expression 62 | / function_call_expression 63 | / effect_call_expression 64 | / spawn_expression 65 | / do_expression 66 | / variable 67 | 68 | literal_expression = atom / bool / number / string 69 | 70 | tailrec_expression 71 | = ("recurse" "[" expression ("," expression)* ","? "]") 72 | / ("final" "[" expression "]") 73 | 74 | variable = identifier 75 | {% endapply %} 76 | -------------------------------------------------------------------------------- /www/content/spec/expressions/cond.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Conditional block 3 | layout: spec 4 | weight: 8 5 | --- 6 | 7 | # Conditional block 8 | 9 | A `cond` expression allows branching based on conditions. 10 | 11 | ### Syntax 12 | 13 | {% apply grammkit %} 14 | cond_expression = "cond" "{" (cond_branch ",")+ cond_else_branch ","? "}" 15 | 16 | cond_branch = expression "->" branch_body 17 | 18 | cond_else_branch = "else" "->" branch_body 19 | 20 | branch_body 21 | = expression 22 | / "{" (expression ";")+ "}" 23 | {% endapply %} 24 | 25 | 26 | ### Example 27 | 28 | ```letlang 29 | cond { 30 | age < 18 -> "minor", 31 | age < 60 -> "adult", 32 | else -> "senior", 33 | }; 34 | ``` 35 | 36 | ### Semantics 37 | 38 | Every conditional expression MUST return a boolean (`true` or `false`). If they 39 | don't, an exception MUST be thrown, the value of that exception is defined by 40 | the implementation. 41 | 42 | If a conditional expression throws an exception, that exception MUST NOT be 43 | ignored, and MUST bubble up. 44 | 45 | The body of the branch with the first conditional expression that returns `true` 46 | MUST be evaluated. The `cond` expression MUST evaluate to the value this branch 47 | evaluates to. 48 | 49 | The conditional expressions following one that returned `true` MUST NOT be 50 | evaluated (this is short-circuiting). 51 | 52 | If no conditional expression returns `true`, the default `else` branch MUST be 53 | evaluated. 54 | 55 | Each branch MUST create a new scope. 56 | -------------------------------------------------------------------------------- /www/content/spec/expressions/containers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Containers 3 | layout: spec 4 | weight: 5 5 | --- 6 | 7 | # Containers 8 | 9 | Letlang has 3 types of containers: 10 | 11 | - [tuples](./spec/expressions/containers#tuples) 12 | - [named tuples](./spec/expressions/containers#named-tuples) 13 | - [lists](./spec/expressions/containers#lists) 14 | 15 | ## Tuples 16 | 17 | ### Syntax 18 | 19 | {% apply grammkit %} 20 | tuple_expression 21 | = ("(" (expression ",")? ")") 22 | / ("(" expression ("," expression)* ","? ")") 23 | {% endapply %} 24 | 25 | 26 | ### Example 27 | 28 | ```letlang 29 | (1, 2); 30 | ``` 31 | 32 | ## Named tuples 33 | 34 | Order in a named tuple MUST NOT matter. 35 | 36 | ### Syntax 37 | 38 | {% apply grammkit %} 39 | namedtuple_expression 40 | = ("{" (namedtuple_pair ",")? "}") 41 | / ("{" namedtuple_pair ("," namedtuple_pair)* ","? "}") 42 | 43 | namedtuple_pair = identifier ":" expression 44 | 45 | access_expression = expression "." identifier 46 | {% endapply %} 47 | 48 | 49 | ### Example 50 | 51 | ```letlang 52 | user := { name: "john", age: 42 }; 53 | "john" := user.name; 54 | ``` 55 | 56 | ## Lists 57 | 58 | ### Syntax 59 | 60 | {% apply grammkit %} 61 | list_expression 62 | = ("[" (expression ",")? "]") 63 | / ("[" expression ("," expression)* ","? "]") 64 | 65 | list_headtail_expression 66 | = "[" expression ("," expression)* ","? "|" expression "]" 67 | {% endapply %} 68 | 69 | 70 | ### Example 71 | 72 | ```letlang 73 | l := [3, 4, 5]; 74 | l := [1, 2 | l]; 75 | [1, 2, 3, 4, 5] := l; 76 | ``` 77 | -------------------------------------------------------------------------------- /www/content/spec/expressions/destructuring.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Destructuring 3 | layout: spec 4 | weight: 2 5 | --- 6 | 7 | # Destructuring 8 | 9 | ### Syntax 10 | 11 | {% apply grammkit %} 12 | destructuring_expression = pattern ":=" expression 13 | {% endapply %} 14 | 15 | 16 | ### Example 17 | 18 | ```letlang 19 | (@ok, val) := (@ok, 42); 20 | ``` 21 | 22 | ### Semantics 23 | 24 | A destructuring expression MUST evaluate to the matched value, allowing chaining: 25 | 26 | ```letlang 27 | # evaluates to (1, 2) 28 | (a, 2) := (1, b) := (1, 2); 29 | # a = 1, b = 2 30 | ``` 31 | 32 | If the pattern on the left does not match the expression on the right, an 33 | exception MUST be thrown. The value of this exception is defined by the 34 | implementation: 35 | 36 | ```letlang 37 | @ok := @error; 38 | # equivalent to 39 | throw @implementation_defined_error; 40 | ``` 41 | -------------------------------------------------------------------------------- /www/content/spec/expressions/do.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Do block 3 | layout: spec 4 | weight: 10 5 | --- 6 | 7 | # Do block 8 | 9 | A `do` expression creates a capture scope to intercept effects and/or catch 10 | exceptions. 11 | 12 | ### Syntax 13 | 14 | {% apply grammkit %} 15 | do_expression = "do" "{" (expression ";")+ "}" intercept_block* catch_block? 16 | 17 | intercept_block 18 | = "intercept" effect_ref 19 | "{" pattern_matching_clause ("," pattern_matching_clause)* ","? "}" 20 | 21 | effect_ref 22 | = symbol_path 23 | ("<" type_expression ("," type_expression)* ","? ">")? 24 | 25 | catch_block 26 | = "catch" 27 | "{" pattern_matching_clause ("," pattern_matching_clause)* ","? "}" 28 | {% endapply %} 29 | 30 | 31 | ### Example 32 | 33 | Consider the following effects: 34 | 35 | ```letlang 36 | let log: effect[(string) -> @ok]; 37 | let compare: effect[(t, t) -> @lesser | @greater | @equal]; 38 | ``` 39 | 40 | ```letlang 41 | do { 42 | # ... 43 | } 44 | intercept log { 45 | (msg) -> @ok, 46 | } 47 | intercept compare { 48 | (a, b) when a < b -> @lesser, 49 | (a, b) when a > b -> @greater, 50 | (a, b) when a = b -> @equal, 51 | } 52 | catch { 53 | ((@error, reason)) -> { 54 | # ... 55 | }, 56 | }; 57 | ``` 58 | 59 | ### Semantics 60 | 61 | The body of a `do` expression MUST create a new scope. 62 | 63 | The `do` expression MUST evaluate to the value its body evaluates to. 64 | 65 | When an effect is performed, or an exception is thrown, the runtime MUST check 66 | if one of the `intercept` or `catch` block matches the effect or exception. 67 | 68 | When an effect is intercepted, if no clause matches, the effect MUST bubble up. 69 | 70 | When an exception is caught, if no clause matches, the exception MUST bubble up. 71 | 72 | When an exception is caught and a clause matched, the `do` expression MUST 73 | evaluate to the value the clause evaluates to. 74 | -------------------------------------------------------------------------------- /www/content/spec/expressions/effects.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Effects 3 | layout: spec 4 | weight: 12 5 | --- 6 | 7 | # Effects 8 | 9 | Letlang provides the ability to delegate a computation to the caller. This is 10 | done by first declaring the signature of an effect (see 11 | [this page](./spec/symbols/effect)), then using the `perform` keyword, the 12 | callee can "call" an effect handler defined by the caller using a `do` 13 | expression. 14 | 15 | ### Syntax 16 | 17 | {% apply grammkit %} 18 | effect_call_expression 19 | = "perform" effect_ref "(" expression ("," expression)* ","? ")" 20 | 21 | effect_ref = symbol_path ("<" type_expression ("," type_expression)* ","? ">")? 22 | {% endapply %} 23 | 24 | 25 | ### Example 26 | 27 | Consider this effect: 28 | 29 | ```letlang 30 | let log: effect[(string) -> @ok]; 31 | ``` 32 | 33 | ```letlang 34 | @ok := perform log("hello world"); 35 | ``` 36 | 37 | Or: 38 | 39 | ```letlang 40 | let compare: effect[(t, t) -> @lesser | @equal | @greater] 41 | ``` 42 | 43 | ```letlang 44 | @lesser := perform compare(1, 2); 45 | ``` 46 | 47 | ### Semantics 48 | 49 | When calling an effect, the arguments MUST be evaluated in order. 50 | 51 | Calling an effect MUST interrupt the function's execution. If a matching effect 52 | handler is found in an outer `do` expression, the handler MUST be executed. 53 | 54 | The function MUST resume with the handler's return value. 55 | 56 | If the handler throws an exception, that exception MUST be thrown from the 57 | effect call-site: 58 | 59 | ```letlang 60 | let will_throw: effect[() -> @ok]; 61 | ``` 62 | 63 | ```letlang 64 | @error_caught := do { 65 | do { 66 | perform will_throw(); 67 | } 68 | catch { 69 | (@oops) -> @error_caught, 70 | }; 71 | } 72 | intercept will_throw { 73 | () -> throw @oops, 74 | }; 75 | ``` 76 | 77 | **NB:** This can be shortened to: 78 | 79 | ```letlang 80 | @error_caught := do { 81 | perform will_throw(); 82 | } 83 | intercept will_throw { 84 | () -> throw @oops, 85 | } 86 | catch { 87 | (@oops) -> @error_caught, 88 | }; 89 | ``` 90 | 91 | If no handler is found, the effect call MUST be handled by the runtime. 92 | 93 | The runtime provides effect handlers for builtin effects. Those effects will be 94 | handled according to the [runtime specification](./spec/runtime/effects). 95 | However, if the effect is a user-defined effect, the runtime MUST throw an 96 | exception instead. The value of that exception is defined by the implementation. 97 | -------------------------------------------------------------------------------- /www/content/spec/expressions/exceptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exceptions 3 | layout: spec 4 | weight: 13 5 | --- 6 | 7 | # Exceptions 8 | 9 | Exceptions are effects that do not resume the function's execution. 10 | 11 | ### Syntax 12 | 13 | {% apply grammkit %} 14 | throw_expression = "throw" expression 15 | {% endapply %} 16 | 17 | 18 | ### Example 19 | 20 | ```letlang 21 | throw (@error, "some reason"); 22 | ``` 23 | 24 | ### Semantics 25 | 26 | Throwing an exception MUST interrupt the function's execution. If a matching 27 | exception handler is found in an outer `do` expression, the handler MUST be 28 | executed. 29 | 30 | If no handler is found, the current process MUST terminate. The runtime SHOULD 31 | display the exception with a stack trace. 32 | 33 | The `throw` expression MUST NOT evaluate to a value. Control MUST NOT be given 34 | back. -------------------------------------------------------------------------------- /www/content/spec/expressions/function-call.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Function Call 3 | layout: spec 4 | weight: 11 5 | --- 6 | 7 | # Function Call 8 | 9 | ### Syntax 10 | 11 | {% apply grammkit %} 12 | function_call_expression 13 | = symbol_path ("<" type_expression ("," type_expression)* ","? ">")? 14 | "(" expression ("," expression)* ","? ")" 15 | {% endapply %} 16 | 17 | 18 | ### Example 19 | 20 | ```letlang 21 | foo(42, @ok); 22 | ``` 23 | 24 | ### Semantics 25 | 26 | Function arguments MUST be evaluated in order: 27 | 28 | ```letlang 29 | foo(bar(), baz()); 30 | ``` 31 | 32 | Here, `bar()` is evaluated first, then `baz()`, finally the function `foo` is 33 | called. 34 | 35 | If an exception is thrown during the arguments evaluation, this MUST 36 | short-cirtcuit the evaluation of the remaining arguments: 37 | 38 | ```letlang 39 | foo(throw @error, @unreachable); 40 | ``` 41 | 42 | Here, `throw @error` is evaluated first, but an exception is thrown, therefore 43 | `@unreachable` is never evaluated. 44 | -------------------------------------------------------------------------------- /www/content/spec/expressions/lazy-assert.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Lazy Assertions 3 | layout: spec 4 | weight: 6 5 | --- 6 | 7 | # Lazy Assertions 8 | 9 | Lazy assertions rely on the `let` expression in order to constrain the value of 10 | future variable bindings. 11 | 12 | They provide 2 kinds of constraints: 13 | 14 | - **type constraints:** only values belonging to this type can be bound to this 15 | name 16 | - **guard expression:** when binding a value to a name, the guard expression 17 | MUST evaluate to true 18 | 19 | ### Syntax 20 | 21 | {% apply grammkit %} 22 | let_expression 23 | = "let" "(" let_type_bind ("," let_type_bind)* ","? ")" 24 | let_guard? "{" (expression ";")+ "}" 25 | 26 | let_type_bind = identifier ":" type_expression 27 | 28 | let_guard = "with" expression 29 | {% endapply %} 30 | 31 | 32 | ### Example 33 | 34 | ```letlang 35 | let (x: int, y: int) with x > y { 36 | x := 1; 37 | y := 2; # throws an exception because 1 > 2 is false 38 | }; 39 | ``` 40 | 41 | ### Semantics 42 | 43 | `let` expression MUST create a new scope. 44 | 45 | When destructuring an expression using the pattern matching operator `:=`, the 46 | runtime MUST verify that all `let` constraints that are in scope are satisfied. 47 | 48 | If a `let` constraint is not satisfied, pattern matching MUST fail. 49 | 50 | When nesting `let` expressions with a constraint on the same name, all 51 | constraints MUST be satisfied: 52 | 53 | ```letlang 54 | let (x: int) { 55 | let (x: string) { 56 | # ... 57 | }; 58 | }; 59 | 60 | # is equivalent to: 61 | 62 | let (x: int & string) { 63 | # ... 64 | }; 65 | ``` 66 | 67 | The guard expression of a `let` expression MUST return a boolean (`true` or 68 | `false`). 69 | 70 | If an exception is thrown in the guard expression, it SHOULD be ignored and be 71 | equivalent to returning `false`. 72 | -------------------------------------------------------------------------------- /www/content/spec/expressions/match.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Match block 3 | layout: spec 4 | weight: 7 5 | --- 6 | 7 | # Match block 8 | 9 | A `match` expression allows to branch by pattern on an expression. 10 | 11 | ### Syntax 12 | 13 | {% apply grammkit %} 14 | match_expression 15 | = "match" expression 16 | "{" pattern_matching_clause ("," pattern_matching_clause)* ","? "}" 17 | {% endapply %} 18 | 19 | 20 | ### Example 21 | 22 | ```letlang 23 | match result { 24 | (@ok) -> { 25 | # ... 26 | }, 27 | (@error) -> { 28 | # ... 29 | }, 30 | }; 31 | ``` 32 | 33 | ### Semantics 34 | 35 | A `match` expression MUST evaluate to the value the matching clause evaluates to. 36 | 37 | If no clause matches, and exception MUST be thrown, the value of that exception 38 | is defined by the implementation: 39 | 40 | ```letlang 41 | match 42 { 42 | (41) -> @ok, 43 | }; 44 | 45 | # is equivalent to: 46 | 47 | match 42 { 48 | (41) -> @ok, 49 | (_) -> throw @implementation_defined_error, 50 | }; 51 | ``` 52 | -------------------------------------------------------------------------------- /www/content/spec/expressions/operators.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Operators 3 | layout: spec 4 | weight: 1 5 | --- 6 | 7 | # Operators 8 | 9 | ## Syntax 10 | 11 | {% apply grammkit %} 12 | logic_binary_operator = "or" / "and" 13 | 14 | logic_unary_operator = "not" 15 | 16 | bitwise_arithmetic_binary_operator = "|" / "&" / "^" 17 | 18 | bitwise_arithmetic_unary_operator = "~" 19 | 20 | inclusion_binary_operator = "not"? "in" 21 | 22 | comparison_binary_operator = "<" / "<=" / "=" / "!=" / ">=" / ">" 23 | 24 | bitwise_shift_binary_operator = "<<" / ">>" 25 | 26 | concatenation_binary_operator 27 | = string_concatenation_operator 28 | / list_concatenation_operator 29 | 30 | string_concatenation_operator = "<>" 31 | 32 | list_concatenation_operator = "++" 33 | 34 | arithmetic_binary_operator = "+" / "-" / "*" / "/" / "%" / "**" 35 | 36 | arithmetic_unary_operator = "-" 37 | {% endapply %} 38 | 39 | 40 | ## Precedence Table 41 | 42 | > **NB:** Higher precedence have priority. 43 | 44 | | Operator | Precedence | Associativity | 45 | | --- | --- | --- | 46 | | `a := b` | 1 | right to left | 47 | | `a is b` | 2 | left to right | 48 | | `a is not b` | ^^ | ^^ | 49 | | `a |> b()` | 3 | ^^ | 50 | | `throw b` | 4 | right to left | 51 | | `a or b` | 5 | left to right | 52 | | `a and b` | 6 | ^^ | 53 | | `a | b` | 7 | ^^ | 54 | | `a ^ b` | 8 | ^^ | 55 | | `a & b` | 9 | ^^ | 56 | | `a in b` | 10 | ^^ | 57 | | `a not in b` | ^^ | ^^ | 58 | | `a = b` | 11 | ^^ | 59 | | `a != b` | ^^ | ^^ | 60 | | `a < b` | 12 | ^^ | 61 | | `a <= b` | ^^ | ^^ | 62 | | `a >= b` | ^^ | ^^ | 63 | | `a > b` | ^^ | ^^ | 64 | | `a << b` | 13 | ^^ | 65 | | `a >> b` | ^^ | ^^ | 66 | | `a <> b` | 14 | ^^ | 67 | | `a ++ b` | 15 | ^^ | 68 | | `a + b` | 16 | ^^ | 69 | | `a - b` | ^^ | ^^ | 70 | | `a * b` | 17 | ^^ | 71 | | `a / b` | ^^ | ^^ | 72 | | `a % b` | ^^ | ^^ | 73 | | `a ** b` | 18 | ^^ | 74 | | `-a` | 19 | right to left | 75 | | `not a` | ^^ | ^^ | 76 | | `~a` | ^^ | ^^ | 77 | | `a.b` | 20 | left to right | 78 | -------------------------------------------------------------------------------- /www/content/spec/expressions/pipeline.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pipeline 3 | layout: spec 4 | weight: 4 5 | --- 6 | 7 | # Pipeline 8 | 9 | The expression on the left MUST be injected as first argument to the function 10 | call on the right. 11 | 12 | ### Syntax 13 | 14 | {% apply grammkit %} 15 | pipe_expression = expression "|>" function_call_expression 16 | {% endapply %} 17 | 18 | 19 | ### Example 20 | 21 | ```letlang 22 | 1 |> add(5) |> mul(4); 23 | # equivalent to 24 | mul(add(1, 5), 4); 25 | ``` 26 | -------------------------------------------------------------------------------- /www/content/spec/expressions/receive.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Receive block 3 | layout: spec 4 | weight: 9 5 | --- 6 | 7 | # Receive block 8 | 9 | A `receive` expression is used to wait for a signal on the current process. 10 | 11 | A signal MUST be a tuple containing 3 elements: 12 | 13 | - the type of signal, as an atom (`@message` or `@exited`) 14 | - the PID of the process from which the signal originated 15 | - the payload of the signal 16 | 17 | ### Syntax 18 | 19 | {% apply grammkit %} 20 | receive_expression 21 | = "receive" "{" pattern_matching_clause ("," pattern_matching_clause)* ","? "}" 22 | receive_after_expression? 23 | 24 | receive_after_expression = "after" expression "{" (expression ";")+ "}" 25 | {% endapply %} 26 | 27 | 28 | ### Example 29 | 30 | ```letlang 31 | receive { 32 | (@message, proc_id, msg) -> { 33 | # ... 34 | }, 35 | (@exited, proc_id, reason) -> { 36 | # ... 37 | }, 38 | } 39 | after 1000 { 40 | # ... 41 | }; 42 | ``` 43 | 44 | ### Semantics 45 | 46 | A `receive` expression MUST block until a signal is received. 47 | 48 | The `receive` expression MUST evaluate to the value the matching clause 49 | evaluates to. 50 | 51 | If no clause matched, an exception MUST be thrown. The value of that exception 52 | is defined by the implementation. 53 | 54 | If present, the `after` part of the `receive` expression MUST add a timeout, 55 | after which we no longer wait for a signal. 56 | 57 | The expression of the `after` part MUST evaluate to an integer, which is the 58 | timeout in milliseconds. If it does not evaluate to an integer, an exception 59 | MUST be thrown. The value of that exception is defined by the implementation. 60 | 61 | After the timeout, the body of the `after` part MUST be evaluated. In such case, 62 | the `receive` expression MUST evaluate to the value the body evaluates to. 63 | 64 | The body of the `after` part MUST create a new scope. 65 | -------------------------------------------------------------------------------- /www/content/spec/expressions/spawn.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spawning processes 3 | layout: spec 4 | weight: 14 5 | --- 6 | 7 | # Spawning processes 8 | 9 | A `spawn` expression is used to execute a function in a new concurrent 10 | "process". 11 | 12 | ### Syntax 13 | 14 | {% apply grammkit %} 15 | spawn_expression = "spawn" function_call_expression 16 | {% endapply %} 17 | 18 | 19 | ### Example 20 | 21 | ```letlang 22 | (@ok, pid) := spawn foo("bar"); 23 | ``` 24 | 25 | ### Semantics 26 | 27 | A `spawn` expression MUST return a value of type `(@ok, pid)` or `(@error, term)`. 28 | -------------------------------------------------------------------------------- /www/content/spec/expressions/typecheck.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Type checking 3 | layout: spec 4 | weight: 3 5 | --- 6 | 7 | # Type checking 8 | 9 | A type-checking expression MUST evaluate to a boolean (`true` or `false`). 10 | 11 | ### Syntax 12 | 13 | {% apply grammkit %} 14 | typecheck_expression = expression "is" "not"? type_expression 15 | {% endapply %} 16 | 17 | 18 | ### Example 19 | 20 | ```letlang 21 | 42 is int; 22 | 42 is not string; 23 | 42 is (string | int); 24 | ``` 25 | -------------------------------------------------------------------------------- /www/content/spec/lexical-structure/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Lexical Structure 3 | layout: spec 4 | weight: 2 5 | --- 6 | 7 | # Lexical Structure 8 | -------------------------------------------------------------------------------- /www/content/spec/lexical-structure/comments.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Comments 3 | layout: spec 4 | weight: 4 5 | --- 6 | 7 | # Comments 8 | 9 | ### Syntax 10 | 11 | {% apply grammkit %} 12 | comment = "#" .* "\n"? 13 | {% endapply %} 14 | -------------------------------------------------------------------------------- /www/content/spec/lexical-structure/identfiers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Identifiers 3 | layout: spec 4 | weight: 3 5 | --- 6 | 7 | # Identifiers 8 | 9 | ### Syntax 10 | 11 | {% apply grammkit %} 12 | identifier = [_a-zA-Z] [_0-9a-zA-Z]* 13 | {% endapply %} 14 | -------------------------------------------------------------------------------- /www/content/spec/lexical-structure/input-format.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Input format 3 | layout: spec 4 | weight: 1 5 | --- 6 | 7 | # Input format 8 | 9 | Letlang input SHOULD be interpreted as a sequence of Unicode code points encoded 10 | in UTF-8. 11 | -------------------------------------------------------------------------------- /www/content/spec/lexical-structure/keywords.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Keywords 3 | layout: spec 4 | weight: 2 5 | --- 6 | 7 | # Keywords 8 | 9 | Letlang divides keywords into 3 categories: 10 | 11 | - value keywords 12 | - operator keywords 13 | - syntactic keywords 14 | 15 | # Value keywords 16 | 17 | Those keywords are used to represent specific values: 18 | 19 | - `true` 20 | - `false` 21 | 22 | # Operator keywords 23 | 24 | Those keywords are used as part of Letlang operators: 25 | 26 | - `not` 27 | - `and` 28 | - `or` 29 | - `in` 30 | - `is` 31 | 32 | # Syntactic keywords 33 | 34 | Those keywords are use as part of the syntactic elements of the language, they 35 | are classified in the following subcategories: 36 | 37 | **Module declaration:** 38 | 39 | - `module` 40 | 41 | **Import system:** 42 | 43 | - `from` 44 | - `import` 45 | - `as` 46 | 47 | **Symbol declaration:** 48 | 49 | - `let` 50 | - `pub` 51 | - `class` 52 | - `func` 53 | - `tailrec` 54 | - `effect` 55 | - `extern` 56 | 57 | **Guards:** 58 | 59 | - `when` 60 | - `with` 61 | 62 | **Control flow:** 63 | 64 | - `do` 65 | - `intercept` 66 | - `catch` 67 | - `perform` 68 | - `throw` 69 | - `spawn` 70 | - `receive` 71 | - `after` 72 | - `match` 73 | - `cond` 74 | - `else` 75 | 76 | **Tail recursion:** 77 | 78 | - `recurse` 79 | - `final` 80 | -------------------------------------------------------------------------------- /www/content/spec/lexical-structure/tokens.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tokens 3 | layout: spec 4 | weight: 6 5 | --- 6 | 7 | # Tokens 8 | 9 | Tokens are primitive productions in the grammar defined by regular (non-recursive) 10 | languages. Letlang source input can be broken down into the following kinds of 11 | tokens: 12 | 13 | - [Keywords](./spec/lexical-structure/keywords) 14 | - [Identifiers](./spec/lexical-structure/identifiers) 15 | - [Literals](./spec/lexical-structure/tokens/#literals) 16 | - [Punctuation](./spec/lexical-structure/tokens/#punctuation) 17 | - [Operators](./spec/lexical-structure/tokens/#operators) 18 | 19 | # Literals 20 | 21 | ### Syntax 22 | 23 | {% apply grammkit %} 24 | string = '"' ( [^"\\] / '\\' . )* '"' 25 | 26 | atom = '@' ( ( "'" ([^'] / "\\'" )+ "'" ) / [_a-zA-Z0-9]+ ) 27 | 28 | bool = "true" / "false" 29 | 30 | number 31 | = ( "0b" "_"* [01] [_01]* ) 32 | / ( "0o" "_"* [0-7] [_0-7]* ) 33 | / ( [0-9] [_0-9]* ) 34 | / ( "0x" "_"* [0-9a-fA-F] [_0-9a-fA-F]* ) 35 | / ( ( ( [0-9]+ "."? [0-9]* ) / ( "." [0-9]+ ) ) ( ( [eE] [+-]? )? [0-9]+ )? ) 36 | {% endapply %} 37 | 38 | 39 | # Punctuation 40 | 41 | | Token | Syntax | 42 | | --- | --- | 43 | | Module separator | `::` | 44 | | Parenthesis | `(`, `)` | 45 | | Brackets | `[`, `]` | 46 | | Braces | `${`, `{`, `}` | 47 | | Comma | `,` | 48 | | Dot | `.` | 49 | | Colon | `:` | 50 | | Semicolon | `;` | 51 | | Arrow | `->` | 52 | | Pattern ignore | `_` | 53 | | Ellipsis | `...` | 54 | {.table-hoverable .table-col0-minw .table-col0-left .table-col1-left} 55 | 56 | 57 | # Operators 58 | 59 | | Token | Syntax | 60 | | --- | --- | 61 | | Match | `:=` | 62 | | Arithmetic | `+`, `-`, `*`, `/`, `%`, `**` | 63 | | Comparison | `<`, `<=`, `=`, `!=`, `>=`, `>` | 64 | | Negation | `!`, `¬` | 65 | | Bitwise | `~`, `&`, `|`, `^`, `<<`, `>>` | 66 | | Logic | `not`, `and`, `or` | 67 | | String | `<>` | 68 | | Lists | `++`, `in` | 69 | | Type | `is` | 70 | | Pipeline | `|>` | 71 | {.table-hoverable .table-col0-minw .table-col0-left .table-col1-left} 72 | -------------------------------------------------------------------------------- /www/content/spec/lexical-structure/whitespace.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Whitespace 3 | layout: spec 4 | weight: 5 5 | --- 6 | 7 | # Whitespace 8 | 9 | The lexer MUST skip whitespaces between lexical tokens. 10 | 11 | ### Syntax 12 | 13 | {% apply grammkit %} 14 | whitespace = [ \t\r\n\f]+ 15 | {% endapply %} 16 | -------------------------------------------------------------------------------- /www/content/spec/linkage/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Linkage 3 | layout: spec 4 | weight: 3 5 | --- 6 | 7 | # Linkage 8 | -------------------------------------------------------------------------------- /www/content/spec/linkage/archive-format.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Archive format 3 | layout: spec 4 | weight: 3 5 | --- 6 | 7 | # Archive format 8 | 9 | Library modules MUST be composed of a Rust library crate and a Letlang binary 10 | module interface. 11 | 12 | The Rust crate SHOULD be a Cargo project. 13 | 14 | The generated files MUST be distributed in an archive with the `.lla` extension. 15 | 16 | The archive SHOULD be created with the `ar` UNIX format. 17 | 18 | ## Example 19 | 20 | When building a module `foo` with: 21 | 22 | ```shell 23 | $ letlangc --type=lib foo.let 24 | ``` 25 | 26 | The archive `libfoo.lla` MUST be created. It SHOULD contain the following files: 27 | 28 | ```text 29 | . 30 | ├── libfoo.lli -- Binary Module Interface 31 | └── lldep_foo -- Rust crate name corresponding to Letlang module 32 | ├── Cargo.toml -- Cargo project manifest 33 | └── src 34 | └── lib.rs -- Letlang module translated to Rust library crate 35 | ``` 36 | 37 | ## Linking with other modules 38 | 39 | When building a module `bar` that depends on `foo` with: 40 | 41 | ```shell 42 | $ letlangc --type=lib bar.let -L. -lfoo 43 | ``` 44 | 45 | The compiler MUST look for `libfoo.lla` (as specified by `-lfoo`) in the current 46 | directory (as specified by `-L.`). 47 | 48 | The archive SHOULD be extracted in a `.lltarget` folder where it can be used by 49 | the compiler: 50 | 51 | - to read the binary module interface when resolving symbols 52 | - to specify the correct dependencies in the `lldep_bar` Cargo project manifest 53 | - to test the compilation of the generated Rust code 54 | 55 | The `.lltarget` folder MAY be a Cargo workspace. 56 | 57 | When building a module `baz` that depends on `bar`, the user MUST specify all 58 | the dependencies (direct and transitive): 59 | 60 | ```shell 61 | $ letlangc --type=lib baz.let -L. -lfoo -lbar 62 | ``` 63 | 64 | The order of the `-l` options MUST NOT matter. 65 | 66 | After compilation, the `.lltarget` folder MAY be removed. 67 | -------------------------------------------------------------------------------- /www/content/spec/linkage/binary-module-interface.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Binary Module Interface 3 | layout: spec 4 | weight: 2 5 | --- 6 | 7 | # Binary Module Interface 8 | 9 | Each Letlang module MUST provide metadata about the exported symbols. 10 | 11 | This interface is used when linking against an already compiled module. 12 | 13 | ## Format 14 | 15 | The file format MAY be a JSON document satisfying the following JSON schema: 16 | 17 | ```json 18 | { 19 | "$schema": "https://json-schema.org/draft/2020-12/schema", 20 | "$id": "https://letlang.dev/schemas/bmi.json", 21 | "$defs": { 22 | "interface": { 23 | "type": "object", 24 | "properties": { 25 | "crate_name": { "$ref": "#/$defs/crate-name" }, 26 | "module": { "$ref": "#/$defs/module-path" }, 27 | "symbols": { 28 | "type": "array", 29 | "items": { "$ref": "#/$defs/symbol" } 30 | } 31 | }, 32 | "required": ["module", "symbols"] 33 | }, 34 | "crate-name": { 35 | "type": "string", 36 | "minLength": 1 37 | }, 38 | "module-path": { 39 | "type": "string", 40 | "minLength": 1 41 | }, 42 | "symbol": { 43 | "oneOf": [ 44 | { "$ref": "#/$defs/symbol-class" }, 45 | { "$ref": "#/$defs/symbol-function" }, 46 | { "$ref": "#/$defs/symbol-effect" } 47 | ] 48 | }, 49 | "symbol-class": { 50 | "type": "object", 51 | "properties": { 52 | "kind": { "type": "string", "enum": ["class"] }, 53 | "name": { "type": "string", "minLength": 1 }, 54 | "type-arity": { "type": "integer", "minimum": 0 } 55 | }, 56 | "required": ["kind", "name", "type-arity"] 57 | }, 58 | "symbol-function": { 59 | "type": "object", 60 | "properties": { 61 | "kind": { "type": "string", "enum": ["function"] }, 62 | "name": { "type": "string", "minLength": 1 }, 63 | "type-arity": { "type": "integer", "minimum": 0 }, 64 | "call-arity": { "type": "integer", "minimum": 0 } 65 | }, 66 | "required": ["kind", "name", "type-arity", "call-arity"] 67 | }, 68 | "symbol-effect": { 69 | "type": "object", 70 | "properties": { 71 | "kind": { "type": "string", "enum": ["effect"] }, 72 | "name": { "type": "string", "minLength": 1 }, 73 | "type-arity": { "type": "integer", "minimum": 0 }, 74 | "call-arity": { "type": "integer", "minimum": 0 } 75 | }, 76 | "required": ["kind", "name", "type-arity", "call-arity"] 77 | } 78 | }, 79 | "allOf": [ 80 | { "$ref": "#/$defs/interface" } 81 | ] 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /www/content/spec/linkage/compilation-unit.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Compilation Unit 3 | layout: spec 4 | weight: 1 5 | --- 6 | 7 | # Compilation Unit 8 | 9 | A Letlang source file corresponds to a **module**, which is Letlang's compilation 10 | unit. 11 | 12 | There are 2 kinds of modules: 13 | 14 | - [library modules](./spec/linkage/compilation-unit#library-modules) 15 | - [executable modules](./spec/linkage/compilation-unit#executable-modules) 16 | 17 | ## Library Modules 18 | 19 | They MUST be translated to a single Rust library crate. 20 | 21 | They MUST expose a [Binary Module Interface](./spec/linkage/binary-module-interface). 22 | 23 | The generated Rust library crate SHOULD depend on an `llruntime` Rust crate 24 | containing the Letlang runtime. 25 | 26 | ## Executable modules 27 | 28 | They MUST be translated to a Rust library crate. 29 | 30 | They MUST expose a `main` function. 31 | 32 | A Rust executable crate containing the Letlang runtime bootstrap code, calling 33 | the `main` function, MUST be generated. 34 | 35 | The generated Rust library crates SHOULD depend on an `llruntime` Rust crate 36 | containing the Letlang runtime. 37 | -------------------------------------------------------------------------------- /www/content/spec/linkage/executable.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Executable 3 | layout: spec 4 | weight: 4 5 | --- 6 | 7 | # Executable 8 | 9 | When building an executable from the Letlang module `dummy.let`, a Cargo project 10 | named `lldep_dummy` SHOULD be created in the `.lltarget` folder. It SHOULD 11 | contain the following files: 12 | 13 | ```text 14 | . 15 | ├── libdummy.lli -- Binary Module Interface 16 | └── lldep_dummy -- Rust crate name corresponding to Letlang module 17 | ├── Cargo.toml -- Cargo project manifest 18 | └── src 19 | ├── lib.rs -- Letlang module translated to Rust library crate 20 | └── main.rs -- Letlang runtime bootstrap code, calling the main function 21 | ``` 22 | 23 | When building an executable `dummy` which can depend on modules `foo` and `bar` 24 | with: 25 | 26 | ```shell 27 | $ letlangc --type=exe dummy.let -L. -lfoo -lbar 28 | ``` 29 | 30 | The compiler MUST look for `libfoo.lla` (as specified by `-lfoo`) and 31 | `libbar.lla` (as specified by `-lbar`) in the current directory (as specified 32 | by `-L.`). 33 | 34 | The archives SHOULD be extracted in a `.lltarget` folder where it can be used by 35 | the compiler: 36 | 37 | - to read the binary module interface when resolving symbols 38 | - to specify the correct dependencies in the `lldep_dummy` Cargo project manifest 39 | - to build the whole project 40 | 41 | The `.lltarget` folder MAY be a Cargo workspace. 42 | 43 | The final executable `dummy` (or `dummy.exe` on Windows) MUST be placed in the 44 | current directory. 45 | 46 | After compilation, the `.lltarget` folder MAY be removed. 47 | -------------------------------------------------------------------------------- /www/content/spec/modules-imports.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Modules and Imports 3 | layout: spec 4 | weight: 4 5 | --- 6 | 7 | # Modules 8 | 9 | A module is a Letlang source file that MUST start with the module declaration 10 | statement. 11 | 12 | ### Syntax 13 | 14 | {% apply grammkit %} 15 | module_declaration_statement = "module" module_path ";" 16 | module_path = identifier ("::" identifier)* 17 | {% endapply %} 18 | 19 | 20 | ### Example 21 | 22 | ```letlang 23 | module foo::main; 24 | 25 | # ... 26 | ``` 27 | 28 | # Imports 29 | 30 | Other modules can be imported in the current module scope, and optionnally 31 | aliased. 32 | 33 | Symbols from other modules can be imported in the current module scope, and each 34 | of them can be optionnally aliased. 35 | 36 | ### Syntax 37 | 38 | {% apply grammkit %} 39 | import_statement 40 | = ( "import" module_path ("as" identifier)? ";") 41 | / ( "from" module_path "import" "{" import_symbol ("," import_symbol)* ","? "}" ";") 42 | 43 | import_symbol = identifier ("as" identifier)? 44 | {% endapply %} 45 | 46 | 47 | ### Examples 48 | 49 | ```letlang 50 | import std::io as stdio; 51 | from std::proc import { self, send as send_message }; 52 | ``` 53 | 54 | To access a symbol in an imported module, the complete path to the symbol is 55 | required: 56 | 57 | {% apply grammkit %} 58 | symbol_path = identifier ("::" identifier)* 59 | {% endapply %} 60 | 61 | 62 | ### Example 63 | 64 | ```letlang 65 | stdio::println("hello world"); 66 | ``` 67 | 68 | ```letlang 69 | import std::io as stdio; 70 | import std::proc; 71 | 72 | let pub main: func[() -> @ok] { 73 | () -> { 74 | stdio::println(std::proc::self()); 75 | @ok; 76 | }, 77 | }; 78 | ``` 79 | -------------------------------------------------------------------------------- /www/content/spec/notation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Notation 3 | layout: spec 4 | weight: 1 5 | --- 6 | 7 | # Notation 8 | 9 | ## Grammar 10 | 11 | *Syntax* grammar is represented using 12 | [railroad diagrams](https://en.wikipedia.org/wiki/Syntax_diagram). 13 | -------------------------------------------------------------------------------- /www/content/spec/pattern-matching.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pattern Matching 3 | layout: spec 4 | weight: 6 5 | --- 6 | 7 | # Pattern Matching 8 | 9 | Pattern matching is used to branch on a pattern or destructure data. 10 | 11 | Many expressions have a set of `pattern_matching_clause` rules used for 12 | branching. 13 | 14 | ## Branching 15 | 16 | ### Syntax 17 | 18 | {% apply grammkit %} 19 | pattern_matching_clause = clause_patterns clause_guard? "->" clause_body 20 | 21 | clause_patterns = "(" (pattern ("," pattern)* ","? )? ")" 22 | 23 | clause_guard = "when" expression 24 | 25 | clause_body 26 | = expression 27 | / ("{" (expression ";")+ "}") 28 | {% endapply %} 29 | 30 | 31 | ### Example 32 | 33 | ```letlang 34 | let natural: class[int] { 35 | (n) -> n >= 0, 36 | }; 37 | 38 | let collatz: func[(natural) -> natural] { 39 | (n) when n % 2 = 0 -> n / 2, 40 | (n) -> 3 * n + 1, 41 | }; 42 | ``` 43 | 44 | ### Semantics 45 | 46 | In a class constraint, if no clause matches, then the object MUST NOT belong to 47 | the class: 48 | 49 | ```letlang 50 | let natural: class[int] { 51 | (n) when n >= 0 -> true, 52 | }; 53 | 54 | # is equivalent to: 55 | 56 | let natural: class[int] { 57 | (n) when n >= 0 -> true, 58 | (_) -> false, 59 | }; 60 | ``` 61 | 62 | In all other cases, if no clause matches, an exception MUST be thrown, the value 63 | of this exception is defined by the implementation: 64 | 65 | ```letlang 66 | let foo: func[(int) -> @ok] { 67 | (1) -> @ok, 68 | (2) -> @ok, 69 | }; 70 | 71 | # is equivalent to: 72 | 73 | let foo: func[(int) -> @ok] { 74 | (1) -> @ok, 75 | (2) -> @ok, 76 | (_) -> throw @implementation_defined_error, 77 | }; 78 | ``` 79 | 80 | Each clause MUST create a new scope. 81 | 82 | A clause MUST evaluate to the value the last expression in its branch evaluates 83 | to. 84 | 85 | The guard expression of a clause MUST evaluate to a boolean (`true` or `false`). 86 | 87 | If an exception is thrown in a guard expression, it SHOULD be ignored and be 88 | equivalent to returning `false`. 89 | 90 | ## Pattern syntax 91 | 92 | {% apply grammkit %} 93 | pattern 94 | = pattern_ignore 95 | / pattern_binding 96 | / pattern_literal 97 | / pattern_tuple 98 | / pattern_namedtuple 99 | / pattern_list 100 | / pattern_list_headtail 101 | / pattern_eval 102 | 103 | pattern_ignore = "_" 104 | 105 | pattern_binding = identifier 106 | 107 | pattern_literal = atom / bool / number / string 108 | 109 | pattern_tuple 110 | = ("(" ("..." ","?)? ")") 111 | / ("(" pattern ("," pattern)* ("," "...")? ","? ")") 112 | 113 | pattern_namedtuple 114 | = ("{" ("..." ","?)? "}") 115 | / ("{" pattern_namedtuple_pair ("," pattern_namedtuple_pair)* ("," "...")? ","? "}") 116 | 117 | pattern_namedtuple_pair = identifier ":" pattern 118 | 119 | pattern_list 120 | = ("[" ("..." ","?)? "]") 121 | / ("[" pattern ("," pattern)* ("," "...")? ","? "]") 122 | 123 | pattern_list_headtail 124 | = "[" pattern ("," pattern)* ","? "|" pattern "]" 125 | 126 | pattern_eval 127 | = "${" expression "}" 128 | {% endapply %} 129 | 130 | 131 | ### Semantics 132 | 133 | The ellipsis `...` means *"and the rest"*: 134 | 135 | ```letlang 136 | # a = 1, b = 2 137 | [a, b, ...] := [1, 2, 3, 4]; 138 | 139 | # a = 1, b = 2, tail = [3, 4] 140 | [a, b | tail] := [1, 2, 3, 4]; 141 | 142 | # this will throw an error 143 | [a, b] := [1, 2, 3, 4]; 144 | ``` 145 | 146 | Binding a value to a name MUST happen immediatly: 147 | 148 | ```letlang 149 | # a = 2, because it is rebound a second time 150 | (a, a) := (1, 2); 151 | 152 | # a = 1 and can be used immediatly in an `pattern_eval` rule 153 | (a, ${a + 1}) := (1, 2); 154 | 155 | # will throw an error, because a = 1, and 1 does not match 2 156 | (a, ${a}) := (1, 2); 157 | ``` 158 | -------------------------------------------------------------------------------- /www/content/spec/runtime/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Runtime 3 | layout: spec 4 | weight: 8 5 | --- 6 | 7 | # Runtime 8 | -------------------------------------------------------------------------------- /www/content/spec/runtime/actor.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Actor-based concurrency 3 | layout: spec 4 | weight: 3 5 | --- 6 | 7 | # Actor-based concurrency 8 | 9 | The `main` function of a Letlang executable MUST be run in a Letlang process. 10 | 11 | A Letlang process SHOULD be a lightweight userland thread, also called 12 | "greenthread". 13 | 14 | Each Letlang process MUST have a unique identifier and a "mailbox" to receive 15 | signals from other process. 16 | 17 | A Letlang executable MUST keep running as long as there is at least one process 18 | running, even if the process associated to the `main` function terminated. 19 | 20 | A Letlang "source" process MUST be able to link/unlink itself to/from another 21 | "target" process. When such such link is created, the "source" process will 22 | receive a signal whenever the "target" process terminates (successfully or not). 23 | 24 | When exceptions bubble up to the runtime, the MUST terminate the process. As a 25 | consequence, the runtime MUST send an "exited" signal to all linked processes 26 | with the exception as a signal payload. 27 | 28 | When effects bubble up to the runtime, if the runtime has no builtin handler for 29 | the effect, the runtime MUST terminate the process. As a consequence, the 30 | runtime MUST send an "exited" signal to all linked processes. In such case, the 31 | signal's payload is implementation-defined. 32 | 33 | The function associated to a process MUST return `@ok` to be considered 34 | successful. If the function returns `@ok`, the runtime MUST send an "exited" 35 | signal to all linked processes with an empty payload. If the function returns 36 | any other value, the runtime MUST send an "exited" signal to all linked 37 | processes. In such case, the signal's payload is implementation-defined. 38 | 39 | The runtime MUST provide a function to send "message" signals to another 40 | process. 41 | -------------------------------------------------------------------------------- /www/content/spec/runtime/classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Builtin classes 3 | layout: spec 4 | weight: 1 5 | --- 6 | 7 | # Builtin classes 8 | 9 | The runtime MUST provide the following classes: 10 | 11 | | Class | Description | 12 | | --- | --- | 13 | | `term` | Contains everything, all other classes are subsets of this one | 14 | | `atom` | Contains all atoms (ie. `@ok`, `@error`, ...) | 15 | | `bool` | Contains `true` and `false` | 16 | | `number` | Contains all numbers (ie. `1`, `2.3`, ...) | 17 | | `int` | Contains all integers, it is a subset of `number` | 18 | | `string` | Contains all strings (ie. `"hello world"`) | 19 | | `list` | Contains all lists where their elements are of class `T` | 20 | | `proplist` | Contains all lists where their elements are of class `(atom, T)` | 21 | | `pid` | Opaque class that contains all process identifiers | 22 | | `iocap` | Opaque class that contains all IO capabilities | 23 | {.table-hoverable .table-col0-minw .table-col1-left} 24 | 25 | Each Letlang value is also a singleton type, the runtime MUST provide an 26 | implementation for such types. 27 | 28 | The runtime MUST provide an implementation for tuple types and named tuple types. 29 | -------------------------------------------------------------------------------- /www/content/spec/runtime/iocap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: IO Capabilities 3 | layout: spec 4 | weight: 2 5 | --- 6 | 7 | # IO Capabilities 8 | 9 | IO capabilities are an abstraction of lower-level resources such as file 10 | descriptors, sockets, and other IO-related handles. 11 | 12 | The runtime MUST provide an implementation for such capabilities, and 13 | encapsulate them into an `iocap` value that is opaque to Letlang. 14 | 15 | IO operations are effectful, therefore the runtime MUST provide effect handlers 16 | to acquire, dispose, write and read from IO capabilities. 17 | 18 | The signature of those effects is implementation-defined. 19 | -------------------------------------------------------------------------------- /www/content/spec/symbols/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Symbols 3 | layout: spec 4 | weight: 5 5 | --- 6 | 7 | # Symbols 8 | 9 | Symbols can be private or public. 10 | 11 | Public symbols compose the interface of a Letlang module. 12 | 13 | Symbols can have type parameters. 14 | 15 | ### Syntax 16 | 17 | {% apply grammkit %} 18 | symbol_declaration 19 | = "let" "pub"? identifier type_parameters? ":" 20 | (class_definition / function_definition / effect_definition) ";" 21 | 22 | type_parameters = "<" identifier ("," identifier)* ","? ">" 23 | {% endapply %} 24 | 25 | 26 | ### Semantic 27 | 28 | A symbol declaration MUST create a new scope. 29 | 30 | Type parameters MUST define class symbols in the current declaration's scope. 31 | -------------------------------------------------------------------------------- /www/content/spec/symbols/class.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Class 3 | layout: spec 4 | weight: 1 5 | --- 6 | 7 | # Class 8 | 9 | A class is a collection of objects, similar to **proper classes** in Set theory. 10 | 11 | Any object satisfying the constraints of a class belongs to it. Therefore, an 12 | object does not have a single type. 13 | 14 | A class is defined structurally from a type expression, and can optionally have 15 | constraint expressions to verify if the object satisfy the desired properties. 16 | 17 | ### Syntax 18 | 19 | {% apply grammkit %} 20 | class_definition 21 | = "class" "[" type_expression "]" 22 | ("{" pattern_matching_clause ("," pattern_matching_clause)* ","? "}")? 23 | 24 | type_expression 25 | = (type_expression "&" type_expression) 26 | / (type_expression "|" type_expression) 27 | / ("!" type_expression) 28 | / ("(" type_expression ")") 29 | / type_term 30 | 31 | type_term 32 | = type_ref 33 | / type_literal 34 | / type_tuple 35 | / type_namedtuple 36 | 37 | type_ref = symbol_path ("<" type_expression ("," type_expression)* ","? ">")? 38 | 39 | type_literal = atom / bool / number / string 40 | 41 | type_tuple 42 | = ( "(" ")" ) 43 | / ( "(" type_expression "," ")" ) 44 | / ( "(" type_expression ("," type_expression)+ ","? ")" ) 45 | 46 | type_namedtuple 47 | = ( "{" "}" ) 48 | / ( "{" type_namedtuple_pair ("," type_namedtuple_pair)* ","? "}" ) 49 | 50 | type_namedtuple_pair = identifier ":" type_expression 51 | {% endapply %} 52 | 53 | 54 | ### Examples 55 | 56 | ```letlang 57 | let even: class[int] { 58 | (n) -> n % 2 = 0, 59 | }; 60 | 61 | let odd: class[int & !even]; 62 | ``` 63 | 64 | ```letlang 65 | let ok: class[(@ok, t)]; 66 | let err: class[(@error, e)]; 67 | let result: class[ok | err]; 68 | ``` 69 | 70 | ### Semantics 71 | 72 | Each clause of a class MUST evaluate to a boolean (`true` or `false`). 73 | 74 | If an exception is thrown in the body of a clause, it SHOULD be ignored and be 75 | equivalent to returning `false`. 76 | -------------------------------------------------------------------------------- /www/content/spec/symbols/effect.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Effect 3 | layout: spec 4 | weight: 3 5 | --- 6 | 7 | # Effect 8 | 9 | An effect represents the signature of a side effect. 10 | 11 | An expression can delegate to the caller the handling of a side effect, doing so 12 | will interrupt the function in order to call the handler, and resume the 13 | execution with the value returned by the handler. 14 | 15 | The effect's signature MUST be used for type-checking at the handler's 16 | boundaries, meaning: 17 | 18 | - arguments are type-checked before delegating the side effect to the handler 19 | - the value returned by the handler is type-checked before resuming the 20 | function's execution 21 | 22 | ### Syntax 23 | 24 | {% apply grammkit %} 25 | effect_definition 26 | = "effect" "[" signature "]" 27 | 28 | signature 29 | = "(" (type_expression ("," type_expression)* ","?)? ")" "->" type_expression 30 | {% endapply %} 31 | 32 | 33 | ### Example 34 | 35 | ```letlang 36 | let log: effect[(string) -> @ok]; 37 | 38 | let read_line: effect[() -> string]; 39 | 40 | let sort_compare: effect[(t, t) -> bool]; 41 | ``` 42 | -------------------------------------------------------------------------------- /www/content/spec/symbols/function.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Function 3 | layout: spec 4 | weight: 2 5 | --- 6 | 7 | # Function 8 | 9 | A function is a callable symbol. 10 | 11 | Type checking MUST happen at the function boundaries, meaning: 12 | 13 | - arguments are type-checked before the function is executed 14 | - the return value is type-checked after the function is executed 15 | 16 | Functions can be marked as **tail-recursive**. Such functions MUST be unrolled 17 | as a loop to avoid stack overflows. 18 | 19 | Tail-recursive functions MUST return either: 20 | 21 | - `recurse[...]`: to indicate that we need to iterate again with new arguments 22 | - `final[...]`: to indicate that we should exit the loop and return a value 23 | 24 | ### Syntax 25 | 26 | {% apply grammkit %} 27 | function_definition 28 | = ("func" / "tailrec") 29 | "[" signature "]" 30 | "{" pattern_matching_clause ("," pattern_matching_clause)* ","? "}" 31 | 32 | signature 33 | = "(" (type_expression ("," type_expression)* ","?)? ")" "->" type_expression 34 | {% endapply %} 35 | 36 | 37 | ### Example 38 | 39 | ```letlang 40 | let natural: class[int] { 41 | (n) -> n >= 0, 42 | }; 43 | 44 | let factorial_impl: tailrec[(natural, natural) -> natural] { 45 | (0, acc) -> final[acc], 46 | (n, acc) -> recurse[n - 1, n * acc], 47 | }; 48 | 49 | let factorial: func[(natural) -> natural] { 50 | (0) -> 1, 51 | (1) -> 1, 52 | (n) -> factorial_impl(n, 1), 53 | }; 54 | ``` 55 | 56 | ### Semantics 57 | 58 | The return value of a function MUST be the value the matching clause evalutes 59 | to. 60 | 61 | For tail-recursive functions, the return value MUST be the value passed with 62 | `final[...]`. 63 | -------------------------------------------------------------------------------- /www/data/menu.yml: -------------------------------------------------------------------------------- 1 | items: 2 | - href: "./book/" 3 | tooltip: "Handbook" 4 | icon: "fas fa-book" 5 | - href: "./spec/" 6 | tooltip: "Specification" 7 | icon: "fas fa-code" 8 | - href: "https://github.com/linkdd/letlang" 9 | tooltip: "Github" 10 | icon: "fab fa-github" -------------------------------------------------------------------------------- /www/data/meta.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | author: "David Delassus" 4 | description: "General purpose programming language" 5 | -------------------------------------------------------------------------------- /www/data/news.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | sources: 4 | - https://medium.com/feed/tag/letlang 5 | 6 | categories: 7 | - letlang 8 | -------------------------------------------------------------------------------- /www/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "www/" 3 | publish = "./public/" 4 | command = "npm run build" 5 | -------------------------------------------------------------------------------- /www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "letlang-website", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "build:css": "tailwindcss -i ./assets/style.css -o ./static/css/style.css", 6 | "build:content": "gin -p . build", 7 | "build": "npm run build:css && npm run build:content", 8 | "watch:css": "tailwindcss -i ./assets/style.css -o ./static/css/style.css --watch", 9 | "watch:content": "gin -p . serve", 10 | "watch": "concurrently \"npm run watch:css\" \"npm run watch:content\"" 11 | }, 12 | "dependencies": { 13 | "gin": "linkdd/gin#semver:v0.4.1" 14 | }, 15 | "devDependencies": { 16 | "concurrently": "^8.2.2", 17 | "tailwindcss": "^3.4.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /www/static/img/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LET 5 | -------------------------------------------------------------------------------- /www/static/js/el-transition.js: -------------------------------------------------------------------------------- 1 | export async function enter(element, transitionName = null) { 2 | element.classList.remove('hidden') 3 | await transition('enter', element, transitionName) 4 | } 5 | 6 | export async function leave(element, transitionName = null) { 7 | await transition('leave', element, transitionName) 8 | element.classList.add('hidden') 9 | } 10 | 11 | export async function toggle(element, transitionName = null) { 12 | if (element.classList.contains('hidden')) { 13 | await enter(element, transitionName) 14 | } else { 15 | await leave(element, transitionName) 16 | } 17 | } 18 | 19 | async function transition(direction, element, animation) { 20 | const dataset = element.dataset 21 | const animationClass = animation ? `${animation}-${direction}` : direction 22 | let transition = `transition${direction.charAt(0).toUpperCase() + direction.slice(1)}` 23 | const genesis = dataset[transition] ? dataset[transition].split(" ") : [animationClass] 24 | const start = dataset[`${transition}Start`] ? dataset[`${transition}Start`].split(" ") : [`${animationClass}-start`] 25 | const end = dataset[`${transition}End`] ? dataset[`${transition}End`].split(" ") : [`${animationClass}-end`] 26 | 27 | addClasses(element, genesis) 28 | addClasses(element, start) 29 | await nextFrame() 30 | removeClasses(element, start) 31 | addClasses(element, end); 32 | await afterTransition(element) 33 | removeClasses(element, end) 34 | removeClasses(element, genesis) 35 | } 36 | 37 | function addClasses(element, classes) { 38 | element.classList.add(...classes) 39 | } 40 | 41 | function removeClasses(element, classes) { 42 | element.classList.remove(...classes) 43 | } 44 | 45 | function nextFrame() { 46 | return new Promise(resolve => { 47 | requestAnimationFrame(() => { 48 | requestAnimationFrame(resolve) 49 | }); 50 | }); 51 | } 52 | 53 | function afterTransition(element) { 54 | return new Promise(resolve => { 55 | // safari return string with comma separate values 56 | const computedDuration = getComputedStyle(element).transitionDuration.split(",")[0] 57 | const duration = Number(computedDuration.replace('s', '')) * 1000; 58 | setTimeout(() => { 59 | resolve() 60 | }, duration) 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /www/static/js/news.js: -------------------------------------------------------------------------------- 1 | async function fetchNews(sources, categories) { 2 | const posts = await Promise.all( 3 | sources 4 | .map(feed => `https://api.rss2json.com/v1/api.json?rss_url=${feed}`) 5 | .map(async url => { 6 | const res = await fetch(url) 7 | const data = await res.json() 8 | return data.items 9 | }) 10 | ) 11 | 12 | return posts 13 | .flatMap(posts => posts) 14 | .filter(post => categories.some(c => post.categories.includes(c))) 15 | .sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate)) 16 | } 17 | -------------------------------------------------------------------------------- /www/static/schemas/bmi.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://letlang.dev/schemas/bmi.json", 4 | "$defs": { 5 | "interface": { 6 | "type": "object", 7 | "properties": { 8 | "module": { "$ref": "#/$defs/module-path" }, 9 | "symbols": { 10 | "type": "array", 11 | "items": { "$ref": "#/$defs/symbol" } 12 | } 13 | }, 14 | "required": ["module", "symbols"] 15 | }, 16 | "module-path": { 17 | "type": "string", 18 | "minLength": 1 19 | }, 20 | "symbol": { 21 | "oneOf": [ 22 | { "$ref": "#/$defs/symbol-class" }, 23 | { "$ref": "#/$defs/symbol-function" }, 24 | { "$ref": "#/$defs/symbol-effect" } 25 | ] 26 | }, 27 | "symbol-class": { 28 | "type": "object", 29 | "properties": { 30 | "kind": { "type": "string", "enum": ["class"] }, 31 | "name": { "type": "string", "minLength": 1 }, 32 | "type-arity": { "type": "integer", "minimum": 0 } 33 | }, 34 | "required": ["kind", "name", "type-arity"] 35 | }, 36 | "symbol-function": { 37 | "type": "object", 38 | "properties": { 39 | "kind": { "type": "string", "enum": ["function"] }, 40 | "name": { "type": "string", "minLength": 1 }, 41 | "type-arity": { "type": "integer", "minimum": 0 }, 42 | "call-arity": { "type": "integer", "minimum": 0 } 43 | }, 44 | "required": ["kind", "name", "type-arity", "call-arity"] 45 | }, 46 | "symbol-effect": { 47 | "type": "object", 48 | "properties": { 49 | "kind": { "type": "string", "enum": ["effect"] }, 50 | "name": { "type": "string", "minLength": 1 }, 51 | "type-arity": { "type": "integer", "minimum": 0 }, 52 | "call-arity": { "type": "integer", "minimum": 0 } 53 | }, 54 | "required": ["kind", "name", "type-arity", "call-arity"] 55 | } 56 | }, 57 | "allOf": [ 58 | { "$ref": "#/$defs/interface" } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /www/static/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /www/static/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /www/static/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /www/static/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /www/static/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /www/static/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /www/static/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /www/static/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /www/static/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /www/static/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /www/static/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /www/static/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkdd/letlang/7c9dc6efa7e005fbc918652061d8dd2c979c6e0e/www/static/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /www/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./content/**/*.md', './templates/**/*.html'], 4 | theme: { 5 | extend: { 6 | }, 7 | }, 8 | safelist: [ 9 | '.grammkit-diagram', 10 | '.grammkit-diagram-title', 11 | '.grammkit-diagram-svg', 12 | { 13 | pattern: /table-col\d+-.*/, 14 | }, 15 | ], 16 | plugins: [], 17 | } 18 | 19 | -------------------------------------------------------------------------------- /www/templates/layouts/_default/home.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | 3 | {% block main %} 4 |
5 |
6 |
7 |
10 | 14 | 20 | 21 | LET 28 | 29 |
30 | 31 | lang 32 | 33 |
34 |
35 | 36 |
37 | {{ content|raw }} 38 |
39 | 40 |
41 |

📰 News Feed

42 | 43 | 44 | 45 |
57 | 103 |
104 |
105 |
106 | {% endblock %} 107 | -------------------------------------------------------------------------------- /www/templates/layouts/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ site.title }} - {{ page.title }} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% if block("mobilenav") is defined %} 32 | {% include "partials/nav.html" with { mobilenav: block("mobilenav") } %} 33 | {% else %} 34 | {% include "partials/nav.html" with { mobilenav: null } %} 35 | {% endif %} 36 | 37 | {% block main %} 38 | {% endblock %} 39 | 40 | {% include "partials/footer.html" %} 41 | 42 | {% block scripts %} 43 | {% endblock %} 44 | 45 | 46 | -------------------------------------------------------------------------------- /www/templates/layouts/book/base.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | 3 | {% block main %} 4 |
5 |
6 | {{ content|raw }} 7 | 8 | {% block toc %} 9 | {% endblock %} 10 |
11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /www/templates/layouts/book/section.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/book/base.html" %} 2 | -------------------------------------------------------------------------------- /www/templates/layouts/book/single.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/book/base.html" %} 2 | -------------------------------------------------------------------------------- /www/templates/layouts/lep/base.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | 3 | {% block main %} 4 |
5 |
6 | {{ content|raw }} 7 | 8 | {% block toc %} 9 | {% endblock %} 10 |
11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /www/templates/layouts/lep/section.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/lep/base.html" %} 2 | -------------------------------------------------------------------------------- /www/templates/layouts/lep/single.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/lep/base.html" %} 2 | -------------------------------------------------------------------------------- /www/templates/layouts/spec/base.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | 3 | {% block mobilenav %} 4 | 16 | {% endblock %} 17 | 18 | {% block main %} 19 |
20 |
36 | 59 | 79 | 80 | 84 | 85 |
86 | {{ content|raw }} 87 | 88 | {% block toc %} 89 | {% endblock %} 90 |
91 |
92 | {% endblock %} 93 | 94 | {% block scripts %} 95 | 124 | {% endblock %} 125 | -------------------------------------------------------------------------------- /www/templates/layouts/spec/section.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/spec/base.html" %} 2 | 3 | {% block toc %} 4 |
5 |

Table of Contents:

6 | 7 |
    8 | {% for child in page.children %} 9 |
  • 10 | {{ child.title }} 11 |
  • 12 | {% endfor %} 13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /www/templates/layouts/spec/single.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/spec/base.html" %} 2 | -------------------------------------------------------------------------------- /www/templates/partials/footer.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /www/templates/partials/nav.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/templates/partials/sidenav-menu.html: -------------------------------------------------------------------------------- 1 | {% macro menuitem(page, item) %} 2 | {% import _self as macros %} 3 | 4 |
  • 5 | {% if page.url == item.url %} 6 | 16 | {{ item.title }} 17 | 18 | {% elseif item.isSection %} 19 | 29 | {{ item.title }} 30 | 31 | {% else %} 32 | 41 | {{ item.title }} 42 | 43 | {% endif %} 44 | 45 | {% if item.isSection %} 46 |
      47 | {% for child in item.children %} 48 | {{ macros.menuitem(page, child) }} 49 | {% endfor %} 50 |
    51 | {% endif %} 52 |
  • 53 | {% endmacro %} 54 | 55 | {% import _self as macros %} 56 | 57 |
      58 | {{ macros.menuitem(page, root) }} 59 |
    60 | -------------------------------------------------------------------------------- /www/templates/shortcodes/nav.html: -------------------------------------------------------------------------------- 1 | 42 | --------------------------------------------------------------------------------