├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── actions-rs
│ └── grcov.yml
├── dependabot.yml
└── workflows
│ ├── README.md
│ ├── cargo-check.yml
│ ├── cargo-clippy.yml
│ ├── cargo-test.yml
│ ├── codecov-io.yml
│ ├── coveralls.yml
│ ├── pages.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Cargo.toml
├── FUNDING.yml
├── LICENSE
├── README.md
├── examples
├── README.md
├── deprecated
│ ├── constraint.wr
│ ├── generic.wr
│ ├── represent.wr
│ ├── types.wr
│ └── union.wr
├── fizzbuzz.wr
└── hello-world.wr
├── pages
├── book
│ └── src
│ │ ├── INTRODUCTION.md
│ │ ├── SUMMARY.md
│ │ └── design-notes
│ │ ├── backend.md
│ │ ├── language-constructs.md
│ │ ├── threads.md
│ │ └── user-defined-optimizations.md
└── static
│ ├── assets
│ ├── favicon.png
│ ├── transparent_logo.png
│ ├── white_logo.png
│ └── wright_logo.svg
│ └── index.html
└── wright
├── Cargo.toml
├── benches
├── lexer.rs
└── parser.rs
├── build.rs
├── rustfmt.toml
├── src
├── ast.rs
├── ast
│ ├── decl.rs
│ ├── decl
│ │ └── import.rs
│ ├── identifier.rs
│ ├── literal.rs
│ ├── old
│ │ ├── astOld.rs
│ │ ├── expression.rs
│ │ ├── expression
│ │ │ ├── primary.rs
│ │ │ ├── primary
│ │ │ │ ├── integer_literal.rs
│ │ │ │ └── parens.rs
│ │ │ └── unary.rs
│ │ ├── test_utils.rs
│ │ └── ty.rs
│ ├── path.rs
│ └── ty.rs
├── bin
│ └── wright.rs
├── lexer.rs
├── lexer
│ ├── comments.rs
│ ├── identifier.rs
│ ├── integer_literal.rs
│ ├── quoted.rs
│ ├── token.rs
│ └── trivial.rs
├── lib.rs
├── parser.rs
├── parser
│ ├── decl.rs
│ ├── decl
│ │ └── import.rs
│ ├── error.rs
│ ├── identifier.rs
│ ├── literal.rs
│ ├── literal
│ │ ├── boolean.rs
│ │ └── integer.rs
│ ├── old
│ │ ├── ast.rs
│ │ ├── ast
│ │ │ ├── declaration.rs
│ │ │ ├── declaration
│ │ │ │ ├── class.rs
│ │ │ │ ├── enum.rs
│ │ │ │ ├── function.rs
│ │ │ │ ├── generics.rs
│ │ │ │ ├── import.rs
│ │ │ │ ├── module.rs
│ │ │ │ ├── type.rs
│ │ │ │ ├── union.rs
│ │ │ │ ├── visibility.rs
│ │ │ │ └── where_clause.rs
│ │ │ ├── expression.rs
│ │ │ ├── expression
│ │ │ │ ├── block.rs
│ │ │ │ ├── literal.rs
│ │ │ │ ├── literal
│ │ │ │ │ ├── boolean.rs
│ │ │ │ │ ├── character.rs
│ │ │ │ │ ├── escapes.rs
│ │ │ │ │ ├── integer.rs
│ │ │ │ │ └── string.rs
│ │ │ │ ├── parentheses.rs
│ │ │ │ └── primary.rs
│ │ │ ├── identifier.rs
│ │ │ ├── metadata.rs
│ │ │ ├── path.rs
│ │ │ ├── statement.rs
│ │ │ ├── statement
│ │ │ │ └── bind.rs
│ │ │ └── types.rs
│ │ ├── error.rs
│ │ ├── state.rs
│ │ ├── util.rs
│ │ └── util
│ │ │ ├── discard_error.rs
│ │ │ ├── erase.rs
│ │ │ ├── first_successful.rs
│ │ │ ├── ignore.rs
│ │ │ └── map.rs
│ ├── path.rs
│ ├── ty.rs
│ ├── ty
│ │ ├── primitive.rs
│ │ └── reference.rs
│ └── whitespace.rs
├── repl.rs
├── reporting.rs
├── source_tracking.rs
├── source_tracking
│ ├── filename.rs
│ ├── fragment.rs
│ ├── immutable_string.rs
│ └── source.rs
├── util.rs
└── util
│ └── supports_unicode.rs
└── tests
├── lexer.rs
└── parser.rs
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots/**
20 | If applicable, add screenshots or logs to help explain your problem.
21 |
22 | **System Information**
23 | - OS: [e.g. Ubuntu 18.04]
24 | - Wright Version [e.g. 1.0.0]
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: "[FEATURE REQUEST]"
5 | labels: feature request
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/actions-rs/grcov.yml:
--------------------------------------------------------------------------------
1 | branch: true
2 | output-type: lcov
3 | output-path: ./lcov.info
4 | ignore-not-existing: true
5 | llvm: true
6 | ignore:
7 | - "/*"
8 | - "../*"
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: cargo
4 | directory: "/wright"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 |
--------------------------------------------------------------------------------
/.github/workflows/README.md:
--------------------------------------------------------------------------------
1 | # Note
2 | If you edit any of the files in this directory that include an LLVM build on multiple platforms, please make sure to
3 | update the other ones to match unless you have a reason not to.
4 |
--------------------------------------------------------------------------------
/.github/workflows/cargo-check.yml:
--------------------------------------------------------------------------------
1 | # Combined cargo check with LLVM installation for the three major platforms.
2 |
3 | name: Cargo Check
4 | on: ["push", "pull_request"]
5 |
6 | # Cancel in-progress runs for previous commits if there are any that haven't completed yet.
7 | concurrency:
8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
9 | cancel-in-progress: true
10 |
11 | jobs:
12 | check:
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | # Different features to check against on all platforms. Currently each group is one feature, since each
17 | # depends on the previous, but more combinations could be added in the future.
18 | features:
19 | - none
20 | - std
21 | - supports-unicode
22 | - source-tracking
23 | - reporting
24 | - file_memmap
25 | - ast-models
26 | - lexer
27 | - parser
28 | - wright_library_defaults
29 | - wright_binary
30 | - default
31 | os: [ubuntu-24.04, windows-latest, macos-latest]
32 | shell: ["bash", "msys2 {0}"]
33 | include:
34 | - os: macos-latest
35 | llvm-install-dir: /opt/homebrew/opt/llvm
36 | - targets: --tests
37 | - features: wright_binary
38 | targets: --bins --tests
39 | - features: default
40 | targets: --bins --tests
41 | exclude:
42 | - os: windows-latest
43 | shell: bash
44 | - os: macos-latest
45 | shell: 'msys2 {0}'
46 | - os: ubuntu-24.04
47 | shell: 'msys2 {0}'
48 |
49 | runs-on: ${{ matrix.os }}
50 | continue-on-error: ${{ matrix.allow-failure || false }}
51 |
52 | defaults:
53 | run:
54 | shell: ${{ matrix.shell }}
55 |
56 | steps:
57 | - name: Checkout Wright source
58 | uses: actions/checkout@v4
59 |
60 | # Use MSYS2 on windows to install and check LLVM
61 | - uses: msys2/setup-msys2@v2
62 | if: ${{ matrix.os == 'windows-latest' }}
63 | with:
64 | update: true
65 | # Use special mingw LLVM package.
66 | # Also install the current stable rust
67 | install: >-
68 | mingw-w64-x86_64-llvm
69 | mingw-w64-x86_64-rust
70 |
71 | # Use stable Rust toolchain
72 | - uses: actions-rs/toolchain@v1
73 | with:
74 | toolchain: stable
75 |
76 | - name: Install LLVM (Ubuntu Only)
77 | if: ${{ matrix.os == 'ubuntu-24.04' }}
78 | # See: https://apt.llvm.org/
79 | # Last line: https://gitlab.com/taricorp/llvm-sys.rs/-/issues/13
80 | run: |
81 | wget https://apt.llvm.org/llvm.sh
82 | chmod +x llvm.sh
83 | sudo ./llvm.sh 18 all
84 | sudo apt install libpolly-18-dev libz-dev
85 |
86 | - name: Install LLVM 18 (Mac Only)
87 | if: ${{ matrix.os == 'macos-latest' }}
88 | run: brew install llvm@18
89 |
90 | - name: Get the LLVM version (Windows Only)
91 | if: ${{ matrix.os == 'windows-latest' }}
92 | run: llvm-config --version
93 |
94 | - name: Get the LLVM version (Mac Only)
95 | if: ${{ matrix.os == 'macos-latest' }}
96 | run: ${{ matrix.llvm-install-dir }}/bin/llvm-config --version
97 | # For some reason, this seems to error even when llvm-config is available somewhere. Leaving it in for now.
98 | continue-on-error: true
99 |
100 | - name: Get the LLVM version (Ubuntu Only)
101 | if: ${{ matrix.os == 'ubuntu-24.04' }}
102 | run: llvm-config --version
103 | # For some reason, this seems to error even when llvm-config is available somewhere. Leaving it in for now.
104 | continue-on-error: true
105 |
106 | - name: Run cargo check (Mac Only)
107 | if: ${{ matrix.os == 'macos-latest' }}
108 | run: cargo check --no-default-features -F ${{ matrix.features }} ${{ matrix.targets }}
109 | env:
110 | LLVM_SYS_180_PREFIX: ${{ matrix.llvm-install-dir }}
111 |
112 | - name: Run cargo check (Ubuntu & Windows)
113 | if: ${{ matrix.os != 'macos-latest' }}
114 | run: cargo check --no-default-features -F ${{ matrix.features }} ${{ matrix.targets }}
115 |
--------------------------------------------------------------------------------
/.github/workflows/cargo-clippy.yml:
--------------------------------------------------------------------------------
1 | name: Clippy
2 |
3 | on: ["push", "pull_request"]
4 |
5 | # Cancel in-progress runs for previous commits if there are any that haven't completed yet.
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8 | cancel-in-progress: true
9 |
10 | jobs:
11 | clippy:
12 | runs-on: ubuntu-24.04
13 | steps:
14 | - uses: actions/checkout@v4
15 | # Use stable Rust toolchain
16 | - uses: actions-rs/toolchain@v1
17 | with:
18 | toolchain: stable
19 | - name: Install LLVM
20 | # See: https://apt.llvm.org/
21 | # Last line: https://gitlab.com/taricorp/llvm-sys.rs/-/issues/13
22 | run: |
23 | wget https://apt.llvm.org/llvm.sh
24 | chmod +x llvm.sh
25 | sudo ./llvm.sh 18 all
26 | sudo apt install libpolly-18-dev libz-dev
27 |
28 | - name: Run Clippy
29 | run: cargo clippy -- --deny clippy::all --deny warnings
30 |
--------------------------------------------------------------------------------
/.github/workflows/cargo-test.yml:
--------------------------------------------------------------------------------
1 | # Combined cargo test with LLVM installation for the three major platforms.
2 |
3 | name: Cargo Test
4 | on: ["push", "pull_request"]
5 |
6 | # Cancel in-progress runs for previous commits if there are any that haven't completed yet.
7 | concurrency:
8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
9 | cancel-in-progress: true
10 |
11 | jobs:
12 | test:
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | # Different features to check against on all platforms. Currently each group is one feature, since each
17 | # depends on the previous, but more combinations could be added in the future.
18 | features:
19 | - none
20 | - std
21 | - supports-unicode
22 | - source-tracking
23 | - reporting
24 | - file_memmap
25 | - ast-models
26 | - lexer
27 | - parser
28 | - wright_library_defaults
29 | - wright_binary
30 | - default
31 | os: [ubuntu-24.04, windows-latest, macos-latest]
32 | shell: ["bash", "msys2 {0}"]
33 | include:
34 | - os: macos-latest
35 | llvm-install-dir: /opt/homebrew/opt/llvm
36 | exclude:
37 | - os: windows-latest
38 | shell: bash
39 | - os: macos-latest
40 | shell: 'msys2 {0}'
41 | - os: ubuntu-24.04
42 | shell: 'msys2 {0}'
43 |
44 | runs-on: ${{ matrix.os }}
45 | continue-on-error: ${{ matrix.allow-failure || false }}
46 |
47 | defaults:
48 | run:
49 | shell: ${{ matrix.shell }}
50 |
51 | steps:
52 | - name: Checkout Wright source
53 | uses: actions/checkout@v4
54 |
55 | # Use MSYS2 on windows to install and check LLVM
56 | - uses: msys2/setup-msys2@v2
57 | if: ${{ matrix.os == 'windows-latest' }}
58 | with:
59 | update: true
60 | # Use special mingw LLVM package.
61 | # Also install the current stable rust
62 | install: >-
63 | mingw-w64-x86_64-llvm
64 | mingw-w64-x86_64-rust
65 |
66 | # Use stable Rust toolchain
67 | - uses: actions-rs/toolchain@v1
68 | with:
69 | toolchain: stable
70 |
71 | - name: Install LLVM (Ubuntu Only)
72 | if: ${{ matrix.os == 'ubuntu-24.04' }}
73 | # See: https://apt.llvm.org/
74 | # Last line: https://gitlab.com/taricorp/llvm-sys.rs/-/issues/13
75 | run: |
76 | wget https://apt.llvm.org/llvm.sh
77 | chmod +x llvm.sh
78 | sudo ./llvm.sh 18 all
79 | sudo apt install libpolly-18-dev libz-dev
80 |
81 | - name: Install LLVM 18 (Mac Only)
82 | if: ${{ matrix.os == 'macos-latest' }}
83 | run: brew install llvm@18
84 |
85 | - name: Get the LLVM version (Windows Only)
86 | if: ${{ matrix.os == 'windows-latest' }}
87 | run: llvm-config --version
88 |
89 | - name: Get the LLVM version (Mac Only)
90 | if: ${{ matrix.os == 'macos-latest' }}
91 | run: ${{ matrix.llvm-install-dir }}/bin/llvm-config --version
92 | # For some reason, this seems to error even when llvm-config is available somewhere. Leaving it in for now.
93 | continue-on-error: true
94 |
95 | - name: Get the LLVM version (Ubuntu Only)
96 | if: ${{ matrix.os == 'ubuntu-24.04' }}
97 | run: llvm-config --version
98 | # For some reason, this seems to error even when llvm-config is available somewhere. Leaving it in for now.
99 | continue-on-error: true
100 |
101 | - name: Run cargo test (Mac Only)
102 | if: ${{ matrix.os == 'macos-latest' }}
103 | run: cargo test --no-default-features -F ${{ matrix.features }} --lib
104 | env:
105 | LLVM_SYS_180_PREFIX: ${{ matrix.llvm-install-dir }}
106 |
107 | - name: Run cargo test (Ubuntu & Windows)
108 | if: ${{ matrix.os != 'macos-latest' }}
109 | run: cargo test --no-default-features -F ${{ matrix.features }} --lib
110 |
--------------------------------------------------------------------------------
/.github/workflows/codecov-io.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | pull_request:
5 | branches:
6 | - "main"
7 | env:
8 | CARGO_TERM_COLOR: always
9 |
10 | # Cancel in-progress runs for previous commits if there are any that haven't completed yet.
11 | concurrency:
12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
13 | cancel-in-progress: true
14 |
15 | name: codecov.io Code Coverage
16 | jobs:
17 | coverage:
18 | runs-on: ubuntu-24.04
19 | steps:
20 | - uses: actions/checkout@v4
21 | # - name: Install LLVM
22 | # # See: https://apt.llvm.org/
23 | # # Last line: https://gitlab.com/taricorp/llvm-sys.rs/-/issues/13
24 | # run: |
25 | # wget https://apt.llvm.org/llvm.sh
26 | # chmod +x llvm.sh
27 | # sudo ./llvm.sh 18 all
28 | # sudo apt install libpolly-18-dev libz-dev
29 | - uses: dtolnay/rust-toolchain@nightly
30 | - name: Install cargo-llvm-cov
31 | uses: taiki-e/install-action@cargo-llvm-cov
32 | - name: Generate code coverage
33 | run: cargo llvm-cov --workspace --no-fail-fast --lcov --output-path lcov.info
34 | - name: Upload coverage reports to Codecov
35 | uses: codecov/codecov-action@v4.0.1
36 | with:
37 | token: ${{ secrets.CODECOV_TOKEN }}
38 | slug: vcfxb/wright-lang
39 |
--------------------------------------------------------------------------------
/.github/workflows/coveralls.yml:
--------------------------------------------------------------------------------
1 | on: ["push", "pull_request"]
2 |
3 | name: coveralls Code Coverage
4 |
5 | # Cancel in-progress runs for previous commits if there are any that haven't completed yet.
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
8 | cancel-in-progress: true
9 |
10 | jobs:
11 | coverage:
12 | runs-on: ubuntu-24.04
13 | steps:
14 | - uses: actions/checkout@v4
15 | # - name: Install LLVM
16 | # # See: https://apt.llvm.org/
17 | # # Last line: https://gitlab.com/taricorp/llvm-sys.rs/-/issues/13
18 | # run: |
19 | # wget https://apt.llvm.org/llvm.sh
20 | # chmod +x llvm.sh
21 | # sudo ./llvm.sh 18 all
22 | # sudo apt install libpolly-18-dev libz-dev
23 | - uses: dtolnay/rust-toolchain@nightly
24 | - name: Install cargo-llvm-cov
25 | uses: taiki-e/install-action@cargo-llvm-cov
26 | - name: Generate code coverage
27 | run: cargo llvm-cov --workspace --no-fail-fast --lcov --output-path lcov.info
28 | - name: Coveralls upload
29 | uses: coverallsapp/github-action@v2.3.6
30 | with:
31 | github-token: ${{ secrets.GITHUB_TOKEN }}
32 | file: lcov.info
33 | format: lcov
34 |
--------------------------------------------------------------------------------
/.github/workflows/pages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Pages
2 |
3 | on:
4 | # Runs on pushes targeting the default branch
5 | push:
6 | branches: ["main"]
7 |
8 | # Allows you to run this workflow manually from the Actions tab
9 | workflow_dispatch:
10 |
11 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
12 | permissions:
13 | contents: read
14 | pages: write
15 | id-token: write
16 |
17 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
18 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
19 | concurrency:
20 | group: "pages"
21 | cancel-in-progress: false
22 |
23 | jobs:
24 | # Build job
25 | build:
26 | runs-on: ubuntu-24.04
27 | steps:
28 | - uses: actions/checkout@v4
29 | # Use nightly Rust toolchain since that's what docs.rs uses and some docs for features/compiler flags
30 | # only work on nightly.
31 | - uses: actions-rs/toolchain@v1
32 | with:
33 | toolchain: nightly
34 | - name: Install LLVM
35 | # See: https://apt.llvm.org/
36 | # Last line: https://gitlab.com/taricorp/llvm-sys.rs/-/issues/13
37 | run: |
38 | wget https://apt.llvm.org/llvm.sh
39 | chmod +x llvm.sh
40 | sudo ./llvm.sh 18 all
41 | sudo apt install libpolly-18-dev libz-dev
42 | - name: Install mdBook
43 | run: cargo install mdbook
44 | - name: Setup Pages
45 | id: pages
46 | uses: actions/configure-pages@v3
47 | - name: Build rust docs
48 | run: |
49 | mkdir tmp
50 | cargo +nightly doc
51 | cp -rv target/doc tmp
52 | - name: Build mdBook
53 | run: |
54 | mdbook build pages/book
55 | cp -rv pages/book/book tmp
56 | - name: Copy static files
57 | run: |
58 | cp -rv pages/static/* tmp
59 | - name: Upload artifact
60 | uses: actions/upload-pages-artifact@v3
61 | with:
62 | path: tmp
63 |
64 | # Deployment job
65 | deploy:
66 | environment:
67 | name: github-pages
68 | url: ${{ steps.deployment.outputs.page_url }}
69 | runs-on: ubuntu-24.04
70 | needs: build
71 | steps:
72 | - name: Deploy to GitHub Pages
73 | id: deployment
74 | uses: actions/deploy-pages@v4
75 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | permissions:
4 | contents: write
5 |
6 | on:
7 | push:
8 | tags:
9 | - v[0-9]+.*
10 |
11 | jobs:
12 | create-release:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: taiki-e/create-gh-release-action@v1
17 | with:
18 | # (optional) Path to changelog.
19 | changelog: CHANGELOG.md
20 | # (required) GitHub token for creating GitHub Releases.
21 | token: ${{ secrets.GITHUB_TOKEN }}
22 |
23 | upload-assets:
24 | needs: create-release
25 | strategy:
26 | matrix:
27 | include:
28 | - target: x86_64-unknown-linux-gnu
29 | os: ubuntu-latest
30 | - target: x86_64-apple-darwin
31 | os: macos-latest
32 | - target: x86_64-pc-windows-msvc
33 | os: windows-latest
34 | runs-on: ${{ matrix.os }}
35 | steps:
36 | - uses: actions/checkout@v4
37 | - run: rustup update stable
38 | - uses: taiki-e/upload-rust-binary-action@v1
39 | with:
40 | # (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload.
41 | # Note that glob pattern is not supported yet.
42 | bin: wright
43 | # (optional) Target triple, default is host triple.
44 | # This is optional but it is recommended that this always be set to
45 | # clarify which target you are building for if macOS is included in
46 | # the matrix because GitHub Actions changed the default architecture
47 | # of macos-latest since macos-14.
48 | target: ${{ matrix.target }}
49 | # (optional) On which platform to distribute the `.tar.gz` file.
50 | # [default value: unix]
51 | # [possible values: all, unix, windows, none]
52 | tar: unix
53 | # (optional) On which platform to distribute the `.zip` file.
54 | # [default value: windows]
55 | # [possible values: all, unix, windows, none]
56 | zip: windows
57 | # (required) GitHub token for uploading assets to GitHub Releases.
58 | token: ${{ secrets.GITHUB_TOKEN }}
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | wright/target/
2 | wright/Cargo.lock
3 | Cargo.lock
4 | .idea/
5 | wright/.idea/
6 | target/
7 | scratchpad/
8 | pages/book/book
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.11.0
2 | - Atomic primitive type signature parsing
3 | - Referernce type signature parsing
4 | - Tweaks to printing/formatting of help messages in diagnostics
5 | - Tweaks/updates to links on website
6 | - Fix bug with import declaration parsing not accepting comments in certain places
7 |
8 | ## 0.10.1
9 | - Fix bug in release workflow that prevented wright binaries from being built
10 |
11 | ## 0.10.0
12 | - Boolean literal parsing
13 | - Import declaration parsing
14 | - Fixes to CI workflows
15 | - Updates to a variety of dependencies
16 | - Update rust edition to 2024 and minimum rust version to 1.85.1
17 |
18 | ## Changelog not kept before version 0.10.0
19 |
20 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## 1. Purpose
4 |
5 | A primary goal of the Wright Programming language community is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
6 |
7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
8 |
9 | We invite all those who participate in the Wright Programming language community to help us create safe and positive experiences for everyone.
10 |
11 | ## 2. Open Source Citizenship
12 |
13 | A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
14 |
15 | Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
16 |
17 | If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
18 |
19 | ## 3. Expected Behavior
20 |
21 | The following behaviors are expected and requested of all community members:
22 |
23 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
24 | * Exercise consideration and respect in your speech and actions.
25 | * Attempt collaboration before conflict.
26 | * Refrain from demeaning, discriminatory, or harassing behavior and speech.
27 | * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
28 | * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
29 |
30 | ## 4. Unacceptable Behavior
31 |
32 | The following behaviors are considered harassment and are unacceptable within our community:
33 |
34 | * Violence, threats of violence or violent language directed against another person.
35 | * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
36 | * Posting or displaying sexually explicit or violent material.
37 | * Posting or threatening to post other people’s personally identifying information ("doxing").
38 | * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
39 | * Inappropriate photography or recording.
40 | * Inappropriate physical contact. You should have someone’s consent before touching them.
41 | * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
42 | * Deliberate intimidation, stalking or following (online or in person).
43 | * Advocating for, or encouraging, any of the above behavior.
44 | * Sustained disruption of community events, including talks and presentations.
45 |
46 | ## 5. Consequences of Unacceptable Behavior
47 |
48 | Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
49 |
50 | Anyone asked to stop unacceptable behavior is expected to comply immediately.
51 |
52 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
53 |
54 | ## 6. Reporting Guidelines
55 |
56 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. venusflameblonde@gmail.com.
57 |
58 |
59 |
60 | Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
61 |
62 | ## 7. Addressing Grievances
63 |
64 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Venus Xeon-Blonde with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
65 |
66 |
67 |
68 | ## 8. Scope
69 |
70 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business.
71 |
72 | This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
73 |
74 | ## 9. Contact info
75 |
76 | venusflameblonde@gmail.com
77 |
78 | ## 10. License and attribution
79 |
80 | This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
81 |
82 | Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
83 |
84 | Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/)
85 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["wright"]
3 | # Use the Rust 2021 resolver.
4 | resolver = "2"
5 |
6 | [workspace.package]
7 | edition = "2024"
8 | # Use [cargo msrv](https://crates.io/crates/cargo-msrv) to make sure we get an accurate value for this.
9 | rust-version = "1.85.1"
10 |
--------------------------------------------------------------------------------
/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: vcfxb
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Venus Xeon-Blonde
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Wright has not yet reached version 1.0.0 yet, and is currently in an incomplete/experimental state.
2 |
3 | # The Wright Programming Language
4 | ## *A language that flies*
5 |
6 | *Wright is an all-purpose programming language inspired by Rust, Ada, and Typescript.
7 | Pulling from all three of these excellent languages, Wright intends to offer a combination of speed, ergonomics, and precision.*
8 |
9 | ### Badges
10 | *Wright is automatically checked and tested using the latest available github runners for Ubuntu, MacOS, and Windows*
11 | | Service | Badge |
12 | |:---:|:---:|
13 | | Cargo Check Status |  |
14 | | Cargo Test Status |  |
15 | | Cargo Clippy Status |  |
16 | | Code Coverage (Coveralls) | [](https://coveralls.io/github/vcfxb/wright-lang?branch=main) |
17 | | Code Coverage (Codecov.io) | [](https://codecov.io/github/vcfxb/wright-lang/commits?branch=main) |
18 | | Docs.rs | [](https://docs.rs/wright) |
19 | | Crates.io | [](https://crates.io/crates/wright) |
20 | | GitHub release | [](https://github.com/vcfxb/wright-lang/releases) |
21 | | GitHub (pre-)release | [](https://github.com/vcfxb/wright-lang/releases) |
22 | | Development Status |  |
23 |
24 |
25 |
26 | | | Downloads|
27 | |:---:|:---:|
28 | | Total | |
29 | | Releases |  |
30 | | Pre-Releases|  |
31 | | Crates.io | [](https://crates.io/crates/wright) |
32 | | Crates.io (Latest) | [](https://crates.io/crates/wright/0.10.1) |
33 |
34 | ### Syntax Samples
35 | ```
36 | // Hello World!
37 | use wright::io::println;
38 |
39 | func main() {
40 | println("Hello World!");
41 | }
42 | ```
43 |
44 | ```
45 | // FizzBuzz 1 through 100
46 | use wright::io::println;
47 |
48 | type FizzBuzzInteger = integer constrain |i| { i <= 100 && i >= 0 };
49 |
50 | func fizzbuzz(i: FizzBuzzInteger) {
51 | if i % 15 == 0 { println("FizzBuzz"); }
52 | else if i % 5 == 0 { println("Buzz"); }
53 | else if i % 3 == 0 { println("Fizz"); }
54 | else { println(i); }
55 | }
56 |
57 | func main() {
58 | // Compiler error here if we use a range iterator that contains a value violating the constraints of
59 | // `FizzBuzzInteger`.
60 | (1..=100).for_each(fizzbuzz);
61 | }
62 | ```
63 |
64 | ### The core goals of the language:
65 | * __Developer experience__ -- Every error message, syntax choice, and standard library function should be friendly and well
66 | documented.
67 | * __Robustness__ -- Wright's type system should be expressive enough to appropriately capture the domain, representation,
68 | and functionality of every symbol the programmer interacts with.
69 | * __Speed__ -- Wright leverages the newest major version of LLVM (at the time of writing, LLVM 18), to compile code
70 | directly to assembly, avoiding the overhead of an interpreter, garbage collector, and other associated tools
71 | by default.
72 | * __Memory Safety__ -- Wright pulls significant inspiration from Rust's lifetime system, with some modifications.
73 |
74 | ### Installation:
75 | There are several installation options.
76 | - Get the latest stable version from [the releases page](https://github.com/vcfxb/wright-lang/releases).
77 | - If you have rust, via `cargo install wright`.
78 | - Building from source, by cloning this repository, and running `cargo build --release` in the wright directory, and
79 | then adding `wright/target/release` to your system path. You will need LLVM 18 installed and appropriately
80 | configured to compile Wright. See the [llvm-sys crate docs](https://crates.io/crates/llvm-sys) for tips on how to do
81 | this.
82 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples of wright source code.
2 |
3 | These examples are used to showcase different syntax and design patterns featured in the wright programming language.
4 |
5 | Some of these have been moved to the `deprecated` folder to indicate that they have not been maintained, and may not
6 | reflect the current state of wright and its syntax.
7 |
--------------------------------------------------------------------------------
/examples/deprecated/constraint.wr:
--------------------------------------------------------------------------------
1 |
2 | import wright.cmp;
3 |
4 |
5 |
6 | type KnownMin is integer constrain { self >= MIN };
7 |
8 |
9 | type KnownMax
10 | where T: cmp.Ord
11 | is T constrain T <= MAX;
12 |
13 | type KnownMin
14 | where T: cmp.Ord
15 | is T constrain T >= MIN;
16 |
17 | ## --- OR ---
18 |
19 | constraint KnownMax(t: T) as T
20 | where T: cmp.Ord {
21 | t <= self
22 | }
23 |
24 | constraint KnownMin(t: T) as T
25 | where T: cmp.Ord {
26 | t >= self
27 | }
28 |
29 | constraint KnownRange(t: T) as void
30 | where T: KnownMin + KnownMax
31 | {
32 | ## The maximum value that the constrained T could be.
33 | max_inclusive: T,
34 | ## The minimum value that the constrained T could be.
35 | min_inclusive: T,
36 | }
37 |
38 |
39 |
40 | constraint KnownMax(a: T, b: T)
41 |
42 |
43 | ... not sure yet what I want syntax to look like here.
44 |
45 |
--------------------------------------------------------------------------------
/examples/deprecated/generic.wr:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/deprecated/represent.wr:
--------------------------------------------------------------------------------
1 |
2 |
3 | import wright::box::Box;
4 | import wright::box::NullableBox;
5 |
6 | type Option {
7 | func some(t: T) -> Self;
8 | func none() -> Self;
9 | func is_some(&self) -> bool;
10 | func is_none(&self) -> bool;
11 | # ... etc
12 | }
13 |
14 | union DefaultOptionRepresentation { some: T | none: void };
15 |
16 | implement Option as DefaultOptionRepresentation {
17 | const func some(t: T) -> Self {
18 | DefaultOptionRepresentation { some: t }
19 | }
20 |
21 | const func none() -> Self {
22 | DefaultOptionRepresentation { none: void }
23 | }
24 |
25 | const func is_some(&self) -> bool {
26 | self is DefaultOptionRepresentation.some
27 | }
28 |
29 | const func is_none(&self) -> bool {
30 | self is DefaultOptionRepresentation.none
31 | }
32 |
33 | # ... etc
34 | }
35 |
36 | implement Option> as NullableBox {
37 | func some(t: T) -> Self {
38 | Box::new(t) as NullableBox
39 | }
40 |
41 | const func none() -> Self {
42 | NullableBox::null()
43 | }
44 |
45 | const fn is_some(&self) -> bool {
46 | !self.is_null()
47 | }
48 |
49 | const fn is_none(&self) -> bool {
50 | self.is_null()
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/examples/deprecated/types.wr:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Some types in wright.
4 |
5 | type MyUnion = void | String constrain not String::empty;
6 | type TaggedUnion = { None: void | Name: String constrain not String::empty };
7 |
8 | type MyRecord = { field1: String, field2: integer, field3: integer constrain in 0..=255 };
9 |
10 | type MyEnum = enum of void { VariantA, VariantB, VariantC };
11 |
12 | record MyRecord2 {
13 | field1: String constrain not String::empty,
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/examples/deprecated/union.wr:
--------------------------------------------------------------------------------
1 |
2 | type PerhapsUrl = union { url: Url | not_url: String };
3 |
4 | func main() {
5 | let google: PerhapsUrl = { url: Url::from("https://google.com") };
6 |
7 | if google is PerhapsUrl::url { // could also do `if google is Url`.
8 | println(f"{type of google}");
9 | # prints "PerhapsUrl constrain union.variant = PerhapsUrl=>google and union.type = Url and union.state = "google.com" ..."
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/fizzbuzz.wr:
--------------------------------------------------------------------------------
1 | // FizzBuzz 1 through 100
2 | use wright::io::println;
3 |
4 | type FizzBuzzInteger = integer constrain |i| { i <= 100 && i >= 0 };
5 |
6 | func fizzbuzz(i: FizzBuzzInteger) {
7 | if i % 15 == 0 { println("FizzBuzz"); }
8 | else if i % 5 == 0 { println("Buzz"); }
9 | else if i % 3 == 0 { println("Fizz"); }
10 | else { println(i); }
11 | }
12 |
13 | func main() {
14 | // Compiler error here if we use a range iterator that contains a value violating the constraints of
15 | // `FizzBuzzInteger`.
16 | (1..=100).for_each(fizzbuzz);
17 | }
18 |
--------------------------------------------------------------------------------
/examples/hello-world.wr:
--------------------------------------------------------------------------------
1 | // Hello World!
2 | use wright::io::println;
3 |
4 | func main() {
5 | println("Hello World!");
6 | }
7 |
--------------------------------------------------------------------------------
/pages/book/src/INTRODUCTION.md:
--------------------------------------------------------------------------------
1 | Test
--------------------------------------------------------------------------------
/pages/book/src/SUMMARY.md:
--------------------------------------------------------------------------------
1 |
2 | # Summary
3 |
4 | [Introduction](./INTRODUCTION.md)
5 |
6 | # Design Notes
7 |
8 | - [Language Constructs](./design-notes/language-constructs.md)
9 | - [User Defined Optimizations](./design-notes/user-defined-optimizations.md)
10 | - [Threads](./design-notes/threads.md)
11 | - [Backend](./design-notes/backend.md)
12 |
13 |
--------------------------------------------------------------------------------
/pages/book/src/design-notes/backend.md:
--------------------------------------------------------------------------------
1 |
2 | # The Backend(s) of the Wright Compiler and Interpreter.
3 |
4 | I have had a many thoughts, opinions, and different stances on what I wanted to build for Wright's backend. I have
5 | changed my mind more times than I can count, and I'm certain I will continue to change my mind on this several more
6 | times.
7 |
8 | So far it appears there are a few main target options:
9 |
10 | | | LLVM | Cranelift | JVM / Java Bytecode | Bespoke bytecode compiler & interpreter | Bespoke bytecode compiler & transpiler |
11 | |--- | --- | --- | --- | --- | --- |
12 | | Output | Machine code | Machine code | .class file | Custom bytecode | Custom bytecode & transpiler targets |
13 | | Targets | very many | `x86_64`, `aarch64` (ARM64), `s390x` (IBM Z), `riscv64` | JVM | Anything that the rust based interpreter can run on | very many (assuming transpile to LLVM) |
14 |
15 | Right now I'm largely tempted to target both a bespoke bytecode interpreter (perhaps in addition to a transpiler) and
16 | LLVM. I like the idea of compiling to Cranelift as well, but the additional work for it may be more than it's worth.
17 | Compiling to the JVM would be cool for interoperability with Java/Scala/Kotlin/etc programs, but my language is so
18 | different from them that there would be a significant amount lost in translation from Wright to the JVM. I will start
19 | with the bespoke interpreter/transpiler.
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/pages/book/src/design-notes/language-constructs.md:
--------------------------------------------------------------------------------
1 |
2 | # Language constructs
3 |
4 | There are a variety of constructed abstractions in a language that not only give it logical/expressive power, but help
5 | guide and define the user-friendlyness of the language. A good example of this is Rust's lifetime and borrowing rules.
6 | While these rules make it possible to write and express programs that would be difficult to keep track of in a language
7 | like C, it also steepens the language's learning curve.
8 |
9 | In designing Wright, I want to make a wide variety of language constructs available to the user to help make the language
10 | more elegant, without making it too much more difficult to use. In designing these language constructs, a few principles
11 | should be kept in mind.
12 |
13 | 1. Wright aims to be a relatively simple, easy to use language.
14 | 2. Wright aims to protect the user, to the greatest extent possible, from writing buggy code.
15 | 3. Wright aims to show an appropriate amount of the language's internals. Users should be able to reason about how their
16 | code runs and what allocates, or doesn't.
17 | 4. Wright is a multipurpose programming language. No one paradigm or style should be expressly promoted over others.
18 |
19 | With those principles in mind, we can start to establish a set of features to guide the language's design.
20 |
21 | 1. Wright is strongly typed, and will infer types wherever possible.
22 | 2. ~~Wright is garbage collected.~~ -- I changed my mind on this -- Wright will have lifetimes and borrowing similar to
23 | Rust.
24 | 3. Wright has traits.
25 | 4. Wright has enumerations.
26 | 5. Wright has tagged unions.
27 | 6. Wright has ~~classes~~ record types.
28 | 7. Wright has type aliases.
29 | 8. Wright has constraints (to be discussed further).
30 | 9. Wright has inheritance for traits, enumerations, tagged unions, and constraints.
31 | 10. Functions are first class language constructs in Wright.
32 | 11. Wright does not have macros -- most macro-style meta programming should be achievable with generics.
33 | 12. Wright has abstract types -- representation & implementation can be dependent on the generic used.
34 |
35 | ## On Constraints:
36 |
37 | Wright will be one of the few multipurpose languages that I know of to use constraints. Constraints can be a very
38 | powerful tool for logical induction. They allow a programmer to define and check parts of their program at compile time.
39 | Wright constraints will be invokable both at compile time and at runtime. There may be some exceptions if we ever decide
40 | to allow definition of compile-time only (`const constraint`) constraints. Constraints will be strongly bound to a type,
41 | but that type may be generic (so constraints on lists and arrays will be possible). Constraints will act very similarly
42 | to functions, carrying zero sense of state or instantiation like a class might.
43 |
44 | ## Note
45 |
46 | This document is a work in progress, and may be changed or updated further at a later date.
47 |
--------------------------------------------------------------------------------
/pages/book/src/design-notes/threads.md:
--------------------------------------------------------------------------------
1 |
2 | For many languages, threading can be a point of tension. When to use it (especially now that single-threaded async is more common),
3 | how to use it, and how to optimize it are all common issues.
4 |
5 | In building wright, I decided it would be best to separate async and syncronous code/threads to avoid unnecessarily
6 | compiling/linking/running an async runtime to manage futures.
7 |
--------------------------------------------------------------------------------
/pages/book/src/design-notes/user-defined-optimizations.md:
--------------------------------------------------------------------------------
1 |
2 | # User defined optimizations
3 |
4 | One of the hardest things for me to reconcile as I build this language is how to make it high-level, while still
5 | providing the ability to do relatively low-level things. I would make it completely low-level, however Rust already
6 | exists as a well-liked, mature, production-ready, memory-safe language with many of the same features I hope to build
7 | into Wright. Building Wright as another low-level language with a borrow checker and functional programming elements
8 | would not only make it completely derivative of Rust, but also introduce many of the same drawbacks that Rust has in
9 | terms of expressing Futures & other complex memory-related types and in terms of learning-curve (especially around
10 | the borrow checker).
11 |
12 | In order to do both, the vast majority of programming in wright will be covered under a garbage
13 | collector. Programmers will write classes, enums, and unions, without ever thinking too hard about memory allocation or
14 | management.
15 |
16 | ... TBD
17 |
--------------------------------------------------------------------------------
/pages/static/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vcfxb/wright-lang/35d28bb47428ee3bac7d4552b2d0bf98e1efd499/pages/static/assets/favicon.png
--------------------------------------------------------------------------------
/pages/static/assets/transparent_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vcfxb/wright-lang/35d28bb47428ee3bac7d4552b2d0bf98e1efd499/pages/static/assets/transparent_logo.png
--------------------------------------------------------------------------------
/pages/static/assets/white_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vcfxb/wright-lang/35d28bb47428ee3bac7d4552b2d0bf98e1efd499/pages/static/assets/white_logo.png
--------------------------------------------------------------------------------
/pages/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Wright
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
The Wright Programming Language
21 |
22 |
23 | API docs (latest release)
24 |
25 |
26 | API docs (main)
27 |
28 |
29 | Book
30 |
31 |
32 | Github
33 |
34 |
35 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/wright/Cargo.toml:
--------------------------------------------------------------------------------
1 | # PACKAGE METADATA
2 | [package]
3 | name = "wright"
4 | description = "The wright programming language compiler and tooling."
5 | license = "MIT"
6 | version = "0.11.0"
7 | authors = ["Venus Xeon-Blonde "]
8 | repository = "https://github.com/vcfxb/wright-lang"
9 | documentation = "https://docs.rs/wright"
10 | readme = "../README.md"
11 | keywords = ["wright", "language", "bytecode", "compiler", "interpreter"]
12 | edition.workspace = true
13 | rust-version.workspace = true
14 |
15 | # DOC.RS SPECIFIC METADATA
16 | [package.metadata.docs.rs]
17 | features = ["wright_library_defaults"]
18 |
19 | # CRATES.IO BADGES
20 | [badges]
21 | maintenance = {status = "actively-developed"}
22 |
23 | # LIBRARY METADATA
24 | [lib]
25 | name = "wright"
26 | test = true
27 | doctest = true
28 | doc = true
29 |
30 | # BINARIES
31 | [[bin]]
32 | name = "wright"
33 | test = false
34 | doc = false
35 | doctest = false
36 | required-features = []
37 |
38 | # BENCHMARKS
39 | [[bench]]
40 | name = "lexer"
41 | harness = false
42 |
43 | [[bench]]
44 | name = "parser"
45 | harness = false
46 |
47 | # FEATURE FLAGS
48 | # These are used to determine which parts of the crate are compiled/available.
49 | [features]
50 |
51 | # By default include everything required for building the wright binary, which includes everything used for building
52 | # wright as a library.
53 | # Also include support for detecting unicode capabilities on the host terminal.
54 | default = [
55 | "wright_binary",
56 | "supports-unicode"
57 | ]
58 |
59 | # Features and dependencies required for the wright binary (currently just the library defaults and `clap`).
60 | wright_binary = [
61 | "wright_library_defaults",
62 | "dep:clap"
63 | ]
64 |
65 | # Features and dependencies useful when the wright binary is not being built or used.
66 | wright_library_defaults = [
67 | "file_memmap",
68 | "parser"
69 | ]
70 |
71 | # Wright's parser depends on the ability to report parsing errors and construct AST models.
72 | parser = [
73 | "reporting",
74 | "ast-models",
75 | "lexer"
76 | ]
77 |
78 | # Wright's abstract syntax tree model is built on types from the "source_tracking" module.
79 | ast-models = [
80 | "source-tracking",
81 | "dep:num"
82 | ]
83 |
84 | # Wright's lexical analyzer is build using types from the "source_tracking" module.
85 | lexer = [
86 | "source-tracking",
87 | "dep:unicode-ident"
88 | ]
89 |
90 | # Loading memory mapped files from the disk requires memmap2, fs4, and the reporting feature to correctly and efficiently
91 | # read from disk. We also use `anyhow` to make error handling easier.
92 | file_memmap = [
93 | "reporting",
94 | "dep:memmap2",
95 | "dep:fs4"
96 | ]
97 |
98 | # Reporting errors requires source tracking, codespan-reporting (for rendering diagnostics), and
99 | # termcolor (for pretty output).
100 | reporting = [
101 | "source-tracking",
102 | "dep:termcolor",
103 | "dep:codespan-reporting"
104 | ]
105 |
106 | # Source tracking requires just a few dependencies and standard library.
107 | source-tracking = [
108 | "std",
109 | "dep:dashmap",
110 | "derive_more/display",
111 | ]
112 |
113 | # Optional dependency that enables terminal unicode support selection.
114 | # There are fallbacks -- this is not required for anything else.
115 | supports-unicode = [
116 | "dep:supports-unicode"
117 | ]
118 |
119 | # Feature flag to indicate use of the standard library.
120 | std = [
121 | "derive_more?/std"
122 | ]
123 |
124 | # Feature flag indicating no features are enabled.
125 | none = []
126 |
127 | # SIMPLE DEPENDENCIES:
128 | [dependencies]
129 |
130 | # DEPENDENCIES:
131 |
132 | # Use supports-unicode to determine how we display tokens to the user in debug commands.
133 | # Optional -- can be used in debugging token outputs.
134 | [dependencies.supports-unicode]
135 | version = "3.0.0"
136 | optional = true
137 |
138 | # Num gives us integer types of unbound size/domain.
139 | # Used in AST node representations for integer literals.
140 | [dependencies.num]
141 | version = "0.4"
142 | optional = true
143 |
144 | # Unicode identifier functions.
145 | # Used by:
146 | # - "parser"
147 | [dependencies.unicode-ident]
148 | version = "1.0"
149 | optional = true
150 |
151 | # derive_more is used for allowing us to derive additional traits like From and Display.
152 | # Currently used by features:
153 | # - "source-tracking"
154 | [dependencies.derive_more]
155 | version = "2"
156 | default-features = false
157 | optional = true
158 |
159 | # dashmap is used as a fast, concurrent hash map implementation
160 | # Optional since it's used for source tracking, which can be turned off.
161 | [dependencies.dashmap]
162 | version = "6.0.1"
163 | features = ["rayon"]
164 | optional = true
165 |
166 | # codespan-reporting is the internal engine used to render diagnostics.
167 | # Optional since it's only used when error reporting is required.
168 | [dependencies.codespan-reporting]
169 | version = "0.11.1"
170 | optional = true
171 |
172 | # Terminal output colors
173 | # Optional: Required for reporting.
174 | [dependencies.termcolor]
175 | version = "1.4.1"
176 | optional = true
177 |
178 | # Memory mapped files.
179 | # Optional: Required for memmory mapped file access.
180 | [dependencies.memmap2]
181 | version = "0.9.3"
182 | optional = true
183 |
184 | # Portable (windows, mac, linux) file locking
185 | # Optional: Required for memmory mapped file access.
186 | [dependencies.fs4]
187 | version = "0.12.0"
188 | features = ["sync"]
189 | optional = true
190 |
191 | # Comand-line interface generator
192 | # Optional: Used only by the wright binary.
193 | [dependencies.clap]
194 | version = "4"
195 | features = ["derive"]
196 | optional = true
197 |
198 | # TODO: LLVM has been removed until I'm actually using it and have a better build system to go against it.
199 | # (currently the state of it breaking docs.rs builds and complicating everything else makes me kinda sad).
200 | # # Unsafe bindings to LLVM
201 | # # See https://llvm.org/.
202 | # # Optional: Currently not required by anything yet.
203 | # [dependencies.llvm-sys]
204 | # version = "181"
205 | # features = ["force-static"]
206 | # optional = true
207 |
208 | # TEST DEPENDENCIES
209 | [dev-dependencies]
210 |
211 | # Criterion is used for benchmarking.
212 | criterion = "0.5.1"
213 |
214 | # Rayon is used to do various brute-force tests in parallel
215 | rayon = "1.8.0"
216 |
217 | # indoc is used for indentation in tests
218 | indoc = "2.0.5"
219 |
220 | # For creating in memory buffers to test reporting.
221 | termcolor = "1.4.1"
222 |
223 | # BUILD DEPENDENCIES
224 | [build-dependencies]
225 |
226 | # Used for showing feature/cfg info on rustdoc/docs.rs.
227 | rustc_version = "0.4.0"
228 |
229 | # Used for capturing build time and platform information and making it available at runtime.
230 | built = "0.7"
231 |
--------------------------------------------------------------------------------
/wright/benches/lexer.rs:
--------------------------------------------------------------------------------
1 | //! Lexer benchmarks.
2 |
3 | use std::sync::Arc;
4 |
5 | use criterion::{Bencher, Criterion, black_box, criterion_group, criterion_main};
6 | use wright::{
7 | lexer::Lexer,
8 | source_tracking::{filename::FileName, source::Source},
9 | };
10 |
11 | fn make_test_lexer(s: &str) -> Lexer {
12 | let source = Source::new_from_string(FileName::None, s.to_owned());
13 | Lexer::new(Arc::new(source))
14 | }
15 |
16 | fn bench_symbol_tokens(c: &mut Criterion) {
17 | // Make a benchmark group.
18 | let mut group = c.benchmark_group("lexer symbol benchmarks");
19 |
20 | // Function to make a lexer and get a token from it.
21 | fn make_lexer_and_get_token(b: &mut Bencher, input: &str) {
22 | b.iter(|| black_box(make_test_lexer(input).next_token()));
23 | }
24 |
25 | let inputs = ["+", "+=", "*", "@", "?"];
26 |
27 | for i in inputs {
28 | group.bench_with_input(format!("lexer {i}"), i, make_lexer_and_get_token);
29 | }
30 | }
31 |
32 | fn bench_block_doc_comment(c: &mut Criterion) {
33 | c.bench_function("lexer block style doc comment", move |b: &mut Bencher| {
34 | b.iter(move || {
35 | black_box(make_test_lexer("/*! \n this is a block-style comment \n\n */").next_token())
36 | });
37 | });
38 | }
39 |
40 | criterion_group!(benches, bench_symbol_tokens, bench_block_doc_comment);
41 | criterion_main!(benches);
42 |
--------------------------------------------------------------------------------
/wright/benches/parser.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 |
3 | use criterion::{Bencher, Criterion, black_box, criterion_group, criterion_main};
4 | use wright::{
5 | ast::identifier::Identifier,
6 | lexer::Lexer,
7 | parser::Parser,
8 | source_tracking::{SourceMap, filename::FileName, source::Source},
9 | };
10 |
11 | fn bench_parse_identifier(c: &mut Criterion) {
12 | c.bench_function("parse identifier", move |b: &mut Bencher| {
13 | let map = SourceMap::new();
14 | let source_ref = map.add(Source::new_from_static_str(FileName::None, "test_ident"));
15 | b.iter(|| {
16 | let parser = Parser::new(Lexer::new(Arc::clone(&source_ref)));
17 | Identifier::parse(&mut black_box(parser)).unwrap()
18 | });
19 | });
20 | }
21 |
22 | criterion_group!(benches, bench_parse_identifier);
23 | criterion_main!(benches);
24 |
--------------------------------------------------------------------------------
/wright/build.rs:
--------------------------------------------------------------------------------
1 | //! Build script for wright.
2 | //! This is used for capturing build environment info which is used at runtime.
3 |
4 | use rustc_version::{Channel, version_meta};
5 |
6 | fn main() {
7 | // Set a cfg flag if we're on the nightly channel.
8 |
9 | println!("cargo::rustc-check-cfg=cfg(CHANNEL_NIGHTLY)");
10 | if version_meta().unwrap().channel == Channel::Nightly {
11 | println!("cargo:rustc-cfg=CHANNEL_NIGHTLY");
12 | }
13 |
14 | // Save build info.
15 | // See https://docs.rs/built/0.7.4/built/index.html.
16 | built::write_built_file().expect("Failed to acquire build-time information");
17 | }
18 |
--------------------------------------------------------------------------------
/wright/rustfmt.toml:
--------------------------------------------------------------------------------
1 | # Unstable currently
2 | #merge_imports = true
3 |
4 | newline_style = "Native"
5 | max_width = 100
6 | fn_call_width = 80
7 | match_arm_leading_pipes = "Preserve"
8 |
9 |
--------------------------------------------------------------------------------
/wright/src/ast.rs:
--------------------------------------------------------------------------------
1 | //! [Abstract syntax tree] modeling.
2 | //!
3 | //! [Abstract syntax tree]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
4 |
5 | pub mod decl;
6 | pub mod identifier;
7 | pub mod literal;
8 | pub mod path;
9 | pub mod ty;
10 |
--------------------------------------------------------------------------------
/wright/src/ast/decl.rs:
--------------------------------------------------------------------------------
1 | //! Abstract syntax trees related to top-level declarations in source code.
2 |
3 | pub mod import;
4 |
--------------------------------------------------------------------------------
/wright/src/ast/decl/import.rs:
--------------------------------------------------------------------------------
1 | //! Import declarations.
2 | //!
3 | //! These are similar to the rust `use ...;` style delcarations however with the caveat that wright currently
4 | //! only supports a single path in declarations, rather than a tree of items with curly braces. (we also don't support
5 | //! starting with a `::` prefix yet).
6 |
7 | use crate::{
8 | ast::{identifier::Identifier, path::Path},
9 | source_tracking::fragment::Fragment,
10 | };
11 |
12 | /// A `use item::from::elsewhere [as name];` declaration in a wright source file.
13 | #[derive(Debug)]
14 | pub struct ImportDecl {
15 | /// The full matching source of the declaration, whitespace and all.
16 | pub matching_source: Fragment,
17 |
18 | /// The item being imported.
19 | pub imported_item: Path,
20 |
21 | /// The name it's imported as (usually [None]).
22 | pub imported_as: Option,
23 | }
24 |
--------------------------------------------------------------------------------
/wright/src/ast/identifier.rs:
--------------------------------------------------------------------------------
1 | //! [Identifier]s are used throughout wright as variable names, type names, function names, etc.
2 | //! Their modeling is pretty simple, and is defined here.
3 | //!
4 | //! [Identifier]: https://en.wikipedia.org/wiki/Identifier
5 |
6 | use crate::source_tracking::fragment::Fragment;
7 |
8 | /// Identifiers are used as names for variables, functions, modules, etc.
9 | /// These are defined using [Fragment]s of source code, which will contain the identifier itself.
10 | #[derive(Debug, Clone)]
11 | pub struct Identifier {
12 | /// The fragment of source code containing the identifier.
13 | pub fragment: Fragment,
14 | }
15 |
--------------------------------------------------------------------------------
/wright/src/ast/literal.rs:
--------------------------------------------------------------------------------
1 | //! AST node models representing literal values in source code.
2 |
3 | use num::BigUint;
4 |
5 | use crate::source_tracking::fragment::Fragment;
6 |
7 | /// An integer literal from source. This only contains unsigned integers as writing negative numbers is considered
8 | /// to be a combination of an integer literal with a unary negation.
9 | #[derive(Debug)]
10 | pub struct IntegerLiteral {
11 | /// The [Fragment] of source code containing this integer literal.
12 | pub fragment: Fragment,
13 |
14 | /// The value of the integer parsed from the matching source.
15 | pub value: BigUint,
16 | }
17 |
18 | /// A boolean literal from source.
19 | #[derive(Debug)]
20 | pub struct BooleanLiteral {
21 | /// The [Fragment] of source code containing this boolean literal.
22 | pub fragment: Fragment,
23 |
24 | /// The value of the boolean literal.
25 | pub value: bool,
26 | }
27 |
--------------------------------------------------------------------------------
/wright/src/ast/old/expression.rs:
--------------------------------------------------------------------------------
1 | //! Expression parsing in Wright source code.
2 |
3 | use self::primary::{PrimaryExpression, PrimaryExpressionParsingError};
4 | use super::AstNode;
5 | use crate::parser::fragment::Fragment;
6 |
7 | pub mod primary;
8 | pub mod unary;
9 |
10 | /// An expression in Wright source code is anything that can be evaluated to a value.
11 | #[derive(Debug)]
12 | pub enum Expression<'src> {
13 | Primary(PrimaryExpression<'src>),
14 | }
15 |
16 | /// An error that occurs while parsing an expression.
17 | #[derive(Debug, Clone)]
18 | pub enum ExpressionParsingError<'src> {
19 | /// An expression was expected but not found.
20 | ExpectedExpression {
21 | /// Where the expression was expected.
22 | at: Fragment<'src>,
23 | },
24 |
25 | /// An error parsing a primary expression not caused by inavailability.
26 | PrimaryExpressionParsingError(PrimaryExpressionParsingError<'src>),
27 | }
28 |
29 | #[rustfmt::skip] // Do not auto-reformat this block -- the match arms get too mangled.
30 | impl<'src> AstNode<'src> for Expression<'src> {
31 | type Error = ExpressionParsingError<'src>;
32 |
33 | fn fragment(&self) -> Fragment<'src> {
34 | match self {
35 | Expression::Primary(primary) => primary.fragment(),
36 | }
37 | }
38 |
39 | fn try_parse(ctx: &mut super::AstGeneratorContext<'src>) -> Result
40 | where
41 | Self: Sized,
42 | {
43 | // We need to go in reverse order of strength here (from weakest to strongest) to avoid under parsing.
44 | // (i.e. parsing a primary when the primary expression was the left side of a binary expression).
45 |
46 | // Try parsing a binary expression.
47 | match PrimaryExpression::try_parse(ctx) {
48 | // If we get a primary, early return it.
49 | Ok(primary) => return Ok(Expression::Primary(primary)),
50 |
51 | // If we get an error that is not unavailability, return it early too.
52 | Err(err @ (
53 | | PrimaryExpressionParsingError::OtherIntegerLiteralParsingError(_)
54 | | PrimaryExpressionParsingError::OtherParensExpressionParsingError(_)
55 | )) => {
56 | return Err(ExpressionParsingError::PrimaryExpressionParsingError(err));
57 | }
58 |
59 | // If we get an error that is unavailability, just ignore it and keep going.
60 | Err(PrimaryExpressionParsingError::ExpectedPrimaryExpression { .. }) => {}
61 | }
62 |
63 | // If we get to the end of the function with no parse sucessful, we error.
64 | Err(ExpressionParsingError::ExpectedExpression {
65 | at: ctx.peek_fragment(),
66 | })
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/wright/src/ast/old/expression/primary.rs:
--------------------------------------------------------------------------------
1 | //! Primary expression parsing in Wright source code.
2 | //!
3 | //! Primary expressions are considered the atoms of most expressions most primary expressions are literals,
4 | //! which cannot be broken up into sub-expressions.
5 |
6 | use self::{
7 | integer_literal::{IntegerLiteral, IntegerLiteralParsingError},
8 | parens::{ParensExpression, ParensParsingError},
9 | };
10 | use crate::parser::{ast::{AstGeneratorContext, AstNode}, fragment::Fragment};
11 |
12 | pub mod integer_literal;
13 | pub mod parens;
14 |
15 | /// A primary expression in Wright source code. These are the atoms of expressions, and can be combined using operators
16 | /// to form more complicated expressions.
17 | #[derive(Debug)]
18 | pub enum PrimaryExpression<'src> {
19 | /// An integer literal in wright source code.
20 | IntegerLiteral(IntegerLiteral<'src>),
21 |
22 | /// An expression in parentheses.
23 | ParensExpression(ParensExpression<'src>),
24 | }
25 |
26 | /// Error occuring when someone attempts to parse a primary expression and there is not one.
27 | #[derive(Clone, Debug)]
28 | pub enum PrimaryExpressionParsingError<'src> {
29 | /// An attempt was made to parse a primary expression and there was not one available.
30 | ExpectedPrimaryExpression {
31 | /// The location in source code where a primary expression was expected.
32 | at: Fragment<'src>,
33 | },
34 |
35 | /// An error in parsing an integer literal besides in-availability.
36 | OtherIntegerLiteralParsingError(IntegerLiteralParsingError<'src>),
37 |
38 | /// An error parsing an expression in parentheses besides lack of an opening parenthese.
39 | OtherParensExpressionParsingError(ParensParsingError<'src>),
40 | }
41 |
42 | #[rustfmt::skip] // Do not auto-reformat this block -- the match arms get too mangled.
43 | impl<'src> AstNode<'src> for PrimaryExpression<'src> {
44 | type Error = PrimaryExpressionParsingError<'src>;
45 |
46 | fn fragment(&self) -> Fragment<'src> {
47 | match self {
48 | PrimaryExpression::IntegerLiteral(integer_literal) => integer_literal.fragment(),
49 | PrimaryExpression::ParensExpression(parens_expr) => parens_expr.fragment(),
50 | }
51 | }
52 |
53 | fn try_parse(ctx: &mut AstGeneratorContext<'src>) -> Result
54 | where
55 | Self: Sized,
56 | {
57 | // If it's a successful parse, return Ok. `num` errors also fast-return. If it's an unavailability
58 | // error, keep trying other types of primary.
59 | match IntegerLiteral::try_parse(ctx) {
60 | Ok(int_lit) => return Ok(PrimaryExpression::IntegerLiteral(int_lit)),
61 |
62 | Err(num_err @ IntegerLiteralParsingError::NumParsingError { .. }) => {
63 | return Err(PrimaryExpressionParsingError::OtherIntegerLiteralParsingError(
64 | num_err,
65 | ));
66 | }
67 |
68 | Err(IntegerLiteralParsingError::ExpectedIntegerLiteral { .. }) => {}
69 | }
70 |
71 | // Do the same with a parens expression.
72 | match ParensExpression::try_parse(ctx) {
73 | Ok(parens_expr) => return Ok(PrimaryExpression::ParensExpression(parens_expr)),
74 |
75 | Err(err @ (
76 | | ParensParsingError::ClosingParenNotFound { .. }
77 | | ParensParsingError::ErrorInParentheses { .. }
78 | )) => return Err(PrimaryExpressionParsingError::OtherParensExpressionParsingError(err)),
79 |
80 | // Do nothing -- try parsing other primaries, or let this become an "expected primary expression error".
81 | Err(ParensParsingError::ExpectedParensExpression { .. }) => {}
82 | }
83 |
84 | // If we get to the end of the function, it's an error.
85 | Err(PrimaryExpressionParsingError::ExpectedPrimaryExpression {
86 | at: ctx.peek_fragment(),
87 | })
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/wright/src/ast/old/expression/primary/integer_literal.rs:
--------------------------------------------------------------------------------
1 | //! Parsing and AST node model for integer literals in wright source code.
2 |
3 | use crate::parser::ast::AstGeneratorContext;
4 | use crate::parser::lexer::token::TokenTy;
5 | use crate::parser::{ast::AstNode, fragment::Fragment};
6 | use num::bigint::ParseBigIntError;
7 | use num::{BigUint, Num};
8 |
9 | /// An integer literal in wright source code. Currently these are very simple.
10 | /// The format for integers is currently:
11 | ///
12 | /// `("0x" | "0X" | "0o" | "0b" | "0B")? (digit of given radix or underscore)+`
13 | ///
14 | /// See the [lexer module] for more details.
15 | ///
16 | /// [lexer module]: crate::parser::lexer::integer_literal
17 | #[derive(Debug)]
18 | pub struct IntegerLiteral<'src> {
19 | /// The associated [Fragment] of source code. This is generally pulled directly from the
20 | /// matched [TokenTy::IntegerLiteral] token.
21 | pub fragment: Fragment<'src>,
22 |
23 | /// The value that is represented by this integer literal -- this is represented using a [BigUint]
24 | /// so that the actual type of the literal may be assertained later on depending on its value. Wright may
25 | /// or may not support integer literals larger than [`u64`] eventually, so we do this to keep our options
26 | /// open/flexible.
27 | pub value: BigUint,
28 | }
29 |
30 | /// Errors tha can occur when parsing an integer literal.
31 | #[derive(Clone, Debug)]
32 | pub enum IntegerLiteralParsingError<'src> {
33 | /// Expected to find an [TokenTy::IntegerLiteral] [Token] and didn't.
34 | ExpectedIntegerLiteral {
35 | // The fragment we expected to see an integer literal at.
36 | at: Fragment<'src>,
37 | },
38 |
39 | /// Error after passing string to [`num`].
40 | NumParsingError {
41 | /// The error from [`num`].
42 | error: ParseBigIntError,
43 |
44 | /// The fragment we were trying to parse to an integer literal.
45 | at: Fragment<'src>,
46 | },
47 | }
48 |
49 | impl<'src> AstNode<'src> for IntegerLiteral<'src> {
50 | type Error = IntegerLiteralParsingError<'src>;
51 |
52 | fn fragment(&self) -> Fragment<'src> {
53 | self.fragment
54 | }
55 |
56 | fn try_parse(ctx: &mut AstGeneratorContext<'src>) -> Result
57 | where
58 | Self: Sized,
59 | {
60 | // Get the next token from the context if it is an integer literal.
61 | // Otherwise error.
62 | // We only care about the fragment from the token, so extract that.
63 | let fragment: Fragment = ctx
64 | .next_if_is(TokenTy::IntegerLiteral)
65 | .ok_or(IntegerLiteralParsingError::ExpectedIntegerLiteral {
66 | at: ctx.peek_fragment(),
67 | })?
68 | .fragment;
69 |
70 | // Get the fragment's internal string so we can slice it up and pass it to the num crate for
71 | // heavy lifting.
72 | let literal: &str = fragment.inner;
73 |
74 | // Make a list of prefixes with their radixes to try.
75 | let prefixes = [("0x", 16), ("0X", 16), ("0o", 8), ("0b", 2), ("0B", 2)];
76 |
77 | for (prefix, radix) in prefixes {
78 | if let Some(prefix_stripped) = literal.strip_prefix(prefix) {
79 | // Strip any leading undescores, since `num` eerors on those but we're less strict.
80 | let fully_stripped: &str = prefix_stripped.trim_start_matches('_');
81 | // Pass the rest of the parsing to `num`.
82 | // If this errors, pass it upwards -- it shouldn't because the lexer should radix check
83 | // for us and we just removed all leading undescores but on the off chance that it does, just
84 | // report it.
85 | let value: BigUint = BigUint::from_str_radix(fully_stripped, radix).map_err(
86 | |err: ParseBigIntError| IntegerLiteralParsingError::NumParsingError {
87 | error: err,
88 | at: fragment,
89 | },
90 | )?;
91 |
92 | return Ok(IntegerLiteral { fragment, value });
93 | }
94 | }
95 |
96 | // If no prefixes matched, it's a decimal number -- pass it right to `num`.
97 | // Deal with any errors the same way as above, but this time don't bother stripping undescores
98 | // since the lexer enforces starting with an ascii digit.
99 | let value: BigUint = BigUint::from_str_radix(literal, 10).map_err(|err| {
100 | IntegerLiteralParsingError::NumParsingError {
101 | error: err,
102 | at: fragment,
103 | }
104 | })?;
105 |
106 | Ok(IntegerLiteral { fragment, value })
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/wright/src/ast/old/expression/primary/parens.rs:
--------------------------------------------------------------------------------
1 | //! Expressions grouped in parentheses in Wright.
2 |
3 | use crate::parser::{
4 | ast::{
5 | expression::{Expression, ExpressionParsingError},
6 | AstGeneratorContext, AstNode,
7 | },
8 | fragment::Fragment,
9 | lexer::token::TokenTy,
10 | };
11 |
12 | /// An expression enclosed in parentheses in wright source code.
13 | #[derive(Debug)]
14 | pub struct ParensExpression<'src> {
15 | /// The matching fragment of source code.
16 | pub fragment: Fragment<'src>,
17 |
18 | /// The expression enclosed in parenthesese.
19 | pub expression: Box>,
20 | }
21 |
22 | /// Error parsing parentheses expression.
23 | #[derive(Debug, Clone)]
24 | pub enum ParensParsingError<'src> {
25 | /// A closing parenthese was not found.
26 | ClosingParenNotFound {
27 | /// The location a closing parenthese was expected.
28 | at: Fragment<'src>,
29 | },
30 |
31 | /// An error occurred while parsing withing the parenthesese.
32 | ErrorInParentheses(Box>),
33 |
34 | /// A parentheses expression was expected and was not found.
35 | ExpectedParensExpression {
36 | /// The location a parentheses expression was expected.
37 | at: Fragment<'src>,
38 | },
39 | }
40 |
41 | #[rustfmt::skip] // Do not auto-reformat this block -- parser code gets too mangled.
42 | impl<'src> AstNode<'src> for ParensExpression<'src> {
43 | type Error = ParensParsingError<'src>;
44 |
45 | fn fragment(&self) -> Fragment<'src> {
46 | self.fragment
47 | }
48 |
49 | fn try_parse(ctx: &mut AstGeneratorContext<'src>) -> Result
50 | where
51 | Self: Sized
52 | {
53 | // Fork the parser and attempt to parse on the fork.
54 | let mut fork: AstGeneratorContext = ctx.fork();
55 |
56 | // Parse through the left paren.
57 | fork
58 | .next_if_is(TokenTy::LeftParen)
59 | .ok_or_else( || ParensParsingError::ExpectedParensExpression { at: fork.peek_fragment() })?;
60 |
61 | // Parse the expression in the parentheseses. Error if there is not one.
62 | let expr: Expression = Expression::try_parse(&mut fork)
63 | // Box up the error and then wrap it in the correct variant.
64 | .map_err(Box::new)
65 | .map_err(ParensParsingError::ErrorInParentheses)?;
66 |
67 | // Parse the closing parentheses.
68 | fork
69 | .next_if_is(TokenTy::RightParen)
70 | .ok_or_else(|| ParensParsingError::ClosingParenNotFound { at: fork.peek_fragment() })?;
71 |
72 | // Update the parsing context. Use the trimmed fragment since the lexer may have consumed whitespace
73 | // before the first paren.
74 | let consumed_source: Fragment = ctx.update(&fork).trimmed();
75 | // Return the parens expression.
76 | Ok(Self { fragment: consumed_source, expression: Box::new(expr) })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/wright/src/ast/old/expression/unary.rs:
--------------------------------------------------------------------------------
1 | //! Unary expressions in Wright source code.
2 |
--------------------------------------------------------------------------------
/wright/src/ast/old/test_utils.rs:
--------------------------------------------------------------------------------
1 | //! Test utilities for checking AST generation code.
2 | //! The functions in this module are only available when running cargo test.
3 |
4 | use crate::filemap::{FileId, FileMap, FileName};
5 | use super::{AstGeneratorContext, AstNode};
6 |
7 | /// Run a parser against a given test string of source code.
8 | pub fn test_parser<'file_map, 'src: 'file_map, T: AstNode<'file_map, 'src>>(test_src: &'src str) -> Result
9 | where T: Sized + 'src
10 | {
11 | // Construct a new file map.
12 | let mut file_map: FileMap = FileMap::new();
13 |
14 | // Add the test string to create a file ID.
15 | let file_id: FileId = file_map.add_str_ref(FileName::None, test_src);
16 |
17 | // Create the ast generator context.
18 | let mut ctx: AstGeneratorContext = AstGeneratorContext::new(file_id, &file_map);
19 |
20 | T::try_parse(&mut ctx)
21 | }
22 |
--------------------------------------------------------------------------------
/wright/src/ast/old/ty.rs:
--------------------------------------------------------------------------------
1 | //! AST nodes and modeling for types in source code.
2 |
3 |
4 |
--------------------------------------------------------------------------------
/wright/src/ast/path.rs:
--------------------------------------------------------------------------------
1 | //! [Path]s are used in import statements, and can take the place of an [Identifier] in many people.
2 |
3 | use super::identifier::Identifier;
4 | use crate::source_tracking::fragment::Fragment;
5 |
6 | /// A double-colon separated path/reference to a module/function. This can be used in an `import` declaration and
7 | /// some other places. [Path]s with length of 1 are just [Identifier]s -- [Identifier]s can be considered paths in some
8 | /// instances.
9 | #[derive(Debug, Clone)]
10 | pub struct Path {
11 | /// The [Fragment] of source code containing the full source of this path (including the double-colon separators).
12 | pub full_path: Fragment,
13 |
14 | /// The first (left-most) identifier in this [Path]. This can also be considered the "root" of the path --
15 | /// the module that the following item/identifier can be found in.
16 | pub head: Identifier,
17 |
18 | /// The rest of the [Path], following the first separator.
19 | pub tail: Vec,
20 | }
21 |
--------------------------------------------------------------------------------
/wright/src/ast/ty.rs:
--------------------------------------------------------------------------------
1 | //! AST models for type signatures in wright source.
2 |
3 | use crate::source_tracking::fragment::Fragment;
4 |
5 | /// A type signature in source code.
6 | #[derive(Debug, Clone)]
7 | #[allow(missing_docs)]
8 | pub enum Type {
9 | Atomic(AtomicTy),
10 | Reference(ReferenceTy),
11 | }
12 |
13 | impl Type {
14 | /// Get the matching source for this type signature in source code.
15 | pub fn matching_source(&self) -> &Fragment {
16 | match self {
17 | Type::Atomic(atomic_ty) => &atomic_ty.matching_source,
18 | Type::Reference(reference_ty) => &reference_ty.matching_source,
19 | }
20 | }
21 |
22 | /// Attempt to "downcast" this to an atomic type signature if it is one.
23 | pub fn downcast_primitive(&self) -> Option<&AtomicTy> {
24 | match self {
25 | Type::Atomic(atomic) => Some(atomic),
26 | _ => None,
27 | }
28 | }
29 |
30 | /// Attempt to "downcast" this to a reference type signature if it is one.
31 | pub fn downcast_reference(&self) -> Option<&ReferenceTy> {
32 | match self {
33 | Type::Reference(reference) => Some(reference),
34 | _ => None,
35 | }
36 | }
37 | }
38 |
39 | /// The atomic types of wright -- primitive numeric types, boolean, char, etc.
40 | #[derive(Clone, Copy, Debug, PartialEq, Eq)]
41 | #[allow(missing_docs)]
42 | pub enum AtomicTyVariant {
43 | Bool,
44 | U8,
45 | I8,
46 | U16,
47 | I16,
48 | U32,
49 | I32,
50 | U64,
51 | I64,
52 | F32,
53 | F64,
54 | Char,
55 | }
56 |
57 | /// An atomic type signature in wright source code.
58 | #[derive(Clone, Debug)]
59 | #[allow(missing_docs)]
60 | pub struct AtomicTy {
61 | pub variant: AtomicTyVariant,
62 | pub matching_source: Fragment,
63 | }
64 |
65 | /// Source code for a reference type signature, such as `@u64`.
66 | #[derive(Debug, Clone)]
67 | pub struct ReferenceTy {
68 | /// The source code of the target type.
69 | pub target_ty: Box,
70 | /// The fragment of the whole reference.
71 | pub matching_source: Fragment,
72 | }
73 |
--------------------------------------------------------------------------------
/wright/src/bin/wright.rs:
--------------------------------------------------------------------------------
1 | //! Command line interface for wright.
2 |
3 | use clap::{Parser, Subcommand};
4 | use std::{io, path::PathBuf};
5 | use wright::{
6 | lexer::Lexer,
7 | source_tracking::{SourceMap, SourceRef, source::Source},
8 | };
9 |
10 | /// The wright cli.
11 | #[derive(Parser, Debug)]
12 | #[command(author, version, about, long_about = None, arg_required_else_help = true)]
13 | struct Cli {
14 | /// Whether the output should be only ASCII characters (default auto-detected, if `supports-unicode`
15 | /// crate is compiled in).
16 | ///
17 | /// This option does nothing if the `supports-unicode` crate was not enabled at compile time (in that case all
18 | /// output will be ASCII regardless).
19 | #[arg(short = 'A', long = "ascii")]
20 | force_ascii: bool,
21 | /// The subcommand passed to the wright cli.
22 | #[command(subcommand)]
23 | command: Command,
24 | }
25 |
26 | /// Different sub-commands that the wright cli supports.
27 | #[derive(Subcommand, Debug)]
28 | enum Command {
29 | /// Subcommand for debugging wright's source code and interpreter.
30 | Debug {
31 | #[command(subcommand)]
32 | command: DebugCommand,
33 | },
34 |
35 | /// Subcommand for showing information about this version of wright.
36 | Show {
37 | #[command(subcommand)]
38 | command: ShowCommand,
39 | },
40 | }
41 |
42 | /// Different sub-commands that the debug sub-command supports.
43 | #[derive(Subcommand, Debug)]
44 | enum DebugCommand {
45 | /// Debug the tokens/lexemes for a source file.
46 | Tokens {
47 | /// A file of wright source code.
48 | file: PathBuf,
49 | // /// Pretty print the source code with the tokens lined under them.
50 | // /// If not used, a list of tokens will be printed with their metadata.
51 | // #[arg(short, long)]
52 | // pretty: bool,
53 | },
54 | }
55 |
56 | /// Different subcommands that can be used to get info about a copy of the wright CLI/compiler/etc.
57 | #[derive(Subcommand, Debug)]
58 | enum ShowCommand {
59 | /// Get the version string of this copy of the wright compiler.
60 | Version,
61 |
62 | /// Get the full list of feature names/strings that were enabled when this copy of wright was compiled.
63 | Features,
64 | }
65 |
66 | fn main() -> io::Result<()> {
67 | // Parse the command line arguments.
68 | let cli: Cli = Cli::parse();
69 |
70 | #[cfg(feature = "supports-unicode")]
71 | {
72 | wright::util::supports_unicode::set_force_ascii(cli.force_ascii);
73 | }
74 |
75 | match cli.command {
76 | // Print all the tokens for a given file.
77 | Command::Debug {
78 | command: DebugCommand::Tokens { file },
79 | } => {
80 | let source_map: SourceMap = SourceMap::new();
81 | // Add the given file to the file map.
82 | let source_ref: SourceRef = source_map.add(Source::new_mapped_or_read(file)?);
83 | // Make a lexer over the entirety of the given file.
84 | let mut lexer: Lexer = Lexer::new(source_ref);
85 | // Get all the tokens from the lexer and print them each.
86 | while let Some(token) = lexer.next_token() {
87 | println!("{token}");
88 | }
89 | }
90 |
91 | Command::Show {
92 | command: ShowCommand::Version,
93 | } => {
94 | println!("wright {}", wright::build_info::PKG_VERSION);
95 | }
96 |
97 | Command::Show {
98 | command: ShowCommand::Features,
99 | } => {
100 | for feature in wright::build_info::FEATURES {
101 | println!("{feature}");
102 | }
103 | }
104 | }
105 |
106 | Ok(())
107 | }
108 |
--------------------------------------------------------------------------------
/wright/src/lexer/comments.rs:
--------------------------------------------------------------------------------
1 | //! Implementation of comment token lexing.
2 |
3 | use super::{Lexer, token::TokenTy};
4 |
5 | /// The pattern that begins any single line comments (including doc comments).
6 | pub const SINGLE_LINE_COMMENT_PREFIX: &str = "//";
7 |
8 | /// The pattern that starts any multi-line comments (including doc comments).
9 | pub const MULTI_LINE_COMMENT_START: &str = "/*";
10 |
11 | /// The pattern that ends any multi-line comments (including doc comments).
12 | pub const MULTI_LINE_COMMENT_END: &str = "*/";
13 |
14 | /// Attempt to match a sinlgle line comment from the start of the [Lexer::remaining] fragment.
15 | /// Return a [usize] and optionally a [TokenTy]. The [usize] indicates how many bytes were in the comment.
16 | /// The [TokenTy] (if it's not [None]) should be either [TokenTy::InnerDocComment] or [TokenTy::OuterDocComment].
17 | ///
18 | /// If the [TokenTy] is not [None], the lexer should consume the specified number of bytes (by the [usize]) and
19 | /// Produce a token with the [variant](super::token::Token::variant) from this function.
20 | ///
21 | /// Generally I'm trying to follow the [rust comment spec] here.
22 | ///
23 | /// [rust comment spec]: https://doc.rust-lang.org/reference/comments.html
24 | pub fn try_match_single_line_comment(lexer: &Lexer) -> (usize, Option) {
25 | // Fork the lexer so we can do all the parsing on the fork without worrying about modifying the original
26 | // unnecessarily.
27 | let mut fork: Lexer = lexer.fork();
28 |
29 | // Try to consume the single line comment prefix from the fork.
30 | if fork.consume(SINGLE_LINE_COMMENT_PREFIX) {
31 | // We consumed it successfully, read through a newline or the end of the forked lexer if we get there.
32 |
33 | // First determine if this is a doc comment of some kind.
34 | let is_inner_doc: bool = fork.matches("/") && !fork.matches("//");
35 | let is_outer_doc: bool = fork.matches("!");
36 |
37 | // The consume until a newline, carraige return, or the end of the source fragment.
38 | while !fork.remaining.is_empty() && !fork.matches("\r") && !fork.matches("\n") {
39 | fork.consume_any();
40 | }
41 |
42 | // Determine the kind of token to produce (if any).
43 | let variant: Option = match (is_inner_doc, is_outer_doc) {
44 | (true, false) => Some(TokenTy::InnerDocComment),
45 | (false, true) => Some(TokenTy::OuterDocComment),
46 | (false, false) => None,
47 | (true, true) => unreachable!(
48 | "It is impossible for the `remaining` fragment to start with an `!` and a `/` simultaneously."
49 | ),
50 | };
51 |
52 | // Return the number of bytes consumed and the type of token to
53 | // produce if any.
54 | return (fork.offset_from(lexer), variant);
55 | }
56 |
57 | // If the single line comment prefix was not immediately available, there is no comment.
58 | (0, None)
59 | }
60 |
61 | /// Attempt to match a block comment from the start of the [Lexer::remaining] fragment.
62 | /// Return a [usize] and optionally a [TokenTy]. The [usize] indicates how many bytes were in the comment.
63 | /// The [TokenTy] (if it's not [None]) should be [TokenTy::InnerBlockDocComment], [TokenTy::OuterBlockDocComment], or
64 | /// [TokenTy::UnterminatedBlockComment].
65 | ///
66 | /// If the [TokenTy] is not [None], the lexer should consume the specified number of bytes (by the [usize]) and
67 | /// Produce a token with the [variant](super::token::Token::variant) from this function.
68 | pub fn try_match_block_comment(lexer: &Lexer) -> (usize, Option) {
69 | // Handle corner cases here so we don't have to below.
70 | // These are both considered empty non-documenting comments.
71 | if lexer.matches("/***/") {
72 | return (5, None);
73 | }
74 |
75 | if lexer.matches("/**/") {
76 | return (4, None);
77 | }
78 |
79 | // Make a fork of the lexer to avoid modifying this lexer if we fail to parse.
80 | let mut fork: Lexer = lexer.fork();
81 |
82 | // Try to parse the start of a multi-line comment.
83 | if fork.consume(MULTI_LINE_COMMENT_START) {
84 | // Check if this is a doc comment.
85 | let is_outer_doc: bool = fork.matches("!");
86 | // Use this to indicate that more than one following asterix is not a doc comment.
87 | let is_inner_doc: bool = fork.matches("*") && !fork.matches("**");
88 |
89 | // Consume until we see the end of the doc comment. If we run out of characters, consider the
90 | // comment unterminated.
91 | while !fork.matches(MULTI_LINE_COMMENT_END) {
92 | // Handle nested comments here:
93 | if fork.matches(MULTI_LINE_COMMENT_START) {
94 | // Discard the output -- don't care about doc comments in other comments.
95 | let (nested_comment_bytes, _) = try_match_block_comment(&fork);
96 |
97 | // SAFETY: the return from this function should never be on a char boundary or out of bounds.
98 | // This is because the return value is always either 0 or calculated using `offset_from`.
99 | unsafe { fork.advance_unchecked(nested_comment_bytes) };
100 |
101 | // Restart the loop to keep consuming this comment.
102 | continue;
103 | }
104 |
105 | // Handle unterminated comments here.
106 | if fork.remaining.is_empty() {
107 | // If we have not hit a "*/" before the end of the input, return an unterminated block comment.
108 | let bytes_consumed: usize = fork.offset_from(lexer);
109 | return (bytes_consumed, Some(TokenTy::UnterminatedBlockComment));
110 | }
111 |
112 | // If there's still input, and not a nested comment, consume it.
113 | fork.consume_any();
114 | }
115 |
116 | // If we get here, the comment was terminated. Consume the terminating characters, and return.
117 | // Use debug assert here to make sure that the comment is actually terminated.
118 | let consumed_comment_terminator: bool = fork.consume(MULTI_LINE_COMMENT_END);
119 | debug_assert!(consumed_comment_terminator, "comment is actually terminated");
120 |
121 | // Determine the kind of token to produce (if any).
122 | let variant: Option = match (is_inner_doc, is_outer_doc) {
123 | (true, false) => Some(TokenTy::InnerBlockDocComment),
124 | (false, true) => Some(TokenTy::OuterBlockDocComment),
125 | (false, false) => None,
126 | (true, true) => {
127 | unreachable!("Lexer should not match multiple comment types at once.")
128 | }
129 | };
130 |
131 | return (fork.offset_from(lexer), variant);
132 | }
133 |
134 | (0, None)
135 | }
136 |
137 | #[cfg(test)]
138 | mod tests {
139 | use super::Lexer;
140 |
141 | #[test]
142 | fn ignored_single_line_comment() {
143 | let mut lexer = Lexer::new_test("// test comment ");
144 | assert!(lexer.next_token().is_none());
145 | assert_eq!(lexer.remaining.len(), 0);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/wright/src/lexer/identifier.rs:
--------------------------------------------------------------------------------
1 | //! Implementation related to parsing keywords and identifiers.
2 |
3 | use super::{Lexer, token::Token, token::TokenTy};
4 | use crate::source_tracking::fragment::Fragment;
5 | use std::str::Chars;
6 | use unicode_ident::{is_xid_continue, is_xid_start};
7 |
8 | /// Try to match a fragment recognized to be an identifier or keyword to
9 | /// a keyword or return [TokenTy::Identifier].
10 | fn identifier_or_keyword(fragment: Fragment) -> TokenTy {
11 | use TokenTy::*;
12 |
13 | match fragment.as_str() {
14 | "record" => KwRecord,
15 | "type" => KwType,
16 | "enum" => KwEnum,
17 | "union" => KwUnion,
18 | "func" => KwFunc,
19 | "pure" => KwPure,
20 | "repr" => KwRepr,
21 | "impl" => KwImpl,
22 | "constraint" => KwConstraint,
23 | "references" => KwReferences,
24 | "trait" => KwTrait,
25 | "const" => KwConst,
26 | "where" => KwWhere,
27 |
28 | "use" => KwUse,
29 | "as" => KwAs,
30 | "mod" => KwMod,
31 | "pub" => KwPub,
32 |
33 | "if" => KwIf,
34 | "else" => KwElse,
35 | "match" => KwMatch,
36 |
37 | "for" => KwFor,
38 | "in" => KwIn,
39 | "while" => KwWhile,
40 | "loop" => KwLoop,
41 |
42 | "let" => KwLet,
43 | "var" => KwVar,
44 |
45 | "true" => KwTrue,
46 | "false" => KwFalse,
47 |
48 | "bool" => KwBool,
49 | "u8" => KwU8,
50 | "i8" => KwI8,
51 | "u16" => KwU16,
52 | "i16" => KwI16,
53 | "u32" => KwU32,
54 | "i32" => KwI32,
55 | "f32" => KwF32,
56 | "u64" => KwU64,
57 | "i64" => KwI64,
58 | "f64" => KwF64,
59 | "char" => KwChar,
60 |
61 | "_" => Underscore,
62 |
63 | _ => Identifier,
64 | }
65 | }
66 |
67 | /// Attempt to consume a keyword/[identifier](TokenTy::Identifier)/[underscore](TokenTy::Underscore) from the lexer.
68 | pub fn try_consume_keyword_or_identifier(lexer: &mut Lexer) -> Option {
69 | // Get a character iterator that we can pull from.
70 | let mut chars: Chars = lexer.remaining.chars();
71 | // Get the next character from the iterator, consider it the first char of any potential match.
72 | // Make sure it's a valid identifier start (includes start to all keywords) or is an underscore.
73 | // If it does not exist or match predicates, return None.
74 | let next: char = chars.next().filter(|c| is_xid_start(*c) || *c == '_')?;
75 | // Store/track the number of bytes consumed so far.
76 | let mut bytes_consumed: usize = next.len_utf8();
77 |
78 | // Take remaining chars and add to sum.
79 | bytes_consumed += chars
80 | .take_while(|c| is_xid_continue(*c))
81 | .map(char::len_utf8)
82 | .sum::();
83 |
84 | // Split the token and the new remaining fragment.
85 | // VALIDITY: The character iterator should guarantee that we land on a valid character boundary within the bounds
86 | // of the fragment.
87 | let (token_fragment, new_remaining): (Fragment, Fragment) =
88 | lexer.remaining.split_at_unchecked(bytes_consumed);
89 |
90 | // Get the variant of token to produce.
91 | let variant: TokenTy = identifier_or_keyword(token_fragment.clone());
92 |
93 | // Update the lexer's remaining fragment.
94 | lexer.remaining = new_remaining;
95 |
96 | // Return the token.
97 | Some(Token {
98 | variant,
99 | fragment: token_fragment,
100 | })
101 | }
102 |
103 | #[cfg(test)]
104 | mod tests {
105 | use super::{Lexer, TokenTy};
106 |
107 | #[test]
108 | fn identifiers_and_keywords() {
109 | let mut lexer = Lexer::new_test("const TEST");
110 |
111 | assert_eq!(lexer.next_token().unwrap().variant, TokenTy::KwConst);
112 | assert_eq!(lexer.next_token().unwrap().variant, TokenTy::Whitespace);
113 | assert_eq!(lexer.next_token().unwrap().variant, TokenTy::Identifier);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/wright/src/lexer/integer_literal.rs:
--------------------------------------------------------------------------------
1 | //! Implementation for lexing integer literals.
2 |
3 | use super::{
4 | Lexer,
5 | token::{Token, TokenTy},
6 | };
7 | use std::{iter::Peekable, str::Chars};
8 |
9 | /// Attempt to lex and consume an [TokenTy::IntegerLiteral] from the lexer.
10 | pub fn try_consume_integer_literal(lexer: &mut Lexer) -> Option {
11 | // Make a peekable character iterator.
12 | let mut chars: Peekable = lexer.remaining.chars().peekable();
13 | // Get the first character from the iterator. We can only continue lexing if one exists and is an ascii
14 | // decimal digit.
15 | let next: char = chars.next().filter(char::is_ascii_digit)?;
16 | // Track the number of bytes consumed. We use the length of the parsed first char here but we could probably
17 | // assume it to be 1.
18 | let mut bytes_consumed: usize = next.len_utf8();
19 | // Track the radix
20 | let mut radix: u32 = 10;
21 |
22 | // Change the radix if necessary
23 | if next == '0' {
24 | if let Some(prefix) = chars.next_if(|x| ['x', 'o', 'b', 'X', 'B'].contains(x)) {
25 | // All the possible prefix chars are 1 byte ascii characters.
26 | bytes_consumed += 1;
27 |
28 | radix = match prefix {
29 | 'x' | 'X' => 16,
30 | 'b' | 'B' => 2,
31 | 'o' => 8,
32 | _ => unreachable!("the prefix byte is checked above"),
33 | };
34 | }
35 | }
36 |
37 | // The first character after the optional prefix is required to be a digit, not underscore.
38 | bytes_consumed += chars.next_if(|c| c.is_digit(radix))?.len_utf8();
39 |
40 | // Add the rest of the integer literal.
41 | bytes_consumed += chars
42 | .take_while(|c| c.is_digit(radix) || *c == '_')
43 | .map(char::len_utf8)
44 | .sum::();
45 |
46 | Some(lexer.split_token(bytes_consumed, TokenTy::IntegerLiteral))
47 | }
48 |
49 | #[cfg(test)]
50 | mod tests {
51 | use crate::lexer::integer_literal::try_consume_integer_literal;
52 |
53 | use super::{Lexer, TokenTy};
54 |
55 | #[test]
56 | fn integer_literal() {
57 | let mut lexer = Lexer::new_test("123_456_789.");
58 |
59 | let token = lexer.next_token().unwrap();
60 |
61 | assert_eq!(token.fragment.as_str(), "123_456_789");
62 | assert_eq!(token.variant, TokenTy::IntegerLiteral);
63 | assert_eq!(lexer.remaining.as_str(), ".");
64 | }
65 |
66 | #[test]
67 | fn cant_start_with_underscore() {
68 | let mut lexer = Lexer::new_test("0x__10");
69 |
70 | assert!(try_consume_integer_literal(&mut lexer).is_none());
71 |
72 | assert_eq!(lexer.remaining.as_str(), "0x__10");
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/wright/src/lexer/quoted.rs:
--------------------------------------------------------------------------------
1 | //! Lexing implementation for quoted literals.
2 |
3 | use super::{Lexer, token::Token, token::TokenTy};
4 | use std::str::Chars;
5 |
6 | /// Attempt to parse a quoted literal. This includes [TokenTy::StringLiteral], [TokenTy::CharLiteral], and
7 | /// [TokenTy::FormatStringLiteral].
8 | pub fn try_consume_quoted_literal(lexer: &mut Lexer) -> Option {
9 | // Make a chars iterator to lex from.
10 | let mut chars: Chars = lexer.remaining.chars();
11 | // Get the first char from the character iterator.
12 | // Return none if the first character doesn't exist or is not one of the quote terminating characters.
13 | let first: char = chars.next().filter(|c| ['\'', '"', '`'].contains(c))?;
14 | // Track number of bytes consumed.
15 | let mut bytes_consumed: usize = first.len_utf8();
16 | // Track whether the quoted literal is terminated.
17 | let mut is_terminated: bool = false;
18 |
19 | // Consume from the iterator while possible.
20 | while let Some(consumed) = chars.next() {
21 | // Update the number of bytes consumed.
22 | bytes_consumed += consumed.len_utf8();
23 |
24 | // Check if the character matches the starting char.
25 | // If so, record the literal as terminated and break this loop.
26 | if consumed == first {
27 | is_terminated = true;
28 | break;
29 | }
30 |
31 | // If the character we just consumed is a backslash.
32 | // We only handle escaped terminators here, rather than parsing actual meaning.
33 | // Consume the next character if there is one, regardless of what it is.
34 | // This prevents an escaped terminator from ending the literal.
35 | if consumed == '\\' {
36 | // If there is no next char, do not add anything to the number of bytes consumed.
37 | bytes_consumed += chars.next().map(char::len_utf8).unwrap_or(0);
38 | }
39 | }
40 |
41 | // Return when we have either reached a terminator or run out of characters.
42 | // First determine the variant to return.
43 | let variant: TokenTy = match first {
44 | '\'' => TokenTy::CharLiteral {
45 | terminated: is_terminated,
46 | },
47 |
48 | '\"' => TokenTy::StringLiteral {
49 | terminated: is_terminated,
50 | },
51 |
52 | '`' => TokenTy::FormatStringLiteral {
53 | terminated: is_terminated,
54 | },
55 |
56 | _ => unreachable!("There are no other quoted literals"),
57 | };
58 |
59 | // Summing char lengths from the iterator should never give us an invalid or out of bounds index.
60 | Some(lexer.split_token_unchecked(bytes_consumed, variant))
61 | }
62 |
63 | #[cfg(test)]
64 | mod tests {
65 | use super::super::{Lexer, token::TokenTy};
66 |
67 | #[test]
68 | fn string_literal() {
69 | let mut lexer = Lexer::new_test(r#""Test string literal""#);
70 | let token = lexer.next_token().unwrap();
71 | assert_eq!(token.variant, TokenTy::StringLiteral { terminated: true });
72 | assert_eq!(token.fragment.as_str(), "\"Test string literal\"");
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/wright/src/lexer/token.rs:
--------------------------------------------------------------------------------
1 | //! Token models.
2 |
3 | use crate::source_tracking::fragment::Fragment;
4 | use std::fmt::{self, Display};
5 |
6 | /// A token in wright source code.
7 | #[derive(Debug)]
8 | pub struct Token {
9 | /// What type of token this is.
10 | pub variant: TokenTy,
11 | /// The matching fragment of source code -- this contains the location and length data for the token.
12 | pub fragment: Fragment,
13 | }
14 |
15 | /// The different types of tokens in wright source.
16 | #[rustfmt::skip] // Turn off auto reformat.
17 | #[derive(Clone, Copy, Debug, PartialEq, Eq)]
18 | // Allow missing docs (most of these should be self-evident).
19 | #[allow(missing_docs)]
20 | pub enum TokenTy {
21 | LeftCurly, RightCurly,
22 | LeftBracket, RightBracket,
23 | LeftParen, RightParen,
24 |
25 | Plus, PlusEq,
26 | Star, StarEq,
27 | Div, DivEq,
28 | Xor, XorEq,
29 | Mod, ModEq,
30 | Bang, BangEq,
31 |
32 | Minus, MinusEq, SingleArrow,
33 | Eq, EqEq, DoubleArrow,
34 |
35 | Lt, LtEq, LtLt,
36 | Gt, GtEq, GtGt,
37 | And, AndEq, AndAnd,
38 | Or, OrEq, OrOr,
39 | Colon, ColonEq, ColonColon,
40 |
41 | At,
42 | Tilde,
43 | Semi,
44 | Dot,
45 | Comma,
46 | Hash,
47 | Question,
48 | Dollar,
49 |
50 | // Not in the same group as the other ones there since it can be used at the start of identifiers.
51 | Underscore,
52 |
53 | Identifier,
54 |
55 | OuterDocComment, OuterBlockDocComment,
56 | InnerDocComment, InnerBlockDocComment,
57 |
58 | /// Indicates a block style comment without termination.
59 | /// Separate from [TokenTy::InnerDocComment] and [TokenTy::OuterDocComment] to indicate that
60 | /// unterminated comments will be handled differently (produce errors eventually).
61 | UnterminatedBlockComment,
62 |
63 | KwRecord,
64 | KwType,
65 | KwEnum,
66 | KwUnion,
67 | KwFunc,
68 | KwPure,
69 | KwRepr,
70 | KwImpl,
71 | KwConstraint,
72 | KwReferences,
73 | KwTrait,
74 | KwUse,
75 | KwAs,
76 | KwConst,
77 | KwMod,
78 | KwIf,
79 | KwElse,
80 | KwMatch,
81 | KwFor,
82 | KwIn,
83 | KwWhile,
84 | KwTrue,
85 | KwFalse,
86 | KwLoop,
87 | KwWhere,
88 | KwPub,
89 |
90 | KwLet,
91 | KwVar,
92 |
93 | // Keyword primitive types.
94 | KwBool,
95 | KwU8,
96 | KwI8,
97 | KwU16,
98 | KwI16,
99 | KwU32,
100 | KwI32,
101 | KwF32,
102 | KwU64,
103 | KwI64,
104 | KwF64,
105 | KwChar,
106 |
107 | IntegerLiteral,
108 | StringLiteral { terminated: bool },
109 | FormatStringLiteral { terminated: bool },
110 | CharLiteral { terminated: bool },
111 |
112 | /// Whitespace counts as a token.
113 | Whitespace,
114 |
115 | /// Unknown character in lexer fragment.
116 | Unknown
117 | }
118 |
119 | impl Display for Token {
120 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 | // If the host terminal supports unicode, replace the newline & carriage return characters with pictures,
122 | // otherwise use ascii.
123 | let replacements = match crate::util::supports_unicode::supports_unicode() {
124 | true => &[("\n", "\u{240A}"), ("\r", "\u{240D}")],
125 | false => &[("\n", "[nl]"), ("\r", "[cr]")],
126 | };
127 |
128 | let mut with_replacements = self.fragment.as_str().to_owned();
129 |
130 | for (replace, replace_with) in replacements {
131 | with_replacements = with_replacements.replace(replace, replace_with);
132 | }
133 |
134 | write!(f, "\"{with_replacements}\" ({:?})", self.variant)
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/wright/src/lexer/trivial.rs:
--------------------------------------------------------------------------------
1 | //! Trivial tokens and their implementation.
2 |
3 | use super::{
4 | Lexer,
5 | token::{Token, TokenTy},
6 | };
7 |
8 | /// Trivial tokens that are two ASCII characters and can be matched directly
9 | /// against the input source code.
10 | pub const TWO_ASCII_TRIVIAL_TOKENS: &[(&[u8; 2], TokenTy)] = &[
11 | (b"->", TokenTy::SingleArrow),
12 | (b"-=", TokenTy::MinusEq),
13 | (b"=>", TokenTy::DoubleArrow),
14 | (b"==", TokenTy::EqEq),
15 | (b"&&", TokenTy::AndAnd),
16 | (b"||", TokenTy::OrOr),
17 | (b"<<", TokenTy::LtLt),
18 | (b">>", TokenTy::GtGt),
19 | (b"::", TokenTy::ColonColon),
20 | (b"|=", TokenTy::OrEq),
21 | (b"&=", TokenTy::AndEq),
22 | (b":=", TokenTy::ColonEq),
23 | (b">=", TokenTy::GtEq),
24 | (b"<=", TokenTy::LtEq),
25 | (b"!=", TokenTy::BangEq),
26 | (b"%=", TokenTy::ModEq),
27 | (b"^=", TokenTy::XorEq),
28 | (b"*=", TokenTy::StarEq),
29 | (b"+=", TokenTy::PlusEq),
30 | (b"/=", TokenTy::DivEq),
31 | ];
32 |
33 | /// Single ASCII character trivial tokens that can be matched directly against
34 | /// the source code.
35 | pub const SINGLE_ASCII_CHAR_TRIVIAL_TOKENS: &[(u8, TokenTy)] = &[
36 | (b'(', TokenTy::LeftParen),
37 | (b')', TokenTy::RightParen),
38 | (b'[', TokenTy::LeftBracket),
39 | (b']', TokenTy::RightBracket),
40 | (b'{', TokenTy::LeftCurly),
41 | (b'}', TokenTy::RightCurly),
42 | (b'@', TokenTy::At),
43 | (b';', TokenTy::Semi),
44 | (b'?', TokenTy::Question),
45 | (b',', TokenTy::Comma),
46 | (b'#', TokenTy::Hash),
47 | (b'$', TokenTy::Dollar),
48 | (b'>', TokenTy::Gt),
49 | (b'<', TokenTy::Lt),
50 | (b'-', TokenTy::Minus),
51 | (b':', TokenTy::Colon),
52 | (b'!', TokenTy::Bang),
53 | (b'=', TokenTy::Eq),
54 | (b'&', TokenTy::And),
55 | (b'|', TokenTy::Or),
56 | (b'/', TokenTy::Div),
57 | (b'+', TokenTy::Plus),
58 | (b'^', TokenTy::Xor),
59 | (b'*', TokenTy::Star),
60 | (b'%', TokenTy::Mod),
61 | ];
62 |
63 | /// Attempt to consume a "trivial" token from the start of the [Lexer]'s [Lexer::remaining] fragment.
64 | ///
65 | /// Leave the lexer unmodified if one is not available.
66 | pub fn try_consume_trivial_token(lexer: &mut Lexer) -> Option {
67 | // Get the number of bytes remaining, since we need at least 1 to parse anything.
68 | let bytes_remaining: usize = lexer.bytes_remaining();
69 |
70 | // No token if there are no bytes of source left.
71 | if bytes_remaining == 0 {
72 | return None;
73 | }
74 |
75 | // Attempt to match any two-byte ASCII trivial tokens.
76 | // This must be done before single-ascii byte tokens since matching is greedy.
77 | if bytes_remaining >= 2 {
78 | // Get the first two bytes of the remaining fragment.
79 | // SAFETY: We just checked length.
80 | let bytes: &[u8] = unsafe { lexer.remaining.as_str().as_bytes().get_unchecked(0..2) };
81 |
82 | // Match against each possible token pattern.
83 | for (pattern, kind) in TWO_ASCII_TRIVIAL_TOKENS {
84 | if bytes == *pattern {
85 | // We have already done bounds checking, and this cannot be a character
86 | // boundary since we just matched against ASCII characters.
87 | return Some(lexer.split_token_unchecked(2, *kind));
88 | }
89 | }
90 | }
91 |
92 | // Do the same for single byte patterns.
93 | // SAFETY: We checked that the number of bytes remaining is not 0 above.
94 | let byte: &u8 = unsafe { lexer.remaining.as_str().as_bytes().get_unchecked(0) };
95 |
96 | for (pattern, kind) in SINGLE_ASCII_CHAR_TRIVIAL_TOKENS {
97 | if byte == pattern {
98 | // If we matched, then the first byte is ASCII, and therefore we don't have to worry
99 | // about bounds or unicode boundaries.
100 | return Some(lexer.split_token_unchecked(1, *kind));
101 | }
102 | }
103 |
104 | // If nothing else has matched, there is no trivial token available.
105 | None
106 | }
107 |
108 | #[cfg(test)]
109 | mod tests {
110 | use super::{Lexer, TokenTy};
111 |
112 | #[test]
113 | fn plus_and_plus_eq_tokens() {
114 | let mut plus = Lexer::new_test("+");
115 | let mut plus_eq = Lexer::new_test("+=");
116 |
117 | let plus_token = plus.next_token().unwrap();
118 | let plus_eq_token = plus_eq.next_token().unwrap();
119 |
120 | assert_eq!(plus.bytes_remaining(), 0);
121 | assert_eq!(plus_eq.bytes_remaining(), 0);
122 | assert_eq!(plus_token.variant, TokenTy::Plus);
123 | assert_eq!(plus_eq_token.variant, TokenTy::PlusEq);
124 | }
125 |
126 | #[test]
127 | fn plus_one_token() {
128 | let mut plus_one = Lexer::new_test("+1");
129 | let plus_token = plus_one.next_token().unwrap();
130 | assert_eq!(plus_one.bytes_remaining(), 1);
131 | assert_eq!(plus_token.variant, TokenTy::Plus);
132 | assert_eq!(plus_token.fragment.len(), 1);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/wright/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! The wright programming language crate. This is being re-written from the ground up as of September 2022.
2 |
3 | // Compile without the standard library if the user chooses to do so.
4 | #![cfg_attr(not(any(feature = "std", test)), no_std)]
5 | // We want to enforce good stuff by default.
6 | #![deny(missing_copy_implementations, missing_debug_implementations)]
7 | #![deny(rustdoc::broken_intra_doc_links)]
8 | #![warn(missing_docs)]
9 | // Compiler directive to get docs.rs (which uses the nightly version of the rust compiler) to show
10 | // info about feature required for various modules and functionality.
11 | //
12 | // See: https://stackoverflow.com/a/70914430.
13 | #![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
14 |
15 | // We cannot use memory mapped files on architectures that do not support memmap2.
16 | #[cfg(all(
17 | feature = "file_memmap",
18 | any(target_arch = "wasm32", target_arch = "wasm64")
19 | ))]
20 | compile_error!("Memory mapped files not available on WASM targets");
21 |
22 | // If the "none" feature is enabled, make sure the user has no other features enabled.
23 | //
24 | // Currently all of the features besides "none" depend on "std" so if both "none" and "std"
25 | // are present, raise an error at compile time.
26 | //
27 | // Make sure to keep this updated as more features are added.
28 | #[cfg(all(feature = "none", feature = "std"))]
29 | compile_error!(
30 | "feature \"none\" is enabled, which restricts the usage of any other features including \"std\"."
31 | );
32 |
33 | /// Build information about this copy of wright, provided using .
34 | pub mod build_info {
35 | include!(concat!(env!("OUT_DIR"), "/built.rs"));
36 | }
37 |
38 | #[cfg(feature = "source-tracking")]
39 | pub mod source_tracking;
40 |
41 | #[cfg(feature = "reporting")]
42 | pub mod reporting;
43 |
44 | #[cfg(feature = "lexer")]
45 | pub mod lexer;
46 |
47 | #[cfg(feature = "ast-models")]
48 | pub mod ast;
49 |
50 | #[cfg(feature = "parser")]
51 | pub mod parser;
52 |
53 | pub mod util;
54 |
55 | // pub mod repl;
56 |
--------------------------------------------------------------------------------
/wright/src/parser.rs:
--------------------------------------------------------------------------------
1 | //! This parser module is responsible for turning the stream of [Token]s from the [Lexer] into a tree of [AST] nodes.
2 | //!
3 | //! [AST]: crate::ast
4 | //! [Token]: crate::lexer::token::Token
5 |
6 | use error::{ParserError, ParserErrorKind};
7 |
8 | use super::lexer::Lexer;
9 | use crate::{
10 | lexer::token::{Token, TokenTy},
11 | source_tracking::fragment::Fragment,
12 | };
13 | use std::collections::VecDeque;
14 |
15 | mod decl;
16 | pub mod error;
17 | mod identifier;
18 | mod literal;
19 | mod path;
20 | mod ty;
21 | pub mod whitespace;
22 |
23 | /// The [Parser] struct wraps a [Lexer] and adds lookahead and functions that are useful for parsing.
24 | #[derive(Debug)]
25 | pub struct Parser {
26 | lexer: Lexer,
27 | lookahead: VecDeque,
28 | }
29 |
30 | impl Parser {
31 | /// Construct a new parser around a given [Lexer].
32 | pub fn new(lexer: Lexer) -> Self {
33 | Parser {
34 | lexer,
35 | lookahead: VecDeque::new(),
36 | }
37 | }
38 |
39 | /// Get the number of remaining bytes on this parser. This is potentially useful for checking
40 | /// if a parser has advanced between two calls (or checking if a parser has reached end of input).
41 | pub fn bytes_remaining(&self) -> usize {
42 | let bytes_remaining_in_lookahead_buffer = self
43 | .lookahead
44 | .iter()
45 | .map(|t| t.fragment.len())
46 | .sum::();
47 |
48 | let bytes_remaining_in_lexer = self.lexer.bytes_remaining();
49 |
50 | bytes_remaining_in_lexer + bytes_remaining_in_lookahead_buffer
51 | }
52 |
53 | /// Get the next [Token] from this [Parser]. This may be a token that's already been peeked.
54 | ///
55 | /// Skips any non-document comments encountered via the lexer implementation.
56 | ///
57 | /// Return an error if a [Token] with [TokenTy::Unknown] is encountered.
58 | pub fn next_token(&mut self) -> Result