├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ ├── missing_or_incorrect_documentation.md │ └── question.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README_JA.md ├── VERSION ├── assets ├── LICENSE └── brack-header.png ├── brack.ast.ebnf ├── brack.cst.ebnf ├── build.rs ├── crates ├── brack-codegen │ ├── Cargo.toml │ └── src │ │ ├── curly.rs │ │ ├── expr.rs │ │ ├── generate.rs │ │ ├── lib.rs │ │ ├── square.rs │ │ ├── stmt.rs │ │ └── text.rs ├── brack-common │ ├── Cargo.toml │ └── src │ │ ├── ast.rs │ │ ├── cst.rs │ │ ├── lib.rs │ │ ├── location.rs │ │ ├── logger.rs │ │ ├── project_errors.rs │ │ ├── tokens.rs │ │ └── transformer_errors.rs ├── brack-expander │ ├── Cargo.toml │ └── src │ │ ├── expand.rs │ │ └── lib.rs ├── brack-language-server │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── logger.rs │ │ ├── notification.rs │ │ ├── notification │ │ ├── did_change.rs │ │ ├── did_open.rs │ │ └── did_save.rs │ │ ├── request.rs │ │ ├── request │ │ ├── completion.rs │ │ └── semantic_tokens.rs │ │ ├── response.rs │ │ ├── result.rs │ │ ├── server.rs │ │ └── utils.rs ├── brack-parser │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── angle.rs │ │ ├── bracket.rs │ │ ├── bracket_close.rs │ │ ├── comma.rs │ │ ├── curly.rs │ │ ├── dot.rs │ │ ├── eof.rs │ │ ├── escaped.rs │ │ ├── expr.rs │ │ ├── expr_or_close.rs │ │ ├── ident.rs │ │ ├── lib.rs │ │ ├── modules.rs │ │ ├── newline.rs │ │ ├── parse.rs │ │ ├── parser.rs │ │ ├── square.rs │ │ ├── stmt.rs │ │ ├── text.rs │ │ └── whitespace.rs ├── brack-plugin │ ├── Cargo.toml │ ├── src │ │ ├── feature_flag.rs │ │ ├── lib.rs │ │ ├── metadata.rs │ │ ├── plugin.rs │ │ ├── plugins.rs │ │ ├── types.rs │ │ └── value.rs │ └── test.html.wasm ├── brack-project │ ├── Cargo.toml │ └── src │ │ ├── debug │ │ ├── debug_build.rs │ │ └── debug_download_plugin.rs │ │ ├── lib.rs │ │ ├── manifest.rs │ │ └── project.rs ├── brack-release │ ├── Cargo.toml │ └── src │ │ ├── git.rs │ │ ├── main.rs │ │ └── semver.rs ├── brack-tokenizer │ ├── Cargo.toml │ ├── src │ │ ├── angle_bracket_close.rs │ │ ├── angle_bracket_open.rs │ │ ├── backslash.rs │ │ ├── comma.rs │ │ ├── curly_bracket_close.rs │ │ ├── curly_bracket_open.rs │ │ ├── dispatch.rs │ │ ├── dot.rs │ │ ├── identifier.rs │ │ ├── lib.rs │ │ ├── module.rs │ │ ├── newline.rs │ │ ├── square_bracket_close.rs │ │ ├── square_bracket_open.rs │ │ ├── text.rs │ │ ├── tokenize.rs │ │ ├── tokenizer.rs │ │ ├── utils.rs │ │ └── whitespace.rs │ └── test │ │ ├── angle_bracket.[] │ │ ├── left_angle_bracket.[] │ │ ├── multiple_errors.[] │ │ ├── right_angle_bracket.[] │ │ ├── split_commands_with_an_argument_includes_angle_brackets.[] │ │ ├── split_commands_with_an_argument_includes_curly_brackets.[] │ │ ├── split_commands_with_an_argument_includes_square_brackets.[] │ │ ├── split_commands_with_two_arguments_includes_square_brackets.[] │ │ ├── split_japanese_and_emoji.[] │ │ ├── split_nesting_commands.[] │ │ ├── split_newlines.[] │ │ └── split_no_commands.[] └── brack-transformer │ ├── Cargo.toml │ └── src │ ├── angle.rs │ ├── backslash.rs │ ├── curly.rs │ ├── debug │ ├── debug_compile.rs │ └── debug_parse.rs │ ├── document.rs │ ├── expr.rs │ ├── lib.rs │ ├── simplify.rs │ ├── square.rs │ ├── stmt.rs │ ├── transform.rs │ └── utils.rs ├── flake.lock ├── flake.nix ├── nix ├── actionlint-check.nix ├── build.nix ├── cargo-fmt-check.nix ├── clippy-check.nix └── nixfmt-check.nix ├── pdk └── brack-pdk-rs │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README-ja.md │ ├── README.md │ ├── flake.lock │ ├── flake.nix │ └── src │ ├── lib.rs │ ├── metadata.rs │ ├── types.rs │ └── values.rs ├── shell.nix └── src ├── cli.rs ├── logger.rs └── main.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | Cargo.nix linguist-generated 2 | Cargo.lock linguist-generated 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @momeemt 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a detailed bug report to help us improve Brack 4 | title: 'Bug: ' 5 | labels: 'type: bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | Provide a clear and detailed description of the issue. Include any relevant error messages or logs that can help diagnose the problem. 12 | 13 | ### Steps to reproduce 14 | Detail the steps needed to reproduce the bug. Make sure to be as specific as possible: 15 | 16 | 1. ... 17 | 2. ... 18 | 3. ... 19 | 20 | ### Expected behavior 21 | Describe what you expected to happen instead of the actual outcome. 22 | 23 | ### Actual behavior 24 | Explain what actually happened. If there are error messages, include them here. 25 | 26 | ### Component (optional) 27 | If you know which component your issue relates to, please select it: 28 | 29 | - [ ] CLI tools (brack) 30 | - [ ] Tokenizer (brack-tokenizer) 31 | - [ ] Parser (brack-parser) 32 | - [ ] Transformer (brack-transformer) 33 | - [ ] Macro Expander (brack-expander) 34 | - [ ] Lower (brack-lower) 35 | - [ ] Code Generator (brack-codegen) 36 | - [ ] Infrastructure 37 | - [ ] Language Server (brack-language-server) 38 | - [ ] Project Manager (brack-project-manager) 39 | - [ ] GitHub Actions 40 | 41 | ### Environment (please complete the following information) 42 | - OS: [e.g., macOS, Windows, Linux] 43 | - Brack version: [e.g., v0.1.0] 44 | - Any additional dependencies or configurations (e.g., plugins) 45 | 46 | ### Screenshots or logs (optional) 47 | If applicable, provide screenshots or attach logs to help illustrate the problem. If you are comfortable sharing them, this can greatly assist in identifying the issue. 48 | 49 | ### Additional context 50 | Add any other relevant context or details that might help in resolving the issue, such as specific configurations, custom settings, or unusual usage scenarios. 51 | 52 | ### Metadata 53 | Please run `brack version` and paste the output below. 54 | 55 | ```console 56 | $ brack version 57 | 58 | ``` 59 | 60 | ### Steps taken to verify 61 | Before submitting, ensure you've done the following checks: 62 | 63 | - [ ] Confirmed that the bug is not caused by any plugins 64 | - [ ] Searched [open bug issues] to avoid duplicates 65 | - [ ] Reviewed [open bug pull requests] for possible solutions 66 | 67 | [open bug issues]: https://github.com/brack-lang/brack/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+bug%22 68 | [open bug pull requests]: https://github.com/brack-lang/brack/pulls?q=is%3Aopen+is%3Apr+label%3A%22type%3A+bug%22 69 | 70 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature or improvement for Brack 4 | title: 'Enhancement: ' 5 | labels: 'type: enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the feature or improvement 11 | Provide a clear and detailed description of the feature or improvement you'd like to see in Brack. Explain why this feature would be useful and any specific use cases or examples where it would be applied. 12 | 13 | ### Suggested implementation (optional) 14 | If you have an idea of how this feature could be implemented, please share it here. This can include pseudo-code, specific behaviors, or references to similar features in other tools or languages. 15 | 16 | ### Component (optional) 17 | If you know which component your issue relates to, please select it: 18 | 19 | - [ ] CLI tools (brack) 20 | - [ ] Tokenizer (brack-tokenizer) 21 | - [ ] Parser (brack-parser) 22 | - [ ] Transformer (brack-transformer) 23 | - [ ] Macro Expander (brack-expander) 24 | - [ ] Lower (brack-lower) 25 | - [ ] Code Generator (brack-codegen) 26 | - [ ] Infrastructure 27 | - [ ] Language Server (brack-language-server) 28 | - [ ] Project Manager (brack-project-manager) 29 | - [ ] GitHub Actions 30 | 31 | ### Additional context 32 | Add any other relevant context or details that might help clarify the feature request, such as related tools or other relevant features. 33 | 34 | ### Reference (optional) 35 | If this feature request has been discussed or inspired elsewhere, please include links to relevant discussions: 36 | 37 | - [ ] GitHub Issue (ref: ) 38 | - [ ] GitHub Pull Request (ref: ) 39 | - [ ] GitHub Discussion (ref: ) 40 | - [ ] Discord Server (ref: ) 41 | 42 | ### Steps taken to verify 43 | Before submitting, ensure you've done the following checks: 44 | 45 | - [ ] Confirmed that a similar feature request does not already exist 46 | - [ ] Searched [open enhancement issues] to avoid duplicates 47 | - [ ] Reviewed [open enhancement pull requests] for possible solutions 48 | 49 | [open enhancement issues]: https://github.com/brack-lang/brack/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+enhancement%22 50 | [open enhancement pull requests]: https://github.com/brack-lang/brack/pulls?q=is%3Aopen+is%3Apr+label%3A%22type%3A+enhancement%22 51 | 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/missing_or_incorrect_documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Missing or incorrect documentation 3 | about: Help us improve the Brack documentation 4 | title: 'Documentation: ' 5 | labels: 'type: documentation' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the issue 11 | Provide a clear and detailed description of the documentation problem or missing information. Include specific sections or commands that are unclear, incorrect, or absent. 12 | 13 | ### Suggested improvements 14 | Explain what could be added or changed to improve the documentation. If applicable, suggest specific wording or examples that would clarify the content. 15 | 16 | ### Component (optional) 17 | If you know which component your issue relates to, please select it: 18 | 19 | - [ ] CLI tools (brack) 20 | - [ ] Tokenizer (brack-tokenizer) 21 | - [ ] Parser (brack-parser) 22 | - [ ] Transformer (brack-transformer) 23 | - [ ] Macro Expander (brack-expander) 24 | - [ ] Lower (brack-lower) 25 | - [ ] Code Generator (brack-codegen) 26 | - [ ] Infrastructure 27 | - [ ] Language Server (brack-language-server) 28 | - [ ] Project Manager (brack-project-manager) 29 | - [ ] GitHub Actions 30 | 31 | ### Reference (optional) 32 | If this issue has been discussed elsewhere, please include links to relevant discussions: 33 | 34 | - [ ] GitHub Issue (ref: ) 35 | - [ ] GitHub Pull Request (ref: ) 36 | - [ ] GitHub Discussion (ref: ) 37 | - [ ] Discord Server (ref: ) 38 | 39 | ### Steps taken to verify 40 | Before submitting, ensure you've reviewed the latest version of the documentation: 41 | 42 | - [ ] Checked [latest README] and [latest doc] 43 | - [ ] Searched [open documentation issues] to avoid duplicates 44 | - [ ] Reviewed [open documentation pull requests] for possible solutions 45 | 46 | [latest README]: https://github.com/brack-lang/brack/blob/develop/README.md 47 | [latest doc]: https://github.com/brack-lang/brack/tree/develop/doc 48 | [open documentation issues]: https://github.com/brack-lang/brack/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+documentation%22 49 | [open documentation pull requests]: https://github.com/brack-lang/brack/pulls?q=is%3Aopen+is%3Apr+label%3A%22type%3A+documentation%22 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about Brack usage, features, or development 4 | title: 'Question: ' 5 | labels: 'type: question' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Question 11 | Please provide a clear and concise question about Brack. Explain the context behind your question, such as what you're trying to achieve or any specific problem you're facing. 12 | 13 | ### Component (optional) 14 | If you know which component your issue relates to, please select it: 15 | 16 | - [ ] CLI tools (brack) 17 | - [ ] Tokenizer (brack-tokenizer) 18 | - [ ] Parser (brack-parser) 19 | - [ ] Transformer (brack-transformer) 20 | - [ ] Macro Expander (brack-expander) 21 | - [ ] Lower (brack-lower) 22 | - [ ] Code Generator (brack-codegen) 23 | - [ ] Infrastructure 24 | - [ ] Language Server (brack-language-server) 25 | - [ ] Project Manager (brack-project-manager) 26 | - [ ] GitHub Actions 27 | 28 | ### What you've tried (optional) 29 | If you've already tried certain solutions or approaches, please describe them here. This will help others understand what has or hasn't worked for you. 30 | 31 | ### Additional context 32 | If applicable, provide any additional information that might help others understand your question. This can include code snippets, configurations, or specific use cases. 33 | 34 | ### Reference (optional) 35 | If your question has been discussed or referenced elsewhere, please include links to relevant discussions: 36 | 37 | - [ ] GitHub Issue (ref: ) 38 | - [ ] GitHub Pull Request (ref: ) 39 | - [ ] GitHub Discussion (ref: ) 40 | - [ ] Discord Server (ref: ) 41 | 42 | ### Steps taken to verify 43 | Before submitting your question, please make sure: 44 | 45 | - [ ] Searched [open questions] to ensure it hasn't already been answered 46 | - [ ] Reviewed [documentation] for relevant information 47 | 48 | [open questions]: https://github.com/brack-lang/brack/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+question%22 49 | [documentation]: https://github.com/brack-lang/brack/tree/develop/doc 50 | 51 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | Please provide a clear and concise description of the changes in this pull request. Indicate whether the changes are related to documentation, bug fixes, or feature implementation: 3 | 4 | - [ ] Documentation 5 | - [ ] Bug Fix 6 | - [ ] Feature Implementation 7 | 8 | ### Component 9 | Select the relevant component that your changes apply to: 10 | 11 | - [ ] Brack (CLI tools) 12 | - [ ] Tokenizer 13 | - [ ] Parser 14 | - [ ] Transformer 15 | - [ ] Lower 16 | - [ ] Code Generator (codegen) 17 | - [ ] Commands Expander (expander) 18 | - [ ] Infrastructure (release automation) 19 | - [ ] Language Server (language-server) 20 | - [ ] Plugin Manager (plugin-manager) 21 | - [ ] Workflow (continuous integration) 22 | 23 | ### Related Issue or Reference (optional) 24 | If this pull request addresses or references any issues, pull requests, or discussions, please provide the relevant links here: 25 | 26 | - [ ] GitHub Issue (ref: ) 27 | - [ ] GitHub Pull Request (ref: ) 28 | - [ ] GitHub Discussion (ref: ) 29 | - [ ] Discord Server (ref: ) 30 | 31 | ### Tests and Verification 32 | Please describe any tests you have performed to verify the changes. Include any specific commands, scripts, or manual steps followed to ensure the functionality works as expected. 33 | 34 | - [ ] Unit Tests 35 | - [ ] Integration Tests 36 | - [ ] Manual Testing 37 | 38 | ### Additional Context 39 | If there is any other information that may be helpful in understanding this pull request, such as screenshots, log files, or explanations of design decisions, please include it here. 40 | 41 | ### Steps taken to verify 42 | Before submitting this pull request, please ensure the following: 43 | 44 | - [ ] Confirmed that the changes do not introduce any new issues or bugs 45 | - [ ] Searched [open issues] and [open pull requests] to avoid duplicates 46 | 47 | [open issues]: https://github.com/brack-lang/brack/issues?q=is%3Aopen+is%3Aissue 48 | [open pull requests]: https://github.com/brack-lang/brack/pulls?q=is%3Aopen+is%3Apr 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'dependabot/**' 6 | paths-ignore: 7 | - '.vscode/**' 8 | - '.gitignore' 9 | - 'LICENSE*' 10 | - 'brack.*.ebnf' 11 | - '**/*.md' 12 | pull_request: 13 | paths-ignore: 14 | - '.vscode/**' 15 | - '.gitignore' 16 | - 'LICENSE*' 17 | - 'brack.*.ebnf' 18 | - '**/*.md' 19 | 20 | jobs: 21 | flake-check: 22 | runs-on: ubuntu-24.04 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: cachix/install-nix-action@v31 26 | - uses: cachix/cachix-action@v16 27 | with: 28 | name: brack-lang 29 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 30 | - run: nix flake check --all-systems 31 | build-x86_64-linux: 32 | runs-on: ubuntu-24.04 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: cachix/install-nix-action@v31 36 | - uses: cachix/cachix-action@v16 37 | with: 38 | name: brack-lang 39 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 40 | - run: nix build ".#packages.x86_64-linux.default" 41 | build-x86_64-darwin: 42 | runs-on: macos-13 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: cachix/install-nix-action@v31 46 | - uses: cachix/cachix-action@v16 47 | with: 48 | name: brack-lang 49 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 50 | - run: nix build ".#packages.x86_64-darwin.default" 51 | build-aarch64-darwin: 52 | runs-on: macos-15 53 | steps: 54 | - uses: actions/checkout@v4 55 | - uses: cachix/install-nix-action@v31 56 | - uses: cachix/cachix-action@v16 57 | with: 58 | name: brack-lang 59 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 60 | - run: nix build ".#packages.aarch64-darwin.default" 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target* 2 | .envrc 3 | .direnv 4 | result* 5 | !result.rs 6 | tmp/ -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "vadimcn.vscode-lldb", 4 | "1yib.rust-bundle", 5 | "rust-lang.rust-analyzer" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Debug compile", 8 | "cargo": { 9 | "args": [ 10 | "build", 11 | "--features=debug", 12 | "--bin=${fileBasenameNoExtension}", 13 | ], 14 | }, 15 | "cwd": "${workspaceFolder}" 16 | }, 17 | { 18 | "type": "lldb", 19 | "request": "launch", 20 | "name": "Debug unit tests in executable 'brack'", 21 | "cargo": { 22 | "args": [ 23 | "test", 24 | "--no-run", 25 | "--bin=brack", 26 | "--package=brack" 27 | ], 28 | "filter": { 29 | "name": "brack", 30 | "kind": "bin" 31 | } 32 | }, 33 | "args": [], 34 | "cwd": "${workspaceFolder}" 35 | }, 36 | { 37 | "type": "lldb", 38 | "request": "launch", 39 | "name": "Debug unit tests in library 'brack-codegen'", 40 | "cargo": { 41 | "args": [ 42 | "test", 43 | "--no-run", 44 | "--lib", 45 | "--package=brack-codegen" 46 | ], 47 | "filter": { 48 | "name": "brack-codegen", 49 | "kind": "lib" 50 | } 51 | }, 52 | "args": [], 53 | "cwd": "${workspaceFolder}" 54 | }, 55 | { 56 | "type": "lldb", 57 | "request": "launch", 58 | "name": "Debug unit tests in library 'brack-parser'", 59 | "cargo": { 60 | "args": [ 61 | "test", 62 | "--no-run", 63 | "--lib", 64 | "--package=brack-parser" 65 | ], 66 | "filter": { 67 | "name": "brack-parser", 68 | "kind": "lib" 69 | } 70 | }, 71 | "args": [], 72 | "cwd": "${workspaceFolder}" 73 | }, 74 | { 75 | "type": "lldb", 76 | "request": "launch", 77 | "name": "Debug unit tests in library 'brack-sdk-rs'", 78 | "cargo": { 79 | "args": [ 80 | "test", 81 | "--no-run", 82 | "--lib", 83 | "--package=brack-sdk-rs" 84 | ], 85 | "filter": { 86 | "name": "brack-sdk-rs", 87 | "kind": "lib" 88 | } 89 | }, 90 | "args": [], 91 | "cwd": "${workspaceFolder}" 92 | }, 93 | { 94 | "type": "lldb", 95 | "request": "launch", 96 | "name": "Debug unit tests in library 'brack-tokenizer'", 97 | "cargo": { 98 | "args": [ 99 | "test", 100 | "--no-run", 101 | "--lib", 102 | "--package=brack-tokenizer" 103 | ], 104 | "filter": { 105 | "name": "brack-tokenizer", 106 | "kind": "lib" 107 | } 108 | }, 109 | "args": [], 110 | "cwd": "${workspaceFolder}" 111 | }, 112 | { 113 | "type": "lldb", 114 | "request": "launch", 115 | "name": "Debug unit tests in library 'brack-plugin'", 116 | "cargo": { 117 | "args": [ 118 | "test", 119 | "--no-run", 120 | "--lib", 121 | "--package=brack-plugin" 122 | ], 123 | "filter": { 124 | "name": "brack-plugin", 125 | "kind": "lib" 126 | } 127 | }, 128 | "args": [], 129 | "cwd": "${workspaceFolder}" 130 | }, 131 | { 132 | "type": "lldb", 133 | "request": "launch", 134 | "name": "Debug unit tests in library 'brack-expander'", 135 | "cargo": { 136 | "args": [ 137 | "test", 138 | "--no-run", 139 | "--lib", 140 | "--package=brack-expander" 141 | ], 142 | "filter": { 143 | "name": "brack-expander", 144 | "kind": "lib" 145 | } 146 | }, 147 | "args": [], 148 | "cwd": "${workspaceFolder}" 149 | }, 150 | { 151 | "type": "lldb", 152 | "request": "launch", 153 | "name": "Debug unit tests in library 'brack-language-server'", 154 | "cargo": { 155 | "args": [ 156 | "test", 157 | "--no-run", 158 | "--lib", 159 | "--package=brack-language-server" 160 | ], 161 | "filter": { 162 | "name": "brack-language-server", 163 | "kind": "lib" 164 | } 165 | }, 166 | "args": [], 167 | "cwd": "${workspaceFolder}" 168 | }, 169 | { 170 | "type": "lldb", 171 | "request": "launch", 172 | "name": "Debug unit tests in library 'brack-project-manager'", 173 | "cargo": { 174 | "args": [ 175 | "test", 176 | "--no-run", 177 | "--lib", 178 | "--package=brack-project-manager" 179 | ], 180 | "filter": { 181 | "name": "brack-project-manager", 182 | "kind": "lib" 183 | } 184 | }, 185 | "args": [], 186 | "cwd": "${workspaceFolder}" 187 | } 188 | ], 189 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "nixEnvSelector.suggestion": false, 3 | "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix" 4 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = [ 6 | "Mutsuha Asada ", 7 | "Kanta Ueno ", 8 | ] 9 | description = "A bracket-based lightweight markup language that extends commands with WebAssembly" 10 | 11 | [dependencies] 12 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 13 | brack-project = { git = "https://github.com/brack-lang/brack", package = "brack-project" } 14 | tokio = { version = "1", features = ["full"] } 15 | anstyle = "1.0.11" 16 | colored = "3.0.0" 17 | codespan-reporting = "0.12.0" 18 | file-size = "1.0.3" 19 | 20 | [dependencies.clap] 21 | version = "4.5.39" 22 | features = ["derive"] 23 | 24 | [features] 25 | default = [] 26 | debug = [] 27 | 28 | [workspace] 29 | members = [ 30 | "crates/brack-codegen", 31 | "crates/brack-common", 32 | "crates/brack-expander", 33 | "crates/brack-language-server", 34 | "crates/brack-parser", 35 | "crates/brack-plugin", 36 | "crates/brack-project", 37 | "crates/brack-release", 38 | "crates/brack-tokenizer", 39 | "crates/brack-transformer", 40 | ] 41 | 42 | resolver = "2" 43 | 44 | [patch."https://github.com/brack-lang/brack"] 45 | brack-codegen = { path = "crates/brack-codegen" } 46 | brack-common = { path = "crates/brack-common" } 47 | brack-expander = { path = "crates/brack-expander" } 48 | brack-language-server = { path = "crates/brack-language-server" } 49 | brack-parser = { path = "crates/brack-parser" } 50 | brack-plugin = { path = "crates/brack-plugin" } 51 | brack-project = { path = "crates/brack-project" } 52 | brack-tokenizer = { path = "crates/brack-tokenizer" } 53 | brack-transformer = { path = "crates/brack-transformer" } 54 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Mutsuha Asada 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README_JA.md: -------------------------------------------------------------------------------- 1 | # 🐦‍⬛ The Brack Markup Language 2 | 3 | ![](./assets/brack-header.png) 4 | 5 |
6 | 7 | [English](./README.md) | **日本語** 8 | 9 | [![Release](https://img.shields.io/github/v/release/brack-lang/brack.svg)](https://github.com/brack-lang/brack/tree/main) 10 | [![Pre-Release](https://img.shields.io/github/v/release/brack-lang/brack.svg?include_prereleases&label=prerelease)](https://github.com/user/repository) 11 | [![CI](https://github.com/brack-lang/brack/actions/workflows/ci.yml/badge.svg)](https://github.com/brack-lang/brack/actions/workflows/ci.yml) 12 | 13 | [![Discord Invite Budge](https://dcbadge.limes.pink/api/server/cH94kqUMYH?style=flat)](https://discord.gg/cH94kqUMYH) 14 | [![X Following Budge](https://img.shields.io/twitter/follow/:bracklanguage)](https://twitter.com/intent/user?screen_name=bracklanguage) 15 | 16 |
17 | 18 | Brackは文法がとてもシンプルで、拡張性が非常に高い**マークアップ言語**です。 19 | WebAssemblyバイナリを利用したプラグインシステムを備えていて、あなたが使い慣れたプログラミング言語[^1]を使って、自由にコマンドを追加できます。 20 | 21 | [^1]: ただし、WebAssemblyにコンパイルでき、かつ[Extism](https://extism.org/)のPDK(Plug-in Development Kit)が実装されている必要があります。2024年10月16日現在、ExtismのPDKが提供されているプログラミング言語はRust、JavaScript、Go、Haskell、AssemblyScript、C、Zig、.NETの8種類です。また、v0.2.0時点では、BrackのPDKはRustのみを提供しています。未サポートの言語でExtism、BrackのPDKを実装する貢献は大歓迎です。 22 | 23 | このリポジトリはBrackのコンパイラと言語サーバ、プロジェクト管理ツール、ドキュメントが含まれています。 24 | 以下にBrackに関連するリポジトリをリストします。 25 | 26 | - [brack-lang/brack-pdk-rs](https://github.com/brack-lang/brack-pdk-rs) 27 | - Rust言語によるPDK(プラグイン開発キット) 28 | - [brack-lang/vscode-brack](https://github.com/brack-lang/vscode-brack) 29 | - Brackによる執筆支援を行うVSCodeの拡張機能 30 | 31 | ## 文法 32 | Brackには、インラインコマンドとブロックコマンド、マクロの3種類の文法があります。 33 | これらをコマンド呼び出し構文と呼びます。 34 | 35 | ```brack 36 | {sample.heading lipsum} 37 | 38 | [sample.bold Lorem] ipsum dolor sit amet, consectetur 39 | adipisicing elit, sed do eiusmod tempor 40 | incididunt ut labore et dolore magna 41 | aliqua. 42 | 43 | {sample.hr} 44 | 45 | [sample.italic Ut] enim ad minim veniam, 46 | quis nostrud exercitation ullamco laboris 47 | nisi ut aliquip ex ea commodo consequat. 48 | Duis aute irure dolor in 49 | [sample.anchor reprehenderit, https://en.wiktionary.org/wiki/reprehenderit] 50 | in voluptate velit esse cillum dolore 51 | eu fugiat nulla pariatur. Excepteur sint 52 | occaecat cupidatat non proident, 53 | sunt in culpa qui officia deserunt mollit anim id est laborum. 54 | ``` 55 | 56 | コマンド呼び出し構文が呼び出されると、名前と0個以上の引数を受け取って異なるデータに置換します。 57 | プラグインを識別するための**モジュール名**と、コマンドを識別するための**コマンド名**によって識別されます。 58 | 59 | ```brack 60 | {module.inline-command arg1, arg2, ..., argN} 61 | [module.block-command] 62 | 63 | ``` 64 | 65 | Brackのコンパイラは特定の変換を実装していません。 66 | つまり、プラグインを入れない状態ではすべてのコマンド呼び出しはエラーになります。 67 | Brackの開発チームから提供される、各ターゲットに対応する`std`プラグインやサードパーティ製のプラグインを使ったり、自分自身でプラグインを開発することで文章を変換できます。 68 | 69 | 例として、HTMLターゲットのプラグインである[std.html](https://github.com/brack-lang/std.html)を利用して、変換される文章を以下に示します。 70 | 71 | ```brack 72 | {std.* Hello, World!} 73 | 74 | Hello, this is the document written using [std.* Brack] 75 | . 76 | ``` 77 | 78 | std.htmlは、上の文章を次のように変換します[^not-guarantee]。 79 | 80 | [^not-guarantee]: std.htmlプラグインやBrackコンパイラのバージョンによって変換結果は左右されます。あくまで一例であり、このように変換されることを保証するものではありません。具体的にどのように変換されるかは、プラグインのドキュメントか言語サーバから得られる情報を参照してください。 81 | 82 | ```html 83 |

Hello, World!

84 |

85 | Hello, this is the document written using Brack 86 | 87 | [1] 88 | . 89 |

90 |
91 |
Footnotes
92 |
    93 |
  1. 94 | A markup language whose transformation rules can be extended by WebAssembly 95 |
  2. 96 |
97 |
98 | ``` 99 | 100 | ## 特徴 101 | 102 | - 最小限の文法と小さな学習コスト 103 | - これまで他のマークアップ言語を使っていたとしても、Brackを使い始めることは簡単です。 104 | - 言語サーバによってどのように文章が変換されるのかを知ることができるので、チートシートとエディタを往復する必要ありません。 105 | - WebAssemblyによってプラグインを実装できる 106 | - Markdownではbold(`**text**`)がHTMLにおける`b`タグ(`text`)に変換されます。既存のマークアップ言語の多くはこのような構文規則を拡張することを前提に言語が設計されていません。 107 | - 構文拡張をサポートする言語の多くは処理系と同じ言語でプラグインを書く必要がありますが、BrackではコンパイラがWebAssemblyを呼び出すことで実現するため、そのような制約がありません。 108 | - また、Brackは構文ではなく、名前と型シグネチャで一意に定まる**コマンド**のみを拡張できるため、学習コストを引き上げず、使える文字の制限も増えません。これは軽量マークアップ言語においてとても重要です。 109 | - ターゲットフォーマットが制約されない 110 | - Markdownは処理系によりますが多くはHTMLに変換するために用いられますが、Brackは出力するターゲットフォーマットを制約しません。 111 | - v0.2.0時点では、HTMLやLaTeX、Pandoc Filterなど、テキストであればどのようなフォーマットにも変換できます。そのために式や文などのコンテナに対する特別なコマンド[^container-hook]も定義できます。 112 | - また、PDFや動画などのバイナリ形式を出力するための後処理機構のサポートも計画されています。 113 | - プロジェクト管理ツール、言語サーバを提供 114 | - `brack`コマンドには、プロジェクト管理ツールや言語サーバが含まれています。 115 | - 個別に管理する必要がなく、一度インストールすればすぐに使い始めることができます。 116 | 117 | [^container-hook]: コンテナフック(Container Hooks)と呼びます。 118 | 119 | ## インストール 120 | 121 | ### Cargo(Rust言語のパッケージマネージャ)によるインストール 122 | ```sh 123 | cargo install --git https://github.com/brack-lang/brack brack 124 | ``` 125 | 126 | ### Nixによるインストール 127 | ```sh 128 | nix profile install github:brack-lang/brack 129 | ``` 130 | 131 | ## ビルド 132 | 133 | ### Nixによるビルド(推奨) 134 | [Flakes](https://wiki.nixos.org/wiki/Flakes)を有効にしたNixを使ってもビルドできます。 135 | GitHub Actions上で、Nixによるビルドとテストが行われているため、こちらを利用したビルドを推奨しています。 136 | 137 | ```sh 138 | git clone https://github.com/brack-lang/brack.git 139 | cd brack 140 | nix build . 141 | 142 | # あるいは 143 | echo "nix flake" > .envrc 144 | direnv allow 145 | ``` 146 | 147 | ### Cargoによるビルド 148 | 149 | ```sh 150 | git clone https://github.com/brack-lang/brack.git 151 | cd brack 152 | cargo build --release 153 | ``` 154 | 155 | ## LICENSE 156 | [assets](./assets)と[doc](./doc)内のすべてのファイルは、CC-BY-4.0でライセンスされています。 157 | 残りのファイルは、Apache License 2.0 or MIT License でライセンスされています。 158 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 -------------------------------------------------------------------------------- /assets/brack-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brack-lang/brack/b81446911dc97a419d0e421bcc0d1a9e718a8bfd/assets/brack-header.png -------------------------------------------------------------------------------- /brack.ast.ebnf: -------------------------------------------------------------------------------- 1 | document := (stmt newline newline+)* stmt? newline* EOF 2 | stmt := expr (newline expr)* 3 | expr := (text | square | angle)+ 4 | angle := angle_bracket_open (module | angle) dot (ident | angle) (expr (comma expr)*)? angle_bracket_close 5 | curly := curly_bracket_open (module | angle) dot (ident | angle) (expr (comma expr)*)? curly_bracket_close 6 | square := square_bracket_open (module | angle) dot (ident | angle) (expr (comma expr)*)? square_bracket_close 7 | angle_bracket_open := "<" 8 | angle_bracket_close := ">" 9 | square_bracket_open := "[" 10 | square_bracket_close := "]" 11 | curly_bracket_open := "{" 12 | curly_bracket_close := "}" 13 | module := text 14 | ident := text 15 | text := [^.]+ 16 | whitespace := " " 17 | newline := "\n" 18 | dot := "." 19 | comma := "," 20 | -------------------------------------------------------------------------------- /brack.cst.ebnf: -------------------------------------------------------------------------------- 1 | document := (stmt newline newline+)* stmt? newline* EOF 2 | stmt := expr_or_close (newline expr_or_close)* 3 | expr_or_close := bracket_close | expr 4 | escaped := backslash (dot | comma | bracket_open | bracket_close | backslash)? 5 | expr := (escaped | module | ident | bracket | dot | comma | whitespace | text)+ 6 | bracket := angle | curly | square 7 | angle := angle_bracket_open (expr | newline)* angle_bracket_close? 8 | curly := curly_bracket_open (expr | newline)* curly_bracket_close? 9 | square := square_bracket_open (expr | newline)* square_bracket_close? 10 | backslash := "\" . 11 | angle_bracket_open := "<" 12 | angle_bracket_close := ">" 13 | square_bracket_open := "[" 14 | square_bracket_close := "]" 15 | curly_bracket_open := "{" 16 | curly_bracket_close := "}" 17 | module := text 18 | ident := text 19 | text := [^.]+ 20 | whitespace := " " 21 | newline := "\n" 22 | dot = "." 23 | comma := "," 24 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path}; 2 | 3 | fn main() { 4 | let version_file = path::Path::new("./VERSION"); 5 | if let Ok(version) = fs::read_to_string(version_file) { 6 | println!("cargo:rustc-env=APP_VERSION={}", version.trim()); 7 | } else { 8 | eprintln!("Failed to read VERSION file"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /crates/brack-codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-codegen" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.98" 10 | brack-plugin = { git = "https://github.com/brack-lang/brack", package = "brack-plugin" } 11 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 12 | -------------------------------------------------------------------------------- /crates/brack-codegen/src/curly.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_common::ast::AST; 3 | use brack_plugin::{ 4 | plugins::Plugins, 5 | types::{arg_counter, Type}, 6 | value::Value, 7 | }; 8 | 9 | use crate::{expr, square, text}; 10 | 11 | pub(crate) fn generate(ast: &AST, plugins: &mut Plugins) -> Result { 12 | match ast { 13 | AST::Curly(_) => (), 14 | _ => anyhow::bail!("Curly must be a curly"), 15 | }; 16 | let mut arguments = vec![]; 17 | let module = ast 18 | .children() 19 | .first() 20 | .ok_or_else(|| anyhow::anyhow!("Curly must contain module"))?; 21 | let ident = ast 22 | .children() 23 | .get(1) 24 | .ok_or_else(|| anyhow::anyhow!("Curly must contain identifier"))?; 25 | for child in ast.children().iter().skip(2) { 26 | let res = match child { 27 | AST::Expr(_) => expr::generate(child, plugins)?, 28 | AST::Curly(_) => generate(child, plugins)?, 29 | AST::Square(_) => square::generate(child, plugins)?, 30 | AST::Text(_) => text::generate(child, plugins)?, 31 | AST::Angle(_) => anyhow::bail!("Angle must be expanded by the macro expander."), 32 | ast => anyhow::bail!("Curly cannot contain the following node\n{}", ast), 33 | }; 34 | arguments.push(res); 35 | } 36 | 37 | let module_name = match module { 38 | AST::Module(module) => module.value.clone(), 39 | _ => anyhow::bail!("Module must be a module"), 40 | }; 41 | let module_name = match module_name { 42 | Some(module_name) => module_name, 43 | _ => anyhow::bail!("Module name must be a string"), 44 | }; 45 | 46 | let ident_name = match ident { 47 | AST::Ident(ident) => ident.value.clone(), 48 | _ => anyhow::bail!("Identifier must be an identifier"), 49 | }; 50 | let ident_name = match ident_name { 51 | Some(ident_name) => ident_name, 52 | _ => anyhow::bail!("Identifier name must be a string"), 53 | }; 54 | 55 | let arg_types = plugins.argument_types(&module_name, &ident_name, Type::TBlock)?; 56 | 57 | let (min, max) = arg_counter( 58 | &arg_types 59 | .iter() 60 | .map(|(_, t)| t) 61 | .cloned() 62 | .collect::>(), 63 | ); 64 | 65 | if arguments.len() < min { 66 | // TODO: show the signature of the command 67 | anyhow::bail!("{} requires at least {} arguments", ident_name, min); 68 | } 69 | if arguments.len() > max { 70 | // TODO: show the signature of the command 71 | anyhow::bail!("{} requires at most {} arguments", ident_name, max); 72 | } 73 | 74 | let mut args = vec![]; 75 | for (i, (_, t)) in arg_types.iter().enumerate() { 76 | let arg = match t { 77 | Type::TOption(_) => { 78 | if i < arguments.len() { 79 | Value::TextOption(Some(arguments[i].clone())) 80 | } else { 81 | Value::TextOption(None) 82 | } 83 | } 84 | Type::TArray(_) => Value::TextArray(arguments[i..].to_vec()), 85 | _ => Value::Text(arguments[i].clone()), 86 | }; 87 | args.push(arg); 88 | } 89 | 90 | let text = plugins.call_block_command(&module_name, &ident_name, args)?; 91 | Ok(text) 92 | } 93 | -------------------------------------------------------------------------------- /crates/brack-codegen/src/expr.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_common::ast::AST; 3 | use brack_plugin::{plugins::Plugins, value::Value}; 4 | 5 | use crate::{curly, square, text}; 6 | 7 | pub(crate) fn generate(ast: &AST, plugins: &mut Plugins) -> Result { 8 | match ast { 9 | AST::Expr(_) => (), 10 | _ => anyhow::bail!("Expr must be an expr"), 11 | }; 12 | let mut result = String::from(""); 13 | for child in ast.children() { 14 | let res = match child { 15 | AST::Curly(_) => curly::generate(child, plugins)?, 16 | AST::Square(_) => square::generate(child, plugins)?, 17 | AST::Text(_) => text::generate(child, plugins)?, 18 | AST::Angle(_) => anyhow::bail!("Angle must be expanded by the macro expander."), 19 | AST::Expr(_) => generate(child, plugins)?, 20 | ast => anyhow::bail!("Expr cannot contain the following node\n{}", ast), 21 | }; 22 | result.push_str(&res); 23 | } 24 | 25 | let hook_result = plugins.call_expr_hook(vec![Value::Text(result.clone())])?; 26 | match hook_result { 27 | Some(result) => Ok(result), 28 | _ => Ok(result), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/brack-codegen/src/generate.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_common::ast::AST; 3 | use brack_plugin::{plugins::Plugins, value::Value}; 4 | 5 | use crate::{curly, expr, square, stmt, text}; 6 | 7 | pub fn generate(ast: &AST, plugins: &mut Plugins) -> Result { 8 | match ast { 9 | AST::Document(_) => (), 10 | _ => anyhow::bail!("Document must be a document"), 11 | }; 12 | let mut result = String::from(""); 13 | for child in ast.children() { 14 | let res = match child { 15 | AST::Stmt(_) => stmt::generate(child, plugins)?, 16 | AST::Expr(_) => expr::generate(child, plugins)?, 17 | AST::Curly(_) => curly::generate(child, plugins)?, 18 | AST::Square(_) => square::generate(child, plugins)?, 19 | AST::Text(_) => text::generate(child, plugins)?, 20 | AST::Angle(_) => anyhow::bail!("Angle must be expanded by the macro expander."), 21 | ast => anyhow::bail!("Document cannot contain the following node\n{}", ast), 22 | }; 23 | result.push_str(&res); 24 | } 25 | 26 | let hook_result = plugins.call_document_hook(vec![Value::Text(result.clone())])?; 27 | match hook_result { 28 | Some(result) => Ok(result), 29 | _ => Ok(result), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/brack-codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod curly; 2 | mod expr; 3 | pub mod generate; 4 | mod square; 5 | mod stmt; 6 | mod text; 7 | -------------------------------------------------------------------------------- /crates/brack-codegen/src/square.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_common::ast::AST; 3 | use brack_plugin::{ 4 | plugins::Plugins, 5 | types::{arg_counter, Type}, 6 | value::Value, 7 | }; 8 | 9 | use crate::{curly, expr, text}; 10 | 11 | pub(crate) fn generate(ast: &AST, plugins: &mut Plugins) -> Result { 12 | match ast { 13 | AST::Square(_) => (), 14 | _ => anyhow::bail!("Square must be a square"), 15 | }; 16 | let mut arguments = vec![]; 17 | let module = ast 18 | .children() 19 | .first() 20 | .ok_or_else(|| anyhow::anyhow!("Square must contain module and identifier"))?; 21 | let ident = ast 22 | .children() 23 | .get(1) 24 | .ok_or_else(|| anyhow::anyhow!("Square must contain module and identifier"))?; 25 | for child in ast.children().iter().skip(2) { 26 | let res = match child { 27 | AST::Expr(_) => expr::generate(child, plugins)?, 28 | AST::Curly(_) => curly::generate(child, plugins)?, 29 | AST::Square(_) => generate(child, plugins)?, 30 | AST::Text(_) => text::generate(child, plugins)?, 31 | AST::Angle(_) => anyhow::bail!("Angle must be expanded by the macro expander."), 32 | ast => anyhow::bail!("Square cannot contain the following node\n{}", ast), 33 | }; 34 | arguments.push(res); 35 | } 36 | 37 | let module_name = match module { 38 | AST::Module(module) => module.value.clone(), 39 | _ => anyhow::bail!("Module must be a module"), 40 | }; 41 | let module_name = match module_name { 42 | Some(module_name) => module_name, 43 | _ => anyhow::bail!("Module name must be a string"), 44 | }; 45 | 46 | let ident_name = match ident { 47 | AST::Ident(ident) => ident.value.clone(), 48 | _ => anyhow::bail!("Identifier must be an identifier"), 49 | }; 50 | let ident_name = match ident_name { 51 | Some(ident_name) => ident_name, 52 | _ => anyhow::bail!("Identifier name must be a string"), 53 | }; 54 | 55 | let arg_types = plugins.argument_types(&module_name, &ident_name, Type::TInline)?; 56 | 57 | let (min, max) = arg_counter( 58 | &arg_types 59 | .iter() 60 | .map(|(_, t)| t) 61 | .cloned() 62 | .collect::>(), 63 | ); 64 | 65 | if arguments.len() < min { 66 | // TODO: show the signature of the command 67 | anyhow::bail!("{} requires at least {} arguments", ident_name, min); 68 | } 69 | if arguments.len() > max { 70 | // TODO: show the signature of the command 71 | anyhow::bail!("{} requires at most {} arguments", ident_name, max); 72 | } 73 | 74 | let mut args = vec![]; 75 | for (i, (_, t)) in arg_types.iter().enumerate() { 76 | let arg = match t { 77 | Type::TOption(_) => { 78 | if i < arguments.len() { 79 | Value::TextOption(Some(arguments[i].clone())) 80 | } else { 81 | Value::TextOption(None) 82 | } 83 | } 84 | Type::TArray(_) => Value::TextArray(arguments[i..].to_vec()), 85 | _ => Value::Text(arguments[i].clone()), 86 | }; 87 | args.push(arg); 88 | } 89 | 90 | let result = plugins.call_inline_command(&module_name, &ident_name, args)?; 91 | Ok(result) 92 | } 93 | -------------------------------------------------------------------------------- /crates/brack-codegen/src/stmt.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_common::ast::AST; 3 | use brack_plugin::{plugins::Plugins, value::Value}; 4 | 5 | use crate::{curly, expr, square, text}; 6 | 7 | pub(crate) fn generate(ast: &AST, plugins: &mut Plugins) -> Result { 8 | match ast { 9 | AST::Stmt(_) => (), 10 | _ => anyhow::bail!("Stmt must be a stmt"), 11 | }; 12 | let mut result = String::from(""); 13 | for child in ast.children() { 14 | let res = match child { 15 | AST::Expr(_) => expr::generate(child, plugins)?, 16 | AST::Curly(_) => curly::generate(child, plugins)?, 17 | AST::Square(_) => square::generate(child, plugins)?, 18 | AST::Text(_) => text::generate(child, plugins)?, 19 | AST::Angle(_) => anyhow::bail!("Angle must be expanded by the macro expander."), 20 | ast => anyhow::bail!("Stmt cannot contain the following node\n{}", ast), 21 | }; 22 | result.push_str(&res); 23 | } 24 | 25 | let hook_result = plugins.call_stmt_hook(vec![Value::Text(result.clone())])?; 26 | match hook_result { 27 | Some(result) => Ok(result), 28 | _ => Ok(result), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/brack-codegen/src/text.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_common::ast::AST; 3 | use brack_plugin::{plugins::Plugins, value::Value}; 4 | 5 | pub(crate) fn generate(ast: &AST, plugins: &mut Plugins) -> Result { 6 | let result = ast 7 | .value() 8 | .ok_or_else(|| anyhow::anyhow!("No value found"))? 9 | .to_string(); 10 | let hook_result = plugins.call_text_hook(vec![Value::Text(result.clone())])?; 11 | match hook_result { 12 | Some(result) => Ok(result), 13 | _ => Ok(result), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crates/brack-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = "1.0.219" 8 | serde_json = "1.0.140" 9 | toml = "0.8.22" 10 | reqwest = "0.12.19" 11 | thiserror = "2.0.12" 12 | 13 | [dependencies.uuid] 14 | version = "1.17.0" 15 | features = ["v4", "fast-rng", "macro-diagnostics", "serde"] 16 | -------------------------------------------------------------------------------- /crates/brack-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | pub mod cst; 3 | pub mod location; 4 | pub mod logger; 5 | pub mod project_errors; 6 | pub mod tokens; 7 | pub mod transformer_errors; 8 | -------------------------------------------------------------------------------- /crates/brack-common/src/location.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::cmp::Ordering; 3 | 4 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 5 | pub struct LocationData { 6 | pub line: usize, 7 | pub character: usize, 8 | } 9 | 10 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 11 | pub struct Location { 12 | pub start: LocationData, 13 | pub end: LocationData, 14 | } 15 | 16 | impl LocationData { 17 | pub fn to_usize(&self, source: &str) -> usize { 18 | let lines: Vec<&str> = source.lines().collect(); 19 | let mut offset = 0; 20 | 21 | for line in lines.iter().take(self.line) { 22 | offset += line.len() + 1; 23 | } 24 | 25 | offset + self.character 26 | } 27 | } 28 | 29 | pub fn mock_location() -> Location { 30 | Location { 31 | start: LocationData { 32 | line: 0, 33 | character: 0, 34 | }, 35 | end: LocationData { 36 | line: 0, 37 | character: 0, 38 | }, 39 | } 40 | } 41 | 42 | pub fn merge_location(location1: &Location, location2: &Location) -> Location { 43 | let start = match location1.start.line.cmp(&location2.start.line) { 44 | Ordering::Less => location1.start.clone(), 45 | Ordering::Equal => match location1.start.character.cmp(&location2.start.character) { 46 | Ordering::Less => location1.start.clone(), 47 | _ => location2.start.clone(), 48 | }, 49 | Ordering::Greater => location2.start.clone(), 50 | }; 51 | 52 | let end = match location1.end.line.cmp(&location2.end.line) { 53 | Ordering::Less => location2.end.clone(), 54 | Ordering::Equal => match location1.end.line.cmp(&location2.end.character) { 55 | Ordering::Greater => location1.end.clone(), 56 | _ => location2.end.clone(), 57 | }, 58 | Ordering::Greater => location1.end.clone(), 59 | }; 60 | 61 | Location { start, end } 62 | } 63 | -------------------------------------------------------------------------------- /crates/brack-common/src/logger.rs: -------------------------------------------------------------------------------- 1 | use crate::project_errors::{ProjectDebug, ProjectError, ProjectInfo, ProjectWarning}; 2 | use std::path::PathBuf; 3 | 4 | pub trait Logger { 5 | fn error(&self, error: &ProjectError); 6 | fn warn(&self, warning: &ProjectWarning); 7 | fn info(&self, info: &ProjectInfo); 8 | fn debug(&self, debug: &ProjectDebug); 9 | fn set_path(&mut self, path: PathBuf); 10 | fn get_path(&self) -> PathBuf; 11 | } 12 | -------------------------------------------------------------------------------- /crates/brack-common/src/tokens.rs: -------------------------------------------------------------------------------- 1 | use crate::location::Location; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 5 | pub enum Token { 6 | Empty(Location), 7 | Text(String, Location), 8 | Module(String, Location), 9 | Ident(String, Location), 10 | NewLine(Location), 11 | WhiteSpace(Location), 12 | Dot(Location), 13 | BackSlash(Location), 14 | AngleBracketOpen(Location), 15 | AngleBracketClose(Location), 16 | SquareBracketOpen(Location), 17 | SquareBracketClose(Location), 18 | CurlyBracketOpen(Location), 19 | CurlyBracketClose(Location), 20 | Comma(Location), 21 | EOF(Location), 22 | } 23 | 24 | impl Token { 25 | pub fn get_location(&self) -> Location { 26 | match self { 27 | Token::Empty(location) => location.clone(), 28 | Token::Text(_, location) => location.clone(), 29 | Token::Module(_, location) => location.clone(), 30 | Token::Ident(_, location) => location.clone(), 31 | Token::NewLine(location) => location.clone(), 32 | Token::WhiteSpace(location) => location.clone(), 33 | Token::Dot(location) => location.clone(), 34 | Token::BackSlash(location) => location.clone(), 35 | Token::AngleBracketOpen(location) => location.clone(), 36 | Token::AngleBracketClose(location) => location.clone(), 37 | Token::SquareBracketOpen(location) => location.clone(), 38 | Token::SquareBracketClose(location) => location.clone(), 39 | Token::CurlyBracketOpen(location) => location.clone(), 40 | Token::CurlyBracketClose(location) => location.clone(), 41 | Token::Comma(location) => location.clone(), 42 | Token::EOF(location) => location.clone(), 43 | } 44 | } 45 | 46 | pub fn set_location(&mut self, location: Location) { 47 | match self { 48 | Token::Empty(l) => *l = location, 49 | Token::Text(_, l) => *l = location, 50 | Token::Module(_, l) => *l = location, 51 | Token::Ident(_, l) => *l = location, 52 | Token::NewLine(l) => *l = location, 53 | Token::WhiteSpace(l) => *l = location, 54 | Token::Dot(l) => *l = location, 55 | Token::BackSlash(l) => *l = location, 56 | Token::AngleBracketOpen(l) => *l = location, 57 | Token::AngleBracketClose(l) => *l = location, 58 | Token::SquareBracketOpen(l) => *l = location, 59 | Token::SquareBracketClose(l) => *l = location, 60 | Token::CurlyBracketOpen(l) => *l = location, 61 | Token::CurlyBracketClose(l) => *l = location, 62 | Token::Comma(l) => *l = location, 63 | Token::EOF(l) => *l = location, 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/brack-common/src/transformer_errors.rs: -------------------------------------------------------------------------------- 1 | use crate::location::Location; 2 | use std::fmt::{self, Display, Formatter}; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum TransformError { 7 | AngleNotOpened(Location), 8 | AngleNotClosed(Location), 9 | CurlyNotOpened(Location), 10 | CurlyNotClosed(Location), 11 | SquareNotOpened(Location), 12 | SquareNotClosed(Location), 13 | MismatchedBracket(Location), 14 | ModuleNotFound(Location), 15 | IdentifierNotFound(Location), 16 | DotNotFound(Location), 17 | CommaNotFound(Location), 18 | UnexpectedDot(Location), 19 | UnexpectedComma(Location), 20 | InvalidBackslash(Location), 21 | } 22 | 23 | impl TransformError { 24 | pub fn get_location(&self) -> Location { 25 | match self { 26 | Self::AngleNotOpened(location) => location.clone(), 27 | Self::AngleNotClosed(location) => location.clone(), 28 | Self::CurlyNotOpened(location) => location.clone(), 29 | Self::CurlyNotClosed(location) => location.clone(), 30 | Self::SquareNotOpened(location) => location.clone(), 31 | Self::SquareNotClosed(location) => location.clone(), 32 | Self::MismatchedBracket(location) => location.clone(), 33 | Self::ModuleNotFound(location) => location.clone(), 34 | Self::IdentifierNotFound(location) => location.clone(), 35 | Self::DotNotFound(location) => location.clone(), 36 | Self::CommaNotFound(location) => location.clone(), 37 | Self::UnexpectedDot(location) => location.clone(), 38 | Self::UnexpectedComma(location) => location.clone(), 39 | Self::InvalidBackslash(location) => location.clone(), 40 | } 41 | } 42 | 43 | pub fn get_message(&self) -> String { 44 | match self { 45 | Self::AngleNotOpened(_) => "Angle bracket not opened".to_string(), 46 | Self::AngleNotClosed(_) => "Angle bracket not closed".to_string(), 47 | Self::CurlyNotOpened(_) => "Curly bracket not opened".to_string(), 48 | Self::CurlyNotClosed(_) => "Curly bracket not closed".to_string(), 49 | Self::SquareNotOpened(_) => "Square bracket not opened".to_string(), 50 | Self::SquareNotClosed(_) => "Square bracket not closed".to_string(), 51 | Self::MismatchedBracket(_) => "Mismatched bracket".to_string(), 52 | Self::ModuleNotFound(_) => "Need module".to_string(), 53 | Self::IdentifierNotFound(_) => "Need identifier after module".to_string(), 54 | Self::DotNotFound(_) => "Need dot after module".to_string(), 55 | Self::CommaNotFound(_) => "Need comma after module".to_string(), 56 | Self::UnexpectedDot(_) => "Unexpected dot".to_string(), 57 | Self::UnexpectedComma(_) => "Unexpected comma".to_string(), 58 | Self::InvalidBackslash(_) => { 59 | "Backslash must be followed by dot, comma, backslash, or bracket".to_string() 60 | } 61 | } 62 | } 63 | } 64 | 65 | impl Display for TransformError { 66 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 67 | let location = self.get_location(); 68 | let message = self.get_message(); 69 | write!( 70 | f, 71 | "Error at line {}, column {} to line {}, column {}: {}", 72 | location.start.line, 73 | location.start.character, 74 | location.end.line, 75 | location.end.character, 76 | message 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/brack-expander/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-expander" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.98" 10 | brack-plugin = { git = "https://github.com/brack-lang/brack", package = "brack-plugin" } 11 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 12 | -------------------------------------------------------------------------------- /crates/brack-expander/src/expand.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_common::ast::AST; 3 | use brack_plugin::plugins::Plugins; 4 | 5 | fn expand_angle(overall_ast: &AST, ast: &AST, plugins: &mut Plugins) -> Result { 6 | let mut module_name = String::from(""); 7 | let mut ident_name = String::from(""); 8 | 9 | for child in ast.children() { 10 | match child { 11 | AST::Module(node) => { 12 | module_name = node 13 | .clone() 14 | .value 15 | .ok_or_else(|| anyhow::anyhow!("No value found"))? 16 | } 17 | AST::Ident(node) => { 18 | ident_name = node 19 | .clone() 20 | .value 21 | .ok_or_else(|| anyhow::anyhow!("No value found"))? 22 | } 23 | _ => (), 24 | } 25 | } 26 | 27 | let new_ast = 28 | plugins.call_macro_command(&module_name, &ident_name, overall_ast.clone(), ast.id())?; 29 | Ok(new_ast) 30 | } 31 | 32 | fn expand_other(overall_ast: &AST, ast: &AST, plugins: &mut Plugins) -> Result { 33 | let mut children = vec![]; 34 | match ast { 35 | AST::Text(_) => { 36 | return Ok(ast.clone()); 37 | } 38 | AST::Module(_) => { 39 | return Ok(ast.clone()); 40 | } 41 | AST::Ident(_) => { 42 | return Ok(ast.clone()); 43 | } 44 | _ => {} 45 | } 46 | for child in ast.children() { 47 | match child { 48 | AST::Angle(_) => children.push(expand_angle(overall_ast, child, plugins)?), 49 | _ => children.push(expand_other(overall_ast, child, plugins)?), 50 | } 51 | } 52 | Ok(ast.clone()) 53 | } 54 | 55 | pub fn expander(ast: &AST, plugins: &mut Plugins) -> Result { 56 | let overall_ast = ast.clone(); 57 | expand_other(&overall_ast, ast, plugins) 58 | } 59 | -------------------------------------------------------------------------------- /crates/brack-expander/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod expand; 2 | -------------------------------------------------------------------------------- /crates/brack-language-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-language-server" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.98" 10 | serde = "1.0.219" 11 | serde_json = "1.0.134" 12 | tokio = { version = "1", features = ["full"] } 13 | lsp-types = "0.97" 14 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 15 | brack-parser = { git = "https://github.com/brack-lang/brack", package = "brack-parser" } 16 | brack-tokenizer = { git = "https://github.com/brack-lang/brack", package = "brack-tokenizer" } 17 | brack-transformer = { git = "https://github.com/brack-lang/brack", package = "brack-transformer" } 18 | brack-plugin = { git = "https://github.com/brack-lang/brack", package = "brack-plugin" } 19 | brack-project = { git = "https://github.com/brack-lang/brack", package = "brack-project" } 20 | urlencoding = "2.1.3" 21 | url = "2.5.4" 22 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod logger; 2 | mod notification; 3 | mod request; 4 | mod response; 5 | mod result; 6 | pub mod server; 7 | mod utils; 8 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/logger.rs: -------------------------------------------------------------------------------- 1 | use brack_common::project_errors::{ProjectDebug, ProjectError, ProjectInfo, ProjectWarning}; 2 | use std::path::PathBuf; 3 | 4 | pub struct Logger {} 5 | 6 | impl brack_common::logger::Logger for Logger { 7 | fn error(&self, _error: &ProjectError) {} 8 | 9 | fn warn(&self, _warning: &ProjectWarning) {} 10 | 11 | fn info(&self, _info: &ProjectInfo) {} 12 | 13 | fn debug(&self, _debug: &ProjectDebug) {} 14 | 15 | fn set_path(&mut self, _path: PathBuf) {} 16 | 17 | fn get_path(&self) -> PathBuf { 18 | PathBuf::new() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/notification.rs: -------------------------------------------------------------------------------- 1 | use crate::server::Server; 2 | use anyhow::Result; 3 | use lsp_types::{ 4 | DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, 5 | }; 6 | use serde::Deserialize; 7 | use serde_json::Value; 8 | 9 | pub mod did_change; 10 | pub mod did_open; 11 | pub mod did_save; 12 | 13 | impl Server { 14 | pub(crate) async fn handle_notification(&mut self, msg: &Value, method: &str) -> Result<()> { 15 | match method { 16 | "initialized" => { 17 | self.log_message("Brack Language Server has been initialized!") 18 | .await?; 19 | Ok(()) 20 | } 21 | "textDocument/didOpen" => { 22 | let params = DidOpenTextDocumentParams::deserialize(msg["params"].clone())?; 23 | self.handle_text_document_did_open(params).await 24 | } 25 | "textDocument/didChange" => { 26 | let params = DidChangeTextDocumentParams::deserialize(msg["params"].clone())?; 27 | self.handle_text_document_did_change(params).await 28 | } 29 | "textDocument/didSave" => { 30 | let params = DidSaveTextDocumentParams::deserialize(msg["params"].clone())?; 31 | self.handle_text_document_did_save(params).await 32 | } 33 | _ => Ok(()), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/notification/did_change.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use lsp_types::DidChangeTextDocumentParams; 3 | 4 | use crate::server::Server; 5 | 6 | impl Server { 7 | pub(crate) async fn handle_text_document_did_change( 8 | &self, 9 | _params: DidChangeTextDocumentParams, 10 | ) -> Result<()> { 11 | Ok(()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/notification/did_open.rs: -------------------------------------------------------------------------------- 1 | use crate::{logger::Logger, server::Server}; 2 | use anyhow::Result; 3 | use brack_project::project::Project; 4 | use lsp_types::DidOpenTextDocumentParams; 5 | use std::path::Path; 6 | 7 | impl Server { 8 | pub(crate) async fn handle_text_document_did_open( 9 | &mut self, 10 | params: DidOpenTextDocumentParams, 11 | ) -> Result<()> { 12 | let file_path_str = params.text_document.uri.as_str(); 13 | let file_path = Path::new(file_path_str); 14 | 15 | // root/docs/file.[] -> root 16 | let root = file_path 17 | .parent() 18 | .ok_or_else(|| anyhow::anyhow!("Invalid file path"))? 19 | .parent() 20 | .ok_or_else(|| anyhow::anyhow!("Invalid file path"))?; 21 | 22 | let logger = Logger {}; 23 | let project = Project::new_with_manifest(&logger, root) 24 | .map_err(|e| anyhow::anyhow!("Failed to create project: {:?}", e))?; 25 | self.project = Some(project); 26 | 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/notification/did_save.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_parser::parse::parse; 3 | use brack_tokenizer::tokenize::tokenize; 4 | use brack_transformer::transform::transform; 5 | use lsp_types::{Diagnostic, DidSaveTextDocumentParams}; 6 | use std::fs::read_to_string; 7 | 8 | use crate::{server::Server, utils::to_url}; 9 | 10 | impl Server { 11 | pub(crate) async fn handle_text_document_did_save( 12 | &self, 13 | param: DidSaveTextDocumentParams, 14 | ) -> Result<()> { 15 | let path = to_url(param.text_document.uri)? 16 | .to_file_path() 17 | .map_err(|e| anyhow::anyhow!("Failed to convert URI to file path: {:?}", e))?; 18 | let path_str = path 19 | .to_str() 20 | .ok_or_else(|| anyhow::anyhow!("Invalid file path"))?; 21 | 22 | let file = read_to_string(path_str)?; 23 | let tokens = tokenize(&file); 24 | let cst = parse(&tokens); 25 | let (_, errors) = transform(&cst); 26 | 27 | if errors.is_empty() { 28 | let diagnostics: Vec = vec![]; 29 | return self.send_publish_diagnostics(path_str, &diagnostics).await; 30 | } 31 | 32 | let mut diagnostics = vec![]; 33 | for error in errors { 34 | let location = error.get_location(); 35 | let message = error.get_message(); 36 | let diagnostic = Diagnostic { 37 | range: lsp_types::Range { 38 | start: lsp_types::Position { 39 | line: location.start.line as u32, 40 | character: location.start.character as u32, 41 | }, 42 | end: lsp_types::Position { 43 | line: location.end.line as u32, 44 | character: location.end.character as u32, 45 | }, 46 | }, 47 | message, 48 | ..Default::default() 49 | }; 50 | diagnostics.push(diagnostic); 51 | } 52 | self.send_publish_diagnostics(path_str, &diagnostics).await 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/request/completion.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_plugin::{metadata::Metadata, plugin::Plugin, plugins::Plugins, types::Type}; 3 | use lsp_types::{CompletionItem, CompletionParams, CompletionResponse, InsertTextFormat}; 4 | 5 | use crate::server::Server; 6 | 7 | fn build_completion_item( 8 | module_name: &str, 9 | name: &str, 10 | typ: &Type, 11 | command_metadata: &Metadata, 12 | ) -> CompletionItem { 13 | let insert_text = Some(match typ { 14 | Type::TInline => format!("{}.{} $0]", module_name, name), 15 | Type::TBlock => format!("{}.{} $0}}", module_name, name), 16 | Type::TAST => format!("{}.{} $0>", module_name, name), 17 | _ => panic!("Invalid type"), 18 | }); 19 | CompletionItem { 20 | label: format!("{}.{}", module_name, name), 21 | detail: Some(format!( 22 | "Argument Types: {:?}\nReturn Type: {:?}", 23 | command_metadata.argument_types, command_metadata.return_type 24 | )), 25 | insert_text, 26 | insert_text_format: Some(InsertTextFormat::SNIPPET), 27 | ..CompletionItem::default() 28 | } 29 | } 30 | 31 | impl Server { 32 | pub(crate) async fn handle_completion( 33 | &self, 34 | params: CompletionParams, 35 | ) -> Result> { 36 | if self.project.is_none() { 37 | // BLS doesn't support single-file mode now. 38 | return Ok(None); 39 | } 40 | let project = self.project.as_ref().unwrap(); 41 | let mut completion_items = vec![]; 42 | let mut plugin_vec = vec![]; 43 | for (name, (path, feature_flag)) in &project.plugins_metadata { 44 | let plugin = Plugin::new(name, path, feature_flag.clone())?; 45 | plugin_vec.push(plugin); 46 | } 47 | let plugins = Plugins::new(plugin_vec)?; 48 | let start = params 49 | .context 50 | .ok_or_else(|| anyhow::anyhow!("No context"))? 51 | .trigger_character; 52 | if start.is_none() { 53 | return Ok(None); 54 | } 55 | let start = start.unwrap(); 56 | for plugin in plugins.name_to_plugin.values() { 57 | for ((name, typ), command_metadata) in plugin.signature_to_metadata.iter() { 58 | if (start == *"[" && matches!(typ, Type::TInline)) 59 | || (start == *"{" && matches!(typ, Type::TBlock)) 60 | { 61 | completion_items.push(build_completion_item( 62 | &plugin.name, 63 | name, 64 | typ, 65 | command_metadata, 66 | )); 67 | } 68 | } 69 | } 70 | Ok(Some(CompletionResponse::Array(completion_items))) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/request/semantic_tokens.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_common::tokens::Token; 3 | use brack_tokenizer::tokenize::tokenize; 4 | use lsp_types::{SemanticToken, SemanticTokenType, SemanticTokens, SemanticTokensParams}; 5 | use std::fs::read_to_string; 6 | 7 | use crate::{server::Server, utils::to_url}; 8 | 9 | fn token_kind_to_type(token: &Token) -> u32 { 10 | let typ = match token { 11 | Token::Module(_, _) => SemanticTokenType::NAMESPACE, 12 | Token::Ident(_, _) => SemanticTokenType::VARIABLE, 13 | Token::AngleBracketOpen(_) | Token::AngleBracketClose(_) => SemanticTokenType::MACRO, 14 | Token::CurlyBracketOpen(_) | Token::CurlyBracketClose(_) => SemanticTokenType::METHOD, 15 | Token::SquareBracketOpen(_) | Token::SquareBracketClose(_) => SemanticTokenType::FUNCTION, 16 | _ => return 100, // no decoration 17 | }; 18 | token_type_as_u32(typ) 19 | } 20 | 21 | fn token_type_as_u32(token_type: SemanticTokenType) -> u32 { 22 | match token_type.as_str() { 23 | "namespace" => 0, 24 | "type" => 1, 25 | "class" => 2, 26 | "enum" => 3, 27 | "interface" => 4, 28 | "struct" => 5, 29 | "typeParameter" => 6, 30 | "parameter" => 7, 31 | "variable" => 8, 32 | "property" => 9, 33 | "enumMember" => 10, 34 | "event" => 11, 35 | "function" => 12, 36 | "method" => 13, 37 | "macro" => 14, 38 | "keyword" => 15, 39 | "modifier" => 16, 40 | "comment" => 17, 41 | "string" => 18, 42 | "number" => 19, 43 | "regexp" => 20, 44 | "operator" => 21, 45 | _ => 8, 46 | } 47 | } 48 | 49 | fn separate(tokens: &Vec) -> Vec { 50 | let mut semantic_tokens = Vec::new(); 51 | let mut prev_line = 0; 52 | let mut prev_char = 0; 53 | 54 | for token in tokens { 55 | let location = token.get_location(); 56 | let delta_line = location.start.line as u32 - prev_line; 57 | let delta_start = if delta_line == 0 { 58 | location.start.character as u32 - prev_char 59 | } else { 60 | location.start.character as u32 61 | }; 62 | 63 | semantic_tokens.push(SemanticToken { 64 | delta_line, 65 | delta_start, 66 | length: (location.end.character - location.start.character) as u32, 67 | token_type: token_kind_to_type(token), 68 | token_modifiers_bitset: 0, 69 | }); 70 | 71 | prev_line = location.start.line as u32; 72 | prev_char = location.start.character as u32; 73 | } 74 | 75 | semantic_tokens 76 | } 77 | 78 | impl Server { 79 | pub(crate) async fn handle_semantic_tokens_full( 80 | &self, 81 | params: SemanticTokensParams, 82 | ) -> Result> { 83 | let path = to_url(params.text_document.uri)? 84 | .to_file_path() 85 | .map_err(|e| anyhow::anyhow!("Failed to convert URI to file path: {:?}", e))?; 86 | let file = read_to_string(path)?; 87 | 88 | let tokens = tokenize(&file); 89 | 90 | let separated = separate(&tokens); 91 | Ok(Some(SemanticTokens { 92 | result_id: None, 93 | data: separated, 94 | })) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/response.rs: -------------------------------------------------------------------------------- 1 | use crate::server::Server; 2 | use anyhow::Result; 3 | use serde_json::Value; 4 | 5 | impl Server { 6 | pub(crate) async fn handle_response(&self, _: &Value, _: i64) -> Result<()> { 7 | Ok(()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/result.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | #[derive(Serialize)] 4 | pub(crate) struct BLSResult { 5 | jsonrpc: String, 6 | id: i64, 7 | result: R, 8 | } 9 | 10 | impl BLSResult { 11 | pub fn new(id: i64, result: R) -> Self { 12 | Self { 13 | jsonrpc: "2.0".to_string(), 14 | id, 15 | result, 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::str::from_utf8; 2 | 3 | use anyhow::Result; 4 | use brack_project::project::Project; 5 | use lsp_types::{ClientCapabilities, Diagnostic}; 6 | use serde::Serialize; 7 | use serde_json::{from_str, json, Value}; 8 | use tokio::io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}; 9 | 10 | #[derive(Default)] 11 | pub struct Server { 12 | pub(crate) client_capabilities: ClientCapabilities, 13 | pub(crate) project: Option, 14 | } 15 | 16 | impl Server { 17 | pub(crate) async fn send_stdout(&self, message: &T) -> Result<()> { 18 | let msg = serde_json::to_string(message)?; 19 | let mut output = stdout(); 20 | output 21 | .write_all(format!("Content-Length: {}\r\n\r\n{}", msg.len(), msg).as_bytes()) 22 | .await?; 23 | output.flush().await?; 24 | Ok(()) 25 | } 26 | 27 | pub(crate) async fn send_message(&self, msg: &str) -> Result<()> { 28 | let mut output = stdout(); 29 | output 30 | .write_all(format!("Content-Length: {}\r\n\r\n{}", msg.len(), msg).as_bytes()) 31 | .await?; 32 | output.flush().await?; 33 | Ok(()) 34 | } 35 | 36 | pub(crate) async fn log_message(&self, message: &str) -> Result<()> { 37 | let response = json!({ 38 | "jsonrpc": "2.0", 39 | "method": "window/logMessage", 40 | "params": { 41 | "type": 3, 42 | "message": message 43 | } 44 | }) 45 | .to_string(); 46 | self.send_message(&response).await 47 | } 48 | 49 | pub(crate) async fn send_error_response( 50 | &self, 51 | id: Option, 52 | code: i32, 53 | message: &str, 54 | ) -> Result<()> { 55 | let response = json!({ 56 | "jsonrpc": "2.0", 57 | "id": id, 58 | "error": { 59 | "code": code, 60 | "message": message, 61 | } 62 | }) 63 | .to_string(); 64 | self.send_message(&response).await 65 | } 66 | 67 | pub(crate) async fn send_invalid_request_response(&self) -> Result<()> { 68 | self.send_error_response(None, -32600, "received an invalid request") 69 | .await 70 | } 71 | 72 | pub(crate) async fn send_method_not_found_response(&self, id: i64, method: &str) -> Result<()> { 73 | self.send_error_response(Some(id), -32601, &format!("{} is not supported", method)) 74 | .await 75 | } 76 | 77 | #[allow(dead_code)] 78 | pub(crate) async fn send_parse_error_response(&self) -> Result<()> { 79 | self.send_error_response(None, -32700, "received an invalid JSON") 80 | .await 81 | } 82 | 83 | pub(crate) async fn send_publish_diagnostics( 84 | &self, 85 | uri: &str, 86 | diagnostics: &Vec, 87 | ) -> Result<()> { 88 | // check client_capabilities.text_document.publish_diagnostics 89 | if self 90 | .client_capabilities 91 | .text_document 92 | .as_ref() 93 | .and_then(|td| td.publish_diagnostics.as_ref()) 94 | .is_none() 95 | { 96 | return Ok(()); 97 | } 98 | 99 | let response = json!({ 100 | "jsonrpc": "2.0", 101 | "method": "textDocument/publishDiagnostics", 102 | "params": { 103 | "uri": uri, 104 | "diagnostics": json!(diagnostics), 105 | } 106 | }) 107 | .to_string(); 108 | self.send_message(&response).await 109 | } 110 | 111 | pub(crate) async fn dispatch(&mut self, msg: Value) -> Result<()> { 112 | match ( 113 | msg.get("id").and_then(|i| i.as_i64()), 114 | msg.get("method").and_then(|m| m.as_str()), 115 | ) { 116 | (Some(id), Some(method)) => self.handle_request(&msg, id, method).await, 117 | (Some(id), None) => self.handle_response(&msg, id).await, 118 | (None, Some(method)) => self.handle_notification(&msg, method).await, 119 | _ => self.send_invalid_request_response().await, 120 | } 121 | } 122 | 123 | pub async fn run(&mut self) -> Result<()> { 124 | let mut stdin = stdin(); 125 | let mut buffer = Vec::new(); 126 | 127 | loop { 128 | let mut tmp_buffer = [0; 1024]; 129 | 130 | let chunk = stdin.read(&mut tmp_buffer).await?; 131 | 132 | if chunk == 0 { 133 | break; 134 | } 135 | buffer.extend_from_slice(&tmp_buffer[..chunk]); 136 | 137 | let buffer_string = from_utf8(&buffer)?; 138 | if !buffer_string.contains("\r\n\r\n") { 139 | continue; 140 | } 141 | 142 | let splitted_buffer = buffer_string.split("\r\n\r\n").collect::>(); 143 | let header_string = splitted_buffer[0]; 144 | 145 | let mut content_length = -1; 146 | let header_length = header_string.len() + 4; 147 | for line in header_string.split("\r\n") { 148 | let splitted_line = line.split(": ").collect::>(); 149 | let key = splitted_line[0]; 150 | let value = splitted_line[1]; 151 | if key == "Content-Length" { 152 | content_length = value.parse::()?; 153 | } 154 | } 155 | 156 | if content_length == -1 { 157 | continue; 158 | } 159 | let total_length = header_length + content_length as usize; 160 | 161 | if buffer.len() < total_length { 162 | continue; 163 | } 164 | 165 | let msg: Value = from_str(&buffer_string[header_length..total_length])?; 166 | self.dispatch(msg).await?; 167 | buffer.drain(0..total_length); 168 | } 169 | 170 | Ok(()) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /crates/brack-language-server/src/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use lsp_types::Uri; 3 | use url::Url; 4 | use urlencoding::decode; 5 | 6 | pub fn to_url(uri: Uri) -> Result { 7 | let uri = decode(uri.as_str())?.into_owned(); 8 | Url::parse(uri.as_str()).map_err(Into::into) 9 | } 10 | -------------------------------------------------------------------------------- /crates/brack-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-parser" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 10 | serde = { version = "1.0.219", features = ["derive"] } 11 | serde_json = "1.0.134" 12 | 13 | [features] 14 | default = [] 15 | debug = [] 16 | -------------------------------------------------------------------------------- /crates/brack-parser/README.md: -------------------------------------------------------------------------------- 1 | # brack-parser 2 | It generates a concrete syntax tree (CST) from tokens of the Brack language. 3 | The CST has nodes that are not related to semantic analysis and code generation, for example whitespace (` `), newline (`\n`), period (`.`), and comma (`,`), and so on. 4 | These nodes are useful for implementing formatters, especially snippet expanders. 5 | 6 | ## Syntax 7 | You can also refer to `./brack.cst.ebnf` for concrete syntax. 8 | 9 | ```ebnf 10 | document := (stmt newline newline+)* stmt newline* EOF 11 | stmt := expr_or_close (newline expr_or_close)* 12 | expr_or_close := expr | bracket_close 13 | escaped := backslash (dot | comma | bracket_open | bracket_close | backslash | .) 14 | expr := (escaped | module | ident | bracket | dot | comma | whitespace | text)* 15 | bracket := bracket_open (expr | newline)* bracket_close? 16 | bracket_open := angle_bracket_open | square_bracket_open | curly_bracket_open 17 | bracket_close := angle_bracket_close | square_bracket_close | curly_bracket_close 18 | angle_bracket_open := "<" 19 | angle_bracket_close := ">" 20 | square_bracket_open := "[" 21 | square_bracket_close := "]" 22 | curly_bracket_open := "{" 23 | curly_bracket_close := "}" 24 | module := text 25 | ident := text 26 | text := [^.]+ 27 | whitespace := " " 28 | newline := "\n" 29 | dot = "." 30 | comma := "," 31 | backslash := "\" 32 | ``` 33 | 34 | ## Example 35 | ```rs 36 | let tokens = tokenize(code)?; 37 | let cst = parse(tokens)?; 38 | ``` 39 | 40 | ## CST 41 | CST is defined below. 42 | 43 | ```rs 44 | pub enum CST { 45 | Document(InnerNode), 46 | Stmt(InnerNode), 47 | Expr(InnerNode), 48 | Bracket(InnerNode), 49 | AngleBracketOpen(LeafNode), 50 | AngleBracketClose(LeafNode), 51 | SquareBracketOpen(LeafNode), 52 | SquareBracketClose(LeafNode), 53 | CurlyBracketOpen(LeafNode), 54 | CurlyBracketClose(LeafNode), 55 | Module(LeafNode), 56 | Ident(LeafNode), 57 | Text(LeafNode), 58 | Whitespace(LeafNode), 59 | Newline(LeafNode), 60 | Dot(LeafNode), 61 | Comma(LeafNode), 62 | EOF(LeafNode), 63 | } 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /crates/brack-parser/src/angle.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_angle, new_angle_bracket_close, new_angle_bracket_open}; 2 | use brack_common::location::Location; 3 | use brack_common::tokens::Token; 4 | 5 | use crate::{expr, newline, parser::Parser}; 6 | 7 | // angle_bracket_open (expr | newline)* angle_bracket_close? 8 | pub fn parse(tokens: &[Token]) -> Option { 9 | let mut result = new_angle(); 10 | 11 | let bracket_open_location = if let Some(token) = tokens.first() { 12 | token.get_location() 13 | } else { 14 | return None; 15 | }; 16 | 17 | let (cst, mut tokens) = parse_angle_bracket_open(tokens)?; 18 | result.add(cst); 19 | 20 | loop { 21 | if let Some((cst, new_tokens)) = expr::parse(tokens) { 22 | result.add(cst); 23 | tokens = new_tokens; 24 | } else if let Some((cst, new_tokens)) = newline::parse(tokens) { 25 | result.add(cst); 26 | tokens = new_tokens; 27 | } else { 28 | break; 29 | } 30 | } 31 | 32 | let bracket_close_location = if let Some(token) = tokens.first() { 33 | token.get_location() 34 | } else { 35 | return None; 36 | }; 37 | 38 | let tokens = if let Some((cst, tokens)) = parse_angle_bracket_close(tokens) { 39 | result.add(cst); 40 | tokens 41 | } else { 42 | tokens 43 | }; 44 | 45 | result.set_location(Location { 46 | start: bracket_open_location.start, 47 | end: bracket_close_location.end, 48 | }); 49 | 50 | Some((result, tokens)) 51 | } 52 | 53 | // angle_bracket_open 54 | fn parse_angle_bracket_open(tokens: &[Token]) -> Option { 55 | if let Some(token) = tokens.first() { 56 | match token { 57 | Token::AngleBracketOpen(location) => { 58 | return Some((new_angle_bracket_open(location.clone()), &tokens[1..])); 59 | } 60 | _ => return None, 61 | } 62 | } 63 | None 64 | } 65 | 66 | // angle_bracket_close 67 | fn parse_angle_bracket_close(tokens: &[Token]) -> Option { 68 | if let Some(token) = tokens.first() { 69 | match token { 70 | Token::AngleBracketClose(location) => { 71 | return Some((new_angle_bracket_close(location.clone()), &tokens[1..])); 72 | } 73 | _ => return None, 74 | } 75 | } 76 | None 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use brack_common::cst::{ 82 | matches_kind, new_angle, new_angle_bracket_close, new_angle_bracket_open, new_dot, 83 | new_expr, new_ident, new_module, new_text, new_whitespace, 84 | }; 85 | use brack_common::location::mock_location; 86 | use brack_common::tokens::Token; 87 | 88 | #[test] 89 | fn test_bracket_parse_valid_angle_bracket() { 90 | let tokens = vec![ 91 | Token::AngleBracketOpen(mock_location()), 92 | Token::Module("std".to_string(), mock_location()), 93 | Token::Dot(mock_location()), 94 | Token::Ident("*".to_string(), mock_location()), 95 | Token::WhiteSpace(mock_location()), 96 | Token::Text("Hello!".to_string(), mock_location()), 97 | Token::AngleBracketClose(mock_location()), 98 | ]; 99 | if let Some((cst, tokens)) = super::parse(&tokens) { 100 | assert!(matches_kind(&cst, &new_angle())); 101 | assert!(matches_kind( 102 | &cst.children()[0], 103 | &new_angle_bracket_open(mock_location()) 104 | )); 105 | assert!(matches_kind(&cst.children()[1], &new_expr())); 106 | assert!(matches_kind( 107 | &cst.children()[1].children()[0], 108 | &new_module("std".to_string(), mock_location()) 109 | )); 110 | assert!(matches_kind( 111 | &cst.children()[1].children()[1], 112 | &new_dot(mock_location()) 113 | )); 114 | assert!(matches_kind( 115 | &cst.children()[1].children()[2], 116 | &new_ident("*".to_string(), mock_location()) 117 | )); 118 | assert!(matches_kind( 119 | &cst.children()[1].children()[3], 120 | &new_whitespace(mock_location()) 121 | )); 122 | assert!(matches_kind( 123 | &cst.children()[1].children()[4], 124 | &new_text("Hello!".to_string(), mock_location()) 125 | )); 126 | assert!(matches_kind( 127 | &cst.children()[2], 128 | &new_angle_bracket_close(mock_location()) 129 | )); 130 | assert_eq!(tokens.len(), 0); 131 | } else { 132 | panic!("Expected to parse an angle bracket"); 133 | } 134 | } 135 | 136 | #[test] 137 | fn test_bracket_parse_invalid_angle_bracket() { 138 | let tokens = vec![ 139 | Token::AngleBracketOpen(mock_location()), 140 | Token::Module("std".to_string(), mock_location()), 141 | Token::Dot(mock_location()), 142 | Token::Ident("*".to_string(), mock_location()), 143 | Token::WhiteSpace(mock_location()), 144 | Token::Text("Hello!".to_string(), mock_location()), 145 | Token::EOF(mock_location()), 146 | ]; 147 | if let Some((cst, _)) = super::parse(&tokens) { 148 | assert!(matches_kind(&cst, &new_angle())); 149 | assert!(matches_kind( 150 | &cst.children()[0], 151 | &new_angle_bracket_open(mock_location()) 152 | )); 153 | assert!(matches_kind(&cst.children()[1], &new_expr())); 154 | assert!(matches_kind( 155 | &cst.children()[1].children()[0], 156 | &new_module("std".to_string(), mock_location()) 157 | )); 158 | assert!(matches_kind( 159 | &cst.children()[1].children()[1], 160 | &new_dot(mock_location()) 161 | )); 162 | assert!(matches_kind( 163 | &cst.children()[1].children()[2], 164 | &new_ident("*".to_string(), mock_location()) 165 | )); 166 | assert!(matches_kind( 167 | &cst.children()[1].children()[3], 168 | &new_whitespace(mock_location()) 169 | )); 170 | assert!(matches_kind( 171 | &cst.children()[1].children()[4], 172 | &new_text("Hello!".to_string(), mock_location()) 173 | )); 174 | } else { 175 | panic!("Expected to parse an angle bracket"); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /crates/brack-parser/src/bracket.rs: -------------------------------------------------------------------------------- 1 | use brack_common::tokens::Token; 2 | 3 | use crate::{angle, curly, parser::Parser, square}; 4 | 5 | // angle | curly | square 6 | pub fn parse(tokens: &[Token]) -> Option { 7 | if let Some((cst, new_tokens)) = angle::parse(tokens) { 8 | return Some((cst, new_tokens)); 9 | } else if let Some((cst, new_tokens)) = curly::parse(tokens) { 10 | return Some((cst, new_tokens)); 11 | } else if let Some((cst, new_tokens)) = square::parse(tokens) { 12 | return Some((cst, new_tokens)); 13 | } 14 | None 15 | } 16 | -------------------------------------------------------------------------------- /crates/brack-parser/src/bracket_close.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::Parser; 2 | use brack_common::cst::{ 3 | new_angle_bracket_close, new_curly_bracket_close, new_square_bracket_close, 4 | }; 5 | use brack_common::tokens::Token; 6 | 7 | // angle_bracket_close | square_bracket_close | curly_bracket_close 8 | pub fn parse(tokens: &[Token]) -> Option { 9 | if let Some(token) = tokens.first() { 10 | match token { 11 | Token::AngleBracketClose(location) => { 12 | return Some((new_angle_bracket_close(location.clone()), &tokens[1..])); 13 | } 14 | Token::SquareBracketClose(location) => { 15 | return Some((new_square_bracket_close(location.clone()), &tokens[1..])); 16 | } 17 | Token::CurlyBracketClose(location) => { 18 | return Some((new_curly_bracket_close(location.clone()), &tokens[1..])); 19 | } 20 | _ => return None, 21 | } 22 | } 23 | None 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use brack_common::cst::{ 29 | matches_kind, new_angle_bracket_close, new_curly_bracket_close, new_square_bracket_close, 30 | }; 31 | use brack_common::location::mock_location; 32 | use brack_common::tokens::Token; 33 | 34 | #[test] 35 | fn test_bracket_close_parse_only_angle_bracket_close() { 36 | let tokens = vec![Token::AngleBracketClose(mock_location())]; 37 | if let Some((cst, tokens)) = super::parse(&tokens) { 38 | assert_eq!(tokens.len(), 0); 39 | assert!(matches_kind( 40 | &cst, 41 | &new_angle_bracket_close(mock_location()) 42 | )); 43 | } else { 44 | panic!("Expected to parse an angle bracket close token"); 45 | } 46 | } 47 | 48 | #[test] 49 | fn test_bracket_close_parse_only_square_bracket_close() { 50 | let tokens = vec![Token::SquareBracketClose(mock_location())]; 51 | if let Some((cst, tokens)) = super::parse(&tokens) { 52 | assert_eq!(tokens.len(), 0); 53 | assert!(matches_kind( 54 | &cst, 55 | &new_square_bracket_close(mock_location()) 56 | )); 57 | } else { 58 | panic!("Expected to parse a square bracket close token"); 59 | } 60 | } 61 | 62 | #[test] 63 | fn test_bracket_close_parse_only_curly_bracket_close() { 64 | let tokens = vec![Token::CurlyBracketClose(mock_location())]; 65 | if let Some((cst, tokens)) = super::parse(&tokens) { 66 | assert_eq!(tokens.len(), 0); 67 | assert!(matches_kind( 68 | &cst, 69 | &new_curly_bracket_close(mock_location()) 70 | )); 71 | } else { 72 | panic!("Expected to parse a curly bracket close token"); 73 | } 74 | } 75 | 76 | #[test] 77 | fn test_bracket_close_parse_failures() { 78 | let tokens = vec![]; 79 | assert!(super::parse(&tokens).is_none()); 80 | 81 | let tokens = vec![Token::AngleBracketOpen(mock_location())]; 82 | assert!(super::parse(&tokens).is_none()); 83 | 84 | let tokens = vec![Token::SquareBracketOpen(mock_location())]; 85 | assert!(super::parse(&tokens).is_none()); 86 | 87 | let tokens = vec![Token::CurlyBracketOpen(mock_location())]; 88 | assert!(super::parse(&tokens).is_none()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/brack-parser/src/comma.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_comma; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::parser::Parser; 5 | 6 | pub fn parse(tokens: &[Token]) -> Option { 7 | if let Some(token) = tokens.first() { 8 | match token { 9 | Token::Comma(location) => { 10 | return Some((new_comma(location.clone()), &tokens[1..])); 11 | } 12 | _ => return None, 13 | } 14 | } 15 | None 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use brack_common::cst::{matches_kind, new_comma}; 21 | use brack_common::location::mock_location; 22 | use brack_common::tokens::Token; 23 | 24 | #[test] 25 | fn test_comma_parse_only_comma() { 26 | let tokens = vec![Token::Comma(mock_location())]; 27 | if let Some((cst, tokens)) = super::parse(&tokens) { 28 | assert_eq!(tokens.len(), 0); 29 | assert!(matches_kind(&cst, &new_comma(mock_location()))); 30 | } else { 31 | panic!("Expected to parse a comma token"); 32 | } 33 | } 34 | 35 | #[test] 36 | fn test_comma_parse_failure() { 37 | let tokens = vec![Token::AngleBracketOpen(mock_location())]; 38 | let result = super::parse(&tokens); 39 | assert!(result.is_none()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/brack-parser/src/curly.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_curly, new_curly_bracket_close, new_curly_bracket_open}; 2 | use brack_common::location::Location; 3 | use brack_common::tokens::Token; 4 | 5 | use crate::{expr, newline, parser::Parser}; 6 | 7 | // curly_bracket_open (expr | newline)* curly_bracket_close? 8 | pub fn parse(tokens: &[Token]) -> Option { 9 | let mut result = new_curly(); 10 | let mut tokens = tokens; 11 | 12 | let bracket_open_location = if let Some(token) = tokens.first() { 13 | token.get_location() 14 | } else { 15 | return None; 16 | }; 17 | 18 | if let Some((cst, new_tokens)) = parse_curly_bracket_open(tokens) { 19 | result.add(cst); 20 | tokens = new_tokens; 21 | } else { 22 | return None; 23 | } 24 | 25 | loop { 26 | if let Some((cst, new_tokens)) = expr::parse(tokens) { 27 | result.add(cst); 28 | tokens = new_tokens; 29 | } else if let Some((cst, new_tokens)) = newline::parse(tokens) { 30 | result.add(cst); 31 | tokens = new_tokens; 32 | } else { 33 | break; 34 | } 35 | } 36 | 37 | let bracket_close_location = if let Some(token) = tokens.first() { 38 | token.get_location() 39 | } else { 40 | return None; 41 | }; 42 | 43 | let tokens = if let Some((cst, tokens)) = parse_curly_bracket_close(tokens) { 44 | result.add(cst); 45 | tokens 46 | } else { 47 | tokens 48 | }; 49 | 50 | result.set_location(Location { 51 | start: bracket_open_location.start, 52 | end: bracket_close_location.end, 53 | }); 54 | 55 | Some((result, tokens)) 56 | } 57 | 58 | // curly_bracket_open 59 | fn parse_curly_bracket_open(tokens: &[Token]) -> Option { 60 | if let Some(token) = tokens.first() { 61 | match token { 62 | Token::CurlyBracketOpen(location) => { 63 | return Some((new_curly_bracket_open(location.clone()), &tokens[1..])); 64 | } 65 | _ => return None, 66 | } 67 | } 68 | None 69 | } 70 | 71 | // curly_bracket_close 72 | fn parse_curly_bracket_close(tokens: &[Token]) -> Option { 73 | if let Some(token) = tokens.first() { 74 | match token { 75 | Token::CurlyBracketClose(location) => { 76 | return Some((new_curly_bracket_close(location.clone()), &tokens[1..])); 77 | } 78 | _ => return None, 79 | } 80 | } 81 | None 82 | } 83 | -------------------------------------------------------------------------------- /crates/brack-parser/src/dot.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_dot; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::parser::Parser; 5 | 6 | // dot = '.' 7 | pub fn parse(tokens: &[Token]) -> Option { 8 | if let Some(token) = tokens.first() { 9 | match token { 10 | Token::Dot(location) => { 11 | return Some((new_dot(location.clone()), &tokens[1..])); 12 | } 13 | _ => return None, 14 | } 15 | } 16 | None 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use brack_common::cst::{matches_kind, new_dot}; 22 | use brack_common::location::mock_location; 23 | use brack_common::tokens::Token; 24 | 25 | #[test] 26 | fn test_dot_parse_only_dot() { 27 | let tokens = vec![Token::Dot(mock_location())]; 28 | if let Some((cst, tokens)) = super::parse(&tokens) { 29 | assert_eq!(tokens.len(), 0); 30 | assert!(matches_kind(&cst, &new_dot(mock_location()))); 31 | } else { 32 | panic!("Expected to parse a dot token"); 33 | } 34 | } 35 | 36 | #[test] 37 | fn test_dot_parse_failures() { 38 | let tokens = vec![]; 39 | assert!(super::parse(&tokens).is_none()); 40 | 41 | let tokens = vec![Token::AngleBracketOpen(mock_location())]; 42 | assert!(super::parse(&tokens).is_none()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/brack-parser/src/eof.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_eof; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::parser::Parser; 5 | 6 | // EOF 7 | pub fn parse(tokens: &[Token]) -> Option { 8 | if let Some(token) = tokens.first() { 9 | match token { 10 | Token::EOF(location) => { 11 | return Some((new_eof(location.clone()), &tokens[1..])); 12 | } 13 | _ => return None, 14 | } 15 | } 16 | None 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use brack_common::cst::{matches_kind, new_eof}; 22 | use brack_common::location::mock_location; 23 | use brack_common::tokens::Token; 24 | 25 | #[test] 26 | fn test_eof_parse_only_eof() { 27 | let tokens = vec![Token::EOF(mock_location())]; 28 | if let Some((cst, tokens)) = super::parse(&tokens) { 29 | assert_eq!(tokens.len(), 0); 30 | assert!(matches_kind(&cst, &new_eof(mock_location()))); 31 | } else { 32 | panic!("Expected to parse an EOF token"); 33 | } 34 | } 35 | 36 | #[test] 37 | fn test_eof_parse_failure() { 38 | let tokens = vec![]; 39 | let result = super::parse(&tokens); 40 | assert!(result.is_none()); 41 | 42 | let tokens = vec![Token::AngleBracketOpen(mock_location())]; 43 | let result = super::parse(&tokens); 44 | assert!(result.is_none()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/brack-parser/src/expr.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_expr; 2 | use brack_common::location::Location; 3 | use brack_common::tokens::Token; 4 | 5 | use crate::{bracket, comma, dot, escaped, ident, modules, parser::Parser, text, whitespace}; 6 | 7 | // (escaped | module | ident | bracket | dot | comma | whitespace | text)+ 8 | pub fn parse(tokens: &[Token]) -> Option { 9 | let mut tokens = tokens; 10 | let mut expr = new_expr(); 11 | 12 | loop { 13 | if let Some((cst, new_tokens)) = escaped::parse(tokens) { 14 | expr.add(cst); 15 | tokens = new_tokens; 16 | } else if let Some((cst, new_tokens)) = modules::parse(tokens) { 17 | expr.add(cst); 18 | tokens = new_tokens; 19 | } else if let Some((cst, new_tokens)) = ident::parse(tokens) { 20 | expr.add(cst); 21 | tokens = new_tokens; 22 | } else if let Some((cst, new_tokens)) = bracket::parse(tokens) { 23 | expr.add(cst); 24 | tokens = new_tokens; 25 | } else if let Some((cst, new_tokens)) = dot::parse(tokens) { 26 | expr.add(cst); 27 | tokens = new_tokens; 28 | } else if let Some((cst, new_tokens)) = comma::parse(tokens) { 29 | expr.add(cst); 30 | tokens = new_tokens; 31 | } else if let Some((cst, new_tokens)) = whitespace::parse(tokens) { 32 | expr.add(cst); 33 | tokens = new_tokens; 34 | } else if let Some((cst, new_tokens)) = text::parse(tokens) { 35 | expr.add(cst); 36 | tokens = new_tokens; 37 | } else { 38 | break; 39 | } 40 | } 41 | 42 | if expr.children().is_empty() { 43 | return None; 44 | } 45 | 46 | expr.set_location(Location { 47 | start: expr.children().first().unwrap().location().start, 48 | end: expr.children().last().unwrap().location().end, 49 | }); 50 | Some((expr, tokens)) 51 | } 52 | -------------------------------------------------------------------------------- /crates/brack-parser/src/expr_or_close.rs: -------------------------------------------------------------------------------- 1 | use brack_common::tokens::Token; 2 | 3 | use crate::{bracket_close, expr, parser::Parser}; 4 | 5 | // expr | bracket_close 6 | pub fn parse(tokens: &[Token]) -> Option { 7 | if let Some((cst, new_tokens)) = expr::parse(tokens) { 8 | return Some((cst, new_tokens)); 9 | } else if let Some((cst, new_tokens)) = bracket_close::parse(tokens) { 10 | return Some((cst, new_tokens)); 11 | } 12 | None 13 | } 14 | -------------------------------------------------------------------------------- /crates/brack-parser/src/ident.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_ident; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::parser::Parser; 5 | 6 | // ident 7 | pub fn parse(tokens: &[Token]) -> Option { 8 | if let Some(token) = tokens.first() { 9 | match token { 10 | Token::Ident(text, location) => { 11 | return Some((new_ident(text.clone(), location.clone()), &tokens[1..])); 12 | } 13 | _ => return None, 14 | } 15 | } 16 | None 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use brack_common::cst::{matches_kind, new_ident}; 22 | use brack_common::location::mock_location; 23 | use brack_common::tokens::Token; 24 | 25 | #[test] 26 | fn test_ident_parse_only_ident() { 27 | let tokens = vec![Token::Ident("foo".to_string(), mock_location())]; 28 | if let Some((cst, tokens)) = super::parse(&tokens) { 29 | assert_eq!(tokens.len(), 0); 30 | assert!(matches_kind( 31 | &cst, 32 | &new_ident("foo".to_string(), mock_location()) 33 | )); 34 | } else { 35 | panic!("Expected to parse an identifier"); 36 | } 37 | } 38 | 39 | #[test] 40 | fn test_ident_parse_failure() { 41 | let tokens = vec![Token::Dot(mock_location())]; 42 | let result = super::parse(&tokens); 43 | assert!(result.is_none()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/brack-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod angle; 2 | mod bracket; 3 | mod bracket_close; 4 | mod comma; 5 | mod curly; 6 | mod dot; 7 | mod eof; 8 | mod escaped; 9 | mod expr; 10 | mod expr_or_close; 11 | mod ident; 12 | mod modules; 13 | mod newline; 14 | pub mod parse; 15 | mod parser; 16 | mod square; 17 | mod stmt; 18 | mod text; 19 | mod whitespace; 20 | -------------------------------------------------------------------------------- /crates/brack-parser/src/modules.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_module; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::parser::Parser; 5 | 6 | // text 7 | pub fn parse(tokens: &[Token]) -> Option { 8 | if let Some(token) = tokens.first() { 9 | match token { 10 | Token::Module(text, location) => { 11 | return Some((new_module(text.clone(), location.clone()), &tokens[1..])); 12 | } 13 | _ => return None, 14 | } 15 | } 16 | None 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use brack_common::cst::{matches_kind, new_module}; 22 | use brack_common::location::mock_location; 23 | use brack_common::tokens::Token; 24 | 25 | #[test] 26 | fn test_module_parse_only_module() { 27 | let tokens = vec![Token::Module("module".to_string(), mock_location())]; 28 | if let Some((cst, tokens)) = super::parse(&tokens) { 29 | assert_eq!(tokens.len(), 0); 30 | assert!(matches_kind( 31 | &cst, 32 | &new_module("module".to_string(), mock_location()) 33 | )); 34 | } else { 35 | panic!("Expected to parse a module token"); 36 | } 37 | } 38 | 39 | #[test] 40 | fn test_module_parse_failure() { 41 | let tokens = vec![Token::Dot(mock_location())]; 42 | let result = super::parse(&tokens); 43 | assert!(result.is_none()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/brack-parser/src/newline.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_newline; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::parser::Parser; 5 | 6 | // newline = '\n' 7 | pub fn parse(tokens: &[Token]) -> Option { 8 | if let Some(token) = tokens.first() { 9 | match token { 10 | Token::NewLine(location) => { 11 | return Some((new_newline(location.clone()), &tokens[1..])); 12 | } 13 | _ => return None, 14 | } 15 | } 16 | None 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use brack_common::cst::{matches_kind, new_newline}; 22 | use brack_common::location::mock_location; 23 | use brack_common::tokens::Token; 24 | 25 | #[test] 26 | fn test_newline_parse_only_newline() { 27 | let tokens = vec![Token::NewLine(mock_location())]; 28 | if let Some((cst, tokens)) = super::parse(&tokens) { 29 | assert_eq!(tokens.len(), 0); 30 | assert!(matches_kind(&cst, &new_newline(mock_location()))); 31 | } else { 32 | panic!("Expected to parse a newline token"); 33 | } 34 | } 35 | 36 | #[test] 37 | fn test_newline_parse_failure() { 38 | let tokens = vec![Token::Dot(mock_location())]; 39 | let result = super::parse(&tokens); 40 | assert!(result.is_none()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/brack-parser/src/parse.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_document, CST}; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::{eof, newline, stmt}; 5 | 6 | // (stmt newline newline+)* stmt? newline* EOF 7 | pub fn parse(tokens: &[Token]) -> CST { 8 | let mut tokens = tokens; 9 | let mut cst = new_document(); 10 | 11 | loop { 12 | let mut csts = vec![]; 13 | let mut tokens1 = tokens; 14 | 15 | if let Some((cst1, new_tokens)) = stmt::parse(tokens1) { 16 | tokens1 = new_tokens; 17 | csts.push(cst1); 18 | } else { 19 | break; 20 | } 21 | 22 | if let Some((cst2, new_tokens)) = newline::parse(tokens1) { 23 | tokens1 = new_tokens; 24 | csts.push(cst2); 25 | } else { 26 | break; 27 | } 28 | 29 | while let Some((cst3, new_tokens)) = newline::parse(tokens1) { 30 | tokens1 = new_tokens; 31 | csts.push(cst3); 32 | } 33 | 34 | tokens = tokens1; 35 | for cst1 in csts { 36 | cst.add(cst1); 37 | } 38 | } 39 | 40 | if let Some((cst1, new_tokens)) = stmt::parse(tokens) { 41 | cst.add(cst1); 42 | tokens = new_tokens; 43 | } 44 | 45 | while let Some((cst1, new_tokens)) = newline::parse(tokens) { 46 | cst.add(cst1); 47 | tokens = new_tokens; 48 | } 49 | 50 | if let Some((cst1, _)) = eof::parse(tokens) { 51 | cst.add(cst1); 52 | } else { 53 | panic!("EOF not found"); 54 | } 55 | 56 | cst 57 | } 58 | -------------------------------------------------------------------------------- /crates/brack-parser/src/parser.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::CST; 2 | use brack_common::tokens::Token; 3 | 4 | pub type Parser<'a> = (CST, &'a [Token]); 5 | -------------------------------------------------------------------------------- /crates/brack-parser/src/square.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_square, new_square_bracket_close, new_square_bracket_open}; 2 | use brack_common::location::Location; 3 | use brack_common::tokens::Token; 4 | 5 | use crate::{expr, newline, parser::Parser}; 6 | 7 | // square_bracket_open (expr | newline)* square_bracket_close? 8 | pub fn parse(tokens: &[Token]) -> Option { 9 | let mut result = new_square(); 10 | 11 | let bracket_open_location = if let Some(token) = tokens.first() { 12 | token.get_location() 13 | } else { 14 | return None; 15 | }; 16 | 17 | let (cst, mut tokens) = parse_square_bracket_open(tokens)?; 18 | result.add(cst); 19 | 20 | loop { 21 | if let Some((cst, new_tokens)) = expr::parse(tokens) { 22 | result.add(cst); 23 | tokens = new_tokens; 24 | } else if let Some((cst, new_tokens)) = newline::parse(tokens) { 25 | result.add(cst); 26 | tokens = new_tokens; 27 | } else { 28 | break; 29 | } 30 | } 31 | 32 | let bracket_close_location = if let Some(token) = tokens.first() { 33 | token.get_location() 34 | } else { 35 | return None; 36 | }; 37 | 38 | let tokens = if let Some((cst, tokens)) = parse_square_bracket_close(tokens) { 39 | result.add(cst); 40 | tokens 41 | } else { 42 | tokens 43 | }; 44 | 45 | result.set_location(Location { 46 | start: bracket_open_location.start, 47 | end: bracket_close_location.end, 48 | }); 49 | 50 | Some((result, tokens)) 51 | } 52 | 53 | // square_bracket_open 54 | fn parse_square_bracket_open(tokens: &[Token]) -> Option { 55 | if let Some(token) = tokens.first() { 56 | match token { 57 | Token::SquareBracketOpen(location) => { 58 | return Some((new_square_bracket_open(location.clone()), &tokens[1..])); 59 | } 60 | _ => return None, 61 | } 62 | } 63 | None 64 | } 65 | 66 | // square_bracket_close 67 | fn parse_square_bracket_close(tokens: &[Token]) -> Option { 68 | if let Some(token) = tokens.first() { 69 | match token { 70 | Token::SquareBracketClose(location) => { 71 | return Some((new_square_bracket_close(location.clone()), &tokens[1..])); 72 | } 73 | _ => return None, 74 | } 75 | } 76 | None 77 | } 78 | -------------------------------------------------------------------------------- /crates/brack-parser/src/stmt.rs: -------------------------------------------------------------------------------- 1 | use crate::{expr_or_close, newline, parser::Parser}; 2 | use brack_common::cst::new_stmt; 3 | use brack_common::tokens::Token; 4 | 5 | // expr_or_close (newline expr_or_close)* 6 | pub fn parse(tokens: &[Token]) -> Option { 7 | let mut stmt = new_stmt(); 8 | let (cst, mut tokens) = expr_or_close::parse(tokens)?; 9 | stmt.add(cst); 10 | 11 | loop { 12 | if let Some((cst1, new_tokens)) = newline::parse(tokens) { 13 | if let Some((cst2, new_tokens)) = expr_or_close::parse(new_tokens) { 14 | stmt.add(cst1); 15 | stmt.add(cst2); 16 | tokens = new_tokens; 17 | continue; 18 | } 19 | } 20 | break; 21 | } 22 | 23 | Some((stmt, tokens)) 24 | } 25 | -------------------------------------------------------------------------------- /crates/brack-parser/src/text.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_text; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::parser::Parser; 5 | 6 | // text 7 | pub fn parse(tokens: &[Token]) -> Option { 8 | if let Some(token) = tokens.first() { 9 | match token { 10 | Token::Text(text, location) => { 11 | return Some((new_text(text.clone(), location.clone()), &tokens[1..])); 12 | } 13 | _ => return None, 14 | } 15 | } 16 | None 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use brack_common::cst::{matches_kind, new_text}; 22 | use brack_common::location::mock_location; 23 | use brack_common::tokens::Token; 24 | 25 | #[test] 26 | fn test_text_parse_only_text() { 27 | let tokens = vec![Token::Text("text".to_string(), mock_location())]; 28 | if let Some((cst, tokens)) = super::parse(&tokens) { 29 | assert_eq!(tokens.len(), 0); 30 | assert!(matches_kind( 31 | &cst, 32 | &new_text("text".to_string(), mock_location()) 33 | )); 34 | } else { 35 | panic!("Expected to parse a text token"); 36 | } 37 | } 38 | 39 | #[test] 40 | fn test_text_parse_failure() { 41 | let tokens = vec![Token::Dot(mock_location())]; 42 | let result = super::parse(&tokens); 43 | assert!(result.is_none()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/brack-parser/src/whitespace.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::new_whitespace; 2 | use brack_common::tokens::Token; 3 | 4 | use crate::parser::Parser; 5 | 6 | // whitespace 7 | pub fn parse(tokens: &[Token]) -> Option { 8 | if let Some(token) = tokens.first() { 9 | match token { 10 | Token::WhiteSpace(location) => { 11 | return Some((new_whitespace(location.clone()), &tokens[1..])); 12 | } 13 | _ => return None, 14 | } 15 | } 16 | None 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use brack_common::cst::{matches_kind, new_whitespace}; 22 | use brack_common::location::mock_location; 23 | use brack_common::tokens::Token; 24 | 25 | #[test] 26 | fn test_whitespace_parse_only_whitespace() { 27 | let tokens = vec![Token::WhiteSpace(mock_location())]; 28 | if let Some((cst, tokens)) = super::parse(&tokens) { 29 | assert_eq!(tokens.len(), 0); 30 | assert!(matches_kind(&cst, &new_whitespace(mock_location()))); 31 | } else { 32 | panic!("Expected to parse a whitespace token"); 33 | } 34 | } 35 | 36 | #[test] 37 | fn test_whitespace_parse_failure() { 38 | let tokens = vec![Token::Dot(mock_location())]; 39 | let result = super::parse(&tokens); 40 | assert!(result.is_none()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/brack-plugin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-plugin" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.98" 10 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 11 | extism = "1.11.1" 12 | serde = { version = "1.0.219", features = ["derive"] } 13 | serde_json = "1.0.134" 14 | extism-convert = { version = "1.11.1" } 15 | 16 | -------------------------------------------------------------------------------- /crates/brack-plugin/src/feature_flag.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 4 | pub struct FeatureFlag { 5 | pub document_hook: bool, 6 | pub stmt_hook: bool, 7 | pub expr_hook: bool, 8 | pub text_hook: bool, 9 | } 10 | -------------------------------------------------------------------------------- /crates/brack-plugin/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod feature_flag; 2 | pub mod metadata; 3 | pub mod plugin; 4 | pub mod plugins; 5 | pub mod types; 6 | pub mod value; 7 | -------------------------------------------------------------------------------- /crates/brack-plugin/src/metadata.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::types::Type; 4 | 5 | #[derive(Debug, Serialize, Deserialize, Clone)] 6 | pub struct Metadata { 7 | pub command_name: String, 8 | pub call_name: String, 9 | pub argument_types: Vec<(String, Type)>, 10 | pub return_type: Type, 11 | } 12 | -------------------------------------------------------------------------------- /crates/brack-plugin/src/plugin.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | fs::{self}, 4 | path::Path, 5 | }; 6 | 7 | use crate::{feature_flag::FeatureFlag, metadata::Metadata, types::Type}; 8 | use anyhow::Result; 9 | use extism::{FromBytes, Plugin as ExtismPlugin, ToBytes}; 10 | use extism_convert::Json; 11 | 12 | #[derive(Debug)] 13 | pub struct Plugin { 14 | pub name: String, 15 | pub(crate) extism_plugin: ExtismPlugin, 16 | pub signature_to_metadata: HashMap<(String, Type), Metadata>, 17 | pub(crate) feature_flag: FeatureFlag, 18 | } 19 | 20 | impl Plugin { 21 | pub fn new>( 22 | name: &str, 23 | wasm_bin_path: P, 24 | feature_flag: FeatureFlag, 25 | ) -> Result { 26 | let wasm_bin = fs::read(wasm_bin_path)?; 27 | let mut extism_plugin = ExtismPlugin::new(wasm_bin, [], true)?; 28 | let Json(metadatas) = extism_plugin.call::<(), Json>>("get_metadata", ())?; 29 | let mut signature_to_metadata = HashMap::new(); 30 | 31 | let mut exists_document_hook = false; 32 | let mut exists_stmt_hook = false; 33 | let mut exists_expr_hook = false; 34 | let mut exists_text_hook = false; 35 | 36 | for metadata in metadatas { 37 | let command_name = metadata.command_name.clone(); 38 | let return_type = metadata.return_type.clone(); 39 | if command_name == "document" && feature_flag.document_hook { 40 | if return_type != Type::TBlock { 41 | return Err(anyhow::anyhow!("document hook must return TBlock")); 42 | } 43 | exists_document_hook = true; 44 | } 45 | if command_name == "stmt" && feature_flag.stmt_hook { 46 | if return_type != Type::TBlock { 47 | return Err(anyhow::anyhow!("stmt hook must return TBlock")); 48 | } 49 | exists_stmt_hook = true; 50 | } 51 | if command_name == "expr" && feature_flag.expr_hook { 52 | if return_type != Type::TInline { 53 | return Err(anyhow::anyhow!("expr hook must return TInline")); 54 | } 55 | exists_expr_hook = true; 56 | } 57 | if command_name == "text" && feature_flag.text_hook { 58 | if return_type != Type::TInline { 59 | return Err(anyhow::anyhow!("text hook must return TInline")); 60 | } 61 | exists_text_hook = true; 62 | } 63 | signature_to_metadata.insert((command_name, return_type), metadata); 64 | } 65 | 66 | if feature_flag.document_hook && !exists_document_hook { 67 | return Err(anyhow::anyhow!("document hook not found")); 68 | } 69 | if feature_flag.stmt_hook && !exists_stmt_hook { 70 | return Err(anyhow::anyhow!("stmt hook not found")); 71 | } 72 | if feature_flag.expr_hook && !exists_expr_hook { 73 | return Err(anyhow::anyhow!("expr hook not found")); 74 | } 75 | if feature_flag.text_hook && !exists_text_hook { 76 | return Err(anyhow::anyhow!("text hook not found")); 77 | } 78 | 79 | Ok(Self { 80 | name: name.to_string(), 81 | extism_plugin, 82 | signature_to_metadata, 83 | feature_flag, 84 | }) 85 | } 86 | 87 | pub(crate) fn call ToBytes<'a>, U: for<'a> FromBytes<'a>>( 88 | &mut self, 89 | command_name: &str, 90 | return_type: Type, 91 | args: T, 92 | ) -> Result { 93 | let metadata = self 94 | .signature_to_metadata 95 | .get(&(command_name.to_string(), return_type)) 96 | .ok_or_else(|| anyhow::anyhow!("metadata not found: {}", command_name))?; 97 | let result = self 98 | .extism_plugin 99 | .call::(metadata.call_name.clone(), args)?; 100 | Ok(result) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/brack-plugin/src/plugins.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use anyhow::Result; 4 | use brack_common::ast::AST; 5 | use extism::{FromBytes, ToBytes}; 6 | use extism_convert::Json; 7 | 8 | use crate::{plugin::Plugin, types::Type, value::Value}; 9 | 10 | pub struct Plugins { 11 | pub name_to_plugin: HashMap, 12 | document_hook_plugin_name: Option, 13 | stmt_hook_plugin_name: Option, 14 | expr_hook_plugin_name: Option, 15 | text_hook_plugin_name: Option, 16 | } 17 | 18 | impl Plugins { 19 | pub fn new(plugins: Vec) -> Result { 20 | let mut name_to_plugin = HashMap::new(); 21 | let mut document_hook_plugin_name = None; 22 | let mut stmt_hook_plugin_name = None; 23 | let mut expr_hook_plugin_name = None; 24 | let mut text_hook_plugin_name = None; 25 | 26 | for plugin in plugins { 27 | let name = plugin.name.clone(); 28 | if plugin.feature_flag.document_hook { 29 | if document_hook_plugin_name.is_some() { 30 | return Err(anyhow::anyhow!("only one document hook is allowed")); 31 | } 32 | document_hook_plugin_name = Some(name.clone()); 33 | } 34 | if plugin.feature_flag.stmt_hook { 35 | if stmt_hook_plugin_name.is_some() { 36 | return Err(anyhow::anyhow!("only one stmt hook is allowed")); 37 | } 38 | stmt_hook_plugin_name = Some(name.clone()); 39 | } 40 | if plugin.feature_flag.expr_hook { 41 | if expr_hook_plugin_name.is_some() { 42 | return Err(anyhow::anyhow!("only one expr hook is allowed")); 43 | } 44 | expr_hook_plugin_name = Some(name.clone()); 45 | } 46 | if plugin.feature_flag.text_hook { 47 | if text_hook_plugin_name.is_some() { 48 | return Err(anyhow::anyhow!("only one text hook is allowed")); 49 | } 50 | text_hook_plugin_name = Some(name.clone()); 51 | } 52 | name_to_plugin.insert(name, plugin); 53 | } 54 | 55 | Ok(Self { 56 | name_to_plugin, 57 | document_hook_plugin_name, 58 | stmt_hook_plugin_name, 59 | expr_hook_plugin_name, 60 | text_hook_plugin_name, 61 | }) 62 | } 63 | 64 | pub fn argument_types( 65 | &self, 66 | module_name: &str, 67 | command_name: &str, 68 | typ: Type, 69 | ) -> Result> { 70 | let plugin = self 71 | .name_to_plugin 72 | .get(module_name) 73 | .ok_or_else(|| anyhow::anyhow!("plugin not found: {}", module_name))?; 74 | let metadata = plugin 75 | .signature_to_metadata 76 | .get(&(command_name.to_string(), typ)) 77 | .ok_or_else(|| anyhow::anyhow!("command not found: {}", command_name))?; 78 | Ok(metadata.argument_types.clone()) 79 | } 80 | 81 | fn call ToBytes<'a>, U: for<'a> FromBytes<'a>>( 82 | &mut self, 83 | plugin_name: &str, 84 | command_name: &str, 85 | return_type: Type, 86 | args: T, 87 | ) -> Result { 88 | let plugin = self 89 | .name_to_plugin 90 | .get_mut(plugin_name) 91 | .ok_or_else(|| anyhow::anyhow!("plugin not found: {}", plugin_name))?; 92 | let result = plugin.call(command_name, return_type, args)?; 93 | Ok(result) 94 | } 95 | 96 | pub fn call_inline_command( 97 | &mut self, 98 | plugin_name: &str, 99 | command_name: &str, 100 | args: Vec, 101 | ) -> Result { 102 | let result = self.call::>, String>( 103 | plugin_name, 104 | command_name, 105 | Type::TInline, 106 | Json(args), 107 | )?; 108 | Ok(result) 109 | } 110 | 111 | pub fn call_block_command( 112 | &mut self, 113 | plugin_name: &str, 114 | command_name: &str, 115 | args: Vec, 116 | ) -> Result { 117 | let result = self.call::>, String>( 118 | plugin_name, 119 | command_name, 120 | Type::TBlock, 121 | Json(args), 122 | )?; 123 | Ok(result) 124 | } 125 | 126 | pub fn call_macro_command( 127 | &mut self, 128 | plugin_name: &str, 129 | command_name: &str, 130 | ast: AST, 131 | id: String, 132 | ) -> Result { 133 | let result = self.call::, Json>( 134 | plugin_name, 135 | command_name, 136 | Type::TAST, 137 | Json((ast, id)), 138 | )?; 139 | let Json(ast) = result; 140 | Ok(ast) 141 | } 142 | 143 | pub fn call_document_hook(&mut self, args: Vec) -> Result> { 144 | let document_hook_plugin_name = self.document_hook_plugin_name.clone(); 145 | if let Some(plugin_name) = document_hook_plugin_name { 146 | let result = self.call_block_command(&plugin_name, "document", args)?; 147 | return Ok(Some(result)); 148 | } 149 | Ok(None) 150 | } 151 | 152 | pub fn call_stmt_hook(&mut self, args: Vec) -> Result> { 153 | let stmt_hook_plugin_name = self.stmt_hook_plugin_name.clone(); 154 | if let Some(plugin_name) = stmt_hook_plugin_name { 155 | let result = self.call_block_command(&plugin_name, "stmt", args)?; 156 | return Ok(Some(result)); 157 | } 158 | Ok(None) 159 | } 160 | 161 | pub fn call_expr_hook(&mut self, args: Vec) -> Result> { 162 | let expr_hook_plugin_name = self.expr_hook_plugin_name.clone(); 163 | if let Some(plugin_name) = expr_hook_plugin_name { 164 | let result = self.call_inline_command(&plugin_name, "expr", args)?; 165 | return Ok(Some(result)); 166 | } 167 | Ok(None) 168 | } 169 | 170 | pub fn call_text_hook(&mut self, args: Vec) -> Result> { 171 | let text_hook_plugin_name = self.text_hook_plugin_name.clone(); 172 | if let Some(plugin_name) = text_hook_plugin_name { 173 | let result = self.call_inline_command(&plugin_name, "text", args)?; 174 | return Ok(Some(result)); 175 | } 176 | Ok(None) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /crates/brack-plugin/src/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] 4 | pub enum Type { 5 | TInline, 6 | TOption(Box), 7 | TBlock, 8 | TArray(Box), 9 | TInlineCmd(String), 10 | TBlockCmd(String), 11 | TAST, 12 | } 13 | 14 | pub fn arg_counter(arg_types: &Vec) -> (usize, usize) { 15 | let mut min = 0; 16 | let mut max = 0; 17 | for arg_type in arg_types { 18 | match arg_type { 19 | Type::TOption(_) => { 20 | min += 0; 21 | max += 1; 22 | } 23 | Type::TArray(_) => { 24 | min += 0; 25 | max = usize::MAX; 26 | } 27 | _ => { 28 | min += 1; 29 | max += 1; 30 | } 31 | } 32 | } 33 | (min, max) 34 | } 35 | -------------------------------------------------------------------------------- /crates/brack-plugin/src/value.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone)] 4 | pub enum Value { 5 | Text(String), 6 | TextArray(Vec), 7 | TextOption(Option), 8 | } 9 | -------------------------------------------------------------------------------- /crates/brack-plugin/test.html.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brack-lang/brack/b81446911dc97a419d0e421bcc0d1a9e718a8bfd/crates/brack-plugin/test.html.wasm -------------------------------------------------------------------------------- /crates/brack-project/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-project" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.98" 10 | reqwest = { version = "0.12.19", features = ["json", "stream"] } 11 | serde = { version = "1.0.219", features = ["derive"] } 12 | tokio = { version = "1.45.1", features = ["macros"] } 13 | toml = "0.8.22" 14 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 15 | brack-plugin = { git = "https://github.com/brack-lang/brack", package = "brack-plugin" } 16 | brack-tokenizer = { git = "https://github.com/brack-lang/brack", package = "brack-tokenizer" } 17 | brack-parser = { git = "https://github.com/brack-lang/brack", package = "brack-parser" } 18 | brack-expander = { git = "https://github.com/brack-lang/brack", package = "brack-expander" } 19 | brack-transformer = { git = "https://github.com/brack-lang/brack", package = "brack-transformer" } 20 | brack-codegen = { git = "https://github.com/brack-lang/brack", package = "brack-codegen" } 21 | futures = "0.3.31" 22 | sha2 = "0.10.9" 23 | bytes = "1.10.1" 24 | walkdir = "2.5.0" 25 | indicatif = "0.17.11" 26 | futures-util = "0.3" 27 | tar = "0.4.44" 28 | colored = "3.0.0" 29 | 30 | [features] 31 | default = [] 32 | debug = [] 33 | 34 | [[bin]] 35 | name = "debug_download_plugin" 36 | path = "src/debug/debug_download_plugin.rs" 37 | required-features = ["debug"] 38 | 39 | [[bin]] 40 | name = "debug_build" 41 | path = "src/debug/debug_build.rs" 42 | required-features = ["debug"] 43 | -------------------------------------------------------------------------------- /crates/brack-project/src/debug/debug_build.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_project_manager::project::{self, Project}; 3 | use tokio; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<()> { 7 | let mut project = Project::new(); 8 | project.load_brack_toml()?; 9 | project.download_plugins_using_config().await?; 10 | project.build()?; 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /crates/brack-project/src/debug/debug_download_plugin.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use brack_project_manager::project::Project; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<()> { 6 | let mut project = Project::new(); 7 | project.load_brack_toml()?; 8 | project.clear_plugins()?; 9 | project.download_plugins_using_config().await?; 10 | dbg!(&project.plugins_metadata); 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /crates/brack-project/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod manifest; 2 | pub mod project; 3 | -------------------------------------------------------------------------------- /crates/brack-project/src/manifest.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Serialize, Deserialize, Default)] 5 | pub struct Manifest { 6 | pub name: String, 7 | pub version: String, 8 | pub authors: Option>, 9 | pub licenses: Option>, 10 | pub dependencies: Option>, 11 | pub channels: Option>, 12 | pub document: Option, 13 | pub plugin: Option, 14 | } 15 | 16 | #[derive(Serialize, Deserialize)] 17 | pub struct Author { 18 | pub name: String, 19 | pub email: String, 20 | } 21 | 22 | #[derive(Serialize, Deserialize, Clone)] 23 | pub enum License { 24 | Apache20, 25 | MIT, 26 | } 27 | 28 | #[derive(Serialize, Deserialize, Clone)] 29 | pub enum Dependency { 30 | Version(String), 31 | Channel { 32 | channel: Option, 33 | version: String, 34 | feature_flags: Option>, 35 | }, 36 | GitHub { 37 | owner: String, 38 | name: String, 39 | tag: String, 40 | feature_flags: Option>, 41 | }, 42 | Local { 43 | path: String, 44 | feature_flags: Option>, 45 | }, 46 | } 47 | 48 | #[derive(Serialize, Deserialize)] 49 | pub enum OutputLevel { 50 | Token, 51 | Cst, 52 | Ast, 53 | ExpandedAst, 54 | IR, 55 | FinalOutput, 56 | } 57 | 58 | #[derive(Serialize, Deserialize)] 59 | pub enum OutputFormat { 60 | Json, 61 | Text, 62 | } 63 | 64 | #[derive(Serialize, Deserialize)] 65 | pub struct DocumentSettings { 66 | // FIXME: it will change into enum because we implement specific code generator for each backend 67 | pub target: String, 68 | pub output_level: Option, 69 | pub output_format: Option, 70 | pub src: String, 71 | } 72 | 73 | #[derive(Serialize, Deserialize)] 74 | pub enum CommandType { 75 | Inline, 76 | Block, 77 | AST, 78 | } 79 | 80 | #[derive(Serialize, Deserialize)] 81 | pub struct CommandParam { 82 | pub name: String, 83 | 84 | #[serde(rename = "type")] 85 | pub type_: CommandType, 86 | } 87 | 88 | #[derive(Serialize, Deserialize)] 89 | pub struct Command { 90 | pub callee: String, 91 | pub params: Vec, 92 | pub return_type: Option, 93 | } 94 | 95 | #[derive(Serialize, Deserialize)] 96 | pub struct PluginSettings { 97 | pub support_backends: Vec, 98 | pub inlines: Option>, 99 | pub blocks: Option>, 100 | pub macros: Option>, 101 | } 102 | -------------------------------------------------------------------------------- /crates/brack-release/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-release" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.98" 8 | clap = { version = "4.5.39", features = ["derive"] } 9 | tokio = { version = "1", features = ["full"] } 10 | toml_edit = "0.22.25" 11 | 12 | -------------------------------------------------------------------------------- /crates/brack-release/src/git.rs: -------------------------------------------------------------------------------- 1 | use crate::semver::SemVer; 2 | use anyhow::{bail, Result}; 3 | use tokio::process::Command; 4 | 5 | pub async fn git_switch(branch: &str) -> Result<()> { 6 | let status = Command::new("git") 7 | .arg("switch") 8 | .arg(branch) 9 | .status() 10 | .await?; 11 | if !status.success() { 12 | bail!("Failed to switch branch to '{}'", branch); 13 | } 14 | Ok(()) 15 | } 16 | 17 | pub async fn git_switch_new(branch: &str) -> Result<()> { 18 | let status = Command::new("git") 19 | .arg("switch") 20 | .arg("-c") 21 | .arg(branch) 22 | .status() 23 | .await?; 24 | if !status.success() { 25 | bail!("Failed to create and switch branch to '{}'", branch); 26 | } 27 | Ok(()) 28 | } 29 | 30 | pub async fn git_pull(remote: &str, branch: &str) -> Result<()> { 31 | let status = Command::new("git") 32 | .arg("pull") 33 | .arg(remote) 34 | .arg(branch) 35 | .status() 36 | .await?; 37 | if !status.success() { 38 | bail!("Failed to pull {} {}", remote, branch); 39 | } 40 | Ok(()) 41 | } 42 | 43 | pub async fn git_merge_no_ff(branch: &str) -> Result<()> { 44 | let status = Command::new("git") 45 | .arg("merge") 46 | .arg("--no-ff") 47 | .arg(branch) 48 | .status() 49 | .await?; 50 | if !status.success() { 51 | bail!( 52 | "Failed to merge branch '{}'. Possibly a merge conflict?", 53 | branch 54 | ); 55 | } 56 | Ok(()) 57 | } 58 | 59 | pub async fn git_commit_all(message: &str) -> Result<()> { 60 | let status = Command::new("git") 61 | .args(["commit", "-am", message]) 62 | .status() 63 | .await?; 64 | if !status.success() { 65 | bail!("Failed to commit. message='{}'", message); 66 | } 67 | Ok(()) 68 | } 69 | 70 | pub async fn git_push(remote: &str, branch: &str) -> Result<()> { 71 | let status = Command::new("git") 72 | .arg("push") 73 | .arg(remote) 74 | .arg(branch) 75 | .status() 76 | .await?; 77 | if !status.success() { 78 | bail!("Failed to push to {}/{}", remote, branch); 79 | } 80 | Ok(()) 81 | } 82 | 83 | pub async fn git_push_tags(remote: &str) -> Result<()> { 84 | let status = Command::new("git") 85 | .arg("push") 86 | .arg(remote) 87 | .arg("--tags") 88 | .status() 89 | .await?; 90 | if !status.success() { 91 | bail!("Failed to push tags to {}", remote); 92 | } 93 | Ok(()) 94 | } 95 | 96 | pub async fn create_git_tag(version: &SemVer) -> Result<()> { 97 | let status = Command::new("git") 98 | .arg("tag") 99 | .arg("-a") 100 | .arg(format!("v{}", version)) 101 | .arg("-m") 102 | .arg(format!("release version: {}", version)) 103 | .status() 104 | .await?; 105 | if !status.success() { 106 | bail!("Failed to create git tag: {:?}", status); 107 | } 108 | Ok(()) 109 | } 110 | -------------------------------------------------------------------------------- /crates/brack-release/src/semver.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::fmt::{Display, Formatter}; 3 | 4 | #[derive(Debug)] 5 | pub struct SemVer { 6 | major: u64, 7 | minor: u64, 8 | patch: u64, 9 | rc: Option, 10 | } 11 | 12 | impl SemVer { 13 | pub fn new(major: u64, minor: u64, patch: u64) -> Self { 14 | Self { 15 | major, 16 | minor, 17 | patch, 18 | rc: None, 19 | } 20 | } 21 | 22 | pub fn new_with_rc(major: u64, minor: u64, patch: u64, rc: u64) -> Self { 23 | Self { 24 | major, 25 | minor, 26 | patch, 27 | rc: Some(rc), 28 | } 29 | } 30 | 31 | pub fn new_with_string(version: &str) -> Result { 32 | // X.Y.Z-rc.A 33 | let mut version_split_rc = version.split("-rc."); 34 | let mut version = version_split_rc 35 | .next() 36 | .ok_or_else(|| anyhow::anyhow!("No version found"))? 37 | .split('.'); 38 | let rc = version_split_rc.next().map(|rc| rc.parse()).transpose()?; 39 | let major = version 40 | .next() 41 | .ok_or_else(|| anyhow::anyhow!("No major version found"))? 42 | .parse()?; 43 | let minor = version 44 | .next() 45 | .ok_or_else(|| anyhow::anyhow!("No minor version found"))? 46 | .parse()?; 47 | let patch = version 48 | .next() 49 | .ok_or_else(|| anyhow::anyhow!("No patch version found"))? 50 | .parse()?; 51 | match rc { 52 | Some(rc) => Ok(Self::new_with_rc(major, minor, patch, rc)), 53 | None => Ok(Self::new(major, minor, patch)), 54 | } 55 | } 56 | 57 | pub fn next_major(&self) -> Result { 58 | match self.rc { 59 | Some(_) => Err(anyhow::anyhow!( 60 | "You have to release before bumping major version" 61 | )), 62 | None => Ok(Self::new_with_rc(self.major + 1, 0, 0, 1)), 63 | } 64 | } 65 | 66 | pub fn next_minor(&self) -> Result { 67 | match self.rc { 68 | Some(_) => Err(anyhow::anyhow!( 69 | "You have to release before bumping minor version" 70 | )), 71 | None => Ok(Self::new_with_rc(self.major, self.minor + 1, 0, 1)), 72 | } 73 | } 74 | 75 | pub fn next_patch(&self) -> Result { 76 | match self.rc { 77 | Some(_) => Err(anyhow::anyhow!( 78 | "You have to release before bumping patch version" 79 | )), 80 | None => Ok(Self::new_with_rc(self.major, self.minor, self.patch + 1, 1)), 81 | } 82 | } 83 | 84 | pub fn next_rc(&self) -> Result { 85 | match self.rc { 86 | Some(rc) => Ok(Self::new_with_rc( 87 | self.major, 88 | self.minor, 89 | self.patch, 90 | rc + 1, 91 | )), 92 | None => Err(anyhow::anyhow!("Not release candidate version")), 93 | } 94 | } 95 | 96 | pub fn release(&self) -> Result { 97 | match self.rc { 98 | Some(_) => Ok(Self::new(self.major, self.minor, self.patch)), 99 | None => Err(anyhow::anyhow!("Not release candidate version")), 100 | } 101 | } 102 | } 103 | 104 | impl Display for SemVer { 105 | fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { 106 | match self.rc { 107 | Some(rc) => write!(f, "{}.{}.{}-rc.{}", self.major, self.minor, self.patch, rc), 108 | None => write!(f, "{}.{}.{}", self.major, self.minor, self.patch), 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-tokenizer" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.98" 10 | unicode-segmentation = "1.12" 11 | serde = { version = "1.0.219", features = ["derive"] } 12 | pretty_assertions = "1.4.1" 13 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 14 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/angle_bracket_close.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | let angle_nest_count = t 23 | .angle_nest_count 24 | .unwrap_or_else(|| panic!("`Tokenizer.angle_nest_count` is not set")); 25 | 26 | tokens.push(Token::AngleBracketClose(Location { 27 | start: LocationData { 28 | line, 29 | character: column, 30 | }, 31 | end: LocationData { 32 | line, 33 | character: column + 1, 34 | }, 35 | })); 36 | 37 | let t2 = Tokenizer { 38 | column: Some(column + 1), 39 | token_start_column: Some(column + 1), 40 | untreated: Some(tail), 41 | pool: Some(String::new()), 42 | tokens: Some(tokens), 43 | angle_nest_count: Some(angle_nest_count - 1), 44 | ..Default::default() 45 | }; 46 | dispatch(&t.merge(&t2)) 47 | } 48 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/angle_bracket_open.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | let angle_nest_count = t 23 | .angle_nest_count 24 | .unwrap_or_else(|| panic!("`Tokenizer.angle_nest_count` is not set")); 25 | 26 | tokens.push(Token::AngleBracketOpen(Location { 27 | start: LocationData { 28 | line, 29 | character: column, 30 | }, 31 | end: LocationData { 32 | line, 33 | character: column + 1, 34 | }, 35 | })); 36 | 37 | let t2 = Tokenizer { 38 | column: Some(column + 1), 39 | token_start_column: Some(column + 1), 40 | untreated: Some(tail), 41 | pool: Some(String::new()), 42 | tokens: Some(tokens), 43 | angle_nest_count: Some(angle_nest_count + 1), 44 | looking_for_identifier: Some(true), 45 | ..Default::default() 46 | }; 47 | dispatch(&t.merge(&t2)) 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | use crate::tokenize::tokenize; 54 | use anyhow::Result; 55 | use brack_common::tokens::Token::{AngleBracketOpen, EOF}; 56 | 57 | #[test] 58 | fn test_angle_bracket_open() -> Result<()> { 59 | let input = "<"; 60 | let expected_output = vec![ 61 | AngleBracketOpen(Location { 62 | start: LocationData { 63 | line: 0, 64 | character: 0, 65 | }, 66 | end: LocationData { 67 | line: 0, 68 | character: 1, 69 | }, 70 | }), 71 | EOF(Location { 72 | start: LocationData { 73 | line: 0, 74 | character: 1, 75 | }, 76 | end: LocationData { 77 | line: 0, 78 | character: 1, 79 | }, 80 | }), 81 | ]; 82 | let actual_output = tokenize(input); 83 | assert_eq!(expected_output, actual_output); 84 | Ok(()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/backslash.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | let mut tokens = t 12 | .tokens 13 | .clone() 14 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 15 | let line = t 16 | .line 17 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 18 | let column = t 19 | .column 20 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 21 | 22 | tokens.push(Token::BackSlash(Location { 23 | start: LocationData { 24 | line, 25 | character: column, 26 | }, 27 | end: LocationData { 28 | line, 29 | character: column + 1, 30 | }, 31 | })); 32 | 33 | let t2 = Tokenizer { 34 | column: Some(column + 1), 35 | token_start_column: Some(column + 1), 36 | untreated: Some(tail), 37 | tokens: Some(tokens), 38 | ..Default::default() 39 | }; 40 | dispatch(&t.merge(&t2)) 41 | } 42 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/comma.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | 23 | tokens.push(Token::Comma(Location { 24 | start: LocationData { 25 | line, 26 | character: column, 27 | }, 28 | end: LocationData { 29 | line, 30 | character: column + 1, 31 | }, 32 | })); 33 | 34 | let t2 = Tokenizer { 35 | column: Some(column + 1), 36 | token_start_column: Some(column + 1), 37 | untreated: Some(tail), 38 | pool: Some(String::new()), 39 | tokens: Some(tokens), 40 | ..Default::default() 41 | }; 42 | dispatch(&t.merge(&t2)) 43 | } 44 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/curly_bracket_close.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | let curly_nest_count = t 23 | .curly_nest_count 24 | .unwrap_or_else(|| panic!("`Tokenizer.curly_nest_count` is not set")); 25 | 26 | tokens.push(Token::CurlyBracketClose(Location { 27 | start: LocationData { 28 | line, 29 | character: column, 30 | }, 31 | end: LocationData { 32 | line, 33 | character: column + 1, 34 | }, 35 | })); 36 | 37 | let t2 = Tokenizer { 38 | column: Some(column + 1), 39 | token_start_column: Some(column + 1), 40 | untreated: Some(tail), 41 | pool: Some(String::new()), 42 | tokens: Some(tokens), 43 | curly_nest_count: Some(curly_nest_count - 1), 44 | ..Default::default() 45 | }; 46 | dispatch(&t.merge(&t2)) 47 | } 48 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/curly_bracket_open.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | let curly_nest_count = t 23 | .curly_nest_count 24 | .unwrap_or_else(|| panic!("`Tokenizer.curly_nest_count` is not set")); 25 | 26 | tokens.push(Token::CurlyBracketOpen(Location { 27 | start: LocationData { 28 | line, 29 | character: column, 30 | }, 31 | end: LocationData { 32 | line, 33 | character: column + 1, 34 | }, 35 | })); 36 | 37 | let t2 = Tokenizer { 38 | column: Some(column + 1), 39 | token_start_column: Some(column + 1), 40 | untreated: Some(tail), 41 | pool: Some(String::new()), 42 | tokens: Some(tokens), 43 | curly_nest_count: Some(curly_nest_count + 1), 44 | looking_for_identifier: Some(true), 45 | ..Default::default() 46 | }; 47 | dispatch(&t.merge(&t2)) 48 | } 49 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/dispatch.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | angle_bracket_close, angle_bracket_open, backslash, comma, curly_bracket_close, 3 | curly_bracket_open, dot, identifier, module, newline, square_bracket_close, 4 | square_bracket_open, text, tokenizer::Tokenizer, utils::separate, whitespace, 5 | }; 6 | use brack_common::location::{Location, LocationData}; 7 | use brack_common::tokens::Token; 8 | 9 | pub fn dispatch(t: &Tokenizer) -> Vec { 10 | let s = t 11 | .untreated 12 | .clone() 13 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 14 | let pool = t 15 | .pool 16 | .clone() 17 | .unwrap_or_else(|| panic!("`Tokenizer.pool` is not set")); 18 | let column = t 19 | .column 20 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 21 | 22 | let (head, tail) = separate(&s); 23 | 24 | if head == "\0" { 25 | let mut tokens = t 26 | .tokens 27 | .clone() 28 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 29 | let line = t 30 | .line 31 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 32 | let column = t 33 | .column 34 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 35 | tokens.push(Token::EOF(Location { 36 | start: LocationData { 37 | line, 38 | character: column, 39 | }, 40 | end: LocationData { 41 | line, 42 | character: column, 43 | }, 44 | })); 45 | return tokens; 46 | } 47 | 48 | let (head2, _) = separate(&tail); 49 | 50 | let angle_c = t 51 | .angle_nest_count 52 | .unwrap_or_else(|| panic!("`Tokenizer.angle_nest_count` is not set")); 53 | let curly_c = t 54 | .curly_nest_count 55 | .unwrap_or_else(|| panic!("`Tokenizer.curly_nest_count` is not set")); 56 | let square_c = t 57 | .square_nest_count 58 | .unwrap_or_else(|| panic!("`Tokenizer.square_nest_count` is not set")); 59 | let look_for_ident = t 60 | .looking_for_identifier 61 | .unwrap_or_else(|| panic!("`Tokenizer.looking_for_identifier` is not set")); 62 | let nested = (angle_c + curly_c + square_c) > 0; 63 | match (&head[..], &head2[..]) { 64 | ("\\", _) => backslash::tokenize(t), 65 | (_, ">") if look_for_ident => identifier::tokenize(t), 66 | (_, "}") if look_for_ident => identifier::tokenize(t), 67 | (_, "]") if look_for_ident => identifier::tokenize(t), 68 | ("<", _) => angle_bracket_open::tokenize(t), 69 | (">", _) => angle_bracket_close::tokenize(t), 70 | ("{", _) => curly_bracket_open::tokenize(t), 71 | ("}", _) => curly_bracket_close::tokenize(t), 72 | ("[", _) => square_bracket_open::tokenize(t), 73 | ("]", _) => square_bracket_close::tokenize(t), 74 | (".", _) if nested => dot::tokenize(t), 75 | (",", _) if nested => comma::tokenize(t), 76 | (" ", _) if nested => whitespace::tokenize(t), 77 | ("\n", _) => newline::tokenize(t), 78 | (_, " ") if look_for_ident => identifier::tokenize(t), 79 | (_, ".") if look_for_ident => module::tokenize(t), 80 | (_, "<") | (_, "{") | (_, "[") | (_, "\n") | (_, "\0") => text::tokenize(t), 81 | (_, " ") if nested => text::tokenize(t), 82 | (_, ",") if nested => text::tokenize(t), 83 | (_, ".") if nested => text::tokenize(t), 84 | (_, ">") | (_, "]") | (_, "}") | (_, "\\") => text::tokenize(t), 85 | _ => { 86 | let t2 = Tokenizer { 87 | column: Some(column + 1), 88 | untreated: Some(tail), 89 | pool: Some(format!("{}{}", pool, head)), 90 | ..Default::default() 91 | }; 92 | dispatch(&t.merge(&t2)) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/dot.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | 23 | tokens.push(Token::Dot(Location { 24 | start: LocationData { 25 | line, 26 | character: column, 27 | }, 28 | end: LocationData { 29 | line, 30 | character: column + 1, 31 | }, 32 | })); 33 | 34 | let t2 = Tokenizer { 35 | column: Some(column + 1), 36 | token_start_column: Some(column + 1), 37 | untreated: Some(tail), 38 | pool: Some(String::new()), 39 | tokens: Some(tokens), 40 | ..Default::default() 41 | }; 42 | dispatch(&t.merge(&t2)) 43 | } 44 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/identifier.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (head, tail) = separate(&s); 11 | let mut pool = t 12 | .pool 13 | .clone() 14 | .unwrap_or_else(|| panic!("`Tokenizer.pool` is not set")); 15 | pool.push_str(&head); 16 | 17 | let mut tokens = t 18 | .tokens 19 | .clone() 20 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 21 | let token_start_line = t 22 | .token_start_line 23 | .unwrap_or_else(|| panic!("`Tokenizer.token_start_line` is not set")); 24 | let token_start_column = t 25 | .token_start_column 26 | .unwrap_or_else(|| panic!("`Tokenizer.token_start_column` is not set")); 27 | let line = t 28 | .line 29 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 30 | let column = t 31 | .column 32 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 33 | 34 | tokens.push(Token::Ident( 35 | pool, 36 | Location { 37 | start: LocationData { 38 | line: token_start_line, 39 | character: token_start_column, 40 | }, 41 | end: LocationData { 42 | line, 43 | character: column + 1, 44 | }, 45 | }, 46 | )); 47 | 48 | let t2 = Tokenizer { 49 | column: Some(column + 1), 50 | token_start_column: Some(column + 1), 51 | untreated: Some(tail), 52 | pool: Some(String::new()), 53 | tokens: Some(tokens), 54 | looking_for_identifier: Some(false), 55 | ..Default::default() 56 | }; 57 | dispatch(&t.merge(&t2)) 58 | } 59 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod angle_bracket_close; 2 | mod angle_bracket_open; 3 | mod backslash; 4 | mod comma; 5 | mod curly_bracket_close; 6 | mod curly_bracket_open; 7 | mod dispatch; 8 | mod dot; 9 | mod identifier; 10 | mod module; 11 | mod newline; 12 | mod square_bracket_close; 13 | mod square_bracket_open; 14 | mod text; 15 | pub mod tokenize; 16 | mod tokenizer; 17 | mod utils; 18 | mod whitespace; 19 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/module.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (head, tail) = separate(&s); 11 | let mut pool = t 12 | .pool 13 | .clone() 14 | .unwrap_or_else(|| panic!("`Tokenizer.pool` is not set")); 15 | pool.push_str(&head); 16 | 17 | let mut tokens = t 18 | .tokens 19 | .clone() 20 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 21 | let token_start_line = t 22 | .token_start_line 23 | .unwrap_or_else(|| panic!("`Tokenizer.token_start_line` is not set")); 24 | let token_start_column = t 25 | .token_start_column 26 | .unwrap_or_else(|| panic!("`Tokenizer.token_start_column` is not set")); 27 | let line = t 28 | .line 29 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 30 | let column = t 31 | .column 32 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 33 | tokens.push(Token::Module( 34 | pool, 35 | Location { 36 | start: LocationData { 37 | line: token_start_line, 38 | character: token_start_column, 39 | }, 40 | end: LocationData { 41 | line, 42 | character: column + 1, 43 | }, 44 | }, 45 | )); 46 | 47 | let t2 = Tokenizer { 48 | column: Some(column + 1), 49 | token_start_column: Some(column + 1), 50 | untreated: Some(tail), 51 | pool: Some(String::new()), 52 | tokens: Some(tokens), 53 | ..Default::default() 54 | }; 55 | dispatch(&t.merge(&t2)) 56 | } 57 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/newline.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let line = t 11 | .line 12 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 13 | let column = t 14 | .column 15 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 16 | let (_, tail) = separate(&s); 17 | let mut tokens = t 18 | .tokens 19 | .clone() 20 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 21 | 22 | tokens.push(Token::NewLine(Location { 23 | start: LocationData { 24 | line, 25 | character: column, 26 | }, 27 | end: LocationData { 28 | line, 29 | character: column + 1, 30 | }, 31 | })); 32 | 33 | let t2 = Tokenizer { 34 | line: Some(line + 1), 35 | column: Some(0), 36 | token_start_line: Some(line + 1), 37 | token_start_column: Some(0), 38 | untreated: Some(tail), 39 | pool: Some(String::new()), 40 | tokens: Some(tokens), 41 | ..Default::default() 42 | }; 43 | dispatch(&t.merge(&t2)) 44 | } 45 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/square_bracket_close.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | let square_nest_count = t 23 | .square_nest_count 24 | .unwrap_or_else(|| panic!("`Tokenizer.square_nest_count` is not set")); 25 | 26 | tokens.push(Token::SquareBracketClose(Location { 27 | start: LocationData { 28 | line, 29 | character: column, 30 | }, 31 | end: LocationData { 32 | line, 33 | character: column + 1, 34 | }, 35 | })); 36 | 37 | let t2 = Tokenizer { 38 | column: Some(column + 1), 39 | token_start_column: Some(column + 1), 40 | untreated: Some(tail), 41 | pool: Some(String::new()), 42 | tokens: Some(tokens), 43 | square_nest_count: Some(square_nest_count - 1), 44 | ..Default::default() 45 | }; 46 | dispatch(&t.merge(&t2)) 47 | } 48 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/square_bracket_open.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | let square_nest_count = t 23 | .square_nest_count 24 | .unwrap_or_else(|| panic!("`Tokenizer.square_nest_count` is not set")); 25 | 26 | tokens.push(Token::SquareBracketOpen(Location { 27 | start: LocationData { 28 | line, 29 | character: column, 30 | }, 31 | end: LocationData { 32 | line, 33 | character: column + 1, 34 | }, 35 | })); 36 | 37 | let t2 = Tokenizer { 38 | column: Some(column + 1), 39 | token_start_column: Some(column + 1), 40 | untreated: Some(tail), 41 | pool: Some(String::new()), 42 | tokens: Some(tokens), 43 | square_nest_count: Some(square_nest_count + 1), 44 | looking_for_identifier: Some(true), 45 | ..Default::default() 46 | }; 47 | dispatch(&t.merge(&t2)) 48 | } 49 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/text.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (head, tail) = separate(&s); 11 | let mut pool = t 12 | .pool 13 | .clone() 14 | .unwrap_or_else(|| panic!("`Tokenizer.pool` is not set")); 15 | pool.push_str(&head); 16 | 17 | let mut tokens = t 18 | .tokens 19 | .clone() 20 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 21 | let token_start_line = t 22 | .token_start_line 23 | .unwrap_or_else(|| panic!("`Tokenizer.token_start_line` is not set")); 24 | let token_start_column = t 25 | .token_start_column 26 | .unwrap_or_else(|| panic!("`Tokenizer.token_start_column` is not set")); 27 | let line = t 28 | .line 29 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 30 | let column = t 31 | .column 32 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 33 | tokens.push(Token::Text( 34 | pool, 35 | Location { 36 | start: LocationData { 37 | line: token_start_line, 38 | character: token_start_column, 39 | }, 40 | end: LocationData { 41 | line, 42 | character: column + 1, 43 | }, 44 | }, 45 | )); 46 | 47 | let t2 = Tokenizer { 48 | column: Some(column + 1), 49 | token_start_column: Some(column + 1), 50 | untreated: Some(tail), 51 | pool: Some(String::new()), 52 | tokens: Some(tokens), 53 | ..Default::default() 54 | }; 55 | dispatch(&t.merge(&t2)) 56 | } 57 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/tokenizer.rs: -------------------------------------------------------------------------------- 1 | use brack_common::tokens::Token; 2 | 3 | #[derive(Debug, Default, Clone, PartialEq)] 4 | pub struct Tokenizer { 5 | pub uri: String, 6 | pub line: Option, 7 | pub column: Option, 8 | pub token_start_line: Option, 9 | pub token_start_column: Option, 10 | pub untreated: Option, 11 | pub pool: Option, 12 | pub tokens: Option>, 13 | pub angle_nest_count: Option, 14 | pub square_nest_count: Option, 15 | pub curly_nest_count: Option, 16 | pub looking_for_identifier: Option, 17 | } 18 | 19 | impl Tokenizer { 20 | pub fn merge(&self, other: &Tokenizer) -> Tokenizer { 21 | Tokenizer { 22 | uri: other.uri.clone(), 23 | line: match other.line { 24 | Some(s) => Some(s), 25 | _ => self.line, 26 | }, 27 | column: match other.column { 28 | Some(s) => Some(s), 29 | _ => self.column, 30 | }, 31 | token_start_line: match other.token_start_line { 32 | Some(s) => Some(s), 33 | _ => self.token_start_line, 34 | }, 35 | token_start_column: match other.token_start_column { 36 | Some(s) => Some(s), 37 | _ => self.token_start_column, 38 | }, 39 | untreated: match &other.untreated { 40 | Some(s) => Some(s.clone()), 41 | _ => self.untreated.clone(), 42 | }, 43 | pool: match &other.pool { 44 | Some(s) => Some(s.clone()), 45 | _ => self.pool.clone(), 46 | }, 47 | tokens: match &other.tokens { 48 | Some(s) => Some(s.clone()), 49 | _ => self.tokens.clone(), 50 | }, 51 | angle_nest_count: match other.angle_nest_count { 52 | Some(s) => Some(s), 53 | _ => self.angle_nest_count, 54 | }, 55 | square_nest_count: match other.square_nest_count { 56 | Some(s) => Some(s), 57 | _ => self.square_nest_count, 58 | }, 59 | curly_nest_count: match other.curly_nest_count { 60 | Some(s) => Some(s), 61 | _ => self.curly_nest_count, 62 | }, 63 | looking_for_identifier: match other.looking_for_identifier { 64 | Some(s) => Some(s), 65 | _ => self.looking_for_identifier, 66 | }, 67 | } 68 | } 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use brack_common::location::{Location, LocationData}; 74 | 75 | #[test] 76 | fn test_merge() { 77 | use super::*; 78 | let a = Tokenizer { 79 | uri: "a".to_string(), 80 | line: Some(1), 81 | column: Some(1), 82 | token_start_line: Some(1), 83 | token_start_column: Some(1), 84 | untreated: Some("a".to_string()), 85 | pool: Some("a".to_string()), 86 | tokens: Some(vec![Token::Text( 87 | "a".to_string(), 88 | Location { 89 | start: LocationData { 90 | line: 1, 91 | character: 1, 92 | }, 93 | end: LocationData { 94 | line: 1, 95 | character: 1, 96 | }, 97 | }, 98 | )]), 99 | angle_nest_count: Some(1), 100 | square_nest_count: Some(1), 101 | curly_nest_count: Some(1), 102 | looking_for_identifier: Some(true), 103 | }; 104 | let b = Tokenizer { 105 | uri: "b".to_string(), 106 | line: Some(2), 107 | column: Some(2), 108 | token_start_line: Some(2), 109 | token_start_column: Some(2), 110 | untreated: Some("b".to_string()), 111 | pool: Some("b".to_string()), 112 | tokens: Some(vec![Token::Text( 113 | "b".to_string(), 114 | Location { 115 | start: LocationData { 116 | line: 2, 117 | character: 2, 118 | }, 119 | end: LocationData { 120 | line: 2, 121 | character: 2, 122 | }, 123 | }, 124 | )]), 125 | angle_nest_count: Some(2), 126 | square_nest_count: Some(2), 127 | curly_nest_count: Some(2), 128 | looking_for_identifier: Some(false), 129 | }; 130 | let c = a.merge(&b); 131 | let res = Tokenizer { 132 | uri: "b".to_string(), 133 | line: Some(2), 134 | column: Some(2), 135 | token_start_line: Some(2), 136 | token_start_column: Some(2), 137 | untreated: Some("b".to_string()), 138 | pool: Some("b".to_string()), 139 | tokens: Some(vec![Token::Text( 140 | "b".to_string(), 141 | Location { 142 | start: LocationData { 143 | line: 2, 144 | character: 2, 145 | }, 146 | end: LocationData { 147 | line: 2, 148 | character: 2, 149 | }, 150 | }, 151 | )]), 152 | angle_nest_count: Some(2), 153 | square_nest_count: Some(2), 154 | curly_nest_count: Some(2), 155 | looking_for_identifier: Some(false), 156 | }; 157 | assert_eq!(c, res); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/utils.rs: -------------------------------------------------------------------------------- 1 | use unicode_segmentation::UnicodeSegmentation; 2 | 3 | pub fn separate(s: &str) -> (String, String) { 4 | let graphemes = s.graphemes(true); 5 | match graphemes.count() { 6 | 0 => ('\0'.to_string(), String::new()), 7 | 1 => (s.graphemes(true).next().unwrap().to_string(), String::new()), 8 | _ => { 9 | let mut graphemes = s.graphemes(true); 10 | let head = graphemes.next().unwrap().to_string(); 11 | let tail = graphemes.collect::(); 12 | (head, tail) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/src/whitespace.rs: -------------------------------------------------------------------------------- 1 | use crate::{dispatch::dispatch, tokenizer::Tokenizer, utils::separate}; 2 | use brack_common::location::{Location, LocationData}; 3 | use brack_common::tokens::Token; 4 | 5 | pub fn tokenize(t: &Tokenizer) -> Vec { 6 | let s = t 7 | .untreated 8 | .clone() 9 | .unwrap_or_else(|| panic!("`Tokenizer.untreated` is not set")); 10 | let (_, tail) = separate(&s); 11 | 12 | let mut tokens = t 13 | .tokens 14 | .clone() 15 | .unwrap_or_else(|| panic!("`Tokenizer.tokens` is not set")); 16 | let line = t 17 | .line 18 | .unwrap_or_else(|| panic!("`Tokenizer.line` is not set")); 19 | let column = t 20 | .column 21 | .unwrap_or_else(|| panic!("`Tokenizer.column` is not set")); 22 | 23 | tokens.push(Token::WhiteSpace(Location { 24 | start: LocationData { 25 | line, 26 | character: column, 27 | }, 28 | end: LocationData { 29 | line, 30 | character: column + 1, 31 | }, 32 | })); 33 | 34 | let t2 = Tokenizer { 35 | column: Some(column + 1), 36 | token_start_column: Some(column + 1), 37 | untreated: Some(tail), 38 | pool: Some(String::new()), 39 | tokens: Some(tokens), 40 | ..Default::default() 41 | }; 42 | dispatch(&t.merge(&t2)) 43 | } 44 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/angle_bracket.[]: -------------------------------------------------------------------------------- 1 | <> 2 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/left_angle_bracket.[]: -------------------------------------------------------------------------------- 1 | < 2 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/multiple_errors.[]: -------------------------------------------------------------------------------- 1 | <> 2 | 3 | [std.hoge] 4 | 5 | text 6 | 7 | {foo.hoge a,} 8 | 9 | \a 10 | 11 | これは文章です 12 | そうですね 13 | 14 | \[ 15 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/right_angle_bracket.[]: -------------------------------------------------------------------------------- 1 | > 2 | -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/split_commands_with_an_argument_includes_angle_brackets.[]: -------------------------------------------------------------------------------- 1 | Hello, <* World!> -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/split_commands_with_an_argument_includes_curly_brackets.[]: -------------------------------------------------------------------------------- 1 | Hello, {std.* World!} -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/split_commands_with_an_argument_includes_square_brackets.[]: -------------------------------------------------------------------------------- 1 | Hello, [std.* World!] -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/split_commands_with_two_arguments_includes_square_brackets.[]: -------------------------------------------------------------------------------- 1 | Hello, [std.@ World!, https://example\.com/] -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/split_japanese_and_emoji.[]: -------------------------------------------------------------------------------- 1 | こんにちは!🇯🇵 -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/split_nesting_commands.[]: -------------------------------------------------------------------------------- 1 | Hello, [std.* [std.@ World!, https://example\.com/]] -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/split_newlines.[]: -------------------------------------------------------------------------------- 1 | Hello, 2 | World, 3 | {std.** Contact} 4 | [std.@ My website, https://example\.com/] 5 | 6 | 2023.12.28 -------------------------------------------------------------------------------- /crates/brack-tokenizer/test/split_no_commands.[]: -------------------------------------------------------------------------------- 1 | Hello, World! -------------------------------------------------------------------------------- /crates/brack-transformer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-transformer" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | brack-common = { git = "https://github.com/brack-lang/brack", package = "brack-common" } 10 | serde = { version = "1.0.219", features = ["derive"] } 11 | serde_json = "1.0.134" 12 | 13 | [features] 14 | default = [] 15 | debug = [] 16 | 17 | [[bin]] 18 | name = "debug_compile" 19 | path = "src/debug/debug_compile.rs" 20 | required-features = ["debug"] 21 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/angle.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{InnerNode, CST}; 2 | use brack_common::location::merge_location; 3 | use brack_common::transformer_errors::TransformError; 4 | 5 | use crate::{ 6 | simplify, 7 | utils::{ 8 | check_if_dot, check_if_ident_or_angle_bracket, check_if_module_or_angle_bracket, 9 | check_unexpected_dot, check_valid_arguments, remove_elements_not_included_ast, 10 | remove_whitespaces_and_newlines, 11 | }, 12 | }; 13 | 14 | fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { 15 | let mut errors = vec![]; 16 | match (csts[0].clone(), csts[csts.len() - 1].clone()) { 17 | (CST::AngleBracketOpen(_), CST::AngleBracketClose(_)) => (), 18 | (CST::AngleBracketOpen(left), CST::CurlyBracketClose(right)) 19 | | (CST::AngleBracketOpen(left), CST::SquareBracketClose(right)) => errors.push( 20 | TransformError::MismatchedBracket(merge_location(&left.location, &right.location)), 21 | ), 22 | (CST::AngleBracketOpen(left), right) => errors.push(TransformError::AngleNotClosed( 23 | merge_location(&left.location, &right.location()), 24 | )), 25 | _ => panic!( 26 | "Maybe cst parser is broken because CST::Angle must have bracket-open node first." 27 | ), 28 | } 29 | errors 30 | } 31 | 32 | pub fn simplify(cst: &CST) -> (CST, Vec) { 33 | let node = match cst { 34 | CST::Angle(node) => node, 35 | _ => panic!("Cannot pass non-angle-bracket node to angle::simplify"), 36 | }; 37 | let mut errors = vec![]; 38 | let mut csts = vec![]; 39 | for child in node.children.clone() { 40 | let (cst, mut node_errors) = simplify::simplify(&child); 41 | let nodes = match cst { 42 | CST::Expr(node) => node.children.clone(), 43 | node => vec![node], 44 | }; 45 | csts.append(&mut nodes.clone()); 46 | errors.append(&mut node_errors); 47 | } 48 | 49 | errors.append(&mut check_if_the_first_and_last_node_are_brackets(&csts)); 50 | errors.append(&mut check_if_module_or_angle_bracket(&csts)); 51 | errors.append(&mut check_if_dot(&csts)); 52 | errors.append(&mut check_if_ident_or_angle_bracket(&csts)); 53 | errors.append(&mut check_unexpected_dot(&csts)); 54 | let csts = remove_whitespaces_and_newlines(&csts); 55 | let (csts, mut new_errors) = check_valid_arguments(&csts); 56 | errors.append(&mut new_errors); 57 | let csts = remove_elements_not_included_ast(&csts); 58 | 59 | ( 60 | CST::Angle(InnerNode { 61 | id: node.id.clone(), 62 | children: csts, 63 | location: node.location.clone(), 64 | }), 65 | errors, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/backslash.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_invalid, CST}; 2 | use brack_common::transformer_errors::TransformError; 3 | 4 | pub fn simplify(cst: &CST) -> (CST, Vec) { 5 | let node = match cst { 6 | CST::BackSlash(node) => node, 7 | _ => panic!("Cannot pass non-back-slash node to backslash::simplify"), 8 | }; 9 | let mut errors = vec![]; 10 | 11 | if node.children.is_empty() { 12 | errors.push(TransformError::InvalidBackslash(node.location.clone())); 13 | return (new_invalid(node.location.clone()), errors); 14 | } 15 | 16 | (cst.children()[0].clone(), errors) 17 | } 18 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/curly.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{InnerNode, CST}; 2 | use brack_common::location::merge_location; 3 | use brack_common::transformer_errors::TransformError; 4 | 5 | use crate::{ 6 | simplify, 7 | utils::{ 8 | check_if_dot, check_if_ident_or_angle_bracket, check_if_module_or_angle_bracket, 9 | check_unexpected_dot, check_valid_arguments, remove_elements_not_included_ast, 10 | remove_whitespaces_and_newlines, 11 | }, 12 | }; 13 | 14 | fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { 15 | let mut errors = vec![]; 16 | match (csts[0].clone(), csts[csts.len() - 1].clone()) { 17 | (CST::CurlyBracketOpen(_), CST::CurlyBracketClose(_)) => (), 18 | (CST::CurlyBracketOpen(left), CST::AngleBracketClose(right)) 19 | | (CST::CurlyBracketOpen(left), CST::SquareBracketClose(right)) => errors.push( 20 | TransformError::MismatchedBracket(merge_location(&left.location, &right.location)), 21 | ), 22 | (CST::CurlyBracketOpen(left), right) => errors.push(TransformError::CurlyNotClosed( 23 | merge_location(&left.location, &right.location()), 24 | )), 25 | _ => panic!( 26 | "Maybe cst parser is broken because CST::Curly must have bracket-open node first." 27 | ), 28 | } 29 | errors 30 | } 31 | 32 | pub fn simplify(cst: &CST) -> (CST, Vec) { 33 | let node = match cst { 34 | CST::Curly(node) => node, 35 | _ => panic!("Cannot pass non-curly-bracket node to curly::simplify"), 36 | }; 37 | let mut errors = vec![]; 38 | let mut csts = vec![]; 39 | for child in node.children.clone() { 40 | let (cst, mut node_errors) = simplify::simplify(&child); 41 | let nodes = match cst { 42 | CST::Expr(node) => node.children.clone(), 43 | node => vec![node], 44 | }; 45 | csts.append(&mut nodes.clone()); 46 | errors.append(&mut node_errors); 47 | } 48 | 49 | errors.append(&mut check_if_the_first_and_last_node_are_brackets(&csts)); 50 | errors.append(&mut check_if_module_or_angle_bracket(&csts)); 51 | errors.append(&mut check_if_dot(&csts)); 52 | errors.append(&mut check_if_ident_or_angle_bracket(&csts)); 53 | errors.append(&mut check_unexpected_dot(&csts)); 54 | let csts = remove_whitespaces_and_newlines(&csts); 55 | let (csts, mut new_errors) = check_valid_arguments(&csts); 56 | errors.append(&mut new_errors); 57 | let csts = remove_elements_not_included_ast(&csts); 58 | 59 | ( 60 | CST::Curly(InnerNode { 61 | id: node.id.clone(), 62 | children: csts, 63 | location: node.location.clone(), 64 | }), 65 | errors, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/debug/debug_compile.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | static FILE_PATH: &str = "sample-text/no_commands.[]"; 4 | 5 | fn main() { 6 | let tokens = brack_tokenizer::tokenize::tokenize(&FILE_PATH).unwrap(); 7 | let ast = brack_parser::parse::parse(&tokens).unwrap(); 8 | dbg!(ast); 9 | } 10 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/debug/debug_parse.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | static FILE_PATH: &str = "sample-text/no_commands.[]"; 4 | 5 | fn main() { 6 | let tokens = brack_tokenizer::tokenize::tokenize(&FILE_PATH).unwrap(); 7 | let ast = brack_parser::parse::parse(&tokens).unwrap(); 8 | dbg!(ast); 9 | } 10 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/document.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_document, CST}; 2 | use brack_common::transformer_errors::TransformError; 3 | 4 | use crate::{simplify, utils::remove_elements_not_included_ast}; 5 | 6 | pub fn simplify(cst: &CST) -> (CST, Vec) { 7 | let node = match cst { 8 | CST::Document(node) => node, 9 | _ => panic!("Cannot pass non-document node to document::simplify"), 10 | }; 11 | let mut errors = vec![]; 12 | let mut csts = vec![]; 13 | 14 | for child in node.children.clone() { 15 | let (cst, mut node_errors) = simplify::simplify(&child); 16 | csts.push(cst); 17 | errors.append(&mut node_errors); 18 | } 19 | 20 | csts = remove_elements_not_included_ast(&csts); 21 | 22 | let mut document = new_document(); 23 | document.set_location(node.location.clone()); 24 | for child in csts { 25 | document.add(child); 26 | } 27 | (document, errors) 28 | } 29 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/expr.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_expr, CST}; 2 | use brack_common::transformer_errors::TransformError; 3 | 4 | use crate::simplify; 5 | 6 | pub fn simplify(cst: &CST) -> (CST, Vec) { 7 | let node = match cst { 8 | CST::Expr(node) => node, 9 | _ => panic!("Cannot pass non-expr node to expr::simplify"), 10 | }; 11 | let mut errors = vec![]; 12 | let mut csts = vec![]; 13 | 14 | for child in node.children.clone() { 15 | let (cst, mut node_errors) = simplify::simplify(&child); 16 | csts.push(cst); 17 | errors.append(&mut node_errors); 18 | } 19 | 20 | let mut expr = new_expr(); 21 | for child in csts { 22 | expr.add(child); 23 | } 24 | expr.set_location(node.location.clone()); 25 | (expr, errors) 26 | } 27 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod angle; 2 | mod backslash; 3 | mod curly; 4 | mod document; 5 | mod expr; 6 | mod simplify; 7 | mod square; 8 | mod stmt; 9 | pub mod transform; 10 | mod utils; 11 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/simplify.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::CST; 2 | use brack_common::transformer_errors::TransformError; 3 | 4 | use crate::{angle, backslash, curly, document, expr, square, stmt}; 5 | 6 | pub fn simplify(cst: &CST) -> (CST, Vec) { 7 | match cst { 8 | CST::Document(_) => document::simplify(cst), 9 | CST::Stmt(_) => stmt::simplify(cst), 10 | CST::Expr(_) => expr::simplify(cst), 11 | CST::Angle(_) => angle::simplify(cst), 12 | CST::Curly(_) => curly::simplify(cst), 13 | CST::Square(_) => square::simplify(cst), 14 | CST::BackSlash(_) => backslash::simplify(cst), 15 | node => (node.clone(), vec![]), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/square.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{InnerNode, CST}; 2 | use brack_common::location::merge_location; 3 | use brack_common::transformer_errors::TransformError; 4 | 5 | use crate::{ 6 | simplify, 7 | utils::{ 8 | check_if_dot, check_if_ident_or_angle_bracket, check_if_module_or_angle_bracket, 9 | check_unexpected_dot, check_valid_arguments, remove_elements_not_included_ast, 10 | remove_whitespaces_and_newlines, 11 | }, 12 | }; 13 | 14 | fn check_if_the_first_and_last_node_are_brackets(csts: &[CST]) -> Vec { 15 | let mut errors = vec![]; 16 | match (csts[0].clone(), csts[csts.len() - 1].clone()) { 17 | (CST::SquareBracketOpen(_), CST::SquareBracketClose(_)) => (), 18 | (CST::SquareBracketOpen(left), CST::AngleBracketClose(right)) 19 | | (CST::SquareBracketOpen(left), CST::CurlyBracketClose(right)) => errors.push( 20 | TransformError::MismatchedBracket(merge_location(&left.location, &right.location)), 21 | ), 22 | (CST::SquareBracketOpen(left), right) => errors.push(TransformError::SquareNotClosed( 23 | merge_location(&left.location, &right.location()), 24 | )), 25 | _ => panic!( 26 | "Maybe cst parser is broken because CST::Square must have bracket-open node first." 27 | ), 28 | } 29 | errors 30 | } 31 | 32 | pub fn simplify(cst: &CST) -> (CST, Vec) { 33 | let node = match cst { 34 | CST::Square(node) => node, 35 | _ => panic!("Cannot pass non-square-bracket node to curly::simplify"), 36 | }; 37 | let mut errors = vec![]; 38 | let mut csts = vec![]; 39 | for child in node.children.clone() { 40 | let (cst, mut node_errors) = simplify::simplify(&child); 41 | let nodes = match cst { 42 | CST::Expr(node) => node.children.clone(), 43 | node => vec![node], 44 | }; 45 | csts.append(&mut nodes.clone()); 46 | errors.append(&mut node_errors); 47 | } 48 | 49 | errors.append(&mut check_if_the_first_and_last_node_are_brackets(&csts)); 50 | errors.append(&mut check_if_module_or_angle_bracket(&csts)); 51 | errors.append(&mut check_if_dot(&csts)); 52 | errors.append(&mut check_if_ident_or_angle_bracket(&csts)); 53 | errors.append(&mut check_unexpected_dot(&csts)); 54 | let csts = remove_whitespaces_and_newlines(&csts); 55 | let (csts, mut new_errors) = check_valid_arguments(&csts); 56 | errors.append(&mut new_errors); 57 | let csts = remove_elements_not_included_ast(&csts); 58 | 59 | ( 60 | CST::Square(InnerNode { 61 | id: node.id.clone(), 62 | children: csts, 63 | location: node.location.clone(), 64 | }), 65 | errors, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/stmt.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_stmt, CST}; 2 | use brack_common::transformer_errors::TransformError; 3 | 4 | use crate::{simplify, utils::remove_elements_not_included_ast}; 5 | 6 | pub fn simplify(cst: &CST) -> (CST, Vec) { 7 | let node = match cst { 8 | CST::Stmt(node) => node, 9 | _ => panic!("Cannot pass non-stmt node to stmt::simplify"), 10 | }; 11 | let mut errors = vec![]; 12 | let mut csts = vec![]; 13 | 14 | for child in node.children.clone() { 15 | let (cst, mut node_errors) = simplify::simplify(&child); 16 | csts.push(cst); 17 | errors.append(&mut node_errors); 18 | } 19 | 20 | csts = remove_elements_not_included_ast(&csts); 21 | 22 | let mut stmt = new_stmt(); 23 | for child in csts.clone() { 24 | stmt.add(child); 25 | } 26 | stmt.set_location(node.location.clone()); 27 | (stmt, errors) 28 | } 29 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/transform.rs: -------------------------------------------------------------------------------- 1 | use brack_common::ast::{ 2 | new_angle, new_curly, new_document, new_expr, new_ident, new_invalid, new_module, new_square, 3 | new_stmt, new_text, AST, 4 | }; 5 | use brack_common::cst::CST; 6 | use brack_common::transformer_errors::TransformError; 7 | 8 | use crate::simplify; 9 | 10 | pub fn transform(cst: &CST) -> (AST, Vec) { 11 | let (cst, errors) = simplify::simplify(cst); 12 | 13 | fn aux(cst: &CST) -> AST { 14 | match cst { 15 | CST::Document(node) => { 16 | let mut children = vec![]; 17 | for child in node.children.clone() { 18 | children.push(aux(&child)); 19 | } 20 | new_document(children, node.location.clone()) 21 | } 22 | CST::Stmt(node) => { 23 | let mut children = vec![]; 24 | for child in node.children.clone() { 25 | children.push(aux(&child)); 26 | } 27 | new_stmt(children, node.location.clone()) 28 | } 29 | CST::Expr(node) => { 30 | let mut children = vec![]; 31 | for child in node.children.clone() { 32 | children.push(aux(&child)); 33 | } 34 | new_expr(children, node.location.clone()) 35 | } 36 | CST::Angle(node) => { 37 | let mut children = vec![]; 38 | for child in node.children.clone() { 39 | children.push(aux(&child)); 40 | } 41 | new_angle(children, node.location.clone()) 42 | } 43 | CST::Curly(node) => { 44 | let mut children = vec![]; 45 | for child in node.children.clone() { 46 | children.push(aux(&child)); 47 | } 48 | new_curly(children, node.location.clone()) 49 | } 50 | CST::Square(node) => { 51 | let mut children = vec![]; 52 | for child in node.children.clone() { 53 | children.push(aux(&child)); 54 | } 55 | new_square(children, node.location.clone()) 56 | } 57 | CST::Ident(node) => new_ident(node.value.clone(), node.location.clone()), 58 | CST::Module(node) => new_module(node.value.clone(), node.location.clone()), 59 | CST::Invalid(node) => new_invalid(node.location.clone()), 60 | CST::Text(node) => new_text(node.value.clone(), node.location.clone()), 61 | node => panic!("Cannot pass non-ast node to transform::aux: {:?}", node), 62 | } 63 | } 64 | 65 | (aux(&cst), errors) 66 | } 67 | 68 | // #[cfg(test)] 69 | // mod tests { 70 | // use anyhow::Result; 71 | // use brack_parser::parse::parse; 72 | // use brack_tokenizer::tokenize::tokenize; 73 | 74 | // #[test] 75 | // fn test_simplify_square_1() -> Result<()> { 76 | // let tokens = tokenize("../brack-tokenizer/test/multiple_errors.[]")?; 77 | // println!("{:?}", tokens); 78 | // let cst = parse(&tokens)?; 79 | // println!("{}", cst); 80 | // let (ast, errors) = super::transform(&cst); 81 | // println!("{}", ast); 82 | 83 | // Err(anyhow::anyhow!(errors 84 | // .iter() 85 | // .map(|e| e.to_string()) 86 | // .collect::>() 87 | // .join("\n"))) 88 | // } 89 | // } 90 | -------------------------------------------------------------------------------- /crates/brack-transformer/src/utils.rs: -------------------------------------------------------------------------------- 1 | use brack_common::cst::{new_expr, CST}; 2 | use brack_common::transformer_errors::TransformError; 3 | 4 | pub fn check_if_module_or_angle_bracket(csts: &[CST]) -> Vec { 5 | if csts.len() < 2 { 6 | return vec![]; 7 | } 8 | let cst = csts[1].clone(); 9 | match cst { 10 | CST::Module(_) => vec![], 11 | CST::Angle(_) => vec![], 12 | _ => vec![TransformError::ModuleNotFound(cst.location())], 13 | } 14 | } 15 | 16 | pub fn check_if_dot(csts: &[CST]) -> Vec { 17 | if csts.len() < 3 { 18 | return vec![]; 19 | } 20 | let cst = csts[2].clone(); 21 | match cst { 22 | CST::Dot(_) => vec![], 23 | _ => vec![TransformError::DotNotFound(cst.location())], 24 | } 25 | } 26 | 27 | pub fn check_if_ident_or_angle_bracket(csts: &[CST]) -> Vec { 28 | if csts.len() < 4 { 29 | return vec![]; 30 | } 31 | let cst = csts[3].clone(); 32 | match cst { 33 | CST::Ident(_) => vec![], 34 | CST::Angle(_) => vec![], 35 | _ => vec![TransformError::IdentifierNotFound(cst.location())], 36 | } 37 | } 38 | 39 | pub fn remove_elements_not_included_ast(csts: &[CST]) -> Vec { 40 | let mut new_csts = vec![]; 41 | for cst in csts { 42 | match cst { 43 | CST::Whitespace(_) 44 | | CST::Newline(_) 45 | | CST::Comma(_) 46 | | CST::Dot(_) 47 | | CST::AngleBracketOpen(_) 48 | | CST::AngleBracketClose(_) 49 | | CST::CurlyBracketOpen(_) 50 | | CST::CurlyBracketClose(_) 51 | | CST::SquareBracketOpen(_) 52 | | CST::SquareBracketClose(_) 53 | | CST::EOF(_) => (), 54 | _ => new_csts.push(cst.clone()), 55 | } 56 | } 57 | new_csts 58 | } 59 | 60 | pub fn check_valid_arguments(csts: &[CST]) -> (Vec, Vec) { 61 | if csts.len() < 4 { 62 | return (csts.to_vec(), vec![]); 63 | } 64 | let mut errors = vec![]; 65 | let mut new_csts = csts[0..4].to_vec(); // [AngleBracketOpen, Module, Dot, Ident 66 | let mut expr = new_expr(); 67 | let mut previous_comma = false; 68 | for i in 4..csts.len() { 69 | match csts[i].clone() { 70 | CST::Comma(_) => { 71 | if expr.children().is_empty() { 72 | errors.push(TransformError::UnexpectedComma(csts[i].location())); 73 | continue; 74 | } 75 | new_csts.push(expr); 76 | expr = new_expr(); 77 | previous_comma = true; 78 | } 79 | CST::AngleBracketClose(_) | CST::CurlyBracketClose(_) | CST::SquareBracketClose(_) => { 80 | if !expr.children().is_empty() { 81 | new_csts.push(expr.clone()); 82 | } else if previous_comma { 83 | errors.push(TransformError::UnexpectedComma(csts[i - 1].location())); 84 | } 85 | expr = new_expr(); 86 | new_csts.push(csts[i].clone()); 87 | break; 88 | } 89 | CST::Dot(_) => { 90 | errors.push(TransformError::UnexpectedDot(csts[i].location())); 91 | continue; 92 | } 93 | _ => { 94 | expr.add(csts[i].clone()); 95 | previous_comma = false; 96 | } 97 | } 98 | } 99 | if !expr.children().is_empty() { 100 | new_csts.push(expr); 101 | } 102 | (new_csts, errors) 103 | } 104 | 105 | pub fn check_unexpected_dot(csts: &[CST]) -> Vec { 106 | let mut errors = vec![]; 107 | for cst in csts.iter().skip(3) { 108 | if let CST::Dot(_) = cst { 109 | errors.push(TransformError::UnexpectedDot(cst.location())); 110 | } 111 | } 112 | errors 113 | } 114 | 115 | pub fn remove_whitespaces_and_newlines(csts: &Vec) -> Vec { 116 | let mut result = vec![]; 117 | for cst in csts { 118 | match cst { 119 | CST::Whitespace(_) | CST::Newline(_) => (), 120 | _ => result.push(cst.clone()), 121 | } 122 | } 123 | result 124 | } 125 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1741352980, 9 | "narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1743367904, 24 | "narHash": "sha256-sOos1jZGKmT6xxPvxGQyPTApOunXvScV4lNjBCXd/CI=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "7ffe0edc685f14b8c635e3d6591b0bbb97365e6c", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-24.11", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs-lib": { 38 | "locked": { 39 | "lastModified": 1740877520, 40 | "narHash": "sha256-oiwv/ZK/2FhGxrCkQkB83i7GnWXPPLzoqFHpDD3uYpk=", 41 | "owner": "nix-community", 42 | "repo": "nixpkgs.lib", 43 | "rev": "147dee35aab2193b174e4c0868bd80ead5ce755c", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "nix-community", 48 | "repo": "nixpkgs.lib", 49 | "type": "github" 50 | } 51 | }, 52 | "root": { 53 | "inputs": { 54 | "flake-parts": "flake-parts", 55 | "nixpkgs": "nixpkgs", 56 | "rust-overlay": "rust-overlay" 57 | } 58 | }, 59 | "rust-overlay": { 60 | "inputs": { 61 | "nixpkgs": [ 62 | "nixpkgs" 63 | ] 64 | }, 65 | "locked": { 66 | "lastModified": 1743475035, 67 | "narHash": "sha256-uLjVsb4Rxnp1zmFdPCDmdODd4RY6ETOeRj0IkC0ij/4=", 68 | "owner": "oxalica", 69 | "repo": "rust-overlay", 70 | "rev": "bee11c51c2cda3ac57c9e0149d94b86cc1b00d13", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "oxalica", 75 | "repo": "rust-overlay", 76 | "type": "github" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A bracket-based lightweight markup language that extends commands with WebAssembly"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; 6 | rust-overlay = { 7 | url = "github:oxalica/rust-overlay"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | flake-parts.url = "github:hercules-ci/flake-parts"; 11 | }; 12 | 13 | outputs = 14 | inputs@{ flake-parts, ... }: 15 | flake-parts.lib.mkFlake { inherit inputs; } { 16 | systems = [ 17 | "x86_64-linux" 18 | "x86_64-darwin" 19 | "aarch64-darwin" 20 | ]; 21 | 22 | perSystem = 23 | { 24 | config, 25 | self', 26 | inputs', 27 | pkgs, 28 | system, 29 | ... 30 | }: 31 | { 32 | _module.args.pkgs = import inputs.nixpkgs { 33 | inherit system; 34 | overlays = [ 35 | (import inputs.rust-overlay) 36 | ]; 37 | }; 38 | 39 | devShells.default = pkgs.mkShell { 40 | buildInputs = with pkgs; [ 41 | nil 42 | (rust-bin.stable.latest.default.override { 43 | extensions = [ "rust-src" ]; 44 | }) 45 | rust-analyzer 46 | ]; 47 | }; 48 | 49 | packages.default = pkgs.callPackage ./nix/build.nix { 50 | doCheck = false; 51 | }; 52 | 53 | formatter = pkgs.nixfmt-rfc-style; 54 | 55 | checks = { 56 | cargo-test = pkgs.callPackage ./nix/build.nix { }; 57 | cargo-fmt-check = pkgs.callPackage ./nix/cargo-fmt-check.nix { }; 58 | nixfmt-check = pkgs.callPackage ./nix/nixfmt-check.nix { }; 59 | clippy-check = pkgs.callPackage ./nix/clippy-check.nix { }; 60 | actionlint-check = pkgs.callPackage ./nix/actionlint-check.nix { }; 61 | }; 62 | 63 | apps.default = { 64 | type = "app"; 65 | program = "${self'.packages.default}/bin/brack"; 66 | }; 67 | 68 | apps.brack-release = { 69 | type = "app"; 70 | program = "${self'.packages.default}/bin/brack-release"; 71 | }; 72 | }; 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /nix/actionlint-check.nix: -------------------------------------------------------------------------------- 1 | { 2 | runCommand, 3 | actionlint, 4 | }: 5 | runCommand "actionlint-check" 6 | { 7 | buildInputs = [ 8 | actionlint 9 | ]; 10 | src = ../.; 11 | } 12 | '' 13 | cp -r "$src"/. . 14 | mkdir -p $out 15 | actionlint .github/workflows/* 16 | '' 17 | -------------------------------------------------------------------------------- /nix/build.nix: -------------------------------------------------------------------------------- 1 | { 2 | makeRustPlatform, 3 | rust-bin, 4 | pkg-config, 5 | openssl, 6 | doCheck ? true, 7 | }: 8 | let 9 | toolchain = rust-bin.stable.latest.default; 10 | rustPlatform = makeRustPlatform { 11 | cargo = toolchain; 12 | rustc = toolchain; 13 | }; 14 | in 15 | rustPlatform.buildRustPackage { 16 | pname = "brack"; 17 | version = "0.2.0"; 18 | 19 | src = ../.; 20 | cargoLock.lockFile = ../Cargo.lock; 21 | 22 | buildInputs = [ 23 | openssl 24 | openssl.dev 25 | ]; 26 | 27 | nativeBuildInputs = [ pkg-config ]; 28 | 29 | inherit doCheck; 30 | } 31 | -------------------------------------------------------------------------------- /nix/cargo-fmt-check.nix: -------------------------------------------------------------------------------- 1 | { 2 | runCommand, 3 | rust-bin, 4 | }: 5 | let 6 | toolchain = rust-bin.stable.latest.default; 7 | in 8 | runCommand "cargo-fmt-check" 9 | { 10 | buildInputs = [ 11 | toolchain 12 | ]; 13 | src = ../.; 14 | } 15 | '' 16 | mkdir -p $out 17 | cargo fmt --all --check --manifest-path $src/Cargo.toml 18 | '' 19 | -------------------------------------------------------------------------------- /nix/clippy-check.nix: -------------------------------------------------------------------------------- 1 | { 2 | makeRustPlatform, 3 | rust-bin, 4 | openssl, 5 | pkg-config, 6 | }: 7 | let 8 | toolchain = rust-bin.stable.latest.default; 9 | rustPlatform = makeRustPlatform { 10 | cargo = toolchain; 11 | rustc = toolchain; 12 | }; 13 | in 14 | rustPlatform.buildRustPackage { 15 | pname = "brack"; 16 | version = "0.2.0"; 17 | 18 | src = ../.; 19 | cargoLock.lockFile = ../Cargo.lock; 20 | 21 | buildInputs = [ 22 | openssl 23 | openssl.dev 24 | ]; 25 | 26 | nativeBuildInputs = [ pkg-config ]; 27 | 28 | checkPhase = '' 29 | cargo clippy --all-features -- -D warnings 30 | ''; 31 | } 32 | -------------------------------------------------------------------------------- /nix/nixfmt-check.nix: -------------------------------------------------------------------------------- 1 | { 2 | runCommand, 3 | nixfmt-rfc-style, 4 | }: 5 | runCommand "nixfmt-check" 6 | { 7 | buildInputs = [ 8 | nixfmt-rfc-style 9 | ]; 10 | src = ../.; 11 | } 12 | '' 13 | mkdir -p $out 14 | find . -name '*.nix' -exec nixfmt-rfc-style --check {} + 15 | '' 16 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | 4 | # Added by cargo 5 | # 6 | # already existing elements were commented out 7 | 8 | #/target 9 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.89" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 10 | 11 | [[package]] 12 | name = "brack-pdk-rs" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "anyhow", 16 | "serde", 17 | "serde_json", 18 | ] 19 | 20 | [[package]] 21 | name = "itoa" 22 | version = "1.0.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 25 | 26 | [[package]] 27 | name = "memchr" 28 | version = "2.7.4" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 31 | 32 | [[package]] 33 | name = "proc-macro2" 34 | version = "1.0.87" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" 37 | dependencies = [ 38 | "unicode-ident", 39 | ] 40 | 41 | [[package]] 42 | name = "quote" 43 | version = "1.0.37" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 46 | dependencies = [ 47 | "proc-macro2", 48 | ] 49 | 50 | [[package]] 51 | name = "ryu" 52 | version = "1.0.18" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 55 | 56 | [[package]] 57 | name = "serde" 58 | version = "1.0.210" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 61 | dependencies = [ 62 | "serde_derive", 63 | ] 64 | 65 | [[package]] 66 | name = "serde_derive" 67 | version = "1.0.210" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 70 | dependencies = [ 71 | "proc-macro2", 72 | "quote", 73 | "syn", 74 | ] 75 | 76 | [[package]] 77 | name = "serde_json" 78 | version = "1.0.128" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 81 | dependencies = [ 82 | "itoa", 83 | "memchr", 84 | "ryu", 85 | "serde", 86 | ] 87 | 88 | [[package]] 89 | name = "syn" 90 | version = "2.0.79" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 93 | dependencies = [ 94 | "proc-macro2", 95 | "quote", 96 | "unicode-ident", 97 | ] 98 | 99 | [[package]] 100 | name = "unicode-ident" 101 | version = "1.0.13" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 104 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brack-pdk-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.86" 8 | serde = { version = "1.0.203", features = ["derive"] } 9 | serde_json = "1.0.117" 10 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Mutsuha Asada 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/README-ja.md: -------------------------------------------------------------------------------- 1 | # brack-rs-pdk 2 | [Brack](https://github.com/brack-lang/brack)のプラグインをRustで開発するためのユーティリティを提供します。 3 | 4 | ## LICENSE 5 | MIT OR Apache-2.0 6 | 7 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/README.md: -------------------------------------------------------------------------------- 1 | # brack-pdk-rs 2 | The crate provides utilities for developing [Brack](https://github.com/brack-lang/brack) plugins with Rust. 3 | 4 | ## LICENSE 5 | MIT OR Apache-2.0 6 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1726560853, 9 | "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1728018373, 24 | "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "bc947f541ae55e999ffdb4013441347d83b00feb", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs", 41 | "rust-overlay": "rust-overlay" 42 | } 43 | }, 44 | "rust-overlay": { 45 | "inputs": { 46 | "nixpkgs": [ 47 | "nixpkgs" 48 | ] 49 | }, 50 | "locked": { 51 | "lastModified": 1728354625, 52 | "narHash": "sha256-r+Sa1NRRT7LXKzCaVaq75l1GdZcegODtF06uaxVVVbI=", 53 | "owner": "oxalica", 54 | "repo": "rust-overlay", 55 | "rev": "d216ade5a0091ce60076bf1f8bc816433a1fc5da", 56 | "type": "github" 57 | }, 58 | "original": { 59 | "owner": "oxalica", 60 | "repo": "rust-overlay", 61 | "type": "github" 62 | } 63 | }, 64 | "systems": { 65 | "locked": { 66 | "lastModified": 1681028828, 67 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 68 | "owner": "nix-systems", 69 | "repo": "default", 70 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "nix-systems", 75 | "repo": "default", 76 | "type": "github" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Brack plugins development kit (pdk) for Rust"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 | rust-overlay = { 7 | url = "github:oxalica/rust-overlay"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | flake-utils.url = "github:numtide/flake-utils"; 11 | }; 12 | 13 | outputs = { 14 | self, 15 | nixpkgs, 16 | rust-overlay, 17 | flake-utils, 18 | }: 19 | flake-utils.lib.eachDefaultSystem ( 20 | system: let 21 | pkgs = import nixpkgs { 22 | inherit system; 23 | overlays = [ 24 | (import rust-overlay) 25 | ]; 26 | }; 27 | toolchain = pkgs.rust-bin.stable.latest.default; 28 | in { 29 | devShells.default = pkgs.mkShell { 30 | buildInputs = with pkgs; 31 | [ 32 | alejandra 33 | nil 34 | toolchain 35 | rust-analyzer 36 | crate2nix 37 | pkg-config 38 | openssl 39 | openssl.dev 40 | ] 41 | ++ pkgs.lib.optional pkgs.stdenv.isDarwin [ 42 | darwin.Security 43 | darwin.apple_sdk.frameworks.SystemConfiguration 44 | ]; 45 | }; 46 | } 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod metadata; 2 | pub mod types; 3 | pub mod values; -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/src/metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Type; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Serialize, Deserialize, Clone)] 5 | pub struct Metadata { 6 | pub command_name: String, 7 | pub call_name: String, 8 | pub argument_types: Vec<(String, Type)>, 9 | pub return_type: Type, 10 | } 11 | 12 | impl Metadata { 13 | pub fn new(command_name: String, call_name: String, argument_types: Vec<(String, Type)>, return_type: Type) -> Metadata { 14 | Metadata { 15 | command_name, 16 | call_name, 17 | argument_types, 18 | return_type, 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/src/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] 4 | pub enum Type { 5 | TInline, 6 | TOption(Box), 7 | TBlock, 8 | TArray(Box), 9 | TInlineCmd(String), 10 | TBlockCmd(String), 11 | TAST, 12 | } -------------------------------------------------------------------------------- /pdk/brack-pdk-rs/src/values.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone)] 4 | pub enum Value { 5 | Text(String), 6 | TextArray(Vec), 7 | TextOption(Option), 8 | } -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import ( 2 | let 3 | lock = builtins.fromJSON (builtins.readFile ./flake.lock); 4 | in 5 | fetchTarball { 6 | url = 7 | lock.nodes.flake-compat.locked.url 8 | or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 9 | sha256 = lock.nodes.flake-compat.locked.narHash; 10 | } 11 | ) { src = ./.; }).shellNix 12 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use cli::{Cli, SubCommands}; 3 | use std::env; 4 | 5 | mod cli; 6 | mod logger; 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | let cli = Cli::parse(); 11 | let raw_command = env::args().collect::>().join(" "); 12 | match cli.subcommand.clone() { 13 | SubCommands::Create { 14 | path, 15 | plugin, 16 | document, 17 | } => cli.create(raw_command, path, plugin, document), 18 | SubCommands::Build => cli.build(raw_command).await, 19 | SubCommands::Clean { dry_run } => cli.clean(raw_command, dry_run), 20 | SubCommands::Plugin(plugin) => cli.plugin(&plugin), 21 | SubCommands::Channel(channel) => cli.channel(raw_command, &channel).await, 22 | SubCommands::LanguageServer => cli.language_server(), 23 | } 24 | } 25 | --------------------------------------------------------------------------------