├── .gitignore ├── crates ├── vulpi-tests │ ├── suite │ │ ├── layout_parsing.vp │ │ ├── expr.vp │ │ ├── tuple.vp │ │ ├── unicode_escape.vp │ │ ├── pipe.vp │ │ ├── handler.vp │ │ ├── modules.vp │ │ ├── when.vp │ │ ├── unicode_escape.expect │ │ ├── abstract.vp │ │ ├── records.vp │ │ ├── algebraic.vp │ │ ├── tuple.expect │ │ ├── expressions.vp │ │ ├── records.expect │ │ ├── expr.expect │ │ ├── when.expect │ │ ├── effects.vp │ │ ├── layout_parsing.expect │ │ ├── modules.expect │ │ ├── handler.expect │ │ ├── pipe.expect │ │ ├── effects.expect │ │ ├── abstract.expect │ │ └── expressions.expect │ ├── tests │ │ └── suite.rs │ ├── src │ │ ├── util.rs │ │ └── lib.rs │ └── Cargo.toml ├── vulpi-syntax │ ├── src │ │ ├── lib.rs │ │ ├── concrete │ │ │ ├── literal.rs │ │ │ ├── kind.rs │ │ │ ├── statements.rs │ │ │ ├── pattern.rs │ │ │ ├── type.rs │ │ │ ├── mod.rs │ │ │ ├── expr.rs │ │ │ └── top_level.rs │ │ ├── lambda.rs │ │ ├── elaborated.rs │ │ └── tokens.rs │ └── Cargo.toml ├── vulpi-show │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── vulpi-location │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── vulpi-ir │ ├── src │ │ ├── lib.rs │ │ ├── uncurry.rs │ │ └── dead_code.rs │ └── Cargo.toml ├── vulpi-vfs │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── path.rs ├── vulpi-report │ ├── Cargo.toml │ └── src │ │ ├── hash.rs │ │ ├── renderer │ │ ├── mod.rs │ │ └── classic.rs │ │ └── lib.rs ├── vulpi-intern │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── no_rc.rs ├── vulpi-macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── vulpi-typer │ ├── src │ │ ├── check │ │ │ ├── mod.rs │ │ │ ├── expr.rs │ │ │ └── pat.rs │ │ ├── infer │ │ │ ├── kind.rs │ │ │ ├── mod.rs │ │ │ ├── literal.rs │ │ │ ├── type.rs │ │ │ └── pat.rs │ │ ├── module.rs │ │ ├── errors.rs │ │ ├── eval.rs │ │ └── context.rs │ └── Cargo.toml ├── vulpi-lexer │ ├── Cargo.toml │ └── src │ │ ├── error.rs │ │ └── literals.rs ├── vulpi-cli │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── vulpi-parser │ ├── Cargo.toml │ └── src │ │ ├── error.rs │ │ ├── literal.rs │ │ ├── identifier.rs │ │ ├── pattern.rs │ │ ├── type.rs │ │ └── lib.rs ├── vulpi-js │ └── Cargo.toml ├── vulpi-resolver │ ├── Cargo.toml │ └── src │ │ ├── error.rs │ │ ├── dependencies.rs │ │ └── cycle.rs └── vulpi-build │ ├── Cargo.toml │ └── src │ ├── real.rs │ └── lib.rs ├── .vscode └── settings.json ├── images └── logo.png ├── .idea ├── vcs.xml └── workspace.xml ├── Cargo.toml ├── example ├── Elements.vp ├── Main.vp ├── List.vp ├── Prelude.vp ├── Bindings.vp └── DOM.vp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/layout_parsing.vp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/expr.vp: -------------------------------------------------------------------------------- 1 | let main : Int = 2 + 3 * 4 -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/tuple.vp: -------------------------------------------------------------------------------- 1 | let tuple : (String, Int) = ("Ata", 2) 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.intelliSenseEngineFallback": "enabled" 3 | } -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/unicode_escape.vp: -------------------------------------------------------------------------------- 1 | let main : String = "ata\n\"teste ザ ワールド" -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algebraic-dev/vulpi/HEAD/images/logo.png -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod r#abstract; 2 | pub mod concrete; 3 | pub mod elaborated; 4 | pub mod lambda; 5 | pub mod tokens; 6 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/pipe.vp: -------------------------------------------------------------------------------- 1 | let inc (n: Int) : Int = n + 1 2 | 3 | let main : Int = 4 | 1 5 | |> inc 6 | |> inc 7 | |> inc -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/handler.vp: -------------------------------------------------------------------------------- 1 | let logToStdout! = 2 | cases 3 | { Log.log e } => do 4 | a 5 | b 6 | { Log.log e } => other 7 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/modules.vp: -------------------------------------------------------------------------------- 1 | mod MyOwn where 2 | type Result a b = 3 | | Ok a 4 | | Err b 5 | 6 | let main : Result Int Int = MyOwn.Result 2 3 -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/when.vp: -------------------------------------------------------------------------------- 1 | let ok : Int = 2 | when 2 is 3 | 2 | 3 if a == 2 => 1 4 | 1 => 0 5 | _ => 2 6 | 7 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/unicode_escape.expect: -------------------------------------------------------------------------------- 1 | suite/unicode_escape.vp:1:12: name not found: 2 | 3 | 1 | let main : String = "ata\n\"teste ザ ワールド" 4 | | ^^^^^^ 5 | 6 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/abstract.vp: -------------------------------------------------------------------------------- 1 | pub mod Ata where 2 | pub type A 3 | pub type B 4 | 5 | mod Beta where 6 | type C 7 | use Self.Ata 8 | 9 | let ata (x: A) (y: B) : C = 2 -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /crates/vulpi-tests/tests/suite.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_test_frameworks)] 2 | #![test_runner(vulpi_tests::test_runner)] 3 | 4 | use vulpi_tests::test; 5 | 6 | test!("/suite", |_file_name| { todo!() }); 7 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/records.vp: -------------------------------------------------------------------------------- 1 | type User (t: * -> *) = { 2 | name : String, 3 | data : t Int 4 | } 5 | 6 | let main = do 7 | let user = User { name = "ata", data = 2 } 8 | 9 | let updated = user { name = "lel" } -------------------------------------------------------------------------------- /crates/vulpi-show/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-show" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/algebraic.vp: -------------------------------------------------------------------------------- 1 | type Result a b = 2 | | Ok a 3 | | Err b 4 | 5 | type True 6 | type False 7 | 8 | type Is a = 9 | | T : Is True 10 | | F : Is False 11 | 12 | let main : Is True = Is.T 13 | 14 | 15 | -------------------------------------------------------------------------------- /crates/vulpi-location/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-location" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | vulpi-show = { path = "../vulpi-show" } 10 | -------------------------------------------------------------------------------- /crates/vulpi-ir/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is the module for the IR representation of the language. This is used to lower the AST into 2 | //! a form that is easier to work with for code generation. 3 | 4 | pub mod transform; 5 | pub mod pattern; 6 | pub mod inline; 7 | pub mod dead_code; 8 | pub mod uncurry; 9 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/tuple.expect: -------------------------------------------------------------------------------- 1 | suite/tuple.vp:1:22: name not found: 2 | 3 | 1 | let tuple : (String, Int) = ("Ata", 2) 4 | | ^^^ 5 | 6 | suite/tuple.vp:1:14: name not found: 7 | 8 | 1 | let tuple : (String, Int) = ("Ata", 2) 9 | | ^^^^^^ 10 | 11 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/expressions.vp: -------------------------------------------------------------------------------- 1 | pub mod Prelude where 2 | pub type Int 3 | 4 | pub type Shake = 5 | | True 6 | | False 7 | 8 | type Priv = 9 | | True Shake 10 | | False 11 | 12 | pub mod Ata where 13 | type Bool = 14 | | True 15 | | False 16 | 17 | let ata : Self.Shake = Bool.True 18 | 19 | -------------------------------------------------------------------------------- /crates/vulpi-vfs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-vfs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | filetime = "0.2.22" 10 | vulpi-location = { path = "../vulpi-location" } 11 | vulpi-intern = { path = "../vulpi-intern" } 12 | -------------------------------------------------------------------------------- /crates/vulpi-report/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-report" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | vulpi-location = { path = "../vulpi-location" } 10 | vulpi-vfs = { path = "../vulpi-vfs" } 11 | 12 | yansi = "0.5.1" 13 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/concrete/literal.rs: -------------------------------------------------------------------------------- 1 | use crate::tokens::Token; 2 | use vulpi_location::Spanned; 3 | use vulpi_macros::Show; 4 | 5 | #[derive(Show, Clone)] 6 | pub enum LiteralKind { 7 | String(Token), 8 | Integer(Token), 9 | Float(Token), 10 | Char(Token), 11 | Unit(Token), 12 | } 13 | 14 | pub type Literal = Spanned; 15 | -------------------------------------------------------------------------------- /crates/vulpi-tests/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::fs::DirEntry; 2 | 3 | /// Splits a dir entry into a name and it's extension. 4 | pub fn split_name(file: &DirEntry) -> (String, String) { 5 | let path = file.path(); 6 | ( 7 | path.file_prefix().unwrap().to_string_lossy().to_string(), 8 | path.extension().unwrap().to_string_lossy().to_string(), 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /crates/vulpi-intern/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-intern" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | vulpi-show = { path = "../vulpi-show" } 10 | 11 | lazy_static = "1.4.0" 12 | 13 | [features] 14 | default = ["single-shot"] 15 | single-shot = [] 16 | -------------------------------------------------------------------------------- /crates/vulpi-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [dependencies] 12 | convert_case = "0.6.0" 13 | proc-macro2 = "1.0.60" 14 | quote = "1.0.28" 15 | syn = { version = "2.0", features = ["full"] } 16 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/records.expect: -------------------------------------------------------------------------------- 1 | suite/records.vp:3:14: name not found: 2 | 3 | 1 | type User (t: * -> *) = { 4 | 2 | name : String, 5 | 3 | data : t Int 6 | | ^^^ 7 | 4 | } 8 | 9 | suite/records.vp:2:12: name not found: 10 | 11 | 1 | type User (t: * -> *) = { 12 | 2 | name : String, 13 | | ^^^^^^ 14 | 3 | data : t Int 15 | 16 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/concrete/kind.rs: -------------------------------------------------------------------------------- 1 | use vulpi_location::Spanned; 2 | use vulpi_macros::Show; 3 | 4 | use crate::tokens::Token; 5 | 6 | use super::{Parenthesis, Upper}; 7 | 8 | #[derive(Show, Clone)] 9 | pub enum KindType { 10 | Star(Token), 11 | Variable(Upper), 12 | Arrow(Box, Token, Box), 13 | Parenthesis(Parenthesis>), 14 | } 15 | 16 | pub type Kind = Spanned; 17 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/expr.expect: -------------------------------------------------------------------------------- 1 | suite/expr.vp:1:24: name not found: Operator 2 | 3 | 1 | let main : Int = 2 + 3 * 4 4 | | ^ 5 | 6 | suite/expr.vp:1:20: name not found: Operator 7 | 8 | 1 | let main : Int = 2 + 3 * 4 9 | | ^ 10 | 11 | suite/expr.vp:1:12: name not found: 12 | 13 | 1 | let main : Int = 2 + 3 * 4 14 | | ^^^ 15 | 16 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/check/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module defines the [Check] trait that is used to check the types of the expressions and patterns 2 | //! in the program. 3 | 4 | use crate::{Type, Virtual}; 5 | 6 | pub mod expr; 7 | pub mod pat; 8 | 9 | pub trait Check { 10 | type Return; 11 | type Context<'a>; 12 | 13 | fn check(&self, typ: Type, context: Self::Context<'_>) -> Self::Return; 14 | } 15 | -------------------------------------------------------------------------------- /crates/vulpi-lexer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-lexer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | 11 | vulpi-intern = { path = "../vulpi-intern" } 12 | vulpi-location = { path = "../vulpi-location" } 13 | vulpi-syntax = { path = "../vulpi-syntax" } 14 | vulpi-report = { path = "../vulpi-report" } 15 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-syntax" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | vulpi-location = { path = "../vulpi-location" } 11 | vulpi-intern = { path = "../vulpi-intern" } 12 | 13 | vulpi-show = { path = "../vulpi-show" } 14 | vulpi-macros = { path = "../vulpi-macros" } 15 | 16 | im-rc = "15.1.0" 17 | -------------------------------------------------------------------------------- /crates/vulpi-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | vulpi-build = { path = "../vulpi-build" } 11 | vulpi-report = { path = "../vulpi-report" } 12 | vulpi-vfs = { path = "../vulpi-vfs" } 13 | vulpi-intern = { path = "../vulpi-intern" } 14 | clap = { version = "4.4.8", features = ["derive"] } 15 | -------------------------------------------------------------------------------- /crates/vulpi-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-parser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | vulpi-report = { path = "../vulpi-report" } 10 | vulpi-location = { path = "../vulpi-location" } 11 | vulpi-intern = { path = "../vulpi-intern" } 12 | vulpi-syntax = { path = "../vulpi-syntax" } 13 | vulpi-lexer = { path = "../vulpi-lexer" } 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "crates/vulpi-intern", 5 | "crates/vulpi-lexer", 6 | "crates/vulpi-location", 7 | "crates/vulpi-macros", 8 | "crates/vulpi-parser", 9 | "crates/vulpi-report", 10 | "crates/vulpi-show", 11 | "crates/vulpi-syntax", 12 | "crates/vulpi-tests", 13 | "crates/vulpi-vfs", 14 | "crates/vulpi-resolver", 15 | "crates/vulpi-typer", 16 | "crates/vulpi-cli", 17 | "crates/vulpi-ir", 18 | ] 19 | 20 | resolver = "1" 21 | -------------------------------------------------------------------------------- /crates/vulpi-js/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-js" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | vulpi-syntax = { path = "../vulpi-syntax" } 10 | vulpi-ir = { path = "../vulpi-ir" } 11 | vulpi-intern = { path = "../vulpi-intern" } 12 | vulpi-location = { path = "../vulpi-location" } 13 | 14 | resast = "0.5.0" 15 | resw = "0.6.0-alpha.2" 16 | petgraph = "0.6.4" 17 | ressa = "0.8.2" 18 | -------------------------------------------------------------------------------- /crates/vulpi-typer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-typer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | vulpi-intern = { path = "../vulpi-intern" } 11 | vulpi-syntax = { path = "../vulpi-syntax" } 12 | vulpi-location = { path = "../vulpi-location" } 13 | vulpi-report = { path = "../vulpi-report" } 14 | vulpi-show = { path = "../vulpi-show" } 15 | vulpi-macros = { path = "../vulpi-macros" } 16 | im-rc = "15.1.0" 17 | -------------------------------------------------------------------------------- /crates/vulpi-ir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-ir" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | vulpi-intern = { path = "../vulpi-intern" } 10 | vulpi-syntax = { path = "../vulpi-syntax" } 11 | vulpi-typer = { path = "../vulpi-typer" } 12 | vulpi-macros = { path = "../vulpi-macros" } 13 | vulpi-show = { path = "../vulpi-show" } 14 | vulpi-location = { path = "../vulpi-location" } 15 | im-rc = "15.1.0" 16 | petgraph = "0.6.4" 17 | -------------------------------------------------------------------------------- /example/Elements.vp: -------------------------------------------------------------------------------- 1 | use Prelude 2 | use Yal.DOM 3 | use Yal.List 4 | 5 | pub let mk (tag: String) (attrs: List (Attribute msg)) (html: List (Html msg)) : Html msg = 6 | Html.Node (Node { tag = tag, attributes = attrs, children = html }) 7 | 8 | pub let div : List (Attribute msg) -> List (Html msg) -> Html msg = mk "div" 9 | pub let p : List (Attribute msg) -> List (Html msg) -> Html msg = mk "p" 10 | pub let button : List (Attribute msg) -> List (Html msg) -> Html msg = mk "button" 11 | pub let text : String -> Html msg = Html.Text -------------------------------------------------------------------------------- /crates/vulpi-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | 11 | vulpi-lexer = { path = "../vulpi-lexer" } 12 | vulpi-location = { path = "../vulpi-location" } 13 | vulpi-parser = { path = "../vulpi-parser" } 14 | vulpi-report = { path = "../vulpi-report" } 15 | vulpi-show = { path = "../vulpi-show" } 16 | vulpi-vfs = { path = "../vulpi-vfs" } 17 | vulpi-resolver = { path = "../vulpi-resolver" } 18 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/when.expect: -------------------------------------------------------------------------------- 1 | suite/when.vp:3:18: name not found: 2 | 3 | 1 | let ok : Int = 4 | 2 | when 2 is 5 | 3 | 2 | 3 if a == 2 => 1 6 | | ^ 7 | 4 | 1 => 0 8 | 9 | suite/when.vp:3:20: name not found: Operator 10 | 11 | 1 | let ok : Int = 12 | 2 | when 2 is 13 | 3 | 2 | 3 if a == 2 => 1 14 | | ^^ 15 | 4 | 1 => 0 16 | 17 | suite/when.vp:1:10: name not found: 18 | 19 | 1 | let ok : Int = 20 | | ^^^ 21 | 2 | when 2 is 22 | 23 | -------------------------------------------------------------------------------- /example/Main.vp: -------------------------------------------------------------------------------- 1 | use Prelude 2 | use Yal.Bindings 3 | use Yal.DOM 4 | use Yal.Elements 5 | use Yal.List 6 | 7 | -- App 8 | 9 | type Msg = 10 | | Increment 11 | | Decrement 12 | 13 | let update (x: Int) : Msg -> Int 14 | | Msg.Increment => x + 1 15 | | Msg.Decrement => x - 1 16 | 17 | let view (model: Int) : Html Msg = 18 | div [ Attribute.Id "pudim" ] 19 | [ button [Attribute.OnClick Msg.Increment] [text "Increment"] 20 | , p [] [text ("Count:" ++ intToString model)] 21 | , button [Attribute.OnClick Msg.Decrement] [text "Decrement"] 22 | ] 23 | 24 | let main = do 25 | start view update 0 -------------------------------------------------------------------------------- /crates/vulpi-resolver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-resolver" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | vulpi-intern = { path = "../vulpi-intern" } 11 | vulpi-syntax = { path = "../vulpi-syntax" } 12 | vulpi-location = { path = "../vulpi-location" } 13 | vulpi-report = { path = "../vulpi-report" } 14 | vulpi-show = { path = "../vulpi-show" } 15 | vulpi-macros = { path = "../vulpi-macros" } 16 | vulpi-vfs = { path = "../vulpi-vfs" } 17 | 18 | im-rc = "15.1.0" 19 | petgraph = "0.6.4" 20 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/effects.vp: -------------------------------------------------------------------------------- 1 | pub mod Prelude where 2 | pub type String 3 | pub type Request a b 4 | pub type Unit 5 | 6 | pub effect IO where 7 | run : () 8 | 9 | pub let println (a: String) : { IO } () 10 | 11 | use Prelude 12 | 13 | pub effect Log e where 14 | pub log e : () 15 | 16 | let logToStdout! (x: Request (Log String) a -> a) : {IO} a = 17 | cases 18 | { Log.log e -> k } => do 19 | println e 20 | handle k () with logToStdout! 21 | other => other 22 | 23 | let variosLog : { Log } a = do 24 | Log.log "a" 25 | Log.log "b" 26 | Log.log "c" 27 | 28 | let main : { IO } a = 29 | handle variosLog with logToStdout! -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/layout_parsing.expect: -------------------------------------------------------------------------------- 1 | suite/layout_parsing.vp:9:12: name not found: 2 | 3 | 7 | 4 | 8 | -- Tests if an error causes the layout to collapse 5 | 9 | let test : Int = do 6 | | ^^^ 7 | 10 | ( 8 | 9 | suite/layout_parsing.vp:6:23: name not found: Operator 10 | 11 | 4 | 3 12 | 5 | 13 | 6 | let add1 (n: Int) = n + 1 14 | | ^ 15 | 7 | 16 | 17 | suite/layout_parsing.vp:6:14: name not found: 18 | 19 | 4 | 3 20 | 5 | 21 | 6 | let add1 (n: Int) = n + 1 22 | | ^^^ 23 | 7 | 24 | 25 | suite/layout_parsing.vp:1:12: name not found: 26 | 27 | 1 | let main : Int = do 28 | | ^^^ 29 | 2 | 1 30 | 31 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/concrete/statements.rs: -------------------------------------------------------------------------------- 1 | use vulpi_location::Spanned; 2 | use vulpi_macros::Show; 3 | 4 | use crate::tokens::Token; 5 | 6 | use super::{expr::Expr, tree::Pattern}; 7 | 8 | #[derive(Show, Clone)] 9 | pub struct LetSttm { 10 | pub let_: Token, 11 | pub pattern: Box, 12 | pub eq: Token, 13 | pub expr: Box, 14 | } 15 | 16 | #[derive(Show, Clone)] 17 | pub enum StatementKind { 18 | Let(LetSttm), 19 | Expr(Box), 20 | Error(Vec), 21 | } 22 | 23 | pub type Sttm = Spanned; 24 | 25 | #[derive(Show, Clone)] 26 | pub struct Block { 27 | pub statements: Vec, 28 | } 29 | 30 | #[derive(Show, Clone)] 31 | pub struct DoExpr { 32 | pub do_: Token, 33 | pub block: Block, 34 | } 35 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/modules.expect: -------------------------------------------------------------------------------- 1 | suite/modules.vp:6:29: private definition 2 | 3 | 4 | | Err b 4 | 5 | 5 | 6 | let main : Result Int Int = MyOwn.Result 2 3 6 | | ^^^^^^^^^^^^ 7 | 8 | suite/modules.vp:6:23: name not found: 9 | 10 | 4 | | Err b 11 | 5 | 12 | 6 | let main : Result Int Int = MyOwn.Result 2 3 13 | | ^^^ 14 | 15 | suite/modules.vp:6:19: name not found: 16 | 17 | 4 | | Err b 18 | 5 | 19 | 6 | let main : Result Int Int = MyOwn.Result 2 3 20 | | ^^^ 21 | 22 | suite/modules.vp:6:12: name not found: 23 | 24 | 4 | | Err b 25 | 5 | 26 | 6 | let main : Result Int Int = MyOwn.Result 2 3 27 | | ^^^^^^ 28 | 29 | -------------------------------------------------------------------------------- /crates/vulpi-parser/src/error.rs: -------------------------------------------------------------------------------- 1 | use vulpi_location::Span; 2 | use vulpi_report::IntoDiagnostic; 3 | use vulpi_syntax::tokens::Token; 4 | 5 | #[derive(Debug)] 6 | pub enum ParserError { 7 | UnexpectedToken(Box, Span), 8 | } 9 | 10 | impl IntoDiagnostic for ParserError { 11 | fn message(&self) -> vulpi_report::Text { 12 | match self { 13 | ParserError::UnexpectedToken(token, _) => { 14 | format!("unexpected token '{:?}'", token.kind).into() 15 | } 16 | } 17 | } 18 | 19 | fn severity(&self) -> vulpi_report::Severity { 20 | vulpi_report::Severity::Error 21 | } 22 | 23 | fn location(&self) -> Span { 24 | match self { 25 | ParserError::UnexpectedToken(_, span) => span.clone(), 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/vulpi-lexer/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types for the lexing process. These are converted into [vulpi_report::Diagnostic]. 2 | 3 | use vulpi_location::Span; 4 | use vulpi_report::IntoDiagnostic; 5 | 6 | /// The kind of lexing error. 7 | pub enum ErrorKind { 8 | UnfinishedString, 9 | } 10 | 11 | /// A lexing error. 12 | pub struct Error { 13 | pub location: Span, 14 | pub message: ErrorKind, 15 | } 16 | 17 | impl IntoDiagnostic for Error { 18 | fn message(&self) -> vulpi_report::Text { 19 | match self.message { 20 | ErrorKind::UnfinishedString => vulpi_report::Text::from("unfinished string literal"), 21 | } 22 | } 23 | 24 | fn severity(&self) -> vulpi_report::Severity { 25 | vulpi_report::Severity::Error 26 | } 27 | 28 | fn location(&self) -> Span { 29 | self.location.clone() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/vulpi-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulpi-build" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | vulpi-lexer = { path = "../vulpi-lexer" } 10 | vulpi-location = { path = "../vulpi-location" } 11 | vulpi-parser = { path = "../vulpi-parser" } 12 | vulpi-report = { path = "../vulpi-report" } 13 | vulpi-show = { path = "../vulpi-show" } 14 | vulpi-vfs = { path = "../vulpi-vfs" } 15 | vulpi-resolver = { path = "../vulpi-resolver" } 16 | vulpi-syntax = { path = "../vulpi-syntax" } 17 | vulpi-intern = { path = "../vulpi-intern" } 18 | vulpi-typer = { path = "../vulpi-typer" } 19 | vulpi-ir = { path = "../vulpi-ir" } 20 | vulpi-js = { path = "../vulpi-js" } 21 | 22 | filetime = "0.2.22" 23 | petgraph = "0.6.4" 24 | resw = "0.6.0-alpha.2" 25 | graph-cycles = "0.1.0" 26 | 27 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/concrete/pattern.rs: -------------------------------------------------------------------------------- 1 | use vulpi_location::Spanned; 2 | use vulpi_macros::Show; 3 | 4 | use crate::tokens::Token; 5 | 6 | use super::{literal::Literal, r#type::Type, Lower, Parenthesis, Path, Upper}; 7 | 8 | #[derive(Show, Clone)] 9 | pub struct PatAscription { 10 | pub left: Box, 11 | pub colon: Token, 12 | pub right: Box, 13 | } 14 | 15 | #[derive(Show, Clone)] 16 | pub struct PatApplication { 17 | pub func: Path, 18 | pub args: Vec>, 19 | } 20 | 21 | #[derive(Show, Clone)] 22 | pub enum PatternKind { 23 | Wildcard(Token), 24 | Constructor(Path), 25 | Variable(Lower), 26 | Literal(Literal), 27 | Annotation(PatAscription), 28 | Tuple(Vec<(Pattern, Option)>), 29 | Application(PatApplication), 30 | Parenthesis(Parenthesis>), 31 | } 32 | 33 | pub type Pattern = Spanned; 34 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/infer/kind.rs: -------------------------------------------------------------------------------- 1 | use vulpi_syntax::r#abstract::{Kind, KindType}; 2 | 3 | use crate::{ 4 | r#virtual::Env, 5 | real::{self, Real}, 6 | Type, TypeKind, 7 | }; 8 | 9 | use super::Infer; 10 | 11 | impl Infer for Kind { 12 | type Return = crate::Kind; 13 | 14 | type Context<'a> = Env; 15 | 16 | fn infer(&self, context: Self::Context<'_>) -> Self::Return { 17 | context.set_current_span(self.span.clone()); 18 | 19 | match &self.data { 20 | KindType::Star => Type::typ(), 21 | KindType::Constraint => todo!(), 22 | KindType::Arrow(l, r) => { 23 | let l = l.infer(context.clone()); 24 | let r = r.infer(context); 25 | Type::new(TypeKind::Arrow(real::Arrow { typ: l, body: r })) 26 | } 27 | KindType::Error => Type::error(), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/handler.expect: -------------------------------------------------------------------------------- 1 | suite/handler.vp:6:22: name not found: 2 | 3 | 4 | a 4 | 5 | b 5 | 6 | { Log.log e } => other 6 | | ^^^^^ 7 | 8 | suite/handler.vp:6:7: name not found: Log 9 | 10 | 4 | a 11 | 5 | b 12 | 6 | { Log.log e } => other 13 | | ^^^^^^^ 14 | 15 | suite/handler.vp:5:11: name not found: 16 | 17 | 3 | { Log.log e } => do 18 | 4 | a 19 | 5 | b 20 | | ^ 21 | 6 | { Log.log e } => other 22 | 23 | suite/handler.vp:4:11: name not found: 24 | 25 | 2 | cases 26 | 3 | { Log.log e } => do 27 | 4 | a 28 | | ^ 29 | 5 | b 30 | 31 | suite/handler.vp:3:7: name not found: Log 32 | 33 | 1 | let logToStdout! = 34 | 2 | cases 35 | 3 | { Log.log e } => do 36 | | ^^^^^^^ 37 | 4 | a 38 | 39 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/concrete/type.rs: -------------------------------------------------------------------------------- 1 | use vulpi_location::Spanned; 2 | use vulpi_macros::Show; 3 | 4 | use crate::concrete::Lower; 5 | use crate::tokens::Token; 6 | 7 | use super::{top_level::TypeBinder, Parenthesis, Path, Upper}; 8 | 9 | #[derive(Show, Clone)] 10 | pub struct TypeArrow { 11 | pub left: Box, 12 | pub arrow: Token, 13 | pub right: Box, 14 | } 15 | 16 | #[derive(Show, Clone)] 17 | pub struct TypeApplication { 18 | pub func: Box, 19 | pub args: Vec>, 20 | } 21 | 22 | #[derive(Show, Clone)] 23 | pub struct TypeForall { 24 | pub forall: Token, 25 | pub params: Vec, 26 | pub dot: Token, 27 | pub body: Box, 28 | } 29 | 30 | #[derive(Show, Clone)] 31 | pub enum TypeKind { 32 | Parenthesis(Parenthesis<(Box, Option)>), 33 | Tuple(Parenthesis, Option)>>), 34 | Type(Path), 35 | TypeVariable(Lower), 36 | Arrow(TypeArrow), 37 | Application(TypeApplication), 38 | Forall(TypeForall), 39 | Unit(Token), 40 | } 41 | 42 | pub type Type = Spanned; 43 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/pipe.expect: -------------------------------------------------------------------------------- 1 | suite/pipe.vp:7:5: name not found: Operator 2 | 3 | 5 | |> inc 4 | 6 | |> inc 5 | 7 | |> inc 6 | | ^^ 7 | 8 | suite/pipe.vp:6:5: name not found: Operator 9 | 10 | 4 | 1 11 | 5 | |> inc 12 | 6 | |> inc 13 | | ^^ 14 | 7 | |> inc 15 | 16 | suite/pipe.vp:5:5: name not found: Operator 17 | 18 | 3 | let main : Int = 19 | 4 | 1 20 | 5 | |> inc 21 | | ^^ 22 | 6 | |> inc 23 | 24 | suite/pipe.vp:3:12: name not found: 25 | 26 | 1 | let inc (n: Int) : Int = n + 1 27 | 2 | 28 | 3 | let main : Int = 29 | | ^^^ 30 | 4 | 1 31 | 32 | suite/pipe.vp:1:28: name not found: Operator 33 | 34 | 1 | let inc (n: Int) : Int = n + 1 35 | | ^ 36 | 2 | 37 | 38 | suite/pipe.vp:1:20: name not found: 39 | 40 | 1 | let inc (n: Int) : Int = n + 1 41 | | ^^^ 42 | 2 | 43 | 44 | suite/pipe.vp:1:13: name not found: 45 | 46 | 1 | let inc (n: Int) : Int = n + 1 47 | | ^^^ 48 | 2 | 49 | 50 | -------------------------------------------------------------------------------- /crates/vulpi-report/src/hash.rs: -------------------------------------------------------------------------------- 1 | //! Simple reporter for diagnostics using a hashmap to store things. 2 | 3 | use crate::{Diagnostic, Reporter}; 4 | use std::collections::HashMap; 5 | use vulpi_location::FileId; 6 | 7 | #[derive(Default)] 8 | pub struct HashReporter { 9 | map: HashMap>, 10 | errored: bool, 11 | } 12 | 13 | impl HashReporter { 14 | pub fn new() -> Self { 15 | Self::default() 16 | } 17 | } 18 | 19 | impl Reporter for HashReporter { 20 | fn report(&mut self, diagnostic: Diagnostic) { 21 | self.errored = true; 22 | self.map 23 | .entry(diagnostic.location().file) 24 | .or_default() 25 | .push(diagnostic); 26 | } 27 | 28 | fn diagnostics(&self, file: FileId) -> &[Diagnostic] { 29 | self.map.get(&file).map_or(&[], |v| v) 30 | } 31 | 32 | fn clear(&mut self, file: FileId) { 33 | self.map.remove(&file); 34 | } 35 | 36 | fn all_diagnostics(&self) -> Vec { 37 | self.map.values().flatten().cloned().collect() 38 | } 39 | 40 | fn has_errors(&self) -> bool { 41 | self.errored 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/vulpi-parser/src/literal.rs: -------------------------------------------------------------------------------- 1 | use vulpi_syntax::{ 2 | concrete::tree::{Literal, LiteralKind}, 3 | tokens::TokenData, 4 | }; 5 | 6 | use crate::{Parser, Result}; 7 | 8 | impl<'a> Parser<'a> { 9 | pub fn literal_kind(&mut self) -> Result { 10 | match self.token() { 11 | TokenData::Int => { 12 | let int = self.bump(); 13 | Ok(LiteralKind::Integer(int)) 14 | } 15 | TokenData::Float => { 16 | let float = self.bump(); 17 | Ok(LiteralKind::Float(float)) 18 | } 19 | TokenData::String => { 20 | let string = self.bump(); 21 | Ok(LiteralKind::String(string)) 22 | } 23 | TokenData::Char => { 24 | let char = self.bump(); 25 | Ok(LiteralKind::Char(char)) 26 | } 27 | TokenData::Unit => { 28 | let unit = self.bump(); 29 | Ok(LiteralKind::Unit(unit)) 30 | } 31 | _ => self.unexpected(), 32 | } 33 | } 34 | 35 | pub fn literal(&mut self) -> Result { 36 | self.spanned(Self::literal_kind) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/infer/mod.rs: -------------------------------------------------------------------------------- 1 | //! The inference trait. It correponds to the `Γ |- e ⇒ A -| ∆` on the paper. It is responsible for 2 | //! inferring the type of an expression given a context. 3 | 4 | use vulpi_location::Spanned; 5 | 6 | pub mod expr; 7 | pub mod kind; 8 | pub mod literal; 9 | pub mod pat; 10 | pub mod r#type; 11 | 12 | /// The inference trait. It descovers the type of an expression based on the context. 13 | pub trait Infer { 14 | type Return; 15 | type Context<'a>; 16 | 17 | fn infer(&self, context: Self::Context<'_>) -> Self::Return; 18 | } 19 | 20 | impl Infer for Option { 21 | type Return = Option; 22 | type Context<'a> = T::Context<'a>; 23 | 24 | fn infer(&self, context: Self::Context<'_>) -> Self::Return { 25 | self.as_ref().map(|x| x.infer(context)) 26 | } 27 | } 28 | 29 | impl Infer for Box { 30 | type Return = Box; 31 | type Context<'a> = T::Context<'a>; 32 | fn infer(&self, context: Self::Context<'_>) -> Self::Return { 33 | Box::new(self.as_ref().infer(context)) 34 | } 35 | } 36 | 37 | impl Infer for Spanned { 38 | type Return = T::Return; 39 | 40 | type Context<'a> = T::Context<'a>; 41 | 42 | fn infer(&self, context: Self::Context<'_>) -> Self::Return { 43 | self.data.infer(context) 44 | } 45 | } -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/effects.expect: -------------------------------------------------------------------------------- 1 | suite/effects.vp:23:3: name not found: Log 2 | 3 | 21 | Log.log "a" 4 | 22 | Log.log "b" 5 | 23 | Log.log "c" 6 | | ^^^^^^^ 7 | 24 | 8 | 9 | suite/effects.vp:22:3: name not found: Log 10 | 11 | 20 | let variosLog : { Log } a = do 12 | 21 | Log.log "a" 13 | 22 | Log.log "b" 14 | | ^^^^^^^ 15 | 23 | Log.log "c" 16 | 17 | suite/effects.vp:21:3: name not found: Log 18 | 19 | 19 | 20 | 20 | let variosLog : { Log } a = do 21 | 21 | Log.log "a" 22 | | ^^^^^^^ 23 | 22 | Log.log "b" 24 | 25 | suite/effects.vp:20:19: name not found: 26 | 27 | 18 | other => other 28 | 19 | 29 | 20 | let variosLog : { Log } a = do 30 | | ^^^ 31 | 21 | Log.log "a" 32 | 33 | suite/effects.vp:26:3: unexpected token 'Sep' 34 | 35 | 24 | 36 | 25 | let main : { IO } a = 37 | 26 | handle variosLog with logToStdout! 38 | | 39 | 40 | suite/effects.vp:14:3: unexpected token 'Sep' 41 | 42 | 12 | 43 | 13 | let logToStdout! (x: Request (Log String) a -> a) : {IO} a = 44 | 14 | cases 45 | | 46 | 15 | { Log.log e } => do 47 | 48 | suite/effects.vp:8:3: unexpected token 'Let' 49 | 50 | 6 | pub effect IO where 51 | 7 | 52 | 8 | let println (a: String) : { IO } () 53 | | ^^^ 54 | 9 | 55 | 56 | -------------------------------------------------------------------------------- /crates/vulpi-vfs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Virtual file system for the compiler. It's used to store the source code of the files that are 2 | //! being compiled. 3 | 4 | use std::path::PathBuf; 5 | 6 | use filetime::FileTime; 7 | pub use path::Path; 8 | use vulpi_location::FileId; 9 | 10 | pub mod path; 11 | 12 | #[derive(Debug)] 13 | pub enum Error { 14 | NotFound(PathBuf), 15 | NotFoundId, 16 | AlreadyExists, 17 | } 18 | 19 | /// A virtual file system trait that can be implemented by the user. It's used to store the source 20 | /// code of the files that are being compiled and to store the compiled modules. 21 | pub trait FileSystem { 22 | type Path: Clone; 23 | 24 | fn load(&mut self, path: Self::Path) -> Result; 25 | fn unload(&mut self, id: FileId) -> Result<(), Error>; 26 | fn path(&self, id: FileId) -> Result<&Self::Path, Error>; 27 | 28 | fn store(&mut self, id: FileId, content: String) -> Result<(), Error>; 29 | fn read(&self, id: FileId) -> Result; 30 | 31 | fn create(&mut self, path: Self::Path) -> Result; 32 | fn write(&mut self, id: FileId) -> Result<(), Error>; 33 | fn delete(&mut self, id: FileId) -> Result<(), Error>; 34 | 35 | fn modification_time(&self, id: Self::Path) -> Result; 36 | 37 | fn from_cached_path(&self, path: Path) -> Self::Path; 38 | fn from_src_path(&self, path: Path) -> Self::Path; 39 | } 40 | -------------------------------------------------------------------------------- /crates/vulpi-intern/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The string interner of the compiler. It is used to store strings in a way that is more efficient 2 | //! than storing them directly. It is also used to make sure that strings are unique. This is 3 | //! important for the compiler because it allows us to compare strings by comparing their ids. None 4 | //! of the implementations implement the [Copy] trait because some of them are heavy to clone. 5 | 6 | #[cfg(feature = "single-shot")] 7 | pub mod no_rc; 8 | 9 | #[cfg(feature = "single-shot")] 10 | pub use no_rc::*; 11 | 12 | use std::marker::PhantomData; 13 | 14 | /// A interned symbol that contains a phantom data to make it unique. 15 | #[derive(Clone, PartialEq, Eq, Hash)] 16 | pub struct Interned(Symbol, PhantomData); 17 | 18 | impl Interned { 19 | pub fn new(symbol: Symbol) -> Self { 20 | Self(symbol, PhantomData) 21 | } 22 | 23 | pub fn get(&self) -> String { 24 | self.0.get() 25 | } 26 | 27 | pub fn cast(self) -> Interned { 28 | Interned::new(self.0) 29 | } 30 | } 31 | 32 | pub trait Internable: Sized { 33 | fn intern(&self) -> Interned; 34 | } 35 | 36 | impl std::fmt::Debug for Interned { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | f.debug_tuple("Interned") 39 | .field(&self.0) 40 | .field(&self.1) 41 | .finish() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/infer/literal.rs: -------------------------------------------------------------------------------- 1 | //! Inference of literals 2 | 3 | use vulpi_syntax::{elaborated, r#abstract::Literal, r#abstract::LiteralKind}; 4 | 5 | use super::Infer; 6 | use crate::{context::Context, r#virtual::Virtual, Env, Type}; 7 | 8 | impl Infer for Literal { 9 | type Return = (Type, elaborated::Literal); 10 | 11 | type Context<'a> = (&'a mut Context, Env); 12 | 13 | fn infer(&self, (ctx, env): Self::Context<'_>) -> Self::Return { 14 | env.set_current_span(self.span.clone()); 15 | 16 | match &self.data { 17 | LiteralKind::String(n) => ( 18 | ctx.find_prelude_type("String", env), 19 | Box::new(elaborated::LiteralKind::String(n.clone())), 20 | ), 21 | LiteralKind::Integer(n) => ( 22 | ctx.find_prelude_type("Int", env), 23 | Box::new(elaborated::LiteralKind::Integer(n.clone())), 24 | ), 25 | LiteralKind::Float(n) => ( 26 | ctx.find_prelude_type("Float", env), 27 | Box::new(elaborated::LiteralKind::Float(n.clone())), 28 | ), 29 | LiteralKind::Char(n) => ( 30 | ctx.find_prelude_type("Char", env), 31 | Box::new(elaborated::LiteralKind::Char(n.clone())), 32 | ), 33 | LiteralKind::Unit => (Type::tuple(vec![]), Box::new(elaborated::LiteralKind::Unit)), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/List.vp: -------------------------------------------------------------------------------- 1 | pub use Yal.List.List 2 | use Prelude 3 | 4 | pub type List x = 5 | | Cons x (List x) 6 | | Nil 7 | 8 | pub let unwords : List String -> String 9 | | List.Cons x (List.Cons y xs) => x ++ " " ++ unwords (List.Cons y xs) 10 | | List.Cons x List.Nil => x 11 | | List.Nil => "" 12 | 13 | pub let range (from: Int) (to: Int) : List Int = 14 | when from != to is 15 | True => List.Cons from (range (from + 1) to) 16 | False => List.Nil 17 | 18 | pub let forEach (f: x -> ()) : List x -> () 19 | | List.Nil => () 20 | | List.Cons x xs => do 21 | f x 22 | forEach f xs 23 | 24 | pub let reverseListHelper : List x -> List x -> List x 25 | | List.Nil, ys => ys 26 | | List.Cons x xs, ys => reverseListHelper xs (List.Cons x ys) 27 | 28 | pub let reverseList (x: List x) : List x = 29 | reverseListHelper x List.Nil 30 | 31 | pub let deleteFromList : List x -> x -> List x 32 | | List.Nil, y => List.Nil 33 | | List.Cons x xs, y => 34 | when x == y is 35 | True => deleteFromList xs y 36 | False => List.Cons x (deleteFromList xs y) 37 | 38 | pub let fold : (b -> a -> b) -> b -> List a -> b 39 | | f, acc, List.Nil => acc 40 | | f, acc, List.Cons x xs => fold f (f acc x) xs 41 | 42 | pub let difference : List x -> List x -> List x = 43 | fold deleteFromList 44 | 45 | pub let concatList : List x -> List x -> List x 46 | | List.Cons x xs, ys => List.Cons x (concatList xs ys) 47 | | List.Nil , ys => ys 48 | 49 | pub let listMap (f: a -> b) : List a -> List b 50 | | List.Cons x xs => List.Cons (f x) (listMap f xs) 51 | | List.Nil => List.Nil -------------------------------------------------------------------------------- /example/Prelude.vp: -------------------------------------------------------------------------------- 1 | #javascript " 2 | let obj = (tag, arr) => { 3 | arr.tag = tag 4 | return arr 5 | } 6 | 7 | let add = x => y => x + y 8 | 9 | let sub = x => y => x - y 10 | 11 | let concat = x => y => x + y 12 | 13 | let eq = x => y => { 14 | if (x === y) { 15 | return 1; 16 | } else if ((typeof x == \"object\" && x != null) && (typeof y == \"object\" && y != null)) { 17 | if (Object.keys(x).length != Object.keys(y).length) return 0; 18 | for (var prop in x) { 19 | if (y.hasOwnProperty(prop)) { 20 | if (!eq(x[prop])(y[prop])) return 0; 21 | } else { 22 | return 0; 23 | } 24 | } 25 | return 1; 26 | } 27 | else 28 | return 0; 29 | } 30 | 31 | let id = x => x 32 | " 33 | 34 | pub use Prelude.Bool 35 | pub use Prelude.Option 36 | pub use Prelude.Result 37 | 38 | pub type Int 39 | pub type String 40 | 41 | pub type Bool = 42 | | False 43 | | True 44 | 45 | pub type Result ok err = 46 | | Ok ok 47 | | Err err 48 | 49 | pub type Option data = 50 | | Some data 51 | | None 52 | 53 | pub external add : Int -> Int -> Int = "add" 54 | 55 | pub external sub : Int -> Int -> Int = "sub" 56 | 57 | pub external log : forall a. a -> () = "console.log" 58 | 59 | pub external concat : String -> String -> String = "concat" 60 | 61 | pub external eq : forall a. a -> a -> Bool = "eq" 62 | 63 | pub external neq : forall a. a -> a -> Bool = "1 - eq" 64 | 65 | pub external trustMe : forall a b. a -> b = "id" 66 | 67 | pub external intToString : Int -> String = "id" 68 | 69 | pub let pipe (p: a) (f: a -> b) : b = f p -------------------------------------------------------------------------------- /crates/vulpi-vfs/src/path.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{Display, Error, Formatter}, 3 | path::PathBuf, 4 | }; 5 | 6 | use vulpi_intern::Symbol; 7 | 8 | #[derive(Clone, PartialEq, Eq, Hash)] 9 | pub struct Path { 10 | pub segments: Vec, 11 | } 12 | 13 | impl Path { 14 | pub fn is_empty(&self) -> bool { 15 | self.segments.is_empty() 16 | } 17 | 18 | pub fn with(&self, new: Symbol) -> Path { 19 | let mut segments = self.segments.clone(); 20 | segments.push(new); 21 | Path { segments } 22 | } 23 | 24 | pub fn symbol(&self) -> Symbol { 25 | Symbol::intern( 26 | &self 27 | .segments 28 | .iter() 29 | .map(|x| x.get()) 30 | .collect::>() 31 | .join("."), 32 | ) 33 | } 34 | 35 | pub fn shift(&self) -> Path { 36 | let mut segments = self.segments.clone(); 37 | segments.remove(0); 38 | Path { segments } 39 | } 40 | 41 | pub fn to_pathbuf(&self, cwd: PathBuf) -> ::std::path::PathBuf { 42 | let mut path = cwd; 43 | 44 | for segment in &self.segments { 45 | path.push(segment.get()); 46 | } 47 | 48 | path.set_extension("vp"); 49 | 50 | path 51 | } 52 | } 53 | 54 | impl Display for Path { 55 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 56 | for (i, segment) in self.segments.iter().enumerate() { 57 | if i != 0 { 58 | write!(f, ".")?; 59 | } 60 | 61 | write!(f, "{}", segment.get())?; 62 | } 63 | 64 | Ok(()) 65 | } 66 | } 67 | 68 | #[derive(Clone, PartialEq, Eq, Hash)] 69 | pub struct Qualified { 70 | pub path: Path, 71 | pub name: Symbol, 72 | } 73 | -------------------------------------------------------------------------------- /crates/vulpi-parser/src/identifier.rs: -------------------------------------------------------------------------------- 1 | //! Parses multiple types of identifiers and paths 2 | 3 | use vulpi_syntax::{ 4 | concrete::{Ident, Lower, Path, Upper}, 5 | tokens::TokenData, 6 | }; 7 | 8 | use crate::{Parser, Result}; 9 | 10 | impl<'a> Parser<'a> { 11 | /// Parses a path from the current token. 12 | pub fn path(&mut self, parse: impl Fn(&mut Self) -> Result) -> Result> { 13 | let start = self.span(); 14 | let mut segments = Vec::new(); 15 | 16 | while self.at(TokenData::UpperIdent) && self.then(TokenData::Dot) { 17 | let ident = self.bump(); 18 | let dot = self.bump(); 19 | segments.push((Upper(ident), dot)); 20 | } 21 | 22 | let last = parse(self)?; 23 | 24 | Ok(Path { 25 | segments, 26 | last, 27 | span: self.with_span(start), 28 | }) 29 | } 30 | 31 | pub fn path_ident(&mut self) -> Result> { 32 | self.path(|parser| match parser.peek().kind { 33 | TokenData::LowerIdent => Ok(Ident::Lower(parser.lower()?)), 34 | TokenData::UpperIdent => Ok(Ident::Upper(parser.upper()?)), 35 | _ => parser.unexpected(), 36 | }) 37 | } 38 | 39 | pub fn path_upper(&mut self) -> Result> { 40 | self.path(|parser| parser.upper()) 41 | } 42 | 43 | pub fn path_lower(&mut self) -> Result> { 44 | self.path(|parser| parser.lower()) 45 | } 46 | 47 | pub fn lower(&mut self) -> Result { 48 | // TODO: Handle case error 49 | let ident = self.expect(TokenData::LowerIdent)?; 50 | Ok(Lower(ident)) 51 | } 52 | 53 | pub fn upper(&mut self) -> Result { 54 | // TODO: Handle case error 55 | let ident = self.expect(TokenData::UpperIdent)?; 56 | Ok(Upper(ident)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/vulpi-report/src/renderer/mod.rs: -------------------------------------------------------------------------------- 1 | //! Simple renderer for diagnostics. 2 | 3 | pub mod classic; 4 | 5 | use vulpi_location::Byte; 6 | 7 | /// Trait for rendering diagnostics. 8 | pub trait Renderer { 9 | fn render(&self, ctx: &T, writer: &mut impl std::io::Write) -> std::io::Result<()>; 10 | } 11 | 12 | /// A guide for lines and columns. 13 | #[derive(Debug)] 14 | pub struct LineGuide { 15 | line_bytes: Vec<(usize, usize)>, 16 | } 17 | 18 | impl LineGuide { 19 | pub fn new(content: &str) -> Self { 20 | let mut line_bytes = Vec::new(); 21 | 22 | let mut line_start = 0; 23 | let mut line_end = 0; 24 | 25 | for (i, c) in content.char_indices() { 26 | if c == '\n' { 27 | line_bytes.push((line_start, line_end)); 28 | line_start = i + 1; 29 | } 30 | 31 | line_end = i + 1; 32 | } 33 | 34 | line_bytes.push((line_start, line_end)); 35 | 36 | Self { line_bytes } 37 | } 38 | 39 | pub fn to_line_and_column(&self, place: Byte) -> Option<(usize, usize)> { 40 | let place = place.0; 41 | 42 | for (i, (start, end)) in self.line_bytes.iter().enumerate() { 43 | if place >= *start && place <= *end { 44 | return Some((i, place - start)); 45 | } 46 | } 47 | 48 | None 49 | } 50 | } 51 | 52 | /// A reader is just a wrapper around a string for [std::io::Write]. 53 | #[derive(Default)] 54 | pub struct Reader(String); 55 | 56 | impl ToString for Reader { 57 | fn to_string(&self) -> String { 58 | self.0.clone() 59 | } 60 | } 61 | 62 | impl std::io::Write for Reader { 63 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 64 | self.0.push_str(std::str::from_utf8(buf).unwrap()); 65 | Ok(buf.len()) 66 | } 67 | 68 | fn flush(&mut self) -> std::io::Result<()> { 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /crates/vulpi-parser/src/pattern.rs: -------------------------------------------------------------------------------- 1 | use vulpi_syntax::{ 2 | concrete::{pattern::*, Either}, 3 | tokens::TokenData, 4 | }; 5 | 6 | use crate::{Parser, Result}; 7 | 8 | impl<'a> Parser<'a> { 9 | pub fn pattern_atom_kind(&mut self) -> Result { 10 | match self.token() { 11 | TokenData::Wildcard => Ok(PatternKind::Wildcard(self.bump())), 12 | TokenData::LowerIdent => self.lower().map(PatternKind::Variable), 13 | TokenData::UpperIdent => { 14 | let path = self.path_ident()?; 15 | match path.diferentiate() { 16 | Either::Left(upper) => Ok(PatternKind::Constructor(upper)), 17 | Either::Right(_) => todo!(), 18 | } 19 | } 20 | TokenData::LPar => self 21 | .parenthesis(Self::pattern) 22 | .map(PatternKind::Parenthesis), 23 | _ => self.literal().map(PatternKind::Literal), 24 | } 25 | } 26 | 27 | pub fn pattern_atom(&mut self) -> Result> { 28 | self.spanned(Self::pattern_atom_kind).map(Box::new) 29 | } 30 | 31 | pub fn pattern_application_kind(&mut self) -> Result { 32 | let func = self.path_upper()?; 33 | let args = self.many(Self::pattern_atom)?; 34 | Ok(PatApplication { func, args }) 35 | } 36 | 37 | pub fn pattern_application(&mut self) -> Result> { 38 | if self.at(TokenData::UpperIdent) { 39 | self.spanned(|this| { 40 | let result = this.pattern_application_kind()?; 41 | if result.args.is_empty() { 42 | Ok(PatternKind::Constructor(result.func)) 43 | } else { 44 | Ok(PatternKind::Application(result)) 45 | } 46 | }) 47 | .map(Box::new) 48 | } else { 49 | self.pattern_atom() 50 | } 51 | } 52 | 53 | pub fn pattern(&mut self) -> Result> { 54 | self.pattern_application() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /crates/vulpi-lexer/src/literals.rs: -------------------------------------------------------------------------------- 1 | //! Lexing of literals like strings, integers, floats, etc. 2 | 3 | use vulpi_intern::Symbol; 4 | use vulpi_syntax::tokens::TokenData; 5 | 6 | use crate::{error::ErrorKind, Lexer}; 7 | 8 | impl<'a> Lexer<'a> { 9 | /// Parses a character of a char literal 10 | pub fn char(&mut self) -> Option { 11 | match self.peekable.peek() { 12 | Some('\\') => self.escape(), 13 | Some(_) => self.advance(), 14 | None => None, 15 | } 16 | } 17 | 18 | /// Parses escaped characters 19 | pub(crate) fn escape(&mut self) -> Option { 20 | self.advance(); 21 | 22 | let result = match self.peekable.peek() { 23 | Some('n') => '\n', 24 | Some('r') => '\r', 25 | Some('t') => '\t', 26 | Some('0') => '\0', 27 | Some('\\') => '\\', 28 | Some('\'') => '\'', 29 | Some('"') => '"', 30 | _ => return None, 31 | }; 32 | 33 | self.advance(); 34 | 35 | Some(result) 36 | } 37 | 38 | pub(crate) fn string(&mut self) -> (TokenData, Symbol) { 39 | let mut string = String::new(); 40 | 41 | while let Some(c) = self.peekable.peek() { 42 | match c { 43 | '\\' => { 44 | if let Some(res) = self.escape() { 45 | string.push(res); 46 | } else { 47 | self.accumulate(|x| *x != '"'); 48 | return (TokenData::Error, Symbol::intern(&string)); 49 | } 50 | } 51 | '"' => break, 52 | _ => { 53 | string.push(self.advance().unwrap()); 54 | } 55 | } 56 | } 57 | 58 | if let Some('"') = self.peekable.peek() { 59 | self.advance(); 60 | (TokenData::String, Symbol::intern(&string)) 61 | } else { 62 | self.report(ErrorKind::UnfinishedString); 63 | (TokenData::Error, Symbol::intern(&string)) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/vulpi-resolver/src/error.rs: -------------------------------------------------------------------------------- 1 | use vulpi_intern::Symbol; 2 | use vulpi_location::Span; 3 | use vulpi_report::IntoDiagnostic; 4 | use vulpi_syntax::r#abstract::Qualified; 5 | 6 | pub enum ResolverErrorKind { 7 | NotFound(Symbol), 8 | ListIsNotAvailable, 9 | InvalidPath(Vec), 10 | DuplicatePattern(Symbol), 11 | PrivateDefinition, 12 | CycleBetweenConstants(Vec), 13 | NotImplemented(Symbol, Symbol), 14 | } 15 | 16 | pub struct ResolverError { 17 | pub span: Span, 18 | pub kind: ResolverErrorKind, 19 | } 20 | 21 | impl IntoDiagnostic for ResolverError { 22 | fn message(&self) -> vulpi_report::Text { 23 | match &self.kind { 24 | ResolverErrorKind::NotImplemented(name, feature) => format!( 25 | "the method '{}' is not present in the trait '{}'", 26 | feature.get(), 27 | name.get() 28 | ) 29 | .into(), 30 | ResolverErrorKind::ListIsNotAvailable => "List is not available".into(), 31 | ResolverErrorKind::NotFound(name) => format!("cannot find '{}'", name.get()).into(), 32 | ResolverErrorKind::InvalidPath(name) => format!( 33 | "the path '{}' cannot be found", 34 | name.iter().map(|s| s.get()).collect::>().join(".") 35 | ) 36 | .into(), 37 | ResolverErrorKind::DuplicatePattern(name) => { 38 | format!("duplicate pattern: {}", name.get()).into() 39 | } 40 | ResolverErrorKind::PrivateDefinition => "private definition".into(), 41 | ResolverErrorKind::CycleBetweenConstants(cycle) => { 42 | let mut cycle = cycle.iter().map(|q| q.to_string()).collect::>(); 43 | cycle.sort_by_key(|k| k.to_string()); 44 | 45 | format!("cycle between '{}'", cycle.join(" -> ")).into() 46 | } 47 | } 48 | } 49 | 50 | fn severity(&self) -> vulpi_report::Severity { 51 | vulpi_report::Severity::Error 52 | } 53 | 54 | fn location(&self) -> Span { 55 | self.span.clone() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Vulpi Logo

2 |

The Vulpi Compiler

3 | 4 | # Tasks 5 | 6 | - [x] Lexer 7 | - [x] Layout Parsing 8 | - [x] Escape 9 | - [ ] Interpolation 10 | - [ ] Parser 11 | - [x] Types 12 | - [x] Algebraic Data Types 13 | - [x] Declaration 14 | - [ ] Generalized Version 15 | - [X] Kinds 16 | - [x] Records 17 | - [x] Instance 18 | - [x] Update 19 | - [x] Declaration 20 | - [x] Projection 21 | - [ ] Effects 22 | - [ ] Declaration 23 | - [ ] Handler 24 | - [ ] Traits 25 | - [ ] Implementation 26 | - [ ] Constraint Syntax 27 | - [x] Abstract Data Types 28 | - [x] Type Synonyms 29 | - [x] Tuple 30 | - [x] Let declarations 31 | - [x] Let cases 32 | - [x] Patterns 33 | - [ ] Record Pattern 34 | - [x] ADT Pattern 35 | - [x] Guards 36 | - [x] Or Pattern 37 | - [x] Blocks 38 | - [x] Modules 39 | - [x] Public / Private things 40 | - [ ] Deriving Syntax 41 | - [x] Expressions 42 | - [x] Operators 43 | - [ ] Section Patterns 44 | - [ ] Function applications In Half 45 | - [x] Pipe and Composition 46 | - [x] Precedence 47 | - [x] Type ascription 48 | - [x] Function Call 49 | - [x] Let Expression 50 | - [x] Lambda Expression 51 | - [x] Do Expression 52 | - [x] When Expression 53 | - [x] If Expression 54 | - [x] Record Expression 55 | - [x] Tuple Expression 56 | - [ ] List Expression 57 | - [x] Unit expression 58 | - [ ] Attributes 59 | - [x] Import 60 | - [x] Resolution 61 | - [x] Visibility resolution 62 | - [x] Name resolution 63 | - [x] Duplicated pattern name checking 64 | - [x] Or Pattern 65 | - [x] Desugar 66 | - [x] Type checker 67 | - [x] Higher rank polymorphism 68 | - [x] Higher kinded types 69 | - [ ] Entailment 70 | - [ ] Coverage checker 71 | - [ ] Perceus 72 | - [ ] LLVM -------------------------------------------------------------------------------- /crates/vulpi-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(panic_info_message)] 2 | #![feature(panic_can_unwind)] 3 | 4 | use std::{backtrace::Backtrace, env, panic, path::PathBuf}; 5 | 6 | use vulpi_build::real::RealFileSystem; 7 | use vulpi_intern::Symbol; 8 | use vulpi_report::renderer::classic::Classic; 9 | 10 | use clap::Parser; 11 | 12 | #[derive(Parser)] 13 | enum Cli { 14 | Compile { 15 | package: String, 16 | file_name: String, 17 | 18 | #[clap(short, long)] 19 | output: Option, 20 | }, 21 | } 22 | 23 | fn main() { 24 | panic::set_hook(Box::new(|e| { 25 | eprintln!( 26 | "\n[Error]: internal compiler error '{:?}' at {}", 27 | e.message().unwrap(), 28 | e.location().unwrap() 29 | ); 30 | eprintln!("- It should not occur. Please submit an issue to the Vulpi repository:)"); 31 | eprintln!("- Here: https://github.com/lang-vulpi/vulpi/issues\n"); 32 | 33 | if std::env::var("RUST_BACKTRACE").is_ok() { 34 | let backtrace = Backtrace::capture(); 35 | 36 | eprintln!("Stack trace: \n{}", backtrace) 37 | } 38 | })); 39 | 40 | let result = Cli::parse(); 41 | 42 | match result { 43 | Cli::Compile { 44 | file_name, 45 | package, 46 | output, 47 | } => { 48 | let cwd = env::current_dir().unwrap(); 49 | 50 | let name = Symbol::intern(&package); 51 | 52 | let output = output.unwrap_or_else(|| { 53 | format!("{}.js", file_name.split(".").next().unwrap().to_string()) 54 | }); 55 | 56 | let mut compiler = vulpi_build::ProjectCompiler { 57 | fs: RealFileSystem::new(name.clone(), cwd.clone(), cwd.clone().join("build")), 58 | reporter: vulpi_report::hash_reporter(), 59 | name: name.clone(), 60 | }; 61 | 62 | compiler.compile( 63 | name.clone(), 64 | PathBuf::from(file_name), 65 | PathBuf::from(output), 66 | ); 67 | 68 | let ctx = Classic::new(&compiler.fs, cwd.clone()); 69 | compiler.reporter.to_stderr(ctx) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/vulpi-resolver/src/dependencies.rs: -------------------------------------------------------------------------------- 1 | use vulpi_intern::Symbol; 2 | use vulpi_location::Span; 3 | use vulpi_syntax::concrete::{self, tree::TopLevel, Upper}; 4 | use vulpi_vfs::path::Path; 5 | 6 | #[derive(Clone)] 7 | pub struct Dependencies { 8 | pub declared: Vec, 9 | pub imported: Vec<(Path, Span)>, 10 | pub opened: Vec, 11 | } 12 | 13 | pub fn from_path_upper(path: &concrete::Path) -> Path { 14 | let mut path_result = Path { segments: vec![] }; 15 | 16 | for segment in &path.segments { 17 | path_result.segments.push(segment.0.symbol()); 18 | } 19 | 20 | path_result.segments.push(path.last.symbol()); 21 | 22 | path_result 23 | } 24 | 25 | pub fn dependencies(root: Symbol, program: &concrete::tree::Program) -> Dependencies { 26 | pub fn dependencies( 27 | root: Symbol, 28 | path: Vec, 29 | program: &[TopLevel], 30 | deps: &mut Dependencies, 31 | ) { 32 | for top_level in program { 33 | match top_level { 34 | concrete::tree::TopLevel::Use(use_) => { 35 | let path = from_path_upper(&use_.path); 36 | deps.imported.push((path.clone(), use_.path.span.clone())); 37 | 38 | if use_.alias.is_none() { 39 | deps.opened.push(path); 40 | } 41 | } 42 | concrete::tree::TopLevel::Module(decl) => { 43 | let mut path = path.clone(); 44 | path.push(decl.name.symbol()); 45 | if let Some(res) = &decl.part { 46 | dependencies(root.clone(), path, &res.top_levels, deps); 47 | } else { 48 | deps.declared.push(Path { segments: path }); 49 | } 50 | } 51 | _ => (), 52 | } 53 | } 54 | } 55 | 56 | let mut deps = Dependencies { 57 | declared: Vec::new(), 58 | imported: Vec::new(), 59 | opened: Vec::new(), 60 | }; 61 | 62 | dependencies( 63 | root.clone(), 64 | vec![root.clone()], 65 | &program.top_levels, 66 | &mut deps, 67 | ); 68 | 69 | deps 70 | } 71 | -------------------------------------------------------------------------------- /crates/vulpi-resolver/src/cycle.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use petgraph::{graph::DiGraph, stable_graph::NodeIndex}; 4 | 5 | use vulpi_location::Span; 6 | use vulpi_report::{Diagnostic, Report}; 7 | use vulpi_syntax::{r#abstract::Program, r#abstract::Qualified}; 8 | 9 | use crate::error::ResolverError; 10 | 11 | #[derive(Default)] 12 | pub struct DepHolder { 13 | nodes: HashMap>, 14 | graph: DiGraph<(), ()>, 15 | spans: HashMap, 16 | } 17 | 18 | impl DepHolder { 19 | pub fn register(&mut self, program: &Program) { 20 | for let_ in &program.lets { 21 | let from = *self 22 | .nodes 23 | .entry(let_.signature.name.clone()) 24 | .or_insert_with(|| self.graph.add_node(())); 25 | 26 | if let Some(res) = &let_.constant { 27 | for (to_, span) in res { 28 | let to = self 29 | .nodes 30 | .entry(to_.clone()) 31 | .or_insert_with(|| self.graph.add_node(())); 32 | 33 | self.graph.add_edge(from, *to, ()); 34 | self.spans.insert(to_.clone(), span.clone()); 35 | } 36 | } 37 | } 38 | } 39 | 40 | pub fn report_cycles(&self, report: Report) { 41 | let inv_nodes = self 42 | .nodes 43 | .iter() 44 | .map(|(k, v)| (v, k)) 45 | .collect::>(); 46 | 47 | let cycles = petgraph::algo::tarjan_scc(&self.graph); 48 | 49 | for cycle in cycles { 50 | if cycle.len() > 1 { 51 | let mut cycle = cycle 52 | .iter() 53 | .map(|n| inv_nodes[n].clone()) 54 | .collect::>(); 55 | cycle.sort_by_key(|k| k.to_string()); 56 | 57 | let first = cycle[0].clone(); 58 | 59 | let span = self.spans[&first].clone(); 60 | 61 | report.report(Diagnostic::new(ResolverError { 62 | span, 63 | kind: crate::error::ResolverErrorKind::CycleBetweenConstants(cycle), 64 | })) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /crates/vulpi-ir/src/uncurry.rs: -------------------------------------------------------------------------------- 1 | use vulpi_intern::Symbol; 2 | use vulpi_syntax::{ 3 | lambda::LetDecl, 4 | lambda::{self, Program}, 5 | r#abstract::Qualified, 6 | }; 7 | 8 | pub fn accumulate_lambda_nodes<'a>( 9 | expr: &'a mut lambda::ExprKind, 10 | params: &mut Vec, 11 | ) -> &'a mut lambda::ExprKind { 12 | match expr { 13 | lambda::ExprKind::Lambda(param, body) => { 14 | params.extend(param.clone()); 15 | accumulate_lambda_nodes(body, params) 16 | } 17 | e => e, 18 | } 19 | } 20 | 21 | pub fn create_big_lambda<'a>( 22 | expr: &'a mut lambda::ExprKind, 23 | ) -> Option<(Vec, &mut lambda::ExprKind)> { 24 | let mut params = Vec::new(); 25 | let body = accumulate_lambda_nodes(expr, &mut params); 26 | 27 | if params.is_empty() { 28 | None 29 | } else { 30 | Some((params, body)) 31 | } 32 | } 33 | 34 | pub fn uncurry_program(program: &mut Program) { 35 | let mut new_lets = vec![]; 36 | 37 | for (name, let_) in &mut program.lets { 38 | if let Some((params, body)) = create_big_lambda(&mut let_.body) { 39 | let name = Qualified { 40 | path: name.path.clone(), 41 | name: Symbol::intern(&format!("{}.uncurried", name.name.get())), 42 | }; 43 | 44 | new_lets.push(( 45 | name.clone(), 46 | LetDecl { 47 | name: name.clone(), 48 | body: Box::new(lambda::ExprKind::Lambda( 49 | params.clone(), 50 | Box::new(body.clone()), 51 | )), 52 | constants: None, 53 | is_in_source_code: false, 54 | }, 55 | )); 56 | 57 | *body = lambda::ExprKind::Application( 58 | Box::new(lambda::ExprKind::Function(name.clone())), 59 | params 60 | .into_iter() 61 | .map(lambda::ExprKind::Variable) 62 | .map(Box::new) 63 | .collect(), 64 | ); 65 | } 66 | } 67 | 68 | program.lets.extend(new_lets); 69 | } 70 | 71 | pub fn uncurry(programs: &mut Vec) { 72 | for program in programs { 73 | uncurry_program(program); 74 | } 75 | } -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/lambda.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap}; 2 | 3 | use vulpi_intern::Symbol; 4 | use vulpi_location::Span; 5 | use vulpi_macros::Show; 6 | 7 | use crate::{r#abstract::Qualified, elaborated::Literal}; 8 | 9 | #[derive(Show, Clone)] 10 | pub enum ConsDef { 11 | Enumerated(Qualified, usize), 12 | Heavy(Qualified, usize, usize), 13 | NewType, 14 | Tuple, 15 | } 16 | 17 | #[derive(Clone, Show, Debug, PartialEq, Eq, Hash)] 18 | pub enum Case { 19 | Tuple(usize), 20 | Constructor(Qualified, usize), 21 | Literal(Literal), 22 | } 23 | 24 | #[derive(Show, Clone)] 25 | pub enum Stmt { 26 | Let(Symbol, Expr), 27 | Expr(Expr), 28 | } 29 | 30 | #[derive(Show, Clone)] 31 | pub enum Tree { 32 | Leaf(usize), 33 | Switch(Expr, Vec<(Case, TagType, Tree)>), 34 | } 35 | 36 | #[derive(Show, Clone)] 37 | pub enum TagType { 38 | Field(usize), 39 | Number(usize), 40 | Size, 41 | None 42 | } 43 | 44 | #[derive(Show, Clone)] 45 | pub enum ExprKind { 46 | Lambda(Vec, Expr), 47 | Application(Expr, Vec), 48 | 49 | Variable(Symbol), 50 | Constructor(Qualified), 51 | Function(Qualified), 52 | Object(usize, Vec), 53 | 54 | Projection(Qualified, Expr), 55 | Access(Expr, usize), 56 | 57 | Block(Vec), 58 | Literal(Literal), 59 | 60 | RecordInstance(Qualified, Vec<(Symbol, Expr)>), 61 | RecordUpdate(Qualified, Expr, Vec<(Symbol, Expr)>), 62 | 63 | Tuple(Vec), 64 | 65 | Switch(Symbol, Tree, Vec), 66 | 67 | } 68 | 69 | pub type Expr = Box; 70 | 71 | #[derive(Show, Clone)] 72 | pub struct LetDecl { 73 | pub name: Qualified, 74 | pub body: Expr, 75 | pub is_in_source_code: bool, 76 | pub constants: Option>, 77 | } 78 | 79 | #[derive(Show, Clone)] 80 | pub enum TypeDecl { 81 | Abstract, 82 | Enum(Vec<(Qualified, usize)>), 83 | Record(Vec), 84 | } 85 | 86 | #[derive(Show, Clone)] 87 | pub struct ExternalDecl { 88 | pub name: Qualified, 89 | pub binding: Symbol, 90 | } 91 | 92 | #[derive(Show, Clone, Default)] 93 | pub struct Program { 94 | pub lets: Vec<(Qualified, LetDecl)>, 95 | pub externals: Vec<(Qualified, Symbol)>, 96 | pub commands: Vec<(Symbol, Symbol)>, 97 | pub definitions: HashMap, 98 | } -------------------------------------------------------------------------------- /example/Bindings.vp: -------------------------------------------------------------------------------- 1 | use Prelude 2 | 3 | #javascript " 4 | let addEventListener = el => sym => ev => data => { 5 | el[ev] = () => { 6 | if(window.events && window.events.get(sym)) { 7 | let [fn, state] = window.events.get(sym); 8 | window.events.set(sym, [fn, fn(data, state)]) 9 | } 10 | } 11 | } 12 | 13 | let addListener = symbol => fn => def => { 14 | if(window.events == undefined) { 15 | window.events = new Map(); 16 | } 17 | window.events.set(symbol, [(a,b) => fn(a)(b), def]) 18 | } 19 | " 20 | 21 | pub type GenericNode 22 | pub type NodeElement 23 | pub type TextElement 24 | pub type Children 25 | pub type Symbol msg model 26 | 27 | pub external createNode : String -> NodeElement = "document.createElement" 28 | pub external createText : String -> NodeElement = "document.createTextNode" 29 | pub external setAttribute : NodeElement -> String -> String -> () = "(el => attr => val => el.setAttribute(attr, val))" 30 | pub external appendChild : NodeElement -> GenericNode -> () = "(el => child => el.appendChild(child))" 31 | pub external getParent : GenericNode -> NodeElement = "(child) => child.parentNode" 32 | pub external remove : GenericNode -> () = "(el) => el.remove()" 33 | pub external replaceWith : GenericNode -> GenericNode -> () = "(old => neww => old.replaceWith(neww))" 34 | pub external removeAttribute : NodeElement -> String -> () = "(el => attr => el.removeAttribute(attr))" 35 | pub external prim_getElementById : String -> NodeElement = "(id => document.getElementById(id))" 36 | pub external prim_getChildren : NodeElement -> Children = "(el => el.childNodes)" 37 | pub external childLength : Children -> Int = "(el => el.length)" 38 | pub external idxChild : Children -> Int -> GenericNode = "(child => n => child[n])" 39 | pub external createSymbol : forall a b. String -> Symbol a b = "(name => Symbol(name))" 40 | pub external isNullOrUndefined : forall a. a -> Bool = "(x => x === null || x === undefined ? 1 : 0)" 41 | pub external addEventListener : forall a b. GenericNode -> Symbol a b -> String -> a -> () = "addEventListener" 42 | pub external addListener : forall a b c. Symbol a b -> (a -> c -> c) -> c -> () = "addListener" 43 | pub external removeListener : NodeElement -> String -> () = "(el => attr => {el[attr] = undefined})" -------------------------------------------------------------------------------- /crates/vulpi-typer/src/check/expr.rs: -------------------------------------------------------------------------------- 1 | //! Checking of expressions 2 | 3 | use vulpi_location::Spanned; 4 | use vulpi_syntax::{elaborated, r#abstract::Expr, r#abstract::ExprKind, r#abstract::Sttm}; 5 | 6 | use crate::{context::Context, real::Real, Env, Type, TypeKind, Virtual}; 7 | 8 | use super::Check; 9 | use crate::infer::Infer; 10 | 11 | impl Check for Expr { 12 | type Return = elaborated::Expr>; 13 | 14 | type Context<'a> = (&'a mut Context, Env); 15 | 16 | fn check( 17 | &self, 18 | typ: crate::Type, 19 | (ctx, mut env): Self::Context<'_>, 20 | ) -> Self::Return { 21 | env.set_current_span(self.span.clone()); 22 | 23 | let elem = match (&self.data, typ.deref().as_ref()) { 24 | (ExprKind::Do(block), _) => { 25 | let mut stmts = Vec::new(); 26 | 27 | if !block.sttms.is_empty() { 28 | for (i, stmt) in block.sttms.iter().enumerate() { 29 | let is_last = i == block.sttms.len() - 1; 30 | let (elab, new_env) = if is_last { 31 | stmt.check(typ.clone(), (ctx, env.clone())) 32 | } else { 33 | let (_, new_env, elab) = stmt.infer((ctx, &mut env.clone())); 34 | (elab, new_env) 35 | }; 36 | 37 | env = new_env; 38 | 39 | stmts.push(elab) 40 | } 41 | } 42 | 43 | Box::new(elaborated::ExprKind::Do(stmts)) 44 | } 45 | (_, TypeKind::Forall(l)) => { 46 | let lvl_ty = Type::new(TypeKind::Bound(env.level)); 47 | self.check( 48 | l.body.apply_local(Some(l.name.clone()), lvl_ty.clone()), 49 | (ctx, env.add(Some(l.name.clone()), lvl_ty)), 50 | ) 51 | .data 52 | } 53 | _ => { 54 | let (expr_ty, elab_expr) = self.infer((ctx, env.clone())); 55 | ctx.subsumes(env, expr_ty, typ); 56 | elab_expr.data 57 | } 58 | }; 59 | 60 | Spanned::new(elem, self.span.clone()) 61 | } 62 | } 63 | 64 | impl Check for Sttm { 65 | type Return = (elaborated::Statement>, Env); 66 | 67 | type Context<'a> = (&'a mut Context, Env); 68 | 69 | fn check(&self, ann_ty: Type, (ctx, env): Self::Context<'_>) -> Self::Return { 70 | env.set_current_span(self.span.clone()); 71 | 72 | let (typ, env, elab) = self.infer((ctx, &mut env.clone())); 73 | 74 | ctx.subsumes(env.clone(), typ, ann_ty); 75 | (elab, env) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /crates/vulpi-intern/src/no_rc.rs: -------------------------------------------------------------------------------- 1 | //! A simple string interner with no reference counting so it lives until the end of the program. 2 | 3 | use vulpi_show::Show; 4 | 5 | use std::cell::RefCell; 6 | use std::collections::HashMap; 7 | use std::sync::atomic::{AtomicUsize, Ordering}; 8 | 9 | thread_local! { 10 | static INTERNER: Interner = Interner::default(); 11 | } 12 | 13 | /// A symbol is a reference to a string inside the interner. It is used to compare strings by 14 | /// comparing their ids instead of comparing their content because it is more efficient (it makes 15 | /// the comparison an integer comparison instead of a string comparison). 16 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | pub enum Symbol { 18 | Generated(usize), 19 | Interned(usize), 20 | } 21 | 22 | impl std::fmt::Debug for Symbol { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | write!(f, "{}", self.get()) 25 | } 26 | } 27 | 28 | impl Symbol { 29 | pub fn intern(string: &str) -> Self { 30 | INTERNER.with(|i| i.intern(string)) 31 | } 32 | 33 | pub fn get(&self) -> String { 34 | INTERNER.with(|i| i.get(self).unwrap()) 35 | } 36 | 37 | pub fn get_static(&self) -> &'static str { 38 | INTERNER.with(|i| match self { 39 | Symbol::Generated(_) => todo!(), 40 | Symbol::Interned(id) => { 41 | let id_to_string = i.id_to_string.borrow(); 42 | let string = id_to_string.get(*id).unwrap(); 43 | Box::leak(string.clone().into_boxed_str()) 44 | }, 45 | }) 46 | } 47 | } 48 | 49 | impl Show for Symbol { 50 | fn show(&self) -> vulpi_show::TreeDisplay { 51 | vulpi_show::TreeDisplay::label(&format!("Symbol: {}", self.get())) 52 | } 53 | } 54 | #[derive(Default)] 55 | struct Interner { 56 | id_to_string: RefCell>, 57 | string_to_id: RefCell>, 58 | counter: AtomicUsize, 59 | } 60 | 61 | impl Interner { 62 | fn intern(&self, string: &str) -> Symbol { 63 | if let Some(id) = self.string_to_id.borrow().get(string) { 64 | return id.clone(); 65 | } 66 | 67 | let mut id_to_string = self.id_to_string.borrow_mut(); 68 | let mut string_to_id = self.string_to_id.borrow_mut(); 69 | 70 | let id = Symbol::Interned(self.counter.fetch_add(1, Ordering::SeqCst)); 71 | id_to_string.push(string.to_owned()); 72 | string_to_id.insert(string.to_owned(), id.clone()); 73 | 74 | id 75 | } 76 | 77 | fn get(&self, id: &Symbol) -> Option { 78 | match id { 79 | Symbol::Generated(n) => Some(format!("%{n}")), 80 | Symbol::Interned(id) => self.id_to_string.borrow().get(*id).cloned(), 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/module.rs: -------------------------------------------------------------------------------- 1 | //! Module for declaration of top level items inside the type checker. The main structure of this 2 | //! module is the [Module] structure that is responsible for storing the types of the top level 3 | //! items. 4 | 5 | use std::collections::HashMap; 6 | 7 | use vulpi_intern::Symbol; 8 | use vulpi_syntax::r#abstract::Qualified; 9 | 10 | use crate::{r#virtual::Virtual, real::Real, Type}; 11 | 12 | #[derive(Clone)] 13 | pub enum Def { 14 | Enum(Vec), 15 | Record(Vec), 16 | Effect(Vec), 17 | Type, 18 | Constraint 19 | } 20 | 21 | #[derive(Clone)] 22 | pub struct TypeData { 23 | pub kind: Type, 24 | pub binders: Vec<(Symbol, Type)>, 25 | pub module: Symbol, 26 | pub def: Def, 27 | } 28 | 29 | #[derive(Clone)] 30 | pub struct TraitData { 31 | pub kind: Type, 32 | pub binders: Vec>, 33 | pub supers: Vec>, 34 | pub signatures: Vec<(Qualified, Type)>, 35 | 36 | 37 | } 38 | 39 | #[derive(Clone)] 40 | pub struct LetDef { 41 | pub typ: Type, 42 | pub unbound: Vec<(Symbol, Type)>, 43 | pub args: Vec>, 44 | pub ret: Type, 45 | } 46 | 47 | #[derive(Default)] 48 | pub struct Interface { 49 | /// The types of the functions. 50 | pub variables: HashMap, 51 | 52 | /// The types of the functions. 53 | pub constructors: HashMap, usize, Qualified)>, 54 | 55 | /// The types of the types. 56 | pub types: HashMap, 57 | 58 | /// The fields of the records. 59 | pub fields: HashMap>, 60 | 61 | /// Traits. 62 | pub traits: HashMap, 63 | } 64 | 65 | #[derive(Default)] 66 | pub struct Modules { 67 | /// The modules. 68 | pub modules: HashMap, 69 | } 70 | 71 | impl Modules { 72 | pub fn new() -> Self { 73 | Self { 74 | modules: Default::default(), 75 | } 76 | } 77 | 78 | pub fn typ(&mut self, qualified: &Qualified) -> TypeData { 79 | let module = self.get(&qualified.path); 80 | module.types.get(&qualified.name).unwrap().clone() 81 | } 82 | 83 | pub fn constructor(&mut self, qualified: &Qualified) -> (Type, usize, Qualified) { 84 | let module = self.get(&qualified.path); 85 | module.constructors.get(&qualified.name).unwrap().clone() 86 | } 87 | 88 | pub fn let_decl(&mut self, qualified: &Qualified) -> &mut LetDef { 89 | let module = self.get(&qualified.path); 90 | module.variables.get_mut(&qualified.name).unwrap() 91 | } 92 | 93 | pub fn field(&mut self, qualified: &Qualified) -> Type { 94 | let module = self.get(&qualified.path); 95 | module.fields.get(&qualified.name).unwrap().clone() 96 | } 97 | 98 | pub fn get(&mut self, id: &Symbol) -> &mut Interface { 99 | self.modules.entry(id.clone()).or_default() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /crates/vulpi-location/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This module exposes a lot of structures that locate things inside a source code. It's really 2 | //! useful to generate error messages. 3 | 4 | use std::fmt::Debug; 5 | 6 | use vulpi_show::{Show, TreeDisplay}; 7 | 8 | /// A new-type for a usize. It's used to locate a byte inside a source code. 9 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] 10 | pub struct Byte(pub usize); 11 | 12 | /// A span that locates a piece of data inside a source code. 13 | #[derive(Clone, Default)] 14 | pub struct Span { 15 | pub file: FileId, 16 | pub start: Byte, 17 | pub end: Byte, 18 | } 19 | 20 | impl Show for Span { 21 | fn show(&self) -> vulpi_show::TreeDisplay { 22 | TreeDisplay::label("Span").with(TreeDisplay::label(&format!( 23 | "{}~{}", 24 | self.start.0, self.end.0 25 | ))) 26 | } 27 | 28 | } 29 | 30 | impl Span { 31 | pub fn ghost() -> Self { 32 | Self { 33 | file: FileId(0), 34 | start: Byte(0), 35 | end: Byte(0), 36 | } 37 | } 38 | } 39 | 40 | impl Debug for Span { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | write!(f, "{:?}~{:?}", self.start.0, self.end.0) 43 | } 44 | } 45 | 46 | impl Span { 47 | pub fn new(file: FileId, start: Byte, end: Byte) -> Self { 48 | Self { file, start, end } 49 | } 50 | 51 | pub fn from_usize(file: FileId, start: usize, end: usize) -> Self { 52 | Self { 53 | file, 54 | start: Byte(start), 55 | end: Byte(end), 56 | } 57 | } 58 | 59 | pub fn mix(self, other: Self) -> Self { 60 | Self { 61 | file: self.file, 62 | start: std::cmp::min(self.start, other.start), 63 | end: std::cmp::max(self.end, other.end), 64 | } 65 | } 66 | } 67 | 68 | /// A span that locates a piece of data inside a source code. 69 | #[derive(Clone)] 70 | pub struct Spanned { 71 | pub data: T, 72 | pub span: Span, 73 | } 74 | 75 | impl Show for Spanned { 76 | fn show(&self) -> vulpi_show::TreeDisplay { 77 | TreeDisplay::label("Spanned") 78 | .with(TreeDisplay::label(&format!( 79 | "{}~{}", 80 | self.span.start.0, self.span.end.0 81 | ))) 82 | .with(self.data.show()) 83 | } 84 | } 85 | 86 | impl Spanned { 87 | pub fn map(&self, f: impl FnOnce(&T) -> U) -> Spanned { 88 | Spanned { 89 | data: f(&self.data), 90 | span: self.span.clone(), 91 | } 92 | } 93 | 94 | pub fn with(self, data: U) -> Spanned { 95 | Spanned { 96 | data, 97 | span: self.span, 98 | } 99 | } 100 | } 101 | 102 | impl Debug for Spanned { 103 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 104 | f.debug_tuple("Spanned").field(&self.data).finish() 105 | } 106 | } 107 | 108 | impl Spanned { 109 | pub fn new(data: T, range: Span) -> Self { 110 | Self { data, span: range } 111 | } 112 | } 113 | 114 | /// The identifier of a file. 115 | #[derive(Clone, Default, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] 116 | pub struct FileId(pub usize); 117 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/concrete/mod.rs: -------------------------------------------------------------------------------- 1 | //! The concrete syntax tree for the language. This is the output of the parser. 2 | 3 | pub mod expr; 4 | pub mod kind; 5 | pub mod literal; 6 | pub mod pattern; 7 | pub mod statements; 8 | pub mod top_level; 9 | pub mod r#type; 10 | 11 | use vulpi_intern::Symbol; 12 | use vulpi_macros::Show; 13 | 14 | /// Module that exposes the entire tree 15 | pub mod tree { 16 | pub use super::expr::*; 17 | pub use super::kind::*; 18 | pub use super::literal::*; 19 | pub use super::pattern::*; 20 | pub use super::r#type::*; 21 | pub use super::statements::*; 22 | pub use super::top_level::*; 23 | } 24 | 25 | use vulpi_location::Span; 26 | 27 | use crate::tokens::Token; 28 | 29 | pub enum Either { 30 | Left(L), 31 | Right(R), 32 | } 33 | 34 | #[derive(Show, Clone)] 35 | pub struct Upper(pub Token); 36 | 37 | impl Upper { 38 | pub fn symbol(&self) -> Symbol { 39 | self.0.value.data.clone() 40 | } 41 | } 42 | 43 | #[derive(Show, Clone)] 44 | pub struct Lower(pub Token); 45 | 46 | impl Lower { 47 | pub fn symbol(&self) -> Symbol { 48 | self.0.value.data.clone() 49 | } 50 | } 51 | 52 | #[derive(Show, Clone)] 53 | pub enum Ident { 54 | Upper(Upper), 55 | Lower(Lower), 56 | } 57 | 58 | #[derive(Show, Clone)] 59 | pub struct Path { 60 | pub segments: Vec<(Upper, Token)>, 61 | pub last: T, 62 | pub span: Span, 63 | } 64 | 65 | impl From<&Path> for Vec { 66 | fn from(value: &Path) -> Self { 67 | value 68 | .segments 69 | .iter() 70 | .map(|(upper, _)| upper.symbol()) 71 | .chain(std::iter::once(value.last.symbol())) 72 | .collect::>() 73 | } 74 | } 75 | 76 | impl From<&Path> for Vec { 77 | fn from(value: &Path) -> Self { 78 | value 79 | .segments 80 | .iter() 81 | .map(|(upper, _)| upper.symbol()) 82 | .chain(std::iter::once(value.last.symbol())) 83 | .collect::>() 84 | } 85 | } 86 | 87 | impl Path { 88 | pub fn diferentiate(self) -> Either, Path> { 89 | let Path { 90 | segments, 91 | last, 92 | span, 93 | } = self; 94 | 95 | let segments = segments 96 | .into_iter() 97 | .map(|(upper, dot)| (upper, dot)) 98 | .collect(); 99 | 100 | match last { 101 | Ident::Upper(upper) => Either::Left(Path { 102 | segments, 103 | last: upper, 104 | span, 105 | }), 106 | Ident::Lower(lower) => Either::Right(Path { 107 | segments, 108 | last: lower, 109 | span, 110 | }), 111 | } 112 | } 113 | } 114 | 115 | #[derive(Show, Clone)] 116 | pub struct Parenthesis { 117 | pub left: Token, 118 | pub data: T, 119 | pub right: Token, 120 | } 121 | 122 | impl Parenthesis { 123 | pub fn map(self, f: impl FnOnce(T) -> U) -> Parenthesis { 124 | let Parenthesis { left, data, right } = self; 125 | 126 | Parenthesis { 127 | left, 128 | data: f(data), 129 | right, 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /crates/vulpi-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A tiny test runner for Vulpi based on the `atiny-tests` crate for the Atiny language. 2 | 3 | #![feature(path_file_prefix)] 4 | #![feature(test)] 5 | 6 | extern crate test; 7 | 8 | use std::fs::{self, read_to_string}; 9 | use std::path::PathBuf; 10 | 11 | use test::{TestDesc, TestDescAndFn, TestName}; 12 | 13 | const EXTENSION: &str = "vp"; 14 | 15 | pub mod util; 16 | 17 | /// A bunch of golden-tests that are run by the test runner. The test runner will run each test 18 | /// that is inside the directory described inside the entry. 19 | pub struct Test { 20 | pub directory: &'static str, 21 | pub run: fn(file_name: PathBuf) -> String, 22 | } 23 | 24 | /// The main runner that receives tests and then runs them. 25 | pub fn test_runner(tests: &[&Test]) { 26 | let Some(opts) = get_test_opts() else { 27 | return; 28 | }; 29 | 30 | let mut rendered = Vec::new(); 31 | 32 | for test in tests { 33 | let directory = std::fs::read_dir(test.directory).unwrap(); 34 | 35 | for file in directory.flatten() { 36 | let (file_name, typ) = util::split_name(&file); 37 | 38 | if typ != EXTENSION { 39 | continue; 40 | } 41 | 42 | if file.file_type().unwrap().is_file() { 43 | rendered.push(create_test_description(file_name, file, test.run)); 44 | } 45 | } 46 | } 47 | 48 | match test::run_tests_console(&opts, rendered) { 49 | Ok(true) => { 50 | println!(); 51 | } 52 | Ok(false) => panic!("some tests failed"), 53 | Err(e) => panic!("io error when running tests: {:?}", e), 54 | } 55 | } 56 | 57 | fn create_test_description( 58 | file_name: String, 59 | file: fs::DirEntry, 60 | function: fn(PathBuf) -> String, 61 | ) -> TestDescAndFn { 62 | TestDescAndFn { 63 | desc: TestDesc { 64 | name: TestName::DynTestName(file_name.clone()), 65 | ignore: false, 66 | should_panic: test::ShouldPanic::No, 67 | ignore_message: None, 68 | source_file: "", 69 | start_line: 0, 70 | start_col: 0, 71 | end_line: 0, 72 | end_col: 0, 73 | compile_fail: false, 74 | no_run: false, 75 | test_type: test::TestType::UnitTest, 76 | }, 77 | testfn: test::TestFn::DynTestFn(Box::new(move || { 78 | println!("testing '{}'", file_name); 79 | 80 | let path = file.path(); 81 | 82 | let expect_path = path.with_extension("expect"); 83 | let result = function(path.with_extension(EXTENSION)); 84 | 85 | if let Ok(expects) = read_to_string(expect_path.clone()) { 86 | if expects.eq(&result) { 87 | Ok(()) 88 | } else { 89 | println!("Expected:\n\n{}\n\ngot:\n\n{}", expects, result); 90 | Err("Mismatch".to_string()) 91 | } 92 | } else { 93 | fs::write(expect_path, result).map_err(|err| err.to_string()) 94 | } 95 | })), 96 | } 97 | } 98 | 99 | fn get_test_opts() -> Option { 100 | let args = std::env::args().collect::>(); 101 | let parsed = test::test::parse_opts(&args); 102 | match parsed { 103 | Some(Ok(o)) => Some(o), 104 | Some(Err(msg)) => panic!("{:?}", msg), 105 | None => None, 106 | } 107 | } 108 | 109 | #[macro_export] 110 | macro_rules! test { 111 | ($directory:expr, $code:expr) => { 112 | #[test_case] 113 | const TEST: vulpi_tests::Test = vulpi_tests::Test { 114 | directory: concat!(env!("CARGO_MANIFEST_DIR"), $directory), 115 | run: $code, 116 | }; 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /crates/vulpi-build/src/real.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fs, path::PathBuf}; 2 | 3 | use filetime::FileTime; 4 | use vulpi_intern::Symbol; 5 | use vulpi_location::FileId; 6 | use vulpi_vfs::{path::Path, Error}; 7 | 8 | use super::FileSystem; 9 | 10 | pub struct RealFileSystem { 11 | project_root: PathBuf, 12 | build_root: PathBuf, 13 | root: Symbol, 14 | file_map: HashMap, 15 | path_map: HashMap, 16 | counter: usize, 17 | } 18 | 19 | impl RealFileSystem { 20 | pub fn new(root: Symbol, project_root: PathBuf, build: PathBuf) -> Self { 21 | Self { 22 | root, 23 | project_root, 24 | build_root: build, 25 | file_map: HashMap::new(), 26 | path_map: HashMap::new(), 27 | counter: 0, 28 | } 29 | } 30 | 31 | pub fn get_path(&self, path: PathBuf) -> Result { 32 | let path = &self.project_root.clone().join(path); 33 | path.canonicalize() 34 | .map_err(|_| Error::NotFound(path.clone())) 35 | } 36 | } 37 | 38 | impl FileSystem for RealFileSystem { 39 | type Path = PathBuf; 40 | 41 | fn load(&mut self, path: PathBuf) -> Result { 42 | let path = self.get_path(path)?; 43 | 44 | if let Some(id) = self.path_map.get(&path) { 45 | return Ok(*id); 46 | } 47 | 48 | let content = 49 | fs::read_to_string(path.clone()).map_err(|_| Error::NotFound(path.clone()))?; 50 | 51 | let id = FileId(self.counter); 52 | self.counter += 1; 53 | 54 | let content = (path.clone(), content); 55 | 56 | self.file_map.insert(id, content); 57 | self.path_map.insert(path, id); 58 | 59 | Ok(id) 60 | } 61 | 62 | fn unload(&mut self, id: FileId) -> Result<(), Error> { 63 | self.file_map.remove(&id).ok_or(Error::NotFoundId)?; 64 | Ok(()) 65 | } 66 | 67 | fn store(&mut self, _id: FileId, _content: String) -> Result<(), Error> { 68 | todo!() 69 | } 70 | 71 | fn read(&self, id: FileId) -> Result { 72 | let file = self.file_map.get(&id).ok_or(Error::NotFoundId)?; 73 | Ok(file.1.clone()) 74 | } 75 | 76 | fn create(&mut self, path: PathBuf) -> Result { 77 | let path = self.get_path(path)?; 78 | 79 | if path.exists() { 80 | return Err(Error::AlreadyExists); 81 | } 82 | 83 | let id = FileId(self.counter); 84 | self.counter += 1; 85 | 86 | self.file_map.insert(id, (path.clone(), String::new())); 87 | self.path_map.insert(path, id); 88 | 89 | Ok(id) 90 | } 91 | 92 | fn write(&mut self, id: FileId) -> Result<(), Error> { 93 | if let Some((path, content)) = self.file_map.get(&id) { 94 | fs::write(path, content).map_err(|_| Error::NotFound(path.clone()))?; 95 | Ok(()) 96 | } else { 97 | Err(Error::NotFoundId) 98 | } 99 | } 100 | 101 | fn delete(&mut self, id: FileId) -> Result<(), Error> { 102 | if let Some((path, _)) = self.file_map.get(&id) { 103 | fs::remove_file(path).map_err(|_| Error::NotFound(path.clone()))?; 104 | Ok(()) 105 | } else { 106 | Err(Error::NotFoundId) 107 | } 108 | } 109 | 110 | fn path(&self, id: FileId) -> Result<&PathBuf, Error> { 111 | let file = self.file_map.get(&id).ok_or(Error::NotFoundId)?; 112 | Ok(&file.0) 113 | } 114 | 115 | fn modification_time(&self, path: PathBuf) -> Result { 116 | let metadata = fs::metadata(path.clone()).map_err(|_| Error::NotFound(path.clone()))?; 117 | 118 | Ok(FileTime::from_last_modification_time(&metadata)) 119 | } 120 | 121 | fn from_cached_path(&self, path: Path) -> Self::Path { 122 | path.to_pathbuf(self.build_root.clone()) 123 | } 124 | 125 | fn from_src_path(&self, path: Path) -> Self::Path { 126 | if self.root == path.segments[0] { 127 | path.shift().to_pathbuf(self.project_root.clone()) 128 | } else { 129 | path.to_pathbuf(self.project_root.clone()) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/check/pat.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use vulpi_intern::Symbol; 4 | use vulpi_syntax::{ 5 | elaborated, 6 | r#abstract::{Pattern, PatternArm, PatternKind}, 7 | }; 8 | 9 | use crate::eval::Quote; 10 | use crate::infer::Infer; 11 | use crate::{context::Context, errors::TypeErrorKind, r#virtual::Virtual, real::Real, Env, Type}; 12 | 13 | use super::Check; 14 | 15 | impl Check for PatternArm { 16 | type Return = elaborated::PatternArm>; 17 | 18 | type Context<'a> = (&'a mut Context, Env); 19 | 20 | fn check(&self, mut typ: Type, (ctx, mut env): Self::Context<'_>) -> Self::Return { 21 | let mut map = Default::default(); 22 | 23 | let mut elaborated_patterns = Vec::new(); 24 | 25 | for pat in &self.patterns { 26 | env.set_current_span(pat.span.clone()); 27 | 28 | if let Some((left, right)) = ctx.as_function(&env, typ.clone()) { 29 | let elab = pat.check(left, (ctx, &mut map, env.clone())); 30 | elaborated_patterns.push(elab); 31 | typ = right; 32 | } else { 33 | ctx.report( 34 | &env, 35 | TypeErrorKind::NotAFunction(env.clone(), typ.quote(env.level)), 36 | ); 37 | return elaborated::PatternArm { 38 | patterns: Vec::new(), 39 | guard: None, 40 | expr: self.expr.check(typ, (ctx, env.clone())), 41 | }; 42 | } 43 | } 44 | 45 | for binding in map { 46 | env.add_var(binding.0, binding.1); 47 | } 48 | 49 | let elab_expr = self.expr.check(typ, (ctx, env.clone())); 50 | 51 | let guard = self.guard.as_ref().map(|g| g.infer((ctx, env.clone()))); 52 | 53 | let elab_guard = if let Some((typ, guard)) = guard { 54 | let bool = ctx.find_prelude_type("Bool", env.clone()); 55 | ctx.subsumes(env.clone(), typ, bool); 56 | Some(guard) 57 | } else { 58 | None 59 | }; 60 | 61 | elaborated::PatternArm { 62 | patterns: elaborated_patterns, 63 | guard: elab_guard, 64 | expr: elab_expr, 65 | } 66 | } 67 | } 68 | 69 | impl Check for Vec { 70 | type Return = Vec>>; 71 | 72 | type Context<'a> = (&'a mut Context, Env); 73 | 74 | fn check(&self, typ: Type, (ctx, env): Self::Context<'_>) -> Self::Return { 75 | if self.is_empty() { 76 | ctx.report(&env, TypeErrorKind::EmptyCase); 77 | vec![] 78 | } else { 79 | let size = self[0].patterns.len(); 80 | 81 | let mut elab_arms = Vec::new(); 82 | let elab_arm = self[0].check(typ.clone(), (ctx, env.clone())); 83 | 84 | elab_arms.push(elab_arm); 85 | 86 | for pat in self.iter().skip(1) { 87 | if pat.patterns.len() != size { 88 | ctx.report(&env, TypeErrorKind::WrongArity(pat.patterns.len(), size)); 89 | return vec![]; 90 | } 91 | 92 | let elab_arm = pat.check(typ.clone(), (ctx, env.clone())); 93 | elab_arms.push(elab_arm); 94 | } 95 | 96 | elab_arms 97 | } 98 | } 99 | } 100 | 101 | impl Check for Pattern { 102 | type Return = elaborated::Pattern; 103 | 104 | type Context<'a> = (&'a mut Context, &'a mut HashMap>, Env); 105 | 106 | fn check(&self, ann_ty: Type, (ctx, map, env): Self::Context<'_>) -> Self::Return { 107 | env.set_current_span(self.span.clone()); 108 | match &self.data { 109 | PatternKind::Wildcard => Box::new(elaborated::PatternKind::Wildcard), 110 | PatternKind::Variable(n) => { 111 | map.insert(n.clone(), ann_ty); 112 | 113 | Box::new(elaborated::PatternKind::Variable(n.clone())) 114 | } 115 | _ => { 116 | let (typ, elab_pat) = self.infer((ctx, map, env.clone())); 117 | ctx.subsumes(env, typ, ann_ty); 118 | elab_pat 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Module for definition of errors that can occur during type checking. 2 | 3 | use vulpi_intern::Symbol; 4 | use vulpi_location::Span; 5 | use vulpi_report::{IntoDiagnostic, Text}; 6 | use vulpi_syntax::r#abstract::Qualified; 7 | 8 | use crate::{ 9 | coverage::{Pat, Row}, 10 | real::Real, 11 | Env, Type, 12 | }; 13 | 14 | pub enum TypeErrorKind { 15 | EmptyCase, 16 | UnboundTypeVariable(Symbol), 17 | TypeMismatch(Env, Type, Type), 18 | KindMismatch(Env, Type, Type), 19 | InfiniteType, 20 | CannotFind(Symbol), 21 | AtLeastOneArgument, 22 | EscapingScope, 23 | NotAFunctionKind, 24 | WrongArity(usize, usize), 25 | NotAFunction(Env, Type), 26 | NotImplemented, 27 | MissingLabel(Qualified), 28 | InvalidLabels(Vec), 29 | PatternsNotAllowedHere, 30 | DuplicatedField, 31 | NotFoundField, 32 | NotARecord, 33 | MissingField(Symbol), 34 | NonExhaustive(Row), 35 | } 36 | 37 | pub struct TypeError { 38 | pub span: Span, 39 | pub kind: TypeErrorKind, 40 | } 41 | 42 | impl IntoDiagnostic for TypeError { 43 | fn message(&self) -> Text { 44 | match &self.kind { 45 | TypeErrorKind::TypeMismatch(env, left, right) => Text::from(format!( 46 | "type mismatch: {} != {}", 47 | left.show(env), 48 | right.show(env) 49 | )), 50 | TypeErrorKind::EmptyCase => Text::from("empty case".to_string()), 51 | TypeErrorKind::KindMismatch(env, left, right) => Text::from(format!( 52 | "kind mismatch: {} != {}", 53 | left.show(env), 54 | right.show(env), 55 | )), 56 | TypeErrorKind::InfiniteType => Text::from("infinite type".to_string()), 57 | TypeErrorKind::EscapingScope => Text::from("escaping scope".to_string()), 58 | TypeErrorKind::NotAFunctionKind => Text::from("not a function kind".to_string()), 59 | TypeErrorKind::UnboundTypeVariable(name) => { 60 | Text::from(format!("unbound type variable: {}", name.get())) 61 | } 62 | TypeErrorKind::WrongArity(expected, found) => Text::from(format!( 63 | "wrong arity: expected {} arguments, found {}", 64 | expected, found 65 | )), 66 | TypeErrorKind::NotAFunction(env, ty) => { 67 | Text::from(format!("not a function: {}", ty.show(env))) 68 | } 69 | TypeErrorKind::CannotFind(name) => Text::from(format!("cannot find: {}", name.get())), 70 | TypeErrorKind::NotImplemented => Text::from("not implemented".to_string()), 71 | TypeErrorKind::DuplicatedField => Text::from("duplicated field".to_string()), 72 | TypeErrorKind::NotFoundField => Text::from("not found field".to_string()), 73 | TypeErrorKind::NotARecord => Text::from("not a record".to_string()), 74 | TypeErrorKind::MissingField(name) => { 75 | Text::from(format!("missing field: {}", name.get())) 76 | } 77 | TypeErrorKind::MissingLabel(name) => { 78 | Text::from(format!("missing label: {}", name.name.get())) 79 | } 80 | TypeErrorKind::InvalidLabels(labels) => Text::from(format!( 81 | "invalid labels: {}", 82 | labels 83 | .iter() 84 | .map(|label| label.name.get()) 85 | .collect::>() 86 | .join(", ") 87 | )), 88 | 89 | TypeErrorKind::PatternsNotAllowedHere => { 90 | Text::from("patterns are not allowed here".to_string()) 91 | } 92 | TypeErrorKind::AtLeastOneArgument => { 93 | Text::from("at least one argument is required".to_string()) 94 | } 95 | 96 | TypeErrorKind::NonExhaustive(row) => { 97 | Text::from(format!("non-exhaustive patterns: {}", row)) 98 | } 99 | } 100 | } 101 | 102 | fn severity(&self) -> vulpi_report::Severity { 103 | vulpi_report::Severity::Error 104 | } 105 | 106 | fn location(&self) -> Span { 107 | self.span.clone() 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /crates/vulpi-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::redundant_clone)] 2 | 3 | extern crate proc_macro; 4 | 5 | use proc_macro::TokenStream; 6 | use quote::quote; 7 | use syn::Item; 8 | 9 | #[proc_macro_derive(Show, attributes(helper))] 10 | pub fn derive_helper_attr(item: TokenStream) -> TokenStream { 11 | let parsed = syn::parse::(item).unwrap(); 12 | 13 | let name; 14 | let mut sttms = vec![quote! { 15 | use vulpi_show::{TreeDisplay}; 16 | }]; 17 | 18 | let gen; 19 | 20 | match parsed { 21 | Item::Enum(enum_) => { 22 | name = enum_.ident; 23 | 24 | gen = enum_.generics; 25 | 26 | let mut variants = vec![]; 27 | 28 | for variant in &enum_.variants { 29 | let mut counter = 0; 30 | 31 | let name_str = variant.ident.to_string(); 32 | let mut variant_fields = vec![quote! { let res = TreeDisplay::label(#name_str); }]; 33 | let mut names = Vec::new(); 34 | 35 | for field in &variant.fields { 36 | if let Some(ident) = &field.ident { 37 | names.push(ident.clone()); 38 | let name_str = ident.to_string(); 39 | variant_fields.push(quote! { 40 | let res = res.with(TreeDisplay::label(#name_str).with(#name.show())); 41 | }); 42 | } else { 43 | let name = syn::Ident::new( 44 | &format!("field{}", counter), 45 | proc_macro2::Span::call_site(), 46 | ); 47 | names.push(name.clone()); 48 | counter += 1; 49 | variant_fields.push(quote! { 50 | let res = res.with(#name.show()); 51 | }); 52 | }; 53 | } 54 | 55 | let variant = variant.ident.clone(); 56 | 57 | if names.is_empty() { 58 | variants.push(quote! { 59 | #name::#variant => { 60 | #(#variant_fields)* 61 | res 62 | } 63 | }); 64 | } else { 65 | variants.push(quote! { 66 | #name::#variant(#(#names),*) => { 67 | #(#variant_fields)* 68 | res 69 | } 70 | }); 71 | } 72 | } 73 | 74 | sttms.push(quote! { 75 | let res = match self { 76 | #(#variants)* 77 | }; 78 | }); 79 | } 80 | Item::Struct(struct_) => { 81 | name = struct_.ident; 82 | 83 | gen = struct_.generics; 84 | 85 | for (i, field) in struct_.fields.iter().enumerate() { 86 | if let Some(ident) = &field.ident { 87 | let ident_str = ident.to_string(); 88 | sttms.push(quote! { 89 | let res = res.with(TreeDisplay::label(#ident_str).with(self.#ident.show())); 90 | }); 91 | } else { 92 | let num = syn::Index::from(i); 93 | sttms.push(quote! { 94 | let res = res.with(self.#num.show()); 95 | }); 96 | } 97 | } 98 | 99 | let name_str = name.to_string(); 100 | sttms.insert(1, quote! { let res = TreeDisplay::label(#name_str); }); 101 | } 102 | _ => panic!("Only structs and enums are supported"), 103 | } 104 | 105 | let mut gen_changed = gen.clone(); 106 | 107 | for gen in &mut gen_changed.params { 108 | if let syn::GenericParam::Type(type_) = gen { 109 | type_.bounds.push(syn::parse_quote!(vulpi_show::Show)); 110 | } 111 | } 112 | 113 | quote! { 114 | impl #gen_changed vulpi_show::Show for #name #gen { 115 | fn show(&self) -> vulpi_show::TreeDisplay { 116 | #(#sttms)* 117 | res 118 | } 119 | } 120 | } 121 | .into() 122 | } 123 | -------------------------------------------------------------------------------- /crates/vulpi-parser/src/type.rs: -------------------------------------------------------------------------------- 1 | use vulpi_location::Spanned; 2 | use vulpi_syntax::concrete::{ 3 | r#type::*, 4 | tree::{Kind, KindType}, 5 | Lower, 6 | }; 7 | use vulpi_syntax::tokens::TokenData; 8 | 9 | use crate::{Parser, Result}; 10 | 11 | impl<'a> Parser<'a> { 12 | fn kind_atom_raw(&mut self) -> Result { 13 | match self.token() { 14 | TokenData::Star => Ok(KindType::Star(self.bump())), 15 | TokenData::LPar => Ok(KindType::Parenthesis(self.parenthesis(Self::kind)?)), 16 | TokenData::UpperIdent => Ok(KindType::Variable(self.upper()?)), 17 | _ => self.unexpected(), 18 | } 19 | } 20 | 21 | fn kind_atom(&mut self) -> Result> { 22 | self.spanned(Self::kind_atom_raw).map(Box::new) 23 | } 24 | 25 | fn kind_arrow(&mut self) -> Result> { 26 | let left = self.kind_atom()?; 27 | 28 | if self.at(TokenData::RightArrow) { 29 | let arrow = self.bump(); 30 | let right = self.kind()?; 31 | 32 | Ok(Box::new(Spanned { 33 | span: left.span.clone().mix(right.span.clone()), 34 | data: KindType::Arrow(left, arrow, right), 35 | })) 36 | } else { 37 | Ok(left) 38 | } 39 | } 40 | 41 | pub fn kind(&mut self) -> Result> { 42 | self.kind_arrow() 43 | } 44 | 45 | fn type_variable(&mut self) -> Result { 46 | self.lower() 47 | } 48 | 49 | fn type_forall(&mut self) -> Result { 50 | let forall = self.expect(TokenData::Forall)?; 51 | let left = self.many(Self::type_binder)?; 52 | let dot = self.expect(TokenData::Dot)?; 53 | let right = self.typ()?; 54 | 55 | Ok(TypeForall { 56 | forall, 57 | params: left, 58 | dot, 59 | body: right, 60 | }) 61 | } 62 | 63 | fn type_atom_raw(&mut self) -> Result { 64 | match self.token() { 65 | TokenData::LowerIdent => self.type_variable().map(TypeKind::TypeVariable), 66 | TokenData::UpperIdent => self.path(Self::upper).map(TypeKind::Type), 67 | TokenData::Unit => Ok(TypeKind::Unit(self.bump())), 68 | TokenData::LPar => { 69 | let exprs = self.parenthesis(|this| this.sep_by(TokenData::Comma, Self::typ))?; 70 | 71 | if exprs.data.is_empty() { 72 | todo!() 73 | } else if exprs.data.len() == 1 { 74 | Ok(TypeKind::Parenthesis( 75 | exprs.map(|x| x.into_iter().next().unwrap()), 76 | )) 77 | } else { 78 | Ok(TypeKind::Tuple(exprs)) 79 | } 80 | } 81 | 82 | _ => self.unexpected(), 83 | } 84 | } 85 | 86 | pub fn type_atom(&mut self) -> Result> { 87 | self.spanned(Self::type_atom_raw).map(Box::new) 88 | } 89 | 90 | fn type_application(&mut self) -> Result> { 91 | let func = self.type_atom()?; 92 | 93 | let args = self.many(Self::type_atom)?; 94 | 95 | if args.is_empty() { 96 | Ok(func) 97 | } else { 98 | let start = func.span.clone(); 99 | let end = args.last().unwrap().span.clone(); 100 | 101 | Ok(Box::new(Spanned { 102 | span: start.mix(end), 103 | data: TypeKind::Application(TypeApplication { func, args }), 104 | })) 105 | } 106 | } 107 | 108 | fn type_arrow(&mut self) -> Result> { 109 | let left = self.type_application()?; 110 | 111 | if self.at(TokenData::RightArrow) { 112 | let arrow = self.bump(); 113 | 114 | let right = self.type_arrow()?; 115 | 116 | Ok(Box::new(Spanned { 117 | span: left.span.clone().mix(right.span.clone()), 118 | data: TypeKind::Arrow(TypeArrow { left, arrow, right }), 119 | })) 120 | } else { 121 | Ok(left) 122 | } 123 | } 124 | 125 | /// Parses types 126 | pub fn typ(&mut self) -> Result> { 127 | match self.token() { 128 | TokenData::Forall => self 129 | .spanned(|x| x.type_forall().map(TypeKind::Forall)) 130 | .map(Box::new), 131 | _ => self.type_arrow(), 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/elaborated.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use vulpi_intern::Symbol; 4 | use vulpi_location::{Span, Spanned}; 5 | use vulpi_macros::Show; 6 | 7 | use crate::r#abstract::Qualified; 8 | 9 | #[derive(Show, PartialEq, Eq, Hash, Clone, Debug)] 10 | pub enum LiteralKind { 11 | String(Symbol), 12 | Integer(Symbol), 13 | Float(Symbol), 14 | Char(Symbol), 15 | Unit, 16 | } 17 | 18 | pub type Literal = Box; 19 | 20 | #[derive(Show, Clone)] 21 | pub struct LetStatement { 22 | pub pattern: Pattern, 23 | pub expr: Expr, 24 | } 25 | 26 | #[derive(Show, Clone)] 27 | pub enum SttmKind { 28 | Let(LetStatement), 29 | Expr(Expr), 30 | Error, 31 | } 32 | 33 | pub type Statement = SttmKind; 34 | 35 | pub type Block = Vec>; 36 | 37 | #[derive(Show, Clone)] 38 | pub struct PatOr { 39 | pub left: Pattern, 40 | pub right: Pattern, 41 | } 42 | 43 | #[derive(Show, Clone, Debug)] 44 | pub struct PatApplication { 45 | pub func: Qualified, 46 | pub args: Vec, 47 | } 48 | 49 | #[derive(Show, Clone, Debug)] 50 | pub enum PatternKind { 51 | Wildcard, 52 | Variable(Symbol), 53 | Literal(Literal), 54 | Application(PatApplication), 55 | Tuple(Vec), 56 | Error, 57 | } 58 | 59 | pub type Pattern = Box; 60 | 61 | #[derive(Show, Clone)] 62 | pub struct LambdaExpr { 63 | pub param: Pattern, 64 | pub body: Expr, 65 | } 66 | 67 | #[derive(Show, Clone)] 68 | pub enum AppKind { 69 | Infix, 70 | Normal, 71 | } 72 | 73 | #[derive(Show, Clone)] 74 | pub struct ApplicationExpr { 75 | pub typ: T, 76 | pub func: Expr, 77 | pub args: Expr, 78 | } 79 | 80 | #[derive(Show, Clone)] 81 | pub struct ProjectionExpr { 82 | pub field: Qualified, 83 | pub expr: Expr, 84 | } 85 | 86 | #[derive(Show, Clone)] 87 | pub struct PatternArm { 88 | pub patterns: Vec, 89 | pub expr: Expr, 90 | pub guard: Option>, 91 | } 92 | 93 | #[derive(Show, Clone)] 94 | pub struct WhenExpr { 95 | pub scrutinee: Vec>, 96 | pub arms: Vec>, 97 | } 98 | 99 | #[derive(Show, Clone)] 100 | pub struct LetExpr { 101 | pub pattern: Pattern, 102 | pub body: Expr, 103 | pub next: Expr, 104 | } 105 | 106 | #[derive(Show, Clone)] 107 | pub struct RecordInstance { 108 | pub name: Qualified, 109 | pub fields: Vec<(Symbol, Expr)>, 110 | } 111 | 112 | #[derive(Show, Clone)] 113 | pub struct RecordUpdate { 114 | pub name: Qualified, 115 | pub expr: Expr, 116 | pub fields: Vec<(Symbol, Expr)>, 117 | } 118 | 119 | #[derive(Show, Clone)] 120 | pub struct Tuple { 121 | pub exprs: Vec>, 122 | } 123 | 124 | #[derive(Show, Clone)] 125 | pub enum ExprKind { 126 | Lambda(LambdaExpr), 127 | Application(ApplicationExpr), 128 | 129 | Variable(Symbol), 130 | Constructor(Qualified, Qualified), 131 | Function(Qualified, T), 132 | 133 | Projection(ProjectionExpr), 134 | Let(LetExpr), 135 | When(WhenExpr), 136 | Do(Block), 137 | Literal(Literal), 138 | 139 | RecordInstance(RecordInstance), 140 | RecordUpdate(RecordUpdate), 141 | Tuple(Tuple), 142 | 143 | Error, 144 | } 145 | 146 | pub type Expr = Spanned>>; 147 | 148 | #[derive(Show, Clone)] 149 | pub struct LetDecl { 150 | pub name: Qualified, 151 | pub binders: Vec<(Pattern, T)>, 152 | pub body: Vec>, 153 | pub constants: Option>, 154 | } 155 | 156 | #[derive(Show, Clone)] 157 | pub enum TypeDecl { 158 | Abstract, 159 | Enum(Vec<(Qualified, usize)>), 160 | Record(Vec), 161 | } 162 | 163 | #[derive(Show, Clone)] 164 | pub struct ExternalDecl { 165 | pub name: Qualified, 166 | pub typ: T, 167 | pub binding: Symbol, 168 | } 169 | 170 | #[derive(Show, Clone)] 171 | pub struct Program { 172 | pub modules: HashMap>, 173 | pub lets: HashMap>, 174 | pub types: HashMap, 175 | pub externals: HashMap>, 176 | pub commands: Vec<(Symbol, Symbol)>, 177 | } 178 | 179 | impl Default for Program { 180 | fn default() -> Self { 181 | Self { 182 | modules: HashMap::new(), 183 | lets: HashMap::new(), 184 | types: HashMap::new(), 185 | externals: HashMap::new(), 186 | commands: Vec::new(), 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/infer/type.rs: -------------------------------------------------------------------------------- 1 | //! Inference of types 2 | 3 | use crate::{ 4 | context::Context, 5 | errors::TypeErrorKind, 6 | eval::{Eval, Quote}, 7 | r#virtual::Env, 8 | r#virtual::Virtual, 9 | real::{self, Real}, 10 | Index, Kind, Type, 11 | }; 12 | 13 | use super::Infer; 14 | use vulpi_syntax::{r#abstract, r#abstract::TypeKind}; 15 | 16 | impl Infer for r#abstract::Type { 17 | type Return = (Type, Kind); 18 | 19 | type Context<'a> = (&'a mut Context, Env); 20 | 21 | fn infer(&self, (ctx, env): Self::Context<'_>) -> Self::Return { 22 | env.set_current_span(self.span.clone()); 23 | 24 | match &self.data { 25 | TypeKind::Arrow(pi) => { 26 | let (typ, kind) = pi.left.infer((ctx, env.clone())); 27 | env.set_current_span(pi.left.span.clone()); 28 | ctx.subsumes(env.clone(), kind, Kind::typ()); 29 | 30 | let (body, kind) = pi.right.infer((ctx, env.clone())); 31 | env.set_current_span(pi.right.span.clone()); 32 | ctx.subsumes(env.clone(), kind, Kind::typ()); 33 | 34 | let typ = Type::new(crate::TypeKind::Arrow(real::Arrow { typ, body })); 35 | (typ, Kind::typ()) 36 | } 37 | TypeKind::Tuple(t) => { 38 | let mut types = Vec::new(); 39 | 40 | for ty in t { 41 | let (ty, kind) = ty.infer((ctx, env.clone())); 42 | ctx.subsumes(env.clone(), kind, Kind::typ()); 43 | types.push(ty); 44 | } 45 | 46 | (Type::tuple(types), Kind::typ()) 47 | } 48 | TypeKind::Application(app) => { 49 | let (ty, mut k) = app.func.infer((ctx, env.clone())); 50 | 51 | let mut args = Vec::new(); 52 | 53 | for arg in &app.args { 54 | env.set_current_span(arg.span.clone()); 55 | 56 | let (arg_ty, arg_kind) = arg.infer((ctx, env.clone())); 57 | 58 | args.push(arg_ty); 59 | 60 | if let Some((left, right)) = ctx.as_function(&env, k.deref()) { 61 | ctx.subsumes(env.clone(), arg_kind, left); 62 | k = right; 63 | } else { 64 | ctx.report( 65 | &env, 66 | TypeErrorKind::NotAFunction(env.clone(), k.quote(env.level)), 67 | ); 68 | return (Type::error(), Kind::error()); 69 | } 70 | } 71 | 72 | (Type::::application(ty, args), k) 73 | } 74 | TypeKind::Forall(forall) => { 75 | let mut env = env.clone(); 76 | let mut names = Vec::new(); 77 | 78 | for binder in &forall.params { 79 | let (name, ty) = binder.infer((ctx, env.clone())); 80 | env = env.add(Some(name.clone()), ty.eval(&env)); 81 | names.push((name, ty)); 82 | } 83 | 84 | let (ty, kind) = forall.body.infer((ctx, env)); 85 | 86 | let forall = names.into_iter().fold(ty, |body, (name, kind)| { 87 | Type::forall(real::Forall { name, kind, body }) 88 | }); 89 | 90 | (forall, kind) 91 | } 92 | TypeKind::TypeVariable(name) => { 93 | let Some((index, _, kind)) = env.find(name) else { 94 | ctx.report(&env, TypeErrorKind::CannotFind(name.clone())); 95 | return (Type::error(), Type::error()); 96 | }; 97 | 98 | (Type::bound(Index(index)), kind) 99 | } 100 | TypeKind::Type(name) => (Type::variable(name.clone()), ctx.modules.typ(name).kind), 101 | TypeKind::Unit => (Type::tuple(Vec::new()), Kind::typ()), 102 | TypeKind::Error => (Type::error(), Kind::error()), 103 | } 104 | } 105 | } 106 | 107 | impl Infer for r#abstract::TypeBinder { 108 | type Return = (vulpi_intern::Symbol, Type); 109 | 110 | type Context<'a> = (&'a mut Context, Env); 111 | 112 | fn infer(&self, (ctx, env): Self::Context<'_>) -> Self::Return { 113 | match self { 114 | r#abstract::TypeBinder::Implicit(n) => (n.clone(), ctx.hole(&env, Kind::typ())), 115 | r#abstract::TypeBinder::Explicit(n, k) => (n.clone(), k.infer(env.clone())), 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/vulpi-show/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{HashMap, HashSet}, 3 | fmt::Display, 4 | ops::Range, 5 | }; 6 | 7 | #[derive(Debug)] 8 | pub struct TreeDisplay { 9 | pub label: String, 10 | pub children: Vec, 11 | } 12 | 13 | impl TreeDisplay { 14 | pub fn pretty_print( 15 | &self, 16 | fmt: &mut std::fmt::Formatter, 17 | indent: String, 18 | last: bool, 19 | ) -> std::fmt::Result { 20 | let indent_now = format!("{}{}", indent, if last { "└" } else { "├" }); 21 | writeln!(fmt, "{}{}", indent_now, self.label)?; 22 | let indent = format!("{}{} ", indent, if last { " " } else { "│" }); 23 | for (index, child) in self.children.iter().enumerate() { 24 | child.pretty_print(fmt, indent.clone(), index == self.children.len() - 1)?; 25 | } 26 | Ok(()) 27 | } 28 | 29 | pub fn label(label: &str) -> Self { 30 | Self { 31 | label: label.to_string(), 32 | children: vec![], 33 | } 34 | } 35 | 36 | pub fn with(mut self, child: TreeDisplay) -> Self { 37 | self.children.push(child); 38 | self 39 | } 40 | } 41 | 42 | impl Display for TreeDisplay { 43 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 44 | self.pretty_print(fmt, "".to_string(), true) 45 | } 46 | } 47 | 48 | pub trait Show { 49 | fn show(&self) -> TreeDisplay; 50 | } 51 | 52 | impl Show for &T { 53 | fn show(&self) -> TreeDisplay { 54 | (*self).show() 55 | } 56 | } 57 | 58 | impl Show for usize { 59 | fn show(&self) -> TreeDisplay { 60 | TreeDisplay::label(&self.to_string()) 61 | } 62 | } 63 | 64 | impl Show for String { 65 | fn show(&self) -> TreeDisplay { 66 | TreeDisplay::label(self) 67 | } 68 | } 69 | 70 | impl Show for &str { 71 | fn show(&self) -> TreeDisplay { 72 | TreeDisplay::label(self) 73 | } 74 | } 75 | 76 | impl Show for Box { 77 | fn show(&self) -> TreeDisplay { 78 | self.as_ref().show() 79 | } 80 | } 81 | 82 | impl Show for Vec { 83 | fn show(&self) -> TreeDisplay { 84 | let mut node = TreeDisplay::label("Vec"); 85 | for child in self { 86 | node = node.with(child.show()); 87 | } 88 | node 89 | } 90 | } 91 | 92 | impl Show for HashMap { 93 | fn show(&self) -> TreeDisplay { 94 | let mut node = TreeDisplay::label("HashMap"); 95 | for (key, value) in self { 96 | node = node.with( 97 | TreeDisplay::label("Entry") 98 | .with(key.show()) 99 | .with(value.show()), 100 | ); 101 | } 102 | node 103 | } 104 | } 105 | 106 | impl Show for HashSet { 107 | fn show(&self) -> TreeDisplay { 108 | let mut node = TreeDisplay::label("HashSet"); 109 | for child in self { 110 | node = node.with(child.show()); 111 | } 112 | node 113 | } 114 | } 115 | 116 | impl Show for (T, U) { 117 | fn show(&self) -> TreeDisplay { 118 | let mut node = TreeDisplay::label("Tuple"); 119 | node = node.with(self.0.show()); 120 | node = node.with(self.1.show()); 121 | node 122 | } 123 | } 124 | 125 | impl Show for (T, U, V) { 126 | fn show(&self) -> TreeDisplay { 127 | let mut node = TreeDisplay::label("Tuple"); 128 | node = node.with(self.0.show()); 129 | node = node.with(self.1.show()); 130 | node = node.with(self.2.show()); 131 | node 132 | } 133 | } 134 | 135 | impl Show for Range { 136 | fn show(&self) -> TreeDisplay { 137 | TreeDisplay::label(&format!("Range({:?}..{:?})", self.start, self.end)) 138 | } 139 | } 140 | 141 | impl Show for bool { 142 | fn show(&self) -> TreeDisplay { 143 | TreeDisplay::label(&self.to_string()) 144 | } 145 | } 146 | 147 | impl Show for Option { 148 | fn show(&self) -> TreeDisplay { 149 | match self { 150 | Some(value) => value.show(), 151 | None => TreeDisplay::label("None"), 152 | } 153 | } 154 | } 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | use super::*; 159 | 160 | #[test] 161 | fn test() { 162 | let node = TreeDisplay::label("root") 163 | .with(TreeDisplay::label("child1")) 164 | .with(TreeDisplay::label("child2").with(TreeDisplay::label("child3"))); 165 | println!("{}", node); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 30 | 31 | 33 | 34 | 36 | 37 | 39 | { 40 | "associatedIndex": 4 41 | } 42 | 43 | 44 | 47 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 1700257974817 74 | 85 | 86 | 87 | 88 | 90 | -------------------------------------------------------------------------------- /crates/vulpi-report/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Module for handling errors that can occur during the compilation process. It's used to report 2 | //! errors to the user. 3 | 4 | use std::{cell::RefCell, rc::Rc}; 5 | 6 | use renderer::{classic::Classic, Renderer}; 7 | use vulpi_location::{FileId, Span}; 8 | 9 | pub mod hash; 10 | pub mod renderer; 11 | 12 | /// A type for representing the severity of a [Diagnostic]. 13 | pub enum Severity { 14 | Error, 15 | Warning, 16 | Info, 17 | } 18 | 19 | /// A type for representing the color of a [Word]. It's all numerated because it's easier to change 20 | /// the color of a word according to what the user wants. 21 | pub enum Color { 22 | Fst, 23 | Snd, 24 | Trd, 25 | Fth, 26 | } 27 | 28 | /// A type for representing the style of a [Word]. 29 | pub enum Style { 30 | Bold, 31 | Dimmed, 32 | Normal, 33 | } 34 | 35 | /// A type for representing a word in a [Text]. 36 | pub struct Word(Style, Color, String); 37 | 38 | /// A type for representing a text. It's used to generate error messages. 39 | pub enum Text { 40 | Phrase(Vec), 41 | Styled(Style, String), 42 | Colored(Color, String), 43 | Text(String), 44 | Break, 45 | } 46 | 47 | impl From<&str> for Text { 48 | fn from(s: &str) -> Self { 49 | Text::Text(s.to_owned()) 50 | } 51 | } 52 | 53 | impl From for Text { 54 | fn from(s: String) -> Self { 55 | Text::Text(s) 56 | } 57 | } 58 | 59 | /// A position in the source code that has or not a message. It's used to generate underlined parts 60 | /// with messages. 61 | pub struct Marker { 62 | pub position: Span, 63 | pub subtitle: Option, 64 | } 65 | 66 | /// Errors that can occur during the compilation process. 67 | pub trait IntoDiagnostic { 68 | fn code(&self) -> Option { 69 | None 70 | } 71 | 72 | fn hint(&self) -> Option { 73 | None 74 | } 75 | 76 | fn message(&self) -> Text; 77 | 78 | fn severity(&self) -> Severity; 79 | 80 | fn location(&self) -> Span; 81 | } 82 | 83 | /// A diagnostic with reference counting. It is a wrapper around a [IntoDiagnostic] trait object. 84 | #[derive(Clone)] 85 | pub struct Diagnostic(Rc); 86 | 87 | impl Diagnostic { 88 | pub fn new(diagnostic: impl IntoDiagnostic + 'static) -> Self { 89 | Self(Rc::new(diagnostic)) 90 | } 91 | 92 | pub fn code(&self) -> Option { 93 | self.0.code() 94 | } 95 | 96 | pub fn hint(&self) -> Option { 97 | self.0.hint() 98 | } 99 | 100 | pub fn message(&self) -> Text { 101 | self.0.message() 102 | } 103 | 104 | pub fn severity(&self) -> Severity { 105 | self.0.severity() 106 | } 107 | 108 | pub fn location(&self) -> Span { 109 | self.0.location() 110 | } 111 | } 112 | 113 | /// A reporter is a structure that gets and record errors. It's used to store and report errors to 114 | /// the user. 115 | pub trait Reporter { 116 | /// Reports a new error to the reporter. 117 | fn report(&mut self, diagnostic: Diagnostic); 118 | 119 | /// Gets all the diagnostics of a file. 120 | fn diagnostics(&self, file: FileId) -> &[Diagnostic]; 121 | 122 | /// Get all diagnostics 123 | fn all_diagnostics(&self) -> Vec; 124 | 125 | /// Clears all the diagnostics of a file. It's used for LSP. 126 | fn clear(&mut self, file: FileId); 127 | 128 | /// Check if has errors 129 | fn has_errors(&self) -> bool; 130 | } 131 | 132 | /// A structure that stores and reports errors to the user. It's inside a Rc or Arc because it 133 | /// needs to be shared between all steps of the compiler 134 | #[derive(Clone)] 135 | pub struct Report(Rc>); 136 | 137 | impl Report { 138 | pub fn new(reporter: impl Reporter + 'static) -> Self { 139 | Self(Rc::new(RefCell::new(reporter))) 140 | } 141 | 142 | pub fn report(&self, diagnostic: Diagnostic) { 143 | self.0.borrow_mut().report(diagnostic); 144 | } 145 | 146 | pub fn diagnostics(&self, file: FileId) -> Vec { 147 | self.0.borrow().diagnostics(file).to_vec() 148 | } 149 | 150 | pub fn all_diagnostics(&self) -> Vec { 151 | self.0.borrow().all_diagnostics() 152 | } 153 | 154 | pub fn clear(&self, file: FileId) { 155 | self.0.borrow_mut().clear(file); 156 | } 157 | 158 | pub fn has_errors(&self) -> bool { 159 | self.0.borrow().has_errors() 160 | } 161 | 162 | pub fn to_stderr(&self, ctx: Classic) { 163 | if self.has_errors() { 164 | eprintln!(); 165 | 166 | for diagnostic in self.all_diagnostics().iter().rev() { 167 | diagnostic.render(&ctx, &mut std::io::stderr()).unwrap(); 168 | } 169 | } 170 | } 171 | } 172 | 173 | pub fn hash_reporter() -> Report { 174 | Report::new(hash::HashReporter::new()) 175 | } 176 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/eval.rs: -------------------------------------------------------------------------------- 1 | //! Module for evaluation and quotation of real and virtual [Type]s. The main difference between 2 | //! the two is that virtual types contain closures and can be executed while real types are just 3 | //! types. 4 | 5 | use super::{ 6 | r#virtual, 7 | r#virtual::Env, 8 | r#virtual::Virtual, 9 | real::{self, Real}, 10 | Hole, HoleInner, Level, Type, TypeKind, 11 | }; 12 | 13 | /// Trait for evaluation of types. 14 | pub trait Eval { 15 | fn eval(&self, env: &Env) -> T; 16 | } 17 | 18 | impl Eval> for Type { 19 | fn eval(&self, env: &Env) -> Type { 20 | match self.as_ref() { 21 | TypeKind::Arrow(pi) => Type::new(TypeKind::Arrow(r#virtual::Pi { 22 | typ: pi.typ.clone().eval(env), 23 | body: pi.body.clone().eval(env), 24 | })), 25 | TypeKind::Forall(f) => Type::new(TypeKind::Forall(r#virtual::Forall { 26 | name: f.name.clone(), 27 | kind: f.kind.clone().eval(env), 28 | body: r#virtual::Closure { 29 | env: env.clone(), 30 | body: f.body.clone(), 31 | }, 32 | })), 33 | TypeKind::Type => Type::new(TypeKind::Type), 34 | TypeKind::Hole(r) => Type::new(TypeKind::Hole(r.clone())), 35 | TypeKind::Variable(v) => Type::new(TypeKind::Variable(v.clone())), 36 | TypeKind::Bound(v) => env.types[v.0].clone(), 37 | TypeKind::Tuple(v) => Type::new(TypeKind::Tuple(v.clone().eval(env))), 38 | TypeKind::Application(v, u) => Type::new(TypeKind::Application( 39 | v.clone().eval(env), 40 | u.clone().eval(env), 41 | )), 42 | TypeKind::Error => Type::new(TypeKind::Error), 43 | TypeKind::Qualified(from, to) => { 44 | let from = from.clone().eval(env); 45 | let to = to.clone().eval(env); 46 | Type::new(TypeKind::Qualified(from, to)) 47 | } 48 | TypeKind::Constraint => Type::new(TypeKind::Constraint), 49 | } 50 | } 51 | } 52 | 53 | impl Eval>> for Vec> { 54 | fn eval(&self, env: &Env) -> Vec> { 55 | self.iter().map(|v| v.eval(env)).collect() 56 | } 57 | } 58 | 59 | impl Eval> for Hole { 60 | fn eval(&self, env: &Env) -> Type { 61 | match &*self.0.borrow() { 62 | HoleInner::Empty(s, k, l) => { 63 | Type::new(TypeKind::Hole(Hole::empty(s.clone(), k.eval(env), *l))) 64 | } 65 | HoleInner::Filled(f) => f.clone().eval(env), 66 | } 67 | } 68 | } 69 | 70 | /// Quotation of types. 71 | pub trait Quote { 72 | fn quote(&self, lvl: Level) -> T; 73 | } 74 | 75 | impl Quote> for Hole { 76 | fn quote(&self, depth: Level) -> Type { 77 | match &*self.0.borrow() { 78 | HoleInner::Empty(_, _, _) => Type::new(TypeKind::Hole(self.clone())), 79 | HoleInner::Filled(f) => f.clone().quote(depth), 80 | } 81 | } 82 | } 83 | 84 | impl Quote>> for Vec> { 85 | fn quote(&self, lvl: Level) -> Vec> { 86 | self.iter().map(|v| v.quote(lvl)).collect() 87 | } 88 | } 89 | 90 | impl Quote> for Type { 91 | fn quote(&self, depth: Level) -> Type { 92 | match self.as_ref() { 93 | TypeKind::Type => Type::new(TypeKind::Type), 94 | TypeKind::Arrow(pi) => Type::new(TypeKind::Arrow(real::Arrow { 95 | typ: pi.typ.clone().quote(depth), 96 | body: pi.body.clone().quote(depth), 97 | })), 98 | TypeKind::Forall(f) => Type::new(TypeKind::Forall(real::Forall { 99 | name: f.name.clone(), 100 | kind: f.kind.clone().quote(depth), 101 | body: f 102 | .body 103 | .apply_local(Some(f.name.clone()), Type::new(TypeKind::Bound(depth))) 104 | .quote(depth.inc()), 105 | })), 106 | TypeKind::Hole(h) => h.quote(depth), 107 | TypeKind::Variable(v) => Type::new(TypeKind::Variable(v.clone())), 108 | TypeKind::Bound(i) => Type::new(TypeKind::Bound(Level::to_index(depth, *i))), 109 | TypeKind::Tuple(p) => Type::new(TypeKind::Tuple(p.quote(depth))), 110 | TypeKind::Application(func, arg) => { 111 | let func = func.quote(depth); 112 | let arg = arg.quote(depth); 113 | Type::new(TypeKind::Application(func, arg)) 114 | } 115 | TypeKind::Error => Type::new(TypeKind::Error), 116 | TypeKind::Qualified(from, to) => { 117 | let from = from.clone().quote(depth); 118 | let to = to.clone().quote(depth); 119 | Type::new(TypeKind::Qualified(from, to)) 120 | } 121 | TypeKind::Constraint => Type::new(TypeKind::Constraint), 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/context.rs: -------------------------------------------------------------------------------- 1 | //! This file declares a mutable environment that is useful to keep track of information that does 2 | //! not need to be immutable like the Env. 3 | 4 | use vulpi_intern::Symbol; 5 | use vulpi_report::{Diagnostic, Report}; 6 | use vulpi_syntax::{elaborated, r#abstract::Qualified}; 7 | 8 | use crate::{ 9 | errors::{TypeError, TypeErrorKind}, 10 | module::Modules, 11 | r#virtual::Env, 12 | r#virtual::Pi, 13 | r#virtual::Virtual, 14 | real::Real, 15 | HoleInner, State, Type, TypeKind, 16 | }; 17 | 18 | /// A mutable context that is used differently from [Env]. It is used to keep data between every 19 | /// thing inside the type checker. 20 | pub struct Context { 21 | pub counter: usize, 22 | pub reporter: Report, 23 | pub modules: Modules, 24 | pub elaborated: elaborated::Program>, 25 | pub errored: bool, 26 | } 27 | 28 | impl Context { 29 | pub fn new(reporter: Report) -> Self { 30 | Self { 31 | counter: 0, 32 | reporter, 33 | modules: Default::default(), 34 | elaborated: Default::default(), 35 | errored: false, 36 | } 37 | } 38 | 39 | pub fn report(&mut self, env: &Env, kind: TypeErrorKind) { 40 | self.errored = true; 41 | self.reporter.report(Diagnostic::new(TypeError { 42 | span: env.span.borrow().clone(), 43 | kind, 44 | })); 45 | } 46 | 47 | fn inc_counter(&mut self) -> usize { 48 | self.counter += 1; 49 | self.counter - 1 50 | } 51 | 52 | pub fn find_prelude_type(&mut self, name: &str, env: Env) -> Type { 53 | let path = Symbol::intern("Prelude"); 54 | let name = Symbol::intern(name); 55 | if self.modules.get(&path).types.get(&name).is_some() { 56 | Type::variable(Qualified { path, name }) 57 | } else { 58 | self.report(&env, crate::errors::TypeErrorKind::CannotFind(name)); 59 | Type::error() 60 | } 61 | } 62 | 63 | /// Creates a new name with the prefix `t_` and a unique number. 64 | pub fn new_name(&mut self) -> Symbol { 65 | Symbol::intern(&format!("t_{}", self.inc_counter())) 66 | } 67 | 68 | /// Creates a new hole that is a type that is not yet known 69 | pub fn hole(&mut self, env: &Env, kind: Type) -> Type { 70 | env.hole(kind, self.new_name()) 71 | } 72 | 73 | pub fn as_function( 74 | &mut self, 75 | env: &Env, 76 | typ: Type, 77 | ) -> Option<(Type, Type)> { 78 | match typ.deref().as_ref() { 79 | TypeKind::Arrow(pi) => Some((pi.typ.clone(), pi.body.clone())), 80 | TypeKind::Error => Some((typ.clone(), typ.clone())), 81 | TypeKind::Forall(_) => { 82 | let typ = self.instantiate(env, &typ); 83 | self.as_function(env, typ) 84 | } 85 | TypeKind::Hole(empty) => { 86 | let hole_inner = empty.0.borrow().clone(); 87 | if let HoleInner::Empty(_, kind, _) = hole_inner { 88 | let hole_a = self.hole(env, kind.clone()); 89 | let hole_b = self.hole(env, kind); 90 | 91 | empty.fill(Type::new(TypeKind::Arrow(Pi { 92 | typ: hole_a.clone(), 93 | body: hole_b.clone(), 94 | }))); 95 | 96 | Some((hole_a, hole_b)) 97 | } else { 98 | unreachable!() 99 | } 100 | } 101 | _ => None, 102 | } 103 | } 104 | 105 | /// Instantiates a poly type to a monotype. 106 | pub fn instantiate(&mut self, env: &Env, typ: &Type) -> Type { 107 | match typ.deref().as_ref() { 108 | TypeKind::Forall(forall) => { 109 | let arg = env.hole(forall.kind.clone(), forall.name.clone()); 110 | let kind = forall.kind.clone(); 111 | // Applies the body using the hole argument. 112 | forall.body.apply(Some(forall.name.clone()), arg, kind) 113 | } 114 | _ => typ.clone(), 115 | } 116 | } 117 | 118 | pub fn instantiate_with(&mut self, typ: &Type, arg: Type) -> Type { 119 | match typ.deref().as_ref() { 120 | TypeKind::Forall(forall) => { 121 | let kind = forall.kind.clone(); 122 | forall.body.apply(Some(forall.name.clone()), arg, kind) 123 | } 124 | _ => typ.clone(), 125 | } 126 | } 127 | 128 | pub fn instantiate_with_arguments( 129 | &mut self, 130 | ty: &Type, 131 | args: Vec>, 132 | ) -> Type { 133 | let mut typ = ty.clone(); 134 | for arg in args { 135 | typ = self.instantiate_with(&typ, arg); 136 | } 137 | typ 138 | } 139 | 140 | pub fn instantiate_all(&mut self, env: &Env, typ: &Type) -> Type { 141 | match typ.deref().as_ref() { 142 | TypeKind::Forall(_) => { 143 | let res = self.instantiate(env, typ); 144 | self.instantiate_all(env, &res) 145 | } 146 | _ => typ.clone(), 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /crates/vulpi-build/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Facilities to build a entire crate of vulpi files. This module is responsible for building the 2 | //! crate from the source files and resolving the modules. 3 | 4 | use std::{collections::HashMap, path::PathBuf, fs::File, rc::Rc, cell::RefCell}; 5 | 6 | use resw::Writer; 7 | use vulpi_intern::Symbol; 8 | use vulpi_ir::{transform, inline, dead_code, uncurry}; 9 | use vulpi_location::{FileId, Span}; 10 | use vulpi_report::Report; 11 | 12 | use vulpi_resolver::{ 13 | cycle::DepHolder, 14 | dependencies::{self, Dependencies}, 15 | Context, Module, 16 | }; 17 | 18 | use vulpi_show::Show; 19 | use vulpi_syntax::concrete::tree::Program; 20 | use vulpi_typer::declare::{Programs, Declare}; 21 | use vulpi_vfs::{path::Path, FileSystem}; 22 | 23 | pub mod real; 24 | 25 | pub enum Interface { 26 | Compiled(Module, Dependencies), 27 | Uncompiled(Program), 28 | } 29 | 30 | pub struct ProjectCompiler { 31 | pub name: Symbol, 32 | pub fs: FS, 33 | pub reporter: Report, 34 | } 35 | 36 | impl ProjectCompiler { 37 | fn load(&mut self, _span: Span, path: FS::Path) -> Option { 38 | if let Ok(id) = self.fs.load(path) { 39 | Some(id) 40 | } else { 41 | None 42 | } 43 | } 44 | 45 | fn parse(&mut self, id: FileId) -> Program { 46 | let source = self.fs.read(id).unwrap(); 47 | vulpi_parser::parse(self.reporter.clone(), id, &source) 48 | } 49 | 50 | pub fn find_dependencies( 51 | &mut self, 52 | bag: &mut HashMap, 53 | deps: Dependencies, 54 | ) { 55 | for (path, span) in deps.imported { 56 | if !bag.contains_key(&path) { 57 | if let Some(id) = self.load(span.clone(), self.fs.from_src_path(path.clone())) { 58 | let program = self.parse(id); 59 | let deps = dependencies::dependencies(self.name.clone(), &program); 60 | bag.insert(path.clone(), (Interface::Uncompiled(program), deps.clone())); 61 | self.find_dependencies(bag, deps); 62 | } 63 | } 64 | } 65 | } 66 | 67 | pub fn compile(&mut self, module: Symbol, path: FS::Path, output: PathBuf) { 68 | // TODO: Fix this error :( I can't now because it would require changes 69 | // to the vulpi-report module. Good luck Sofia from the future! 70 | 71 | let root = self.fs.load(path).unwrap(); 72 | let parsed = self.parse(root); 73 | 74 | let path = Path { 75 | segments: vec![module.clone(), Symbol::intern("Main")], 76 | }; 77 | 78 | let mut bag = HashMap::new(); 79 | let deps = dependencies::dependencies(self.name.clone(), &parsed); 80 | bag.insert(path.clone(), (Interface::Uncompiled(parsed), deps.clone())); 81 | 82 | self.find_dependencies(&mut bag, deps); 83 | 84 | let mut modules = HashMap::new(); 85 | 86 | let available: Rc>> = Default::default(); 87 | 88 | for (path, (program, deps)) in bag { 89 | match program { 90 | Interface::Compiled(module, _) => { 91 | modules.insert(path, (module, None, deps)); 92 | } 93 | Interface::Uncompiled(parsed) => { 94 | let context = Context::new(available.clone(), path.clone(), self.reporter.clone()); 95 | let solved = vulpi_resolver::resolve(&context, parsed); 96 | modules.insert( 97 | path, 98 | (context.module.clone(), Some((context, solved)), deps), 99 | ); 100 | } 101 | } 102 | } 103 | 104 | for (module, _, _) in modules.values() { 105 | let path = module.name().clone(); 106 | let mut borrow_mut = available.borrow_mut(); 107 | borrow_mut.insert(path, module.clone()); 108 | } 109 | 110 | let mut programs = vec![]; 111 | 112 | let mut dep = DepHolder::default(); 113 | 114 | for (_, ctx, _) in modules.into_values() { 115 | if let Some((ctx, resolver)) = ctx { 116 | let program = resolver.eval(ctx.clone()); 117 | dep.register(&program); 118 | programs.push(program); 119 | } 120 | } 121 | 122 | dep.report_cycles(self.reporter.clone()); 123 | 124 | let mut ctx = vulpi_typer::Context::new(self.reporter.clone()); 125 | let env = vulpi_typer::Env::default(); 126 | 127 | let programs = Programs(programs); 128 | println!("{}", programs.0[0].show()); 129 | 130 | Declare::declare(&programs, (&mut ctx, env.clone())); 131 | let programs = Declare::define(&programs, (&mut ctx, env)); 132 | 133 | 134 | if !self.reporter.has_errors() { 135 | let mut res = transform::Transform::transform(&vulpi_ir::transform::Programs(programs), &mut Default::default()); 136 | 137 | uncurry::uncurry(&mut res); 138 | inline::inline(&mut res); 139 | dead_code::dead_code_remove(&mut res); 140 | 141 | let js = vulpi_js::Transform::transform(vulpi_js::Programs(res), &mut Default::default()); 142 | let f = File::create(output).unwrap(); 143 | let mut w = Writer::new(f); 144 | 145 | w.write_program(&js).unwrap(); 146 | } 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/abstract.expect: -------------------------------------------------------------------------------- 1 | └Module 2 | └decls 3 | └Vec 4 | ├Module 5 | │ └ModuleDecl 6 | │ ├namespace 7 | │ │ └Symbol: Ata 8 | │ ├visibility 9 | │ │ └Public 10 | │ ├name 11 | │ │ └Symbol: Ata 12 | │ └decls 13 | │ └Vec 14 | │ ├Type 15 | │ │ └TypeDecl 16 | │ │ ├namespace 17 | │ │ │ └Symbol: Ata.A 18 | │ │ ├visibility 19 | │ │ │ └Public 20 | │ │ ├name 21 | │ │ │ └Qualified 22 | │ │ │ ├Ata 23 | │ │ │ └A 24 | │ │ ├binders 25 | │ │ │ └Vec 26 | │ │ └def 27 | │ │ └Abstract 28 | │ └Type 29 | │ └TypeDecl 30 | │ ├namespace 31 | │ │ └Symbol: Ata.B 32 | │ ├visibility 33 | │ │ └Public 34 | │ ├name 35 | │ │ └Qualified 36 | │ │ ├Ata 37 | │ │ └B 38 | │ ├binders 39 | │ │ └Vec 40 | │ └def 41 | │ └Abstract 42 | └Module 43 | └ModuleDecl 44 | ├namespace 45 | │ └Symbol: Beta 46 | ├visibility 47 | │ └Private 48 | ├name 49 | │ └Symbol: Beta 50 | └decls 51 | └Vec 52 | ├Type 53 | │ └TypeDecl 54 | │ ├namespace 55 | │ │ └Symbol: Beta.C 56 | │ ├visibility 57 | │ │ └Private 58 | │ ├name 59 | │ │ └Qualified 60 | │ │ ├Beta 61 | │ │ └C 62 | │ ├binders 63 | │ │ └Vec 64 | │ └def 65 | │ └Abstract 66 | └Let 67 | └LetDecl 68 | ├visibility 69 | │ └Private 70 | ├name 71 | │ └Symbol: ata 72 | ├binders 73 | │ └Vec 74 | │ ├Binder 75 | │ │ ├pattern 76 | │ │ │ └Spanned 77 | │ │ │ ├106~107 78 | │ │ │ └Variable 79 | │ │ │ └Symbol: x 80 | │ │ └ty 81 | │ │ └Spanned 82 | │ │ ├109~110 83 | │ │ └Type 84 | │ │ └Qualified 85 | │ │ ├Ata 86 | │ │ └A 87 | │ └Binder 88 | │ ├pattern 89 | │ │ └Spanned 90 | │ │ ├113~114 91 | │ │ └Variable 92 | │ │ └Symbol: y 93 | │ └ty 94 | │ └Spanned 95 | │ ├116~117 96 | │ └Type 97 | │ └Qualified 98 | │ ├Ata 99 | │ └B 100 | ├ret 101 | │ └Tuple 102 | │ ├None 103 | │ └Spanned 104 | │ ├121~122 105 | │ └Type 106 | │ └Qualified 107 | │ ├Beta 108 | │ └C 109 | └body 110 | └LetMode 111 | └cases 112 | └Vec 113 | └LetCase 114 | └pattern 115 | └PatternArm 116 | ├patterns 117 | │ └Vec 118 | ├expr 119 | │ └Spanned 120 | │ ├125~126 121 | │ └Literal 122 | │ └Spanned 123 | │ ├125~126 124 | │ └Integer 125 | │ └Symbol: 2 126 | └guard 127 | └None 128 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/concrete/expr.rs: -------------------------------------------------------------------------------- 1 | use pattern::Pattern; 2 | use tree::{DoExpr, Literal, Type}; 3 | use vulpi_location::Spanned; 4 | use vulpi_macros::Show; 5 | 6 | use crate::tokens::Token; 7 | 8 | use super::*; 9 | 10 | #[derive(Show, Clone)] 11 | pub enum Operator { 12 | Add(Token), 13 | Sub(Token), 14 | Mul(Token), 15 | Div(Token), 16 | Rem(Token), 17 | And(Token), 18 | Or(Token), 19 | Xor(Token), 20 | Not(Token), 21 | Eq(Token), 22 | Neq(Token), 23 | Lt(Token), 24 | Gt(Token), 25 | Le(Token), 26 | Ge(Token), 27 | Shl(Token), 28 | Shr(Token), 29 | Pipe(Token), 30 | Concat(Token), 31 | } 32 | 33 | impl Operator { 34 | pub fn get_span(&self) -> Span { 35 | match self { 36 | Operator::Add(t) => t.value.span.clone(), 37 | Operator::Sub(t) => t.value.span.clone(), 38 | Operator::Mul(t) => t.value.span.clone(), 39 | Operator::Div(t) => t.value.span.clone(), 40 | Operator::Rem(t) => t.value.span.clone(), 41 | Operator::And(t) => t.value.span.clone(), 42 | Operator::Or(t) => t.value.span.clone(), 43 | Operator::Xor(t) => t.value.span.clone(), 44 | Operator::Not(t) => t.value.span.clone(), 45 | Operator::Eq(t) => t.value.span.clone(), 46 | Operator::Neq(t) => t.value.span.clone(), 47 | Operator::Lt(t) => t.value.span.clone(), 48 | Operator::Gt(t) => t.value.span.clone(), 49 | Operator::Le(t) => t.value.span.clone(), 50 | Operator::Ge(t) => t.value.span.clone(), 51 | Operator::Shl(t) => t.value.span.clone(), 52 | Operator::Shr(t) => t.value.span.clone(), 53 | Operator::Pipe(t) => t.value.span.clone(), 54 | Operator::Concat(t) => t.value.span.clone(), 55 | } 56 | } 57 | } 58 | 59 | #[derive(Show, Clone)] 60 | pub struct LambdaExpr { 61 | pub lambda: Token, 62 | pub patterns: Vec>, 63 | pub arrow: Token, 64 | pub expr: Box, 65 | } 66 | 67 | #[derive(Show, Clone)] 68 | pub struct ListExpr { 69 | pub left_bracket: Token, 70 | pub values: Vec<(Box, Option)>, 71 | pub right_bracket: Token, 72 | } 73 | 74 | #[derive(Show, Clone)] 75 | pub struct ApplicationExpr { 76 | pub func: Box, 77 | pub args: Vec>, 78 | } 79 | 80 | #[derive(Show, Clone)] 81 | pub struct ProjectionExpr { 82 | pub expr: Box, 83 | pub dot: Token, 84 | pub field: Lower, 85 | } 86 | 87 | #[derive(Show, Clone)] 88 | pub struct BinaryExpr { 89 | pub left: Box, 90 | pub op: Operator, 91 | pub right: Box, 92 | } 93 | 94 | #[derive(Show, Clone)] 95 | pub struct IfExpr { 96 | pub if_: Token, 97 | pub cond: Box, 98 | pub then: Token, 99 | pub then_expr: Box, 100 | pub else_: Token, 101 | pub else_expr: Box, 102 | } 103 | 104 | #[derive(Show, Clone)] 105 | pub struct PatternArm { 106 | pub patterns: Vec<(Box, Option)>, 107 | pub arrow: Token, 108 | pub expr: Box, 109 | pub guard: Option<(Token, Box)>, 110 | } 111 | 112 | #[derive(Show, Clone)] 113 | pub struct WhenExpr { 114 | pub when: Token, 115 | pub scrutinee: Vec<(Box, Option)>, 116 | pub is: Token, 117 | pub arms: Vec, 118 | } 119 | 120 | #[derive(Show, Clone)] 121 | pub struct AnnotationExpr { 122 | pub expr: Box, 123 | pub colon: Token, 124 | pub typ: Box, 125 | } 126 | 127 | #[derive(Show, Clone)] 128 | pub struct LetExpr { 129 | pub let_: Token, 130 | pub pattern: Box, 131 | pub eq: Token, 132 | pub body: Box, 133 | pub in_: Token, 134 | pub value: Box, 135 | } 136 | 137 | #[derive(Show, Clone)] 138 | pub struct Attribute { 139 | pub name: Upper, 140 | pub eq: Token, 141 | pub value: Box, 142 | } 143 | 144 | #[derive(Show, Clone)] 145 | pub struct HtmlNode { 146 | pub left_angle: Token, 147 | pub name: Lower, 148 | pub attributes: Vec, 149 | pub right_angle: Token, 150 | pub children: Vec, 151 | pub left_angle_slash: Token, 152 | pub name_end: Lower, 153 | pub right_angle_end: Token, 154 | } 155 | 156 | #[derive(Show, Clone)] 157 | pub struct RecordField { 158 | pub name: Lower, 159 | pub eq: Token, 160 | pub expr: Box, 161 | } 162 | 163 | #[derive(Show, Clone)] 164 | pub struct RecordInstance { 165 | pub name: Path, 166 | pub left_brace: Token, 167 | pub fields: Vec<(RecordField, Option)>, 168 | pub right_brace: Token, 169 | } 170 | 171 | #[derive(Show, Clone)] 172 | pub struct RecordUpdate { 173 | pub expr: Box, 174 | pub left_brace: Token, 175 | pub fields: Vec<(RecordField, Option)>, 176 | pub right_brace: Token, 177 | } 178 | 179 | pub type Tuple = Parenthesis>, Option)>>; 180 | 181 | #[derive(Show, Clone)] 182 | pub enum ExprKind { 183 | Lambda(LambdaExpr), 184 | List(ListExpr), 185 | Application(ApplicationExpr), 186 | HtmlNode(HtmlNode), 187 | 188 | Variable(Lower), 189 | Constructor(Path), 190 | Function(Path), 191 | 192 | Projection(ProjectionExpr), 193 | Binary(BinaryExpr), 194 | Let(LetExpr), 195 | When(WhenExpr), 196 | Do(DoExpr), 197 | Literal(Literal), 198 | 199 | Annotation(AnnotationExpr), 200 | RecordInstance(RecordInstance), 201 | RecordUpdate(RecordUpdate), 202 | 203 | Parenthesis(Parenthesis<(Box>, Option)>), 204 | Tuple(Tuple), 205 | } 206 | 207 | pub type Expr = Spanned; 208 | -------------------------------------------------------------------------------- /crates/vulpi-report/src/renderer/classic.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use vulpi_vfs::FileSystem; 4 | use yansi::Paint; 5 | 6 | use crate::{renderer::LineGuide, Color, Diagnostic, Style, Text, Word}; 7 | 8 | use super::Renderer; 9 | 10 | pub struct Classic<'a> { 11 | fs: &'a dyn FileSystem, 12 | cwd: PathBuf, 13 | } 14 | 15 | impl<'a> Classic<'a> { 16 | pub fn new(fs: &'a (dyn FileSystem + 'static), cwd: PathBuf) -> Self { 17 | Self { fs, cwd } 18 | } 19 | } 20 | 21 | fn get_paint(color: &Color) -> fn(String) -> yansi::Paint { 22 | match color { 23 | Color::Fst => Paint::red, 24 | Color::Snd => Paint::yellow, 25 | Color::Trd => Paint::blue, 26 | Color::Fth => Paint::green, 27 | } 28 | } 29 | 30 | impl<'a> Renderer> for (&Color, &str) { 31 | fn render(&self, _: &Classic<'a>, writer: &mut impl std::io::Write) -> std::io::Result<()> { 32 | let paint = get_paint(self.0); 33 | write!(writer, "{}", paint(self.1.to_string())) 34 | } 35 | } 36 | 37 | impl<'a> Renderer> for (&Style, &str) { 38 | fn render(&self, _: &Classic<'a>, writer: &mut impl std::io::Write) -> std::io::Result<()> { 39 | match self.0 { 40 | Style::Bold => write!(writer, "{}", Paint::new(self.1).bold()), 41 | Style::Dimmed => write!(writer, "{}", Paint::new(self.1).dimmed()), 42 | Style::Normal => write!(writer, "{}", self.1), 43 | } 44 | } 45 | } 46 | 47 | impl<'a> Renderer> for Word { 48 | fn render(&self, _: &Classic<'a>, writer: &mut impl std::io::Write) -> std::io::Result<()> { 49 | let Word(style, color, text) = self; 50 | 51 | let paint = get_paint(color)(text.to_string()); 52 | 53 | let paint = match style { 54 | Style::Bold => paint.bold(), 55 | Style::Dimmed => paint.dimmed(), 56 | Style::Normal => paint, 57 | }; 58 | 59 | write!(writer, "{}", paint) 60 | } 61 | } 62 | 63 | impl<'a> Renderer> for Text { 64 | fn render(&self, ctx: &Classic<'a>, writer: &mut impl std::io::Write) -> std::io::Result<()> { 65 | match self { 66 | Text::Phrase(words) => { 67 | for (i, word) in words.iter().enumerate() { 68 | word.render(ctx, writer)?; 69 | 70 | if i != words.len() - 1 { 71 | write!(writer, " ")?; 72 | } 73 | } 74 | 75 | Ok(()) 76 | } 77 | Text::Styled(style, t) => (style, t.as_str()).render(ctx, writer), 78 | Text::Colored(color, t) => (color, t.as_str()).render(ctx, writer), 79 | Text::Text(text) => write!(writer, "{}", Paint::new(text).bold()), 80 | Text::Break => writeln!(writer), 81 | } 82 | } 83 | } 84 | 85 | impl<'a> Renderer> for Diagnostic { 86 | fn render(&self, ctx: &Classic<'a>, writer: &mut impl std::io::Write) -> std::io::Result<()> { 87 | // At this point we are probably sure that the file exists, so we can unwrap. 88 | let path = ctx.fs.path(self.location().file).unwrap(); 89 | let relative = path.strip_prefix(&ctx.cwd).unwrap(); 90 | 91 | let content = ctx.fs.read(self.location().file).unwrap(); 92 | 93 | let range = self.location(); 94 | 95 | let line_guide = LineGuide::new(&content); 96 | 97 | let start = line_guide.to_line_and_column(range.start).unwrap(); 98 | let end = line_guide.to_line_and_column(range.end).unwrap(); 99 | 100 | write!( 101 | writer, 102 | " {} ", 103 | yansi::Color::White 104 | .style() 105 | .bg(yansi::Color::Red) 106 | .paint(" ERROR ") 107 | )?; 108 | 109 | self.message().render(ctx, writer)?; 110 | 111 | let guide = Paint::new("┌─>").fg(yansi::Color::Cyan).dimmed(); 112 | 113 | writeln!(writer)?; 114 | writeln!(writer)?; 115 | writeln!( 116 | writer, 117 | " {guide} {}:{}:{} ", 118 | relative.display(), 119 | start.0 + 1, 120 | start.1 + 1 121 | )?; 122 | 123 | let vbar = Paint::new("│").fg(yansi::Color::Cyan).dimmed(); 124 | 125 | writeln!(writer, " {vbar} ")?; 126 | 127 | let is_inline = start.0 == end.0; 128 | 129 | let lines = content.lines().collect::>(); 130 | 131 | let minimum = start.0.saturating_sub(2); 132 | let maximum = (end.0 + 2).min(lines.len()); 133 | 134 | for (i, line) in lines[minimum..maximum].iter().enumerate() { 135 | let line_number = minimum + i + 1; 136 | 137 | write!(writer, " {:>3} {vbar} ", line_number)?; 138 | 139 | if is_inline && line_number == start.0 + 1 { 140 | let line = line.to_string(); 141 | 142 | writeln!(writer, "{}", line)?; 143 | 144 | writeln!( 145 | writer, 146 | " {vbar} {}{}", 147 | " ".repeat(start.1), 148 | Paint::new("^".repeat(end.1 - start.1)) 149 | .bold() 150 | .fg(yansi::Color::Red) 151 | )?; 152 | } else if is_inline && line_number == end.0 + 1 { 153 | let mut line = line.to_string(); 154 | 155 | line.insert(end.1 + 1, '^'); 156 | 157 | writeln!(writer, "{}", line)?; 158 | } else { 159 | writeln!(writer, "{}", line)?; 160 | } 161 | } 162 | 163 | writeln!(writer) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /crates/vulpi-ir/src/dead_code.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, mem}; 2 | 3 | use petgraph::{graph::DiGraph, stable_graph::NodeIndex}; 4 | use vulpi_intern::Symbol; 5 | use vulpi_syntax::{ 6 | lambda::{self, LetDecl, Program}, 7 | r#abstract::Qualified, 8 | }; 9 | 10 | pub struct Context { 11 | nodes: HashMap, 12 | graph: DiGraph<(), ()>, 13 | current: Qualified, 14 | } 15 | 16 | impl Default for Context { 17 | fn default() -> Self { 18 | Context { 19 | nodes: HashMap::new(), 20 | graph: DiGraph::new(), 21 | current: Qualified { 22 | path: Symbol::intern(""), 23 | name: Symbol::intern(""), 24 | }, 25 | } 26 | } 27 | } 28 | 29 | pub trait Check { 30 | fn check(&mut self, ctx: &mut Context); 31 | } 32 | 33 | impl Check for lambda::ExprKind { 34 | fn check(&mut self, ctx: &mut Context) { 35 | match self { 36 | lambda::ExprKind::Lambda(_, expr) => expr.check(ctx), 37 | lambda::ExprKind::Application(expr, args) => { 38 | expr.check(ctx); 39 | for arg in args { 40 | arg.check(ctx); 41 | } 42 | } 43 | lambda::ExprKind::Variable(_) => {} 44 | lambda::ExprKind::Constructor(c) | lambda::ExprKind::Function(c) => { 45 | let node = ctx 46 | .nodes 47 | .entry(c.clone()) 48 | .or_insert_with(|| ctx.graph.add_node(())) 49 | .clone(); 50 | let current = ctx 51 | .nodes 52 | .entry(ctx.current.clone()) 53 | .or_insert_with(|| ctx.graph.add_node(())); 54 | 55 | ctx.graph.add_edge(*current, node, ()); 56 | } 57 | lambda::ExprKind::Object(_, args) => { 58 | for arg in args { 59 | arg.check(ctx); 60 | } 61 | } 62 | lambda::ExprKind::Projection(_, expr) => expr.check(ctx), 63 | lambda::ExprKind::Access(expr, _) => expr.check(ctx), 64 | lambda::ExprKind::Block(stmts) => { 65 | for stmt in stmts { 66 | match stmt { 67 | lambda::Stmt::Let(_, expr) => expr.check(ctx), 68 | lambda::Stmt::Expr(expr) => expr.check(ctx), 69 | } 70 | } 71 | } 72 | lambda::ExprKind::Literal(_) => {} 73 | lambda::ExprKind::RecordInstance(_, args) => { 74 | for (_, arg) in args { 75 | arg.check(ctx); 76 | } 77 | } 78 | lambda::ExprKind::RecordUpdate(_, expr, args) => { 79 | expr.check(ctx); 80 | for (_, arg) in args { 81 | arg.check(ctx); 82 | } 83 | } 84 | lambda::ExprKind::Tuple(args) => { 85 | for arg in args { 86 | arg.check(ctx); 87 | } 88 | } 89 | lambda::ExprKind::Switch(_, _, actions) => { 90 | for action in actions { 91 | action.check(ctx); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | impl Check for LetDecl { 99 | fn check(&mut self, ctx: &mut Context) { 100 | self.body.check(ctx); 101 | } 102 | } 103 | 104 | impl Check for Program { 105 | fn check(&mut self, ctx: &mut Context) { 106 | for (name, decl) in &mut self.lets { 107 | ctx.current = name.clone(); 108 | ctx.nodes 109 | .entry(ctx.current.clone()) 110 | .or_insert_with(|| ctx.graph.add_node(())); 111 | decl.check(ctx); 112 | } 113 | } 114 | } 115 | 116 | impl Check for Vec { 117 | fn check(&mut self, ctx: &mut Context) { 118 | for program in self { 119 | program.check(ctx); 120 | } 121 | } 122 | } 123 | 124 | pub fn is_constant(expr: &lambda::Expr) -> bool { 125 | match &**expr { 126 | lambda::ExprKind::Lambda(_, _) => false, 127 | _ => true, 128 | } 129 | } 130 | 131 | pub fn has_no_side_effects(expr: &lambda::Expr) -> bool { 132 | match &**expr { 133 | lambda::ExprKind::Lambda(_, _) => true, 134 | lambda::ExprKind::Application(_, _) => false, 135 | lambda::ExprKind::Variable(_) => true, 136 | lambda::ExprKind::Constructor(_) => true, 137 | lambda::ExprKind::Function(_) => true, 138 | lambda::ExprKind::Object(_, _) => true, 139 | lambda::ExprKind::Projection(_, expr) => has_no_side_effects(expr), 140 | lambda::ExprKind::Access(expr, _) => has_no_side_effects(expr), 141 | lambda::ExprKind::Block(_) => true, 142 | lambda::ExprKind::Literal(_) => true, 143 | lambda::ExprKind::RecordInstance(_, _) => false, 144 | lambda::ExprKind::RecordUpdate(_, _, _) => false, 145 | lambda::ExprKind::Tuple(args) => args.iter().all(has_no_side_effects), 146 | lambda::ExprKind::Switch(_, _, _) => false, 147 | } 148 | } 149 | 150 | pub fn remove_lets(program: &mut Program, ctx: &mut Context) { 151 | program.lets = mem::take(&mut program.lets) 152 | .into_iter() 153 | .filter(|(name, body)| { 154 | let node = ctx.nodes.get(name).unwrap(); 155 | ctx.graph 156 | .neighbors_directed(*node, petgraph::Direction::Incoming) 157 | .count() 158 | != 0 159 | || (is_constant(&body.body) && body.is_in_source_code) 160 | || !has_no_side_effects(&body.body) 161 | }) 162 | .collect(); 163 | } 164 | 165 | pub fn dead_code_remove(programs: &mut Vec) { 166 | let mut ctx = Context::default(); 167 | programs.check(&mut ctx); 168 | 169 | for program in programs { 170 | remove_lets(program, &mut ctx); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /example/DOM.vp: -------------------------------------------------------------------------------- 1 | use Prelude 2 | use Yal.List 3 | use Yal.Bindings 4 | 5 | pub type AttributePatch msg = 6 | | Add (Attribute msg) 7 | | Remove (Attribute msg) 8 | 9 | pub type Patch msg = 10 | | Add (Html msg) 11 | | Remove 12 | | Update (List (Patch msg)) (List (AttributePatch msg)) 13 | | Replace (Html msg) 14 | | NoPatch 15 | 16 | pub type Attribute msg = 17 | | Id String 18 | | ClassList (List String) 19 | | OnClick msg 20 | 21 | pub type Node msg = { 22 | tag : String, 23 | attributes : List (Attribute msg), 24 | children : List (Html msg) 25 | } 26 | 27 | pub type Html msg = 28 | | Node (Node msg) 29 | | Text String 30 | 31 | -- Diffing 32 | 33 | let diffChildren : List (Html msg) -> List (Html msg) -> List (Patch msg) -> List (Patch msg) 34 | | Nil , Nil , acc => reverseList acc 35 | | Cons x xs, Nil , acc => diffChildren xs Nil (Cons Patch.Remove acc) 36 | | Nil , Cons y ys, acc => diffChildren Nil ys (Cons (Patch.Add y) acc) 37 | | Cons x xs, Cons y ys, acc => diffChildren xs ys (Cons (diff x y) acc) 38 | 39 | let diffAttrs (old: List (Attribute msg)) (new1: List (Attribute msg)) : List (AttributePatch msg) = 40 | concatList (listMap AttributePatch.Remove (difference old new1)) 41 | (listMap AttributePatch.Add (difference new1 old)) 42 | 43 | let diff : Html msg -> Html msg -> Patch msg 44 | | Html.Node node, Html.Text text => Patch.Replace (Html.Text text) 45 | | Html.Text text, Html.Node node => Patch.Replace (Html.Node node) 46 | | Html.Text text, Html.Text text1 => 47 | when text == text1 is 48 | True => Patch.NoPatch 49 | False => Patch.Replace (Html.Text text1) 50 | | Html.Node node, Html.Node node1 => 51 | when node.tag != node1.tag is 52 | True => Patch.Replace (Html.Node node1) 53 | False => do 54 | let children = diffChildren node.children node1.children Nil 55 | let attributes = diffAttrs node.attributes node1.attributes 56 | Patch.Update children attributes 57 | 58 | type Element = 59 | | Node NodeElement 60 | | Text TextElement 61 | 62 | type State model msg = { 63 | model: model, 64 | view: Html msg 65 | } 66 | 67 | let createTextNode (s: String) : TextElement = trustMe (createText s) 68 | 69 | let toGeneric : Element -> GenericNode 70 | | Element.Node x => trustMe x 71 | | Element.Text t => trustMe t 72 | 73 | let toText (x: GenericNode) : Element = Element.Text (trustMe x) 74 | let toNode (x: GenericNode) : Element = Element.Node (trustMe x) 75 | 76 | let getChildren (element: NodeElement) : List GenericNode = do 77 | let children = prim_getChildren element 78 | let length = childLength children 79 | listMap (idxChild children) (range 0 length) 80 | 81 | let getElementById (x: String) : Option NodeElement = do 82 | let res = prim_getElementById x 83 | when isNullOrUndefined res is 84 | True => None 85 | False => Some res 86 | 87 | -- Render 88 | 89 | let renderAttribute (symbol: Symbol msg model) (parent: NodeElement) : Attribute msg -> () 90 | | Attribute.Id i => setAttribute parent "id" i 91 | | Attribute.ClassList classes => setAttribute parent "className" (unwords classes) 92 | | Attribute.OnClick msg => addEventListener (trustMe parent) symbol "onclick" msg 93 | 94 | let renderNode (symbol: Symbol msg model) (node: Node msg) : NodeElement = do 95 | let parent = createNode node.tag 96 | forEach (\a => a |> render symbol |> appendChild parent) node.children 97 | forEach (renderAttribute symbol parent) node.attributes 98 | parent 99 | 100 | let render (symbol: Symbol msg model) : Html msg -> GenericNode 101 | | Html.Node node => trustMe (renderNode symbol node) 102 | | Html.Text text => trustMe (createTextNode text) 103 | 104 | let deleteAttribute (element: NodeElement) : (Attribute msg) -> () 105 | | Attribute.OnClick _ => removeListener element "onevent" 106 | | Attribute.Id _ => removeAttribute element "id" 107 | | Attribute.ClassList _ => removeAttribute element "className" 108 | 109 | let patchAttributes (symbol: Symbol msg model) (element: NodeElement) : List (AttributePatch msg) -> () 110 | | Nil => () 111 | | Cons (AttributePatch.Add attr) attrs => do 112 | renderAttribute symbol element attr 113 | patchAttributes symbol element attrs 114 | | Cons (AttributePatch.Remove attr) attrs => do 115 | deleteAttribute element attr 116 | patchAttributes symbol element attrs 117 | 118 | let patchChildren (symbol: Symbol msg model) (parent: GenericNode) : List GenericNode -> List (Patch msg) -> () 119 | | Cons x xs, Cons p ps => do 120 | patch symbol x p 121 | patchChildren symbol parent xs ps 122 | | Nil, Cons p ps => do 123 | patch symbol parent p 124 | patchChildren symbol parent Nil ps 125 | | Nil , Nil => () 126 | | Cons _ _, Nil => () 127 | 128 | let patch (symbol: Symbol msg model) (parent: GenericNode) : Patch msg -> () 129 | | Patch.NoPatch => () 130 | | Patch.Replace html => do 131 | let res = render symbol html 132 | replaceWith (trustMe parent) res 133 | | Patch.Update children attrs => do 134 | let domChildren = getChildren (trustMe parent) 135 | patchChildren symbol parent domChildren children 136 | patchAttributes symbol (trustMe parent) attrs 137 | | Patch.Add element => do 138 | let res = render symbol element 139 | appendChild (trustMe parent) res 140 | | Patch.Remove => 141 | remove parent 142 | 143 | -- Start 144 | 145 | let rootUpdater (symbol: Symbol msg model) (view: model -> Html msg) (update: model -> msg -> model) 146 | (parent: NodeElement) (msg: msg) (state: State model msg) : State model msg = do 147 | let newModel = update state.model msg 148 | let newView = view newModel 149 | patch symbol (trustMe parent) (diff state.view newView) 150 | State { model = newModel, view = newView } 151 | 152 | pub let start (view: model -> Html msg) (update: model -> msg -> model) (initial: model) : () = do 153 | when getElementById "main" is 154 | None => log "cannot find main" 155 | Some main => do 156 | let symbol = createSymbol "events" 157 | let html = view initial 158 | let rendered = render symbol html 159 | appendChild (trustMe main) rendered 160 | let state = State { model = initial, view = html } 161 | addListener symbol (rootUpdater symbol view update (trustMe rendered)) state -------------------------------------------------------------------------------- /crates/vulpi-tests/suite/expressions.expect: -------------------------------------------------------------------------------- 1 | └Module 2 | └decls 3 | └Vec 4 | ├Module 5 | │ └ModuleDecl 6 | │ ├namespace 7 | │ │ └Symbol: Prelude 8 | │ ├visibility 9 | │ │ └Public 10 | │ ├name 11 | │ │ └Symbol: Prelude 12 | │ └decls 13 | │ └Vec 14 | │ └Type 15 | │ └TypeDecl 16 | │ ├namespace 17 | │ │ └Symbol: Prelude.Int 18 | │ ├visibility 19 | │ │ └Public 20 | │ ├name 21 | │ │ └Qualified 22 | │ │ ├Prelude 23 | │ │ └Int 24 | │ ├binders 25 | │ │ └Vec 26 | │ └def 27 | │ └Abstract 28 | ├Type 29 | │ └TypeDecl 30 | │ ├namespace 31 | │ │ └Symbol: Shake 32 | │ ├visibility 33 | │ │ └Public 34 | │ ├name 35 | │ │ └Qualified 36 | │ │ ├ 37 | │ │ └Shake 38 | │ ├binders 39 | │ │ └Vec 40 | │ └def 41 | │ └Sum 42 | │ └SumDecl 43 | │ └constructors 44 | │ └Vec 45 | │ ├Constructor 46 | │ │ ├name 47 | │ │ │ └Symbol: True 48 | │ │ ├args 49 | │ │ │ └Vec 50 | │ │ └typ 51 | │ │ └None 52 | │ └Constructor 53 | │ ├name 54 | │ │ └Symbol: False 55 | │ ├args 56 | │ │ └Vec 57 | │ └typ 58 | │ └None 59 | ├Type 60 | │ └TypeDecl 61 | │ ├namespace 62 | │ │ └Symbol: Priv 63 | │ ├visibility 64 | │ │ └Private 65 | │ ├name 66 | │ │ └Qualified 67 | │ │ ├ 68 | │ │ └Priv 69 | │ ├binders 70 | │ │ └Vec 71 | │ └def 72 | │ └Sum 73 | │ └SumDecl 74 | │ └constructors 75 | │ └Vec 76 | │ ├Constructor 77 | │ │ ├name 78 | │ │ │ └Symbol: True 79 | │ │ ├args 80 | │ │ │ └Vec 81 | │ │ │ └Spanned 82 | │ │ │ ├105~110 83 | │ │ │ └Type 84 | │ │ │ └Qualified 85 | │ │ │ ├ 86 | │ │ │ └Shake 87 | │ │ └typ 88 | │ │ └None 89 | │ └Constructor 90 | │ ├name 91 | │ │ └Symbol: False 92 | │ ├args 93 | │ │ └Vec 94 | │ └typ 95 | │ └None 96 | └Module 97 | └ModuleDecl 98 | ├namespace 99 | │ └Symbol: Ata 100 | ├visibility 101 | │ └Public 102 | ├name 103 | │ └Symbol: Ata 104 | └decls 105 | └Vec 106 | ├Type 107 | │ └TypeDecl 108 | │ ├namespace 109 | │ │ └Symbol: Ata.Bool 110 | │ ├visibility 111 | │ │ └Private 112 | │ ├name 113 | │ │ └Qualified 114 | │ │ ├Ata 115 | │ │ └Bool 116 | │ ├binders 117 | │ │ └Vec 118 | │ └def 119 | │ └Sum 120 | │ └SumDecl 121 | │ └constructors 122 | │ └Vec 123 | │ ├Constructor 124 | │ │ ├name 125 | │ │ │ └Symbol: True 126 | │ │ ├args 127 | │ │ │ └Vec 128 | │ │ └typ 129 | │ │ └None 130 | │ └Constructor 131 | │ ├name 132 | │ │ └Symbol: False 133 | │ ├args 134 | │ │ └Vec 135 | │ └typ 136 | │ └None 137 | └Let 138 | └LetDecl 139 | ├visibility 140 | │ └Private 141 | ├name 142 | │ └Symbol: ata 143 | ├binders 144 | │ └Vec 145 | ├ret 146 | │ └Tuple 147 | │ ├None 148 | │ └Spanned 149 | │ ├205~215 150 | │ └Type 151 | │ └Qualified 152 | │ ├ 153 | │ └Shake 154 | └body 155 | └LetMode 156 | └cases 157 | └Vec 158 | └LetCase 159 | └pattern 160 | └PatternArm 161 | ├patterns 162 | │ └Vec 163 | ├expr 164 | │ └Spanned 165 | │ ├218~227 166 | │ └Constructor 167 | │ └Qualified 168 | │ ├Ata.Bool 169 | │ └True 170 | └guard 171 | └None 172 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/tokens.rs: -------------------------------------------------------------------------------- 1 | //! This module declares a bunch of tokens that are units of meaning in the language. There are a 2 | //! bunch of them that are virtual token. 3 | 4 | use std::fmt::Debug; 5 | 6 | use vulpi_intern::Symbol; 7 | use vulpi_location::Spanned; 8 | use vulpi_show::{Show, TreeDisplay}; 9 | 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 11 | pub enum TokenData { 12 | Let, // 'let' keyword 13 | When, // 'when' keyword 14 | Is, // 'is' keyword 15 | With, // 'with' keyword 16 | If, // 'if' keyword 17 | Else, // 'else' keyword 18 | Then, // 'then' keyword 19 | Use, // 'use' keyword 20 | As, // 'as' keyword 21 | Type, // 'type' keyword 22 | Pub, // 'pub' keyword 23 | Do, // 'do' keyword 24 | In, // 'in' keyword 25 | Forall, // 'forall' keyword 26 | Where, // 'where' keyword 27 | Mod, // 'mod' keyword 28 | Handle, // 'handle' keyword 29 | Cases, // 'request' keyword 30 | Effect, // 'effect' keyword 31 | External, // 'external' keyword 32 | Trait, // 'trait' keyword 33 | Impl, // 'impl' keyword 34 | 35 | String, // String literal 36 | Int, // Integer literal 37 | Float, // Float Literal 38 | Char, // Char literal 39 | 40 | LBrace, // '{' 41 | RBrace, // '}' 42 | LPar, // '(' 43 | RPar, // ')' 44 | LBracket, // '[' 45 | RBracket, // ']' 46 | LeftArrow, // '<-' 47 | RightArrow, // '->' 48 | FatArrow, // '=>' 49 | Unit, 50 | 51 | LowerIdent, // Identifier 52 | UpperIdent, // Identifier 53 | Command, // Command 54 | Wildcard, 55 | 56 | Colon, // ':' 57 | Semicolon, // ';' 58 | Comma, // ',' 59 | Dot, // '.' 60 | Exclamation, // '!' 61 | Equal, // '=' 62 | Bar, // '|' 63 | PipeRight, // '|>' 64 | PlusPlus, // '++' 65 | 66 | Plus, // '+' 67 | Minus, // '-' 68 | Star, // '*' 69 | Slash, // '/' 70 | BackSlash, // '\' 71 | Percent, // '%' 72 | Caret, // '^' 73 | Ampersand, // '&' 74 | Tilde, // '~' 75 | 76 | Greater, // '>' 77 | Less, // '<' 78 | LessSlash, // '=' 80 | LessEqual, // '<=' 81 | NotEqual, // '!=' 82 | DoubleEqual, // '==' 83 | 84 | And, // '&&' 85 | Or, // '||' 86 | 87 | Begin, // Virtual token for beginning of a block 88 | End, // Virtual token for end of a block 89 | Sep, // Virtual token for a semicolon 90 | 91 | Error, 92 | Eof, 93 | } 94 | 95 | #[derive(Debug, Clone)] 96 | pub struct Comment { 97 | pub whitespace: Spanned, 98 | pub comment: Spanned, 99 | } 100 | 101 | #[derive(Clone)] 102 | pub struct Token { 103 | pub comments: Vec, 104 | pub whitespace: Spanned, 105 | pub kind: TokenData, 106 | pub value: Spanned, 107 | } 108 | 109 | impl Show for Token { 110 | fn show(&self) -> vulpi_show::TreeDisplay { 111 | TreeDisplay::label("Token").with(TreeDisplay::label(&self.to_string())) 112 | } 113 | } 114 | 115 | impl Token { 116 | pub fn is(&self, kind: TokenData) -> bool { 117 | self.kind == kind 118 | } 119 | 120 | pub fn data(&self) -> String { 121 | self.value.data.get() 122 | } 123 | 124 | pub fn symbol(&self) -> Symbol { 125 | self.value.data.clone() 126 | } 127 | } 128 | 129 | impl Debug for Token { 130 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 131 | f.debug_tuple("Token").field(&self.kind).finish() 132 | } 133 | } 134 | 135 | impl ToString for Token { 136 | fn to_string(&self) -> String { 137 | use TokenData::*; 138 | 139 | match self.kind { 140 | String => format!("\"{}\"", self.value.data.get()), 141 | Int => format!("int({})", self.value.data.get()), 142 | Float => format!("float({})", self.value.data.get()), 143 | LowerIdent => format!("lower {}", self.value.data.get()), 144 | UpperIdent => format!("upper {}", self.value.data.get()), 145 | Colon => ":".to_string(), 146 | Semicolon => ";".to_string(), 147 | Comma => ",".to_string(), 148 | Dot => ".".to_string(), 149 | Exclamation => "!".to_string(), 150 | Equal => "=".to_string(), 151 | Bar => "|".to_string(), 152 | Plus => "+".to_string(), 153 | Minus => "-".to_string(), 154 | Star => "*".to_string(), 155 | Slash => "/".to_string(), 156 | Percent => "%".to_string(), 157 | Caret => "^".to_string(), 158 | Ampersand => "&".to_string(), 159 | Tilde => "~".to_string(), 160 | Greater => ">".to_string(), 161 | Less => "<".to_string(), 162 | GreaterEqual => ">=".to_string(), 163 | LessEqual => "<=".to_string(), 164 | NotEqual => "!=".to_string(), 165 | DoubleEqual => "==".to_string(), 166 | And => "&&".to_string(), 167 | Or => "||".to_string(), 168 | Begin => "{{".to_string(), 169 | End => "}}".to_string(), 170 | Sep => ";".to_string(), 171 | Error => "error".to_string(), 172 | Eof => "eof".to_string(), 173 | Let => "let".to_string(), 174 | When => "when".to_string(), 175 | Is => "is".to_string(), 176 | With => "with".to_string(), 177 | If => "if".to_string(), 178 | Else => "else".to_string(), 179 | Then => "then".to_string(), 180 | Use => "use".to_string(), 181 | As => "as".to_string(), 182 | Type => "type".to_string(), 183 | Pub => "pub".to_string(), 184 | Do => "do".to_string(), 185 | Where => "where".to_string(), 186 | Forall => "forall".to_string(), 187 | Trait => "trait".to_string(), 188 | Impl => "impl".to_string(), 189 | In => "in".to_string(), 190 | LBrace => "{{".to_string(), 191 | RBrace => "}}".to_string(), 192 | LPar => "(".to_string(), 193 | RPar => ")".to_string(), 194 | LBracket => "[".to_string(), 195 | RBracket => "]".to_string(), 196 | LessSlash => " "<-".to_string(), 198 | RightArrow => "->".to_string(), 199 | FatArrow => "=>".to_string(), 200 | BackSlash => "\\".to_string(), 201 | PipeRight => "|>".to_string(), 202 | Char => format!("char('{}')", self.value.data.get()), 203 | Unit => "()".to_string(), 204 | Wildcard => "_".to_string(), 205 | Mod => "mod".to_string(), 206 | Handle => "handle".to_string(), 207 | Cases => "cases".to_string(), 208 | Effect => "effect".to_string(), 209 | External => "external".to_string(), 210 | PlusPlus => "++".to_string(), 211 | Command => format!("command {}", self.value.data.get()), 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /crates/vulpi-syntax/src/concrete/top_level.rs: -------------------------------------------------------------------------------- 1 | use vulpi_intern::Symbol; 2 | use vulpi_macros::Show; 3 | 4 | use crate::tokens::Token; 5 | 6 | #[derive(Show, Clone)] 7 | pub enum Visibility { 8 | Public(Token), 9 | Private, 10 | } 11 | 12 | use super::{ 13 | expr::{Expr, PatternArm}, 14 | kind::Kind, 15 | r#type::Type, 16 | tree::Pattern, 17 | Lower, Parenthesis, Path, Upper, 18 | }; 19 | 20 | #[derive(Show, Clone)] 21 | pub struct Binder { 22 | pub left_paren: Token, 23 | pub pattern: Box, 24 | pub colon: Token, 25 | pub typ: Box, 26 | pub right_paren: Token, 27 | } 28 | 29 | #[derive(Show, Clone)] 30 | pub struct TraitBinder { 31 | pub left_bracket: Token, 32 | pub typ: Box, 33 | pub right_bracket: Token, 34 | } 35 | 36 | #[derive(Show, Clone)] 37 | pub struct LetCase { 38 | pub pipe: Token, 39 | pub arm: PatternArm, 40 | } 41 | 42 | #[derive(Show, Clone)] 43 | pub enum LetMode { 44 | Body(Token, Box), 45 | Cases(Vec), 46 | } 47 | 48 | #[derive(Show, Clone)] 49 | pub struct LetSignature { 50 | pub visibility: Visibility, 51 | pub let_: Token, 52 | pub name: Lower, 53 | pub binders: Vec, 54 | pub ret: Option<(Token, Box)>, 55 | } 56 | 57 | #[derive(Show, Clone)] 58 | pub struct TraitDecl { 59 | pub visibility: Visibility, 60 | pub trait_: Token, 61 | pub supers: Vec, 62 | pub name: Upper, 63 | pub binders: Vec, 64 | pub where_: Token, 65 | pub body: Vec, 66 | } 67 | 68 | #[derive(Show, Clone)] 69 | pub struct TraitImpl { 70 | pub impl_: Token, 71 | pub supers: Vec, 72 | pub name: Path, 73 | pub types: Vec>, 74 | pub where_: Token, 75 | pub body: Vec, 76 | } 77 | 78 | #[derive(Show, Clone)] 79 | pub struct LetDecl { 80 | pub signature: LetSignature, 81 | pub body: LetMode, 82 | } 83 | 84 | #[derive(Show, Clone)] 85 | pub struct Constructor { 86 | pub pipe: Token, 87 | pub name: Upper, 88 | pub args: Vec>, 89 | pub typ: Option<(Token, Box)>, 90 | } 91 | 92 | #[derive(Show, Clone)] 93 | pub struct SumDecl { 94 | pub constructors: Vec, 95 | } 96 | 97 | #[derive(Show, Clone)] 98 | pub struct Field { 99 | pub visibility: Visibility, 100 | pub name: Lower, 101 | pub colon: Token, 102 | pub typ: Box, 103 | } 104 | 105 | #[derive(Show, Clone)] 106 | pub struct RecordDecl { 107 | pub left_brace: Token, 108 | pub fields: Vec<(Field, Option)>, 109 | pub right_brace: Token, 110 | } 111 | 112 | #[derive(Show, Clone)] 113 | pub struct ExplicitTypeBinder { 114 | pub name: Lower, 115 | pub colon: Token, 116 | pub kind: Box, 117 | } 118 | 119 | #[derive(Show, Clone)] 120 | pub enum TypeBinder { 121 | Implicit(Lower), 122 | Explicit(Parenthesis) 123 | } 124 | 125 | #[derive(Show, Clone)] 126 | pub enum LetBinder { 127 | Param(Binder), 128 | Trait(TraitBinder), 129 | } 130 | 131 | #[derive(Show, Clone)] 132 | pub enum TypeDef { 133 | Sum(SumDecl), 134 | Record(RecordDecl), 135 | Synonym(Box), 136 | } 137 | 138 | #[derive(Show, Clone)] 139 | pub struct TypeDecl { 140 | pub visibility: Visibility, 141 | pub type_: Token, 142 | pub name: Upper, 143 | pub binders: Vec, 144 | pub def: Option<(Token, TypeDef)>, 145 | } 146 | 147 | #[derive(Show, Clone)] 148 | pub struct UseAlias { 149 | pub as_: Token, 150 | pub alias: Upper, 151 | } 152 | 153 | #[derive(Show, Clone)] 154 | pub struct UseDecl { 155 | pub visibility: Visibility, 156 | pub use_: Token, 157 | pub path: Path, 158 | pub alias: Option, 159 | } 160 | 161 | #[derive(Show, Clone)] 162 | pub struct ModuleInline { 163 | pub name: Upper, 164 | pub where_: Token, 165 | pub top_levels: Vec, 166 | } 167 | 168 | impl ModuleInline { 169 | pub fn modules(&self) -> impl Iterator { 170 | self.top_levels 171 | .iter() 172 | .filter_map(|top_level| match &top_level { 173 | TopLevel::Module(module) => Some(&**module), 174 | _ => None, 175 | }) 176 | } 177 | 178 | pub fn uses(&self) -> impl Iterator { 179 | self.top_levels 180 | .iter() 181 | .filter_map(|top_level| match &top_level { 182 | TopLevel::Use(use_) => Some(&**use_), 183 | _ => None, 184 | }) 185 | } 186 | } 187 | 188 | #[derive(Show, Clone)] 189 | pub struct ModuleDecl { 190 | pub visibility: Visibility, 191 | pub mod_: Token, 192 | pub name: Upper, 193 | pub part: Option, 194 | } 195 | 196 | impl ModuleDecl { 197 | fn declares(&self, mut path: Vec) -> Vec> { 198 | if let Some(module) = &self.part { 199 | let mut paths = Vec::new(); 200 | 201 | for module in module.modules() { 202 | path.push(module.name.symbol()); 203 | paths.extend(module.declares(path.clone())); 204 | } 205 | 206 | paths 207 | } else { 208 | path.push(self.name.symbol()); 209 | vec![path] 210 | } 211 | } 212 | } 213 | 214 | #[derive(Show, Clone)] 215 | pub struct ExtDecl { 216 | pub visibility: Visibility, 217 | pub external: Token, 218 | pub name: Lower, 219 | pub colon: Token, 220 | pub typ: Box, 221 | pub equal: Token, 222 | pub str: Token, 223 | } 224 | 225 | #[derive(Show, Clone)] 226 | pub struct CommandDecl { 227 | pub name: Symbol, 228 | pub command: Symbol, 229 | } 230 | 231 | #[derive(Show, Clone)] 232 | pub enum TopLevel { 233 | Let(Box), 234 | Type(Box), 235 | Use(Box), 236 | Impl(Box), 237 | Trait(Box), 238 | Module(Box), 239 | Error(Vec), 240 | External(Box), 241 | Command(Box), 242 | } 243 | 244 | #[derive(Show, Clone)] 245 | pub struct Program { 246 | pub top_levels: Vec, 247 | pub eof: Token, 248 | } 249 | 250 | impl Program { 251 | pub fn modules(&self) -> impl Iterator { 252 | self.top_levels 253 | .iter() 254 | .filter_map(|top_level| match top_level { 255 | TopLevel::Module(module) => Some(&**module), 256 | _ => None, 257 | }) 258 | } 259 | 260 | pub fn uses(&self) -> impl Iterator { 261 | self.top_levels 262 | .iter() 263 | .filter_map(|top_level| match top_level { 264 | TopLevel::Use(use_) => Some(&**use_), 265 | _ => None, 266 | }) 267 | } 268 | 269 | pub fn types(&self) -> impl Iterator { 270 | self.top_levels 271 | .iter() 272 | .filter_map(|top_level| match top_level { 273 | TopLevel::Type(type_) => Some(&**type_), 274 | _ => None, 275 | }) 276 | } 277 | 278 | pub fn lets(&self) -> impl Iterator { 279 | self.top_levels 280 | .iter() 281 | .filter_map(|top_level| match top_level { 282 | TopLevel::Let(let_) => Some(&**let_), 283 | _ => None, 284 | }) 285 | } 286 | 287 | pub fn declares(&self) -> Vec> { 288 | let mut dependencies = Vec::new(); 289 | 290 | for module in self.modules() { 291 | dependencies.extend(module.declares(Vec::new())); 292 | } 293 | 294 | dependencies 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /crates/vulpi-typer/src/infer/pat.rs: -------------------------------------------------------------------------------- 1 | //! Inference of patterns 2 | 3 | use std::collections::HashMap; 4 | 5 | use vulpi_intern::Symbol; 6 | use vulpi_syntax::{ 7 | elaborated::{self, PatApplication}, 8 | r#abstract::Pattern, 9 | r#abstract::PatternArm, 10 | r#abstract::PatternKind, 11 | }; 12 | 13 | use crate::{ 14 | context::Context, 15 | errors::TypeErrorKind, 16 | real::Real, 17 | Env, Kind, Type, 18 | {eval::Eval, r#virtual::Virtual}, 19 | }; 20 | 21 | use super::Infer; 22 | 23 | impl Infer for PatternArm { 24 | type Return = ( 25 | Vec>, 26 | Type, 27 | elaborated::PatternArm>, 28 | ); 29 | 30 | type Context<'a> = (&'a mut Context, Env); 31 | 32 | fn infer(&self, (ctx, mut env): Self::Context<'_>) -> Self::Return { 33 | let mut patterns = Vec::new(); 34 | let mut elaborated_patterns = Vec::new(); 35 | 36 | let mut map = Default::default(); 37 | for pat in &self.patterns { 38 | let (typ, elab) = pat.infer((ctx, &mut map, env.clone())); 39 | patterns.push(typ); 40 | elaborated_patterns.push(elab); 41 | } 42 | 43 | for binding in map { 44 | env.add_var(binding.0, binding.1); 45 | } 46 | 47 | let (typ, elab_expr) = self.expr.infer((ctx, env.clone())); 48 | 49 | let guard = self.guard.as_ref().map(|g| g.infer((ctx, env.clone()))); 50 | 51 | let elab_guard = if let Some((typ, guard)) = guard { 52 | let bool = ctx.find_prelude_type("Bool", env.clone()); 53 | ctx.subsumes(env.clone(), typ, bool); 54 | Some(guard) 55 | } else { 56 | None 57 | }; 58 | 59 | ( 60 | patterns, 61 | typ, 62 | elaborated::PatternArm { 63 | patterns: elaborated_patterns, 64 | guard: elab_guard, 65 | expr: elab_expr, 66 | }, 67 | ) 68 | } 69 | } 70 | 71 | impl Infer for Vec { 72 | type Return = ( 73 | Type, 74 | Vec>, 75 | Type, 76 | Vec>>, 77 | ); 78 | 79 | type Context<'a> = (&'a mut Context, Env); 80 | 81 | fn infer(&self, (ctx, env): Self::Context<'_>) -> Self::Return { 82 | if self.is_empty() { 83 | ( 84 | ctx.hole(&env, Kind::typ()), 85 | vec![], 86 | ctx.hole(&env, Kind::typ()), 87 | vec![], 88 | ) 89 | } else { 90 | let ret = ctx.hole(&env, Kind::typ()); 91 | 92 | let (types, fst_type, elab_arm) = self[0].infer((ctx, env.clone())); 93 | 94 | ctx.subsumes(env.clone(), fst_type.clone(), ret.clone()); 95 | 96 | let mut elab_arms = vec![elab_arm]; 97 | 98 | for pat in self.iter().skip(1) { 99 | let (new_types, new_ret_type, elab_arm) = pat.infer((ctx, env.clone())); 100 | 101 | elab_arms.push(elab_arm); 102 | 103 | if new_types.len() != types.len() { 104 | ctx.report( 105 | &env, 106 | TypeErrorKind::WrongArity(new_types.len(), types.len()), 107 | ); 108 | return (Type::error(), types, Type::error(), vec![]); 109 | } 110 | 111 | for (old, new) in types.iter().zip(new_types) { 112 | ctx.subsumes(env.clone(), old.clone(), new); 113 | } 114 | 115 | ctx.subsumes(env.clone(), new_ret_type.clone(), ret.clone()); 116 | } 117 | 118 | ( 119 | Type::::function(types.clone(), fst_type.clone()), 120 | types, 121 | fst_type, 122 | elab_arms, 123 | ) 124 | } 125 | } 126 | } 127 | 128 | impl Infer for Pattern { 129 | type Return = (Type, elaborated::Pattern); 130 | 131 | type Context<'a> = (&'a mut Context, &'a mut HashMap>, Env); 132 | 133 | fn infer(&self, (ctx, map, env): Self::Context<'_>) -> Self::Return { 134 | env.set_current_span(self.span.clone()); 135 | 136 | match &self.data { 137 | PatternKind::Wildcard => ( 138 | ctx.hole(&env, Type::typ()), 139 | Box::new(elaborated::PatternKind::Wildcard), 140 | ), 141 | PatternKind::Tuple(tuple) => { 142 | let mut types = Vec::new(); 143 | let mut elab_pats = Vec::new(); 144 | 145 | for pat in tuple { 146 | let (typ, elab_pat) = pat.infer((ctx, map, env.clone())); 147 | types.push(typ); 148 | elab_pats.push(elab_pat); 149 | } 150 | 151 | ( 152 | Type::tuple(types), 153 | Box::new(elaborated::PatternKind::Tuple(elab_pats)), 154 | ) 155 | } 156 | PatternKind::Variable(symbol) => { 157 | let value = ctx.hole(&env, Type::typ()); 158 | 159 | if let Some(typ) = map.get(symbol) { 160 | ctx.subsumes(env, typ.clone(), value.clone()); 161 | } else { 162 | map.insert(symbol.clone(), value.clone()); 163 | } 164 | 165 | ( 166 | value, 167 | Box::new(elaborated::PatternKind::Variable(symbol.clone())), 168 | ) 169 | } 170 | PatternKind::Literal(lit) => { 171 | let (typ, lit) = lit.infer((ctx, env)); 172 | (typ, Box::new(elaborated::PatternKind::Literal(lit))) 173 | } 174 | PatternKind::Ascription(ann) => { 175 | let (typ, _) = ann.typ.infer((ctx, env.clone())); 176 | let eval_typ = typ.eval(&env); 177 | let (value, pat) = ann.pat.infer((ctx, map, env.clone())); 178 | ctx.subsumes(env, eval_typ.clone(), value); 179 | (eval_typ, pat) 180 | } 181 | PatternKind::Or(_) => { 182 | unimplemented!("Or patterns are not yet implemented") 183 | } 184 | PatternKind::Application(app) => { 185 | let (typ, arity, _) = ctx.modules.constructor(&app.func); 186 | 187 | let mut typ = typ.eval(&env); 188 | 189 | if arity != app.args.len() { 190 | ctx.report(&env, TypeErrorKind::WrongArity(arity, app.args.len())); 191 | return (Type::error(), Box::new(elaborated::PatternKind::Error)); 192 | } 193 | 194 | let mut types = Vec::new(); 195 | let mut args = Vec::new(); 196 | 197 | for arg in &app.args { 198 | let (arg_ty, elab_arg) = arg.infer((ctx, map, env.clone())); 199 | 200 | types.push(arg_ty.clone()); 201 | args.push(elab_arg); 202 | 203 | let Some((param_ty, rest)) = ctx.as_function(&env, typ) else { 204 | unreachable!() 205 | }; 206 | 207 | typ = rest; 208 | 209 | ctx.subsumes(env.clone(), arg_ty, param_ty); 210 | } 211 | 212 | ( 213 | typ, 214 | Box::new(elaborated::PatternKind::Application(PatApplication { 215 | func: app.func.clone(), 216 | args, 217 | })), 218 | ) 219 | } 220 | PatternKind::Error => (Type::error(), Box::new(elaborated::PatternKind::Error)), 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /crates/vulpi-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is the parser of the vulpi language. It takes a stream of tokens and produces a tree of 2 | //! nodes. It's a classical LL(1) parser with a recursive descent and pratt parsing. 3 | 4 | use error::ParserError; 5 | use vulpi_lexer::Lexer; 6 | use vulpi_location::{Byte, FileId, Span, Spanned}; 7 | use vulpi_report::{Diagnostic, Report}; 8 | 9 | use vulpi_syntax::concrete::tree::Program; 10 | use vulpi_syntax::concrete::Parenthesis; 11 | use vulpi_syntax::tokens::{Token, TokenData}; 12 | 13 | pub mod error; 14 | pub mod expr; 15 | pub mod identifier; 16 | pub mod literal; 17 | pub mod pattern; 18 | pub mod top_level; 19 | pub mod r#type; 20 | 21 | pub type Result = std::result::Result; 22 | 23 | /// The parser main structure. 24 | pub struct Parser<'a> { 25 | pub lexer: Lexer<'a>, 26 | 27 | pub last_pos: Span, 28 | 29 | pub current: Token, 30 | pub next: Token, 31 | 32 | pub eaten: bool, 33 | pub file: FileId, 34 | 35 | pub reporter: Report, 36 | } 37 | 38 | impl<'a> Parser<'a> { 39 | pub fn new(mut lexer: Lexer<'a>, file: FileId, report: Report) -> Self { 40 | let current = lexer.bump(); 41 | let next = lexer.bump(); 42 | 43 | Self { 44 | lexer, 45 | current, 46 | next, 47 | last_pos: Span { 48 | file, 49 | start: Byte(0), 50 | end: Byte(0), 51 | }, 52 | eaten: false, 53 | file, 54 | reporter: report, 55 | } 56 | } 57 | 58 | /// Advances a single token in the stream. 59 | pub fn bump(&mut self) -> Token { 60 | self.eaten = true; 61 | 62 | let mut ret = self.lexer.bump(); 63 | std::mem::swap(&mut self.current, &mut self.next); 64 | std::mem::swap(&mut ret, &mut self.next); 65 | 66 | self.last_pos = ret.value.span.clone(); 67 | 68 | ret 69 | } 70 | 71 | /// Returns the current token. 72 | pub fn peek(&self) -> &Token { 73 | &self.current 74 | } 75 | 76 | /// Removes a token if it matches the given one. 77 | pub fn expect(&mut self, token: TokenData) -> Result { 78 | if self.peek().kind == token { 79 | Ok(self.bump()) 80 | } else { 81 | self.unexpected() 82 | } 83 | } 84 | 85 | /// Removes a token if it matches the given one but does not finishes the parsing process if 86 | /// it doesn't. 87 | pub fn eat(&mut self, token: TokenData) -> Token { 88 | if self.peek().kind != token { 89 | let unexpected_err = self.unexpected_err(); 90 | self.report(unexpected_err); 91 | } 92 | self.bump() 93 | } 94 | 95 | /// Reports an error and advances a token. 96 | pub fn report(&mut self, err: ParserError) { 97 | self.reporter.report(Diagnostic::new(err)); 98 | self.bump(); 99 | } 100 | 101 | /// Removes a token if it matches the given one but does not finishes the parsing process if 102 | /// it doesn't, instead it pops the current layout in order to continue parsing. 103 | pub fn expect_or_pop_layout(&mut self, token: TokenData) -> Result<()> { 104 | if self.peek().kind == token { 105 | self.bump(); 106 | } else { 107 | self.lexer.pop_layout(); 108 | } 109 | Ok(()) 110 | } 111 | 112 | fn unexpected(&mut self) -> Result { 113 | Err(self.unexpected_err()) 114 | } 115 | 116 | fn unexpected_err(&mut self) -> ParserError { 117 | error::ParserError::UnexpectedToken( 118 | Box::new(self.peek().clone()), 119 | self.peek().value.span.clone(), 120 | ) 121 | } 122 | 123 | /// Returns true if the current token matches the given one. 124 | pub fn at(&self, token: TokenData) -> bool { 125 | self.peek().kind == token 126 | } 127 | 128 | /// Returns true if the next token matches the given one. 129 | pub fn then(&self, token: TokenData) -> bool { 130 | self.next.kind == token 131 | } 132 | 133 | /// Returns true if the current token matches any of the given ones. 134 | pub fn at_any(&self, tokens: &[TokenData]) -> bool { 135 | tokens.iter().any(|token| self.at(*token)) 136 | } 137 | 138 | /// Returns a list of tokens until the next one matches any of the given ones. 139 | pub fn recover(&mut self, at_any: &[TokenData]) -> Vec { 140 | let mut tokens = Vec::new(); 141 | 142 | while !self.at_any(at_any) && !self.at(TokenData::Eof) { 143 | tokens.push(self.bump()); 144 | } 145 | 146 | tokens 147 | } 148 | 149 | /// It tries to parse the given function and returns the result if it succeeds. Otherwise, if 150 | /// it doesnt have consumed any token, it returns [None]. If it has consumed a token, it 151 | /// returns an error. 152 | pub fn test(&mut self, fun: impl FnOnce(&mut Self) -> Result) -> Result> { 153 | self.eaten = false; 154 | let result = fun(self); 155 | 156 | match result { 157 | Ok(value) => Ok(Some(value)), 158 | Err(error) if self.eaten => Err(error), 159 | Err(_) => Ok(None), 160 | } 161 | } 162 | 163 | /// Returns the current span. 164 | pub fn span(&self) -> Span { 165 | self.peek().value.span.clone() 166 | } 167 | 168 | /// Returns the current token kind. 169 | pub fn token(&self) -> TokenData { 170 | self.peek().kind 171 | } 172 | 173 | /// Creates a span around a function call. 174 | pub fn spanned(&mut self, fun: impl FnOnce(&mut Self) -> Result) -> Result> { 175 | let start = self.span(); 176 | let value = fun(self)?; 177 | let end = self.last_pos.clone(); 178 | 179 | Ok(Spanned::new(value, start.mix(end))) 180 | } 181 | 182 | /// Parses a list of elements separated by a given token. 183 | pub fn sep_by( 184 | &mut self, 185 | sep: TokenData, 186 | mut fun: impl FnMut(&mut Self) -> Result, 187 | ) -> Result)>> { 188 | let mut values = Vec::new(); 189 | 190 | while let Some(res) = self.test(&mut fun)? { 191 | let sep = if self.at(sep) { 192 | Some(self.bump()) 193 | } else { 194 | None 195 | }; 196 | 197 | let at_end = sep.is_none(); 198 | 199 | values.push((res, sep)); 200 | 201 | if at_end { 202 | break; 203 | } 204 | } 205 | 206 | if self.at(sep) && !values.is_empty() { 207 | values.last_mut().unwrap().1 = Some(self.bump()); 208 | } 209 | 210 | Ok(values) 211 | } 212 | 213 | /// Parses a list of elements. 214 | pub fn many(&mut self, mut fun: impl FnMut(&mut Self) -> Result) -> Result> { 215 | let mut values = Vec::new(); 216 | 217 | while let Some(result) = self.test(&mut fun)? { 218 | values.push(result); 219 | } 220 | 221 | Ok(values) 222 | } 223 | 224 | pub fn with_span(&mut self, start: Span) -> Span { 225 | let end = self.last_pos.clone(); 226 | start.mix(end) 227 | } 228 | } 229 | 230 | impl<'a> Parser<'a> { 231 | pub fn parenthesis( 232 | &mut self, 233 | parse: impl Fn(&mut Self) -> Result, 234 | ) -> Result> { 235 | let left = self.expect(TokenData::LPar)?; 236 | let data = parse(self)?; 237 | let right = self.expect(TokenData::RPar)?; 238 | 239 | Ok(Parenthesis { left, data, right }) 240 | } 241 | } 242 | 243 | /// The entrypoint of the parsing, it parses a string into a Program. 244 | pub fn parse(reporter: Report, file_id: FileId, source: &str) -> Program { 245 | let lexer = Lexer::new(source, file_id, reporter.clone()); 246 | let mut parser = Parser::new(lexer, file_id, reporter); 247 | parser.program() 248 | } 249 | --------------------------------------------------------------------------------