├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── readme.md ├── src ├── common │ ├── diagnostic.rs │ ├── interner.rs │ └── mod.rs ├── frontend │ ├── ast.rs │ ├── lex.rs │ ├── mod.rs │ └── parse.rs └── main.rs └── tests ├── data ├── case.fl └── result.fl ├── functions ├── extern.fl ├── one_arg.fl ├── single_arg.fl └── two_arg.fl ├── io └── print.fl ├── operators ├── custom.fl ├── id_as_op.fl └── overloading.fl └── space └── basic.fl /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Added by cargo 4 | 5 | /target 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "cfg-if" 18 | version = "1.0.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 21 | 22 | [[package]] 23 | name = "codespan-reporting" 24 | version = "0.11.1" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 27 | dependencies = [ 28 | "termcolor", 29 | "unicode-width", 30 | ] 31 | 32 | [[package]] 33 | name = "fluoc" 34 | version = "0.1.0" 35 | dependencies = [ 36 | "codespan-reporting", 37 | "hashbrown", 38 | ] 39 | 40 | [[package]] 41 | name = "getrandom" 42 | version = "0.2.3" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 45 | dependencies = [ 46 | "cfg-if", 47 | "libc", 48 | "wasi", 49 | ] 50 | 51 | [[package]] 52 | name = "hashbrown" 53 | version = "0.11.2" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 56 | dependencies = [ 57 | "ahash", 58 | ] 59 | 60 | [[package]] 61 | name = "libc" 62 | version = "0.2.103" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" 65 | 66 | [[package]] 67 | name = "once_cell" 68 | version = "1.8.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 71 | 72 | [[package]] 73 | name = "termcolor" 74 | version = "1.1.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 77 | dependencies = [ 78 | "winapi-util", 79 | ] 80 | 81 | [[package]] 82 | name = "unicode-width" 83 | version = "0.1.9" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 86 | 87 | [[package]] 88 | name = "version_check" 89 | version = "0.9.3" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 92 | 93 | [[package]] 94 | name = "wasi" 95 | version = "0.10.2+wasi-snapshot-preview1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 98 | 99 | [[package]] 100 | name = "winapi" 101 | version = "0.3.9" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 104 | dependencies = [ 105 | "winapi-i686-pc-windows-gnu", 106 | "winapi-x86_64-pc-windows-gnu", 107 | ] 108 | 109 | [[package]] 110 | name = "winapi-i686-pc-windows-gnu" 111 | version = "0.4.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 114 | 115 | [[package]] 116 | name = "winapi-util" 117 | version = "0.1.5" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 120 | dependencies = [ 121 | "winapi", 122 | ] 123 | 124 | [[package]] 125 | name = "winapi-x86_64-pc-windows-gnu" 126 | version = "0.4.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 129 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fluoc" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | codespan-reporting = "0.11.1" 8 | hashbrown = "0.11" 9 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Fluo 2 | 3 | A compiled, statically typed, and concurrent programming language. 4 | Fluo is heavily inspired by Haskell, Rust, Elixir, and Nim. 5 | -------------------------------------------------------------------------------- /src/common/diagnostic.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | fmt::{Debug, Display}, 4 | ops::Range, 5 | }; 6 | 7 | use codespan_reporting::diagnostic::{Diagnostic as CSDiagnostic, Label, LabelStyle, Severity}; 8 | 9 | #[derive(Debug, Default, Clone, Copy)] 10 | pub struct FileId(usize); 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct Span { 14 | s: usize, 15 | e: usize, 16 | fid: FileId, 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct Tagged(pub V, pub T); 21 | pub type Spanned = Tagged; 22 | 23 | pub type Failable = Result; 24 | 25 | impl Span { 26 | pub fn new(s: usize, e: usize, fid: FileId) -> Self { 27 | Self { s, e, fid } 28 | } 29 | 30 | pub fn single_char(s: usize, fid: FileId) -> Self { 31 | Self { s, e: s + 1, fid } 32 | } 33 | } 34 | 35 | impl Into> for Span { 36 | fn into(self) -> Range { 37 | self.s..self.e 38 | } 39 | } 40 | 41 | #[derive(Debug)] 42 | pub struct Diagnostic { 43 | ty: ErrorType, 44 | msg: Cow<'static, str>, 45 | span: Span, 46 | annotations: Vec, 47 | } 48 | 49 | impl Diagnostic { 50 | pub fn new(ty: ErrorType, msg: &'static str, span: Span) -> Self { 51 | Self { 52 | annotations: Vec::new(), 53 | ty, 54 | msg: Cow::Borrowed(msg), 55 | span, 56 | } 57 | } 58 | 59 | pub fn new_owned(ty: ErrorType, msg: String, span: Span) -> Self { 60 | Self { 61 | annotations: Vec::new(), 62 | ty, 63 | msg: Cow::Owned(msg), 64 | span, 65 | } 66 | } 67 | 68 | pub fn with_annot_owned(mut self, msg: String, span: Span) -> Self { 69 | self.annotations.push(Annotation::new_owned(msg, span)); 70 | self 71 | } 72 | 73 | pub fn with_annot(mut self, msg: &'static str, span: Span) -> Self { 74 | self.annotations.push(Annotation::new(msg, span)); 75 | self 76 | } 77 | 78 | pub fn with_annot_no_msg(mut self, span: Span) -> Self { 79 | self.annotations.push(Annotation::new_msgless(span)); 80 | self 81 | } 82 | } 83 | 84 | impl Into> for Diagnostic { 85 | fn into(self) -> CSDiagnostic { 86 | CSDiagnostic::new(Severity::Error) 87 | .with_code(self.ty) 88 | .with_message(self.msg) 89 | .with_labels(self.annotations.into_iter().map(|a| a.into()).collect()) 90 | } 91 | } 92 | 93 | #[derive(Debug)] 94 | pub struct Annotation { 95 | msg: Option>, 96 | span: Span, 97 | } 98 | 99 | impl Into> for Annotation { 100 | fn into(self) -> Label { 101 | let mut label = Label::new(LabelStyle::Primary, self.span.fid, self.span); 102 | if let Some(msg) = self.msg { 103 | label = label.with_message(msg); 104 | } 105 | label 106 | } 107 | } 108 | 109 | impl Annotation { 110 | pub fn new_owned(msg: String, span: Span) -> Self { 111 | Self { 112 | msg: Some(Cow::Owned(msg)), 113 | span, 114 | } 115 | } 116 | 117 | pub fn new_msgless(span: Span) -> Self { 118 | Self { msg: None, span } 119 | } 120 | 121 | pub fn new(msg: &'static str, span: Span) -> Self { 122 | Self { 123 | msg: Some(Cow::Borrowed(msg)), 124 | span, 125 | } 126 | } 127 | } 128 | 129 | #[derive(Debug)] 130 | pub enum ErrorType { 131 | LexError, 132 | } 133 | 134 | impl Display for ErrorType { 135 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 136 | write!( 137 | f, 138 | "{}", 139 | match self { 140 | Self::LexError => "lex_error", 141 | } 142 | ) 143 | } 144 | } 145 | 146 | impl Into for ErrorType { 147 | fn into(self) -> String { 148 | self.to_string() 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/common/interner.rs: -------------------------------------------------------------------------------- 1 | // Credit to https://matklad.github.io/2020/03/22/fast-simple-rust-interner.html 2 | use std::mem; 3 | 4 | use hashbrown::HashMap; 5 | 6 | #[derive(Default, Clone, Debug, Copy, PartialEq, Eq, Hash)] 7 | #[repr(transparent)] 8 | pub struct StrId(pub usize); 9 | 10 | impl StrId { 11 | pub fn inner(self) -> usize { 12 | self.0 13 | } 14 | } 15 | 16 | #[derive(Default, Debug)] 17 | pub struct Interner { 18 | map: HashMap<&'static str, StrId>, 19 | vec: Vec<&'static str>, 20 | buf: String, 21 | full: Vec, 22 | } 23 | 24 | impl Interner { 25 | pub fn with_capacity(cap: usize) -> Interner { 26 | let cap = cap.next_power_of_two(); 27 | Interner { 28 | map: HashMap::default(), 29 | vec: Vec::new(), 30 | buf: String::with_capacity(cap), 31 | full: Vec::new(), 32 | } 33 | } 34 | 35 | pub fn intern(&mut self, name: &str) -> StrId { 36 | if let Some(&id) = self.map.get(name) { 37 | return id; 38 | } 39 | let name = unsafe { self.alloc(name) }; 40 | let id = StrId(self.map.len()); 41 | self.map.insert(name, id); 42 | self.vec.push(name); 43 | 44 | debug_assert!(self.lookup(id) == name); 45 | debug_assert!(self.intern(name) == id); 46 | 47 | id 48 | } 49 | 50 | pub fn lookup(&self, id: StrId) -> &str { 51 | self.vec[id.0] 52 | } 53 | 54 | unsafe fn alloc(&mut self, name: &str) -> &'static str { 55 | let cap = self.buf.capacity(); 56 | if cap < self.buf.len() + name.len() { 57 | let new_cap = (cap.max(name.len()) + 1).next_power_of_two(); 58 | let new_buf = String::with_capacity(new_cap); 59 | let old_buf = mem::replace(&mut self.buf, new_buf); 60 | self.full.push(old_buf); 61 | } 62 | 63 | let interned = { 64 | let start = self.buf.len(); 65 | self.buf.push_str(name); 66 | &self.buf[start..] 67 | }; 68 | 69 | &*(interned as *const str) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod diagnostic; 2 | pub mod interner; 3 | -------------------------------------------------------------------------------- /src/frontend/ast.rs: -------------------------------------------------------------------------------- 1 | use crate::common::diagnostic::Spanned; 2 | 3 | #[derive(Debug)] 4 | pub enum Statement { 5 | Expression(Spanned), 6 | } 7 | 8 | #[derive(Debug)] 9 | pub enum Expr {} 10 | -------------------------------------------------------------------------------- /src/frontend/lex.rs: -------------------------------------------------------------------------------- 1 | use std::{iter::Peekable, str::Chars}; 2 | 3 | use crate::common::{ 4 | diagnostic::{Diagnostic, ErrorType, Failable, FileId, Span, Spanned, Tagged}, 5 | interner::{Interner, StrId}, 6 | }; 7 | 8 | pub struct Lexer<'a> { 9 | stream: Peekable>, 10 | position: usize, 11 | file_id: FileId, 12 | interner: &'a mut Interner, 13 | } 14 | 15 | #[derive(Debug, PartialEq)] 16 | pub enum Token { 17 | LParen, 18 | RParen, 19 | LAngle, 20 | RAngle, 21 | 22 | Equals, 23 | Colon, 24 | 25 | Fn, 26 | End, 27 | Type, 28 | Case, 29 | Do, 30 | Import, 31 | 32 | String(StrId), 33 | Operator(StrId), 34 | Atom(StrId), 35 | Ident(StrId), 36 | 37 | Eof, 38 | } 39 | 40 | const EOF: char = '\0'; 41 | 42 | impl<'a> Iterator for Lexer<'a> { 43 | type Item = Failable>; 44 | fn next(&mut self) -> Option { 45 | Some(self.next_token()) 46 | } 47 | } 48 | 49 | impl<'a> Lexer<'a> { 50 | pub fn new( 51 | stream: Peekable>, 52 | file_id: FileId, 53 | interner: &'a mut Interner, 54 | ) -> Lexer<'a> { 55 | Self { 56 | stream, 57 | file_id, 58 | position: 0, 59 | interner, 60 | } 61 | } 62 | 63 | pub fn next_token(&mut self) -> Failable> { 64 | let start_pos = self.position; 65 | let token = match self.eat() { 66 | '(' => Token::LParen, 67 | ')' => Token::RParen, 68 | '>' => Token::LAngle, 69 | '<' => Token::RAngle, 70 | ':' => { 71 | let next = self.peek_char(); 72 | if Self::is_id_start(next) { 73 | self.eat(); 74 | let s = self.consume_while(next, Self::is_id_continue); 75 | Token::Atom(self.interner.intern(&s)) 76 | } else { 77 | let span = Span::new(start_pos, start_pos + 1, self.file_id); 78 | return Err(Diagnostic::new( 79 | ErrorType::LexError, 80 | "valid identitfier must follow `:`", 81 | span, 82 | )); 83 | } 84 | } 85 | c if Self::is_id_start(c) => { 86 | let s = self.consume_while(c, Self::is_id_continue); 87 | Token::Ident(self.interner.intern(&s)) 88 | } 89 | c if Self::is_whitespace(c) => { 90 | return self.next_token(); 91 | } 92 | '\0' => Token::Eof, 93 | _ => { 94 | let span = Span::new(start_pos, start_pos + 1, self.file_id); 95 | return Err( 96 | Diagnostic::new(ErrorType::LexError, "unrecognized character", span) 97 | .with_annot_no_msg(span), 98 | ); 99 | } 100 | }; 101 | let end_pos = self.position; 102 | Ok(Tagged(token, Span::new(start_pos, end_pos, self.file_id))) 103 | } 104 | 105 | fn is_operator(c: char) -> bool { 106 | match c { 107 | '!' | '@' | '$' | '%' | '^' | '&' | '*' | '-' | '+' | '=' | '>' | '<' | '.' | '?' 108 | | '/' => true, 109 | _ => false, 110 | } 111 | } 112 | 113 | #[inline] 114 | /// Go forward one character in the character stream 115 | fn eat(&mut self) -> char { 116 | self.position += 1; 117 | self.stream.next().unwrap_or(EOF) 118 | } 119 | 120 | #[inline] 121 | /// Go forward one character without peeking 122 | fn peek_char(&mut self) -> char { 123 | *self.stream.peek().unwrap_or(&EOF) 124 | } 125 | 126 | #[inline] 127 | fn is_id_start(c: char) -> bool { 128 | c.is_ascii_alphabetic() || c == '_' 129 | } 130 | 131 | #[inline] 132 | fn is_id_continue(c: char) -> bool { 133 | c.is_ascii_alphanumeric() || c == '_' 134 | } 135 | 136 | #[inline] 137 | fn is_eof(&mut self) -> bool { 138 | self.peek_char() == EOF 139 | } 140 | 141 | #[inline] 142 | fn is_whitespace(c: char) -> bool { 143 | c == ' ' || c == '\n' || c == '\t' 144 | } 145 | 146 | #[inline] 147 | fn consume_while(&mut self, first: char, predicate: impl Fn(char) -> bool) -> String { 148 | let mut acc = String::with_capacity(1); 149 | acc.push(first); 150 | while predicate(self.peek_char()) && !self.is_eof() { 151 | acc.push(self.eat()); 152 | } 153 | acc 154 | } 155 | 156 | fn consume_until(&mut self, predicate: impl Fn(char) -> bool) -> String { 157 | let mut acc = String::with_capacity(1); 158 | while predicate(self.peek_char()) && !self.is_eof() { 159 | acc.push(self.eat()); 160 | } 161 | acc 162 | } 163 | } 164 | 165 | #[cfg(test)] 166 | mod test_lexer { 167 | use super::*; 168 | use std::collections::HashMap; 169 | 170 | macro_rules! assert_tokens { 171 | ($input_str: expr, $token_slice: expr, $test_name: ident) => { 172 | #[test] 173 | fn $test_name() -> Failable<()> { 174 | let stream = $input_str.chars().peekable(); 175 | let mut interner = Interner::default(); 176 | let mut lexer = Lexer::new(stream, FileId::default(), &mut interner); 177 | let mut next_tok = lexer.next().unwrap()?.0; 178 | let mut collected_tokens = Vec::new(); 179 | while next_tok != Token::Eof { 180 | collected_tokens.push(next_tok); 181 | next_tok = lexer.next().unwrap()?.0; 182 | } 183 | assert_eq!(collected_tokens, $token_slice); 184 | Ok(()) 185 | } 186 | }; 187 | } 188 | 189 | assert_tokens!("", [], empty); 190 | assert_tokens!("()", [Token::LParen, Token::RParen], paren); 191 | assert_tokens!(" ( \t) ", [Token::LParen, Token::RParen], paren_with_space); 192 | 193 | macro_rules! match_tokens { 194 | ($test_name: ident, $input_str: expr, {$($k:expr => $v:expr),*}, $($matches: pat),*) => { 195 | #[test] 196 | fn $test_name() -> Failable<()> { 197 | let stream = $input_str.chars().peekable(); 198 | let mut interner = Interner::default(); 199 | let mut lexer = Lexer::new(stream, FileId::default(), &mut interner); 200 | 201 | let mut token = 0; 202 | $({ 203 | let next_tok = lexer.next().unwrap()?.0; 204 | if let $matches = next_tok { 205 | token += 1; 206 | } else { 207 | panic!("Failed at token {}. Found token: {:?}.", token, next_tok) 208 | } 209 | })* 210 | let next_tok = lexer.next().unwrap()?.0; 211 | assert_eq!(next_tok, Token::Eof); 212 | 213 | let expectations: HashMap = HashMap::from([$(($k, $v),)*]); 214 | for (k, v) in expectations { 215 | let s = interner.lookup(StrId(k)); 216 | assert_eq!(s, v, "Index {} was `{}`, but expected it to be `{}`", k, s, v); 217 | } 218 | Ok(()) 219 | } 220 | }; 221 | } 222 | 223 | match_tokens!( 224 | atom_test, 225 | "(:testing ok)", 226 | {0 => "testing", 1 => "ok"}, 227 | Token::LParen, 228 | Token::Atom(StrId(0)), 229 | Token::Ident(StrId(1)), 230 | Token::RParen 231 | ); 232 | 233 | match_tokens!( 234 | ident_with_space, 235 | "abc efghijklmno_pqrstuvwxyz _QWERTYUIOP ASDFGHJKL ZXCVBNM", 236 | { 237 | 0 => "abc", 238 | 1 => "efghijklmno_pqrstuvwxyz", 239 | 2 => "_QWERTYUIOP", 240 | 3 => "ASDFGHJKL", 241 | 4 => "ZXCVBNM" 242 | }, 243 | Token::Ident(StrId(0)), 244 | Token::Ident(StrId(1)), 245 | Token::Ident(StrId(2)), 246 | Token::Ident(StrId(3)), 247 | Token::Ident(StrId(4)) 248 | ); 249 | } 250 | -------------------------------------------------------------------------------- /src/frontend/mod.rs: -------------------------------------------------------------------------------- 1 | mod ast; 2 | mod lex; 3 | mod parse; 4 | -------------------------------------------------------------------------------- /src/frontend/parse.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::read_to_string; 2 | 3 | mod common; 4 | mod frontend; 5 | 6 | fn main() { 7 | let file = read_to_string("tests/io/print.fl").unwrap(); 8 | println!("{:?}", file); 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/case.fl: -------------------------------------------------------------------------------- 1 | # { "output": "none\n123\n" } 2 | type Maybe = (:some, v) | :none 3 | 4 | none_value = :none 5 | 6 | fnprint_maybe(val: Maybe) 7 | case val do 8 | :none -> print("none") 9 | (:some, v) -> print(v) 10 | end 11 | end 12 | 13 | print_maybe(:none) 14 | print_maybe((:some, 123)) 15 | -------------------------------------------------------------------------------- /tests/data/result.fl: -------------------------------------------------------------------------------- 1 | # { "output": "error!\n1\n" } 2 | type Result = (:ok, t) | (:err, e) 3 | 4 | ok_val: Result = (:ok, 1) 5 | err_val: Result = (:err, "error!") 6 | 7 | (:err, error) = err_val 8 | print(error) 9 | 10 | (:ok, val) = ok_val 11 | print(val) 12 | -------------------------------------------------------------------------------- /tests/functions/extern.fl: -------------------------------------------------------------------------------- 1 | # { "output": "5\n" } 2 | extern fn add(x: int, y: int): int 3 | 4 | print(add(2, 3)) 5 | -------------------------------------------------------------------------------- /tests/functions/one_arg.fl: -------------------------------------------------------------------------------- 1 | # { "output": "3\n" } 2 | fn identity(x): int 3 | x 4 | end 5 | 6 | print(identity(3)) 7 | -------------------------------------------------------------------------------- /tests/functions/single_arg.fl: -------------------------------------------------------------------------------- 1 | # { "output": "2\n" } 2 | fn two(): int 3 | 2 4 | end 5 | 6 | print(two()) 7 | -------------------------------------------------------------------------------- /tests/functions/two_arg.fl: -------------------------------------------------------------------------------- 1 | # { "output": "5\n" } 2 | fn add(x: int, y: int): int 3 | x + y 4 | end 5 | 6 | print(add(3, 2)) 7 | -------------------------------------------------------------------------------- /tests/io/print.fl: -------------------------------------------------------------------------------- 1 | # { "output": "hello there\n" } 2 | print("hello there") 3 | -------------------------------------------------------------------------------- /tests/operators/custom.fl: -------------------------------------------------------------------------------- 1 | # { "output": "false\nfalse\n" } 2 | fn <*>(l: int, r: int): bool 3 | false 4 | end 5 | 6 | print(3 <*> 3) 7 | print(2 <*> 3) 8 | -------------------------------------------------------------------------------- /tests/operators/id_as_op.fl: -------------------------------------------------------------------------------- 1 | # { "output": "127\n" } 2 | fn add(x: int, y: int): int 3 | x + y 4 | end 5 | 6 | print(123 `add` 4) 7 | -------------------------------------------------------------------------------- /tests/operators/overloading.fl: -------------------------------------------------------------------------------- 1 | # { "output": "6\nfalse\n" } 2 | fn <^>(l: int, r: int): int 3 | l + r 4 | end 5 | 6 | fn <^>(l: int, r: bool): bool 7 | r 8 | end 9 | 10 | print(3 <^> 3) 11 | print(2 <^> false) 12 | -------------------------------------------------------------------------------- /tests/space/basic.fl: -------------------------------------------------------------------------------- 1 | # { "output": "123\nhello\n" } 2 | import space 3 | 4 | type NumOrStr = (:number, int) | (:string, str) 5 | 6 | add_value = fn( 7 | s: space.Space, 8 | value: NumOrStr, 9 | ) 10 | s.add(value) 11 | end 12 | 13 | test_space = space.Space.new() 14 | add_value(test_space, (:number, 123)) 15 | add_value(test_space, (:string, "hello")) 16 | 17 | (:number, x) = test_space.take() 18 | (:string, str) = test_space.take() 19 | 20 | print(x) 21 | print(str) 22 | --------------------------------------------------------------------------------