├── .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 | 
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