├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bin └── fir.rs ├── build.rs ├── cloc_lang_def.txt ├── compiler ├── Ast.fir ├── AstPrinter.fir ├── Error.fir ├── Lexer.fir ├── Main.fir ├── ParseTree.fir ├── Peg.fir ├── PegGrammar.fir ├── PegGrammar.peg ├── PegTestLib.fir ├── PegTests.fir ├── Scanner.fir ├── TestGrammar.fir ├── TestGrammar.peg ├── Token.fir ├── TypeGrammar.fir ├── TypeGrammar.peg ├── TypeGrammarTest.fir └── test_lexer.sh ├── editor └── vim │ ├── ftdetect │ └── fir.vim │ ├── ftplugin │ └── fir.vim │ ├── indent │ └── fir.vim │ └── syntax │ └── fir.vim ├── justfile ├── lib ├── Array.fir ├── Char.fir ├── Exn.fir ├── HashMap.fir ├── Iter.fir ├── Num.fir ├── Option.fir ├── PPrint.fir ├── Prelude.fir ├── Result.fir ├── Str.fir ├── StrBuf.fir └── Vec.fir ├── src ├── ast.rs ├── ast │ └── printer.rs ├── collections.rs ├── import_resolver.rs ├── interpolation.rs ├── interpreter.rs ├── interpreter │ └── heap.rs ├── lexer.rs ├── lib.rs ├── lowering.rs ├── lowering │ └── printer.rs ├── mono_ast.rs ├── mono_ast │ └── printer.rs ├── monomorph.rs ├── parser.lalrpop ├── parser.rs ├── parser_utils.rs ├── record_collector.rs ├── scanner.rs ├── scope_map.rs ├── token.rs ├── type_checker.rs ├── type_checker │ ├── apply.rs │ ├── convert.rs │ ├── expr.rs │ ├── instantiation.rs │ ├── kind_inference.rs │ ├── pat.rs │ ├── pat_coverage.rs │ ├── row_utils.rs │ ├── stmt.rs │ ├── traits.rs │ ├── ty.rs │ ├── ty_map.rs │ └── unification.rs └── utils.rs └── tests ├── AndOrEvaluation.fir ├── ArithmeticExpressions.fir ├── AsExpr.fir ├── AssignValBug.fir ├── AssocFnClosures.fir ├── AssocFnClosuresGeneric.fir ├── AssocFnMethodNameConflict.fir ├── AssocFnParentNameCheck.fir ├── AssocFnSelectFail.fir ├── BitwiseOps.fir ├── Break.fir ├── BreakContinueTypeChecking.fir ├── BreakFail.fir ├── CaptureSelf.fir ├── Char.fir ├── CharEscapes.fir ├── ClosureTypeInference1.fir ├── Closures1.fir ├── Closures2.fir ├── ClosuresFail1.fir ├── ClosuresFail2.fir ├── CmdArgs.fir ├── Comments.fir ├── ConPatFail1.fir ├── ConPatFail2.fir ├── ConPatFail3.fir ├── ConPatFail4.fir ├── Continue.fir ├── ContinueFail.fir ├── DotDotPat.fir ├── EmptyTrait.fir ├── EmptyType.fir ├── ErrorHandling.fir ├── Exn1.fir ├── ExnFail1.fir ├── ExnFail2.fir ├── ExnFail3.fir ├── ExnFail4.fir ├── ExplicitTyArgs.fir ├── FnExnTypes1.fir ├── FnExnTypes2.fir ├── FnTypes1.fir ├── ForLoop.fir ├── ForPatBind.fir ├── HashMap.fir ├── Hi.fir ├── IfCheckingFail1.fir ├── IfMatchStmtChecking.fir ├── ImplBounds1.fir ├── ImplBounds2.fir ├── ImplBounds3.fir ├── ImplBoundsFail1.fir ├── ImplMethodTypeChecking.fir ├── ImplicitTyParams.fir ├── IntTyInference.fir ├── IsExpr.fir ├── IsExprFail1.fir ├── IsExprFail2.fir ├── IsExprFail3.fir ├── IsExprInWhile.fir ├── IsExprShadowing.fir ├── IterChain.fir ├── IterCount.fir ├── IterMap.fir ├── IterSkip.fir ├── LetLoweringBug.fir ├── LineBreaks.fir ├── LoopLabels.fir ├── MatchCheckingFail1.fir ├── MatchGuards.fir ├── MatchInRhs.fir ├── MatchTcBug.fir ├── MonomorphiserShadowing.fir ├── MultipleBounds.fir ├── NamedFieldShorthand.fir ├── NamedFields1.fir ├── OrPatBinders.fir ├── PPrintExample.fir ├── Panic.fir ├── ParserCombinators1.fir ├── ParserCombinators2.fir ├── PatCon1.fir ├── PatConFail1.fir ├── PatConFail2.fir ├── PatConFail3.fir ├── PatConFail4.fir ├── Peekable.fir ├── PolyMethods.fir ├── ProductNamedFieldPatShorthand.fir ├── RecordNamedFieldFail1.fir ├── RecordNamedFieldShorthand.fir ├── Records.fir ├── ReturnTy.fir ├── ReturnTypeExpectedType.fir ├── Rows1.fir ├── Rows2.fir ├── Rows3.fir ├── RowsFail1.fir ├── RowsFail2.fir ├── RowsFail3.fir ├── RowsFail4.fir ├── RowsFail5.fir ├── SameMethodName.fir ├── SingleLineFnSyntax.fir ├── Str.fir ├── StrBuf.fir ├── StrCharIndices.fir ├── StrEscapes.fir ├── StrLines.fir ├── StrPatEscape.fir ├── StrPats.fir ├── StrSplitWhitespace.fir ├── StringContinuationEscape.fir ├── SumNamedFieldPatShorthand.fir ├── Tc1.fir ├── Tc2.fir ├── Tc3.fir ├── Tc4.fir ├── Tc5.fir ├── TcFail1.fir ├── TcFail2.fir ├── TcFail3.fir ├── TcFail4.fir ├── ThrowNonVariant.fir ├── ThrowingIter.fir ├── ThrowingIter1.fir ├── ThrowingIter2.fir ├── ThrowingIter3.fir ├── TraitImplFail1.fir ├── TraitImplFail2.fir ├── TraitImplFail4.fir ├── TraitImplSelfTyFail.fir ├── TraitMethodTyVarSubst.fir ├── TraitVariantTyParamKind.fir ├── Traits1.fir ├── Traits2.fir ├── Traits3.fir ├── Traits4.fir ├── TraitsFail1.fir ├── TraitsFail2.fir ├── TraitsFail3.fir ├── TraitsFail4.fir ├── UnificationRowKind.fir ├── UnificationRowKindFail1.fir ├── UnificationRowKindFail2.fir ├── UnusedTyParam.fir ├── VariantTypeRefinement.fir ├── Vec1.fir ├── VecGrowFromZero.fir ├── VecIter.fir ├── VecSort.fir ├── WhileLetLoop.fir └── WhileLoop.fir /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Get stable toolchain 13 | uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: stable 16 | override: true 17 | 18 | - name: Build 19 | run: cargo build --verbose 20 | 21 | check_formatting: 22 | name: Check formatting 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Get stable toolchain 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: stable 31 | override: true 32 | 33 | - name: 'Check formatting' 34 | run: cargo fmt --all --check 35 | 36 | check_lints: 37 | name: Check lints 38 | needs: [build] 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | 43 | - name: Get Rust stable toolchain 44 | uses: actions-rs/toolchain@v1 45 | with: 46 | toolchain: stable 47 | override: true 48 | 49 | - name: Check lints 50 | run: cargo clippy --all-targets --all -- -Dwarnings 51 | 52 | run_compiler_unit_tests: 53 | name: Compiler unit tests 54 | needs: [build] 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v2 58 | 59 | - name: Get stable toolchain 60 | uses: actions-rs/toolchain@v1 61 | with: 62 | toolchain: stable 63 | override: true 64 | 65 | - name: Install just 66 | run: sudo apt install -y just 67 | 68 | - name: Test 69 | run: just compiler_unit_test 70 | 71 | run_compiler_golden_tests: 72 | name: Compiler golden tests 73 | needs: [build] 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@v2 77 | 78 | - name: Get stable toolchain 79 | uses: actions-rs/toolchain@v1 80 | with: 81 | toolchain: stable 82 | override: true 83 | 84 | - name: Install just 85 | run: sudo apt install -y just 86 | 87 | - name: Install goldentests 88 | run: cargo install --git https://github.com/osa1/golden-tests --features=binary 89 | 90 | - name: Build interpreter 91 | run: cargo build --verbose 92 | 93 | - name: Test 94 | run: just compiler_golden_test 95 | 96 | run_interpreter_unit_tests: 97 | name: Interpreter unit tests 98 | needs: [build] 99 | runs-on: ubuntu-latest 100 | steps: 101 | - uses: actions/checkout@v2 102 | 103 | - name: Get stable toolchain 104 | uses: actions-rs/toolchain@v1 105 | with: 106 | toolchain: stable 107 | override: true 108 | 109 | - name: Install just 110 | run: sudo apt install -y just 111 | 112 | - name: Test 113 | run: just interpreter_unit_test 114 | 115 | run_interpreter_golden_tests: 116 | name: Interpreter golden tests 117 | needs: [build] 118 | runs-on: ubuntu-latest 119 | steps: 120 | - uses: actions/checkout@v2 121 | 122 | - name: Get stable toolchain 123 | uses: actions-rs/toolchain@v1 124 | with: 125 | toolchain: stable 126 | override: true 127 | 128 | - name: Install just 129 | run: sudo apt install -y just 130 | 131 | - name: Install goldentests 132 | run: cargo install --git https://github.com/osa1/golden-tests --features=binary 133 | 134 | - name: Build interpreter 135 | run: cargo build --verbose 136 | 137 | - name: Test 138 | run: just interpreter_golden_test 139 | 140 | check_generated_files: 141 | name: Check that generated files are up-to-date 142 | needs: [build] 143 | runs-on: ubuntu-latest 144 | steps: 145 | - uses: actions/checkout@v2 146 | 147 | - name: Get stable toolchain 148 | uses: actions-rs/toolchain@v1 149 | with: 150 | toolchain: stable 151 | override: true 152 | 153 | - name: Install just 154 | run: sudo apt install -y just 155 | 156 | - name: Install lalrpop 157 | run: cargo install lalrpop 158 | 159 | - name: Build interpreter 160 | run: cargo build --verbose 161 | 162 | - name: Check 163 | run: | 164 | just update_generated_files 165 | git -P diff 166 | ! git status --porcelain | grep -q '^ M' 167 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fir" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [[bin]] 10 | name = "fir" 11 | path = "bin/fir.rs" 12 | 13 | [dependencies] 14 | bytemuck = "1.16.0" 15 | clap = { version = "4.5.27", default-features = false, features = ["std", "string", "help", "error-context", "usage", "suggestions"] } 16 | console_error_panic_hook = "0.1.7" 17 | fnv = "1.0.7" 18 | indoc = "2.0.5" 19 | lalrpop-util = "0.20.2" 20 | lazy_static = "1.4.0" 21 | lexgen = "0.16.0" 22 | lexgen_util = "0.16.0" 23 | ordermap = "0.5.5" 24 | rustc_tools_util = "0.4.0" 25 | smol_str = "0.2.2" 26 | wasm-bindgen = "0.2" 27 | 28 | [build-dependencies] 29 | rustc_tools_util = "0.4.0" 30 | 31 | [profile.release] 32 | panic = "abort" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Ömer Sinan Ağacan 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 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, 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, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fir programming language 2 | 3 | Fir is a programming language with: 4 | 5 | - Static types and indentation sensitive syntax 6 | - Traits (typeclasses) for polymorphism 7 | - Monomorphisation and unboxed/value types 8 | - Effects 9 | - [Built-in tooling][tooling] and a [large standard library][libs] 10 | 11 | For introduction, see blog posts: 12 | 13 | - [Error handling in Fir][1] 14 | - [Throwing iterators in Fir][2] 15 | 16 | [1]: https://osa1.net/posts/2025-01-18-fir-error-handling.html 17 | [2]: https://osa1.net/posts/2025-04-17-throwing-iterators-fir.html 18 | [tooling]: https://github.com/fir-lang/fir/issues/28 19 | [libs]: https://github.com/fir-lang/fir/issues/76 20 | -------------------------------------------------------------------------------- /bin/fir.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let matches = clap::Command::new("fir") 3 | .version(version_info_str(rustc_tools_util::get_version_info!())) 4 | .arg( 5 | clap::Arg::new(TYPECHECK) 6 | .long(TYPECHECK) 7 | .num_args(0) 8 | .help("Don't run the program, only type check."), 9 | ) 10 | .arg( 11 | clap::Arg::new(NO_PRELUDE) 12 | .long(NO_PRELUDE) 13 | .num_args(0) 14 | .help("Don't implicitly import Prelude."), 15 | ) 16 | .arg( 17 | clap::Arg::new(NO_BACKTRACE) 18 | .long(NO_BACKTRACE) 19 | .num_args(0) 20 | .help("Don't print backtraces in panics."), 21 | ) 22 | .arg( 23 | clap::Arg::new(PRINT_TOKENS) 24 | .long(PRINT_TOKENS) 25 | .num_args(0) 26 | .help("Print tokens."), 27 | ) 28 | .arg( 29 | clap::Arg::new(PRINT_PARSED_AST) 30 | .long(PRINT_PARSED_AST) 31 | .num_args(0) 32 | .help("Print AST after parsing."), 33 | ) 34 | .arg( 35 | clap::Arg::new(PRINT_CHECKED_AST) 36 | .long(PRINT_CHECKED_AST) 37 | .num_args(0) 38 | .help("Print AST after type checking."), 39 | ) 40 | .arg( 41 | clap::Arg::new(PRINT_MONO_AST) 42 | .long(PRINT_MONO_AST) 43 | .num_args(0) 44 | .help("Print AST after monomorphisation."), 45 | ) 46 | .arg( 47 | clap::Arg::new(PRINT_LOWERED_AST) 48 | .long(PRINT_LOWERED_AST) 49 | .num_args(0) 50 | .help("Print AST after lowering."), 51 | ) 52 | .arg( 53 | clap::Arg::new(MAIN) 54 | .long(MAIN) 55 | .num_args(1) 56 | .required(false) 57 | .default_value("main") 58 | .help("Name of the main function to run."), 59 | ) 60 | .arg( 61 | clap::Arg::new(PROGRAM) 62 | .num_args(1) 63 | .required(true) 64 | .help("Path to the program to run."), 65 | ) 66 | .arg( 67 | clap::Arg::new(IMPORT_PATH) 68 | .long(IMPORT_PATH) 69 | .short('i') 70 | .action(clap::ArgAction::Append) 71 | .help("= pairs for resolving imports") 72 | .value_parser(parse_key_val), 73 | ) 74 | .arg( 75 | clap::Arg::new(PROGRAM_ARGS) 76 | .last(true) 77 | .allow_hyphen_values(true) 78 | .num_args(0..), 79 | ) 80 | .get_matches(); 81 | 82 | let compiler_opts = fir::CompilerOpts { 83 | typecheck: matches.get_flag(TYPECHECK), 84 | no_prelude: matches.get_flag(NO_PRELUDE), 85 | no_backtrace: matches.get_flag(NO_BACKTRACE), 86 | print_tokens: matches.get_flag(PRINT_TOKENS), 87 | print_parsed_ast: matches.get_flag(PRINT_PARSED_AST), 88 | print_checked_ast: matches.get_flag(PRINT_CHECKED_AST), 89 | print_mono_ast: matches.get_flag(PRINT_MONO_AST), 90 | print_lowered_ast: matches.get_flag(PRINT_LOWERED_AST), 91 | main: matches.get_one(MAIN).cloned().unwrap(), 92 | import_paths: matches 93 | .get_many::<(String, String)>(IMPORT_PATH) 94 | .map(|pairs| pairs.map(|(k, v)| (k.clone(), v.clone())).collect()) 95 | .unwrap_or_default(), 96 | }; 97 | 98 | let program: String = matches.get_one::(PROGRAM).unwrap().clone(); 99 | 100 | let program_args: Vec = match matches.get_many::(PROGRAM_ARGS) { 101 | Some(args) => args.cloned().collect(), 102 | None => vec![], 103 | }; 104 | 105 | fir::main(compiler_opts, program, program_args) 106 | } 107 | 108 | const TYPECHECK: &str = "typecheck"; 109 | const NO_PRELUDE: &str = "no-prelude"; 110 | const NO_BACKTRACE: &str = "no-backtrace"; 111 | const PRINT_TOKENS: &str = "print-tokens"; 112 | const PRINT_PARSED_AST: &str = "print-parsed-ast"; 113 | const PRINT_CHECKED_AST: &str = "print-checked-ast"; 114 | const PRINT_MONO_AST: &str = "print-mono-ast"; 115 | const PRINT_LOWERED_AST: &str = "print-lowered-ast"; 116 | const MAIN: &str = "main"; 117 | const PROGRAM: &str = "program"; 118 | const PROGRAM_ARGS: &str = "program-args"; 119 | const IMPORT_PATH: &str = "import-path"; 120 | 121 | // This is the same as `VersionInfo`'s `Display`, except it doesn't show the crate name as clap adds 122 | // command name as prefix in `--version`. 123 | fn version_info_str(version_info: rustc_tools_util::VersionInfo) -> String { 124 | let hash = version_info.commit_hash.clone().unwrap_or_default(); 125 | let hash_trimmed = hash.trim(); 126 | 127 | let date = version_info.commit_date.clone().unwrap_or_default(); 128 | let date_trimmed = date.trim(); 129 | 130 | if (hash_trimmed.len() + date_trimmed.len()) > 0 { 131 | format!( 132 | "{}.{}.{} ({hash_trimmed} {date_trimmed})", 133 | version_info.major, version_info.minor, version_info.patch, 134 | ) 135 | } else { 136 | format!( 137 | "{}.{}.{}", 138 | version_info.major, version_info.minor, version_info.patch 139 | ) 140 | } 141 | } 142 | 143 | fn parse_key_val(s: &str) -> Result<(String, String), String> { 144 | let parts: Vec<&str> = s.splitn(2, '=').collect(); 145 | if parts.len() != 2 { 146 | return Err(format!("invalid key=value: `{}`", s)); 147 | } 148 | Ok((parts[0].to_string(), parts[1].to_string())) 149 | } 150 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | rustc_tools_util::setup_version_info!(); 3 | } 4 | -------------------------------------------------------------------------------- /cloc_lang_def.txt: -------------------------------------------------------------------------------- 1 | # Fir language definition for cloc. 2 | # 3 | # Example use: cloc --read-lang-def=cloc_lang_def.txt lib compiler 4 | 5 | Fir 6 | filter remove_inline #.*$ 7 | filter remove_between_general #| |# 8 | extension fir 9 | 3rd_gen_scale 1.25 10 | -------------------------------------------------------------------------------- /compiler/Error.fir: -------------------------------------------------------------------------------- 1 | type Error: 2 | loc: Loc 3 | msg: Str 4 | 5 | type Loc: 6 | file: Str 7 | byteIdx: U32 8 | byteLen: U32 9 | line: U32 10 | col: U32 11 | 12 | # TODO: Debug assert that `self.file == other.file` 13 | Loc.combine(self, other: Loc): Loc 14 | Loc(file = self.file, 15 | byteIdx = self.byteIdx, 16 | byteLen = other.byteIdx + other.byteLen - self.byteIdx, 17 | line = self.line, 18 | col = self.col) 19 | -------------------------------------------------------------------------------- /compiler/Main.fir: -------------------------------------------------------------------------------- 1 | import Ast 2 | import AstPrinter 3 | import Lexer 4 | import Scanner 5 | 6 | main(args: Array[Str]) 7 | if args.len() >= 2: 8 | let filePath = args.get(1) 9 | let fileContents = readFileUtf8(filePath) 10 | let (tokens, error) = tokenize(filePath, fileContents) 11 | tokens = scan(tokens) 12 | for token: Token in tokens.iter(): 13 | print(token) 14 | if error is Option.Some(error): 15 | printStr("ERROR: `error.loc.byteIdx`: `error.msg`") 16 | return 17 | 18 | print("SCANNER TESTS") 19 | print("") 20 | scannerTests() 21 | 22 | assertEq[Eq[t], ToStr[t]](expected: t, found: t): [AssertionError(msg: Str), ..r] () 23 | if expected != found: 24 | throw(~AssertionError(msg = "Expected `expected`, found `found`")) 25 | -------------------------------------------------------------------------------- /compiler/ParseTree.fir: -------------------------------------------------------------------------------- 1 | type ParseTree[t, nt]: 2 | Terminal(t) 3 | 4 | NonTerminal: 5 | kind: nt 6 | nodes: Vec[ParseTree[t, nt]] 7 | 8 | type ParseError[t]: 9 | UnexpectedEof 10 | UnexpectedToken(t) 11 | 12 | impl[ToStr[t], ToStr[nt]] ToStr[ParseTree[t, nt]]: 13 | toStr(self: ParseTree[t, nt]): Str 14 | self.toDoc().render(80) 15 | 16 | impl[ToStr[t]] ToStr[ParseError[t]]: 17 | toStr(self: ParseError[t]): Str 18 | match self: 19 | ParseError.UnexpectedEof: "unexpected end of input" 20 | ParseError.UnexpectedToken(t): "unexpected token `t`" 21 | 22 | ParseTree.toDoc[ToStr[t], ToStr[nt]](self: ParseTree[t, nt]): Doc 23 | match self: 24 | ParseTree.Terminal(t): 25 | Doc.str(t.toStr()) 26 | 27 | ParseTree.NonTerminal(kind, nodes): 28 | if nodes.len() == 0: 29 | return Doc.str(kind.toStr()) 30 | 31 | let doc = Doc.str(kind.toStr()) + Doc.char('(') + Doc.break_(0) 32 | let nodeIdx: U32 = 0 33 | for node: ParseTree[t, nt] in nodes.iter(): 34 | if nodeIdx != 0: 35 | doc += Doc.char(',') + Doc.break_(1) 36 | doc += node.toDoc() 37 | nodeIdx += 1 38 | 39 | # Add a trailing comma when splitting the node list into lines. 40 | doc += Doc.whenNotFlat(Doc.char(',')) 41 | 42 | (doc.nest(4) + Doc.break_(0) + Doc.char(')')).group() 43 | 44 | ParseTree.asTerminal(self: ParseTree[t, nt]): t 45 | match self: 46 | ParseTree.Terminal(t): t 47 | ParseTree.NonTerminal(..): panic("ParseTree.asTerminal: tree is a non-terminal") 48 | 49 | ParseTree.asNonTerminal[Eq[nt], ToStr[nt]](self: ParseTree[t, nt], kind_: nt): Vec[ParseTree[t, nt]] 50 | match self: 51 | ParseTree.Terminal(_): 52 | panic("ParseTree.asNonTerminal: tree is a terminal") 53 | ParseTree.NonTerminal(kind, nodes): 54 | if kind != kind_: 55 | panic("ParseTree.asNonTerminal: expected `kind_`, found `kind`") 56 | nodes 57 | -------------------------------------------------------------------------------- /compiler/PegGrammar.peg: -------------------------------------------------------------------------------- 1 | # PEG grammar of PEG grammars. 2 | 3 | import ParseTree 4 | import Token 5 | 6 | type Terminal = Token 7 | type NonTerminal = NonTerminal 8 | 9 | Terminals: 10 | "lowerId" = Token(kind = TokenKind.LowerId, ..) 11 | "UpperId" = Token(kind = TokenKind.UpperId, ..) 12 | "Terminals" = Token(kind = TokenKind.UpperId, text = "Terminals", ..) 13 | "type" = Token(kind = TokenKind.Type, ..) 14 | "import" = Token(kind = TokenKind.Import, ..) 15 | ":" = Token(kind = TokenKind.Colon, ..) 16 | "INDENT" = Token(kind = TokenKind.Indent, ..) 17 | "DEDENT" = Token(kind = TokenKind.Dedent, ..) 18 | "NEWLINE" = Token(kind = TokenKind.Newline, ..) 19 | "+" = Token(kind = TokenKind.Plus, ..) 20 | "?" = Token(kind = TokenKind.Question, ..) 21 | "*" = Token(kind = TokenKind.Star, ..) 22 | "_" = Token(kind = TokenKind.Underscore, ..) 23 | "-" = Token(kind = TokenKind.Minus, ..) 24 | "=" = Token(kind = TokenKind.Eq, ..) 25 | "(" = Token(kind = TokenKind.LParen, ..) 26 | ")" = Token(kind = TokenKind.RParen, ..) 27 | "." = Token(kind = TokenKind.Dot, ..) 28 | "Str" = Token(kind = TokenKind.Str, ..) 29 | "ANY" = Token(..) 30 | 31 | grammar: 32 | importDecls typeDecls terminalsDecl nonTerminalDecls 33 | 34 | importDecls: 35 | importDecl* 36 | 37 | importDecl: 38 | _"import" "UpperId" (_"." "UpperId")* _"NEWLINE" 39 | 40 | typeDecls: 41 | typeDecl* 42 | 43 | typeDecl: 44 | # NB. We can generalize the right-hand side to types using the compiler's 45 | # type parser. 46 | _"type" "UpperId" _"=" "UpperId" _"NEWLINE" 47 | 48 | terminalsDecl: 49 | (_"Terminals" _":" _"NEWLINE" _"INDENT" terminalDecl+ _"DEDENT")? 50 | 51 | terminalDecl: 52 | "Str" _"=" (-"NEWLINE" "ANY")+ _"NEWLINE" 53 | 54 | nonTerminalDecls: 55 | nonTerminalDecl* 56 | 57 | nonTerminalDecl: 58 | "lowerId" _":" _"NEWLINE" _"INDENT" nonTerminalAlt+ _"DEDENT" 59 | 60 | nonTerminalAlt: 61 | symbol+ _"NEWLINE" 62 | 63 | symbol: 64 | symbolPrefix? symbolNonRec symbolSuffix? 65 | 66 | symbolPrefix: 67 | "-" # negative lookahead (fails when symbol succeeds) 68 | "_" # silence, or ignore (don't push match to the tree) 69 | 70 | symbolNonRec: 71 | "lowerId" # non-terminal 72 | "Str" # terminal 73 | _"(" symbol+ _")" # group 74 | 75 | symbolSuffix: 76 | "*" # zero or more 77 | "+" # one or more 78 | "?" # optional (zero or one) 79 | -------------------------------------------------------------------------------- /compiler/PegTestLib.fir: -------------------------------------------------------------------------------- 1 | import Scanner 2 | 3 | type TestError: 4 | ParseError(ParseError[Token]) 5 | Other(Str) 6 | 7 | impl ToStr[TestError]: 8 | toStr(self: TestError): Str 9 | match self: 10 | TestError.ParseError(parseError): parseError.toStr() 11 | TestError.Other(msg): msg 12 | 13 | runParser( 14 | input: Str, 15 | parseFn: Fn(Vec[Token], U32): ParseError[Token] (tree: ParseTree[Token, NonTerminal], newCursor: U32), 16 | runScan: Bool, 17 | ): TestError ParseTree[Token, NonTerminal] 18 | let (tokens, error) = tokenize("", input) 19 | if error is Option.Some(error): 20 | panic(lexerErrorStr(error)) 21 | if runScan: 22 | tokens = scan(tokens) 23 | let result = match try({ parseFn(tokens, 0) }): 24 | Result.Err(err): throw(TestError.ParseError(err)) 25 | Result.Ok(result): result 26 | if result.newCursor != tokens.len(): 27 | throw(TestError.Other("parser didn't consume all input, input len = `tokens.len()`, cursor after parsing = `result.newCursor`")) 28 | result.tree 29 | 30 | runTest( 31 | testName: Str, 32 | input: Str, 33 | parseFn: Fn(Vec[Token], U32): ParseError[Token] (tree: ParseTree[Token, NonTerminal], newCursor: U32), 34 | ): exn () 35 | print(testName) 36 | match try({ runParser(input, parseFn, Bool.False) }): 37 | Result.Ok(tree): print(tree.toDoc().render(80)) 38 | Result.Err(err): print("ERR: `err`") 39 | print("") 40 | 41 | runTest_( 42 | input: Str, 43 | parseFn: Fn(Vec[Token], U32): ParseError[Token] (tree: ParseTree[Token, NonTerminal], newCursor: U32), 44 | ): exn () 45 | print(input) 46 | match try({ runParser(input, parseFn, Bool.False) }): 47 | Result.Ok(tree): print(tree.toDoc().render(80)) 48 | Result.Err(err): print("ERR: `err`") 49 | print("") 50 | 51 | runTestIndent( 52 | input: Str, 53 | parseFn: Fn(Vec[Token], U32): ParseError[Token] (tree: ParseTree[Token, NonTerminal], newCursor: U32), 54 | ): exn Bool 55 | print(input) 56 | let result = match try({ runParser(input, parseFn, Bool.True) }): 57 | Result.Ok(tree): 58 | print(tree.toDoc().render(80)) 59 | Bool.True 60 | Result.Err(err): 61 | print("ERR: `err`") 62 | Bool.False 63 | print("") 64 | result 65 | 66 | lexerErrorStr(err: Error): Str 67 | "`err.loc.file`:`err.loc.line + 1`:`err.loc.col + 1`: `err.msg`" 68 | -------------------------------------------------------------------------------- /compiler/PegTests.fir: -------------------------------------------------------------------------------- 1 | # This program tests PEG parser generator, using the test grammar in TestGrammar.peg, compiled to 2 | # TestGrammar.fir. 3 | # 4 | # Without vector literals it's painful to compare parse trees with expected trees, so for now, this 5 | # prints parse trees, which we then compare with expected output using `goldentests`. 6 | 7 | import Lexer 8 | import PegTestLib 9 | import TestGrammar 10 | 11 | main() 12 | runTest("'a' as terminalA", "a", terminalA) 13 | runTest("'b' as terminalA (should fail)", "b", terminalA) 14 | runTest("'a b' as terminalA (should fail)", "a b", terminalA) 15 | runTest("'b' as terminalB", "b", terminalB) 16 | runTest("'a' as terminalAOrB", "a", terminalAOrB) 17 | runTest("'b' as terminalAOrB", "b", terminalAOrB) 18 | runTest("'c' as terminalAOrB (should fail)", "c", terminalAOrB) 19 | runTest("'b b' as terminalAThenB (should fail)", "b b", terminalAThenB) 20 | runTest("'a b' as terminalAThenB", "a b", terminalAThenB) 21 | runTest("'b' as zeroOrMOreAThenB", "b", zeroOrMoreAThenB) 22 | runTest("'a b' as zeroOrMoreAThenB", "a b", zeroOrMoreAThenB) 23 | runTest("'a a b' as zeroOrMoreAThenB", "a a b", zeroOrMoreAThenB) 24 | runTest("'b' as oneOrMoreAThenB", "b", oneOrMoreAThenB) 25 | runTest("'a b' as oneOrMoreAThenB", "a b", oneOrMoreAThenB) 26 | runTest("'a a b' as oneOrMoreAThenB", "a a b", oneOrMoreAThenB) 27 | runTest("'a a b' as zeroOrOneAThenB", "a a b", zeroOrOneAThenB) 28 | runTest("'a b' as zeroOrOneAThenB", "a b", zeroOrOneAThenB) 29 | runTest("'b' as zeroOrOneAThenB", "b", zeroOrOneAThenB) 30 | runTest("'a b' as ignoreAThenB", "a b", ignoreAThenB) 31 | runTest("'a b' as ignoreGroupAThenB", "a b", ignoreGroupAThenB) 32 | runTest("'a a' as nonTerminals", "a a", nonTerminals) 33 | runTest("'a b' as nonTerminals", "a b", nonTerminals) 34 | runTest("'c a' as nonTerminalsBacktrack", "c a", nonTerminalsBacktrack) 35 | runTest("'b' as negLookahead", "b", negLookahead) 36 | runTest("'a' as negLookahead (should fail)", "a", negLookahead) 37 | 38 | # expected stdout: 39 | # 'a' as terminalA 40 | # TerminalA(LowerId(1:1:"a")) 41 | # 42 | # 'b' as terminalA (should fail) 43 | # ERR: unexpected token LowerId(1:1:"b") 44 | # 45 | # 'a b' as terminalA (should fail) 46 | # ERR: parser didn't consume all input, input len = 2, cursor after parsing = 1 47 | # 48 | # 'b' as terminalB 49 | # TerminalB(LowerId(1:1:"b")) 50 | # 51 | # 'a' as terminalAOrB 52 | # TerminalAOrB(LowerId(1:1:"a")) 53 | # 54 | # 'b' as terminalAOrB 55 | # TerminalAOrB(LowerId(1:1:"b")) 56 | # 57 | # 'c' as terminalAOrB (should fail) 58 | # ERR: unexpected token LowerId(1:1:"c") 59 | # 60 | # 'b b' as terminalAThenB (should fail) 61 | # ERR: unexpected token LowerId(1:1:"b") 62 | # 63 | # 'a b' as terminalAThenB 64 | # TerminalAThenB(LowerId(1:1:"a"), LowerId(1:3:"b")) 65 | # 66 | # 'b' as zeroOrMOreAThenB 67 | # ZeroOrMoreAThenB(LowerId(1:1:"b")) 68 | # 69 | # 'a b' as zeroOrMoreAThenB 70 | # ZeroOrMoreAThenB(LowerId(1:1:"a"), LowerId(1:3:"b")) 71 | # 72 | # 'a a b' as zeroOrMoreAThenB 73 | # ZeroOrMoreAThenB(LowerId(1:1:"a"), LowerId(1:3:"a"), LowerId(1:5:"b")) 74 | # 75 | # 'b' as oneOrMoreAThenB 76 | # ERR: unexpected token LowerId(1:1:"b") 77 | # 78 | # 'a b' as oneOrMoreAThenB 79 | # OneOrMoreAThenB(LowerId(1:1:"a"), LowerId(1:3:"b")) 80 | # 81 | # 'a a b' as oneOrMoreAThenB 82 | # OneOrMoreAThenB(LowerId(1:1:"a"), LowerId(1:3:"a"), LowerId(1:5:"b")) 83 | # 84 | # 'a a b' as zeroOrOneAThenB 85 | # ERR: unexpected token LowerId(1:1:"a") 86 | # 87 | # 'a b' as zeroOrOneAThenB 88 | # ZeroOrOneAThenB(LowerId(1:1:"a"), LowerId(1:3:"b")) 89 | # 90 | # 'b' as zeroOrOneAThenB 91 | # ZeroOrOneAThenB(LowerId(1:1:"b")) 92 | # 93 | # 'a b' as ignoreAThenB 94 | # IgnoreAThenB(LowerId(1:3:"b")) 95 | # 96 | # 'a b' as ignoreGroupAThenB 97 | # IgnoreGroupAThenB 98 | # 99 | # 'a a' as nonTerminals 100 | # NonTerminals(TerminalAOrB(LowerId(1:1:"a")), TerminalAOrB(LowerId(1:3:"a"))) 101 | # 102 | # 'a b' as nonTerminals 103 | # NonTerminals(TerminalAOrB(LowerId(1:1:"a")), TerminalAOrB(LowerId(1:3:"b"))) 104 | # 105 | # 'c a' as nonTerminalsBacktrack 106 | # NonTerminalsBacktrack(LowerId(1:1:"c"), LowerId(1:3:"a")) 107 | # 108 | # 'b' as negLookahead 109 | # NegLookahead(TerminalAOrB(LowerId(1:1:"b"))) 110 | # 111 | # 'a' as negLookahead (should fail) 112 | # ERR: unexpected token LowerId(1:1:"a") 113 | 114 | # expected stderr: compiler/Token.fir:138:9: Unexhaustive pattern match 115 | -------------------------------------------------------------------------------- /compiler/TestGrammar.peg: -------------------------------------------------------------------------------- 1 | # PEG parsers in this file are used to test the PEG parser generator. 2 | 3 | import ParseTree 4 | import Token 5 | 6 | type Terminal = Token 7 | type NonTerminal = NonTerminal 8 | 9 | Terminals: 10 | "a" = Token(kind = TokenKind.LowerId, text = "a", ..) 11 | "b" = Token(kind = TokenKind.LowerId, text = "b", ..) 12 | "c" = Token(kind = TokenKind.LowerId, text = "c", ..) 13 | 14 | terminalA: 15 | "a" 16 | 17 | terminalB: 18 | "b" 19 | 20 | terminalAOrB: 21 | "a" 22 | "b" 23 | 24 | terminalAThenB: 25 | "a" "b" 26 | 27 | zeroOrMoreAThenB: 28 | "a"* "b" 29 | 30 | oneOrMoreAThenB: 31 | "a"+ "b" 32 | 33 | zeroOrOneAThenB: 34 | "a"? "b" 35 | 36 | ignoreAThenB: 37 | _"a" "b" 38 | 39 | ignoreAThenIgnoreB: 40 | _"a" _"b" 41 | 42 | ignoreGroupAThenB: 43 | _("a" "b") 44 | 45 | nonTerminals: 46 | terminalAOrB terminalAOrB 47 | 48 | nonTerminalsBacktrack: 49 | terminalAOrB terminalAOrB 50 | "c" "a" 51 | 52 | negLookahead: 53 | -"a" terminalAOrB 54 | -------------------------------------------------------------------------------- /compiler/TypeGrammar.peg: -------------------------------------------------------------------------------- 1 | import ParseTree 2 | import Token 3 | 4 | type Terminal = Token 5 | type NonTerminal = NonTerminal 6 | 7 | Terminals: 8 | "LowerId" = Token(kind = TokenKind.LowerId | TokenKind.Self_, ..) 9 | "UpperId" = Token(kind = TokenKind.UpperId, ..) 10 | "type" = Token(kind = TokenKind.Type, ..) 11 | "prim" = Token(kind = TokenKind.Prim, ..) 12 | "Fn" = Token(kind = TokenKind.UpperFn, ..) 13 | "(" = Token(kind = TokenKind.LParen, ..) 14 | ")" = Token(kind = TokenKind.RParen, ..) 15 | "[" = Token(kind = TokenKind.LBracket, ..) 16 | "]" = Token(kind = TokenKind.RBracket, ..) 17 | ":" = Token(kind = TokenKind.Colon, ..) 18 | "," = Token(kind = TokenKind.Comma, ..) 19 | ".." = Token(kind = TokenKind.DotDot, ..) 20 | "NEWLINE" = Token(kind = TokenKind.Newline, ..) 21 | "INDENT" = Token(kind = TokenKind.Indent, ..) 22 | "DEDENT" = Token(kind = TokenKind.Dedent, ..) 23 | 24 | type_: 25 | namedType 26 | "LowerId" 27 | recordType 28 | variantType 29 | fnType 30 | 31 | recordType: 32 | _"(" _")" 33 | _"(" recordTypeFields _")" 34 | 35 | variantType: 36 | _"[" _"]" 37 | _"[" variantAlt (_"," variantAlt)* _"," _".." "LowerId" _"]" 38 | _"[" variantAlt (_"," variantAlt)* _","? _"]" 39 | 40 | fnType: 41 | _"Fn" _"(" fnArgs? _")" returnType? 42 | 43 | namedType: 44 | "UpperId" _"[" type_ (_"," type_)* _","? _"]" 45 | "UpperId" 46 | 47 | recordTypeFields: 48 | recordTypeField (_"," recordTypeField)* _"," _".." "LowerId" 49 | recordTypeField (_"," recordTypeField)* _","? 50 | 51 | recordTypeField: 52 | "LowerId" _":" type_ 53 | type_ 54 | 55 | variantAlt: 56 | "UpperId" _"(" recordTypeFields _")" 57 | "UpperId" 58 | 59 | fnArgs: 60 | type_ (_"," type_)* ","? 61 | 62 | returnType: 63 | _":" type_ type_? 64 | 65 | # -------------------------------------------------------------------------------------------------- 66 | 67 | typeDecl: 68 | _"type" "UpperId" (_"[" typeParams _"]")? _":" _"NEWLINE" _"INDENT" typeDeclRhs _"DEDENT" 69 | _"type" "UpperId" (_"[" typeParams _"]")? _"NEWLINE" 70 | "prim" _"type" "UpperId" (_"[" typeParams _"]")? _"NEWLINE" 71 | 72 | typeParams: 73 | "LowerId" (_"," "LowerId")* ","? 74 | "LowerId" 75 | 76 | typeDeclRhs: 77 | conDecl+ 78 | namedField+ 79 | 80 | conDecl: 81 | "UpperId" _":" _"NEWLINE" _"INDENT" namedField+ _"DEDENT" 82 | "UpperId" _"(" unnamedFields? _")" _"NEWLINE" 83 | "UpperId" _"NEWLINE" 84 | 85 | namedField: 86 | "LowerId" _":" type_ _"NEWLINE" 87 | 88 | unnamedFields: 89 | type_ (_"," type_)* _","? 90 | -------------------------------------------------------------------------------- /compiler/test_lexer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | cargo build --release 7 | 8 | for file in tests/*.fir; do 9 | [ -e "$file" ] || continue 10 | ./target/release/fir compiler/Main.fir -- "$file" 11 | done 12 | -------------------------------------------------------------------------------- /editor/vim/ftdetect/fir.vim: -------------------------------------------------------------------------------- 1 | autocmd BufNewFile,BufRead *.fir setfiletype fir 2 | -------------------------------------------------------------------------------- /editor/vim/ftplugin/fir.vim: -------------------------------------------------------------------------------- 1 | setlocal comments=:##,:# 2 | setlocal formatoptions+=c 3 | setlocal commentstring=#%s 4 | setlocal foldmethod=indent 5 | setlocal textwidth=100 6 | -------------------------------------------------------------------------------- /editor/vim/indent/fir.vim: -------------------------------------------------------------------------------- 1 | if exists('b:did_indent') 2 | finish 3 | endif 4 | let b:did_indent = 1 5 | 6 | setlocal autoindent 7 | 8 | setlocal indentexpr=FirIndent() 9 | 10 | if exists('*FirIndent') 11 | finish 12 | endif 13 | 14 | function! FirIndent() 15 | " By default use the previous line's indentation. 16 | let indentTo = indent(v:lnum) 17 | 18 | " Indent after ':' and '=' unless in a comment. 19 | let previousNonBlankLine = prevnonblank(v:lnum - 1) 20 | let previousLine = getline(previousNonBlankLine) 21 | 22 | " Get the syntax ID of the first non-blank character of the previous line. 23 | let synID = synID(previousNonBlankLine, col([previousNonBlankLine, '$']) - strlen(previousLine) + 1, 1) 24 | let synName = synIDattr(synID, 'name') 25 | 26 | if synName =~? 'comment' 27 | " If the previous line is a comment, use its indentation level. 28 | let indentTo = indent(previousNonBlankLine) 29 | elseif previousLine =~# '[:=]$' 30 | let indentTo = indent(previousNonBlankLine) + &shiftwidth 31 | endif 32 | 33 | return indentTo 34 | endfunction 35 | -------------------------------------------------------------------------------- /editor/vim/syntax/fir.vim: -------------------------------------------------------------------------------- 1 | syntax case match 2 | 3 | syntax keyword firKeyword 4 | \ as 5 | \ break 6 | \ continue 7 | \ elif 8 | \ else 9 | \ export 10 | \ fn 11 | \ Fn 12 | \ for 13 | \ if 14 | \ impl 15 | \ import 16 | \ in 17 | \ is 18 | \ jump 19 | \ let 20 | \ loop 21 | \ match 22 | \ prim 23 | \ return 24 | \ trait 25 | \ type 26 | \ var 27 | \ while 28 | 29 | syntax region firLineComment start="#" end="$" contains=@Spell 30 | 31 | syntax match firNumber display "\<\(0x\|0X\|0b\|0B\)\?[a-fA-F0-9][a-fA-F0-9_]*\(u8\|u16\|u32\|u64\|i8\|i16\|i32\|i64\)\?\>" 32 | 33 | syntax match firType "\<_\?[A-Z][a-zA-Z0-9_']*\>" 34 | 35 | syntax match firVariable "\<_\?[a-z][a-zA-Z0-9_']*\>" 36 | 37 | syntax cluster firStringContains contains=firInterpolation 38 | syntax region firString matchgroup=firStringDelimiter start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=@Spell,@firStringContains 39 | syntax region firChar start=+'+ skip=+\\\\\|\\'+ end=+'+ 40 | syntax match firInterpolation contained "`\([^`]\+\)`" extend 41 | 42 | syntax match firParenStart "(" 43 | syntax match firParenEnd ")" 44 | syntax match firBracketStart "\[" 45 | syntax match firBracketEnd "\]" 46 | syntax match firBraceStart "{" 47 | syntax match firBraceEnd "}" 48 | 49 | syntax match firComma "," 50 | syntax match firColon ":" 51 | 52 | syn region firBlockComment start="#|" end="|#" 53 | \ contains= 54 | \ firBlockCommentBlockComment, 55 | \ firTodo, 56 | \ @Spell 57 | 58 | syn keyword firTodo TODO FIXME BUG contained 59 | 60 | syntax match firOperator "=\|==\|+=\|-=\|*=\|\^=\|+\|-\|*\|!\|&\|&&\||\|||\|<\|<<\|<=\|>\|>>\|>=\|!=\|/\|%\|\^\|\.\." 61 | 62 | highlight default link firKeyword Keyword 63 | highlight default link firLineComment Comment 64 | highlight default link firNumber Number 65 | highlight default link firStringDelimiter String 66 | highlight default link firString String 67 | highlight default link firChar Character 68 | highlight default link firType Type 69 | highlight default link firVariable Variable 70 | highlight default link firBlockComment Comment 71 | highlight default link firTodo Todo 72 | highlight default link firOperator Operator 73 | highlight default link firComma Delimiter 74 | highlight default link firColon Delimiter 75 | highlight default link firParenStart Delimiter 76 | highlight default link firParenEnd Delimiter 77 | highlight default link firBraceStart Delimiter 78 | highlight default link firBraceEnd Delimiter 79 | highlight default link firBracketStart Delimiter 80 | highlight default link firBracketEnd Delimiter 81 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | export FIR_ROOT := justfile_directory() 2 | 3 | show_recipes: 4 | @just --list 5 | 6 | format: 7 | cargo fmt 8 | 9 | lint: 10 | cargo clippy --all-targets 11 | 12 | check: 13 | cargo check 14 | 15 | watch: 16 | echo src/parser.lalrpop | entr -r lalrpop src/parser.lalrpop & cargo watch 17 | 18 | test: build interpreter_unit_test interpreter_golden_test compiler_unit_test compiler_golden_test 19 | 20 | interpreter_unit_test: 21 | cargo test 22 | 23 | interpreter_golden_test: 24 | goldentests target/debug/fir tests '# ' 25 | 26 | interpreter_update_goldens: build 27 | goldentests target/debug/fir tests '# ' --overwrite 28 | 29 | compiler_unit_test: 30 | cargo run -- compiler/Main.fir 31 | 32 | compiler_golden_test: 33 | goldentests target/debug/fir compiler/PegTests.fir '# ' 34 | goldentests target/debug/fir compiler/TypeGrammarTest.fir '# ' 35 | 36 | compiler_update_goldens: 37 | goldentests target/debug/fir compiler/PegTests.fir '# ' --overwrite 38 | goldentests target/debug/fir compiler/TypeGrammarTest.fir '# ' --overwrite 39 | 40 | build: generate_parser 41 | cargo build 42 | 43 | generate_parser: 44 | lalrpop src/parser.lalrpop 45 | cargo fmt -- src/parser.rs 46 | 47 | update_generated_files: 48 | #!/usr/bin/env bash 49 | lalrpop src/parser.lalrpop 50 | cargo run -- compiler/Peg.fir -- compiler/TestGrammar.peg > compiler/TestGrammar.fir 51 | cargo run -- compiler/Peg.fir -- compiler/TypeGrammar.peg > compiler/TypeGrammar.fir 52 | output=$(cargo run -- compiler/Peg.fir -- compiler/PegGrammar.peg) 53 | if [ $? -eq 0 ]; then 54 | echo "$output" > compiler/PegGrammar.fir 55 | else 56 | echo "cargo run failed" 57 | exit 1 58 | fi 59 | 60 | # build_site tested with: 61 | # 62 | # - wasm-pack 0.12.1 63 | # - wasm-opt version 114 (version_114-3-g5f6594c52) 64 | 65 | build_site: generate_parser 66 | #!/usr/bin/env bash 67 | 68 | OUT_DIR=../fir-lang.github.io 69 | 70 | wasm-pack build \ 71 | --target web \ 72 | --release \ 73 | --out-dir target/wasm-pack \ 74 | --no-pack \ 75 | --no-typescript 76 | 77 | cp target/wasm-pack/fir.js $OUT_DIR/fir.js 78 | cp target/wasm-pack/fir_bg.wasm $OUT_DIR/fir_bg.wasm 79 | 80 | # Copy standard library 81 | rm $OUT_DIR/fir/lib/* 82 | cp lib/* $OUT_DIR/fir/lib/ 83 | 84 | # Copy samples 85 | cp tests/ErrorHandling.fir $OUT_DIR/ErrorHandling.fir 86 | cp tests/ThrowingIter.fir $OUT_DIR/ThrowingIter.fir 87 | cp tests/PPrintExample.fir $OUT_DIR/PPrintExample.fir 88 | -------------------------------------------------------------------------------- /lib/Array.fir: -------------------------------------------------------------------------------- 1 | prim type Array[t] 2 | 3 | prim Array.new(len: U32): Array[t] 4 | prim Array.len(self: Array[t]): U32 5 | prim Array.get(self: Array[t], idx: U32): t 6 | prim Array.set(self: Array[t], idx: U32, elem: t) 7 | 8 | Array.fromIter[Iterator[iter, item, exn]](iter: iter): exn Array[item] 9 | let size = match iter.size[iter, item, exn, exn](): 10 | Option.Some(size): size 11 | Option.None: panic("Array.fromIter iterator size unknown") 12 | 13 | let array = Array.new(size) 14 | 15 | for i: U32 in range(0u32, size): 16 | array.set(i, iter.next().unwrap()) 17 | 18 | array 19 | 20 | impl[ToStr[t]] ToStr[Array[t]]: 21 | toStr(self: Array[t]): Str 22 | let buf = StrBuf.withCapacity(self.len() * 5 + 2) 23 | buf.push('[') 24 | for i: U32 in range(0u32, self.len()): 25 | if i != 0: 26 | buf.push(',') 27 | buf.pushStr(self.get(i).toStr()) 28 | buf.push(']') 29 | buf.toStr() 30 | -------------------------------------------------------------------------------- /lib/Char.fir: -------------------------------------------------------------------------------- 1 | # TODO: We may want to allow unnamed fields: `type Char(U32)`. 2 | type Char: 3 | _codePoint: U32 4 | 5 | impl Eq[Char]: 6 | __eq(self: Char, other: Char): Bool 7 | self._codePoint == other._codePoint 8 | 9 | impl Ord[Char]: 10 | cmp(self: Char, other: Char): Ordering 11 | self._codePoint.cmp(other._codePoint) 12 | 13 | Char.fromU32(codePoint: U32): Option[Char] 14 | if codePoint >= 0x110000 || (codePoint >= 0xD800 && codePoint <= 0xDFFF): 15 | return Option.None 16 | 17 | Option.Some(Char(_codePoint = codePoint)) 18 | 19 | Char.asU32(self): U32 20 | self._codePoint 21 | 22 | Char.lenUtf8(self): U32 23 | if self._codePoint < 0x80: 24 | 1 25 | elif self._codePoint < 0x800: 26 | 2 27 | elif self._codePoint < 0x10000: 28 | 3 29 | else: 30 | 4 31 | 32 | Char.isAsciiWhitespace(self): Bool 33 | self == ' ' || self == '\t' || self == '\n' || self == '\r' 34 | # TODO: 35 | # || self == '\x0C' 36 | 37 | Char.isAsciiUppercase(self): Bool 38 | self._codePoint >= 'A'._codePoint && self._codePoint <= 'Z'._codePoint 39 | 40 | Char.isAsciiLowercase(self): Bool 41 | self._codePoint >= 'a'._codePoint && self._codePoint <= 'z'._codePoint 42 | 43 | Char.isAsciiDigit(self): Bool 44 | self._codePoint >= '0'._codePoint && self._codePoint <= '9'._codePoint 45 | 46 | Char.isAsciiAlphanumeric(self): Bool 47 | self.isAsciiUppercase() || self.isAsciiLowercase() || self.isAsciiDigit() 48 | 49 | Char.isAsciiHexDigit(self): Bool 50 | self.isAsciiDigit() || (self._codePoint >= 'a'._codePoint && self._codePoint <= 'f'._codePoint && self._codePoint >= 'A'._codePoint && self._codePoint <= 'F'._codePoint) 51 | 52 | Char.isAsciiBinDigit(self): Bool 53 | self == '0' || self == '1' 54 | 55 | Char.toAsciiUppercase(self): Char 56 | if self.isAsciiLowercase(): 57 | return Char(_codePoint = self._codePoint - 32) 58 | return self 59 | 60 | Char.toAsciiLowercase(self): Char 61 | if self.isAsciiUppercase(): 62 | return Char(_codePoint = self._codePoint + 32) 63 | return self 64 | 65 | impl ToStr[Char]: 66 | toStr(self: Char): Str 67 | let strBuf = StrBuf.withCapacity(self.lenUtf8() + 2) 68 | strBuf.push('\'') 69 | strBuf.push(self) 70 | strBuf.push('\'') 71 | strBuf.toStr() # TODO: This copies the array, maybe add an internal version that doesn't 72 | -------------------------------------------------------------------------------- /lib/Exn.fir: -------------------------------------------------------------------------------- 1 | prim throw(exn: exn): exn a 2 | 3 | prim try(cb: Fn(): exn a): Result[exn, a] 4 | 5 | untry(res: Result[exn, a]): exn a 6 | match res: 7 | Result.Ok(a): a 8 | Result.Err(err): throw(err) 9 | -------------------------------------------------------------------------------- /lib/HashMap.fir: -------------------------------------------------------------------------------- 1 | type HashMap[k, v]: 2 | _elems: Vec[Option[Elem[k, v]]] 3 | 4 | type Elem[k, v]: 5 | key: k 6 | value: v 7 | next: Option[Elem[k, v]] 8 | 9 | HashMap.fromIter[Iterator[iter, (key: k, value: v), exn], Eq[k], Hash[k]](iter: iter): exn HashMap[k, v] 10 | let map = HashMap.withCapacity(10) 11 | for item: (key: k, value: v) in iter: 12 | map.insert(item.key, item.value) 13 | map 14 | 15 | HashMap.withCapacity(cap: U32): HashMap[k, v] 16 | let elems: Vec[Option[Elem[k, v]]] = Vec.withCapacity(cap) 17 | for i: U32 in range(0u32, cap): 18 | elems.push(Option.None) 19 | HashMap(_elems = elems) 20 | 21 | # Insert new value, return old value. 22 | HashMap.insert[Hash[k], Eq[k]](self: HashMap[k, v], key: k, value: v): Option[v] 23 | let hash = key.hash() 24 | let index = hash.mod(self._elems.len()) 25 | match self._elems.get(index): 26 | Option.None: 27 | self._elems.set(index, Option.Some(Elem(key = key, value = value, next = Option.None))) 28 | return Option.None 29 | Option.Some(elem): 30 | loop: 31 | if elem.key == key: 32 | let old = elem.value 33 | elem.value = value 34 | return Option.Some(old) 35 | else: 36 | match elem.next: 37 | Option.Some(next): 38 | elem = next 39 | Option.None: 40 | elem.next = Option.Some(Elem(key = key, value = value, next = Option.None)) 41 | return Option.None 42 | 43 | panic("unreachable") 44 | 45 | HashMap.get[Hash[k], Eq[k]](self: HashMap[k, v], key: k): Option[v] 46 | let hash = key.hash() 47 | let index = hash.mod(self._elems.len()) 48 | let elem = self._elems.get(index) 49 | loop: 50 | match elem: 51 | Option.None: return Option.None 52 | Option.Some(elem_): 53 | if elem_.key == key: 54 | return Option.Some(elem_.value) 55 | else: 56 | elem = elem_.next 57 | 58 | panic("unreachable") 59 | 60 | impl[ToStr[k], ToStr[v]] ToStr[HashMap[k, v]]: 61 | toStr(self: HashMap[k, v]): Str 62 | let buf = StrBuf.withCapacity(100) 63 | buf.push('{') 64 | let printed = 0 65 | for i: U32 in range(0u32, self._elems.len()): 66 | let elem = self._elems.get(i) 67 | while elem is Option.Some(elem_): 68 | if printed != 0: 69 | buf.pushStr(", ") 70 | buf.pushStr(elem_.key.toStr()) 71 | buf.pushStr(": ") 72 | buf.pushStr(elem_.value.toStr()) 73 | printed += 1 74 | elem = elem_.next 75 | buf.push('}') 76 | buf.toStr() 77 | -------------------------------------------------------------------------------- /lib/Num.fir: -------------------------------------------------------------------------------- 1 | prim type I64 2 | 3 | prim type U64 4 | 5 | prim type I32 6 | 7 | impl Add[I32]: 8 | prim __add(self: I32, other: I32): I32 9 | 10 | impl Sub[I32]: 11 | prim __sub(self: I32, other: I32): I32 12 | 13 | impl Mul[I32]: 14 | prim __mul(self: I32, other: I32): I32 15 | 16 | impl Div[I32]: 17 | prim __div(self: I32, other: I32): I32 18 | 19 | impl Eq[I32]: 20 | prim __eq(self: I32, other: I32): Bool 21 | 22 | impl Ord[I32]: 23 | prim cmp(self: I32, other: I32): Ordering 24 | 25 | impl BitAnd[I32]: 26 | prim __bitand(self: I32, other: I32): I32 27 | 28 | impl BitOr[I32]: 29 | prim __bitor(self: I32, other: I32): I32 30 | 31 | impl Shl[I32]: 32 | prim __shl(self: I32, shift: I32): I32 33 | 34 | impl Shr[I32]: 35 | prim __shr(self: I32, shift: I32): I32 36 | 37 | impl Neg[I32]: 38 | prim __neg(self: I32): I32 39 | 40 | impl ToStr[I32]: 41 | prim toStr(self: I32): Str 42 | 43 | impl Step[I32]: 44 | step(self: I32): I32 = self + 1 45 | 46 | prim type U32 47 | 48 | U32.max(): U32 = 4294967295 49 | 50 | impl Add[U32]: 51 | prim __add(self: U32, other: U32): U32 52 | 53 | impl Sub[U32]: 54 | prim __sub(self: U32, other: U32): U32 55 | 56 | impl Mul[U32]: 57 | prim __mul(self: U32, other: U32): U32 58 | 59 | impl Div[U32]: 60 | prim __div(self: U32, other: U32): U32 61 | 62 | impl Eq[U32]: 63 | prim __eq(self: U32, other: U32): Bool 64 | 65 | impl Ord[U32]: 66 | prim cmp(self: U32, other: U32): Ordering 67 | 68 | impl BitAnd[U32]: 69 | prim __bitand(self: U32, other: U32): U32 70 | 71 | impl BitOr[U32]: 72 | prim __bitor(self: U32, other: U32): U32 73 | 74 | impl BitXor[U32]: 75 | prim __bitxor(self: U32, other: U32): U32 76 | 77 | impl Shl[U32]: 78 | prim __shl(self: U32, shift: U32): U32 79 | 80 | impl Shr[U32]: 81 | prim __shr(self: U32, shift: U32): U32 82 | 83 | impl ToStr[U32]: 84 | prim toStr(self: U32): Str 85 | 86 | impl Step[U32]: 87 | step(self: U32): U32 = self + 1 88 | 89 | prim U32.mod(self: U32, rhs: U32): U32 90 | 91 | prim type I8 92 | 93 | impl Add[I8]: 94 | prim __add(self: I8, other: I8): I8 95 | 96 | impl Sub[I8]: 97 | prim __sub(self: I8, other: I8): I8 98 | 99 | impl Mul[I8]: 100 | prim __mul(self: I8, other: I8): I8 101 | 102 | impl Div[I8]: 103 | prim __div(self: I8, other: I8): I8 104 | 105 | impl Eq[I8]: 106 | prim __eq(self: I8, other: I8): Bool 107 | 108 | impl Ord[I8]: 109 | prim cmp(self: I8, other: I8): Ordering 110 | 111 | impl BitAnd[I8]: 112 | prim __bitand(self: I8, other: I8): I8 113 | 114 | impl BitOr[I8]: 115 | prim __bitor(self: I8, other: I8): I8 116 | 117 | impl Shl[I8]: 118 | prim __shl(self: I8, shift: I8): I8 119 | 120 | impl Shr[I8]: 121 | prim __shr(self: I8, shift: I8): I8 122 | 123 | impl Neg[I8]: 124 | prim __neg(self: I8): I8 125 | 126 | impl ToStr[I8]: 127 | prim toStr(self: I8): Str 128 | 129 | impl Step[I8]: 130 | step(self: I8): I8 = self + 1i8 131 | 132 | prim type U8 133 | 134 | impl Add[U8]: 135 | prim __add(self: U8, other: U8): U8 136 | 137 | impl Sub[U8]: 138 | prim __sub(self: U8, other: U8): U8 139 | 140 | impl Mul[U8]: 141 | prim __mul(self: U8, other: U8): U8 142 | 143 | impl Div[U8]: 144 | prim __div(self: U8, other: U8): U8 145 | 146 | impl Eq[U8]: 147 | prim __eq(self: U8, other: U8): Bool 148 | 149 | impl Ord[U8]: 150 | prim cmp(self: U8, other: U8): Ordering 151 | 152 | impl BitAnd[U8]: 153 | prim __bitand(self: U8, other: U8): U8 154 | 155 | impl BitOr[U8]: 156 | prim __bitor(self: U8, other: U8): U8 157 | 158 | impl Shl[U8]: 159 | prim __shl(self: U8, shift: U8): U8 160 | 161 | impl Shr[U8]: 162 | prim __shr(self: U8, shift: U8): U8 163 | 164 | impl ToStr[U8]: 165 | prim toStr(self: U8): Str 166 | 167 | impl Step[U8]: 168 | step(self: U8): U8 = self + 1u8 169 | 170 | ################################################################################ 171 | # 172 | # Conversions 173 | # 174 | ################################################################################ 175 | 176 | prim U8.asI8(self: U8): I8 177 | prim U8.asI32(self: U8): I32 178 | prim U8.asU32(self: U8): U32 179 | 180 | prim I8.asU8(self: I8): U8 181 | prim I8.asI32(self: I8): I32 182 | prim I8.asU32(self: I8): U32 183 | prim I8.abs(self: I8): I8 184 | 185 | prim U32.asI8(self: U32): I8 186 | prim U32.asU8(self: U32): U8 187 | prim U32.asI32(self: U32): I32 188 | 189 | prim I32.asI8(self: I32): I8 190 | prim I32.asU8(self: I32): U8 191 | prim I32.asU32(self: I32): U32 192 | prim I32.abs(self: I32): I32 193 | -------------------------------------------------------------------------------- /lib/Option.fir: -------------------------------------------------------------------------------- 1 | type Option[t]: 2 | None 3 | Some(t) 4 | 5 | impl[ToStr[t]] ToStr[Option[t]]: 6 | toStr(self: Option[t]): Str 7 | match self: 8 | Option.None: "Option.None" 9 | Option.Some(t): "Option.Some(`t`)" 10 | 11 | impl[Eq[t]] Eq[Option[t]]: 12 | __eq(self: Option[t], other: Option[t]): Bool 13 | match (left = self, right = other): 14 | (left = Option.None, right = Option.None): Bool.True 15 | (left = Option.Some(left), right = Option.Some(right)): left == right 16 | _: Bool.False 17 | 18 | Option.map(self: Option[t1], f: Fn(t1): exn t2): exn Option[t2] 19 | match self: 20 | Option.None: Option.None 21 | Option.Some(val): Option.Some(f(val)) 22 | 23 | Option.unwrap(self: Option[t]): t 24 | match self: 25 | Option.None: panic("Unwrapping Option.None") 26 | Option.Some(val): val 27 | 28 | Option.unwrapOr(self: Option[t], default: t): t 29 | match self: 30 | Option.None: default 31 | Option.Some(val): val 32 | 33 | Option.unwrapOrElse(self: Option[t], f: Fn(): exn t): exn t 34 | match self: 35 | Option.None: f() 36 | Option.Some(val): val 37 | 38 | Option.guard(self: Option[t], guard: Fn(t): exn Bool): exn Option[t] 39 | match self: 40 | Option.None: Option.None 41 | Option.Some(val): 42 | if guard(val): 43 | Option.Some(val) 44 | else: 45 | Option.None 46 | 47 | Option.isSome(self: Option[t]): Bool 48 | match self: 49 | Option.Some(_): Bool.True 50 | Option.None: Bool.False 51 | 52 | Option.isNone(self: Option[t]): Bool 53 | match self: 54 | Option.Some(_): Bool.False 55 | Option.None: Bool.True 56 | -------------------------------------------------------------------------------- /lib/PPrint.fir: -------------------------------------------------------------------------------- 1 | ## Fir port of OCaml's PPrint[1]. 2 | ## 3 | ## [1]: https://github.com/fpottier/pprint 4 | 5 | type Doc: 6 | _Empty 7 | 8 | _Str: 9 | str: Str 10 | lenChars: U32 11 | 12 | _Blank: 13 | spaces: U32 14 | 15 | _IfFlat: 16 | flat: Doc 17 | notFlat: Doc 18 | 19 | _HardLine 20 | 21 | _Cat: 22 | req: _SpaceReq 23 | doc1: Doc 24 | doc2: Doc 25 | 26 | _Nest: 27 | req: _SpaceReq 28 | indent: U32 29 | doc: Doc 30 | 31 | _Group: 32 | req: _SpaceReq 33 | doc: Doc 34 | 35 | type _State: 36 | width: U32 37 | column: U32 38 | output: StrBuf 39 | 40 | Doc.render(self, width: U32): Str 41 | # TODO: For now copying the initial buffer size from the original PPrint, but we should be able 42 | # to estimate better numbers using the `Doc` type. 43 | let output = StrBuf.withCapacity(512) 44 | let state = _State(width, column = 0, output) 45 | self._render(state, 0, Bool.False) 46 | output.toStr() 47 | 48 | Doc._render(self, state: _State, indent: U32, flatten: Bool) 49 | match self: 50 | Doc._Empty: () 51 | 52 | Doc._Str(str, lenChars): 53 | state.output.pushStr(str) 54 | state.column += lenChars 55 | 56 | Doc._Blank(spaces): 57 | for _: U32 in range(0u32, spaces): 58 | state.output.push(' ') 59 | state.column += spaces 60 | 61 | Doc._HardLine: 62 | if flatten: 63 | panic("") 64 | state.output.push('\n') 65 | for _: U32 in range(0u32, indent): 66 | state.output.push(' ') 67 | state.column = indent 68 | 69 | Doc._IfFlat(flat, notFlat): 70 | if flatten: 71 | flat._render(state, indent, flatten) 72 | else: 73 | notFlat._render(state, indent, flatten) 74 | 75 | Doc._Cat(req, doc1, doc2): 76 | doc1._render(state, indent, flatten) 77 | doc2._render(state, indent, flatten) 78 | 79 | Doc._Nest(req, indent = extraIndent, doc): 80 | doc._render(state, indent + extraIndent, flatten) 81 | 82 | Doc._Group(req, doc): 83 | if !flatten: 84 | flatten = match req: 85 | _SpaceReq.Infinite: Bool.True 86 | _SpaceReq.Exact(req): state.column + req <= state.width 87 | doc._render(state, indent, flatten) 88 | 89 | # Space requirement. 90 | type _SpaceReq: 91 | Exact(U32) 92 | Infinite 93 | 94 | impl Add[_SpaceReq]: 95 | __add(self: _SpaceReq, other: _SpaceReq): _SpaceReq 96 | if self is _SpaceReq.Exact(req1) && other is _SpaceReq.Exact(req2): 97 | return _SpaceReq.Exact(req1 + req2) 98 | _SpaceReq.Infinite 99 | 100 | Doc._req(self): _SpaceReq 101 | match self: 102 | Doc._Empty: _SpaceReq.Exact(0) 103 | Doc._Str(lenChars, ..): _SpaceReq.Exact(lenChars) 104 | Doc._Blank(spaces): _SpaceReq.Exact(spaces) 105 | Doc._IfFlat(flat, ..): flat._req() 106 | Doc._HardLine: _SpaceReq.Infinite 107 | Doc._Cat(req, ..) | Doc._Nest(req, ..) | Doc._Group(req, ..): req 108 | 109 | _countChars(s: Str): U32 110 | let i = 0u32 111 | for x: Char in s.chars(): 112 | i += 1 113 | i 114 | 115 | Doc.empty(): Doc = Doc._Empty 116 | 117 | Doc.str(str: Str): Doc = Doc._Str(str, lenChars = _countChars(str)) 118 | 119 | Doc.char(c: Char): Doc 120 | # TODO: Implement a shorthand char to string 121 | let buf = StrBuf.withCapacity(1) 122 | buf.push(c) 123 | Doc.str(buf.toStr()) 124 | 125 | Doc.space(): Doc = Doc._Str(str = " ", lenChars = 1) 126 | 127 | Doc.hardLine(): Doc = Doc._HardLine 128 | 129 | Doc.blank(spaces: U32): Doc = Doc._Blank(spaces) 130 | 131 | Doc.ifFlat(doc1: Doc, doc2: Doc): Doc 132 | match doc1: 133 | Doc._IfFlat(flat = doc1, ..) | doc1: 134 | Doc._IfFlat(flat = doc1, notFlat = doc2) 135 | 136 | Doc.whenFlat(doc: Doc): Doc 137 | Doc.ifFlat(doc, Doc.empty()) 138 | 139 | Doc.whenNotFlat(doc: Doc): Doc 140 | Doc.ifFlat(Doc.empty(), doc) 141 | 142 | Doc.break_(i: U32): Doc 143 | Doc.ifFlat(Doc.blank(i), Doc.hardLine()) 144 | 145 | Doc.nest(self, indent: U32): Doc 146 | Doc._Nest(req = self._req(), indent, doc = self) 147 | 148 | Doc.nested(indent: U32, doc: Doc): Doc 149 | doc.nest(indent) 150 | 151 | Doc.group(self): Doc 152 | let req = self._req() 153 | if req is _SpaceReq.Infinite: 154 | self 155 | else: 156 | Doc._Group(req, doc = self) 157 | 158 | Doc.cat(self, doc2: Doc): Doc 159 | if self is Doc._Empty: 160 | return doc2 161 | 162 | if doc2 is Doc._Empty: 163 | return self 164 | 165 | Doc._Cat(req = self._req() + doc2._req(), doc1 = self, doc2) 166 | 167 | impl Add[Doc]: 168 | __add(self: Doc, other: Doc): Doc = self.cat(other) 169 | -------------------------------------------------------------------------------- /lib/Prelude.fir: -------------------------------------------------------------------------------- 1 | import Array 2 | import Char 3 | import Exn 4 | import HashMap 5 | import Iter 6 | import Num 7 | import Option 8 | import PPrint 9 | import Result 10 | import Str 11 | import StrBuf 12 | import Vec 13 | 14 | # A marker trait used to mark a type parameter as a record row. 15 | # 16 | # This is temporary until we implement kind inference. (#46) 17 | trait RecRow[t] 18 | 19 | # A marker trait used to mark a type parameter as a variant row. 20 | # 21 | # This is temporary until we implement kind inference. (#46) 22 | trait VarRow[t] 23 | 24 | # The `+` operator. 25 | trait Add[t]: 26 | __add(self: t, other: t): t 27 | 28 | # The `-` operator. 29 | trait Sub[t]: 30 | __sub(self: t, other: t): t 31 | 32 | # The unary `-` operator. 33 | trait Neg[t]: 34 | __neg(self: t): t 35 | 36 | # The `*` operator. 37 | trait Mul[t]: 38 | __mul(self: t, other: t): t 39 | 40 | # The `/` operator. 41 | trait Div[t]: 42 | __div(self: t, other: t): t 43 | 44 | # The `==` operator. 45 | trait Eq[t]: 46 | __eq(self: t, other: t): Bool 47 | 48 | __neq(self: t, other: t): Bool 49 | !self.__eq(other) 50 | 51 | # The `|` operator. 52 | trait BitOr[t]: 53 | __bitor(self: t, other: t): t 54 | 55 | # The `^` operator. 56 | trait BitXor[t]: 57 | __bitxor(self: t, other: t): t 58 | 59 | # The `&` operator. 60 | trait BitAnd[t]: 61 | __bitand(self: t, other: t): t 62 | 63 | # The `<<` operator. 64 | trait Shl[t]: 65 | __shl(self: t, other: t): t 66 | 67 | # The `>>` operator. 68 | trait Shr[t]: 69 | __shr(self: t, other: t): t 70 | 71 | trait Ord[t]: 72 | cmp(self: t, other: t): Ordering 73 | 74 | __lt(self: t, other: t): Bool 75 | match self.cmp(other): 76 | Ordering.Less: Bool.True 77 | _: Bool.False 78 | 79 | __le(self: t, other: t): Bool 80 | match self.cmp(other): 81 | Ordering.Less | Ordering.Equal: Bool.True 82 | _: Bool.False 83 | 84 | __gt(self: t, other: t): Bool 85 | match self.cmp(other): 86 | Ordering.Greater: Bool.True 87 | _: Bool.False 88 | 89 | __ge(self: t, other: t): Bool 90 | match self.cmp(other): 91 | Ordering.Greater | Ordering.Equal: Bool.True 92 | _: Bool.False 93 | 94 | trait ToStr[t]: 95 | toStr(self: t): Str 96 | 97 | impl ToStr[()]: 98 | toStr(self: ()): Str = "()" 99 | 100 | # The bottom type, e.g. return type of a function that doesn't return. 101 | # We may want to rename this later. 102 | type Void 103 | 104 | 105 | prim printStrNoNl(s: Str) 106 | 107 | printStr(s: Str) 108 | printStrNoNl(s) 109 | printStrNoNl("\n") 110 | 111 | print[ToStr[a]](a: a) = printStr(a.toStr()) 112 | 113 | printNoNl[ToStr[a]](a: a) = printStrNoNl(a.toStr()) 114 | 115 | prim panic(msg: Str): Void 116 | 117 | type Ordering: 118 | Less 119 | Equal 120 | Greater 121 | 122 | impl ToStr[Ordering]: 123 | toStr(self: Ordering): Str 124 | match self: 125 | Ordering.Less: "Ordering.Less" 126 | Ordering.Equal: "Ordering.Equal" 127 | Ordering.Greater: "Ordering.Greater" 128 | 129 | type Bool: 130 | False 131 | True 132 | 133 | impl ToStr[Bool]: 134 | toStr(self: Bool): Str 135 | match self: 136 | Bool.True: "Bool.True" 137 | Bool.False: "Bool.False" 138 | 139 | prim readFileUtf8(path: Str): Str 140 | 141 | trait Hash[t]: 142 | hash(self: t): U32 143 | -------------------------------------------------------------------------------- /lib/Result.fir: -------------------------------------------------------------------------------- 1 | type Result[e, t]: 2 | Err(e) 3 | Ok(t) 4 | 5 | impl[ToStr[e], ToStr[t]] ToStr[Result[e, t]]: 6 | toStr(self: Result[e, t]): Str 7 | match self: 8 | Result.Err(e): "Result.Err(`e`)" 9 | Result.Ok(t): "Result.Ok(`t`)" 10 | 11 | Result.map(self: Result[e, t], f: Fn(t): exn t2): exn Result[e, t2] 12 | match self: 13 | Result.Ok(val): Result.Ok(f(val)) 14 | Result.Err(err): Result.Err(err) 15 | 16 | Result.mapErr(self: Result[e, t], f: Fn(e): exn e2): exn Result[e2, t] 17 | match self: 18 | Result.Ok(val): Result.Ok(val) 19 | Result.Err(err): Result.Err(f(err)) 20 | 21 | Result.unwrap[ToStr[e]](self: Result[e, t]): t 22 | match self: 23 | Result.Ok(val): val 24 | Result.Err(err): panic(err.toStr()) 25 | 26 | Result.unwrapOr(self: Result[e, t], default: t): t 27 | match self: 28 | Result.Ok(val): val 29 | Result.Err(_): default 30 | 31 | Result.unwrapOrElse(self: Result[e, t], f: Fn(e): exn t): exn t 32 | match self: 33 | Result.Ok(val): val 34 | Result.Err(err): f(err) 35 | 36 | Result.isOk(self: Result[e, t]): Bool 37 | match self: 38 | Result.Ok(_): Bool.True 39 | Result.Err(_): Bool.False 40 | 41 | Result.isErr(self: Result[e, t]): Bool 42 | match self: 43 | Result.Ok(_): Bool.False 44 | Result.Err(_): Bool.True 45 | -------------------------------------------------------------------------------- /lib/StrBuf.fir: -------------------------------------------------------------------------------- 1 | type StrBuf: 2 | # UTF-8 encoding of the string. 3 | _bytes: Vec[U8] 4 | 5 | StrBuf.withCapacity(cap: U32): StrBuf 6 | StrBuf(_bytes = Vec.withCapacity(cap)) 7 | 8 | StrBuf.len(self): U32 9 | self._bytes.len() 10 | 11 | StrBuf.clear(self) 12 | self._bytes.clear() 13 | 14 | StrBuf.push(self, char: Char) 15 | let code = char._codePoint 16 | let len = char.lenUtf8() 17 | if len == 1: 18 | self._bytes.push(code.asU8()) 19 | elif len == 2: 20 | let b0 = ((code >> 6) & 0x1F).asU8() | 0b1100_0000 21 | let b1 = (code & 0x3F).asU8() | 0b1000_0000 22 | self._bytes.push(b0) 23 | self._bytes.push(b1) 24 | elif len == 3: 25 | let b0 = ((code >> 12) & 0x0F).asU8() | 0b1110_0000 26 | let b1 = ((code >> 6) & 0x3F).asU8() | 0b1000_0000 27 | let b2 = (code & 0x3F).asU8() | 0b1000_0000 28 | self._bytes.push(b0) 29 | self._bytes.push(b1) 30 | self._bytes.push(b2) 31 | else: 32 | let b0 = ((code >> 18) & 0x07).asU8() | 0b1111_0000 33 | let b1 = ((code >> 12) & 0x3F).asU8() | 0b1000_0000 34 | let b2 = ((code >> 6) & 0x3F).asU8() | 0b1000_0000 35 | let b3 = (code & 0x3F).asU8() | 0b1000_0000 36 | self._bytes.push(b0) 37 | self._bytes.push(b1) 38 | self._bytes.push(b2) 39 | self._bytes.push(b3) 40 | 41 | StrBuf.pushStr(self, str: Str) 42 | # TODO: Maybe consider memcpy-ing the bytes. 43 | self._bytes.reserve(str.len()) 44 | for char: Char in str.chars(): 45 | self.push(char) 46 | 47 | impl ToStr[StrBuf]: 48 | toStr(self: StrBuf): Str 49 | Str.fromUtf8Vec(self._bytes) 50 | -------------------------------------------------------------------------------- /lib/Vec.fir: -------------------------------------------------------------------------------- 1 | type Vec[t]: 2 | _data: Array[t] 3 | _len: U32 4 | 5 | Vec.withCapacity(cap: U32): Vec[t] 6 | Vec(_data = Array.new(cap), _len = 0) 7 | 8 | Vec.fromIter[Iterator[iter, item, exn]](iter: iter): exn Vec[item] 9 | let size = iter.size[iter, item, exn, exn]().unwrapOr(10) 10 | let vec = Vec.withCapacity(size) 11 | for item: item in iter: 12 | vec.push(item) 13 | vec 14 | 15 | Vec.reserve(self: Vec[t], cap: U32) 16 | let len = self._len 17 | let newCap = self._data.len() 18 | while newCap - len < cap: 19 | newCap *= 2 20 | 21 | if newCap != self._data.len(): 22 | let newData = Array.new(newCap) 23 | for i: U32 in range(0u32, self._data.len()): 24 | newData.set(i, self._data.get(i)) 25 | self._data = newData 26 | 27 | Vec.spareCapacity(self: Vec[t]): U32 28 | self._data.len() - self._len 29 | 30 | Vec.len(self: Vec[t]): U32 31 | self._len 32 | 33 | Vec.clear(self: Vec[t]) 34 | # In the compiled version we should actually clear the elements to allow garbage collection. 35 | self._len = 0 36 | 37 | Vec.truncate(self: Vec[t], len: U32) 38 | if len < self._len: 39 | self._len = len 40 | 41 | Vec.push(self: Vec[t], elem: t) 42 | let cap = self._data.len() 43 | 44 | if self._len == cap: 45 | let newData = Array.new((cap * 2) | 3) 46 | for i: U32 in range(0u32, cap): 47 | newData.set(i, self._data.get(i)) 48 | self._data = newData 49 | 50 | self._data.set(self._len, elem) 51 | self._len += 1 52 | 53 | Vec.pop(self: Vec[t]): Option[t] 54 | if self._len == 0: 55 | return Option.None 56 | 57 | self._len -= 1 58 | Option.Some(self._data.get(self._len)) 59 | 60 | Vec.set(self: Vec[t], idx: U32, elem: t) 61 | if idx >= self._len: 62 | panic("Vec.set OOB (len=`self._len`, idx=`idx`)") 63 | 64 | self._data.set(idx, elem) 65 | 66 | Vec.get(self: Vec[t], idx: U32): t 67 | if idx >= self._len: 68 | panic("Vec.get OOB (len=`self._len`, idx=`idx`)") 69 | 70 | self._data.get(idx) 71 | 72 | Vec.swap(self: Vec[t], i: U32, j: U32) 73 | let tmp = self.get(i) 74 | self.set(i, self.get(j)) 75 | self.set(j, tmp) 76 | 77 | Vec.iter(self: Vec[t]): VecIter[t] 78 | VecIter( 79 | _vec = self, 80 | _idx = 0, 81 | ) 82 | 83 | Vec.first(self: Vec[t]): Option[t] 84 | if self.len() == 0: 85 | return Option.None 86 | Option.Some(self._data.get(0)) 87 | 88 | Vec.last(self: Vec[t]): Option[t] 89 | let len = self.len() 90 | if len == 0: 91 | return Option.None 92 | Option.Some(self._data.get(len - 1)) 93 | 94 | Vec.sort[Ord[t]](self: Vec[t]) 95 | if self.len() != 0: 96 | _quicksort(self, 0, self.len() - 1) 97 | 98 | _quicksort[Ord[t]](vec: Vec[t], low: U32, high: U32) 99 | if low >= high: 100 | return 101 | 102 | let p = _partition(vec, low, high) 103 | 104 | if p != low: 105 | _quicksort(vec, low, p - 1) 106 | 107 | _quicksort(vec, p + 1, high) 108 | 109 | _partition[Ord[t]](vec: Vec[t], low: U32, high: U32): U32 110 | let pivot = vec.get(high) 111 | let i = low 112 | 113 | for j: U32 in irange(low, high - 1): 114 | if vec.get(j) <= pivot: 115 | vec.swap(i, j) 116 | i += 1 117 | 118 | vec.swap(i, high) 119 | i 120 | 121 | impl[ToStr[t]] ToStr[Vec[t]]: 122 | toStr(self: Vec[t]): Str 123 | let buf = StrBuf.withCapacity(self.len() * 5 + 2) 124 | buf.push('[') 125 | for i: U32 in range(0u32, self.len()): 126 | if i != 0: 127 | buf.push(',') 128 | buf.pushStr(self.get(i).toStr()) 129 | buf.push(']') 130 | buf.toStr() 131 | 132 | type VecIter[t]: 133 | _vec: Vec[t] 134 | _idx: U32 135 | 136 | impl Iterator[VecIter[t], t, exn]: 137 | next(self: VecIter[t]): exn Option[t] 138 | if self._idx >= self._vec.len(): 139 | return Option.None 140 | 141 | let val = self._vec.get(self._idx) 142 | self._idx += 1 143 | Option.Some(val) 144 | -------------------------------------------------------------------------------- /src/collections.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | pub use std::collections::hash_map::Entry; 4 | 5 | pub use crate::scope_map::{ScopeMap, ScopeSet}; 6 | 7 | pub use ordermap::{OrderMap, OrderSet}; 8 | 9 | pub type Map = fnv::FnvHashMap; 10 | 11 | pub type Set = fnv::FnvHashSet; 12 | 13 | pub type TreeMap = std::collections::BTreeMap; 14 | 15 | pub type TreeSet = std::collections::BTreeSet; 16 | -------------------------------------------------------------------------------- /src/import_resolver.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{self, Id}; 2 | use crate::collections::{Map, Set}; 3 | 4 | use std::path::PathBuf; 5 | 6 | use smol_str::SmolStr; 7 | 8 | /// Replaces import declarations with the parsed ASTs of the imported modules. 9 | /// 10 | /// A simple import resolver that will do until we implement a proper module system. 11 | /// 12 | /// Handles modules imported multiple times transitively. 13 | /// 14 | /// The returned module won't have any import declarations. 15 | /// 16 | /// Imports `Fir.Prelude` always. Any other import path needs to have one component and will be 17 | /// resolved to a file at the root. 18 | pub fn resolve_imports( 19 | import_paths: &Map, 20 | module_root: &str, 21 | module: ast::Module, 22 | import_prelude: bool, 23 | print_tokens: bool, 24 | ) -> ast::Module { 25 | let mut new_module: Vec> = vec![]; 26 | let mut imported_modules: Set> = Default::default(); 27 | 28 | let fir_root = import_paths.get("Fir").unwrap(); 29 | 30 | resolve_imports_( 31 | import_paths, 32 | module_root, 33 | module, 34 | &mut new_module, 35 | &mut imported_modules, 36 | print_tokens, 37 | ); 38 | 39 | // Import Prelude if it's not already imported. 40 | let prelude_path = vec![FIR.clone(), PRELUDE.clone()]; 41 | if import_prelude && !imported_modules.contains(&prelude_path) { 42 | let prelude_path_buf = module_path(fir_root, &PRELUDE); 43 | let prelude_module = crate::parse_file(&prelude_path_buf, &PRELUDE, print_tokens); 44 | imported_modules.insert(prelude_path); 45 | resolve_imports_( 46 | import_paths, 47 | fir_root, 48 | prelude_module, 49 | &mut new_module, 50 | &mut imported_modules, 51 | print_tokens, 52 | ); 53 | } 54 | 55 | new_module 56 | } 57 | 58 | static FIR: Id = SmolStr::new_static("Fir"); 59 | static PRELUDE: Id = SmolStr::new_static("Prelude"); 60 | 61 | fn resolve_imports_( 62 | import_paths: &Map, 63 | module_root: &str, 64 | module: ast::Module, 65 | new_module: &mut ast::Module, 66 | imported_modules: &mut Set>, 67 | print_tokens: bool, 68 | ) { 69 | for decl in module { 70 | match &decl.node { 71 | ast::TopDecl::Type(_) 72 | | ast::TopDecl::Fun(_) 73 | | ast::TopDecl::Trait(_) 74 | | ast::TopDecl::Impl(_) => new_module.push(decl), 75 | ast::TopDecl::Import(import) => { 76 | let path = &import.node.path; 77 | 78 | if imported_modules.contains(path) { 79 | continue; 80 | } 81 | 82 | // NB. We don't support directories in import paths currently. 83 | assert!( 84 | path.len() <= 2, 85 | "We don't allow directories in import paths currently. Invalid path: {}", 86 | path.join(".") 87 | ); 88 | 89 | let root = if path.len() == 2 { 90 | match import_paths.get(path[0].as_str()) { 91 | Some(root) => root, 92 | None => panic!("Path {} is not in import paths", path[0]), 93 | } 94 | } else { 95 | module_root 96 | }; 97 | 98 | let imported_module = path.last().unwrap(); 99 | imported_modules.insert(path.clone()); 100 | 101 | let imported_module_path = module_path(root, imported_module); 102 | let imported_module = 103 | crate::parse_file(&imported_module_path, imported_module, print_tokens); 104 | resolve_imports_( 105 | import_paths, 106 | module_root, 107 | imported_module, 108 | new_module, 109 | imported_modules, 110 | print_tokens, 111 | ); 112 | } 113 | } 114 | } 115 | } 116 | 117 | fn module_path(root: &str, module: &Id) -> PathBuf { 118 | let mut path = PathBuf::new(); 119 | path.push(root); 120 | path.push(module.as_str()); 121 | path.set_extension("fir"); 122 | path 123 | } 124 | -------------------------------------------------------------------------------- /src/interpolation.rs: -------------------------------------------------------------------------------- 1 | use crate::ast; 2 | use crate::lexer::lex; 3 | use crate::parser::LExprParser; 4 | use crate::token::Token; 5 | 6 | use std::rc::Rc; 7 | 8 | use lexgen_util::Loc; 9 | use smol_str::SmolStr; 10 | 11 | #[derive(Debug, Clone)] 12 | pub enum StringPart { 13 | Str(String), 14 | Expr(ast::L), 15 | } 16 | 17 | // Lexer ensures any interpolation (the part between `$(` and `)`) have balanced parens. In 18 | // addition, we don't handle string literals inside interpolations, so interpolations can't be 19 | // nested. 20 | pub fn parse_string_parts(module: &Rc, s: &str, mut loc: Loc) -> Vec { 21 | let mut parts: Vec = vec![]; 22 | 23 | let mut escape = false; 24 | let mut chars = s.char_indices().peekable(); 25 | let mut str_part_start: usize = 0; 26 | 27 | 'outer: while let Some((byte_idx, char)) = chars.next() { 28 | if char == '\n' { 29 | loc.line += 1; 30 | loc.col = 0; 31 | } else { 32 | loc.col += 1; 33 | } 34 | 35 | loc.byte_idx += char.len_utf8(); 36 | 37 | if escape { 38 | escape = false; 39 | continue; 40 | } 41 | 42 | if char == '\\' { 43 | if let Some((_, '\n')) = chars.peek() { 44 | while let Some((_, ' ' | '\t' | '\n' | '\r')) = chars.peek() { 45 | chars.next(); 46 | } 47 | escape = false; 48 | continue; 49 | } 50 | escape = true; 51 | continue; 52 | } 53 | 54 | if char == '`' { 55 | let start_byte = byte_idx; 56 | let start_loc = Loc { 57 | line: loc.line, 58 | col: loc.col, 59 | byte_idx: loc.byte_idx, 60 | }; 61 | 62 | parts.push(StringPart::Str(copy_update_escapes( 63 | &s[str_part_start..byte_idx], 64 | ))); 65 | 66 | for (byte_idx, char) in chars.by_ref() { 67 | if char == '\n' { 68 | loc.line += 1; 69 | loc.col = 0; 70 | } else { 71 | loc.col += 1; 72 | } 73 | 74 | loc.byte_idx += char.len_utf8(); 75 | 76 | if escape { 77 | escape = false; 78 | continue; 79 | } 80 | 81 | if char == '\\' { 82 | escape = true; 83 | continue; 84 | } 85 | 86 | if char == '`' { 87 | // Lex and parse interpolation. 88 | let interpolation = &s[start_byte + 1..byte_idx]; 89 | let mut tokens: Vec<(Loc, Token, Loc)> = lex(interpolation, module); 90 | for (ref mut l, _, ref mut r) in &mut tokens { 91 | *l = update_loc(l, &start_loc); 92 | *r = update_loc(r, &start_loc); 93 | } 94 | let parser = LExprParser::new(); 95 | let expr = match parser.parse(module, tokens) { 96 | Ok(expr) => expr, 97 | Err(err) => crate::report_parse_error(&SmolStr::new(module), err), 98 | }; 99 | parts.push(StringPart::Expr(expr)); 100 | str_part_start = byte_idx + 1; 101 | continue 'outer; 102 | } 103 | } 104 | } 105 | } 106 | 107 | if str_part_start != s.len() { 108 | parts.push(StringPart::Str(copy_update_escapes( 109 | &s[str_part_start..s.len()], 110 | ))); 111 | } 112 | 113 | parts 114 | } 115 | 116 | fn update_loc(err_loc: &Loc, start_loc: &Loc) -> Loc { 117 | let byte_idx = err_loc.byte_idx + start_loc.byte_idx; 118 | if err_loc.line == 0 { 119 | Loc { 120 | line: start_loc.line, 121 | col: start_loc.col + err_loc.col, 122 | byte_idx, 123 | } 124 | } else { 125 | Loc { 126 | line: start_loc.line + err_loc.line, 127 | col: err_loc.col, 128 | byte_idx, 129 | } 130 | } 131 | } 132 | 133 | /// Copy the tokenized string, converting escape sequences to characters. 134 | pub(crate) fn copy_update_escapes(s: &str) -> String { 135 | let mut ret = String::with_capacity(s.len()); 136 | let mut chars = s.chars().peekable(); 137 | 138 | while let Some(char) = chars.next() { 139 | if char == '\\' { 140 | // Escape sequences are recognized by the lexer, so we know '\\' is followed by one of 141 | // the characters below. 142 | match chars.next().unwrap() { 143 | '\\' => ret.push('\\'), 144 | 'n' => ret.push('\n'), 145 | 't' => ret.push('\t'), 146 | 'r' => ret.push('\r'), 147 | '"' => ret.push('"'), 148 | '`' => ret.push('`'), 149 | '\n' => { 150 | while let Some(next) = chars.peek().copied() { 151 | match next { 152 | ' ' | '\t' | '\n' | '\r' => { 153 | chars.next(); 154 | } 155 | _ => break, 156 | } 157 | } 158 | } 159 | other => panic!("Weird escape character: {:?}", other), 160 | } 161 | } else { 162 | ret.push(char); 163 | } 164 | } 165 | 166 | ret 167 | } 168 | 169 | #[cfg(test)] 170 | impl StringPart { 171 | fn as_str(&self) -> &str { 172 | match self { 173 | StringPart::Str(str) => str, 174 | StringPart::Expr(_) => panic!(), 175 | } 176 | } 177 | 178 | fn as_expr(&self) -> &ast::L { 179 | match self { 180 | StringPart::Str(_) => panic!(), 181 | StringPart::Expr(expr) => expr, 182 | } 183 | } 184 | } 185 | 186 | #[test] 187 | fn interpolation_parsing_1() { 188 | let s = r#"abc"#; 189 | let parts = parse_string_parts(&"test".into(), s, Loc::default()); 190 | assert_eq!(parts.len(), 1); 191 | assert_eq!(parts[0].as_str(), "abc"); 192 | } 193 | 194 | #[test] 195 | fn interpolation_parsing_2() { 196 | let s = r#"abc `a`"#; 197 | let parts = parse_string_parts(&"test".into(), s, Loc::default()); 198 | assert_eq!(parts.len(), 2); 199 | assert_eq!(parts[0].as_str(), "abc "); 200 | parts[1].as_expr(); 201 | } 202 | -------------------------------------------------------------------------------- /src/interpreter/heap.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreter::*; 2 | use crate::lowering::{CONSTR_CON_IDX, FUN_CON_IDX, METHOD_CON_IDX}; 3 | 4 | use bytemuck::cast_slice; 5 | 6 | /// Heap is just a growable array of words. A word is 8 bytes, to easily allow references larger 7 | /// than 4 bytes, which we may need when interpreting the bootstrapping compiler without a GC. 8 | #[derive(Debug)] 9 | pub struct Heap { 10 | pub values: Box<[u64]>, 11 | hp: usize, 12 | } 13 | 14 | impl std::ops::Index for Heap { 15 | type Output = u64; 16 | 17 | fn index(&self, index: u64) -> &Self::Output { 18 | debug_assert!( 19 | (index as usize) < self.hp, 20 | "index={}, hp={}", 21 | index, 22 | self.hp 23 | ); 24 | &self.values[index as usize] 25 | } 26 | } 27 | 28 | impl std::ops::IndexMut for Heap { 29 | fn index_mut(&mut self, index: u64) -> &mut Self::Output { 30 | debug_assert!((index as usize) < self.hp); 31 | &mut self.values[index as usize] 32 | } 33 | } 34 | 35 | impl Heap { 36 | pub fn new() -> Self { 37 | // Heap pointer starts from 1. Address 0 is used as "null" or "uninitialized" marker in 38 | // arrays. 39 | Heap { 40 | values: vec![0; INITIAL_HEAP_SIZE_WORDS].into_boxed_slice(), 41 | hp: 1, 42 | } 43 | } 44 | 45 | pub fn allocate(&mut self, size: usize) -> u64 { 46 | if self.hp + size > self.values.len() { 47 | let mut new_values: Box<[u64]> = vec![0; self.values.len() * 2].into_boxed_slice(); 48 | new_values[0..self.hp].copy_from_slice(&self.values[0..self.hp]); 49 | self.values = new_values; 50 | } 51 | 52 | let hp = self.hp; 53 | self.hp += size; 54 | hp as u64 55 | } 56 | 57 | // TODO: These should be allocated once and reused. 58 | pub fn allocate_tag(&mut self, tag: u64) -> u64 { 59 | let alloc = self.allocate(1); 60 | self[alloc] = tag; 61 | alloc 62 | } 63 | 64 | pub fn allocate_str(&mut self, str_tag: u64, array_tag: u64, string: &[u8]) -> u64 { 65 | let size_words = string.len().div_ceil(8); 66 | let array = self.allocate(size_words + 2); 67 | self[array] = array_tag; 68 | self[array + 1] = string.len() as u64; 69 | let bytes: &mut [u8] = 70 | &mut cast_slice_mut::(&mut self.values[array as usize + 2..])[..string.len()]; 71 | bytes.copy_from_slice(string); 72 | 73 | let alloc = self.allocate(4); 74 | self[alloc] = str_tag; 75 | self[alloc + 1] = array; 76 | self[alloc + 2] = 0; 77 | self[alloc + 3] = string.len() as u64; 78 | alloc 79 | } 80 | 81 | pub fn allocate_str_view( 82 | &mut self, 83 | ty_tag: u64, 84 | str: u64, 85 | byte_start: u32, 86 | byte_len: u32, 87 | ) -> u64 { 88 | let array = self[str + 1]; 89 | let start = val_as_u32(self[str + 2]); 90 | 91 | let new_str = self.allocate(4); 92 | self[new_str] = ty_tag; 93 | self[new_str + 1] = array; 94 | self[new_str + 2] = u32_as_val(start + byte_start); 95 | self[new_str + 3] = u32_as_val(byte_len); 96 | 97 | new_str 98 | } 99 | 100 | pub fn str_bytes(&self, str_addr: u64) -> &[u8] { 101 | let str_byte_array = self[str_addr + 1]; 102 | let str_start = val_as_u32(self[str_addr + 2]); 103 | let str_len = val_as_u32(self[str_addr + 3]); 104 | &cast_slice::(&self.values[(str_byte_array + 2) as usize..]) 105 | [str_start as usize..(str_start + str_len) as usize] 106 | } 107 | 108 | pub fn allocate_constr(&mut self, type_tag: u64) -> u64 { 109 | let alloc = self.allocate(2); 110 | self[alloc] = CONSTR_CON_IDX.as_u64(); 111 | self[alloc + 1] = type_tag; 112 | alloc 113 | } 114 | 115 | pub fn allocate_fun(&mut self, fun_idx: u64) -> u64 { 116 | let alloc = self.allocate(2); 117 | self[alloc] = FUN_CON_IDX.as_u64(); 118 | self[alloc + 1] = fun_idx; 119 | alloc 120 | } 121 | 122 | pub fn allocate_method(&mut self, receiver: u64, fun_idx: u64) -> u64 { 123 | let alloc = self.allocate(3); 124 | self[alloc] = METHOD_CON_IDX.as_u64(); 125 | self[alloc + 1] = receiver; 126 | self[alloc + 2] = fun_idx; 127 | alloc 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/parser_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{self, Id, L}; 2 | 3 | use smol_str::SmolStr; 4 | 5 | pub(crate) fn parse_char_lit(text: &str) -> char { 6 | let mut chars = text.chars(); 7 | 8 | let quote = chars.next(); // skip ' 9 | debug_assert_eq!(quote, Some('\'')); 10 | 11 | let char = chars.next().unwrap(); 12 | if char == '\\' { 13 | match chars.next().unwrap() { 14 | '\'' => '\'', 15 | 'n' => '\n', 16 | 't' => '\t', 17 | 'r' => '\r', 18 | '\\' => '\\', 19 | other => panic!("Unknown escaped character: '\\{}'", other), 20 | } 21 | } else { 22 | char 23 | } 24 | } 25 | 26 | pub(crate) fn process_param_list( 27 | params: Vec<(Id, Option>)>, 28 | module: &std::rc::Rc, 29 | loc: &lexgen_util::Loc, 30 | ) -> (ast::SelfParam, Vec<(Id, Option>)>) { 31 | let mut self_param = ast::SelfParam::No; 32 | let mut typed_params: Vec<(Id, Option>)> = Vec::with_capacity(params.len()); 33 | 34 | for (param_id, param_ty) in params { 35 | if param_id == "self" { 36 | self_param = match param_ty { 37 | Some(self_ty) => ast::SelfParam::Explicit(self_ty), 38 | None => ast::SelfParam::Implicit, 39 | }; 40 | } else { 41 | match param_ty { 42 | Some(ty) => typed_params.push((param_id, Some(ty))), 43 | None => panic!( 44 | "{}:{}:{}: Parameter {} without type", 45 | module, 46 | loc.line + 1, 47 | loc.col + 1, 48 | param_id 49 | ), 50 | } 51 | } 52 | } 53 | 54 | (self_param, typed_params) 55 | } 56 | 57 | pub(crate) fn path_parts(path: &SmolStr) -> Vec { 58 | let parts: Vec = path.split('.').map(SmolStr::new).collect(); 59 | assert_eq!(parts.len(), 2); 60 | parts 61 | } 62 | -------------------------------------------------------------------------------- /src/scope_map.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use crate::collections::Map; 4 | 5 | use std::borrow::Borrow; 6 | use std::hash::Hash; 7 | 8 | #[derive(Debug)] 9 | pub struct ScopeMap(Vec>); 10 | 11 | impl Default for ScopeMap { 12 | fn default() -> Self { 13 | Self::new() 14 | } 15 | } 16 | 17 | impl ScopeMap { 18 | pub fn new() -> Self { 19 | Self::from_map(Default::default()) 20 | } 21 | 22 | pub fn from_map(map: Map) -> Self { 23 | ScopeMap(vec![map]) 24 | } 25 | 26 | pub fn len_scopes(&self) -> usize { 27 | self.0.len() 28 | } 29 | 30 | /// Exit the current scope. Panics if we're not in a scope. 31 | pub fn exit(&mut self) -> Map { 32 | self.0.pop().unwrap() 33 | } 34 | 35 | /// Enter a new scope. 36 | pub fn enter(&mut self) { 37 | self.0.push(Default::default()); 38 | } 39 | } 40 | 41 | impl ScopeMap { 42 | /// Bind at the current scope. If the mapped thing is already mapped in the *current scope* 43 | /// (not in a parent scope!), returns the old value for the thing. The return value can be used 44 | /// to check duplicate definitions. 45 | pub fn insert(&mut self, k: K, v: V) -> Option { 46 | self.0.last_mut().unwrap().insert(k, v) 47 | } 48 | 49 | /// Get the value of the key, from the outer-most scope that has the key. 50 | pub fn get(&self, k: &Q) -> Option<&V> 51 | where 52 | K: Borrow, 53 | Q: ?Sized + Hash + Eq, 54 | { 55 | for map in self.0.iter().rev() { 56 | if let Some(val) = map.get(k) { 57 | return Some(val); 58 | } 59 | } 60 | None 61 | } 62 | 63 | pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> 64 | where 65 | K: Borrow, 66 | Q: ?Sized + Hash + Eq, 67 | { 68 | for map in self.0.iter_mut().rev() { 69 | if let Some(val) = map.get_mut(k) { 70 | return Some(val); 71 | } 72 | } 73 | None 74 | } 75 | 76 | /// Get the value of the key in the outer-most scope. Unlike `get`, this does not look at 77 | /// parent scopes when the key is not in the outer-most scope. 78 | pub fn get_current_scope(&self, k: &Q) -> Option<&V> 79 | where 80 | K: Borrow, 81 | Q: ?Sized + Hash + Eq, 82 | { 83 | self.0.last().unwrap().get(k) 84 | } 85 | 86 | pub fn contains_key(&self, k: &Q) -> bool 87 | where 88 | K: Borrow, 89 | Q: ?Sized + Hash + Eq, 90 | { 91 | self.get(k).is_some() 92 | } 93 | } 94 | 95 | #[derive(Debug)] 96 | pub struct ScopeSet(ScopeMap); 97 | 98 | impl Default for ScopeSet { 99 | fn default() -> Self { 100 | Self::new() 101 | } 102 | } 103 | 104 | impl ScopeSet { 105 | pub fn new() -> Self { 106 | ScopeSet(Default::default()) 107 | } 108 | 109 | pub fn len_scopes(&self) -> usize { 110 | self.0 .0.len() 111 | } 112 | 113 | /// Exit the current scope. Panics if we're not in a scope. 114 | pub fn exit(&mut self) { 115 | self.0 .0.pop().unwrap(); 116 | } 117 | 118 | /// Enter a new scope. 119 | pub fn enter(&mut self) { 120 | self.0 .0.push(Default::default()); 121 | } 122 | } 123 | 124 | impl ScopeSet { 125 | /// Bind at the current scope. 126 | pub fn insert(&mut self, k: K) { 127 | self.0 .0.last_mut().unwrap().insert(k, ()); 128 | } 129 | 130 | /// Get the value of the key, from the outer-most scope that has the key. 131 | pub fn is_bound(&self, k: &Q) -> bool 132 | where 133 | K: Borrow, 134 | Q: ?Sized + Hash + Eq, 135 | { 136 | for map in self.0 .0.iter().rev() { 137 | if map.contains_key(k) { 138 | return true; 139 | } 140 | } 141 | false 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use smol_str::SmolStr; 2 | 3 | #[derive(Debug, Clone, PartialEq, Eq)] 4 | pub struct Token { 5 | pub kind: TokenKind, 6 | pub text: SmolStr, 7 | } 8 | 9 | impl Token { 10 | pub fn smol_str(&self) -> SmolStr { 11 | self.text.clone() 12 | } 13 | } 14 | 15 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 16 | pub enum TokenKind { 17 | /// An identifier starting with an uppercase letter. 18 | UpperId, 19 | 20 | /// `UpperId '.' UpperId`, without spaces in between. 21 | UpperIdPath, 22 | 23 | /// `UpperId '.' UpperId`, follwed by a '['. 24 | UpperIdPathLBracket, 25 | 26 | /// An identifier starting with an uppercase letter, followed by a '['. 27 | UpperIdLBracket, 28 | 29 | /// An identifier starting with a '~' followed by uppercase letter. 30 | TildeUpperId, 31 | 32 | /// `'~' UpperId '.' UpperId`, without spaces in between. 33 | TildeUpperIdPath, 34 | 35 | /// An identifier starting with a lowercase letter. 36 | LowerId, 37 | 38 | /// A label is a lowercase id that starts with a single quote. 39 | Label, 40 | 41 | // Keywords 42 | As, 43 | Break, 44 | Continue, 45 | Elif, 46 | Else, 47 | Export, 48 | Fn, 49 | For, 50 | If, 51 | Impl, 52 | Import, 53 | In, 54 | Is, 55 | Jump, 56 | Let, 57 | Loop, 58 | Match, 59 | Prim, 60 | Return, 61 | Trait, 62 | Type, 63 | UpperFn, 64 | Var, 65 | While, 66 | 67 | // Delimiters 68 | LParen, 69 | LParenRow, 70 | RParen, 71 | LBracket, 72 | LBracketRow, 73 | RBracket, 74 | LBrace, 75 | RBrace, 76 | SingleQuote, 77 | 78 | // Punctuation 79 | Colon, 80 | Semicolon, 81 | Comma, 82 | Dot, 83 | Backslash, 84 | Underscore, 85 | 86 | // Operators 87 | Amp, 88 | AmpAmp, 89 | Caret, 90 | CaretEq, 91 | DotDot, 92 | Eq, 93 | EqEq, 94 | Exclamation, 95 | ExclamationEq, 96 | LAngle, 97 | DoubleLAngle, 98 | LAngleEq, 99 | Minus, 100 | MinusEq, 101 | Percent, 102 | Pipe, 103 | PipePipe, 104 | Plus, 105 | PlusEq, 106 | RAngle, 107 | DoubleRAngle, 108 | RAngleEq, 109 | Slash, 110 | Star, 111 | StarEq, 112 | Tilde, 113 | 114 | // Literals 115 | String, 116 | Int(Option), 117 | HexInt(Option), 118 | BinInt(Option), 119 | Char, 120 | 121 | // Virtual tokens, used to handle layout. Generatd by the scanner. 122 | Indent, 123 | Dedent, 124 | Newline, 125 | } 126 | 127 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 128 | pub enum IntKind { 129 | I8, 130 | U8, 131 | I32, 132 | U32, 133 | } 134 | -------------------------------------------------------------------------------- /src/type_checker/apply.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{self, Id}; 2 | use crate::type_checker::unification::unify; 3 | use crate::type_checker::*; 4 | use crate::utils::loc_display; 5 | 6 | pub(crate) fn apply_con_ty( 7 | con_ty: &Ty, 8 | args: &Vec>, 9 | cons: &ScopeMap, 10 | var_gen: &mut TyVarGen, 11 | level: u32, 12 | loc: &ast::Loc, 13 | ignore_extra: bool, 14 | ) -> Ty { 15 | match con_ty { 16 | Ty::Fun { 17 | args: con_ty_args, 18 | ret: con_ty_ret, 19 | exceptions: con_ty_exceptions, 20 | } => { 21 | // This function is only called on constructors, which don't have exception types. 22 | assert!(con_ty_exceptions.is_none()); 23 | 24 | match con_ty_args { 25 | FunArgs::Positional(con_ty_args) => { 26 | if (!ignore_extra && con_ty_args.len() != args.len()) 27 | || args.len() > con_ty_args.len() 28 | { 29 | panic!( 30 | "{}: Constructor takes {} positional arguments, but applied {}", 31 | loc_display(loc), 32 | con_ty_args.len(), 33 | args.len() 34 | ); 35 | } 36 | for (ty1, ty2) in con_ty_args.iter().zip(args.iter()) { 37 | if let Some(name) = &ty2.name { 38 | panic!( 39 | "{}: Constructor takes positional arguments, but applied named argument '{}'", 40 | loc_display(loc), name 41 | ); 42 | } 43 | unify(ty1, &ty2.node, cons, var_gen, level, loc); 44 | } 45 | } 46 | 47 | FunArgs::Named(con_ty_args) => { 48 | let mut arg_names: Set<&Id> = Default::default(); 49 | 50 | for arg in args { 51 | let name = match arg.name.as_ref() { 52 | Some(name) => name, 53 | None => { 54 | panic!( 55 | "{}: Constructor takes named arguments, but applied positional argument", 56 | loc_display(loc) 57 | ); 58 | } 59 | }; 60 | if con_ty_args.get(name).is_none() { 61 | panic!( 62 | "{}: Constructor doesn't take named argument '{}'", 63 | loc_display(loc), 64 | name, 65 | ); 66 | } 67 | if !arg_names.insert(name) { 68 | panic!( 69 | "{}: Named argument '{}' applied multiple times", 70 | loc_display(loc), 71 | name, 72 | ); 73 | } 74 | } 75 | 76 | let con_ty_arg_names: Set<&Id> = con_ty_args.keys().collect(); 77 | if !ignore_extra && con_ty_arg_names != arg_names { 78 | let con_args_str = con_ty_arg_names 79 | .iter() 80 | .map(ToString::to_string) 81 | .collect::>() 82 | .join(", "); 83 | let applied_args_str = arg_names 84 | .iter() 85 | .map(ToString::to_string) 86 | .collect::>() 87 | .join(", "); 88 | panic!( 89 | "{}: Constructor takes named arguments {{{}}}, but applied {{{}}}", 90 | loc_display(loc), 91 | con_args_str, 92 | applied_args_str 93 | ); 94 | } 95 | 96 | for ast::Named { name, node } in args { 97 | let name = match name.as_ref() { 98 | Some(name) => name, 99 | None => { 100 | panic!( 101 | "{}: Constructor takes named arguments, but applied positional argument", 102 | loc_display(loc) 103 | ); 104 | } 105 | }; 106 | let ty1 = con_ty_args.get(name).unwrap(); 107 | unify(ty1, node, cons, var_gen, level, loc); 108 | } 109 | } 110 | } 111 | 112 | (**con_ty_ret).clone() 113 | } 114 | 115 | Ty::Con(_, _) | Ty::Var(_) | Ty::App(_, _, _) | Ty::Anonymous { .. } | Ty::QVar(_, _) => { 116 | if args.is_empty() { 117 | return con_ty.clone(); 118 | } 119 | panic!( 120 | "{}: BUG: ty = {:?}, args = {:?}", 121 | loc_display(loc), 122 | con_ty, 123 | args 124 | ) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/type_checker/row_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::Id; 2 | use crate::collections::*; 3 | use crate::type_checker::ty::*; 4 | 5 | pub(crate) fn collect_rows( 6 | cons: &ScopeMap, 7 | ty: &Ty, // record or variant, used in errors 8 | ty_kind: RecordOrVariant, 9 | labels: &TreeMap, 10 | mut extension: Option>, 11 | ) -> (TreeMap, Option) { 12 | let mut all_labels: TreeMap = labels 13 | .iter() 14 | .map(|(id, ty)| (id.clone(), ty.deep_normalize(cons))) 15 | .collect(); 16 | 17 | while let Some(ext) = extension { 18 | match *ext { 19 | Ty::Anonymous { 20 | labels, 21 | extension: next_ext, 22 | kind, 23 | is_row, 24 | } => { 25 | assert_eq!(kind, ty_kind); 26 | assert!(is_row); 27 | for (label_id, label_ty) in labels { 28 | if all_labels.insert(label_id, label_ty).is_some() { 29 | panic!("BUG: Duplicate label in anonymous type {}", ty); 30 | } 31 | } 32 | extension = next_ext; 33 | } 34 | 35 | Ty::Var(var) => { 36 | assert!( 37 | matches!(var.kind(), Kind::Row(_)), 38 | "{:?} : {:?}", 39 | var, 40 | var.kind() 41 | ); 42 | match var.normalize(cons) { 43 | Ty::Anonymous { 44 | labels, 45 | extension: next_ext, 46 | kind, 47 | is_row, 48 | } => { 49 | assert!( 50 | is_row, 51 | "Extension variable in anonymous type is not a row: {:#?}", 52 | ty 53 | ); 54 | assert_eq!(kind, ty_kind); 55 | for (label_id, label_ty) in labels { 56 | if all_labels.insert(label_id, label_ty).is_some() { 57 | panic!("BUG: Duplicate field in anonymous type {}", ty); 58 | } 59 | } 60 | extension = next_ext; 61 | } 62 | 63 | other => return (all_labels, Some(other)), 64 | } 65 | } 66 | 67 | other => return (all_labels, Some(other)), 68 | } 69 | } 70 | 71 | (all_labels, None) 72 | } 73 | -------------------------------------------------------------------------------- /src/type_checker/traits.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Trait resolving: 3 | 4 | Note: "matching" below means one-way unification. 5 | 6 | 1. Try to find a trait that matches the goal in the current asumptions. 7 | 2. Check trait impls in the trait env: 8 | - For each impl of the goal pred's trait, match arguments of the pred with the trait's arguments. 9 | - Add `preds` of the impl to the goals to solve. 10 | 11 | Example: 12 | 13 | let vec: Vec[U32] = Vec.withCapacity(10) 14 | let vecIter: VecIter[U32] = vec.iter() 15 | let map: Map[VecIter[U32], U32, Str] = vecIter.map(fn(x) { x.toStr() }) 16 | let x: Str = map.next() 17 | 18 | When type checking the `next` call, we find the `Iterator.next`: 19 | 20 | [iter: *, item: *, Iterator[iter, item]] Fn(self: iter): Option[item] 21 | 22 | - Unify `iter ~ Map[VecIter[U32], U32, Str]`, which generates the predicate: 23 | 24 | Iterator[Map[VecIter[U32], U32, Str]], item] 25 | 26 | - Unify return value with the expected type which is `Str`. Predicate becomes: 27 | 28 | Iterator[Map[VecIter[U32], U32, Str]], Str] 29 | 30 | Note: Predicates are resolved after checking the whole function. 31 | 32 | - Solving the predicate: 33 | 34 | - Find matching impl, which is 35 | 36 | [Iterator[iter, a]] Iterator[Map[iter, a, b], b] 37 | 38 | - Substituting the variables with types in the predicate: 39 | 40 | iter => VecIter[U32] 41 | a => U32 42 | b => Str 43 | 44 | The `impl` predicate becomes: `Iterator[VecIter[U32], U32]` which we add to the goals. 45 | 46 | - Solving the new goal works the same way: 47 | 48 | - Find matching impl, which is 49 | 50 | Iterator[VecIter[t], t] 51 | 52 | - Substitute variables with the types: 53 | 54 | t => U32 55 | 56 | - The impl doesn't have predicates, so we're done. 57 | */ 58 | 59 | use crate::ast::{self, Id}; 60 | use crate::collections::*; 61 | use crate::type_checker::convert::*; 62 | use crate::type_checker::ty::*; 63 | use crate::type_checker::ty_map::TyMap; 64 | use crate::type_checker::unification::try_unify_one_way; 65 | use crate::utils::loc_display; 66 | 67 | /// Maps trait ids to implementations. 68 | pub type TraitEnv = Map>; 69 | 70 | #[derive(Debug)] 71 | pub struct TraitImpl { 72 | // Example: `impl[Iterator[iter, a]] Iterator[Map[iter, a, b], b]: ...` 73 | 74 | // Free variables in the `impl`. 75 | // 76 | // In the example: `iter`, `a`, `b`. 77 | pub qvars: Vec<(Id, Kind)>, 78 | 79 | // Arguments of the trait. 80 | // 81 | // In the example: `[Map[iter, a, b], b]`, where `iter`, `a` and `b` are `QVar`s in `qvars`. 82 | pub trait_args: Vec, 83 | 84 | // Predicates of the implementation. 85 | // 86 | // In the example: `[(Iterator, [iter, a])]`, where `iter` and `a` are `QVar`s in `qvars`. 87 | // 88 | // Note: these types should be instantiated together with `trait_args` so that the same `QVar` 89 | // in arguments and preds will be the same instantiated type variable, and as we match args 90 | // the preds will be updated. 91 | pub preds: Vec<(Id, Vec)>, 92 | } 93 | 94 | pub fn collect_trait_env(pgm: &ast::Module, tys: &mut TyMap) -> TraitEnv { 95 | let mut env: TraitEnv = Default::default(); 96 | 97 | for item in pgm { 98 | let impl_ = match &item.node { 99 | ast::TopDecl::Impl(impl_) => impl_, 100 | _ => continue, 101 | }; 102 | 103 | let trait_id = impl_.node.trait_.node.clone(); 104 | 105 | /* 106 | let ty_con = tys 107 | .get_con(&trait_id) 108 | .unwrap_or_else(|| panic!("{}: Undefined trait {}", loc_display(&impl_.loc), trait_id)); 109 | 110 | let trait_details = ty_con.trait_details().unwrap_or_else(|| { 111 | panic!( 112 | "{}: Type {} is not a trait", 113 | loc_display(&impl_.loc), 114 | trait_id 115 | ) 116 | }); 117 | */ 118 | 119 | tys.enter_scope(); 120 | 121 | let preds: Set = convert_and_bind_context( 122 | tys, 123 | &impl_.node.context, 124 | TyVarConversion::ToQVar, 125 | &impl_.loc, 126 | ); 127 | 128 | let trait_impl = TraitImpl { 129 | qvars: impl_.node.context.type_params.clone(), 130 | trait_args: impl_ 131 | .node 132 | .tys 133 | .iter() 134 | .map(|ty| convert_ast_ty(tys, &ty.node, &ty.loc)) 135 | .collect(), 136 | preds: preds 137 | .into_iter() 138 | .map( 139 | |Pred { 140 | trait_, 141 | params, 142 | loc: _, 143 | }| (trait_, params), 144 | ) 145 | .collect(), 146 | }; 147 | 148 | tys.exit_scope(); 149 | 150 | env.entry(trait_id.clone()).or_default().push(trait_impl); 151 | } 152 | 153 | env 154 | } 155 | 156 | impl TraitImpl { 157 | /// Try to match the trait arguments. If successful, return new goals. 158 | pub fn try_match( 159 | &self, 160 | args: &[Ty], 161 | var_gen: &mut TyVarGen, 162 | tys: &TyMap, 163 | loc: &ast::Loc, 164 | ) -> Option)>> { 165 | if args.len() != self.trait_args.len() { 166 | panic!( 167 | "{}: BUG: Number of arguments applied to the trait don't match the arity", 168 | loc_display(loc) 169 | ); 170 | } 171 | 172 | // Maps `QVar`s to instantiations. 173 | let var_map: Map = self 174 | .qvars 175 | .iter() 176 | .map(|(qvar, kind)| { 177 | let instantiated_var = var_gen.new_var(0, kind.clone(), loc.clone()); 178 | (qvar.clone(), Ty::Var(instantiated_var.clone())) 179 | }) 180 | .collect(); 181 | 182 | for (impl_arg, ty_arg) in self.trait_args.iter().zip(args.iter()) { 183 | let instantiated_impl_arg = impl_arg.subst_qvars(&var_map); 184 | if !try_unify_one_way(&instantiated_impl_arg, ty_arg, tys.cons(), var_gen, 0, loc) { 185 | return None; 186 | } 187 | } 188 | 189 | Some( 190 | self.preds 191 | .iter() 192 | .map(|(trait_, args)| { 193 | ( 194 | trait_.clone(), 195 | args.iter().map(|arg| arg.subst_qvars(&var_map)).collect(), 196 | ) 197 | }) 198 | .collect(), 199 | ) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/type_checker/ty_map.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::Id; 2 | use crate::collections::ScopeMap; 3 | use crate::type_checker::{Ty, TyCon}; 4 | 5 | /// A map of type constructors and variables in scope. 6 | #[derive(Debug, Default)] 7 | pub struct TyMap { 8 | cons: ScopeMap, 9 | vars: ScopeMap, 10 | } 11 | 12 | impl TyMap { 13 | pub fn len_scopes(&self) -> usize { 14 | self.cons.len_scopes() 15 | } 16 | 17 | pub fn cons(&self) -> &ScopeMap { 18 | &self.cons 19 | } 20 | 21 | pub fn enter_scope(&mut self) { 22 | self.cons.enter(); 23 | self.vars.enter(); 24 | } 25 | 26 | pub fn exit_scope(&mut self) { 27 | self.cons.exit(); 28 | self.vars.exit(); 29 | } 30 | 31 | pub fn get_con(&self, id: &Id) -> Option<&TyCon> { 32 | self.cons.get(id) 33 | } 34 | 35 | pub fn get_con_mut(&mut self, id: &Id) -> Option<&mut TyCon> { 36 | self.cons.get_mut(id) 37 | } 38 | 39 | pub fn get_var(&self, id: &Id) -> Option<&Ty> { 40 | self.vars.get(id) 41 | } 42 | 43 | pub fn has_con(&self, id: &Id) -> bool { 44 | self.get_con(id).is_some() 45 | } 46 | 47 | #[allow(unused)] 48 | pub fn has_var(&self, id: &Id) -> bool { 49 | self.get_var(id).is_some() 50 | } 51 | 52 | pub fn insert_var(&mut self, id: Id, ty: Ty) { 53 | self.vars.insert(id, ty); 54 | } 55 | 56 | pub fn insert_con(&mut self, id: Id, con: TyCon) { 57 | self.cons.insert(id, con); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::ast; 2 | 3 | pub fn loc_display(loc: &ast::Loc) -> impl std::fmt::Display + '_ { 4 | LocDisplay(loc) 5 | } 6 | 7 | struct LocDisplay<'a>(&'a ast::Loc); 8 | 9 | impl std::fmt::Display for LocDisplay<'_> { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | write!( 12 | f, 13 | "{}:{}:{}", 14 | self.0.module, 15 | self.0.line_start + 1, 16 | self.0.col_start + 1 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/AndOrEvaluation.fir: -------------------------------------------------------------------------------- 1 | main() 2 | if Bool.True || panic("FAIL 1"): 3 | printStr("OK 1") 4 | 5 | if Bool.False && panic("FAIL 2"): 6 | panic("FAIL 3") 7 | 8 | printStr("OK 2") 9 | 10 | # expected stdout: 11 | # OK 1 12 | # OK 2 13 | -------------------------------------------------------------------------------- /tests/ArithmeticExpressions.fir: -------------------------------------------------------------------------------- 1 | # An implementation of the "Untyped Arithmetic Expressions" language described in Types and 2 | # Programming Languages section 3. 3 | 4 | testMain(): [] () 5 | main("0") 6 | main("succ(0)") 7 | main("succ(succ(0))") 8 | main("succ(pred(succ(0)))") 9 | main("pred(succ(pred(succ(0))))") 10 | main("if true then succ(0) else 0") 11 | main("if iszero(0) then succ(0) else 0") 12 | 13 | main(pgm: Str) = print(eval(parse(pgm))) 14 | 15 | type Term: 16 | True 17 | 18 | False 19 | 20 | If: 21 | cond: Term 22 | then: Term 23 | else_: Term 24 | 25 | Zero 26 | 27 | Succ(Term) 28 | 29 | Pred(Term) 30 | 31 | IsZero(Term) 32 | 33 | type Value: 34 | Bool(Bool) 35 | Num(I32) 36 | 37 | eval(term: Term): Value 38 | match term: 39 | Term.True: Value.Bool(Bool.True) 40 | 41 | Term.False: Value.Bool(Bool.False) 42 | 43 | # TODO: Allow simplifying this as `Term.If(cond, then, else_)`. 44 | Term.If(cond = cond, then = then, else_ = else_): 45 | match eval(cond): 46 | Value.Bool(b): 47 | if b: 48 | eval(then) 49 | else: 50 | eval(else_) 51 | Value.Num(i): 52 | panic("If condition evaluated to number `i`") 53 | 54 | Term.Zero: Value.Num(0) 55 | 56 | Term.Succ(term): 57 | match eval(term): 58 | Value.Bool(bool): panic("Succ argument evaluated to bool `bool`") 59 | Value.Num(value): Value.Num(value + 1) 60 | 61 | Term.Pred(term): 62 | match eval(term): 63 | Value.Bool(bool): panic("Pred argument evaluated to bool `bool`") 64 | Value.Num(value): Value.Num(value - 1) 65 | 66 | Term.IsZero(term): 67 | match eval(term): 68 | Value.Bool(bool): panic("IsZero argument evaluated to bool `bool`") 69 | Value.Num(value): Value.Bool(value == 0) 70 | 71 | impl ToStr[Value]: 72 | toStr(self: Value): Str 73 | match self: 74 | Value.Bool(value): value.toStr() 75 | Value.Num(value): value.toStr() 76 | 77 | parse(pgm: Str): Term 78 | let (term = term, rest = rest) = parseWithRest(pgm) 79 | if !rest.isEmpty(): 80 | panic("Leftover code after parsing the program: \"`rest`\"") 81 | term 82 | 83 | skipWhitespace(pgm: Str): Str 84 | while pgm.len() > 0 && pgm.startsWith(" "): 85 | pgm = pgm.substr(1, pgm.len()) 86 | pgm 87 | 88 | ## Parse the program, return the parsed term and the unparsed part of the program. 89 | parseWithRest(pgm: Str): (term: Term, rest: Str) 90 | match skipWhitespace(pgm): 91 | "0" rest: 92 | (term = Term.Zero, rest = rest) 93 | "true" rest: 94 | (term = Term.True, rest = rest) 95 | "false" rest: 96 | (term = Term.False, rest = rest) 97 | "succ" rest: 98 | let (arg = arg, rest = rest) = parseArg(rest) 99 | (term = Term.Succ(arg), rest = rest) 100 | "pred" rest: 101 | let (arg = arg, rest = rest) = parseArg(rest) 102 | (term = Term.Pred(arg), rest = rest) 103 | "iszero" rest: 104 | let (arg = arg, rest = rest) = parseArg(rest) 105 | (term = Term.IsZero(arg), rest = rest) 106 | "if" rest: 107 | let (term = cond, rest = rest) = parseWithRest(rest) 108 | let rest = expectKeyword(rest, "then") 109 | let (term = then, rest = rest) = parseWithRest(rest) 110 | let rest = expectKeyword(rest, "else") 111 | let (term = else_, rest = rest) = parseWithRest(rest) 112 | (term = Term.If(cond = cond, then = then, else_ = else_), rest = rest) 113 | _: 114 | panic("Invalid syntax: \"`pgm`\"") 115 | 116 | parseArg(pgm: Str): (arg: Term, rest: Str) 117 | # Skip "(" 118 | if pgm.isEmpty(): 119 | panic("Unexpected end of program while parsing arguments") 120 | 121 | if !pgm.startsWith("("): 122 | panic("Missing argument list") 123 | 124 | let pgm = pgm.substr(1, pgm.len()) 125 | 126 | # Parse argument 127 | let (term = arg, rest = rest) = parseWithRest(pgm) 128 | 129 | # Skip ")" 130 | if rest.isEmpty(): 131 | panic("Unexpected end of program while parsing arguments") 132 | 133 | if !rest.startsWith(")"): 134 | panic("Missing ')'") 135 | 136 | let rest = rest.substr(1, rest.len()) 137 | 138 | (arg = arg, rest = rest) 139 | 140 | expectKeyword(pgm: Str, kw: Str): Str 141 | let pgm = skipWhitespace(pgm) 142 | if !pgm.startsWith(kw.toStr()): 143 | panic("Expected \"`kw`\", found \"`pgm`\"") 144 | pgm.substr(kw.len(), pgm.len()) 145 | 146 | 147 | # args: --main testMain 148 | # expected stdout: 149 | # 0 150 | # 1 151 | # 2 152 | # 1 153 | # 0 154 | # 1 155 | # 1 156 | -------------------------------------------------------------------------------- /tests/AsExpr.fir: -------------------------------------------------------------------------------- 1 | main() 2 | printStr((0xFFu8.asI8()).toStr()) 3 | printStr((0xFFFFFFFFu32.asI32()).toStr()) 4 | printStr((0xFFFFFFFFu32.asU8()).toStr()) 5 | 6 | # TODO: Add more tests 7 | 8 | # expected stdout: 9 | # -1 10 | # -1 11 | # 255 12 | -------------------------------------------------------------------------------- /tests/AssignValBug.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let a: U32 = 0 3 | let x: Result[Str, ()] = try({ 4 | a += 1 5 | }) 6 | match x: 7 | Result.Err(err): print(err) 8 | Result.Ok(()): print(a) 9 | 10 | # expected stdout: 0 11 | -------------------------------------------------------------------------------- /tests/AssocFnClosures.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | i: I32 3 | 4 | T.print(self) 5 | printStr("T(i = `self.i`)") 6 | 7 | type MyBool: 8 | False 9 | True 10 | 11 | MyBool.print(self) 12 | match self: 13 | MyBool.False: printStr("False") 14 | MyBool.True: printStr("True") 15 | 16 | main() 17 | let a = T(i = 123).print 18 | let b = MyBool.True.print 19 | let c = MyBool.False.print 20 | let d: Fn(U32): [] Vec[U32] = Vec.withCapacity 21 | let e: Fn(Vec[U32]): [] U32 = Vec.len 22 | let f: Fn(Str): [] Option[Str] = Option.Some 23 | a() 24 | b() 25 | c() 26 | 27 | let vec = d(3u32) 28 | printStr(e(vec).toStr()) 29 | 30 | match f("Hi"): 31 | Option.None: panic("") 32 | Option.Some(msg): printStr(msg) 33 | 34 | # expected stdout: 35 | # T(i = 123) 36 | # True 37 | # False 38 | # 0 39 | # Hi 40 | -------------------------------------------------------------------------------- /tests/AssocFnClosuresGeneric.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | i: I32 3 | 4 | T.print1[ToStr[a]](self, other: a) 5 | printStr("T(i = `self.i`, other = `other.toStr()`)") 6 | 7 | T.print2(self) 8 | printStr("T(i = `self.i`)") 9 | 10 | main() 11 | let f1 = T(i = 1).print1 12 | f1("hi") 13 | 14 | let f2 = T(i = 2).print2 15 | f2() 16 | 17 | # expected stdout: 18 | # T(i = 1, other = hi) 19 | # T(i = 2) 20 | -------------------------------------------------------------------------------- /tests/AssocFnMethodNameConflict.fir: -------------------------------------------------------------------------------- 1 | 2 | type T1: 3 | a: U32 4 | 5 | trait Foo[t]: 6 | foo(self: t) 7 | 8 | T1.foo(self) 9 | print("(associated) T1.foo(a = `self.a`)") 10 | 11 | impl Foo[T1]: 12 | foo(self: T1) 13 | print("(trait) T1.foo(a = `self.a`)") 14 | 15 | main() 16 | let t1 = T1(a = 1u32) 17 | t1.foo() 18 | 19 | # Test standard library `Option.map` colliding with `Iterator.map` 20 | print(Option.Some(123u32).map(fn(i: U32): Str { i.toStr() })) 21 | 22 | # expected stdout: 23 | # (associated) T1.foo(a = 1) 24 | # Option.Some(123) 25 | -------------------------------------------------------------------------------- /tests/AssocFnParentNameCheck.fir: -------------------------------------------------------------------------------- 1 | type Foo: 2 | x: U32 3 | 4 | Fo.f(self: Foo) 5 | print("Foo.f") 6 | 7 | main() 8 | let a = Foo(x = 123u32) 9 | a.f() 10 | 11 | # args: --no-backtrace 12 | # expected exit status: 101 13 | # expected stderr: tests/AssocFnParentNameCheck.fir:4:1: Unknown type Fo 14 | -------------------------------------------------------------------------------- /tests/AssocFnSelectFail.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let a = Vec.withCapacity(10u32) 3 | let b = a.withCapacity 4 | 5 | # args: --typecheck --no-backtrace 6 | # expected exit status: 101 7 | # expected stderr: 8 | # tests/AssocFnSelectFail.fir:3:13: Type Vec[_2] does not have field or method withCapacity 9 | -------------------------------------------------------------------------------- /tests/BitwiseOps.fir: -------------------------------------------------------------------------------- 1 | main() 2 | printStr((0b0000_1111u8 | 0b1111_0000u8 == 0b1111_1111u8).toStr()) 3 | printStr((0xFFu32 | 0xFF00u32 == 0xFFFFu32).toStr()) 4 | 5 | # TODO: Add more tests 6 | 7 | # expected stdout: 8 | # Bool.True 9 | # Bool.True 10 | -------------------------------------------------------------------------------- /tests/Break.fir: -------------------------------------------------------------------------------- 1 | testFor(n: I32): I32 2 | let sum = 0i32 3 | for i: I32 in range(0, 10000): 4 | if sum > 100: 5 | break 6 | sum += i 7 | sum 8 | 9 | testWhile(n: I32): I32 10 | let sum = 0i32 11 | let i = 0i32 12 | while Bool.True: 13 | if sum > 100: 14 | break 15 | sum += i 16 | i += 1 17 | sum 18 | 19 | main() 20 | printStr(testFor(10).toStr()) 21 | printStr(testFor(20).toStr()) 22 | printStr(testWhile(10).toStr()) 23 | printStr(testWhile(20).toStr()) 24 | 25 | # expected stdout: 26 | # 105 27 | # 105 28 | # 105 29 | # 105 30 | -------------------------------------------------------------------------------- /tests/BreakContinueTypeChecking.fir: -------------------------------------------------------------------------------- 1 | main() 2 | loop: 3 | let x: U32 = match f(): 4 | Option.None: 5 | print("break") 6 | break 7 | Option.Some(i): 8 | i 9 | print(x) 10 | 11 | let i: U32 = 0 12 | loop: 13 | i += 1 14 | if i == 2: 15 | break 16 | let x: U32 = match f(): 17 | Option.None: 18 | print("continue") 19 | continue 20 | Option.Some(i): 21 | i 22 | print(x) 23 | 24 | f(): Option[U32] = Option.None 25 | 26 | # expected stdout: 27 | # break 28 | # continue 29 | -------------------------------------------------------------------------------- /tests/BreakFail.fir: -------------------------------------------------------------------------------- 1 | main() 2 | break 3 | 4 | # args: --typecheck --no-prelude --no-backtrace 5 | # expected exit status: 101 6 | # expected stderr: tests/BreakFail.fir:2:5: `break` or `continue` statement not inside a loop 7 | -------------------------------------------------------------------------------- /tests/CaptureSelf.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | a: U32 3 | 4 | T.captureSelf(self: T): Fn(): [..errs] () 5 | fn(): [..errs] () { 6 | printStr(self.a.toStr()) 7 | } 8 | 9 | main() = T(a = 123).captureSelf()() 10 | 11 | # expected stdout: 123 12 | -------------------------------------------------------------------------------- /tests/Char.fir: -------------------------------------------------------------------------------- 1 | main() 2 | printStr(('a' == 'a').toStr()) 3 | printStr(('a' == 'b').toStr()) 4 | match 'a': 5 | 'b': panic("'a' == 'b'".toStr()) 6 | 'a': printStr("👍") 7 | _: panic("Unexpected character") 8 | 9 | printStr('👍'._codePoint.toStr()) 10 | 11 | # We can't convert or push a Char to a Str, for now check the code point. 12 | printStr(Char.fromU32(128077u32).unwrap()._codePoint.toStr()) 13 | 14 | printStr('a'.lenUtf8().toStr()) 15 | printStr('é'.lenUtf8().toStr()) 16 | printStr('你'.lenUtf8().toStr()) 17 | printStr('😄'.lenUtf8().toStr()) 18 | 19 | testIsAsciiUppercase('A') 20 | testIsAsciiUppercase('Z') 21 | testIsAsciiUppercase('b') 22 | 23 | testIsAsciiLowercase('a') 24 | testIsAsciiLowercase('z') 25 | testIsAsciiLowercase('A') 26 | 27 | testIsAsciiDigit('0') 28 | testIsAsciiDigit('9') 29 | testIsAsciiDigit('b') 30 | 31 | testIsAsciiAlphanumeric('[') 32 | testIsAsciiAlphanumeric('a') 33 | 34 | testIsAsciiUppercase(c: Char) 35 | printStr("`c`.isAsciiUppercase() = `c.isAsciiUppercase()`") 36 | 37 | testIsAsciiLowercase(c: Char) 38 | printStr("`c`.isAsciiLowercase() = `c.isAsciiLowercase()`") 39 | 40 | testIsAsciiDigit(c: Char) 41 | printStr("`c`.isAsciiDigit() = `c.isAsciiDigit()`") 42 | 43 | testIsAsciiAlphanumeric(c: Char) 44 | printStr("`c`.isAsciiAlphanumeric() = `c.isAsciiAlphanumeric()`") 45 | 46 | # expected stdout: 47 | # Bool.True 48 | # Bool.False 49 | # 👍 50 | # 128077 51 | # 128077 52 | # 1 53 | # 2 54 | # 3 55 | # 4 56 | # 'A'.isAsciiUppercase() = Bool.True 57 | # 'Z'.isAsciiUppercase() = Bool.True 58 | # 'b'.isAsciiUppercase() = Bool.False 59 | # 'a'.isAsciiLowercase() = Bool.True 60 | # 'z'.isAsciiLowercase() = Bool.True 61 | # 'A'.isAsciiLowercase() = Bool.False 62 | # '0'.isAsciiDigit() = Bool.True 63 | # '9'.isAsciiDigit() = Bool.True 64 | # 'b'.isAsciiDigit() = Bool.False 65 | # '['.isAsciiAlphanumeric() = Bool.False 66 | # 'a'.isAsciiAlphanumeric() = Bool.True 67 | -------------------------------------------------------------------------------- /tests/CharEscapes.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let buf = StrBuf.withCapacity(10) 3 | buf.push('\'') 4 | buf.push('\n') 5 | buf.push('\t') 6 | buf.push('\r') 7 | buf.push('\\') 8 | printStr(buf._bytes.get(0).toStr()) 9 | printStr(buf._bytes.get(1).toStr()) 10 | printStr(buf._bytes.get(2).toStr()) 11 | printStr(buf._bytes.get(3).toStr()) 12 | printStr(buf._bytes.get(4).toStr()) 13 | 14 | # expected stdout: 15 | # 39 16 | # 10 17 | # 9 18 | # 13 19 | # 92 20 | -------------------------------------------------------------------------------- /tests/ClosureTypeInference1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: Option[U32] = Option.Some(123u32) 3 | print(x.map(fn(i) { i + 10 })) 4 | print(x.map(fn(i) { i.toStr() })) 5 | print(x.map(fn(i) { i.toStr() }).unwrapOr("wat")) 6 | print(try({ 7 | x.map(fn(i) { 8 | if Bool.False: 9 | return 123u32 10 | throw("Hi") 11 | }) 12 | })) 13 | print(x.guard(fn(i) { i != 0 })) 14 | 15 | # expected stdout: 16 | # Option.Some(133) 17 | # Option.Some(123) 18 | # 123 19 | # Result.Err(Hi) 20 | # Option.Some(123) 21 | -------------------------------------------------------------------------------- /tests/Closures1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let c1 = fn() { 3 | printStr("Hi") 4 | } 5 | 6 | let val = "captured" 7 | 8 | let c2 = fn() { 9 | printStr(val) 10 | } 11 | 12 | c1() 13 | c2() 14 | 15 | val = "not captured" 16 | 17 | c2 = fn() { 18 | c2() 19 | c1() 20 | } 21 | 22 | c2() 23 | 24 | # expected stdout: 25 | # Hi 26 | # captured 27 | # captured 28 | # Hi 29 | -------------------------------------------------------------------------------- /tests/Closures2.fir: -------------------------------------------------------------------------------- 1 | test(f: Fn(): [Err] U32) 2 | () 3 | 4 | main() 5 | test({ 123u32 }) 6 | test(fn() { 123u32 }) 7 | test(fn(): U32 { 123 }) 8 | test(fn(): [Err] U32 { 123 }) 9 | test(fn(): [Err] U32 { 123u32 }) 10 | -------------------------------------------------------------------------------- /tests/ClosuresFail1.fir: -------------------------------------------------------------------------------- 1 | test(f: Fn(): [Err] U32) 2 | () 3 | 4 | main() 5 | test({ 123i32 }) 6 | 7 | # args: --typecheck --no-backtrace 8 | # expected exit status: 101 9 | # expected stderr: tests/ClosuresFail1.fir:5:12: Unable to unify types I32 and U32 10 | -------------------------------------------------------------------------------- /tests/ClosuresFail2.fir: -------------------------------------------------------------------------------- 1 | test(f: Fn(): [Err] U32) 2 | () 3 | 4 | main() 5 | test(fn() { throw(~Foo) }) 6 | 7 | # args: --typecheck --no-backtrace 8 | # expected exit status: 101 9 | # expected stderr: 10 | # tests/ClosuresFail2.fir:5:17: Unable to unify variant with constructors {"Foo"} with variant with constructors {"Err"} 11 | -------------------------------------------------------------------------------- /tests/CmdArgs.fir: -------------------------------------------------------------------------------- 1 | main(args: Array[Str]) 2 | printStr(args.toStr()) 3 | 4 | # args after: -- a b c 5 | # expected stdout: [tests/CmdArgs.fir,a,b,c] 6 | -------------------------------------------------------------------------------- /tests/Comments.fir: -------------------------------------------------------------------------------- 1 | # This is a single line comment. 2 | 3 | #| 4 | This is a 5 | multi-line 6 | comment 7 | |# 8 | 9 | #| 10 | Multi-line comment 11 | #| with nested comments |# 12 | |# 13 | 14 | # Commented-out code: 15 | 16 | #| 17 | test() = a b # comments 18 | |# 19 | 20 | main() = () 21 | -------------------------------------------------------------------------------- /tests/ConPatFail1.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | a: U32 3 | b: U32 4 | c: U32 5 | 6 | main() 7 | let x = T(c = 1u32, b = 2u32, a = 3u32) 8 | match x: 9 | T(a = a, b = b, c = c, d = d): () 10 | 11 | # args: --no-backtrace 12 | # expected exit status: 101 13 | # expected stderr: tests/ConPatFail1.fir:9:9: Constructor doesn't take named argument 'd' 14 | -------------------------------------------------------------------------------- /tests/ConPatFail2.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | a: U32 3 | b: U32 4 | c: U32 5 | 6 | main() 7 | let x = T(c = 1u32, b = 2u32, a = 3u32) 8 | match x: 9 | T(a = a, b = b): () 10 | 11 | # args: --no-backtrace 12 | # expected exit status: 101 13 | # expected stderr: 14 | # tests/ConPatFail2.fir:9:9: Constructor takes named arguments {a, b, c}, but applied {a, b} 15 | -------------------------------------------------------------------------------- /tests/ConPatFail3.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | a: U32 3 | b: U32 4 | c: U32 5 | 6 | main() 7 | let x = T(c = 1u32, b = 2u32, a = 3u32) 8 | match x: 9 | T(b = a, b = b): () 10 | 11 | # args: --no-backtrace 12 | # expected exit status: 101 13 | # expected stderr: tests/ConPatFail3.fir:9:9: Named argument 'b' applied multiple times 14 | -------------------------------------------------------------------------------- /tests/ConPatFail4.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | a: U32 3 | b: U32 4 | c: U32 5 | 6 | main() 7 | let x = T(c = 1u32, b = 2u32, a = 3u32) 8 | match x: 9 | T(a = a, b): () 10 | 11 | # args: --no-backtrace 12 | # expected exit status: 101 13 | # expected stderr: 14 | # tests/ConPatFail4.fir:9:9: Constructor takes named arguments {a, b, c}, but applied {a, b} 15 | -------------------------------------------------------------------------------- /tests/Continue.fir: -------------------------------------------------------------------------------- 1 | testFor(n: I32): I32 2 | let sum = 0i32 3 | for i: I32 in range(0, n): 4 | # TODO: Use modulus once we have it. 5 | if i & 0b1 == 0b1: 6 | continue 7 | sum += i 8 | sum 9 | 10 | testWhile(n: I32): I32 11 | let sum = 0i32 12 | let i = 0i32 13 | while Bool.True: 14 | if i == n: 15 | break 16 | 17 | # TODO: Use modulus once we have it. 18 | if i & 0b1 == 0b1: 19 | i += 1 20 | continue 21 | 22 | sum += i 23 | i += 1 24 | sum 25 | 26 | main() 27 | printStr(testFor(10).toStr()) 28 | printStr(testFor(20).toStr()) 29 | printStr(testWhile(10).toStr()) 30 | printStr(testWhile(20).toStr()) 31 | 32 | # expected stdout: 33 | # 20 34 | # 90 35 | # 20 36 | # 90 37 | -------------------------------------------------------------------------------- /tests/ContinueFail.fir: -------------------------------------------------------------------------------- 1 | main() 2 | continue 3 | 4 | # args: --typecheck --no-prelude --no-backtrace 5 | # expected exit status: 101 6 | # expected stderr: tests/ContinueFail.fir:2:5: `break` or `continue` statement not inside a loop 7 | -------------------------------------------------------------------------------- /tests/DotDotPat.fir: -------------------------------------------------------------------------------- 1 | type Foo: 2 | a: U32 3 | b: U32 4 | c: U32 5 | 6 | main() 7 | let x = Foo(a = 1u32, b = 2u32, c = 3u32) 8 | 9 | match x: 10 | Foo(a = x, ..): 11 | print(x) 12 | 13 | match x: 14 | Foo(b = x, ..): 15 | print(x) 16 | 17 | match x: 18 | Foo(c = x, ..): 19 | print(x) 20 | 21 | match x: 22 | Foo(b = x, a = y, ..): 23 | print(x + y) 24 | 25 | match x: 26 | Foo(c = x, a = y, ..): 27 | print(x + y) 28 | 29 | match x: 30 | Foo(c = x, b = y, ..): 31 | print(x + y) 32 | 33 | # expected stdout: 34 | # 1 35 | # 2 36 | # 3 37 | # 3 38 | # 4 39 | # 5 40 | -------------------------------------------------------------------------------- /tests/EmptyTrait.fir: -------------------------------------------------------------------------------- 1 | trait Marker[t] 2 | 3 | type T: 4 | x: U32 5 | 6 | impl Marker[T] 7 | 8 | main() = () 9 | -------------------------------------------------------------------------------- /tests/EmptyType.fir: -------------------------------------------------------------------------------- 1 | # `type Void` is an empty type. A function that returns it is considered as diverging. 2 | # `type Empty = Empty` is a type with just one term: `Empty`. 3 | 4 | type Void 5 | 6 | type Empty: 7 | Empty 8 | 9 | returnsEmpty(): Empty 10 | Empty.Empty 11 | 12 | diverges(): Void 13 | diverges() 14 | 15 | expectsEmpty(value: Empty): () 16 | () 17 | 18 | test() 19 | expectsEmpty(diverges()) 20 | 21 | # args: --typecheck --no-prelude 22 | -------------------------------------------------------------------------------- /tests/Exn1.fir: -------------------------------------------------------------------------------- 1 | f1(): [E1, ..r] () 2 | throw(~E1) 3 | 4 | f2(): [E2, ..r] () 5 | untry(Result.Err(~E2)) 6 | 7 | f3(): [E1, E2, ..r] () 8 | f1() 9 | f2() 10 | 11 | forEach(vec: Vec[t], f: Fn(t): [..r] ()): [..r] () 12 | for t in vec.iter(): 13 | f(t) 14 | 15 | throwOnOdd(i: U32): [OddNumberError(i: U32), ..r] () 16 | if i & 1 == 0: 17 | throw(~OddNumberError(i = i)) 18 | 19 | forEachWithControlFlow(vec: Vec[t], f: Fn(t): [Break, ..r] ()): [..r] Result[[..r], ()] 20 | for t in vec.iter(): 21 | match try(fn(): [Break, ..r] () { f(t) }): 22 | Result.Err(~Break): break 23 | Result.Err(other): return Result.Err(other) 24 | Result.Ok(()): () 25 | 26 | Result.Ok(()) 27 | 28 | main() 29 | match try(f1): 30 | Result.Err(~E1): printStr("OK") 31 | Result.Ok(()): panic("") 32 | 33 | match try(f3): 34 | Result.Err(~E1): printStr("OK") 35 | Result.Err(~E2): panic("1") 36 | Result.Ok(()): panic("2") 37 | 38 | let vec: Vec[U32] = Vec.withCapacity(3) 39 | vec.push(1) 40 | vec.push(2) 41 | vec.push(3) 42 | 43 | match try(fn(): [OddNumberError(i: U32)] () { forEach(vec, throwOnOdd) }): 44 | Result.Err(~OddNumberError(i = i)): printStr("Odd number: `i.toStr()`") 45 | Result.Ok(()): () 46 | 47 | printStr("---") 48 | 49 | forEachWithControlFlow(vec, fn(i: U32): [Break] () { 50 | printStr(i.toStr()) 51 | }) 52 | 53 | printStr("---") 54 | 55 | forEachWithControlFlow(vec, fn(i: U32): [Break] () { 56 | printStr(i.toStr()) 57 | if i & 0b1 == 0: 58 | throw(~Break) 59 | }) 60 | 61 | () 62 | 63 | # expected stdout: 64 | # OK 65 | # OK 66 | # Odd number: 2 67 | # --- 68 | # 1 69 | # 2 70 | # 3 71 | # --- 72 | # 1 73 | # 2 74 | 75 | # expected stderr: 76 | # tests/Exn1.fir:29:5: Unexhaustive pattern match 77 | # tests/Exn1.fir:33:5: Unexhaustive pattern match 78 | -------------------------------------------------------------------------------- /tests/ExnFail1.fir: -------------------------------------------------------------------------------- 1 | f1(): [] () 2 | throw(~A) 3 | 4 | # args: --typecheck --no-backtrace 5 | # expected exit status: 101 6 | # expected stderr: 7 | # tests/ExnFail1.fir:2:5: Unable to unify variant with constructors {"A"} with variant with constructors {} 8 | -------------------------------------------------------------------------------- /tests/ExnFail2.fir: -------------------------------------------------------------------------------- 1 | f1(): [A] a 2 | throw(~A) 3 | 4 | f2(): [A] a 5 | throw(~B) 6 | 7 | # args: --typecheck --no-backtrace 8 | # expected exit status: 101 9 | # expected stderr: 10 | # tests/ExnFail2.fir:5:5: Unable to unify variant with constructors {"B"} with variant with constructors {"A"} 11 | -------------------------------------------------------------------------------- /tests/ExnFail3.fir: -------------------------------------------------------------------------------- 1 | f1(): [..r] a 2 | throw(~A) 3 | 4 | # args: --typecheck --no-backtrace 5 | # expected exit status: 101 6 | # expected stderr: 7 | # tests/ExnFail3.fir:2:5: Unable to unify variant with constructors {"A"} with variant with constructors {} 8 | -------------------------------------------------------------------------------- /tests/ExnFail4.fir: -------------------------------------------------------------------------------- 1 | f1() 2 | throw(~A) 3 | 4 | # args: --typecheck --no-backtrace 5 | # expected exit status: 101 6 | # expected stderr: 7 | # tests/ExnFail4.fir:2:5: Unable to unify types 8 | # [A: (), .._2] and 9 | # ?exn 10 | # ( 11 | # Anonymous { labels: {"A": Anonymous { labels: {}, extension: None, kind: Record, is_row: false }}, extension: Some(Var(TyVarRef(TyVar { id: 2, kind: Row(Variant), level: 0, link: None, loc: tests/ExnFail4.fir:2:11-2:13 }))), kind: Variant, is_row: false } 12 | # Con("?exn", Star) 13 | # ) 14 | -------------------------------------------------------------------------------- /tests/ExplicitTyArgs.fir: -------------------------------------------------------------------------------- 1 | main() 2 | print(Option.None[U32]) 3 | print(foo[[], U32]()) 4 | print(T(x = ()).f[U32, [], Str]()) 5 | 6 | foo(): Option[t] 7 | Option.None[t] 8 | 9 | type T[a]: 10 | x: () 11 | 12 | T.f(self: T[a]): Option[t] 13 | Option.None[t] 14 | 15 | # expected stdout: 16 | # Option.None 17 | # Option.None 18 | # Option.None 19 | -------------------------------------------------------------------------------- /tests/FnExnTypes1.fir: -------------------------------------------------------------------------------- 1 | foo(f: Fn(): exn1 ()): exn2 () 2 | f() 3 | 4 | main() 5 | foo(fn() { print("Hi") }) 6 | foo(fn() { throw("Hi") }) 7 | 8 | # args: --no-backtrace 9 | # expected exit status: 101 10 | # expected stderr: tests/FnExnTypes1.fir:2:5: Unable to unify types exn1 and exn2 11 | -------------------------------------------------------------------------------- /tests/FnExnTypes2.fir: -------------------------------------------------------------------------------- 1 | foo(f: Fn(): exn1 ()): exn1 () 2 | f() 3 | 4 | main() 5 | foo(fn() { print("Hi") }) 6 | foo(fn() { throw("Hi") }) 7 | 8 | # args: --no-backtrace 9 | # expected exit status: 101 10 | # expected stderr: 11 | # tests/FnExnTypes2.fir:6:5: Unable to unify types 12 | # Str and 13 | # [] 14 | # ( 15 | # Con("Str", Star) 16 | # Anonymous { labels: {}, extension: None, kind: Variant, is_row: false } 17 | # ) 18 | -------------------------------------------------------------------------------- /tests/FnTypes1.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | test(i: I32): I32 = i 4 | 5 | blah(): Fn(I32): exn I32 = test 6 | 7 | f(): exn () 8 | let x: Fn(I32): exn I32 = test 9 | test(123) 10 | 11 | let y: Fn(): exn Fn(I32): exn I32 = blah 12 | 13 | # args: --typecheck --no-prelude 14 | -------------------------------------------------------------------------------- /tests/ForLoop.fir: -------------------------------------------------------------------------------- 1 | fac1(n: I32): I32 2 | let ret = 1 3 | for i: I32 in irange(2, n): 4 | ret *= i 5 | ret 6 | 7 | fac2(n: I32): I32 8 | let ret = 1 9 | for i: I32 in range(2, n + 1): 10 | ret *= i 11 | ret 12 | 13 | main() 14 | printStr(fac1(0).toStr()) 15 | printStr(fac1(5).toStr()) 16 | printStr(fac1(10).toStr()) 17 | 18 | printStr(fac2(0).toStr()) 19 | printStr(fac2(5).toStr()) 20 | printStr(fac2(10).toStr()) 21 | 22 | # expected stdout: 23 | # 1 24 | # 120 25 | # 3628800 26 | # 1 27 | # 120 28 | # 3628800 29 | -------------------------------------------------------------------------------- /tests/ForPatBind.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let v: Vec[(x: U32, y: U32)] = Vec.withCapacity(10) 3 | v.push((x = 1, y = 2)) 4 | v.push((x = 3, y = 4)) 5 | v.push((x = 5, y = 6)) 6 | for (x = x, y = y): (x: U32, y: U32) in v.iter(): 7 | printStr("x = `x.toStr()`, y = `y.toStr()`") 8 | 9 | # expected stdout: 10 | # x = 1, y = 2 11 | # x = 3, y = 4 12 | # x = 5, y = 6 13 | -------------------------------------------------------------------------------- /tests/HashMap.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let map: HashMap[Str, U32] = HashMap.withCapacity(10) 3 | printStr(map.insert("a", 1).toStr()) 4 | printStr(map.insert("a", 2).toStr()) 5 | printStr(map.get("a").toStr()) 6 | printStr(map.get("b").toStr()) 7 | printStr(map.toStr()) 8 | printStr(map.insert("b", 3).toStr()) 9 | printStr(map.toStr()) 10 | 11 | # expected stdout: 12 | # Option.None 13 | # Option.Some(1) 14 | # Option.Some(2) 15 | # Option.None 16 | # {a: 2} 17 | # Option.None 18 | # {b: 3, a: 2} 19 | -------------------------------------------------------------------------------- /tests/Hi.fir: -------------------------------------------------------------------------------- 1 | main() 2 | printStr("Hi") 3 | 4 | # expected stdout: 5 | # Hi 6 | -------------------------------------------------------------------------------- /tests/IfCheckingFail1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x = if Bool.True: 3 | 1u32 4 | else: 5 | "Hi" 6 | 7 | # args: --no-backtrace 8 | # expected exit status: 101 9 | # expected stderr: tests/IfCheckingFail1.fir:5:9: Unable to unify types Str and U32 10 | -------------------------------------------------------------------------------- /tests/IfMatchStmtChecking.fir: -------------------------------------------------------------------------------- 1 | main() 2 | if Bool.False: 3 | 1u32 4 | 5 | if Bool.False: 6 | 1u32 7 | elif Bool.True: 8 | 2u32 9 | 10 | match Bool.True: 11 | Bool.True: 123u32 12 | 13 | match Bool.True: 14 | Bool.False: "Hi" 15 | Bool.True: 123u32 16 | 17 | let x: U32 = if Bool.True: 18 | 1u32 19 | else: 20 | 2u32 21 | 22 | print(x) 23 | 24 | let y: U32 = match Bool.True: 25 | Bool.True: 1u32 26 | Bool.False: 2u32 27 | 28 | print(y) 29 | 30 | # Examples from #112: 31 | let builder = StrBuilder(_buf = StrBuf.withCapacity(10)) 32 | if Bool.False: 33 | builder.str("blah") 34 | 35 | if Bool.False: 36 | builder.str("blah") 37 | else: 38 | print("OK") 39 | 40 | # Example from the compiler code: 41 | let char = match "foo".chars().next(): 42 | Option.Some(c): 43 | match c: 44 | 'a': 'b' 45 | _: 'x' 46 | Option.None: 'x' 47 | 48 | print(char) 49 | 50 | () 51 | 52 | type StrBuilder: 53 | _buf: StrBuf 54 | 55 | StrBuilder.str(self, s: Str): StrBuilder 56 | self._buf.pushStr(s) 57 | self 58 | 59 | StrBuilder.char(self, c: Char): StrBuilder 60 | self._buf.push(c) 61 | self 62 | 63 | # expected stdout: 64 | # 1 65 | # 1 66 | # OK 67 | # 'x' 68 | 69 | # expected stderr: tests/IfMatchStmtChecking.fir:10:5: Unexhaustive pattern match 70 | -------------------------------------------------------------------------------- /tests/ImplBounds1.fir: -------------------------------------------------------------------------------- 1 | type A[t]: 2 | i: t 3 | 4 | A.test[ToStr[t1]](self: A[t1]) 5 | printStr(self.i.toStr()) 6 | 7 | main() = A(i = 123i32).test() 8 | 9 | # TODO(24): We can't run this program as the monomorphiser cannot handle renamed type parameters. 10 | # args: --typecheck --no-backtrace 11 | -------------------------------------------------------------------------------- /tests/ImplBounds2.fir: -------------------------------------------------------------------------------- 1 | type A[t]: 2 | i: t 3 | 4 | A.test[ToStr[t1], Eq[t2]](self: A[t1], t2: t2) 5 | printStr(self.i.toStr()) 6 | printStr((t2 == t2).toStr()) 7 | 8 | main() = A(i = 123i32).test(456u32) 9 | 10 | # args: --typecheck --no-backtrace 11 | -------------------------------------------------------------------------------- /tests/ImplBounds3.fir: -------------------------------------------------------------------------------- 1 | prim type Array[t] 2 | 3 | prim type U32 4 | 5 | trait Ord[t]: 6 | cmp(self: t, other: t) 7 | 8 | type Vec[t]: 9 | _data: Array[t] 10 | _len: U32 11 | 12 | Vec.len(self: Vec[t]): U32 13 | self._len 14 | 15 | Vec.sort[Ord[t]](self: Vec[t]) 16 | _quicksort(self, 0, self.len()) 17 | 18 | _quicksort[Ord[t]](vec: Vec[t], low: U32, high: U32) 19 | () 20 | 21 | # args: --typecheck --no-backtrace --no-prelude 22 | -------------------------------------------------------------------------------- /tests/ImplBoundsFail1.fir: -------------------------------------------------------------------------------- 1 | type A[t]: 2 | i: t 3 | 4 | A.test[ToStr[t1]](self: A[t1, t2]) 5 | printStr(self.i.toStr()) 6 | 7 | main() = A(i = 123i32).test() 8 | 9 | # args: --typecheck --no-backtrace 10 | # expected exit status: 101 11 | # expected stderr: 12 | # tests/ImplBoundsFail1.fir:4:25: Incorrect number of type arguments to A, expected 1, found 2 13 | -------------------------------------------------------------------------------- /tests/ImplMethodTypeChecking.fir: -------------------------------------------------------------------------------- 1 | # Regression test for issue #90. 2 | 3 | trait Iter2[iter, item, errs]: 4 | next2(self: iter): [..errs] Option[item] 5 | 6 | map2(self: iter, f: Fn(item): [..errs] b): [..errs2] Map2[iter, item, b, errs] 7 | Map2(iter = self, f = f) 8 | 9 | type Map2[iter, a, b, varRowErrs]: 10 | iter: iter 11 | f: Fn(a): [..varRowErrs] b 12 | 13 | impl[Iter2[iter1, a1, errs1]] Iter2[Map2[iter1, a1, b1, errs1], b1, errs1]: 14 | next2(self: Map2[iter1, a1, b1, errs1]): [..errs1] Option[b1] = panic("") 15 | 16 | # args: --typecheck 17 | -------------------------------------------------------------------------------- /tests/ImplicitTyParams.fir: -------------------------------------------------------------------------------- 1 | id(a: t): t = a 2 | 3 | f1(): Result[[E1, ..r], ()] 4 | Result.Err(~E1) 5 | 6 | type X[t]: 7 | a: t 8 | 9 | X.f2(self: X[t], other: t): t 10 | self.a 11 | 12 | X.f3(self: X[t], other: t2): t2 13 | other 14 | 15 | trait Test[t]: 16 | f(): a 17 | x(): Fn(a): [..r] b 18 | 19 | main() = () 20 | -------------------------------------------------------------------------------- /tests/IntTyInference.fir: -------------------------------------------------------------------------------- 1 | expectI8(i: I8) = () 2 | 3 | expectU8(i: U8) = () 4 | 5 | expectI32(i: I32) = () 6 | 7 | expectU32(i: U32) = () 8 | 9 | main() 10 | expectI8(123) 11 | expectU8(123) 12 | expectI32(123) 13 | expectU32(123) 14 | -------------------------------------------------------------------------------- /tests/IsExpr.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: Option[U32] = Option.None 3 | 4 | if x is Option.None: 5 | print("NA") 6 | 7 | if !(x is Option.Some(_)): 8 | print("NA") 9 | 10 | if !(x is Option.Some(a)): 11 | print("NA") 12 | 13 | let y: Option[U32] = Option.Some(123) 14 | if y is Option.Some(a): 15 | print(a) 16 | 17 | if x is Option.None && y is Option.Some(a): 18 | print(a) 19 | 20 | if x is Option.None && y is Option.Some(a) && a.mod(2) == 0: 21 | print(a) 22 | else: 23 | print("NO") 24 | 25 | if x is Option.None && y is Option.Some(a) && a.mod(2) != 0: 26 | print(a) 27 | 28 | if x is Option.None | Option.None || x is Option.Some(_): 29 | print("YES") 30 | 31 | let res: Result[U32, U32] = Result.Ok(456) 32 | if res is Result.Ok(a) | Result.Err(a): 33 | print(a) 34 | 35 | if res is Result.Ok(a) | Result.Err(a) && a.mod(2) == 0: 36 | print(a) 37 | 38 | if res is Result.Err(a) | Result.Ok(a) && a.mod(2) == 0: 39 | print(a) 40 | 41 | # expected stdout: 42 | # NA 43 | # NA 44 | # NA 45 | # 123 46 | # 123 47 | # NO 48 | # 123 49 | # YES 50 | # 456 51 | # 456 52 | # 456 53 | -------------------------------------------------------------------------------- /tests/IsExprFail1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: Option[U32] = Option.None 3 | if x is Option.Some(a) || a == 0: 4 | () 5 | 6 | # args: --no-backtrace 7 | # expected exit status: 101 8 | # expected stderr: tests/IsExprFail1.fir:3:31: Unbound variable a 9 | -------------------------------------------------------------------------------- /tests/IsExprFail2.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: Option[U32] = Option.None 3 | if x is Option.Some(a) || Bool.True: 4 | print(a) 5 | 6 | # args: --no-backtrace 7 | # expected exit status: 101 8 | # expected stderr: tests/IsExprFail2.fir:4:15: Unbound variable a 9 | -------------------------------------------------------------------------------- /tests/IsExprFail3.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: Option[U32] = Option.None 3 | let y: Option[U32] = Option.None 4 | if x is Option.Some(a) && y is Option.Some(a): 5 | print(a) 6 | 7 | # args: --no-backtrace 8 | # expected exit status: 101 9 | # expected stderr: tests/IsExprFail3.fir:4:8: Left and right exprs in `&&` bind same variables: a 10 | -------------------------------------------------------------------------------- /tests/IsExprInWhile.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: Option[U32] = Option.Some(0) 3 | while x is Option.Some(x1): 4 | print(x1) 5 | if x1 == 10: 6 | x = Option.None 7 | else: 8 | x = Option.Some(x1 + 1) 9 | 10 | # expected stdout: 11 | # 0 12 | # 1 13 | # 2 14 | # 3 15 | # 4 16 | # 5 17 | # 6 18 | # 7 19 | # 8 20 | # 9 21 | # 10 22 | -------------------------------------------------------------------------------- /tests/IsExprShadowing.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: Option[U32] = Option.Some(123) 3 | 4 | if x is Option.Some(x): 5 | print(x) 6 | x = 999 7 | print(x) 8 | 9 | print(x) 10 | 11 | let y: Result[U32, U32] = Result.Ok(456) 12 | 13 | if y is Result.Ok(y) | Result.Err(y): 14 | print(y) 15 | y = 999 16 | print(y) 17 | 18 | print(y) 19 | 20 | # expected stdout: 21 | # 123 22 | # 999 23 | # Option.Some(123) 24 | # 456 25 | # 999 26 | # Result.Ok(456) 27 | -------------------------------------------------------------------------------- /tests/IterChain.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let iter = once(1u32).chain(empty()).chain(once(2u32)).chain(empty()) 3 | 4 | for i: U32 in iter: 5 | print(i) 6 | 7 | for i: U32 in iter: 8 | print(i) 9 | 10 | let iter = "abcd".chars().chain(once('e')) 11 | 12 | for i: Char in iter: 13 | print(i) 14 | 15 | print("---") 16 | 17 | let iter = onceWith(fn() { 18 | print("OnceWith 1") 19 | 'a' 20 | }) \ 21 | .chain(empty()) \ 22 | .chain(onceWith(fn() { 23 | print("OnceWith 2") 24 | 'b' 25 | })) \ 26 | .chain(empty()) 27 | 28 | for i: Char in iter: 29 | print(i) 30 | 31 | for i: Char in iter: 32 | print(i) 33 | 34 | # expected stdout: 35 | # 1 36 | # 2 37 | # 'a' 38 | # 'b' 39 | # 'c' 40 | # 'd' 41 | # 'e' 42 | # --- 43 | # OnceWith 1 44 | # 'a' 45 | # OnceWith 2 46 | # 'b' 47 | -------------------------------------------------------------------------------- /tests/IterCount.fir: -------------------------------------------------------------------------------- 1 | main() 2 | print("asdf".chars().count[CharIter, Char, []]()) 3 | print(empty().count()) 4 | print(once(1u32).count[Once[U32], U32, []]()) 5 | 6 | # expected stdout: 7 | # 4 8 | # 0 9 | # 1 10 | -------------------------------------------------------------------------------- /tests/IterMap.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let sum = 0u32 3 | for x: U32 in "1 2 3".splitWhitespace().map(parseU32): 4 | sum += x 5 | printStr(sum.toStr()) 6 | 7 | parseU32(s: Str): U32 8 | let result: U32 = 0 9 | 10 | for c: Char in s.chars(): 11 | let digit = c.asU32() - '0'.asU32() 12 | result *= 10 13 | result += digit 14 | 15 | result 16 | 17 | # expected stdout: 6 18 | -------------------------------------------------------------------------------- /tests/IterSkip.fir: -------------------------------------------------------------------------------- 1 | type MyIter: 2 | start: U32 3 | end: U32 4 | 5 | impl Iterator[MyIter, U32, exn]: 6 | next(self: MyIter): exn Option[U32] 7 | print("next") 8 | let next = self.start 9 | if next == self.end: 10 | Option.None 11 | else: 12 | self.start += 1 13 | Option.Some(next) 14 | 15 | main() 16 | print("----- 1") 17 | for x: U32 in MyIter(start = 0, end = 5).skip(0): 18 | print(x) 19 | 20 | print("----- 2") 21 | for x: U32 in MyIter(start = 0, end = 5).skip(3): 22 | print(x) 23 | 24 | print("----- 3") 25 | for x: U32 in MyIter(start = 0, end = 5).skip(5): 26 | print(x) 27 | 28 | # `Skip` could stop calling `next` after the first `None`, but it's probably also fine to just 29 | # call it. 30 | print("----- 4") 31 | for x: U32 in MyIter(start = 0, end = 0).skip(5): 32 | print(x) 33 | 34 | # expected stdout: 35 | # ----- 1 36 | # next 37 | # 0 38 | # next 39 | # 1 40 | # next 41 | # 2 42 | # next 43 | # 3 44 | # next 45 | # 4 46 | # next 47 | # ----- 2 48 | # next 49 | # next 50 | # next 51 | # next 52 | # 3 53 | # next 54 | # 4 55 | # next 56 | # ----- 3 57 | # next 58 | # next 59 | # next 60 | # next 61 | # next 62 | # next 63 | # ----- 4 64 | # next 65 | # next 66 | # next 67 | # next 68 | # next 69 | # next 70 | -------------------------------------------------------------------------------- /tests/LetLoweringBug.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: U32 = 123 3 | printStr(x.toStr()) 4 | let x = x 5 | printStr(x.toStr()) 6 | 7 | # expected stdout: 8 | # 123 9 | # 123 10 | -------------------------------------------------------------------------------- /tests/LineBreaks.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let \ 3 | vec \ 4 | : \ 5 | Vec[U32] \ 6 | = \ 7 | Vec \ 8 | . \ 9 | withCapacity(10) 10 | 11 | vec \ 12 | . \ 13 | push \ 14 | (1u32) 15 | 16 | for \ 17 | x \ 18 | : \ 19 | U32 \ 20 | in \ 21 | vec \ 22 | . \ 23 | iter(): 24 | () 25 | -------------------------------------------------------------------------------- /tests/LoopLabels.fir: -------------------------------------------------------------------------------- 1 | main() 2 | 'outer: for i: I32 in range(0, 4): 3 | 'inner: for j: I32 in range(0, 4): 4 | if j & 0b1 == 0b1: 5 | continue 'inner 6 | if i & 0b1 == 0b1: 7 | continue 'outer 8 | printStr("i = `i.toStr()`, j = `j.toStr()`") 9 | 10 | # expected stdout: 11 | # i = 0, j = 0 12 | # i = 0, j = 2 13 | # i = 2, j = 0 14 | # i = 2, j = 2 15 | -------------------------------------------------------------------------------- /tests/MatchCheckingFail1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x = match Bool.True: 3 | Bool.True: 1u32 4 | Bool.False: "Hi" 5 | 6 | # args: --no-backtrace 7 | # expected exit status: 101 8 | # expected stderr: tests/MatchCheckingFail1.fir:4:21: Unable to unify types Str and U32 9 | -------------------------------------------------------------------------------- /tests/MatchGuards.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x: Option[U32] = Option.Some(120) 3 | match x: 4 | Option.Some(i) if i.mod(2) == 0: print(i) 5 | _: panic("WAT") 6 | 7 | let x: Option[U32] = Option.Some(120) 8 | match x: 9 | Option.Some(i) if i.mod(2) == 1: panic("WAT") 10 | _: print("OK") 11 | 12 | # Check exhaustiveness. 13 | match x: 14 | Option.Some(i) if i.mod(2) == 0: print(i) 15 | Option.None: panic("WAT") 16 | 17 | # Check `is` scoping. 18 | # Currently broken, see #122. 19 | # let y: Option[Option[U32]] = Option.Some(Option.Some(123)) 20 | # match y: 21 | # Option.Some(y) if y is Option.Some(y): print(y) 22 | # _: panic("WAT") 23 | # print(y) 24 | 25 | # Check `is` scoping. 26 | let z: Option[Option[U32]] = Option.Some(Option.Some(123)) 27 | match z: 28 | Option.Some(q1) if q1 is Option.Some(q2): 29 | print(q1) 30 | print(q2) 31 | _: panic("WAT") 32 | 33 | # expected stdout: 34 | # 120 35 | # OK 36 | # 120 37 | # Option.Some(123) 38 | # 123 39 | 40 | # expected stderr: tests/MatchGuards.fir:13:5: Unexhaustive pattern match 41 | -------------------------------------------------------------------------------- /tests/MatchInRhs.fir: -------------------------------------------------------------------------------- 1 | test(arg: Result[U32, U32]): U32 2 | let x = 123u32 3 | 4 | x = match arg: 5 | Result.Ok(val): val 6 | Result.Err(err): return err 7 | 8 | x 9 | 10 | main() 11 | printStr(test(Result.Ok(10u32)).toStr()) 12 | printStr(test(Result.Err(10u32)).toStr()) 13 | 14 | # expected stdout: 15 | # 10 16 | # 10 17 | -------------------------------------------------------------------------------- /tests/MatchTcBug.fir: -------------------------------------------------------------------------------- 1 | test(): U32 = 123u32 2 | 3 | main() 4 | match test(): 5 | idx: () 6 | 7 | printStr("`idx`") 8 | 9 | # args: --no-backtrace 10 | # expected exit status: 101 11 | # expected stderr: tests/MatchTcBug.fir:7:16: Unbound variable idx 12 | -------------------------------------------------------------------------------- /tests/MonomorphiserShadowing.fir: -------------------------------------------------------------------------------- 1 | a(f1: Fn(): [..errs] ()): [..errs] () 2 | f1() 3 | 4 | f1(): [E1, ..r] () 5 | throw(~E1) 6 | 7 | main() 8 | match try({ a(f1) }): 9 | Result.Ok(()): () 10 | Result.Err(~E1): printStr("E1 caught") 11 | Result.Err(~E2): printStr("E2 caught") 12 | 13 | # expected stdout: E1 caught 14 | 15 | # expected stderr: tests/MonomorphiserShadowing.fir:8:5: Unexhaustive pattern match 16 | -------------------------------------------------------------------------------- /tests/MultipleBounds.fir: -------------------------------------------------------------------------------- 1 | test[Eq[t], ToStr[t]](a: t) 2 | printStr((a == a).toStr()) 3 | printStr(a.toStr()) 4 | 5 | type A[t]: 6 | i: t 7 | 8 | A.test[Eq[t], ToStr[t]](self: A[t]) 9 | printStr((self.i == self.i).toStr()) 10 | printStr(self.i.toStr()) 11 | 12 | main() 13 | test(123i32) 14 | test(456u32) 15 | 16 | # expected stdout: 17 | # Bool.True 18 | # 123 19 | # Bool.True 20 | # 456 21 | -------------------------------------------------------------------------------- /tests/NamedFieldShorthand.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | x: U32 3 | y: U32 4 | z: U32 5 | 6 | type A: 7 | A: 8 | x: U32 9 | y: U32 10 | z: U32 11 | 12 | main() 13 | let x = 1u32 14 | let y = 2u32 15 | let z = 3u32 16 | let t = T(z, x, y) 17 | print("x = `t.x`, y = `t.y`, z = `t.z`") 18 | let a = A.A(z, x, y) 19 | match a: 20 | A.A(x, y, z): 21 | print("x = `x`, y = `y`, z = `z`") 22 | 23 | # expected stdout: 24 | # x = 1, y = 2, z = 3 25 | # x = 1, y = 2, z = 3 26 | -------------------------------------------------------------------------------- /tests/NamedFields1.fir: -------------------------------------------------------------------------------- 1 | type Result[t, e]: 2 | Ok: 3 | value: t 4 | 5 | Err: 6 | value: e 7 | 8 | prim type I32 9 | 10 | prim type Str 11 | 12 | trait ToStr[t]: 13 | toStr(self: t): Str 14 | 15 | impl ToStr[I32]: 16 | prim toStr(self: I32): Str 17 | 18 | resultToString(result: Result[I32, I32]): Str 19 | match result: 20 | Result.Ok(value = v): 21 | "Ok(`v`)" 22 | Result.Err(value = v): 23 | "Err(`v`)" 24 | 25 | # args: --typecheck --no-prelude 26 | -------------------------------------------------------------------------------- /tests/OrPatBinders.fir: -------------------------------------------------------------------------------- 1 | type Tree: 2 | Leaf(U32) 3 | Branch(Tree, Tree, U32) 4 | 5 | main() 6 | let t1 = Tree.Leaf(123u32) 7 | match t1: 8 | Tree.Leaf(x) | Tree.Branch(_, _, x): print(x) 9 | 10 | let t2 = Tree.Branch(Tree.Leaf(1u32), Tree.Leaf(2u32), 123u32) 11 | match t2: 12 | Tree.Leaf(x) | Tree.Branch(_, _, x): print(x) 13 | 14 | let t3 = Tree.Branch(Tree.Leaf(123u32), Tree.Branch(Tree.Leaf(1u32), Tree.Leaf(2u32), 3u32), 4u32) 15 | match t3: 16 | Tree.Leaf(x) | Tree.Branch(Tree.Leaf(x) | Tree.Branch(Tree.Leaf(_), Tree.Leaf(x), _), _, _): print(x) 17 | 18 | let t4 = Tree.Leaf(123u32) 19 | match t4: 20 | Tree.Leaf(x) | Tree.Branch(Tree.Leaf(x) | Tree.Branch(Tree.Leaf(_), Tree.Leaf(x), _), _, _): print(x) 21 | 22 | let t5 = Tree.Branch(Tree.Branch(Tree.Leaf(1u32), Tree.Leaf(123u32), 2u32), Tree.Leaf(3u32), 4u32) 23 | match t5: 24 | Tree.Leaf(x) | Tree.Branch(Tree.Leaf(x) | Tree.Branch(Tree.Leaf(_), Tree.Leaf(x), _), _, _): print(x) 25 | 26 | # expected stdout: 27 | # 123 28 | # 123 29 | # 123 30 | # 123 31 | # 123 32 | 33 | # expected stderr: 34 | # tests/OrPatBinders.fir:15:5: Unexhaustive pattern match 35 | # tests/OrPatBinders.fir:19:5: Unexhaustive pattern match 36 | # tests/OrPatBinders.fir:23:5: Unexhaustive pattern match 37 | -------------------------------------------------------------------------------- /tests/PPrintExample.fir: -------------------------------------------------------------------------------- 1 | main() 2 | # Nested function calls, indented differently based on requested string width. 3 | let args: Vec[Doc] = Vec.withCapacity(10) 4 | args.push(Doc.str("longArgument1")) 5 | 6 | let nestedCallArgs: Vec[Doc] = Vec.withCapacity(10) 7 | nestedCallArgs.push(Doc.str("arg1")) 8 | nestedCallArgs.push(Doc.str("arg2")) 9 | args.push(makeCallSyntax(Doc.str("myOtherFunction"), nestedCallArgs)) 10 | 11 | let nestedCallArgs: Vec[Doc] = Vec.withCapacity(10) 12 | args.push(makeCallSyntax(Doc.str("yetAnotherFunction"), nestedCallArgs)) 13 | 14 | let doc = makeCallSyntax(Doc.str("myFunction"), args) 15 | 16 | # Break at column 10: splits all groups into lines. 17 | print(doc.render(10)) 18 | 19 | # Break at column 40: splits the outer group, but keeps the inner one flat. 20 | print(doc.render(40)) 21 | 22 | # Break at column 100: prints the whole expression as one line. 23 | print(doc.render(100)) 24 | 25 | print("") 26 | 27 | # Pretty printing combinators allow composing `Doc` values with multiple lines, without breaking 28 | # indentation and alignment. 29 | # 30 | # `expectationFailure` generates an error message. As arguments, we pass `Doc`s with multiple 31 | # lines and indentation. In the output, the second lines are indented the same amount relative 32 | # to the first lines. 33 | print(expectationFailure( 34 | Doc.nested(2, Doc.str("First user line") + Doc.hardLine() + Doc.str("indented 2")), 35 | Doc.nested(4, Doc.str("Second user line") + Doc.hardLine() + Doc.str("indented 4")), 36 | ).render(80)) 37 | 38 | makeCallSyntax(fun: Doc, args: Vec[Doc]): Doc 39 | if args.len() == 0: 40 | return fun + Doc.str("()") 41 | 42 | let doc = fun + Doc.char('(') + Doc.break_(0) 43 | 44 | let numArgs = args.len() 45 | let argIdx = 0u32 46 | for arg: Doc in args.iter(): 47 | doc += arg 48 | if argIdx != numArgs - 1: 49 | doc += Doc.char(',') 50 | doc += Doc.break_(1) 51 | argIdx += 1 52 | 53 | # Add a trailing comma when splitting the arg list into lines. 54 | if args.len() != 0: 55 | doc += Doc.whenNotFlat(Doc.char(',')) 56 | 57 | (doc.nest(4) + Doc.break_(0) + Doc.char(')')).group() 58 | 59 | expectationFailure(expected: Doc, found: Doc): Doc 60 | let expectedStr = "Expected: " 61 | let foundStr = "Found: " 62 | Doc.nested(expectedStr.len(), Doc.str(expectedStr) + expected) \ 63 | + Doc.hardLine() \ 64 | + Doc.nested(foundStr.len(), Doc.str(foundStr) + found) 65 | 66 | # expected stdout: 67 | # myFunction( 68 | # longArgument1, 69 | # myOtherFunction( 70 | # arg1, 71 | # arg2, 72 | # ), 73 | # yetAnotherFunction(), 74 | # ) 75 | # myFunction( 76 | # longArgument1, 77 | # myOtherFunction(arg1, arg2), 78 | # yetAnotherFunction(), 79 | # ) 80 | # myFunction(longArgument1, myOtherFunction(arg1, arg2), yetAnotherFunction()) 81 | # 82 | # Expected: First user line 83 | # indented 2 84 | # Found: Second user line 85 | # indented 4 86 | -------------------------------------------------------------------------------- /tests/Panic.fir: -------------------------------------------------------------------------------- 1 | optionInt(): Option[I32] = Option.Some(123) 2 | 3 | main() 4 | match optionInt(): 5 | Option.None: () 6 | Option.Some(i): panic("optionInt returned `i`") 7 | 8 | # args: --no-backtrace 9 | # expected stderr: 10 | # tests/Panic.fir:6:25: PANIC: optionInt returned 123 11 | -------------------------------------------------------------------------------- /tests/ParserCombinators1.fir: -------------------------------------------------------------------------------- 1 | type ParseError: 2 | EndOfInput 3 | UnexpectedToken 4 | 5 | impl ToStr[ParseError]: 6 | toStr(self: ParseError): Str 7 | match self: 8 | ParseError.EndOfInput: "end of input" 9 | ParseError.UnexpectedToken: "unexpected token" 10 | 11 | type Parser[input, output]: 12 | f: Fn(Vec[input], U32): ParseError (output: output, nextPos: U32) 13 | 14 | ## Match a single token in the input. 15 | Parser.match_(f: Fn(input): ParseError Option[output]): Parser[input, output] 16 | Parser(f = fn(input, pos) { 17 | if pos >= input.len(): 18 | throw(ParseError.EndOfInput) 19 | match f(input.get(pos)): 20 | Option.None: 21 | throw(ParseError.UnexpectedToken) 22 | Option.Some(output): 23 | (output = output, nextPos = pos + 1) 24 | }) 25 | 26 | ## Match a character in the input. 27 | Parser.char(c: Char): Parser[Char, ()] 28 | Parser.match_(fn(input) { 29 | if input == c: 30 | Option.Some(()) 31 | else: 32 | Option.None 33 | }) 34 | 35 | ## Make the parser optional. 36 | Parser.opt(self: Parser[input, output]): Parser[input, Option[output]] 37 | Parser(f = fn(input, pos) { 38 | match try({ self.f(input, pos) }): 39 | Result.Ok((output, nextPos)): 40 | (output = Option.Some(output), nextPos = nextPos) 41 | Result.Err(_): 42 | (output = Option.None, nextPos = pos) 43 | }) 44 | 45 | 46 | ## Parse `p2` after `self`, return outputs of both. 47 | Parser.seq(self: Parser[input, output1], p2: Parser[input, output2]): Parser[input, (first: output1, second: output2)] 48 | Parser(f = fn(input, pos) { 49 | let res1 = self.f(input, pos) 50 | let res2 = p2.f(input, res1.nextPos) 51 | (output = (first = res1.output, second = res2.output), nextPos = res2.nextPos) 52 | }) 53 | 54 | ## Parse `self`, if fails, parse `p2`. 55 | Parser.alt(self: Parser[input, output], p2: Parser[input, output]): Parser[input, output] 56 | Parser(f = fn(input, pos) { 57 | match try({ self.f(input, pos) }): 58 | Result.Ok(res): res 59 | Result.Err(_): p2.f(input, pos) 60 | }) 61 | 62 | Parser.parse(self: Parser[input, output], input: Vec[input]): ParseError (output: output, nextPos: U32) 63 | self.f(input, 0) 64 | 65 | strToVec(s: Str): Vec[Char] 66 | let chars: Vec[Char] = Vec.withCapacity(s.len()) 67 | for c: Char in s.chars(): 68 | chars.push(c) 69 | chars 70 | 71 | main() 72 | let a = Parser.char('a') 73 | print(try({ a.parse(strToVec("a")).output })) 74 | print(try({ a.parse(strToVec("b")).output })) 75 | 76 | print("---") 77 | let seq = Parser.char('a').seq(Parser.char('b')) 78 | match try({ seq.parse(strToVec("ab")).output }): 79 | Result.Ok(_): print("OK") 80 | Result.Err(err): print(err) 81 | match try({ seq.parse(strToVec("ac")).output }): 82 | Result.Ok(_): print("OK") 83 | Result.Err(err): print(err) 84 | match try({ seq.parse(strToVec("a")).output }): 85 | Result.Ok(_): print("OK") 86 | Result.Err(err): print(err) 87 | 88 | print("---") 89 | let alt = Parser.char('a').alt(Parser.char('b')) 90 | print(try({ alt.parse(strToVec("a")).output })) 91 | print(try({ alt.parse(strToVec("b")).output })) 92 | print(try({ alt.parse(strToVec("c")).output })) 93 | 94 | # expected stdout: 95 | # Result.Ok(()) 96 | # Result.Err(unexpected token) 97 | # --- 98 | # OK 99 | # unexpected token 100 | # end of input 101 | # --- 102 | # Result.Ok(()) 103 | # Result.Ok(()) 104 | # Result.Err(unexpected token) 105 | -------------------------------------------------------------------------------- /tests/ParserCombinators2.fir: -------------------------------------------------------------------------------- 1 | type ParseError: 2 | EndOfInput 3 | UnexpectedToken 4 | 5 | impl ToStr[ParseError]: 6 | toStr(self: ParseError): Str 7 | match self: 8 | ParseError.EndOfInput: "end of input" 9 | ParseError.UnexpectedToken: "unexpected token" 10 | 11 | type Parser[input, output]: 12 | f: Fn(ParserState[input]): ParseError output 13 | 14 | type ParserState[input]: 15 | input: Vec[input] 16 | pos: U32 17 | 18 | Parser.match_(f: Fn(input): ParseError output): Parser[input, output] 19 | Parser(f = fn(state) { 20 | if state.pos >= state.input.len(): 21 | throw(ParseError.EndOfInput) 22 | let output = f(state.input.get(state.pos)) 23 | state.pos += 1 24 | output 25 | }) 26 | 27 | Parser.char(c: Char): Parser[Char, ()] 28 | Parser.match_(fn(c_) { 29 | if c != c_: 30 | throw(ParseError.UnexpectedToken) 31 | }) 32 | 33 | Parser.seq(self: Parser[input, output], cont: Fn(output): ParseError Parser[input, output]): Parser[input, output] 34 | Parser(f = fn(state) { cont(self.f(state)).f(state) }) 35 | 36 | Parser.opt(self: Parser[input, output]): Parser[input, Option[output]] 37 | Parser(f = fn(state) { 38 | let pos0 = state.pos 39 | match try({ self.f(state) }): 40 | Result.Err(_): 41 | state.pos = pos0 42 | Option.None 43 | Result.Ok(val): 44 | Option.Some(val) 45 | }) 46 | 47 | Parser.alt(self: Parser[input, output], p2: Parser[input, output]): Parser[input, output] 48 | Parser(f = fn(state) { 49 | let pos0 = state.pos 50 | match try({ self.f(state) }): 51 | Result.Ok(res): 52 | res 53 | Result.Err(_): 54 | state.pos = pos0 55 | p2.f(state) 56 | }) 57 | 58 | Parser.parse(self: Parser[input, output], input: Vec[input]): ParseError output 59 | let state = ParserState(input, pos = 0) 60 | self.f(state) 61 | 62 | strToVec(s: Str): Vec[Char] 63 | let chars: Vec[Char] = Vec.withCapacity(s.len()) 64 | for c: Char in s.chars(): 65 | chars.push(c) 66 | chars 67 | 68 | main() 69 | let a = Parser.char('a') 70 | print(try({ a.parse(strToVec("a")) })) 71 | print(try({ a.parse(strToVec("b")) })) 72 | 73 | print("---") 74 | let seq = Parser.char('a').seq(fn(_x) { Parser.char('b') }) 75 | print(try({ seq.parse(strToVec("ab")) })) 76 | print(try({ seq.parse(strToVec("ac")) })) 77 | print(try({ seq.parse(strToVec("a")) })) 78 | 79 | print("---") 80 | let alt = Parser.char('a').alt(Parser.char('b')) 81 | print(try({ alt.parse(strToVec("a")) })) 82 | print(try({ alt.parse(strToVec("b")) })) 83 | print(try({ alt.parse(strToVec("c")) })) 84 | 85 | # expected stdout: 86 | # Result.Ok(()) 87 | # Result.Err(unexpected token) 88 | # --- 89 | # Result.Ok(()) 90 | # Result.Err(unexpected token) 91 | # Result.Err(end of input) 92 | # --- 93 | # Result.Ok(()) 94 | # Result.Ok(()) 95 | # Result.Err(unexpected token) 96 | -------------------------------------------------------------------------------- /tests/PatCon1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | match f(): 3 | Foo(a, b): 4 | print(a) 5 | print(b) 6 | 7 | type Foo: 8 | a: U32 9 | b: U32 10 | 11 | f(): Foo = Foo(a = 1, b = 2) 12 | 13 | # expected stdout: 14 | # 1 15 | # 2 16 | -------------------------------------------------------------------------------- /tests/PatConFail1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | match f(): 3 | Foo.A: () 4 | Foo.B: () 5 | 6 | f(): Option[U32] = Option.None 7 | 8 | # args: --no-backtrace 9 | # expected exit status: 101 10 | # expected stderr: tests/PatConFail1.fir:3:9: Undefined type Foo 11 | -------------------------------------------------------------------------------- /tests/PatConFail2.fir: -------------------------------------------------------------------------------- 1 | main() 2 | match f(): 3 | Foo.A: () 4 | Foo.B: () 5 | 6 | type Foo: 7 | X 8 | Y 9 | 10 | f(): Option[U32] = Option.None 11 | 12 | # args: --no-backtrace 13 | # expected exit status: 101 14 | # expected stderr: tests/PatConFail2.fir:3:9: Type Foo does not have a constructor named A 15 | -------------------------------------------------------------------------------- /tests/PatConFail3.fir: -------------------------------------------------------------------------------- 1 | main() 2 | match f(): 3 | Foo.X: () 4 | 5 | type Foo: 6 | a: U32 7 | b: U32 8 | 9 | f(): Foo = Foo(a = 1, b = 2) 10 | 11 | # args: --no-backtrace 12 | # expected exit status: 101 13 | # expected stderr: tests/PatConFail3.fir:3:9: Type Foo is not a sum type 14 | -------------------------------------------------------------------------------- /tests/PatConFail4.fir: -------------------------------------------------------------------------------- 1 | main() 2 | match f(): 3 | Foo: () 4 | 5 | type Foo 6 | 7 | f(): Foo = panic("") 8 | 9 | # args: --no-backtrace 10 | # expected exit status: 101 11 | # expected stderr: tests/PatConFail4.fir:3:9: Type Foo doesn't have any constructors 12 | -------------------------------------------------------------------------------- /tests/Peekable.fir: -------------------------------------------------------------------------------- 1 | type Iter: 2 | next: U32 3 | 4 | impl Iterator[Iter, U32, exn]: 5 | next(self: Iter): exn Option[U32] 6 | if self.next == 3: 7 | printStr("Stopping") 8 | Option.None 9 | else: 10 | printStr("Generating next item: `self.next.toStr()`") 11 | let next = self.next 12 | self.next += 1 13 | Option.Some(next) 14 | 15 | main() 16 | let iter = Iter(next = 0) 17 | let peekable = iter.peekable() 18 | let next: Option[U32] = peekable.next() 19 | printStr(next.toStr()) 20 | let next: Option[U32] = peekable.peek() 21 | printStr(next.toStr()) 22 | let next: Option[U32] = peekable.peek() 23 | printStr(next.toStr()) 24 | let next: Option[U32] = peekable.next() 25 | printStr(next.toStr()) 26 | let next: Option[U32] = peekable.next() 27 | printStr(next.toStr()) 28 | let next: Option[U32] = peekable.peek() 29 | printStr(next.toStr()) 30 | let next: Option[U32] = peekable.next() 31 | printStr(next.toStr()) 32 | () 33 | 34 | # expected stdout: 35 | # Generating next item: 0 36 | # Option.Some(0) 37 | # Generating next item: 1 38 | # Option.Some(1) 39 | # Option.Some(1) 40 | # Option.Some(1) 41 | # Generating next item: 2 42 | # Option.Some(2) 43 | # Stopping 44 | # Option.None 45 | # Stopping 46 | # Option.None 47 | -------------------------------------------------------------------------------- /tests/PolyMethods.fir: -------------------------------------------------------------------------------- 1 | trait T[a]: 2 | f[ToStr[b]](self: a, b: b) 3 | 4 | type X: 5 | value: I32 6 | 7 | impl T[X]: 8 | f[ToStr[q]](self: X, q: q) 9 | printStr("X.f called with q = `q`") 10 | 11 | main() 12 | let x = X(value = 123) 13 | x.f("hi") 14 | 15 | let y = x.f 16 | y(123) 17 | 18 | # expected stdout: 19 | # X.f called with q = hi 20 | # X.f called with q = 123 21 | -------------------------------------------------------------------------------- /tests/ProductNamedFieldPatShorthand.fir: -------------------------------------------------------------------------------- 1 | type Foo: 2 | a: U32 3 | b: U32 4 | c: U32 5 | 6 | main() 7 | let x = Foo(a = 1u32, b = 2u32, c = 3u32) 8 | 9 | match x: 10 | Foo(a, b, c): 11 | print("a = `a`, b = `b`, c = `c`") 12 | 13 | match x: 14 | Foo(c, a, b): 15 | print("a = `a`, b = `b`, c = `c`") 16 | 17 | match x: 18 | Foo(b, ..): 19 | print("b = `b`") 20 | 21 | match x: 22 | Foo(c = q1, b = q2, a): 23 | print("a = `a`, q1 = `q1`, q2 = `q2`") 24 | 25 | # expected stdout: 26 | # a = 1, b = 2, c = 3 27 | # a = 1, b = 2, c = 3 28 | # b = 2 29 | # a = 1, q1 = 3, q2 = 2 30 | -------------------------------------------------------------------------------- /tests/RecordNamedFieldFail1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let (z, x) = f() 3 | print("x = `x`, y = `y`, z = `z`") 4 | 5 | f(): (x: U32, y: U32, z: U32) = (x = 123u32, y = 456u32, z = 789u32) 6 | 7 | # args: --no-backtrace 8 | # expected exit status: 101 9 | # expected stderr: 10 | # tests/RecordNamedFieldFail1.fir:2:9: Unable to unify record with fields {"x", "z"} with record with fields {"x", "y", "z"} 11 | -------------------------------------------------------------------------------- /tests/RecordNamedFieldShorthand.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let (z, x, y) = f() 3 | print("x = `x`, y = `y`, z = `z`") 4 | 5 | let (x, ..) = f() 6 | print("x = `x`") 7 | 8 | let (y, ..) = f() 9 | print("y = `y`") 10 | 11 | let (z, ..) = f() 12 | print("z = `z`") 13 | 14 | let (x, z, ..) = f() 15 | print("x = `x`, z = `z`") 16 | 17 | let (z, x, ..) = f() 18 | print("x = `x`, z = `z`") 19 | 20 | f(): (x: U32, y: U32, z: U32) = (x = 123u32, y = 456u32, z = 789u32) 21 | 22 | # expected stdout: 23 | # x = 123, y = 456, z = 789 24 | # x = 123 25 | # y = 456 26 | # z = 789 27 | # x = 123, z = 789 28 | # x = 123, z = 789 29 | -------------------------------------------------------------------------------- /tests/Records.fir: -------------------------------------------------------------------------------- 1 | # f1(): (I8, I32, Option[Str]) = (123, 456, None) 2 | 3 | f2(): (x: I32, y: I32, optStr: Option[Str]) = (x = 123, y = 456, optStr = Option.Some("Blah")) 4 | 5 | main() 6 | # let (x, y, optStr) = f1() 7 | # printStr(x.toStr()) 8 | # printStr(y.toStr()) 9 | # printStr(optStr.toStr()) 10 | 11 | let (x = a, y = b, optStr = c) = f2() 12 | printStr(a.toStr()) 13 | printStr(b.toStr()) 14 | match c: 15 | Option.Some(c): printStr(c) 16 | Option.None: () 17 | 18 | # expected stdout: 19 | # 123 20 | # 456 21 | # Blah 22 | -------------------------------------------------------------------------------- /tests/ReturnTy.fir: -------------------------------------------------------------------------------- 1 | readFile(s: Str): Result[[IoError, ..r], Str] 2 | Result.Err(~IoError) 3 | 4 | parseU32(s: Str): Result[[InvalidDigit, Overflow, EmptyInput, ..r], U32] 5 | Result.Ok(123u32) 6 | 7 | parseU32FromFile(filePath: Str): Result[[InvalidDigit, Overflow, EmptyInput, IoError, ..r], U32] 8 | let fileContents = match readFile(filePath): 9 | Result.Err(err): return Result.Err(err) 10 | Result.Ok(contents): contents 11 | 12 | parseU32(fileContents) 13 | 14 | main() 15 | () 16 | -------------------------------------------------------------------------------- /tests/ReturnTypeExpectedType.fir: -------------------------------------------------------------------------------- 1 | prim type U32 2 | 3 | type Bool: 4 | False 5 | True 6 | 7 | type Option[t]: 8 | Some(t) 9 | None 10 | 11 | f(): Option[(a: U32, b: U32)] 12 | match Bool.False: 13 | Bool.True: Option.Some((a = 1, b = 2)) 14 | Bool.False: Option.None 15 | 16 | # args: --no-prelude --typecheck 17 | -------------------------------------------------------------------------------- /tests/Rows1.fir: -------------------------------------------------------------------------------- 1 | test[RecRow[r]](r: (x: I32, y: I32, ..r)) 2 | printStr(r.x.toStr()) 3 | printStr(r.y.toStr()) 4 | 5 | id[RecRow[r]](r: (x: I32, ..r)): (x: I32, ..r) 6 | printStr(r.x.toStr()) 7 | r 8 | 9 | type Test[recRow]: 10 | x: (x: I32, ..recRow) 11 | 12 | main() 13 | test(id((x = 1, y = 2))) 14 | test(id((x = 3, y = 4, z = "hi"))) 15 | let struct = Test(x = (x = 123, y = 456)) 16 | test(id(struct.x)) 17 | 18 | # expected stdout: 19 | # 1 20 | # 1 21 | # 2 22 | # 3 23 | # 3 24 | # 4 25 | # 123 26 | # 123 27 | # 456 28 | -------------------------------------------------------------------------------- /tests/Rows2.fir: -------------------------------------------------------------------------------- 1 | mono_fn(alts: [A(x: I32), B(y: Str)]) 2 | match alts: 3 | ~A(x = i): printStr("A(i=`i.toStr()`)") 4 | ~B(y = s): printStr("B(s=`s`)") 5 | 6 | poly_fn[VarRow[r]](alts: [A(x: I32), B(y: Str), ..r]) 7 | match alts: 8 | ~A(x = i): printStr("A(i=`i.toStr()`)") 9 | ~B(y = s): printStr("B(s=`s`)") 10 | _: printStr("Other") 11 | 12 | if_variants_mono(): [A(x: I32), B(y: Str)] 13 | if Bool.True: 14 | ~A(x = 123) 15 | else: 16 | ~B(y = "ahoy") 17 | 18 | if_variants_poly[VarRow[r]](): [A(x: I32), B(y: Str), ..r] 19 | if Bool.True: 20 | ~A(x = 123) 21 | else: 22 | ~B(y = "ahoy") 23 | 24 | poly_return[VarRow[r]](): [B(y: Str), ..r] 25 | ~B(y = "ahoy") 26 | 27 | main() 28 | mono_fn(~A(x = 123)) 29 | mono_fn(~B(y = "hi")) 30 | 31 | # [A(I32), ..r1] ~ [A(I32), B(Str), ..r2] 32 | # r1 --> [B(Str), ..r3] 33 | # r2 --> [..r3] 34 | # During monomorphisation, r3 should be defaulted as []. 35 | poly_fn(~A(x = 123)) 36 | 37 | # [C(Bool), ..r1] ~ [A(I32), B(Str), ..r2] 38 | # r1 --> [A(I32), B(Str), ..r3] 39 | # r2 --> [C(Bool), ..r3] 40 | # Similar to the previous, r3 should be defaulted as []. 41 | poly_fn(~C(t = Bool.False)) 42 | 43 | if_variants_mono() 44 | if_variants_poly() 45 | poly_fn(poly_return()) 46 | () 47 | 48 | # expected stdout: 49 | # A(i=123) 50 | # B(s=hi) 51 | # A(i=123) 52 | # Other 53 | # B(s=ahoy) 54 | -------------------------------------------------------------------------------- /tests/Rows3.fir: -------------------------------------------------------------------------------- 1 | f1[VarRow[r]](): Result[[E1, ..r], ()] 2 | Result.Err(~E1) 3 | 4 | f2[VarRow[r]](): Result[[E1, E2, ..r], ()] 5 | if Bool.True: 6 | Result.Err(~E1) 7 | else: 8 | Result.Err(~E2) 9 | 10 | f3[VarRow[r]](): Result[[E1, E2, ..r], ()] 11 | let err = if Bool.True: 12 | ~E1 13 | else: 14 | ~E2 15 | Result.Err(err) 16 | 17 | f4(): Result[[E1, E2, E3], ()] 18 | f1() 19 | f2() 20 | f3() 21 | 22 | f5Simple(): Result[[E2, E3], ()] 23 | match f4(): 24 | Result.Err(errs): 25 | match errs: 26 | ~E1: Result.Ok(()) 27 | other: Result.Err(other) 28 | 29 | Result.Ok(()): 30 | Result.Ok(()) 31 | 32 | f5Complex(): Result[[E2, E3], ()] 33 | match f4(): 34 | Result.Err(~E1) | Result.Ok(()): 35 | Result.Ok(()) 36 | 37 | Result.Err(other): 38 | Result.Err(other) 39 | 40 | f6[VarRow[errs]](f: Fn(): exn Result[[E1, ..errs], ()]): exn Result[[..errs], ()] 41 | match f(): 42 | Result.Err(~E1) | Result.Ok(()): 43 | Result.Ok(()) 44 | 45 | Result.Err(other): 46 | Result.Err(other) 47 | 48 | # Similar to `f6`, but the call site adds more error cases. 49 | # 50 | # TODO: I'm not sure that this type is right.. Do we need an "absent" constraint 51 | # like `errs \ E1`? 52 | # 53 | # Currently this fails because we try to unify: 54 | # 55 | # [E1, ..r1] ~ [..errs] 56 | # 57 | # and `E1` cannot be added to `errs` as it's a rigid type variable. 58 | # 59 | # f7[errs](f: Fn(): Result[[..errs], ()]): Result[[E1, ..errs], ()] 60 | # match f(): 61 | # Result.Err(err): 62 | # Result.Err(err) 63 | # 64 | # Result.Ok(()): 65 | # Result.Err(~E1) 66 | 67 | f7_2[VarRow[errs]](f: Fn(): exn Result[[E1, ..errs], ()]): exn Result[[E1, ..errs], ()] 68 | match f(): 69 | Result.Err(err): 70 | Result.Err(err) 71 | 72 | Result.Ok(()): 73 | Result.Err(~E1) 74 | 75 | f7_arg_1(): Result[[E1, E2], ()] 76 | Result.Err(~E1) 77 | 78 | f7_arg_2[VarRow[errs]](): Result[[E2, ..errs], ()] 79 | Result.Err(~E2) 80 | 81 | f8(f: Fn(): exn1 [E1, E2]) = () 82 | 83 | f9[VarRow[r]](): [E1, ..r] = ~E1 84 | 85 | f10[VarRow[r]](f: Fn([E1, E2, ..r]): exn1 ()) = () 86 | 87 | f11(x: [E1, E2, E3]) = () 88 | 89 | type SimpleError: 90 | Error([A, B, C]) 91 | NotError(I32) 92 | 93 | f12(a: SimpleError): Result[[C], I32] 94 | match a: 95 | SimpleError.Error(~A | ~B): Result.Err(~C) 96 | SimpleError.Error(other): Result.Err(other) 97 | SimpleError.NotError(i): Result.Ok(i) 98 | 99 | main() 100 | # Almost like function subtyping: a function that throws less can be passed 101 | # as a function that throws more. 102 | f8(f9) 103 | 104 | # Similar to the above, but for arguments: a function that handles more can 105 | # be passed as a function that handles less. 106 | f10(f11) 107 | 108 | f7_2(f7_arg_1) 109 | f7_2(f7_arg_2) 110 | 111 | () 112 | -------------------------------------------------------------------------------- /tests/RowsFail1.fir: -------------------------------------------------------------------------------- 1 | test(r: (x: I32, y: I32, ..r)) 2 | printStr(r.x.toStr()) 3 | printStr(r.y.toStr()) 4 | 5 | main() 6 | test((x = 123)) 7 | 8 | # args: --typecheck --no-backtrace 9 | # expected exit status: 101 10 | # expected stderr: 11 | # tests/RowsFail1.fir:6:10: Unable to unify record with fields {"x"} with record with fields {"x", "y"} 12 | -------------------------------------------------------------------------------- /tests/RowsFail2.fir: -------------------------------------------------------------------------------- 1 | test(r: (x: I32, y: I32, ..r)): (x: I32, y: I32) 2 | printStr(r.x.toStr()) 3 | printStr(r.y.toStr()) 4 | r 5 | 6 | main() 7 | test((x = 123, y = 456)) 8 | 9 | # args: --typecheck --no-backtrace 10 | # expected exit status: 101 11 | # expected stderr: 12 | # tests/RowsFail2.fir:4:5: Unable to unify types 13 | # r and 14 | # row() 15 | # ( 16 | # Con("r", Row(Record)) 17 | # Anonymous { labels: {}, extension: None, kind: Record, is_row: true } 18 | # ) 19 | -------------------------------------------------------------------------------- /tests/RowsFail3.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let x = if Bool.True: 3 | (x = 123i32) 4 | else: 5 | (y = "hi") 6 | () 7 | 8 | # args: --typecheck --no-backtrace 9 | # expected exit status: 101 10 | # expected stderr: 11 | # tests/RowsFail3.fir:5:9: Unable to unify record with fields {"y"} with record with fields {"x"} 12 | -------------------------------------------------------------------------------- /tests/RowsFail4.fir: -------------------------------------------------------------------------------- 1 | mono_fn(alts: [A(x: I32), B(y: Str)]) 2 | match alts: 3 | ~A(x = i): printStr("A(i=`i.toStr()`)") 4 | ~B(y = s): printStr("B(s=`s`)") 5 | 6 | main() = mono_fn(~C()) 7 | 8 | # args: --typecheck --no-backtrace 9 | # expected exit status: 101 10 | # expected stderr: 11 | # tests/RowsFail4.fir:6:18: Unable to unify variant with constructors {"C"} with variant with constructors {"A", "B"} 12 | -------------------------------------------------------------------------------- /tests/RowsFail5.fir: -------------------------------------------------------------------------------- 1 | poly_fn(alts: [A(x: I32), B(y: Str), ..r]) 2 | match alts: 3 | ~A(x = i): printStr("A(i=`i.toStr()`)") 4 | ~B(y = s): printStr("B(s=`s`)") 5 | _: printStr("Other") 6 | 7 | main() = poly_fn(~B(y = Bool.False)) 8 | 9 | # args: --typecheck --no-backtrace 10 | # expected exit status: 101 11 | # expected stderr: tests/RowsFail5.fir:7:18: Unable to unify types Bool and Str 12 | -------------------------------------------------------------------------------- /tests/SameMethodName.fir: -------------------------------------------------------------------------------- 1 | type T1: 2 | a: U32 3 | 4 | type T2: 5 | a: U32 6 | 7 | T1.foo(self) 8 | print("T1.foo(a = `self.a`)") 9 | 10 | T2.foo(self) 11 | print("T2.foo(a = `self.a`)") 12 | 13 | main() 14 | let t1 = T1(a = 1u32) 15 | let t2 = T2(a = 2u32) 16 | t1.foo() 17 | t2.foo() 18 | 19 | # expected stdout: 20 | # T1.foo(a = 1) 21 | # T2.foo(a = 2) 22 | -------------------------------------------------------------------------------- /tests/SingleLineFnSyntax.fir: -------------------------------------------------------------------------------- 1 | main(): () = sayHi() 2 | 3 | sayHi() = printStr("Hi") 4 | 5 | # expected stdout: 6 | # Hi 7 | -------------------------------------------------------------------------------- /tests/Str.fir: -------------------------------------------------------------------------------- 1 | main() 2 | printStr("asdf".startsWith("a").toStr()) 3 | printStr("asdf".startsWith("as").toStr()) 4 | printStr("asdf".startsWith("asd").toStr()) 5 | printStr("asdf".startsWith("asdf").toStr()) 6 | printStr("asdf".startsWith("asdfg").toStr()) 7 | printStr("asdf".charAt(0).toStr()) 8 | printStr("asdf".charAt(1).toStr()) 9 | printStr("asdf".charAt(2).toStr()) 10 | printStr("asdf".charAt(3).toStr()) 11 | 12 | let s = " αあ💩" 13 | printStr(s.charAt(1).toStr()) 14 | printStr(s.charAt(3).toStr()) 15 | printStr(s.charAt(6).toStr()) 16 | 17 | let substr = s.substr(1, s.len()) 18 | printStr(substr.charAt(0).toStr()) 19 | printStr(substr.charAt(2).toStr()) 20 | printStr(substr.charAt(5).toStr()) 21 | 22 | printStr("---") 23 | 24 | for char: Char in s.chars(): 25 | printStr(char.toStr()) 26 | 27 | printStr("---") 28 | 29 | let charIter = s.chars() 30 | printStr(charIter.asStr()) 31 | let _: Option[Char] = charIter.next() 32 | printStr(charIter.asStr()) 33 | let _: Option[Char] = charIter.next() 34 | printStr(charIter.asStr()) 35 | let _: Option[Char] = charIter.next() 36 | printStr(charIter.asStr()) 37 | let _: Option[Char] = charIter.next() 38 | 39 | printStr("---") 40 | 41 | # goldentests ignores trailing empty lines, so add some noise to the last empty lines. 42 | printStr("\"`charIter.asStr()`\"") 43 | let _: Option[Char] = charIter.next() 44 | printStr("\"`charIter.asStr()`\"") 45 | 46 | # expected stdout: 47 | # Bool.True 48 | # Bool.True 49 | # Bool.True 50 | # Bool.True 51 | # Bool.False 52 | # 'a' 53 | # 's' 54 | # 'd' 55 | # 'f' 56 | # 'α' 57 | # 'あ' 58 | # '💩' 59 | # 'α' 60 | # 'あ' 61 | # '💩' 62 | # --- 63 | # ' ' 64 | # 'α' 65 | # 'あ' 66 | # '💩' 67 | # --- 68 | # αあ💩 69 | # αあ💩 70 | # あ💩 71 | # 💩 72 | # --- 73 | # "" 74 | # "" 75 | -------------------------------------------------------------------------------- /tests/StrBuf.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let buf = StrBuf.withCapacity(10u32) 3 | buf.push('A') 4 | printStr(buf.len().toStr()) 5 | printStr(buf._bytes.get(0u32).toStr()) 6 | printStr(buf.toStr()) 7 | buf.clear() 8 | 9 | buf.push('é') 10 | printStr(buf.len().toStr()) 11 | printStr(buf._bytes.get(0u32).toStr()) 12 | printStr(buf._bytes.get(1u32).toStr()) 13 | printStr(buf.toStr()) 14 | buf.clear() 15 | 16 | buf.push('€') 17 | printStr(buf.len().toStr()) 18 | printStr(buf._bytes.get(0u32).toStr()) 19 | printStr(buf._bytes.get(1u32).toStr()) 20 | printStr(buf._bytes.get(2u32).toStr()) 21 | printStr(buf.toStr()) 22 | buf.clear() 23 | 24 | buf.push('😀') 25 | printStr(buf.len().toStr()) 26 | printStr(buf._bytes.get(0u32).toStr()) 27 | printStr(buf._bytes.get(1u32).toStr()) 28 | printStr(buf._bytes.get(2u32).toStr()) 29 | printStr(buf._bytes.get(3u32).toStr()) 30 | printStr(buf.toStr()) 31 | 32 | # expected stdout: 33 | # 1 34 | # 65 35 | # A 36 | # 2 37 | # 195 38 | # 169 39 | # é 40 | # 3 41 | # 226 42 | # 130 43 | # 172 44 | # € 45 | # 4 46 | # 240 47 | # 159 48 | # 152 49 | # 128 50 | # 😀 51 | -------------------------------------------------------------------------------- /tests/StrCharIndices.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let s = " αあ💩" 3 | printStr("|`s`") 4 | for charIdx: (char: Char, idx: U32) in s.charIndices(): 5 | let char = charIdx.char 6 | let idx = charIdx.idx 7 | printStr("`idx.toStr()`: `char.toStr()`") 8 | 9 | let substr = s.substr(1, s.len()) 10 | printStr("|`substr`") 11 | for charIdx: (char: Char, idx: U32) in substr.charIndices(): 12 | let char = charIdx.char 13 | let idx = charIdx.idx 14 | printStr("`idx.toStr()`: `char.toStr()`") 15 | 16 | # expected stdout: 17 | # | αあ💩 18 | # 0: ' ' 19 | # 1: 'α' 20 | # 3: 'あ' 21 | # 6: '💩' 22 | # |αあ💩 23 | # 0: 'α' 24 | # 2: 'あ' 25 | # 5: '💩' 26 | -------------------------------------------------------------------------------- /tests/StrEscapes.fir: -------------------------------------------------------------------------------- 1 | main() 2 | print("\`asdf\`\t|\n|\r|\"") 3 | 4 | # expected stdout: 5 | # `asdf` | 6 | # ||" 7 | -------------------------------------------------------------------------------- /tests/StrLines.fir: -------------------------------------------------------------------------------- 1 | collect(iter: Lines): Vec[Str] 2 | let vec = Vec.withCapacity(10) 3 | for a: Str in iter: 4 | vec.push(a) 5 | vec 6 | 7 | main() 8 | let s = "" 9 | printStr(collect("".lines()).toStr()) 10 | printStr(collect("a".lines()).toStr()) 11 | printStr(collect("a\n".lines()).toStr()) 12 | printStr(collect("\na\n".lines()).toStr()) 13 | printStr(collect("\n\n\n".lines()).toStr()) 14 | printStr(collect("\n\n\na".lines()).toStr()) 15 | 16 | # expected stdout: 17 | # [] 18 | # [a] 19 | # [a] 20 | # [,a] 21 | # [,,] 22 | # [,,,a] 23 | -------------------------------------------------------------------------------- /tests/StrPatEscape.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let s = "\"" 3 | printStr(s) 4 | 5 | match s: 6 | "\"" rest: 7 | printStr("Matched, rest = \"`rest`\"") 8 | other: 9 | panic("") 10 | 11 | match s: 12 | "\"": 13 | printStr("Matched") 14 | other: 15 | panic("") 16 | 17 | # expected stdout: 18 | # " 19 | # Matched, rest = "" 20 | # Matched 21 | -------------------------------------------------------------------------------- /tests/StrPats.fir: -------------------------------------------------------------------------------- 1 | main() 2 | match "test": 3 | "asdf": panic("") 4 | "test": printStr("OK") 5 | _: panic("") 6 | 7 | match "aOK": 8 | "a" rest: printStr(rest) 9 | _: panic("") 10 | 11 | match "asdf": 12 | "asdf" rest: printStr(rest.isEmpty().toStr()) 13 | _: panic("") 14 | 15 | match "aOK": 16 | "a" _: printStr("a matched") 17 | _: panic("") 18 | 19 | match "asdf": 20 | "asdf" _: printStr("asdf matched") 21 | _: panic("") 22 | 23 | # expected stdout: 24 | # OK 25 | # OK 26 | # Bool.True 27 | # a matched 28 | # asdf matched 29 | -------------------------------------------------------------------------------- /tests/StrSplitWhitespace.fir: -------------------------------------------------------------------------------- 1 | collect(iter: SplitWhitespace): Vec[Str] 2 | let vec = Vec.withCapacity(10) 3 | for a: Str in iter: 4 | vec.push(a) 5 | vec 6 | 7 | main() 8 | printStr(collect("".splitWhitespace()).toStr()) 9 | printStr(collect(" ".splitWhitespace()).toStr()) 10 | printStr(collect("a".splitWhitespace()).toStr()) 11 | printStr(collect("a b".splitWhitespace()).toStr()) 12 | printStr(collect("a b".splitWhitespace()).toStr()) 13 | printStr(collect(" a b".splitWhitespace()).toStr()) 14 | printStr(collect("a b ".splitWhitespace()).toStr()) 15 | printStr(collect(" a b ".splitWhitespace()).toStr()) 16 | 17 | printStr("---") 18 | 19 | printStr(collect(" ".substr(1, 2).splitWhitespace()).toStr()) 20 | printStr(collect("a".substr(1, 1).splitWhitespace()).toStr()) 21 | printStr(collect("a b".substr(1, 3).splitWhitespace()).toStr()) 22 | printStr(collect("a b".substr(1, 4).splitWhitespace()).toStr()) 23 | printStr(collect(" a b".splitWhitespace()).toStr()) 24 | 25 | # expected stdout: 26 | # [] 27 | # [] 28 | # [a] 29 | # [a,b] 30 | # [a,b] 31 | # [a,b] 32 | # [a,b] 33 | # [a,b] 34 | # --- 35 | # [] 36 | # [] 37 | # [b] 38 | # [b] 39 | # [a,b] 40 | -------------------------------------------------------------------------------- /tests/StringContinuationEscape.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let s = "a \ 3 | b \ 4 | c" 5 | print(s) 6 | 7 | # expected stdout: a b c 8 | -------------------------------------------------------------------------------- /tests/SumNamedFieldPatShorthand.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | A: 3 | x: U32 4 | y: U32 5 | z: U32 6 | 7 | main() 8 | let val = T.A(x = 123u32, y = 456u32, z = 789u32) 9 | 10 | match val: 11 | T.A(x, y, z): 12 | print("x = `x`, y = `y`, z = `z`") 13 | 14 | match val: 15 | T.A(y, z, x): 16 | print("x = `x`, y = `y`, z = `z`") 17 | 18 | match val: 19 | T.A(y = a, z, x = b): 20 | print("x = `b`, y = `a`, z = `z`") 21 | 22 | match val: 23 | T.A(y = a, ..): 24 | print("y = `a`") 25 | 26 | match val: 27 | T.A(z = x, y = a, ..): 28 | print("y = `a`, z = `x`") 29 | 30 | # expected stdout: 31 | # x = 123, y = 456, z = 789 32 | # x = 123, y = 456, z = 789 33 | # x = 123, y = 456, z = 789 34 | # y = 456 35 | # y = 456, z = 789 36 | -------------------------------------------------------------------------------- /tests/Tc1.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | type T[a]: 4 | field: a 5 | 6 | test(): T[I32] 7 | T(field = 123) 8 | 9 | # args: --typecheck --no-prelude 10 | -------------------------------------------------------------------------------- /tests/Tc2.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | trait T[a]: 4 | getI32(self: a): I32 5 | 6 | type A: 7 | i: I32 8 | 9 | A.getI32(self: A): I32 10 | self.i 11 | 12 | # args: --typecheck --no-prelude 13 | -------------------------------------------------------------------------------- /tests/Tc3.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | trait T[a]: 4 | test(self: a): () 5 | 6 | getI32(self: a): I32 7 | 0 8 | 9 | type A: 10 | i: I32 11 | 12 | impl T[A]: 13 | test(self: A): () 14 | self.getI32() 15 | () 16 | 17 | # args: --typecheck --no-prelude 18 | -------------------------------------------------------------------------------- /tests/Tc4.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | prim printI32(i: I32) 4 | 5 | type Option[t]: 6 | Some(t) 7 | None 8 | 9 | test() 10 | let x = Option.Some(123) 11 | match x: 12 | Option.Some(a): printI32(a) 13 | Option.None: () 14 | 15 | # args: --typecheck --no-prelude 16 | -------------------------------------------------------------------------------- /tests/Tc5.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | type Bool: 4 | False 5 | True 6 | 7 | test(): I32 8 | if Bool.True: 9 | return 123 10 | 11 | 456 12 | 13 | # args: --typecheck --no-prelude 14 | -------------------------------------------------------------------------------- /tests/TcFail1.fir: -------------------------------------------------------------------------------- 1 | prim type Str 2 | prim type I32 3 | 4 | trait T[a]: 5 | toStr(self: a): Str 6 | 7 | impl T[Str]: 8 | toStr(self: Str): I32 9 | 123 10 | 11 | # args: --typecheck --no-prelude --no-backtrace 12 | # expected exit status: 101 13 | # expected stderr: 14 | # tests/TcFail1.fir:8:5: Trait method implementation of toStr does not match the trait method type 15 | # Trait method type: [?exn#0: *] Fn(Str): ?exn#0 Str 16 | # Implementation method type: [?exn: *] Fn(Str): ?exn I32 17 | -------------------------------------------------------------------------------- /tests/TcFail2.fir: -------------------------------------------------------------------------------- 1 | prim type Str 2 | prim type I32 3 | 4 | type Bool: 5 | False 6 | True 7 | 8 | Bool.toStr(self): Str 9 | Bool.False 10 | 11 | # args: --typecheck --no-prelude --no-backtrace 12 | # expected stderr: tests/TcFail2.fir:9:5: Unable to unify types Bool and Str 13 | -------------------------------------------------------------------------------- /tests/TcFail3.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let f = Vec.len 3 | 4 | let vec1 = Vec.withCapacity(10u32) 5 | vec1.push("a") 6 | vec1.push("b") 7 | 8 | let vec2 = Vec.withCapacity(20u32) 9 | vec2.push(123i32) 10 | 11 | printStr(f(vec1).toStr()) 12 | printStr(f(vec2).toStr()) 13 | 14 | # args: --no-backtrace 15 | # expected stderr: tests/TcFail3.fir:12:16: Unable to unify types I32 and Str 16 | -------------------------------------------------------------------------------- /tests/TcFail4.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let f = Vec.withCapacity 3 | 4 | let vec1 = f(10u32) 5 | vec1.push("a") 6 | vec1.push("b") 7 | 8 | let vec2 = f(20u32) 9 | vec2.push(123i32) 10 | 11 | # args: --no-backtrace 12 | # expected stderr: tests/TcFail4.fir:9:15: Unable to unify types I32 and Str 13 | -------------------------------------------------------------------------------- /tests/ThrowNonVariant.fir: -------------------------------------------------------------------------------- 1 | test(): U32 Str 2 | throw(123u32) 3 | 4 | main() 5 | printStr(try(test).toStr()) 6 | 7 | let x: Result[U32, U32] = try({ throw(456u32) }) 8 | printStr(x.toStr()) 9 | 10 | # expected stdout: 11 | # Result.Err(123) 12 | # Result.Err(456) 13 | -------------------------------------------------------------------------------- /tests/ThrowingIter.fir: -------------------------------------------------------------------------------- 1 | # Code for the blog post: https://osa1.net/posts/2025-04-17-throwing-iterators-fir.html 2 | 3 | sum(nums: Vec[U32]): U32 4 | let result: U32 = 0 5 | for i: U32 in nums.iter(): 6 | result += i 7 | result 8 | 9 | parseSum(nums: Vec[Str]): U32 10 | let result: U32 = 0 11 | for i: U32 in nums.iter().map(parseU32): 12 | result += i 13 | result 14 | 15 | parseSumPropagateErrors(nums: Vec[Str]): [Overflow, EmptyInput, InvalidDigit, ..errs] U32 16 | let result: U32 = 0 17 | for i: U32 in nums.iter().map(parseU32Exn): 18 | result += i 19 | result 20 | 21 | parseSumHandleInvalidDigits(nums: Vec[Str]): [Overflow, EmptyInput, ..errs] U32 22 | let result: U32 = 0 23 | for i: U32 in nums.iter().map(parseU32Exn).mapResult(handleInvalidDigit): 24 | result += i 25 | result 26 | 27 | parseSumHandleInvalidDigitsLogRest(nums: Vec[Str]): U32 28 | let result: U32 = 0 29 | for i: Result[[Overflow, EmptyInput], U32] in nums.iter().map(parseU32Exn).mapResult(handleInvalidDigit).try(): 30 | match i: 31 | Result.Err(~Overflow): print("Overflow") 32 | Result.Err(~EmptyInput): print("Empty input") 33 | Result.Ok(i): result += i 34 | result 35 | 36 | main() 37 | let nums: Vec[U32] = Vec.withCapacity(10) 38 | nums.push(1) 39 | nums.push(2) 40 | nums.push(3) 41 | print(sum(nums)) 42 | 43 | let strs: Vec[Str] = Vec.withCapacity(10) 44 | strs.push("1") 45 | strs.push("2") 46 | strs.push("3") 47 | print(parseSum(strs)) 48 | 49 | strs.push("a") 50 | match try({ parseSumPropagateErrors(strs) }): 51 | Result.Ok(sum): print(sum) 52 | Result.Err(~Overflow): print("Overflow") 53 | Result.Err(~EmptyInput): print("EmptyInput") 54 | Result.Err(~InvalidDigit): print("InvalidDigit") 55 | 56 | match try({ parseSumHandleInvalidDigits(strs) }): 57 | Result.Ok(sum): print(sum) 58 | Result.Err(~Overflow): print("Overflow") 59 | Result.Err(~EmptyInput): print("EmptyInput") 60 | 61 | strs.push("") 62 | strs.push("100000000000000000000000000000000") 63 | strs.push("4") 64 | 65 | print(parseSumHandleInvalidDigitsLogRest(strs)) 66 | 67 | #################################################################################################### 68 | # 69 | # Utils and helpers 70 | # 71 | #################################################################################################### 72 | 73 | handleInvalidDigit(parseResult: Result[[InvalidDigit, ..errs], Option[U32]]): [..errs] Option[U32] 74 | match parseResult: 75 | Result.Ok(result): result 76 | Result.Err(~InvalidDigit): Option.Some(0u32) 77 | Result.Err(other): throw(other) 78 | 79 | parseU32(s: Str): U32 80 | if s.len() == 0: 81 | panic("Empty input") 82 | 83 | let result: U32 = 0 84 | 85 | for c: Char in s.chars(): 86 | if c < '0' || c > '9': 87 | panic("Invalid digit") 88 | 89 | let digit = c.asU32() - '0'.asU32() 90 | 91 | result *= 10 92 | result += digit 93 | 94 | result 95 | 96 | parseU32Exn(s: Str): [InvalidDigit, Overflow, EmptyInput, ..r] U32 97 | if s.len() == 0: 98 | throw(~EmptyInput) 99 | 100 | let result: U32 = 0 101 | 102 | for c: Char in s.chars(): 103 | if c < '0' || c > '9': 104 | throw(~InvalidDigit) 105 | 106 | let digit = c.asU32() - '0'.asU32() 107 | 108 | result = match checkedMul(result, 10): 109 | Option.None: throw(~Overflow) 110 | Option.Some(newResult): newResult 111 | 112 | result = match checkedAdd(result, digit): 113 | Option.None: throw(~Overflow) 114 | Option.Some(newResult): newResult 115 | 116 | result 117 | 118 | checkedAdd(i1: U32, i2: U32): Option[U32] 119 | if i1 > 4294967295u32 - i2: 120 | return Option.None 121 | 122 | Option.Some(i1 + i2) 123 | 124 | checkedMul(i1: U32, i2: U32): Option[U32] 125 | let result = i1 * i2 126 | 127 | if i1 != 0 && result / i1 != i2: 128 | return Option.None 129 | 130 | Option.Some(result) 131 | 132 | #################################################################################################### 133 | # 134 | # Test output 135 | # 136 | #################################################################################################### 137 | 138 | # NB. The first two lines should disappear with #49. 139 | 140 | # expected stdout: 141 | # 6 142 | # 6 143 | # InvalidDigit 144 | # 6 145 | # Empty input 146 | # Overflow 147 | # 10 148 | 149 | # expected stderr: 150 | # tests/ThrowingIter.fir:50:5: Unexhaustive pattern match 151 | # tests/ThrowingIter.fir:56:5: Unexhaustive pattern match 152 | -------------------------------------------------------------------------------- /tests/ThrowingIter1.fir: -------------------------------------------------------------------------------- 1 | checkedAdd(i1: U32, i2: U32): Option[U32] 2 | if i1 > 4294967295u32 - i2: 3 | return Option.None 4 | 5 | return Option.Some(i1 + i2) 6 | 7 | checkedMul(i1: U32, i2: U32): Option[U32] 8 | let result = i1 * i2 9 | 10 | if i1 != 0 && result / i1 != i2: 11 | return Option.None 12 | 13 | return Option.Some(result) 14 | 15 | parseU32Exn(s: Str): [InvalidDigit, Overflow, EmptyInput, ..r] U32 16 | if s.len() == 0: 17 | throw(~EmptyInput) 18 | 19 | let result: U32 = 0 20 | 21 | for c: Char in s.chars(): 22 | if c < '0' || c > '9': 23 | throw(~InvalidDigit) 24 | 25 | let digit = c.asU32() - '0'.asU32() 26 | 27 | result = match checkedMul(result, 10): 28 | Option.None: throw(~Overflow) 29 | Option.Some(newResult): newResult 30 | 31 | result = match checkedAdd(result, digit): 32 | Option.None: throw(~Overflow) 33 | Option.Some(newResult): newResult 34 | 35 | result 36 | 37 | main() 38 | let strings: Vec[Str] = Vec.withCapacity(10) 39 | strings.push("1") 40 | strings.push("2") 41 | strings.push("3") 42 | strings.push("4b") 43 | 44 | let ret = try(fn(): [EmptyInput, InvalidDigit, Overflow] () { 45 | for x: U32 in strings.iter().map(parseU32Exn): 46 | printStr(x.toStr()) 47 | }) 48 | 49 | match ret: 50 | Result.Ok(()) | Result.Err(~EmptyInput) | Result.Err(~Overflow): printStr("WAT") 51 | Result.Err(~InvalidDigit): printStr("OK") 52 | 53 | # expected stdout: 54 | # 1 55 | # 2 56 | # 3 57 | # OK 58 | -------------------------------------------------------------------------------- /tests/ThrowingIter2.fir: -------------------------------------------------------------------------------- 1 | checkedAdd(i1: U32, i2: U32): Option[U32] 2 | if i1 > 4294967295u32 - i2: 3 | return Option.None 4 | 5 | return Option.Some(i1 + i2) 6 | 7 | checkedMul(i1: U32, i2: U32): Option[U32] 8 | let result = i1 * i2 9 | 10 | if i1 != 0 && result / i1 != i2: 11 | return Option.None 12 | 13 | return Option.Some(result) 14 | 15 | parseU32Exn(s: Str): [InvalidDigit, Overflow, EmptyInput, ..r] U32 16 | if s.len() == 0: 17 | throw(~EmptyInput) 18 | 19 | let result: U32 = 0 20 | 21 | for c: Char in s.chars(): 22 | if c < '0' || c > '9': 23 | throw(~InvalidDigit) 24 | 25 | let digit = c.asU32() - '0'.asU32() 26 | 27 | result = match checkedMul(result, 10): 28 | Option.None: throw(~Overflow) 29 | Option.Some(newResult): newResult 30 | 31 | result = match checkedAdd(result, digit): 32 | Option.None: throw(~Overflow) 33 | Option.Some(newResult): newResult 34 | 35 | result 36 | 37 | handleInvalidDigit(parseResult: Result[[InvalidDigit, ..errs], Option[U32]]): [..errs] Option[U32] 38 | match parseResult: 39 | Result.Ok(result): result 40 | Result.Err(~InvalidDigit): Option.Some(0u32) 41 | Result.Err(other): throw(other) 42 | 43 | main() 44 | let strings: Vec[Str] = Vec.withCapacity(10) 45 | strings.push("1") 46 | strings.push("2") 47 | strings.push("3") 48 | strings.push("4b") 49 | strings.push("") 50 | 51 | let ret = try(fn(): [EmptyInput, Overflow] () { 52 | for x: U32 in strings.iter().map(parseU32Exn).mapResult(handleInvalidDigit): 53 | printStr(x.toStr()) 54 | }) 55 | 56 | match ret: 57 | Result.Err(~EmptyInput): printStr("EmptyInput") 58 | Result.Err(~Overflow): printStr("Overflow") 59 | Result.Ok(()): printStr("OK") 60 | 61 | # expected stdout: 62 | # 1 63 | # 2 64 | # 3 65 | # 0 66 | # EmptyInput 67 | -------------------------------------------------------------------------------- /tests/ThrowingIter3.fir: -------------------------------------------------------------------------------- 1 | checkedAdd(i1: U32, i2: U32): Option[U32] 2 | if i1 > 4294967295u32 - i2: 3 | return Option.None 4 | 5 | return Option.Some(i1 + i2) 6 | 7 | checkedMul(i1: U32, i2: U32): Option[U32] 8 | let result = i1 * i2 9 | 10 | if i1 != 0 && result / i1 != i2: 11 | return Option.None 12 | 13 | return Option.Some(result) 14 | 15 | parseU32Exn(s: Str): [InvalidDigit, Overflow, EmptyInput, ..r] U32 16 | if s.len() == 0: 17 | throw(~EmptyInput) 18 | 19 | let result: U32 = 0 20 | 21 | for c: Char in s.chars(): 22 | if c < '0' || c > '9': 23 | throw(~InvalidDigit) 24 | 25 | let digit = c.asU32() - '0'.asU32() 26 | 27 | result = match checkedMul(result, 10): 28 | Option.None: throw(~Overflow) 29 | Option.Some(newResult): newResult 30 | 31 | result = match checkedAdd(result, digit): 32 | Option.None: throw(~Overflow) 33 | Option.Some(newResult): newResult 34 | 35 | result 36 | 37 | handleInvalidDigit(parseResult: Result[[InvalidDigit, ..errs], Option[U32]]): [..errs] Option[U32] 38 | match parseResult: 39 | Result.Ok(result): result 40 | Result.Err(~InvalidDigit): Option.Some(0u32) 41 | Result.Err(other): throw(other) 42 | 43 | main() 44 | let strings: Vec[Str] = Vec.withCapacity(10) 45 | strings.push("1") 46 | strings.push("2") 47 | strings.push("3") 48 | strings.push("4b") 49 | strings.push("") 50 | 51 | for x: Result[[Overflow, EmptyInput], U32] in strings.iter().map(parseU32Exn).mapResult(handleInvalidDigit).try(): 52 | match x: 53 | Result.Err(~Overflow): printStr("Overflow") 54 | Result.Err(~EmptyInput): printStr("EmptyInput") 55 | Result.Ok(value): printStr(value.toStr()) 56 | 57 | parseSumHandleInvalidDigitsLogRest(nums: Vec[Str]): U32 58 | let result: U32 = 0 59 | for i: Result[[Overflow, EmptyInput], U32] in nums.iter().map(parseU32Exn).mapResult(handleInvalidDigit).try(): 60 | match i: 61 | Result.Err(~Overflow): printStr("Overflow") 62 | Result.Err(~EmptyInput): printStr("Empty input") 63 | Result.Ok(i): result += i 64 | result 65 | 66 | # expected stdout: 67 | # 1 68 | # 2 69 | # 3 70 | # 0 71 | # EmptyInput 72 | -------------------------------------------------------------------------------- /tests/TraitImplFail1.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | trait T[a]: 4 | a(self: a) 5 | b(self: a) 6 | 7 | impl T[I32]: 8 | a(self: I32) = () 9 | 10 | # args: --typecheck --no-prelude --no-backtrace 11 | # expected stderr: tests/TraitImplFail1.fir:7:1: Trait methods missing: ["b"] 12 | -------------------------------------------------------------------------------- /tests/TraitImplFail2.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | trait T[a]: 4 | a(self: a) 5 | 6 | impl T[I32]: 7 | a(self: a) = () 8 | b(self: a) = () 9 | 10 | # args: --typecheck --no-prelude --no-backtrace 11 | # expected exit status: 101 12 | # expected stderr: 13 | # tests/TraitImplFail2.fir:7:5: Trait method implementation of a does not match the trait method type 14 | # Trait method type: [?exn#0: *] Fn(I32): ?exn#0 () 15 | # Implementation method type: [a: *, ?exn: *] Fn(a): ?exn () 16 | -------------------------------------------------------------------------------- /tests/TraitImplFail4.fir: -------------------------------------------------------------------------------- 1 | prim type I32 2 | 3 | trait T[a]: 4 | a(self: a) 5 | 6 | impl T[I32]: 7 | a(self: I32): I32 = 123 8 | 9 | # args: --typecheck --no-prelude --no-backtrace 10 | # expected exit status: 101 11 | # expected stderr: 12 | # tests/TraitImplFail4.fir:7:5: Trait method implementation of a does not match the trait method type 13 | # Trait method type: [?exn#0: *] Fn(I32): ?exn#0 () 14 | # Implementation method type: [?exn: *] Fn(I32): ?exn I32 15 | -------------------------------------------------------------------------------- /tests/TraitImplSelfTyFail.fir: -------------------------------------------------------------------------------- 1 | trait Test[a]: 2 | fun(self): () 3 | 4 | # args: --typecheck --no-backtrace 5 | # expected stderr: tests/TraitImplSelfTyFail.fir:2:5: Trait methods can't have implicit self type 6 | -------------------------------------------------------------------------------- /tests/TraitMethodTyVarSubst.fir: -------------------------------------------------------------------------------- 1 | trait Test[a, b]: 2 | test(self: a): b 3 | 4 | type AB[a, b]: 5 | a: a 6 | b: b 7 | 8 | impl Test[AB[b, a], a]: 9 | test(self: AB[b, a]): a 10 | self.b 11 | 12 | main() 13 | printStr(AB(a = 1, b = "Hi").test()) 14 | 15 | # expected stdout: Hi 16 | -------------------------------------------------------------------------------- /tests/TraitVariantTyParamKind.fir: -------------------------------------------------------------------------------- 1 | trait Foo[a, errs1]: 2 | foo(self: a): [..errs1] () 3 | 4 | impl Foo[U32, errs2]: 5 | foo(self: U32): [..errs2] () 6 | printStr("Hi") 7 | 8 | main() = 123u32.foo() 9 | 10 | # expected stdout: Hi 11 | -------------------------------------------------------------------------------- /tests/Traits1.fir: -------------------------------------------------------------------------------- 1 | trait Test[t]: 2 | test(self: t) 3 | 4 | impl Test[U32]: 5 | test(self: U32) 6 | printStr("U32.test") 7 | 8 | main() 9 | 123u32.test() 10 | 11 | prim type Str 12 | prim type U32 13 | prim printStr(s: Str) 14 | 15 | # args: --typecheck --no-prelude 16 | -------------------------------------------------------------------------------- /tests/Traits2.fir: -------------------------------------------------------------------------------- 1 | trait Test[t1, t2]: 2 | test(self: t1): t2 3 | 4 | impl Test[U32, U32]: 5 | test(self: U32): U32 6 | self 7 | 8 | main() 9 | let x: U32 = 123u32.test() 10 | 11 | prim type Str 12 | prim type U32 13 | prim printStr(s: Str) 14 | 15 | # args: --typecheck --no-prelude 16 | -------------------------------------------------------------------------------- /tests/Traits3.fir: -------------------------------------------------------------------------------- 1 | trait ToStr[a]: 2 | toStr(self: a): Str 3 | 4 | f[ToStr[a]](a: a) 5 | printStr(a.toStr()) 6 | 7 | prim type Str 8 | prim type I32 9 | prim printStr(s: Str): () 10 | 11 | # args: --typecheck --no-prelude 12 | -------------------------------------------------------------------------------- /tests/Traits4.fir: -------------------------------------------------------------------------------- 1 | trait ToStr[a]: 2 | toStr(self: a): Str 3 | 4 | type A 5 | 6 | A.f[ToStr[a]](self, a: a) 7 | printStr(a.toStr()) 8 | 9 | prim type Str 10 | prim type I32 11 | prim printStr(s: Str): () 12 | 13 | # args: --typecheck --no-prelude 14 | -------------------------------------------------------------------------------- /tests/TraitsFail1.fir: -------------------------------------------------------------------------------- 1 | prim type Str 2 | prim type I32 3 | 4 | prim printStr(s: Str): () 5 | 6 | trait ToStr[a]: 7 | toStr(self: a): Str 8 | 9 | f(a: a) 10 | printStr(a.toStr()) 11 | 12 | # args: --typecheck --no-prelude --no-backtrace 13 | # expected exit status: 101 14 | # expected stderr: tests/TraitsFail1.fir:10:14: Unable to resolve pred ToStr[a] 15 | -------------------------------------------------------------------------------- /tests/TraitsFail2.fir: -------------------------------------------------------------------------------- 1 | trait ToStr[a]: 2 | toStr(self: a): Str 3 | 4 | f[ToStr[a]](a: a) 5 | printStr(a.toStr()) 6 | 7 | g() = f(123u32) 8 | 9 | prim type Str 10 | prim type I32 11 | prim printStr(s: Str): () 12 | 13 | # args: --typecheck --no-prelude --no-backtrace 14 | # expected exit status: 101 15 | # expected stderr: tests/TraitsFail2.fir:7:7: Unable to resolve pred ToStr[U32] 16 | -------------------------------------------------------------------------------- /tests/TraitsFail3.fir: -------------------------------------------------------------------------------- 1 | type T: 2 | i: I32 3 | 4 | impl I32[T]: 5 | test(self: T) = () 6 | 7 | prim type I32 8 | 9 | # args: --no-prelude --typecheck --no-backtrace 10 | # expected exit status: 101 11 | # expected stderr: tests/TraitsFail3.fir:4:1: I32 in impl declararation is not a trait 12 | 13 | -------------------------------------------------------------------------------- /tests/TraitsFail4.fir: -------------------------------------------------------------------------------- 1 | prim type U32 2 | 3 | trait Test[a, b]: 4 | method(self: a, other: b) 5 | 6 | impl Test[U32]: 7 | method(self: U32, other: b) 8 | () 9 | 10 | # args: --no-prelude --typecheck --no-backtrace 11 | # expected exit status: 101 12 | # expected stderr: tests/TraitsFail4.fir:6:1: Trait Test takes 2 type arguments, but impl passes 1 13 | -------------------------------------------------------------------------------- /tests/UnificationRowKind.fir: -------------------------------------------------------------------------------- 1 | type Test[varRowVar, recRowRec]: 2 | variant: [..varRowVar] 3 | record: (..recRowRec) 4 | 5 | main() 6 | let x: Test[row[Foo], row(a: U32)] = Test(variant = ~Foo, record = (a = 123u32)) 7 | 8 | match x.variant: 9 | ~Foo: printStr("OK") 10 | 11 | printStr(x.record.a.toStr()) 12 | 13 | # expected stdout: 14 | # OK 15 | # 123 16 | -------------------------------------------------------------------------------- /tests/UnificationRowKindFail1.fir: -------------------------------------------------------------------------------- 1 | type Test[varRowVar]: 2 | variant: [..varRowVar] 3 | 4 | main() 5 | let x: Test[[Foo]] = Test(variant = ~Foo) 6 | 7 | # args: --typecheck --no-backtrace 8 | # expected exit status: 101 9 | # expected stderr: 10 | # tests/UnificationRowKindFail1.fir:5:26: Unable to unify var with kind row(variant) with type with kind * 11 | -------------------------------------------------------------------------------- /tests/UnificationRowKindFail2.fir: -------------------------------------------------------------------------------- 1 | type Test[recRowRec]: 2 | record: (..recRowRec) 3 | 4 | main() 5 | let x: Test[(a: U32)] = Test(record = (a = 123u32)) 6 | 7 | # args: --typecheck --no-backtrace 8 | # expected exit status: 101 9 | # expected stderr: 10 | # tests/UnificationRowKindFail2.fir:5:29: Unable to unify var with kind row(record) with type with kind * 11 | -------------------------------------------------------------------------------- /tests/UnusedTyParam.fir: -------------------------------------------------------------------------------- 1 | # Not sure if we want to support unused type parameters.. for now this works and 2 | # we don't want to break this *accidentally*, only deliberately. 3 | 4 | test1[ToStr[t]]() = printStr("Hi") 5 | 6 | test2[RecRow[t]]() = printStr("Hi") 7 | 8 | main() = () 9 | -------------------------------------------------------------------------------- /tests/VariantTypeRefinement.fir: -------------------------------------------------------------------------------- 1 | f1(args: [E1, E2, E3]): [E2, E3] 2 | match args: 3 | ~E1: ~E2 4 | ~E2: ~E3 5 | ~E3: ~E3 6 | 7 | f2[VarRow[r]](args: [E1, ..r]): Option[[..r]] 8 | match args: 9 | ~E1: Option.None 10 | other: Option.Some(other) 11 | 12 | main() 13 | f1(~E1) 14 | f2(~E2) 15 | () 16 | -------------------------------------------------------------------------------- /tests/Vec1.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let test = Vec.withCapacity(10u32) 3 | 4 | for i: U32 in range(0u32, 20): 5 | test.push(i) 6 | 7 | for i: U32 in range(0u32, test.len()): 8 | printStr(test.get(i).toStr()) 9 | 10 | # expected stdout: 11 | # 0 12 | # 1 13 | # 2 14 | # 3 15 | # 4 16 | # 5 17 | # 6 18 | # 7 19 | # 8 20 | # 9 21 | # 10 22 | # 11 23 | # 12 24 | # 13 25 | # 14 26 | # 15 27 | # 16 28 | # 17 29 | # 18 30 | # 19 31 | -------------------------------------------------------------------------------- /tests/VecGrowFromZero.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let vec: Vec[U32] = Vec.withCapacity(0) 3 | vec.push(123) 4 | print(vec) 5 | 6 | # expected stdout: [123] 7 | -------------------------------------------------------------------------------- /tests/VecIter.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let vec: Vec[U32] = Vec.withCapacity(10) 3 | vec.push(1) 4 | vec.push(2) 5 | vec.push(3) 6 | for i: U32 in vec.iter(): 7 | printStr(i.toStr()) 8 | printStr(vec.pop().toStr()) 9 | 10 | # expected stdout: 11 | # 1 12 | # Option.Some(3) 13 | # 2 14 | # Option.Some(2) 15 | -------------------------------------------------------------------------------- /tests/VecSort.fir: -------------------------------------------------------------------------------- 1 | main() 2 | for i: U32 in irange(0u32, 5): 3 | test(i) 4 | 5 | test(len: U32) 6 | let vec = Vec.withCapacity(len) 7 | for i: U32 in range(0u32, len): 8 | vec.push(len - i) 9 | vec.sort() 10 | printStr(vec.toStr()) 11 | 12 | # expected stdout: 13 | # [] 14 | # [1] 15 | # [1,2] 16 | # [1,2,3] 17 | # [1,2,3,4] 18 | # [1,2,3,4,5] 19 | -------------------------------------------------------------------------------- /tests/WhileLetLoop.fir: -------------------------------------------------------------------------------- 1 | main() 2 | let v: Vec[U32] = Vec.withCapacity(10) 3 | v.push(123u32) 4 | v.push(456u32) 5 | v.push(789u32) 6 | 7 | let iter = v.iter() 8 | let next: Fn(): [] Option[U32] = iter.next 9 | while next() is Option.Some(next): 10 | printStr(next.toStr()) 11 | 12 | # expected stdout: 13 | # 123 14 | # 456 15 | # 789 16 | -------------------------------------------------------------------------------- /tests/WhileLoop.fir: -------------------------------------------------------------------------------- 1 | fac(n: I32): I32 2 | let ret = 1 3 | while n != 0: 4 | ret *= n 5 | n -= 1 6 | ret 7 | 8 | main() 9 | printStr(fac(0).toStr()) 10 | printStr(fac(5).toStr()) 11 | printStr(fac(10).toStr()) 12 | 13 | # expected stdout: 14 | # 1 15 | # 120 16 | # 3628800 17 | --------------------------------------------------------------------------------