├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── build.yaml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── img ├── builtInHover.png ├── compile.png ├── completion.png ├── logOut.png ├── outLine.png ├── setTheme.png └── userSet.png ├── jest.config.js ├── language-configuration.json ├── logo.png ├── package-lock.json ├── package.json ├── snippets └── snippets.code-snippets ├── src ├── ast │ ├── assetScript.ts │ ├── ast.ts │ ├── base.ts │ ├── builtInType.ts │ ├── context.ts │ ├── contract.ts │ ├── enum.ts │ ├── event.ts │ ├── finder.ts │ ├── identifier.ts │ ├── interface.ts │ ├── kinder.ts │ ├── method.ts │ ├── position.ts │ ├── project.ts │ ├── property.ts │ ├── root.ts │ ├── scope.ts │ ├── txScript.ts │ ├── variable.ts │ ├── visibility.ts │ └── word.ts ├── cache │ └── cache.ts ├── commander │ └── commands.ts ├── compiler │ └── compiler.ts ├── config │ └── global.ts ├── deploy │ ├── build.ts │ ├── config.ts │ └── deploy.ts ├── diagnostics.ts ├── downloader │ ├── config.ts │ └── downloader.ts ├── event.ts ├── extension.ts ├── formatter │ └── formatter.ts ├── logger │ └── logger.ts ├── parser │ ├── RalphLexer.g4 │ ├── RalphLexer.ts │ ├── RalphParser.g4 │ ├── RalphParser.ts │ ├── RalphParserListener.ts │ ├── RalphParserVisitor.ts │ └── parser.ts ├── prettier │ ├── defaultOptions.ts │ ├── index.ts │ ├── languages.ts │ ├── loc.ts │ ├── options.ts │ ├── parsers.ts │ ├── plugin.ts │ ├── prettier.ts │ └── printers.ts ├── provider │ ├── builtIn │ │ ├── contractBuiltIn.ts │ │ ├── func.ts │ │ └── ralph-built-in-functions.json │ ├── completion │ │ ├── annotationProvider.ts │ │ ├── builtInProvider.ts │ │ ├── contractBuiltinProvider.ts │ │ ├── emitProvider.ts │ │ ├── enumProvider.ts │ │ ├── globalProvider.ts │ │ ├── identifierProvider.ts │ │ └── memberProvider.ts │ ├── definitionProvider.ts │ ├── filter.ts │ ├── hover │ │ ├── builtIn │ │ │ ├── function.ts │ │ │ ├── item.ts │ │ │ ├── keyword.ts │ │ │ └── primitives.ts │ │ ├── hoverProvider.ts │ │ └── providers.ts │ ├── implementationProvider.ts │ ├── referenceProvider.ts │ ├── renameProvider.ts │ ├── signatureHelpProvider.ts │ ├── symbolProvider.ts │ ├── typeDefinitionProvider.ts │ └── typeHierarchyProvider.ts ├── util │ ├── kind.ts │ └── util.ts └── visitors │ └── ralphVisitor.ts ├── syntaxes └── ralph.tmLanguage.json ├── tests ├── artifacts │ ├── .project.json │ ├── add │ │ └── add.ral.json │ ├── greeter │ │ └── greeter.ral.json │ ├── greeter_main.ral.json │ ├── main.ral.json │ ├── sub │ │ └── sub.ral.json │ └── test │ │ ├── metadata.ral.json │ │ └── warnings.ral.json └── contracts │ ├── add │ └── add.ral │ ├── greeter │ ├── greeter.ral │ └── greeter_interface.ral │ ├── greeter_main.ral │ ├── main.ral │ ├── sub │ └── sub.ral │ └── test │ ├── metadata.ral │ └── warnings.ral ├── tsconfig.json └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | src/parser -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* .eslintrc.js */ 2 | module.exports = { 3 | extends: [ 4 | 'eslint-config-airbnb-base', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended', 7 | 'plugin:import/recommended', 8 | 'plugin:import/typescript', 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['@typescript-eslint'], 12 | rules: { 13 | 'import/extensions': 'off', 14 | 'no-use-before-define': 'off', 15 | '@typescript-eslint/no-var-requires': 'off', 16 | '@typescript-eslint/no-explicit-any': 'off', 17 | '@typescript-eslint/no-empty-function': 'off', 18 | 'no-prototype-builtins': 'off', 19 | 'class-methods-use-this': 'off', 20 | 'import/prefer-default-export': 'off', 21 | 'no-shadow': 'off', 22 | '@typescript-eslint/no-use-before-define': 'off', 23 | '@typescript-eslint/no-use-before-declare': 'off', 24 | 'no-param-reassign': 'off', 25 | 'no-underscore-dangle': 'off', 26 | }, 27 | settings: { 28 | 'import/resolver': { 29 | node: { 30 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 31 | moduleDirectory: ['node_modules', './src'], 32 | }, 33 | }, 34 | }, 35 | // parserOptions: { 36 | // project: './tsconfig.json', 37 | // }, 38 | } 39 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: weekly 7 | day: sunday 8 | time: '01:00' 9 | labels: 10 | - scope:dependency 11 | target-branch: 'main' 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow that is manually triggered 2 | name: BUILD 3 | 4 | # Controls when the action will run. Workflow runs when manually triggered using the UI 5 | # or API. 6 | on: [push, pull_request] 7 | 8 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 9 | jobs: 10 | automake: 11 | timeout-minutes: 60 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: 16 | - 'ubuntu-latest' 17 | node: 18 | # Run tests on minimal version we support 19 | - '18.10.0' 20 | NPM_CLIENT: 21 | - 'pnpm' 22 | env: 23 | INSTALL_PACKAGE: true 24 | NPM_CLIENT: ${{ matrix.NPM_CLIENT }} 25 | name: Test with ${{ matrix.NPM_CLIENT }} 26 | runs-on: ${{ matrix.os }} 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v3 33 | with: 34 | node-version: ${{ matrix.node }} 35 | 36 | - uses: pnpm/action-setup@v2 37 | name: Install pnpm 38 | id: pnpm-install 39 | with: 40 | version: 7 41 | 42 | - name: build 43 | run: make build 44 | 45 | - name: Upload artifact bin 46 | uses: actions/upload-artifact@v3 47 | with: 48 | name: ralph-vscode 49 | path: | 50 | ralph-vscode-*.vsix 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.vsix 3 | dist 4 | ralphc.jar 5 | .idea 6 | yarn-error.log 7 | .DS_Store 8 | 9 | *.interp 10 | *.java 11 | *.tokens 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | snippets/snippets.code-snippets 4 | src/parser 5 | tests 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "trailingComma": "es5", 6 | "printWidth": 140 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.formatOnSave": true, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll": true 7 | }, 8 | "[javascript]": { 9 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | vsc-extension-quickstart.md 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "ralph" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | compile: 2 | npm run compile 3 | 4 | build: dep compile package 5 | 6 | package: compile 7 | ./node_modules/.bin/vsce package --no-dependencies 8 | 9 | dep: 10 | npm install 11 | 12 | publish: 13 | ./node_modules/.bin/vsce publish 14 | 15 | clean: 16 | rm -rf ./ralph-vscode-*.vsix 17 | rm -rf ./dist 18 | rm -rf node_modules 19 | 20 | fmt: 21 | npm run fmt 22 | 23 | lint: 24 | npm run lint 25 | 26 | antlr4ts: 27 | npm run antlr4ts 28 | 29 | 30 | test: 31 | npm run test 32 | 33 | downLoadBuildIn: 34 | wget https://raw.githubusercontent.com/alephium/alephium/master/protocol/src/main/resources/ralph-built-in-functions.json -O ./src/provider/builtIn/ralph-built-in-functions.json 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ralph Language for VS Code 2 | 3 | ## Overview 4 | 5 | Ralph is the language used in alephium to write smart contracts. 6 | 7 | ## Features 8 | 9 | - Friendly theme setting ✅ 10 | - Highlighting ✅ 11 | - Snippet ✅ 12 | - Formatting ✅(simple format) 13 | - Compile ✅ ( ctl[cmd] + shift + p => Ralph: compile ) 14 | - IntelliSense ✅ 15 | - Provide a hover for the given code position. ✅ 16 | - Document symbol provider. ✅ 17 | - Rename Symbol. ✅ 18 | - Symbol completion. ✅ 19 | - Find All symbol. ✅ 20 | - Go to symbol. ✅ 21 | - Peek symbol. ✅ 22 | - ... 23 | - Linting. ✅ 24 | 25 | ## Extension Settings 26 | 27 | ### Configuration 28 | 29 | ![img.png](img/userSet.png) 30 | 31 | ## Guide 32 | 33 | ### Compile 34 | 35 | ![img.png](./img/compile.png) 36 | 37 | ### Hover 38 | 39 | ![avatar](./img/builtInHover.png) 40 | 41 | ### Outline 42 | 43 | ![img.png](./img/outLine.png) 44 | 45 | ### Completion 46 | 47 | ![img.png](./img/completion.png) 48 | 49 | ### Log output 50 | 51 | ![img.png](./img/logOut.png) 52 | 53 | #### More command compile 54 | 55 | https://github.com/alephium/alephium 56 | 57 | ## FAQ 58 | 59 | 60 | 61 | ## References 62 | 63 | 64 | -------------------------------------------------------------------------------- /img/builtInHover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alephium/ralph-vscode/c0bc6d4f14a16b44892cb11362b77266d6b57cdf/img/builtInHover.png -------------------------------------------------------------------------------- /img/compile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alephium/ralph-vscode/c0bc6d4f14a16b44892cb11362b77266d6b57cdf/img/compile.png -------------------------------------------------------------------------------- /img/completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alephium/ralph-vscode/c0bc6d4f14a16b44892cb11362b77266d6b57cdf/img/completion.png -------------------------------------------------------------------------------- /img/logOut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alephium/ralph-vscode/c0bc6d4f14a16b44892cb11362b77266d6b57cdf/img/logOut.png -------------------------------------------------------------------------------- /img/outLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alephium/ralph-vscode/c0bc6d4f14a16b44892cb11362b77266d6b57cdf/img/outLine.png -------------------------------------------------------------------------------- /img/setTheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alephium/ralph-vscode/c0bc6d4f14a16b44892cb11362b77266d6b57cdf/img/setTheme.png -------------------------------------------------------------------------------- /img/userSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alephium/ralph-vscode/c0bc6d4f14a16b44892cb11362b77266d6b57cdf/img/userSet.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | moduleDirectories: ['node_modules', 'src', 'tests'], 6 | verbose: true, 7 | } 8 | -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//" 4 | }, 5 | "brackets": [ 6 | ["{", "}"], 7 | ["[", "]"], 8 | ["(", ")"] 9 | ], 10 | "autoClosingPairs": [ 11 | { "open": "{", "close": "}" }, 12 | { "open": "[", "close": "]" }, 13 | { "open": "(", "close": ")" }, 14 | { "open": "'", "close": "'", "notIn": ["string", "comment"] }, 15 | { "open": "\"", "close": "\"", "notIn": ["string"] }, 16 | { "open": "`", "close": "`", "notIn": ["string", "comment"] } 17 | ], 18 | "surroundingPairs": [ 19 | ["{", "}"], 20 | ["[", "]"], 21 | ["(", ")"], 22 | ["'", "'"], 23 | ["\"", "\""], 24 | ["`", "`"] 25 | ], 26 | "folding": { 27 | "markers": { 28 | "start": "^\\s*//\\s*#?region\\b", 29 | "end": "^\\s*//\\s*#?endregion\\b" 30 | } 31 | }, 32 | "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", 33 | "indentationRules": { 34 | "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", 35 | "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\)\\}\\]].*$" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alephium/ralph-vscode/c0bc6d4f14a16b44892cb11362b77266d6b57cdf/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ralph-vscode-alephium", 3 | "displayName": "Ralph (Alephium)", 4 | "description": "Alephium Ralph language for Visual Studio code", 5 | "version": "0.5.2", 6 | "engines": { 7 | "vscode": "^1.69.0", 8 | "node": ">=v18.0.0" 9 | }, 10 | "keywords": [ 11 | "ralph", 12 | "RALPH", 13 | "Ralph", 14 | "plugin", 15 | "prettier", 16 | "alephium" 17 | ], 18 | "publisher": "alephium", 19 | "author": "Alephium devs ", 20 | "contributors": [ 21 | { 22 | "email": "syl416419@gmail.com", 23 | "name": "suyanlong" 24 | } 25 | ], 26 | "license": "GUN3", 27 | "bugs": { 28 | "url": "https://github.com/alephium/ralph-vscode/issues" 29 | }, 30 | "homepage": "https://github.com/alephium/ralph-vscode#readme", 31 | "repository": "git+https://github.com/alephium/ralph-vscode.git", 32 | "categories": [ 33 | "Programming Languages", 34 | "Snippets", 35 | "Formatters", 36 | "Linters", 37 | "Other" 38 | ], 39 | "icon": "logo.png", 40 | "main": "./dist/extension.js", 41 | "activationEvents": [ 42 | "onLanguage:ralph", 43 | "onCommand:ralph.compile", 44 | "onCommand:ralph.deploy" 45 | ], 46 | "contributes": { 47 | "commands": [ 48 | { 49 | "command": "ralph.compile", 50 | "title": "Ralph Compile" 51 | }, 52 | { 53 | "command": "ralph.deploy", 54 | "title": "Ralph Deploy To Devnet" 55 | } 56 | ], 57 | "configuration": { 58 | "title": "Ralph settings", 59 | "properties": { 60 | "ralph.compile.cliVersion": { 61 | "type": "string", 62 | "default": "latest", 63 | "description": "The version of CLI for compilation" 64 | }, 65 | "ralph.compile.command": { 66 | "type": "string", 67 | "default": "npx --yes @alephium/cli compile -n devnet", 68 | "description": "The command to compile Ralph projects" 69 | } 70 | } 71 | }, 72 | "languages": [ 73 | { 74 | "id": "ralph", 75 | "aliases": [ 76 | "ralph", 77 | "RALPH", 78 | "Ralph" 79 | ], 80 | "extensions": [ 81 | ".ral" 82 | ], 83 | "configuration": "./language-configuration.json", 84 | "icon": { 85 | "light": "./logo.png", 86 | "dark": "./logo.png" 87 | } 88 | } 89 | ], 90 | "menus": { 91 | "editor/context": [ 92 | { 93 | "command": "ralph.compile", 94 | "when": "resourceLangId == ralph", 95 | "group": "1_ralph@1" 96 | }, 97 | { 98 | "command": "ralph.deploy", 99 | "when": "resourceLangId == ralph", 100 | "group": "1_ralph@2" 101 | } 102 | ] 103 | }, 104 | "grammars": [ 105 | { 106 | "language": "ralph", 107 | "scopeName": "source.ral", 108 | "path": "./syntaxes/ralph.tmLanguage.json" 109 | } 110 | ], 111 | "snippets": [ 112 | { 113 | "language": "ralph", 114 | "path": "./snippets/snippets.code-snippets" 115 | } 116 | ] 117 | }, 118 | "config": { 119 | "alephium_version": "2.1.0" 120 | }, 121 | "scripts": { 122 | "vscode:prepublish": "pnpm run package", 123 | "compile": "webpack", 124 | "watch": "webpack --watch", 125 | "package": "webpack --mode production --devtool hidden-source-map", 126 | "test-compile": "tsc -p ./", 127 | "test-watch": "tsc -watch -p ./", 128 | "lint": "eslint src --ext ts,js --fix", 129 | "fmt": "prettier --write .", 130 | "test": "jest", 131 | "antlr4ts": "antlr4ts -visitor ./src/parser/*.g4", 132 | "update-builtin-schema": "curl -o ./src/provider/builtin/ralph-built-in-functions.json https://raw.githubusercontent.com/alephium/alephium/v${npm_package_config_alephium_version}/protocol/src/main/resources/ralph-built-in-functions.json" 133 | }, 134 | "dependencies": { 135 | "@alephium/cli": "^0.9.1", 136 | "@alephium/web3": "^0.9.1", 137 | "antlr4ts": "0.5.0-alpha.4", 138 | "execa": "^6.1.0", 139 | "immutable": "^4.2.1", 140 | "node-fetch": "^3.3.0", 141 | "prettier": "^2.8.1", 142 | "request": "^2.88.2", 143 | "request-progress": "^3.0.0", 144 | "request-promise-native": "^1.0.9" 145 | }, 146 | "devDependencies": { 147 | "@types/chai": "^4.3.4", 148 | "@types/crypto-js": "^4.1.1", 149 | "@types/node": "^18.11.18", 150 | "@types/prettier": "^2.7.2", 151 | "@types/request": "^2.48.8", 152 | "@types/request-promise-native": "^1.0.18", 153 | "@types/vscode": "1.69.0", 154 | "@typescript-eslint/eslint-plugin": "^5.48.0", 155 | "@typescript-eslint/parser": "^5.48.0", 156 | "antlr4ts-cli": "0.5.0-alpha.4", 157 | "assert": "^2.0.0", 158 | "chai": "^4.3.7", 159 | "eslint": "8.22.0", 160 | "eslint-config-airbnb-base": "^15.0.0", 161 | "eslint-config-prettier": "^8.6.0", 162 | "eslint-import-resolver-typescript": "^3.5.2", 163 | "eslint-import-resolver-webpack": "^0.13.2", 164 | "eslint-plugin-import": "^2.26.0", 165 | "eslint-plugin-prettier": "^4.2.1", 166 | "jest": "^29.3.1", 167 | "prettier-eslint": "^15.0.1", 168 | "ts-jest": "^29.0.3", 169 | "ts-loader": "^9.4.2", 170 | "typescript": "^4.9.4", 171 | "vsce": "^2.15.0", 172 | "webpack": "^5.75.0", 173 | "webpack-cli": "^4.10.0" 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /snippets/snippets.code-snippets: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "TODO": { 4 | "prefix": "todo", 5 | "body": [ 6 | "// TODO ${1:bug}" 7 | ], 8 | "description": "todo" 9 | }, 10 | "code block": { 11 | "prefix": "block", 12 | "body": [ 13 | "{", 14 | "\t$1", 15 | "}" 16 | ], 17 | "description": "region" 18 | }, 19 | "// #region // #endregion": { 20 | "prefix": "region", 21 | "body": [ 22 | "// #region ${1}\n ${2}\n// #endregion" 23 | ], 24 | "description": "region" 25 | }, 26 | "while () {}": { 27 | "prefix": "while", 28 | "body": [ 29 | "while ($1) {", 30 | "\t$2", 31 | "}" 32 | ], 33 | "description": "while" 34 | }, 35 | "while () { }": { 36 | "prefix": "wh", 37 | "body": [ 38 | "while ($1) {", 39 | "\t$2", 40 | "}" 41 | ], 42 | "description": "while" 43 | }, 44 | "loop {}": { 45 | "prefix": "loop", 46 | "body": [ 47 | "while ($1) {", 48 | "\t$2", 49 | "}" 50 | ], 51 | "description": "while" 52 | }, 53 | "for {}": { 54 | "prefix": "for", 55 | "body": [ 56 | "for (let mut $1; $2; $3) {", 57 | "\t$4", 58 | "}" 59 | ], 60 | "description": "while" 61 | }, 62 | "if {}else {}": { 63 | "prefix": "ife", 64 | "body": [ 65 | "if ($1) {", 66 | "\t$2", 67 | "}else {", 68 | "\t$3", 69 | "}" 70 | ], 71 | "description": "if else" 72 | }, 73 | "if {}else if {}": { 74 | "prefix": "ifeif", 75 | "body": [ 76 | "if ($1) {", 77 | "\t$2", 78 | "}else if ($3) {", 79 | "\t$4", 80 | "}" 81 | ], 82 | "description": "if else if" 83 | }, 84 | "if {}": { 85 | "prefix": "if", 86 | "body": [ 87 | "if ($1) {", 88 | "\t$2", 89 | "}" 90 | ], 91 | "description": "if {}" 92 | }, 93 | //---------------------------------------------------------------------------------------------------------------- 94 | "@using($1)": { 95 | "prefix": "using", 96 | "body": [ 97 | "@using($1)" 98 | ], 99 | "description": "using" 100 | }, 101 | "import $1": { 102 | "prefix": "import", 103 | "body": [ 104 | "import \"$1\"" 105 | ], 106 | "description": "import" 107 | }, 108 | "let $1 = $2": { 109 | "prefix": "let", 110 | "body": [ 111 | "let $1 = $2" 112 | ], 113 | "description": "let" 114 | }, 115 | "let mut $1 = $2": { 116 | "prefix": "letm", 117 | "body": [ 118 | "let mut $1 = $2" 119 | ], 120 | "description": "let mut" 121 | }, 122 | "mut $1: $2": { 123 | "prefix": "mut", 124 | "body": [ 125 | "mut $1: $2" 126 | ], 127 | "description": "mut" 128 | }, 129 | 130 | "event $1($2)": { 131 | "prefix": "event", 132 | "body": [ 133 | "event $1($2)" 134 | ], 135 | "description": "event" 136 | }, 137 | "emit $1($2)": { 138 | "prefix": "emit", 139 | "body": [ 140 | "emit $1($2)" 141 | ], 142 | "description": "emit" 143 | }, 144 | //---------------------------------------------------------------------------------------------------------------- 145 | "pub fn main() -> () {}": { 146 | "prefix": "main", 147 | "body": [ 148 | "pub fn main() -> ${1:()} {", 149 | "\t$2", 150 | "}" 151 | ], 152 | "description": "To create an main function" 153 | }, 154 | "pub fn $1(arg: $2) -> $3": { 155 | "prefix": "fn", 156 | "body": [ 157 | "pub fn $1(arg: $2) -> ($3) {", 158 | "\t$4", 159 | "\treturn $5", 160 | "}" 161 | ], 162 | "description": "To create an function" 163 | }, 164 | "fn (arg: type) -> (type) {return}": { 165 | "prefix": "fnr", 166 | "body": [ 167 | "pub fn $1(arg: $2) -> ($3) {", 168 | "\t$4", 169 | "\treturn $5", 170 | "}" 171 | ], 172 | "description": "To create an function" 173 | }, 174 | "pub fn (arg: type) -> (type) {return}": { 175 | "prefix": "pubfn", 176 | "body": [ 177 | "pub fn $1(arg: $2) -> ($3) {", 178 | "\t$4", 179 | "\treturn $5", 180 | "}" 181 | ], 182 | "description": "To create an function" 183 | }, 184 | 185 | //---------------------------------------------------------------------------------------------------------------- 186 | "create an interface": { 187 | "prefix": "interface", 188 | "body": [ 189 | "// To create an interface:", 190 | "Interface $1 {", 191 | "\tpub fn $2() -> ($3)", 192 | "\tpub fn $4() -> ($5)", 193 | "}" 194 | ], 195 | "description": "To create an interface" 196 | }, 197 | "create AssetScript": { 198 | "prefix": "AssetScript", 199 | "body": [ 200 | "// create contract:", 201 | "AssetScript $1 {", 202 | "\tevent $2($3)", 203 | "\tpub fn $4() -> ($5)", 204 | "\tpub fn $6() -> ($7)", 205 | "}" 206 | ], 207 | "description": "create Contract" 208 | }, 209 | "create contract": { 210 | "prefix": "Contract", 211 | "body": [ 212 | "// create contract:", 213 | "Contract $1 {", 214 | "\tevent $2($3)", 215 | "\tpub fn $4() -> ($5)", 216 | "\tpub fn $6() -> ($7)", 217 | "}" 218 | ], 219 | "description": "create contract" 220 | }, 221 | "create TxScript": { 222 | "prefix": "TxScript", 223 | "body": [ 224 | "// invoke contract:", 225 | "TxScript $1 {", 226 | "\tpub fn main() -> () {", 227 | "\t\t$2", 228 | "\t}", 229 | "}" 230 | ], 231 | "description": "create TxScript" 232 | }, 233 | "create script": { 234 | "prefix": "script", 235 | "body": [ 236 | "// invoke contract:", 237 | "TxScript $1 {", 238 | "\tpub fn main() -> () {", 239 | "\t\t$2", 240 | "\t}", 241 | "}" 242 | ], 243 | "description": "create TxScript" 244 | }, 245 | //---------------------------------------------------------------------------------------------------------------- 246 | "create contract extends contract or interface": { 247 | "prefix": "extends", 248 | "body": [ 249 | "// create contract:", 250 | "Contract $1 extends $2{", 251 | "\tevent $3($4)", 252 | "\tpub fn $5() -> ($6)", 253 | "\tpub fn $7() -> ($8)", 254 | "}" 255 | ], 256 | "description": "create contract" 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/ast/assetScript.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, SymbolKind } from 'vscode' 2 | import { Base } from './base' 3 | 4 | export class AssetScript extends Base { 5 | symbolKind(): SymbolKind { 6 | return SymbolKind.Class 7 | } 8 | 9 | completionItemKind(): CompletionItemKind { 10 | return CompletionItemKind.Class 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/ast.ts: -------------------------------------------------------------------------------- 1 | import { Token } from 'antlr4ts/Token' 2 | import * as vscode from 'vscode' 3 | import { 4 | CompletionItem, 5 | CompletionItemKind, 6 | CompletionItemLabel, 7 | DocumentSymbol, 8 | Hover, 9 | Location, 10 | MarkdownString, 11 | Range, 12 | SymbolInformation, 13 | SymbolKind, 14 | TypeHierarchyItem, 15 | Uri, 16 | } from 'vscode' 17 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 18 | import { RuleNode } from 'antlr4ts/tree/RuleNode' 19 | import { Interval } from 'antlr4ts/misc/Interval' 20 | import MapKinds from '../util/kind' 21 | import { Identifier } from './identifier' 22 | import { Word } from './word' 23 | import { Position } from './position' 24 | import { IdentifierKind, SemanticsKind } from './kinder' 25 | import { RalphParser } from '../parser/RalphParser' 26 | 27 | export class SemanticNode implements Identifier { 28 | name: string | undefined 29 | 30 | identifierKind: IdentifierKind | undefined 31 | 32 | semanticsKind: SemanticsKind | undefined 33 | 34 | kind: number | undefined 35 | 36 | _detail: string | undefined 37 | 38 | _sourceIntervalDetail: Interval | undefined 39 | 40 | uri: Uri | undefined 41 | 42 | /** * action scope ** */ 43 | range: Range | undefined 44 | 45 | parent: Identifier | undefined 46 | 47 | ruleContext: RuleNode | undefined 48 | 49 | token?: Token 50 | 51 | constructor(node?: TerminalNode) { 52 | if (node) { 53 | this.name = node.symbol.text! 54 | this._detail = this.name 55 | this.semanticsKind = SemanticsKind.Def 56 | this.identifierKind = IdentifierKind.Type 57 | this.setRange(node.symbol, node.symbol) 58 | this.token = node.symbol 59 | } 60 | } 61 | 62 | get point(): vscode.Position | undefined { 63 | if (this.token) this.convert(this.token) 64 | return undefined 65 | } 66 | 67 | convert(token: Token): vscode.Position { 68 | return new vscode.Position(token.line - 1, token.charPositionInLine) 69 | } 70 | 71 | setRange(begin: Token, end: Token | undefined): this { 72 | this.range = new vscode.Range(this.convert(begin), this.convert(end ?? begin)) 73 | return this 74 | } 75 | 76 | findAll(condition: Word): Identifier[] { 77 | if (condition.name === this.name) return [this] 78 | return [] 79 | } 80 | 81 | find?(word: Word): Identifier | undefined { 82 | if (this.point?.isEqual(word.point!) && word.name === this.name) { 83 | return this 84 | } 85 | return undefined 86 | } 87 | 88 | container(position: Position): Identifier | undefined { 89 | if (this.contains(position)) { 90 | return this.parent 91 | } 92 | return undefined 93 | } 94 | 95 | isDef(): boolean { 96 | return this.semanticsKind === SemanticsKind.Def 97 | } 98 | 99 | isRef(): boolean { 100 | return this.semanticsKind === SemanticsKind.Ref 101 | } 102 | 103 | contains(position: Position): boolean { 104 | this.uri = this.getUri() 105 | if (this.uri && position.uri) { 106 | if (this.uri.path !== position.uri.path) { 107 | return false 108 | } 109 | } 110 | if (this.range) { 111 | return this.range.contains(position.point!) 112 | } 113 | return false 114 | } 115 | 116 | toString(): string { 117 | return `name: ${this.name}, 118 | detail: ${this.detail}, 119 | uri?.path: ${this.getUri()?.path ?? ''}, 120 | scope?.start.line: ${this.range?.start.line ?? 0}, 121 | scope?.end.line: ${this.range?.end.line ?? 0} 122 | ` 123 | } 124 | 125 | setParent(parent: Identifier): this { 126 | this.parent = parent 127 | return this 128 | } 129 | 130 | setIdentifierKind(identifierKind: IdentifierKind): this { 131 | this.identifierKind = identifierKind 132 | return this 133 | } 134 | 135 | setSemanticsKind(kind: SemanticsKind): this { 136 | this.semanticsKind = kind 137 | return this 138 | } 139 | 140 | getUri(): Uri | undefined { 141 | if (this.uri) { 142 | return this.uri 143 | } 144 | return this.parent?.getUri?.() 145 | } 146 | 147 | getChild(): Identifier[] { 148 | return [] 149 | } 150 | 151 | defs(): Identifier[] { 152 | return [] 153 | } 154 | 155 | ref(): Identifier[] { 156 | return [] 157 | } 158 | 159 | def(word: Word): Identifier | undefined { 160 | if (this.isDef() && word.name === this.name) { 161 | return this 162 | } 163 | return undefined 164 | } 165 | 166 | symbolKind(): SymbolKind { 167 | return MapKinds().get(this.kind!)! 168 | } 169 | 170 | completionItemLabel(): CompletionItemLabel { 171 | return { 172 | label: this.name!, 173 | // detail: this.label(), 174 | description: this.detail, 175 | } 176 | } 177 | 178 | label(): string { 179 | return this.name ?? 'undefined' 180 | } 181 | 182 | completionItemKind(): CompletionItemKind { 183 | return CompletionItemKind.Variable 184 | } 185 | 186 | documentSymbol(): DocumentSymbol { 187 | return new DocumentSymbol(this.label(), this.detail, this.symbolKind(), this.range!, this.range!) 188 | } 189 | 190 | symbolInformation(): SymbolInformation { 191 | return new SymbolInformation(this.label(), this.symbolKind(), this.range!, this.getUri()!, this.parent?.name) 192 | } 193 | 194 | completionItem(): CompletionItem { 195 | return new CompletionItem(this.completionItemLabel(), this.completionItemKind()) 196 | } 197 | 198 | hover(): Hover { 199 | return new vscode.Hover(new MarkdownString().appendMarkdown(`\r\t${this.hoverSymbolDetail()}\r\t`)) 200 | } 201 | 202 | location(): Location { 203 | return new Location(this.getUri()!, this.range!.start) 204 | } 205 | 206 | typeHierarchyItem(): TypeHierarchyItem { 207 | return new TypeHierarchyItem(this.symbolKind(), this.label(), this.detail, this.getUri()!, this.range!, this.range!) 208 | } 209 | 210 | parser(): RalphParser | undefined { 211 | return this.parent?.parser?.() 212 | } 213 | 214 | set detail(detail: string) { 215 | this._detail = detail 216 | } 217 | 218 | get detail(): string { 219 | const parser = this.parser() 220 | if (parser && this._sourceIntervalDetail) { 221 | return parser.inputStream.getText(this._sourceIntervalDetail) 222 | } 223 | if (parser && this.ruleContext) { 224 | return parser.inputStream.getText(this.ruleContext.sourceInterval) 225 | } 226 | if (this._detail) return this._detail 227 | return `${this.name}` 228 | } 229 | 230 | hoverSymbolDetail(): string { 231 | const parser = this.parser() 232 | if (parser && this.ruleContext) { 233 | return parser.inputStream.getText(this.ruleContext.sourceInterval) 234 | } 235 | if (parser && this._sourceIntervalDetail) { 236 | return parser.inputStream.getText(this._sourceIntervalDetail) 237 | } 238 | if (this._detail) return this._detail 239 | return `${this.name}` 240 | } 241 | 242 | setRuleContext(ctx: RuleNode): this { 243 | this.ruleContext = ctx 244 | return this 245 | } 246 | 247 | getWordRange(): Range | undefined { 248 | if (this.token) 249 | return new vscode.Range(this.convert(this.token), this.convert(this.token).translate({ characterDelta: this.name?.length })) 250 | return undefined 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/ast/base.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, DocumentSymbol, ParameterInformation, SignatureInformation, SymbolKind } from 'vscode' 2 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 3 | import { OrderedMap } from 'immutable' 4 | import { Identifier } from './identifier' 5 | import { IdentifierKind, SemanticsKind } from './kinder' 6 | import { SemanticNode } from './ast' 7 | import { Word } from './word' 8 | import { Finder } from './finder' 9 | import { Position } from './position' 10 | import { RalphParser } from '../parser/RalphParser' 11 | import { Property } from './property' 12 | 13 | export class Base extends SemanticNode implements Finder { 14 | // TODO Fix use set 15 | members: OrderedMap 16 | 17 | identifiers: Identifier[] 18 | 19 | _parser?: RalphParser 20 | 21 | constructor(node?: TerminalNode) { 22 | super(node) 23 | this.members = OrderedMap() 24 | this.kind = IdentifierKind.Type 25 | this.identifiers = [] 26 | } 27 | 28 | add(member: Identifier) { 29 | if (member && member.name) { 30 | this.members = this.members.set(member.name, member) 31 | } 32 | } 33 | 34 | append(...identifiers: Identifier[]) { 35 | identifiers.forEach((value) => value && this.identifiers.push(value)) 36 | } 37 | 38 | getChild(): Identifier[] { 39 | return Array.from(this.members.values()) 40 | } 41 | 42 | container(position: Position): Identifier | undefined { 43 | if (this.contains(position)) { 44 | for (const value of this.members.values()) { 45 | const obj = value.container?.(position) 46 | if (obj) return obj 47 | } 48 | return this 49 | } 50 | return undefined 51 | } 52 | 53 | findAll(identifier: Word): Identifier[] { 54 | const items: Identifier[] = [] 55 | if (this.name === identifier.name) items.push(this) 56 | this.members.forEach((member) => { 57 | const is = member.findAll?.(identifier) 58 | if (is) items.push(...is) 59 | }) 60 | this.identifiers.forEach((value) => { 61 | if (value.name === identifier.name) items.push(value) 62 | }) 63 | return items 64 | } 65 | 66 | find?(word: Word): Identifier | undefined { 67 | if (this.contains(word)) { 68 | for (const value of this.members.values()) { 69 | const member = value.find?.(word) 70 | if (member) return member 71 | } 72 | for (const value of this.identifiers.values()) { 73 | const member = value.find?.(word) 74 | if (member) return member 75 | } 76 | } 77 | return super.find?.(word) 78 | } 79 | 80 | defs(): Identifier[] { 81 | const members = Array.from(this.members.values()) 82 | const identifiers = this.identifiers.filter((value) => value && value.semanticsKind && value.semanticsKind === SemanticsKind.Def) 83 | return members.concat(identifiers) 84 | } 85 | 86 | def(word: Word): Identifier | undefined { 87 | if (this.contains(word)) { 88 | for (const member of this.members.values()) { 89 | const def = member.def?.(word) 90 | if (def) return def 91 | } 92 | const member = this.members.get(word.name!) 93 | if (member && member.isDef!() && member.contains!(word)) { 94 | return member 95 | } 96 | } 97 | return super.def(word) 98 | } 99 | 100 | ref(): Identifier[] { 101 | return Array.from(this.identifiers.values()) 102 | } 103 | 104 | symbolKind(): SymbolKind { 105 | return SymbolKind.Class 106 | } 107 | 108 | completionItemKind(): CompletionItemKind { 109 | return CompletionItemKind.Class 110 | } 111 | 112 | parser(): RalphParser | undefined { 113 | if (this._parser) { 114 | return this._parser 115 | } 116 | return super.parser() 117 | } 118 | 119 | documentSymbol(): DocumentSymbol { 120 | const doc = super.documentSymbol() 121 | doc.children = Array.from(this.members.values()).map((child) => child.documentSymbol!()) 122 | return doc 123 | } 124 | 125 | signatureInformation(): SignatureInformation { 126 | const signature = new SignatureInformation(this.detail) 127 | signature.parameters = Array.from(this.members.values()) 128 | .filter((value) => value instanceof Property) 129 | .map((value) => new ParameterInformation(value.name!)) 130 | return signature 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/ast/builtInType.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, CompletionItemLabel, SymbolKind } from 'vscode' 2 | import { SemanticNode } from './ast' 3 | 4 | export class BuiltInType extends SemanticNode { 5 | description: string | undefined 6 | 7 | constructor(name: string) { 8 | super() 9 | this.name = name 10 | this.description = this.detail 11 | } 12 | 13 | symbolKind(): SymbolKind { 14 | return SymbolKind.Class 15 | } 16 | 17 | completionItemKind(): CompletionItemKind { 18 | return CompletionItemKind.Class 19 | } 20 | 21 | completionItemLabel(): CompletionItemLabel { 22 | return { 23 | label: this.name!, 24 | // detail: this.detail, 25 | description: this.description, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ast/context.ts: -------------------------------------------------------------------------------- 1 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 2 | import { 3 | ArrayExprContext, 4 | BlockContext, 5 | CallChainContext, 6 | ElseStmtContext, 7 | EmitContext, 8 | ExpressionContext, 9 | ExpressionListContext, 10 | ForStmtContext, 11 | IfStmtContext, 12 | MethodCallContext, 13 | ParamContext, 14 | ParamListContext, 15 | PrimitiveTypeContext, 16 | ResultContext, 17 | ReturnStmtContext, 18 | SimpleStmtContext, 19 | StatementContext, 20 | TypeNameContext, 21 | VarDeclContext, 22 | VarDeclMultiContext, 23 | VarDeclSingleContext, 24 | VarNameContext, 25 | WhileStmtContext, 26 | } from '../parser/RalphParser' 27 | import { Identifier } from './identifier' 28 | import { IdentifierKind, SemanticsKind } from './kinder' 29 | import { SemanticNode } from './ast' 30 | import { Variable } from './variable' 31 | import { Property } from './property' 32 | 33 | export class Context { 34 | parent: Identifier 35 | 36 | constructor(parent: Identifier) { 37 | this.parent = parent 38 | } 39 | 40 | node(ctx: TerminalNode): Identifier { 41 | return new SemanticNode(ctx).setParent(this.parent) 42 | } 43 | 44 | refNode(ctx: TerminalNode): Identifier { 45 | return this.node(ctx).setSemanticsKind!(SemanticsKind.Ref) 46 | } 47 | 48 | defNode(ctx: TerminalNode): Identifier { 49 | return this.node(ctx).setSemanticsKind!(SemanticsKind.Def) 50 | } 51 | 52 | typeNode(typeNode: TerminalNode): Identifier { 53 | return this.refNode(typeNode).setIdentifierKind?.(IdentifierKind.Type).setSemanticsKind?.(SemanticsKind.Ref) 54 | } 55 | 56 | paramListContext(ctx: ParamListContext): Identifier[] { 57 | const identifiers: Identifier[] = [] 58 | ctx.param().forEach((param) => identifiers.push(...this.paramContext(param))) 59 | return identifiers 60 | } 61 | 62 | paramContext(ctx: ParamContext): Identifier[] { 63 | const value = new Property(ctx.IDENTIFIER()) 64 | value.setRuleContext(ctx) 65 | value.detail = ctx.text 66 | value.type_ = this.typeNameContext(ctx.typeName()) 67 | value.setParent(this.parent) 68 | if (ctx.MUT()) value.isMut = true 69 | this.parent.add?.(value) 70 | return [value.type_] 71 | } 72 | 73 | primitiveTypeContext(ctx: PrimitiveTypeContext): Identifier { 74 | const identifiers: Identifier[] = [] 75 | const add = (node: TerminalNode | undefined) => { 76 | if (node) identifiers.push(this.typeNode(node)) 77 | } 78 | add(ctx.I256()) 79 | add(ctx.U256()) 80 | add(ctx.BYTEVEC()) 81 | add(ctx.ADDRESS()) 82 | add(ctx.BYTE()) 83 | add(ctx.BOOL()) 84 | return identifiers[0] 85 | } 86 | 87 | typeNameContext(ctx: TypeNameContext): Identifier { 88 | const identifiers: Identifier[] = [] 89 | const primitiveType = ctx.primitiveType() 90 | if (primitiveType) identifiers.push(this.primitiveTypeContext(primitiveType)) 91 | const identifier = ctx.IDENTIFIER() 92 | if (identifier) identifiers.push(this.typeNode(identifier)) 93 | return identifiers[0] 94 | } 95 | 96 | resultContext(ctx: ResultContext): Identifier[] { 97 | const identifiers: Identifier[] = [] 98 | ctx.typeName().forEach((value) => identifiers.push(this.typeNameContext(value))) 99 | return identifiers 100 | } 101 | 102 | arrayExprContext(ctx: ArrayExprContext): Identifier[] { 103 | const identifiers: Identifier[] = [] 104 | const identifier = ctx.IDENTIFIER() 105 | if (identifier) identifiers.push(this.refNode(identifier).setIdentifierKind?.(IdentifierKind.Variable)) 106 | ctx.expression().forEach((value) => identifiers.push(...this.expressionContext(value))) 107 | return identifiers 108 | } 109 | 110 | expressionContext(ctx: ExpressionContext): Identifier[] { 111 | const identifiers = [] 112 | const expr = ctx.primaryExpr()?.arrayExpr() 113 | if (expr) identifiers.push(...this.arrayExprContext(expr)) 114 | const call = ctx.callChain() 115 | if (call) identifiers.push(...this.callChain(call)) 116 | const ifStmt = ctx.ifStmt() 117 | if (ifStmt) identifiers.push(...this.ifStmtContext(ifStmt)) 118 | const expression = ctx.expression() 119 | if (expression) expression.forEach((value) => identifiers.push(...this.expressionContext(value))) 120 | return identifiers 121 | } 122 | 123 | callChain(ctx: CallChainContext): Identifier[] { 124 | const identifiers: Identifier[] = [] 125 | 126 | const varName = ctx.varName() 127 | if (varName) identifiers.push(this.varNameContext(varName).setSemanticsKind?.(SemanticsKind.Ref)) 128 | 129 | const methodCall = ctx.methodCall() 130 | if (methodCall) identifiers.push(...this.methodCall(methodCall)) 131 | 132 | let caller = identifiers[0] 133 | const callChains = ctx.callChain() 134 | if (callChains) { 135 | callChains.forEach((value) => { 136 | const callee = this.callChain(value) 137 | callee[0].setParent?.(caller) // TODO 138 | caller = callee[0] 139 | identifiers.push(...callee) 140 | }) 141 | } 142 | return identifiers 143 | } 144 | 145 | methodCall(ctx: MethodCallContext): Identifier[] { 146 | const identifiers: Identifier[] = [] 147 | identifiers.push(this.refNode(ctx.IDENTIFIER()).setIdentifierKind!(IdentifierKind.Method)) 148 | identifiers.push(...this.expressionListContext(ctx.expressionList())) 149 | return identifiers 150 | } 151 | 152 | expressionListContext(ctx: ExpressionListContext): Identifier[] { 153 | const identifiers: Identifier[] = [] 154 | ctx.expression().forEach((value) => identifiers.push(...this.expressionContext(value))) 155 | return identifiers 156 | } 157 | 158 | emitContext(ctx: EmitContext): Identifier[] { 159 | const identifiers: Identifier[] = [] 160 | identifiers.push(this.refNode(ctx.IDENTIFIER())) 161 | identifiers.push(...this.expressionListContext(ctx.expressionList())) 162 | return identifiers 163 | } 164 | 165 | returnStmtContext(ctx: ReturnStmtContext): Identifier[] { 166 | const identifiers: Identifier[] = [] 167 | identifiers.push(...this.expressionListContext(ctx.expressionList())) 168 | return identifiers 169 | } 170 | 171 | ifStmtContext(ctx: IfStmtContext): Identifier[] { 172 | const identifiers: Identifier[] = [] 173 | identifiers.push(...this.expressionContext(ctx.expression())) 174 | identifiers.push(...this.blockContext(ctx.block())) 175 | const elseStmt = ctx.elseStmt() 176 | if (elseStmt) identifiers.push(...this.elseStmtContext(elseStmt)) 177 | return identifiers 178 | } 179 | 180 | elseStmtContext(ctx: ElseStmtContext): Identifier[] { 181 | const identifiers: Identifier[] = [] 182 | const block = ctx.block() 183 | if (block) identifiers.push(...this.blockContext(block)) 184 | const ifStmt = ctx.ifStmt() 185 | if (ifStmt) identifiers.push(...this.ifStmtContext(ifStmt)) 186 | return identifiers 187 | } 188 | 189 | blockContext(ctx: BlockContext): Identifier[] { 190 | return this.statementContexts(ctx.statement()) 191 | } 192 | 193 | varDeclSingleContext(ctx: VarDeclSingleContext): Identifier[] { 194 | const identifiers: Identifier[] = [] 195 | const value = new Variable(ctx.varName().IDENTIFIER()) 196 | value.setRuleContext(ctx) 197 | if (ctx.CONST()) { 198 | value._sourceIntervalDetail = ctx.sourceInterval 199 | } else { 200 | value._sourceIntervalDetail = ctx.varName().IDENTIFIER().sourceInterval 201 | } 202 | value.setParent(this.parent) 203 | if (ctx.MUT()) value.isMut = true 204 | this.parent.add?.(value) 205 | identifiers.push(...this.expressionContext(ctx.expression())) 206 | return identifiers 207 | } 208 | 209 | varDeclMultiContext(ctx: VarDeclMultiContext): Identifier[] { 210 | const identifiers: Identifier[] = [] 211 | ctx 212 | .identifierList() 213 | .varName() 214 | .forEach((varName) => { 215 | const value = new Variable(varName.IDENTIFIER()) 216 | value.setRuleContext(ctx) 217 | value._sourceIntervalDetail = varName.IDENTIFIER().sourceInterval 218 | value.setParent(this.parent) 219 | this.parent.add?.(value) 220 | }) 221 | identifiers.push(...this.expressionContext(ctx.expression())) 222 | return identifiers 223 | } 224 | 225 | // todo 226 | varDeclContext(ctx: VarDeclContext): Identifier[] { 227 | const identifiers: Identifier[] = [] 228 | const varDeclSingle = ctx.varDeclSingle() 229 | if (varDeclSingle) identifiers.push(...this.varDeclSingleContext(varDeclSingle)) 230 | const varDeclMulti = ctx.varDeclMulti() 231 | if (varDeclMulti) identifiers.push(...this.varDeclMultiContext(varDeclMulti)) 232 | return identifiers 233 | } 234 | 235 | // VarNameContext 236 | varNameContext(ctx: VarNameContext): Identifier { 237 | return this.defNode(ctx.IDENTIFIER()).setIdentifierKind?.(IdentifierKind.Variable) 238 | } 239 | 240 | simpleStmtContext(ctx: SimpleStmtContext): Identifier[] { 241 | const identifiers: Identifier[] = [] 242 | const varDecl = ctx.varDecl() 243 | if (varDecl) identifiers.push(...this.varDeclContext(varDecl)) 244 | const emit = ctx.emit() 245 | if (emit) identifiers.push(...this.emitContext(emit)) 246 | const expression = ctx.expression() 247 | if (expression) identifiers.push(...this.expressionContext(expression)) 248 | const returnStmt = ctx.returnStmt() 249 | if (returnStmt) identifiers.push(...this.returnStmtContext(returnStmt)) 250 | return identifiers 251 | } 252 | 253 | whileStmtContext(ctx: WhileStmtContext): Identifier[] { 254 | const identifiers: Identifier[] = [] 255 | const expression = ctx.expression() 256 | if (expression) identifiers.push(...this.expressionContext(expression)) 257 | identifiers.push(...this.blockContext(ctx.block())) 258 | return identifiers 259 | } 260 | 261 | forStmtContext(ctx: ForStmtContext): Identifier[] { 262 | const identifiers: Identifier[] = [] 263 | const varName = ctx.varName() 264 | if (varName) identifiers.push(this.varNameContext(varName).setSemanticsKind!(SemanticsKind.Ref)) 265 | ctx.expression().forEach((expression) => identifiers.push(...this.expressionContext(expression))) 266 | identifiers.push(...this.blockContext(ctx.block())) 267 | return identifiers 268 | } 269 | 270 | statementContexts(ctx: StatementContext[]): Identifier[] { 271 | const identifiers: Identifier[] = [] 272 | ctx.forEach((value) => identifiers.push(...this.statementContext(value))) 273 | return identifiers 274 | } 275 | 276 | statementContext(ctx: StatementContext): Identifier[] { 277 | const identifiers: Identifier[] = [] 278 | 279 | const simpleStmt = ctx.simpleStmt() 280 | if (simpleStmt) identifiers.push(...this.simpleStmtContext(simpleStmt)) 281 | 282 | const ifStmt = ctx.ifStmt() 283 | if (ifStmt) identifiers.push(...this.ifStmtContext(ifStmt)) 284 | 285 | const whileStmt = ctx.whileStmt() 286 | if (whileStmt) identifiers.push(...this.whileStmtContext(whileStmt)) 287 | 288 | const forStmt = ctx.forStmt() 289 | if (forStmt) identifiers.push(...this.forStmtContext(forStmt)) 290 | 291 | return identifiers 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/ast/contract.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, SymbolKind } from 'vscode' 2 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 3 | import { Base } from './base' 4 | import { Interface } from './interface' 5 | import { Property } from './property' 6 | 7 | export class Contract extends Base { 8 | interfaces: Map 9 | 10 | parentClass: Map 11 | 12 | subclass: Map 13 | 14 | // TODO: We should pass this as arguments to the constructor when parsing. 15 | private fields: Property[] | undefined 16 | 17 | constructor(node: TerminalNode) { 18 | super(node) 19 | this.subclass = new Map() 20 | this.parentClass = new Map() 21 | this.interfaces = new Map() 22 | this.fields = undefined 23 | } 24 | 25 | getFields(): Property[] { 26 | if (this.fields === undefined) { 27 | this.fields = Array.from(this.members.values()).filter((ident) => ident instanceof Property) as Property[] 28 | } 29 | return this.fields 30 | } 31 | 32 | getImmutableFields(): Property[] { 33 | return this.getFields().filter((f) => !f.isMut) 34 | } 35 | 36 | getMutableFields(): Property[] { 37 | return this.getFields().filter((f) => f.isMut) 38 | } 39 | 40 | symbolKind(): SymbolKind { 41 | return SymbolKind.Class 42 | } 43 | 44 | completionItemKind(): CompletionItemKind { 45 | return CompletionItemKind.Class 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ast/enum.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line max-classes-per-file 2 | import { CompletionItemKind, SymbolKind } from 'vscode' 3 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 4 | import { Interval } from 'antlr4ts/misc/Interval' 5 | import { Base } from './base' 6 | import { EnumContext } from '../parser/RalphParser' 7 | import { SemanticNode } from './ast' 8 | import { IdentifierKind } from './kinder' 9 | import { Identifier } from './identifier' 10 | 11 | export class Enum extends Base { 12 | symbolKind(): SymbolKind { 13 | return SymbolKind.Enum 14 | } 15 | 16 | completionItemKind(): CompletionItemKind { 17 | return CompletionItemKind.Enum 18 | } 19 | 20 | constructor(node: TerminalNode) { 21 | super(node) 22 | this.identifierKind = IdentifierKind.Enum 23 | } 24 | 25 | public static FromContext(ctx: EnumContext): Enum { 26 | const enumValue = new Enum(ctx.IDENTIFIER()) 27 | enumValue.setRuleContext(ctx) 28 | enumValue.setRange(ctx.start, ctx.stop) 29 | enumValue._sourceIntervalDetail = new Interval(ctx.ENUM().sourceInterval.a, ctx.IDENTIFIER().sourceInterval.b) 30 | ctx.varNameAssign().forEach((ctx) => { 31 | const member = new EnumMember(ctx.varName().IDENTIFIER()).setParent(enumValue).setRuleContext(ctx) 32 | member._sourceIntervalDetail = ctx.sourceInterval 33 | member.setRuleContext(ctx) 34 | member.setRange(ctx.start, ctx.stop) 35 | enumValue.add(member) 36 | }) 37 | return enumValue 38 | } 39 | 40 | getType(): Identifier | undefined { 41 | return this 42 | } 43 | } 44 | 45 | export class EnumMember extends SemanticNode { 46 | symbolKind(): SymbolKind { 47 | return SymbolKind.EnumMember 48 | } 49 | 50 | completionItemKind(): CompletionItemKind { 51 | return CompletionItemKind.EnumMember 52 | } 53 | 54 | constructor(node: TerminalNode) { 55 | super(node) 56 | this.identifierKind = IdentifierKind.Variable 57 | } 58 | 59 | getType(): Identifier | undefined { 60 | return this.parent 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ast/event.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, SymbolKind } from 'vscode' 2 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 3 | import { IdentifierKind } from './kinder' 4 | import { EventContext } from '../parser/RalphParser' 5 | import { Base } from './base' 6 | import { Context } from './context' 7 | 8 | export class Event extends Base { 9 | symbolKind(): SymbolKind { 10 | return SymbolKind.Event 11 | } 12 | 13 | completionItemKind(): CompletionItemKind { 14 | return CompletionItemKind.Event 15 | } 16 | 17 | constructor(node: TerminalNode) { 18 | super(node) 19 | this.identifierKind = IdentifierKind.Event 20 | } 21 | 22 | public static FromContext(ctx: EventContext): Event { 23 | const event = new Event(ctx.IDENTIFIER()) 24 | event.setRuleContext(ctx) 25 | event.setRange(ctx.start, ctx.stop) 26 | event._sourceIntervalDetail = ctx.sourceInterval 27 | const context = new Context(event) 28 | event.identifiers.push(...context.paramListContext(ctx.paramList())) 29 | return event 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ast/finder.ts: -------------------------------------------------------------------------------- 1 | import { Word } from './word' 2 | // eslint-disable-next-line import/no-cycle 3 | import { Identifier } from './identifier' 4 | import { Position } from './position' 5 | 6 | export interface Finder { 7 | findAll?(condition: Word): Identifier[] 8 | 9 | find?(word: Word): Identifier | undefined 10 | 11 | defs?(): Identifier[] 12 | 13 | def?(word: Word): Identifier | undefined 14 | 15 | ref?(): Identifier[] 16 | 17 | container?(position: Position): Identifier | undefined 18 | 19 | owner?(word: Word): Identifier | undefined 20 | } 21 | -------------------------------------------------------------------------------- /src/ast/identifier.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { 3 | CompletionItem, 4 | CompletionItemKind, 5 | CompletionItemLabel, 6 | DocumentSymbol, 7 | Hover, 8 | Location, 9 | SignatureInformation, 10 | SymbolInformation, 11 | SymbolKind, 12 | TypeHierarchyItem, 13 | } from 'vscode' 14 | import { RuleNode } from 'antlr4ts/tree/RuleNode' 15 | import { Kinder, IdentifierKind, SemanticsKind } from './kinder' 16 | // eslint-disable-next-line import/no-cycle 17 | import { Finder } from './finder' 18 | import { Position } from './position' 19 | import { RalphParser } from '../parser/RalphParser' 20 | 21 | export interface IntelliSense { 22 | symbolKind?(): SymbolKind 23 | completionItemKind?(): CompletionItemKind 24 | completionItemLabel?(): CompletionItemLabel 25 | documentSymbol?(): DocumentSymbol 26 | symbolInformation?(): SymbolInformation 27 | completionItem?(): CompletionItem 28 | signatureInformation?(): SignatureInformation 29 | hover?(): Hover 30 | location?(): Location 31 | typeHierarchyItem?(): TypeHierarchyItem 32 | } 33 | 34 | export interface Identifier extends Kinder, Finder, Position, IntelliSense { 35 | name?: string 36 | 37 | range?: vscode.Range 38 | 39 | detail?: string 40 | 41 | ruleContext?: RuleNode 42 | 43 | parent?: Identifier 44 | 45 | type_?: Identifier 46 | 47 | keyword?(): string 48 | 49 | toString?(): string 50 | 51 | contains?(identifier: Position): boolean 52 | 53 | isDef?(): boolean 54 | 55 | isRef?(): boolean 56 | 57 | getUri?(): vscode.Uri | undefined 58 | 59 | getChild?(): Identifier[] 60 | 61 | getType?(): Identifier | undefined 62 | 63 | setParent?(parent: Identifier): this 64 | 65 | setSemanticsKind?(kind: SemanticsKind): this 66 | 67 | setIdentifierKind?(kind: IdentifierKind): this 68 | 69 | add?(member: Identifier): void 70 | 71 | append?(...identifiers: Identifier[]): void 72 | 73 | label?(): string 74 | 75 | parser?(): RalphParser | undefined 76 | 77 | getWordRange?(): vscode.Range | undefined 78 | } 79 | -------------------------------------------------------------------------------- /src/ast/interface.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, SymbolKind } from 'vscode' 2 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 3 | import { Base } from './base' 4 | 5 | export class Interface extends Base { 6 | implementer: Map 7 | 8 | constructor(node: TerminalNode) { 9 | super(node) 10 | this.implementer = new Map() 11 | } 12 | 13 | symbolKind(): SymbolKind { 14 | return SymbolKind.Interface 15 | } 16 | 17 | completionItemKind(): CompletionItemKind { 18 | return CompletionItemKind.Interface 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ast/kinder.ts: -------------------------------------------------------------------------------- 1 | export enum IdentifierKind { 2 | Variable = 0, 3 | Method = 1, 4 | Event = 2, 5 | Type = 3, 6 | Enum = 4, 7 | } 8 | 9 | export enum SemanticsKind { 10 | Ref = 0, 11 | Def = 1, 12 | } 13 | 14 | export interface Kinder { 15 | identifierKind?: IdentifierKind 16 | 17 | semanticsKind?: SemanticsKind 18 | } 19 | -------------------------------------------------------------------------------- /src/ast/method.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItem, CompletionItemKind, SymbolKind } from 'vscode' 2 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 3 | import { Interval } from 'antlr4ts/misc/Interval' 4 | import { Base } from './base' 5 | import { MethodDeclContext } from '../parser/RalphParser' 6 | import { Context } from './context' 7 | import { IdentifierKind } from './kinder' 8 | 9 | export class Method extends Base { 10 | isPub: boolean 11 | 12 | constructor(node: TerminalNode) { 13 | super(node) 14 | this.identifierKind = IdentifierKind.Method 15 | this.isPub = false 16 | } 17 | 18 | paramList(): string { 19 | let text = '' 20 | let { size } = this.members 21 | this.members.forEach((value) => { 22 | text += `${value.name}: ${value.type_?.name}` 23 | size -= 1 24 | if (size !== 0) { 25 | text += ', ' 26 | } 27 | }) 28 | return text 29 | } 30 | 31 | symbolKind(): SymbolKind { 32 | return SymbolKind.Method 33 | } 34 | 35 | completionItemKind(): CompletionItemKind { 36 | return CompletionItemKind.Method 37 | } 38 | 39 | completionItem(): CompletionItem { 40 | const item = new CompletionItem(this.completionItemLabel(), this.completionItemKind()) 41 | item.preselect = true 42 | item.commitCharacters = ['.'] 43 | // const text = new SnippetString(`${this.name}`) 44 | // text.appendText('(') 45 | // let { size } = this.members 46 | // this.members.forEach((value) => { 47 | // text.appendTabstop().appendPlaceholder(`${value.name}: ${value.type_?.name}`) 48 | // size -= 1 49 | // if (size !== 0) { 50 | // text.appendText(', ') 51 | // } 52 | // }) 53 | // text.appendText(')') 54 | // item.insertText = text 55 | return item 56 | } 57 | 58 | label(): string { 59 | if (this.isPub) { 60 | return `🔓 ${this.name}` 61 | } 62 | return `🔐 ${this.name}` 63 | } 64 | 65 | public static FromContext(ctx: MethodDeclContext): Method { 66 | const method = new Method(ctx.IDENTIFIER()) 67 | method.setRange(ctx.start, ctx.stop) 68 | method.setRuleContext(ctx) 69 | if (ctx.PUB()) { 70 | method.isPub = true 71 | } 72 | const context = new Context(method) 73 | method.identifiers.push(...context.paramListContext(ctx.paramList())) 74 | const block = ctx.block() 75 | if (block) method.append(...context.blockContext(block)) 76 | method._sourceIntervalDetail = new Interval((ctx.PUB() ?? ctx.FN()).sourceInterval.a, (ctx.result() ?? ctx.R_PAREN()).sourceInterval.b) 77 | return method 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ast/position.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from 'vscode' 2 | import * as vscode from 'vscode' 3 | 4 | export interface Position { 5 | point?: vscode.Position 6 | 7 | uri?: Uri 8 | } 9 | -------------------------------------------------------------------------------- /src/ast/project.ts: -------------------------------------------------------------------------------- 1 | import vscode, { Uri } from 'vscode' 2 | import path from 'path' 3 | import { Root } from './root' 4 | import { Identifier } from './identifier' 5 | import { Word } from './word' 6 | import { Position } from './position' 7 | 8 | export class MultiProjects { 9 | projects: Map 10 | 11 | constructor() { 12 | this.projects = new Map() 13 | } 14 | 15 | projectDir(projectPath: Uri | string): string { 16 | if (projectPath instanceof Uri) { 17 | if (vscode.workspace.rootPath) { 18 | return projectPath.path.split(vscode.workspace.rootPath)[1].split(path.sep)[1] 19 | } 20 | return 'root' 21 | } 22 | return projectPath 23 | } 24 | 25 | merge(path: Uri, members: Identifier[]) { 26 | const dir = this.projectDir(path) 27 | if (this.projects.get(dir)) { 28 | this.root(path)?.merge(path, members) 29 | } else { 30 | const root = new Root() 31 | root.merge(path, members) 32 | this.projects.set(dir, root) 33 | } 34 | } 35 | 36 | remove(path: Uri) { 37 | return this.root(path)?.remove(path) 38 | } 39 | 40 | def(path: Uri | string, word: Word): Identifier | undefined { 41 | return this.root(path)?.def(word) 42 | } 43 | 44 | defs(path?: Uri | string): Identifier[] { 45 | if (path) { 46 | const root = this.root(path) 47 | if (root) return root.defs() 48 | return [] 49 | } 50 | const items: Identifier[] = [] 51 | this.projects.forEach((root) => items.push(...root.defs())) 52 | return items 53 | } 54 | 55 | get(path: Uri | string, name: string): Identifier | undefined { 56 | return this.root(path)?.get(name) 57 | } 58 | 59 | container(path: Uri | string, position: Position): Identifier | undefined { 60 | return this.root(path)?.container(position) 61 | } 62 | 63 | findAll(path: Uri | string, identifier: Word): Identifier[] { 64 | const root = this.root(path) 65 | if (root) return root.findAll(identifier) 66 | return [] 67 | } 68 | 69 | root(path: Uri | string): Root { 70 | const dir = this.projectDir(path) 71 | const root = this.projects.get(dir) 72 | if (root) return root 73 | return new Root() 74 | } 75 | 76 | analyse() { 77 | this.projects.forEach((root) => root.analyse()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ast/property.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, SymbolKind } from 'vscode' 2 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 3 | import { SemanticNode } from './ast' 4 | import { Identifier } from './identifier' 5 | import { IdentifierKind } from './kinder' 6 | import caches from '../cache/cache' 7 | 8 | export class Property extends SemanticNode { 9 | isMut: boolean 10 | 11 | type_: Identifier | undefined 12 | 13 | symbolKind(): SymbolKind { 14 | return SymbolKind.Property 15 | } 16 | 17 | completionItemKind(): CompletionItemKind { 18 | return CompletionItemKind.Property 19 | } 20 | 21 | constructor(node: TerminalNode) { 22 | super(node) 23 | this.isMut = false 24 | this.identifierKind = IdentifierKind.Variable 25 | } 26 | 27 | getType(): Identifier | undefined { 28 | return caches.get(this.getUri()!, this.type_?.name) 29 | } 30 | 31 | label(): string { 32 | if (this.isMut) return `(mut) ${this.name}` 33 | return this.name! 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ast/root.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from 'vscode' 2 | import { Base } from './base' 3 | import { Position } from './position' 4 | import { Identifier } from './identifier' 5 | import { Interface } from './interface' 6 | import { Contract } from './contract' 7 | 8 | export class Root extends Base { 9 | constructor() { 10 | super() 11 | this.name = 'Root' 12 | } 13 | 14 | contains(position: Position): boolean { 15 | return true 16 | } 17 | 18 | merge(uri: Uri, members: Identifier[]) { 19 | members.forEach((member) => { 20 | if (this.members.has(member.name!)) { 21 | const has = this.members.get(member.name!) 22 | if (member instanceof Interface && has instanceof Interface) { 23 | member.implementer = has.implementer 24 | } 25 | 26 | if (member instanceof Contract && has instanceof Contract) { 27 | member.subclass = has.subclass 28 | member.interfaces = has.interfaces 29 | member.parentClass = has.parentClass 30 | } 31 | } 32 | }) 33 | this.remove(uri) 34 | members.forEach((value) => this.add(value)) 35 | } 36 | 37 | remove(uri: Uri) { 38 | Array.from(this.members.values()).forEach((value) => { 39 | if (value.getUri!() === uri) { 40 | this.members = this.members.delete(value.name!) 41 | } 42 | }) 43 | } 44 | 45 | get(name: string): Identifier | undefined { 46 | return this.members.get(name) 47 | } 48 | 49 | analyse() { 50 | this.members 51 | .valueSeq() 52 | .toArray() 53 | .forEach((identifier) => { 54 | if (identifier instanceof Contract) { 55 | identifier.subclass.forEach((value, key) => { 56 | const member = this.members.get(key) 57 | if (member && member instanceof Contract) { 58 | identifier.subclass.set(key, member) 59 | member.members = member.members.merge(identifier.members) 60 | } else { 61 | identifier.subclass.delete(key) 62 | } 63 | }) 64 | 65 | identifier.interfaces.forEach((value, key) => { 66 | const member = this.members.get(key) 67 | if (member && member instanceof Interface) { 68 | identifier.members = identifier.members.merge(member.members) 69 | } else { 70 | identifier.members = identifier.members.delete(key) 71 | } 72 | }) 73 | 74 | identifier.parentClass.forEach((value, key) => { 75 | const member = this.members.get(key) 76 | if (member && member instanceof Contract) { 77 | identifier.members = identifier.members.merge(member.members) 78 | } else { 79 | identifier.members = identifier.members.delete(key) 80 | } 81 | }) 82 | } 83 | 84 | if (identifier instanceof Interface) { 85 | identifier.implementer.forEach((value, key) => { 86 | const member = this.members.get(key) 87 | if (member && member instanceof Base) { 88 | identifier.implementer.set(key, member) 89 | member.members = member.members.merge(identifier.members) 90 | } else { 91 | identifier.implementer.delete(key) 92 | } 93 | }) 94 | } 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/ast/scope.ts: -------------------------------------------------------------------------------- 1 | import { Range, Position, Uri } from 'vscode' 2 | 3 | export enum ScopeKind { 4 | Method = 0, 5 | Contruct = 1, 6 | } 7 | 8 | export class Scope { 9 | scopeKind: ScopeKind 10 | 11 | uri: Uri 12 | 13 | /** * action scope ** */ 14 | scope: Range 15 | 16 | constructor(uri: Uri, range: Range) { 17 | this.uri = uri 18 | this.scope = range 19 | this.scopeKind = ScopeKind.Method 20 | } 21 | 22 | contains(scope: Scope | Position): boolean { 23 | if (scope instanceof Scope) { 24 | if (this.uri && scope.uri) { 25 | if (this.uri.path !== scope.uri.path) { 26 | return false 27 | } 28 | } 29 | if (this.scopeKind >= scope.scopeKind) { 30 | return this.scope.contains(scope.scope) 31 | } 32 | } 33 | if (scope instanceof Position) { 34 | return this.scope.contains(scope) 35 | } 36 | return false 37 | } 38 | 39 | isEqual(scope: Scope): boolean { 40 | return this.uri === scope.uri && this.scope === scope.scope 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ast/txScript.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, SymbolKind } from 'vscode' 2 | import { Base } from './base' 3 | 4 | export class TxScript extends Base { 5 | symbolKind(): SymbolKind { 6 | return SymbolKind.Class 7 | } 8 | 9 | completionItemKind(): CompletionItemKind { 10 | return CompletionItemKind.Class 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ast/variable.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItemKind, CompletionItemLabel, SymbolKind } from 'vscode' 2 | import { TerminalNode } from 'antlr4ts/tree/TerminalNode' 3 | import { SemanticNode } from './ast' 4 | import { Identifier } from './identifier' 5 | import { IdentifierKind } from './kinder' 6 | import caches from '../cache/cache' 7 | 8 | export class Variable extends SemanticNode { 9 | isMut: boolean 10 | 11 | type_: Identifier | undefined 12 | 13 | symbolKind(): SymbolKind { 14 | return SymbolKind.Variable 15 | } 16 | 17 | completionItemKind(): CompletionItemKind { 18 | return CompletionItemKind.Variable 19 | } 20 | 21 | constructor(node: TerminalNode) { 22 | super(node) 23 | this.isMut = false 24 | this.identifierKind = IdentifierKind.Variable 25 | } 26 | 27 | getType(): Identifier | undefined { 28 | return caches.get(this.getUri()!, this.type_?.name) 29 | } 30 | 31 | label(): string { 32 | if (this.isMut) return `(mut) ${this.name}` 33 | return this.name! 34 | } 35 | 36 | completionItemLabel(): CompletionItemLabel { 37 | return { 38 | label: this.name!, 39 | // detail: this.label(), 40 | description: this.hoverSymbolDetail(), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ast/visibility.ts: -------------------------------------------------------------------------------- 1 | export const stateMutabilityToIcon = { 2 | view: '👀', 3 | pure: '🌳', 4 | constant: '👀', 5 | payable: '💰', 6 | } 7 | 8 | export const visibilityToIcon = { 9 | external: '❗️', 10 | public: '🔓️', 11 | private: '🔐', 12 | internal: '🔒', 13 | } 14 | -------------------------------------------------------------------------------- /src/ast/word.ts: -------------------------------------------------------------------------------- 1 | import { Position } from './position' 2 | 3 | export interface Word extends Position { 4 | name?: string 5 | } 6 | -------------------------------------------------------------------------------- /src/cache/cache.ts: -------------------------------------------------------------------------------- 1 | import { MultiProjects } from '../ast/project' 2 | 3 | // const cache = new Root() 4 | const cache = new MultiProjects() 5 | export default cache 6 | -------------------------------------------------------------------------------- /src/commander/commands.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { Compiler } from '../compiler/compiler' 3 | import { deployToDevnet } from '../deploy/deploy' 4 | 5 | interface Command { 6 | command: string 7 | title: string 8 | callback: (...args: any[]) => any 9 | } 10 | 11 | export const commands: Command[] = [ 12 | { 13 | command: 'ralph.compile', 14 | title: 'Ralph compile', 15 | callback: () => { 16 | if (vscode.window.activeTextEditor) { 17 | new Compiler().compile(vscode.window.activeTextEditor) 18 | } 19 | }, 20 | }, 21 | { 22 | command: 'ralph.deploy', 23 | title: 'Ralph deploy', 24 | callback: () => { 25 | console.log('Begin deploy') 26 | deployToDevnet() 27 | .catch((err) => { 28 | console.log(err) 29 | vscode.window.showErrorMessage(err.message) 30 | }) 31 | .then((value) => { 32 | vscode.window.showInformationMessage('Contracts deployed!') 33 | }) 34 | console.log('end deploy') 35 | }, 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /src/compiler/compiler.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as fs from 'fs' 3 | import { exec } from 'child_process' 4 | import path from 'path' 5 | import * as logger from '../logger/logger' 6 | import { Logger } from '../logger/logger' 7 | import { getCompileCommand, getWorkspaceFolder } from '../util/util' 8 | 9 | export class Compiler { 10 | cmd: string | undefined 11 | 12 | log: logger.Logger 13 | 14 | constructor() { 15 | this.log = new logger.Logger('Compiler') 16 | this.cmd = getCompileCommand() 17 | } 18 | 19 | async compile(editor: vscode.TextEditor) { 20 | const workspace = getWorkspaceFolder() 21 | if (workspace === undefined) { 22 | return 23 | } 24 | 25 | if (!fs.existsSync(path.join(workspace, 'alephium.config.ts'))) { 26 | vscode.window.showErrorMessage( 27 | 'Unrecognized Ralph project, please create one following: https://docs.alephium.org/dapps/getting-started' 28 | ) 29 | return 30 | } 31 | 32 | if (editor.document.isDirty) { 33 | editor.document.save() 34 | } 35 | 36 | Logger.show() 37 | this.log.info(`CMD: ${this.cmd}`) 38 | vscode.window.setStatusBarMessage(`Execute command: ${this.cmd}`) 39 | 40 | if (this.cmd != null) { 41 | exec(this.cmd, { cwd: `${workspace}` }, (_error: any, stdout: string, stderr: string) => { 42 | if (stderr) { 43 | this.log.info(stderr) 44 | vscode.window.showErrorMessage(stderr) 45 | } else if (stdout) { 46 | this.log.info(stdout) 47 | } 48 | }) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/config/global.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode' 2 | 3 | // export interface Global { 4 | // contractsDir?: string 5 | // } 6 | 7 | // export const global: Global = vscode.workspace.getConfiguration().get('ralph.set.global') as Global 8 | -------------------------------------------------------------------------------- /src/deploy/build.ts: -------------------------------------------------------------------------------- 1 | import { web3, Project } from '@alephium/web3' 2 | import * as vscode from 'vscode' 3 | import { defaultConfiguration as config } from './config' 4 | 5 | export async function build() { 6 | const network = config.networks[config.defaultNetwork] 7 | web3.setCurrentNodeProvider(network.nodeUrl) 8 | process.chdir(vscode.workspace.rootPath!) 9 | // Compile the contracts of the project if they are not compiled 10 | await Project.build({}, vscode.workspace.rootPath!, config.sourceDir!, config.artifactDir) 11 | } 12 | -------------------------------------------------------------------------------- /src/deploy/config.ts: -------------------------------------------------------------------------------- 1 | import { Configuration } from '@alephium/cli' 2 | import { Number256 } from '@alephium/web3' 3 | import * as vscode from 'vscode' 4 | 5 | // Settings are usually for configuring 6 | export type Settings = { 7 | issueTokenAmount: Number256 8 | } 9 | 10 | const defaultSettings: Settings = { issueTokenAmount: 100n } 11 | 12 | const vscodeConfig = vscode.workspace.getConfiguration().get('ralph.online.configuration') as Configuration 13 | 14 | const configuration: Configuration = { 15 | defaultNetwork: 'devnet', 16 | networks: { 17 | devnet: { 18 | nodeUrl: 'http://localhost:22973', 19 | privateKeys: ['a642942e67258589cd2b1822c631506632db5a12aabcf413604e785300d762a5'], 20 | settings: defaultSettings, 21 | }, 22 | testnet: { 23 | nodeUrl: process.env.NODE_URL as string, 24 | privateKeys: process.env.PRIVATE_KEYS === undefined ? [] : process.env.PRIVATE_KEYS.split(','), 25 | settings: defaultSettings, 26 | }, 27 | mainnet: { 28 | nodeUrl: process.env.NODE_URL as string, 29 | privateKeys: process.env.PRIVATE_KEYS === undefined ? [] : process.env.PRIVATE_KEYS.split(','), 30 | settings: defaultSettings, 31 | }, 32 | }, 33 | } 34 | 35 | const defaultConfiguration = { ...configuration, ...vscodeConfig } 36 | 37 | export { defaultConfiguration } 38 | -------------------------------------------------------------------------------- /src/deploy/deploy.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { exec } from 'child_process' 3 | import { Logger } from '../logger/logger' 4 | import { getWorkspaceFolder } from '../util/util' 5 | 6 | const logger = new Logger('Deployer') 7 | 8 | export async function deployToDevnet(): Promise { 9 | const workspaceFolder = getWorkspaceFolder() 10 | if (workspaceFolder === undefined) { 11 | return 12 | } 13 | 14 | const cmd = `npx --yes @alephium/cli@latest deploy -n devnet` 15 | exec(cmd, { cwd: `${workspaceFolder}` }, (_error: any, stdout: string, stderr: string) => { 16 | if (stderr) { 17 | logger.info(stderr) 18 | vscode.window.showErrorMessage(stderr) 19 | } else if (stdout) { 20 | logger.info(stdout) 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/diagnostics.ts: -------------------------------------------------------------------------------- 1 | import vscode, { Diagnostic, TextDocument } from 'vscode' 2 | import cache from './cache/cache' 3 | 4 | function isUpperCase(ch: string) { 5 | return ch >= 'A' && ch <= 'Z' 6 | } 7 | 8 | function isLowerCase(ch: string) { 9 | return ch >= 'a' && ch <= 'z' 10 | } 11 | 12 | export function analyseDiagnostic() { 13 | upperCamelCase() 14 | } 15 | 16 | function upperCamelCase() { 17 | cache.defs().forEach((value) => { 18 | if (!isUpperCase(value.name!.charAt(0))) { 19 | const diagnosticCollection = vscode.languages.createDiagnosticCollection('ralph') 20 | diagnosticCollection.set(value.uri!, [ 21 | new Diagnostic( 22 | new vscode.Range(value.range!.start, value.range!.start.with({ character: value.name!.length })), 23 | 'upper camel case' 24 | ), 25 | ]) 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /src/downloader/config.ts: -------------------------------------------------------------------------------- 1 | class Config { 2 | url!: string 3 | 4 | target!: string 5 | } 6 | -------------------------------------------------------------------------------- /src/downloader/downloader.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as path from 'path' 3 | import { existsSync } from 'fs' 4 | import * as os from 'os' 5 | import * as fs from 'fs' 6 | import * as logger from '../logger/logger' 7 | import { download } from '../util/util' 8 | 9 | export class Downloader { 10 | config!: Config 11 | 12 | log: logger.Logger 13 | 14 | constructor() { 15 | this.log = new logger.Logger('Downloader') 16 | this.loadConfig() 17 | } 18 | 19 | private loadConfig() { 20 | this.config = vscode.workspace.getConfiguration().get('ralph.download.config') as Config 21 | } 22 | 23 | async showQuickPick() { 24 | logger.Logger.show() 25 | this.loadConfig() 26 | await this.download() 27 | } 28 | 29 | jarPath(): string { 30 | const dir = path.join(os.homedir(), '.alephium-dev') 31 | if (!existsSync(dir)) fs.mkdirSync(dir) 32 | return path.join(dir, this.config.target) 33 | } 34 | 35 | async download() { 36 | const targetPath = this.jarPath() 37 | if (existsSync(targetPath)) { 38 | fs.unlinkSync(targetPath) 39 | this.log.info(`Remove ${targetPath}`) 40 | } 41 | this.log.info(`Begin download : ${this.config.url}`) 42 | await download(this.config.url, targetPath, (state) => { 43 | this.log.info( 44 | `downloading ${this.config.target}, total: ${Number(state.size.total / 1024).toFixed(2)} KB, speed: ${Number( 45 | state.speed / 1024 46 | ).toFixed(2)} KB/sec, ${Number(state.percent * 100).toFixed(2)}/%, ${this.config.url}` 47 | ) 48 | }) 49 | .then(() => { 50 | const msg = `Download complete: ${this.config.url}` 51 | this.log.info(msg) 52 | vscode.window.showInformationMessage(msg) 53 | }) 54 | .catch((err) => { 55 | this.log.info(err.message) 56 | vscode.window.showErrorMessage(err.message) 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/event.ts: -------------------------------------------------------------------------------- 1 | import vscode, { TextDocument } from 'vscode' 2 | import * as path from 'path' 3 | import cache from './cache/cache' 4 | import Parser from './parser/parser' 5 | import { analyseDiagnostic } from './diagnostics' 6 | 7 | export function registerEvent() { 8 | vscode.workspace.onDidChangeTextDocument((event) => parser(event.document)) 9 | vscode.workspace.onDidOpenTextDocument((doc) => parser(doc)) 10 | vscode.workspace.onDidCloseTextDocument((doc) => parser(doc)) 11 | vscode.workspace.onDidDeleteFiles((events) => events.files.forEach((e) => cache.remove(e))) 12 | vscode.workspace.onDidSaveTextDocument((doc) => { 13 | parser(doc) 14 | analyseDiagnostic() 15 | }) 16 | vscode.workspace.onDidRenameFiles(async (events) => { 17 | events.files.forEach(async (value) => { 18 | cache.remove(value.oldUri) 19 | const doc = await vscode.workspace.openTextDocument(value.newUri) 20 | parser(doc) 21 | }) 22 | }) 23 | vscode.window.onDidChangeActiveTextEditor((event) => { 24 | if (event) { 25 | parser(event.document) 26 | } 27 | }) 28 | vscode.window.onDidChangeVisibleTextEditors((editors) => editors.forEach((editor) => parser(editor.document))) 29 | } 30 | 31 | export function parser(doc: TextDocument) { 32 | if (includes(doc.uri.path) && !doc.isDirty && doc.languageId === 'ralph') { 33 | try { 34 | const identifiers = Parser(doc.uri, doc.getText()) 35 | cache.merge(doc.uri, identifiers) 36 | } catch (err) { 37 | console.log(`Parser error: ${doc.uri}`, err) 38 | } 39 | } 40 | } 41 | 42 | function includes(file: string): boolean { 43 | // if (vscode.workspace.rootPath && global.contractsDir) { 44 | // return file.includes(path.join(vscode.workspace.rootPath, global.contractsDir)) 45 | // } 46 | return true 47 | } 48 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { commands } from './commander/commands' 3 | import { FormatterProvider } from './formatter/formatter' 4 | import { Providers as hoverProvider } from './provider/hover/providers' 5 | import { SymbolProvider } from './provider/symbolProvider' 6 | import { GlobalProvider } from './provider/completion/globalProvider' 7 | import { DefinitionProvider } from './provider/definitionProvider' 8 | import { RalphRenameProvider } from './provider/renameProvider' 9 | import { BuiltInProvider } from './provider/completion/builtInProvider' 10 | import { IdentifierProvider } from './provider/completion/identifierProvider' 11 | import { EnumProvider } from './provider/completion/enumProvider' 12 | import { AnnotationProvider } from './provider/completion/annotationProvider' 13 | import { MemberProvider } from './provider/completion/memberProvider' 14 | import { RalphSignatureHelpProvider } from './provider/signatureHelpProvider' 15 | import { RalphImplementationProvider } from './provider/implementationProvider' 16 | import { RalphTypeDefinitionProvider } from './provider/typeDefinitionProvider' 17 | import { RalphReferenceProvider } from './provider/referenceProvider' 18 | import { RalphTypeHierarchyProvider } from './provider/typeHierarchyProvider' 19 | import { analyseDiagnostic } from './diagnostics' 20 | import cache from './cache/cache' 21 | import { EmitProvider } from './provider/completion/emitProvider' 22 | import { parser, registerEvent } from './event' 23 | import { ContractBuiltInProvider } from './provider/completion/contractBuiltinProvider' 24 | 25 | // this method is called when your extension is activated 26 | // your extension is activated the very first time the command is executed 27 | export async function activate(context: vscode.ExtensionContext) { 28 | // Use the console to output diagnostic information (console.log) and errors (console.error) 29 | // This line of code will only be executed once when your extension is activated 30 | console.log('Congratulations, your extension is now active!') 31 | 32 | await init() 33 | registerEvent() 34 | analyseDiagnostic() 35 | 36 | // The command has been defined in the package.json file 37 | // Now provide the implementation of the command with registerCommand 38 | // The commandId parameter must match the command field in package.json 39 | commands.forEach((value) => { 40 | context.subscriptions.push(vscode.commands.registerCommand(value.command, value.callback)) 41 | }) 42 | 43 | const selector = { scheme: 'file', language: 'ralph' } 44 | hoverProvider().forEach((value) => context.subscriptions.push(vscode.languages.registerHoverProvider(selector, value))) 45 | context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(selector, new FormatterProvider())) 46 | context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(selector, new SymbolProvider())) 47 | context.subscriptions.push(vscode.languages.registerWorkspaceSymbolProvider(new SymbolProvider())) 48 | // context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, new GlobalProvider())) 49 | context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, new ContractBuiltInProvider(), '.')) 50 | context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, new BuiltInProvider())) 51 | context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, new IdentifierProvider())) 52 | context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, new EmitProvider(), 'emit')) 53 | context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, new AnnotationProvider(), '@', '(', ')')) 54 | // context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, new EnumProvider(), '.')) 55 | context.subscriptions.push(vscode.languages.registerCompletionItemProvider(selector, new MemberProvider(), '.')) 56 | context.subscriptions.push(vscode.languages.registerDefinitionProvider(selector, new DefinitionProvider())) 57 | context.subscriptions.push(vscode.languages.registerRenameProvider(selector, new RalphRenameProvider())) 58 | context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(selector, new RalphSignatureHelpProvider(), '(', ')', ',')) 59 | context.subscriptions.push(vscode.languages.registerImplementationProvider(selector, new RalphImplementationProvider())) 60 | context.subscriptions.push(vscode.languages.registerTypeDefinitionProvider(selector, new RalphTypeDefinitionProvider())) 61 | context.subscriptions.push(vscode.languages.registerReferenceProvider(selector, new RalphReferenceProvider())) 62 | context.subscriptions.push(vscode.languages.registerTypeHierarchyProvider(selector, new RalphTypeHierarchyProvider())) 63 | 64 | console.log('register push completed!') 65 | } 66 | 67 | // this method is called when your extension is deactivated 68 | export function deactivate() { 69 | clearInterval(timerId) 70 | } 71 | 72 | async function init() { 73 | const files = await vscode.workspace.findFiles('**/*.ral', '**/{node_modules,.git}/**') 74 | files.forEach(async (uri) => { 75 | const doc = await vscode.workspace.openTextDocument(uri) 76 | try { 77 | parser(doc) 78 | cache.analyse() 79 | } catch (err) { 80 | console.log(`Init failed ${uri}`, err) 81 | } 82 | }) 83 | } 84 | 85 | const timerId = setInterval(() => { 86 | cache.analyse() 87 | }, 1000 * 60 * 5) 88 | -------------------------------------------------------------------------------- /src/formatter/formatter.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-regex-literals */ 2 | /* eslint-disable no-param-reassign */ 3 | import * as vscode from 'vscode' 4 | 5 | export class FormatterProvider implements vscode.DocumentFormattingEditProvider { 6 | private functions: Array = [] 7 | 8 | private keywords: Array = [] 9 | 10 | private types: Array = [] 11 | 12 | private ends: Array = [] 13 | 14 | private skipString: Array = [] 15 | 16 | provideDocumentFormattingEdits(document: vscode.TextDocument) { 17 | if (vscode.window.visibleTextEditors.every((e) => e.document.fileName !== document.fileName)) { 18 | return [] 19 | } 20 | 21 | const out = [] 22 | this.functions = ['assert!', '[A-Za-z_]*(_TO_)[A-Za-z_]*'] 23 | 24 | this.keywords = [ 25 | 'let', 26 | 'mut', 27 | 'const', 28 | 'for', 29 | 'event', 30 | 'emit', 31 | 'struct', 32 | 'TxScript', 33 | 'Contract', 34 | 'AssetScript', 35 | 'enum', 36 | 'Interface', 37 | 'fn', 38 | 'pub', 39 | 'extends', 40 | 'Abstract', 41 | 'implements', 42 | 'import', 43 | 'if', 44 | 'else', 45 | 'while', 46 | 'for', 47 | 'return', 48 | 'alph', 49 | ] 50 | 51 | this.types = [] 52 | 53 | this.ends = [] 54 | 55 | // Do not format this strings 56 | this.skipString = [ 57 | `["']{1}[^\"\'\\\\]*(?:\\\\[\\s\\S][^"'\\\\]*)*["']{1}`, 58 | '\\(\\*[\\s\\S]*?\\*\\)', 59 | '\\/\\*[\\s\\S]*?\\*\\/', 60 | '\\/\\/[^\\n]*\\n', // All single line comments 61 | ] 62 | 63 | let text = document.getText() 64 | text = this.spaces(text) 65 | 66 | out.push(new vscode.TextEdit(new vscode.Range(new vscode.Position(0, 0), document.lineAt(document.lineCount - 1).range.end), text)) 67 | 68 | return out 69 | } 70 | 71 | spaces(text: string): string { 72 | // Delete space between func name and ( 73 | // ABS ( to ABS( 74 | let regEx = new RegExp(`\\b(?:${this.functions.join('|')})\\b\\s+\\(`, 'ig') 75 | text = text.replace(regEx, (match) => { 76 | return match.replace(/\s+/, '') 77 | }) 78 | 79 | // Add space after keywords 80 | // if( to if ( 81 | regEx = new RegExp(`\\b(if|while)\\s*\\(`, 'ig') 82 | text = text.replace(regEx, (match, key) => { 83 | return key !== undefined ? `${key} (` : match 84 | }) 85 | 86 | // } else { 87 | regEx = new RegExp(`}\\s*else\\s*{`, 'ig') 88 | text = text.replace(regEx, (match, key) => (key !== undefined ? '} else {' : match)) 89 | 90 | const addSpace = { 91 | csb: ['\\*\\)', '\\*\\/', '\\/\\/', '\\(\\*', '\\/\\*'], 92 | csa: ['\\(\\*', '\\/\\*', '\\/\\/'], 93 | ss: ['--', '\\+\\+', '&&', '\\|\\|', '==', '->', '<=', '>=', '!=', '\\*', '%', '=', '\\+', '-', '&', '>', '<', '\\|'], 94 | } 95 | 96 | regEx = new RegExp(`(? ` ${sign}`) 98 | 99 | regEx = new RegExp(`(${addSpace.csb.join('|')})(?! )`, 'ig') 100 | text = text.replace(regEx, (match, sign) => `${sign} `) 101 | 102 | // operator 103 | regEx = new RegExp(`${addSpace.ss.map((val) => `\\s*${val}\\s*`).join('|')}`, 'ig') 104 | text = text.replace(regEx, (match, sign) => (sign !== undefined ? ` ${match.trim()} ` : match)) 105 | 106 | // : 107 | regEx = new RegExp(`\\s*\\:\\s*`, 'ig') 108 | text = text.replace(regEx, ': ') 109 | 110 | // . 111 | regEx = new RegExp(`\\s*\\.\\s*`, 'ig') 112 | text = text.replace(regEx, '.') 113 | 114 | // , 115 | regEx = new RegExp(`\\s*,`, 'ig') 116 | text = text.replace(regEx, ',') 117 | 118 | // ) extends 119 | regEx = new RegExp(`\\)\\s*extends\\s*`, 'ig') 120 | text = text.replace(regEx, ') extends ') 121 | 122 | // keyword 123 | regEx = new RegExp(`${this.keywords.map((val) => `${val}\\s{1,}`).join('|')}`, 'ig') 124 | text = text.replace(regEx, (match, sign) => (sign !== undefined ? `${match.trim()} ` : match)) 125 | 126 | return text 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/logger/logger.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode' 2 | 3 | export enum LogLevel { 4 | debug = 0, 5 | info, 6 | warn, 7 | error, 8 | } 9 | 10 | export class Logger { 11 | public static readonly outputChannel = window.createOutputChannel('ralph-vscode-log') 12 | 13 | public static logLevel = LogLevel.info 14 | 15 | source: string 16 | 17 | constructor(source: string) { 18 | this.source = source 19 | } 20 | 21 | public debug(message: string): void { 22 | Logger.callLog(LogLevel.debug, this.source, message) 23 | } 24 | 25 | public info(message: string): void { 26 | Logger.callLog(LogLevel.info, this.source, message) 27 | } 28 | 29 | public warn(message: string): void { 30 | Logger.callLog(LogLevel.warn, this.source, message) 31 | } 32 | 33 | public error(error: Error): void 34 | 35 | public error(message: string): void 36 | 37 | public error(errorOrMessage: string | Error): void { 38 | let message: string = errorOrMessage.toString() 39 | if (errorOrMessage instanceof Error) { 40 | message = `${errorOrMessage}::${errorOrMessage.message}` 41 | } 42 | 43 | Logger.callLog(LogLevel.error, this.source, message) 44 | } 45 | 46 | private static callLog(level: LogLevel, source: string, message: string): void { 47 | if (level >= this.logLevel) { 48 | this.outputChannel.appendLine(`[${this.getTimestamp()} ${LogLevel[level].padEnd(5, '.')}] (${source}) ${message}`) 49 | } 50 | } 51 | 52 | private static getTimestamp(): string { 53 | return new Date().toTimeString().split(' ')[0] 54 | } 55 | 56 | public static show(): void { 57 | this.outputChannel.show() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/parser/RalphLexer.g4: -------------------------------------------------------------------------------- 1 | lexer grammar RalphLexer; 2 | 3 | // LEXER 4 | // src/main/scala/org/alephium/protocol/vm/lang/Lexer.scala 5 | FN : 'fn'; 6 | PUB : 'pub'; 7 | RETURN : 'return' ; 8 | IMPORT : 'import' ; 9 | 10 | INTERFACE : 'Interface'; 11 | ENUM : 'enum'; 12 | TXSCRIPT : 'TxScript'; 13 | CONTRACT : 'Contract'; 14 | ASSETSCRIPT : 'AssetScript'; 15 | 16 | IF : 'if'; 17 | ELSE : 'else'; 18 | WHILE : 'while'; 19 | FOR : 'for'; 20 | // BREAK : 'break'; 21 | // CONTINUE : 'continue'; 22 | 23 | LET : 'let'; 24 | CONST : 'const'; 25 | MUT : 'mut'; 26 | 27 | EXTENDS :'extends'; 28 | ABSTRACT :'Abstract'; 29 | IMPLEMENTS :'implements'; 30 | EVENT :'event'; 31 | EMIT :'emit'; 32 | 33 | //@using|using 34 | ATUSING :'@using'; 35 | USING :'using'; 36 | AT :'@'; 37 | BOOL :'Bool'; 38 | I256 :'I256'; 39 | U256 :'U256'; 40 | BYTE :'Byte'; 41 | BYTEVEC :'ByteVec'; 42 | ADDRESS :'Address'; 43 | 44 | STDANNOTATION :'@std'; 45 | 46 | ATUNUSED :'@unused'; 47 | UNUSED :'unused'; 48 | 49 | //-> 50 | R_ARROW :'->'; 51 | 52 | 53 | IDENTIFIER : LETTER (LETTER | DIGIT)* '!'?; 54 | IMPORT_PATH : '"' ~["./]+ '/' (~'"')* (DIGIT | LETTER)* ('.ral')? '"' ; 55 | 56 | // Punctuation 57 | L_PAREN : '('; 58 | R_PAREN : ')'; 59 | L_CURLY : '{'; 60 | R_CURLY : '}'; 61 | L_BRACKET : '['; 62 | R_BRACKET : ']'; 63 | ASSIGN : '='; 64 | COMMA : ','; 65 | SEMI : ';'; 66 | COLON : ':'; 67 | DOT : '.'; 68 | DOUBT : '?'; 69 | 70 | //Operator 71 | CONCAT : '++'; 72 | ADD : '+'; 73 | SUB : '-'; 74 | MUL : '*'; 75 | DIV : '/'; 76 | MOD : '%'; 77 | EXP : '**'; 78 | MODEXP : '|**|'; 79 | MODADD : '⊕' | '|+|'; 80 | MODSUB : '⊖' | '|-|'; 81 | MODMUL : '⊗' | '|*|'; 82 | SHL : '<<'; 83 | SHR : '>>'; 84 | BITAND : '&'; 85 | XOR : '^'; 86 | BITOR : '|'; 87 | //TestOperator 88 | EQ : '=='; 89 | NQ : '!='; 90 | LT : '<'; 91 | LE : '<='; 92 | GT : '>'; 93 | GE : '>='; 94 | //LogicalOperator 95 | AND : '&&'; 96 | OR : '||'; 97 | NOT : '!'; 98 | 99 | ALPH : 'alph'; 100 | ALPH_TOKEN : 'ALPH'; 101 | 102 | ADDRESS_LIT : '@' (DIGIT | LETTER)*; 103 | ALPH_LIT : DIGIT+ [ ]+ ALPH; 104 | BOOL_LIT : 'true' | 'false'; 105 | BYTEVEC_LIT : '#' (HEX_DIGIT)*; 106 | 107 | //TODO 108 | // Number literals 109 | DECIMAL_LIT : ('0' | [1-9] ('_'? [0-9])*); 110 | BINARY_LIT : '0' [bB] ('_'? BIN_DIGIT)+; 111 | OCTAL_LIT : '0' [oO]? ('_'? OCTAL_DIGIT)+; 112 | HEX_LIT : '0' [xX] ('_'? HEX_DIGIT)+; 113 | 114 | FLOAT_LIT : (DECIMAL_FLOAT_LIT | HEX_FLOAT_LIT); 115 | 116 | DECIMAL_FLOAT_LIT : DECIMALS ('.' DECIMALS? EXPONENT? | EXPONENT) 117 | | '.' DECIMALS EXPONENT? 118 | ; 119 | 120 | HEX_FLOAT_LIT : '0' [xX] HEX_MANTISSA HEX_EXPONENT 121 | ; 122 | 123 | fragment HEX_MANTISSA : ('_'? HEX_DIGIT)+ ('.' ( '_'? HEX_DIGIT )*)? 124 | | '.' HEX_DIGIT ('_'? HEX_DIGIT)*; 125 | 126 | fragment HEX_EXPONENT : [pP] [+-]? DECIMALS; 127 | 128 | 129 | IMAGINARY_LIT : (DECIMAL_LIT | BINARY_LIT | OCTAL_LIT | HEX_LIT | FLOAT_LIT) 'i' ; 130 | 131 | // Rune literals 132 | 133 | fragment RUNE : '\'' (UNICODE_VALUE | BYTE_VALUE) '\'';//: '\'' (~[\n\\] | ESCAPED_VALUE) '\''; 134 | 135 | RUNE_LIT : RUNE; 136 | 137 | BYTE_VALUE : OCTAL_BYTE_VALUE | HEX_BYTE_VALUE; 138 | 139 | OCTAL_BYTE_VALUE: '\\' OCTAL_DIGIT OCTAL_DIGIT OCTAL_DIGIT; 140 | 141 | HEX_BYTE_VALUE: '\\' 'x' HEX_DIGIT HEX_DIGIT; 142 | 143 | LITTLE_U_VALUE: '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT; 144 | 145 | BIG_U_VALUE: '\\' 'U' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT; 146 | 147 | // String literals 148 | 149 | RAW_STRING_LIT : '`' ~'`'* '`'; 150 | INTERPRETED_STRING_LIT : '"' (~["\\] | ESCAPED_VALUE)* '"'; 151 | 152 | fragment UNICODE_VALUE: ~[\r\n'] | LITTLE_U_VALUE | BIG_U_VALUE | ESCAPED_VALUE; 153 | // Fragments 154 | fragment ESCAPED_VALUE 155 | : '\\' ('u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT 156 | | 'U' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT 157 | | [abfnrtv\\'"] 158 | | OCTAL_DIGIT OCTAL_DIGIT OCTAL_DIGIT 159 | | 'x' HEX_DIGIT HEX_DIGIT) 160 | ; 161 | fragment DECIMALS 162 | : [0-9] ('_'? [0-9])* 163 | ; 164 | fragment OCTAL_DIGIT 165 | : [0-7] 166 | ; 167 | fragment HEX_DIGIT 168 | : [0-9a-fA-F] 169 | ; 170 | fragment BIN_DIGIT 171 | : [01] 172 | ; 173 | fragment EXPONENT 174 | : [eE] [+-]? DECIMALS 175 | ; 176 | fragment LETTER 177 | : [a-zA-Z] 178 | | '_' 179 | ; 180 | fragment UNICODE_DIGIT 181 | : [\p{Nd}] 182 | ; 183 | 184 | fragment DIGIT 185 | : [0-9] 186 | ; 187 | 188 | // Whitespace and comments 189 | WS : [ \t\n\r]+ -> channel(1); 190 | COMMENT: '/*' .*? '*/' -> channel(2); 191 | LINE_COMMENT: '//' ~[\r\n]* -> channel(3); 192 | TERMINATOR: [\r\n]+ -> channel(4); 193 | 194 | // Emit an EOS token for any newlines, semicolon, multiline comments or the EOF and 195 | //return to normal lexing 196 | EOS: ([\r\t\n]+ | ';' | '/*' .*? '*/' | EOF); 197 | -------------------------------------------------------------------------------- /src/parser/RalphParser.g4: -------------------------------------------------------------------------------- 1 | parser grammar RalphParser; 2 | 3 | options 4 | { 5 | tokenVocab = RalphLexer; 6 | } 7 | 8 | sourceFile: imports (txScript | contract | interface | assetScript)* EOF; 9 | 10 | identifierList: varName (COMMA varName)*; 11 | 12 | varDeclSingle: (CONST | (LET MUT?)) varName ASSIGN expression; 13 | 14 | varDeclMulti: (CONST | (LET MUT?)) L_PAREN identifierList R_PAREN ASSIGN expression; 15 | 16 | varDecl 17 | : varDeclSingle 18 | | varDeclMulti //# varDeclStmt 19 | ; 20 | 21 | varName: IDENTIFIER; 22 | 23 | //expression 24 | expression: 25 | primaryExpr 26 | | callChain 27 | | ifStmt 28 | | L_PAREN expression R_PAREN 29 | | (SUB | NOT) expression 30 | | expression ( 31 | CONCAT 32 | | ADD 33 | | SUB 34 | | MUL 35 | | DIV 36 | | EXP 37 | | MOD 38 | | MODADD 39 | | MODSUB 40 | | MODMUL 41 | | MODEXP 42 | | SHL 43 | | SHR 44 | | BITAND 45 | | XOR 46 | | BITOR 47 | ) expression 48 | | expression ( 49 | EQ 50 | | NQ 51 | | LT 52 | | LE 53 | | GT 54 | | GE 55 | ) expression 56 | | expression (AND | OR) expression 57 | | expression ASSIGN expression 58 | ; 59 | 60 | expressionList: (expression COMMA?)*; 61 | 62 | callChain: (varName | methodCall) (DOT callChain)*; 63 | 64 | methodCall: IDENTIFIER aps? L_PAREN expressionList R_PAREN; 65 | 66 | apsPerAddress: expression R_ARROW apsTokenPart (COMMA apsTokenPart)*; 67 | apsTokenPart: expression COLON expression; 68 | aps: L_CURLY apsPerAddress (SEMI apsPerAddress)* R_CURLY; 69 | 70 | primaryExpr 71 | : basicLit 72 | | arrayExpr 73 | ; 74 | 75 | primitiveType 76 | : BOOL 77 | | I256 78 | | BYTE 79 | | U256 80 | | BYTEVEC 81 | | ADDRESS 82 | | arrayType 83 | ; 84 | 85 | arrayType 86 | : L_BRACKET typeName SEMI expression R_BRACKET // # arrayTypeDeclStmt 87 | ; 88 | 89 | arrayExpr: IDENTIFIER? L_BRACKET expression (COMMA expression)* R_BRACKET; 90 | 91 | typeName: primitiveType | IDENTIFIER ; 92 | 93 | result 94 | : L_PAREN R_PAREN 95 | | typeName 96 | | L_PAREN (typeName (COMMA typeName)* COMMA?)? R_PAREN 97 | ; 98 | 99 | param: ATUNUSED? MUT? IDENTIFIER COLON typeName; 100 | 101 | paramList: (param COMMA?)*; 102 | 103 | // Function declarations 104 | methodDecl 105 | : (annotation)? PUB? FN IDENTIFIER L_PAREN paramList R_PAREN (R_ARROW result)? block? 106 | ; 107 | 108 | basicLit 109 | : integer 110 | | string_ 111 | | ADDRESS_LIT 112 | | ALPH_LIT 113 | | BOOL_LIT 114 | | ALPH_TOKEN 115 | ; 116 | 117 | integer 118 | : DECIMAL_LIT 119 | | BINARY_LIT 120 | | OCTAL_LIT 121 | | HEX_LIT 122 | | IMAGINARY_LIT 123 | | RUNE_LIT 124 | | BYTEVEC_LIT 125 | ; 126 | 127 | string_: RAW_STRING_LIT | INTERPRETED_STRING_LIT; 128 | 129 | varNameAssign: varName ASSIGN basicLit; 130 | 131 | enum: ENUM IDENTIFIER L_CURLY varNameAssign* R_CURLY; 132 | 133 | typeStructBody: L_CURLY (statement | event | methodDecl | enum)* R_CURLY; 134 | 135 | imports: (IMPORT IMPORT_PATH)*; 136 | 137 | txScript 138 | : TXSCRIPT IDENTIFIER (L_PAREN paramList R_PAREN)? typeStructBody // # txScriptDeclStmt 139 | ; 140 | 141 | assetScript 142 | : ASSETSCRIPT IDENTIFIER (L_PAREN paramList R_PAREN)? typeStructBody 143 | ; 144 | 145 | contract 146 | : (stdAnnotation)? ABSTRACT? CONTRACT IDENTIFIER (L_PAREN paramList R_PAREN)? extends? implements? typeStructBody // # contractDeclStmt 147 | ; 148 | 149 | extends 150 | : EXTENDS contractExtends (COMMA contractExtends)* 151 | ; 152 | 153 | contractExtends: IDENTIFIER L_PAREN expressionList R_PAREN; 154 | 155 | implements 156 | : IMPLEMENTS IDENTIFIER 157 | ; 158 | 159 | stdAnnotation: STDANNOTATION L_PAREN expressionList R_PAREN; 160 | 161 | interface 162 | : (stdAnnotation)? INTERFACE IDENTIFIER (EXTENDS IDENTIFIER)? typeStructBody // # interfaceDeclStmt 163 | ; 164 | 165 | event: EVENT IDENTIFIER L_PAREN paramList R_PAREN; 166 | 167 | emit 168 | : EMIT IDENTIFIER L_PAREN expressionList R_PAREN // # emitStmt 169 | ; 170 | 171 | // [@using(preapprovedAssets = , assetsInContract = )] 172 | annotation 173 | : ATUSING L_PAREN expressionList R_PAREN 174 | ; 175 | 176 | block 177 | : L_CURLY (statement)* R_CURLY 178 | | statement 179 | ; 180 | 181 | statement: 182 | simpleStmt 183 | | ifStmt 184 | | whileStmt 185 | | forStmt 186 | ; 187 | 188 | simpleStmt 189 | : emptyStmt 190 | | returnStmt 191 | | varDecl 192 | | expression 193 | | emit 194 | ; 195 | 196 | emptyStmt: eos; 197 | 198 | returnStmt: RETURN expressionList; 199 | 200 | ifStmt: IF L_PAREN expression R_PAREN block elseStmt?; 201 | 202 | elseStmt: ELSE (block | ifStmt); 203 | 204 | whileStmt 205 | : WHILE L_PAREN expression? R_PAREN block 206 | ; 207 | 208 | forStmt: FOR L_PAREN LET MUT varName ASSIGN expression SEMI expression SEMI expression R_PAREN block; 209 | 210 | eos: EOS; 211 | -------------------------------------------------------------------------------- /src/parser/RalphParserVisitor.ts: -------------------------------------------------------------------------------- 1 | // Generated from ./src/parser/RalphParser.g4 by ANTLR 4.9.0-SNAPSHOT 2 | 3 | 4 | import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; 5 | 6 | import { SourceFileContext } from "./RalphParser"; 7 | import { IdentifierListContext } from "./RalphParser"; 8 | import { VarDeclSingleContext } from "./RalphParser"; 9 | import { VarDeclMultiContext } from "./RalphParser"; 10 | import { VarDeclContext } from "./RalphParser"; 11 | import { VarNameContext } from "./RalphParser"; 12 | import { ExpressionContext } from "./RalphParser"; 13 | import { ExpressionListContext } from "./RalphParser"; 14 | import { CallChainContext } from "./RalphParser"; 15 | import { MethodCallContext } from "./RalphParser"; 16 | import { ApsPerAddressContext } from "./RalphParser"; 17 | import { ApsTokenPartContext } from "./RalphParser"; 18 | import { ApsContext } from "./RalphParser"; 19 | import { PrimaryExprContext } from "./RalphParser"; 20 | import { PrimitiveTypeContext } from "./RalphParser"; 21 | import { ArrayTypeContext } from "./RalphParser"; 22 | import { ArrayExprContext } from "./RalphParser"; 23 | import { TypeNameContext } from "./RalphParser"; 24 | import { ResultContext } from "./RalphParser"; 25 | import { ParamContext } from "./RalphParser"; 26 | import { ParamListContext } from "./RalphParser"; 27 | import { MethodDeclContext } from "./RalphParser"; 28 | import { BasicLitContext } from "./RalphParser"; 29 | import { IntegerContext } from "./RalphParser"; 30 | import { String_Context } from "./RalphParser"; 31 | import { VarNameAssignContext } from "./RalphParser"; 32 | import { EnumContext } from "./RalphParser"; 33 | import { TypeStructBodyContext } from "./RalphParser"; 34 | import { ImportsContext } from "./RalphParser"; 35 | import { TxScriptContext } from "./RalphParser"; 36 | import { AssetScriptContext } from "./RalphParser"; 37 | import { ContractContext } from "./RalphParser"; 38 | import { ExtendsContext } from "./RalphParser"; 39 | import { ContractExtendsContext } from "./RalphParser"; 40 | import { ImplementsContext } from "./RalphParser"; 41 | import { StdAnnotationContext } from "./RalphParser"; 42 | import { InterfaceContext } from "./RalphParser"; 43 | import { EventContext } from "./RalphParser"; 44 | import { EmitContext } from "./RalphParser"; 45 | import { AnnotationContext } from "./RalphParser"; 46 | import { BlockContext } from "./RalphParser"; 47 | import { StatementContext } from "./RalphParser"; 48 | import { SimpleStmtContext } from "./RalphParser"; 49 | import { EmptyStmtContext } from "./RalphParser"; 50 | import { ReturnStmtContext } from "./RalphParser"; 51 | import { IfStmtContext } from "./RalphParser"; 52 | import { ElseStmtContext } from "./RalphParser"; 53 | import { WhileStmtContext } from "./RalphParser"; 54 | import { ForStmtContext } from "./RalphParser"; 55 | import { EosContext } from "./RalphParser"; 56 | 57 | 58 | /** 59 | * This interface defines a complete generic visitor for a parse tree produced 60 | * by `RalphParser`. 61 | * 62 | * @param The return type of the visit operation. Use `void` for 63 | * operations with no return type. 64 | */ 65 | export interface RalphParserVisitor extends ParseTreeVisitor { 66 | /** 67 | * Visit a parse tree produced by `RalphParser.sourceFile`. 68 | * @param ctx the parse tree 69 | * @return the visitor result 70 | */ 71 | visitSourceFile?: (ctx: SourceFileContext) => Result; 72 | 73 | /** 74 | * Visit a parse tree produced by `RalphParser.identifierList`. 75 | * @param ctx the parse tree 76 | * @return the visitor result 77 | */ 78 | visitIdentifierList?: (ctx: IdentifierListContext) => Result; 79 | 80 | /** 81 | * Visit a parse tree produced by `RalphParser.varDeclSingle`. 82 | * @param ctx the parse tree 83 | * @return the visitor result 84 | */ 85 | visitVarDeclSingle?: (ctx: VarDeclSingleContext) => Result; 86 | 87 | /** 88 | * Visit a parse tree produced by `RalphParser.varDeclMulti`. 89 | * @param ctx the parse tree 90 | * @return the visitor result 91 | */ 92 | visitVarDeclMulti?: (ctx: VarDeclMultiContext) => Result; 93 | 94 | /** 95 | * Visit a parse tree produced by `RalphParser.varDecl`. 96 | * @param ctx the parse tree 97 | * @return the visitor result 98 | */ 99 | visitVarDecl?: (ctx: VarDeclContext) => Result; 100 | 101 | /** 102 | * Visit a parse tree produced by `RalphParser.varName`. 103 | * @param ctx the parse tree 104 | * @return the visitor result 105 | */ 106 | visitVarName?: (ctx: VarNameContext) => Result; 107 | 108 | /** 109 | * Visit a parse tree produced by `RalphParser.expression`. 110 | * @param ctx the parse tree 111 | * @return the visitor result 112 | */ 113 | visitExpression?: (ctx: ExpressionContext) => Result; 114 | 115 | /** 116 | * Visit a parse tree produced by `RalphParser.expressionList`. 117 | * @param ctx the parse tree 118 | * @return the visitor result 119 | */ 120 | visitExpressionList?: (ctx: ExpressionListContext) => Result; 121 | 122 | /** 123 | * Visit a parse tree produced by `RalphParser.callChain`. 124 | * @param ctx the parse tree 125 | * @return the visitor result 126 | */ 127 | visitCallChain?: (ctx: CallChainContext) => Result; 128 | 129 | /** 130 | * Visit a parse tree produced by `RalphParser.methodCall`. 131 | * @param ctx the parse tree 132 | * @return the visitor result 133 | */ 134 | visitMethodCall?: (ctx: MethodCallContext) => Result; 135 | 136 | /** 137 | * Visit a parse tree produced by `RalphParser.apsPerAddress`. 138 | * @param ctx the parse tree 139 | * @return the visitor result 140 | */ 141 | visitApsPerAddress?: (ctx: ApsPerAddressContext) => Result; 142 | 143 | /** 144 | * Visit a parse tree produced by `RalphParser.apsTokenPart`. 145 | * @param ctx the parse tree 146 | * @return the visitor result 147 | */ 148 | visitApsTokenPart?: (ctx: ApsTokenPartContext) => Result; 149 | 150 | /** 151 | * Visit a parse tree produced by `RalphParser.aps`. 152 | * @param ctx the parse tree 153 | * @return the visitor result 154 | */ 155 | visitAps?: (ctx: ApsContext) => Result; 156 | 157 | /** 158 | * Visit a parse tree produced by `RalphParser.primaryExpr`. 159 | * @param ctx the parse tree 160 | * @return the visitor result 161 | */ 162 | visitPrimaryExpr?: (ctx: PrimaryExprContext) => Result; 163 | 164 | /** 165 | * Visit a parse tree produced by `RalphParser.primitiveType`. 166 | * @param ctx the parse tree 167 | * @return the visitor result 168 | */ 169 | visitPrimitiveType?: (ctx: PrimitiveTypeContext) => Result; 170 | 171 | /** 172 | * Visit a parse tree produced by `RalphParser.arrayType`. 173 | * @param ctx the parse tree 174 | * @return the visitor result 175 | */ 176 | visitArrayType?: (ctx: ArrayTypeContext) => Result; 177 | 178 | /** 179 | * Visit a parse tree produced by `RalphParser.arrayExpr`. 180 | * @param ctx the parse tree 181 | * @return the visitor result 182 | */ 183 | visitArrayExpr?: (ctx: ArrayExprContext) => Result; 184 | 185 | /** 186 | * Visit a parse tree produced by `RalphParser.typeName`. 187 | * @param ctx the parse tree 188 | * @return the visitor result 189 | */ 190 | visitTypeName?: (ctx: TypeNameContext) => Result; 191 | 192 | /** 193 | * Visit a parse tree produced by `RalphParser.result`. 194 | * @param ctx the parse tree 195 | * @return the visitor result 196 | */ 197 | visitResult?: (ctx: ResultContext) => Result; 198 | 199 | /** 200 | * Visit a parse tree produced by `RalphParser.param`. 201 | * @param ctx the parse tree 202 | * @return the visitor result 203 | */ 204 | visitParam?: (ctx: ParamContext) => Result; 205 | 206 | /** 207 | * Visit a parse tree produced by `RalphParser.paramList`. 208 | * @param ctx the parse tree 209 | * @return the visitor result 210 | */ 211 | visitParamList?: (ctx: ParamListContext) => Result; 212 | 213 | /** 214 | * Visit a parse tree produced by `RalphParser.methodDecl`. 215 | * @param ctx the parse tree 216 | * @return the visitor result 217 | */ 218 | visitMethodDecl?: (ctx: MethodDeclContext) => Result; 219 | 220 | /** 221 | * Visit a parse tree produced by `RalphParser.basicLit`. 222 | * @param ctx the parse tree 223 | * @return the visitor result 224 | */ 225 | visitBasicLit?: (ctx: BasicLitContext) => Result; 226 | 227 | /** 228 | * Visit a parse tree produced by `RalphParser.integer`. 229 | * @param ctx the parse tree 230 | * @return the visitor result 231 | */ 232 | visitInteger?: (ctx: IntegerContext) => Result; 233 | 234 | /** 235 | * Visit a parse tree produced by `RalphParser.string_`. 236 | * @param ctx the parse tree 237 | * @return the visitor result 238 | */ 239 | visitString_?: (ctx: String_Context) => Result; 240 | 241 | /** 242 | * Visit a parse tree produced by `RalphParser.varNameAssign`. 243 | * @param ctx the parse tree 244 | * @return the visitor result 245 | */ 246 | visitVarNameAssign?: (ctx: VarNameAssignContext) => Result; 247 | 248 | /** 249 | * Visit a parse tree produced by `RalphParser.enum`. 250 | * @param ctx the parse tree 251 | * @return the visitor result 252 | */ 253 | visitEnum?: (ctx: EnumContext) => Result; 254 | 255 | /** 256 | * Visit a parse tree produced by `RalphParser.typeStructBody`. 257 | * @param ctx the parse tree 258 | * @return the visitor result 259 | */ 260 | visitTypeStructBody?: (ctx: TypeStructBodyContext) => Result; 261 | 262 | /** 263 | * Visit a parse tree produced by `RalphParser.imports`. 264 | * @param ctx the parse tree 265 | * @return the visitor result 266 | */ 267 | visitImports?: (ctx: ImportsContext) => Result; 268 | 269 | /** 270 | * Visit a parse tree produced by `RalphParser.txScript`. 271 | * @param ctx the parse tree 272 | * @return the visitor result 273 | */ 274 | visitTxScript?: (ctx: TxScriptContext) => Result; 275 | 276 | /** 277 | * Visit a parse tree produced by `RalphParser.assetScript`. 278 | * @param ctx the parse tree 279 | * @return the visitor result 280 | */ 281 | visitAssetScript?: (ctx: AssetScriptContext) => Result; 282 | 283 | /** 284 | * Visit a parse tree produced by `RalphParser.contract`. 285 | * @param ctx the parse tree 286 | * @return the visitor result 287 | */ 288 | visitContract?: (ctx: ContractContext) => Result; 289 | 290 | /** 291 | * Visit a parse tree produced by `RalphParser.extends`. 292 | * @param ctx the parse tree 293 | * @return the visitor result 294 | */ 295 | visitExtends?: (ctx: ExtendsContext) => Result; 296 | 297 | /** 298 | * Visit a parse tree produced by `RalphParser.contractExtends`. 299 | * @param ctx the parse tree 300 | * @return the visitor result 301 | */ 302 | visitContractExtends?: (ctx: ContractExtendsContext) => Result; 303 | 304 | /** 305 | * Visit a parse tree produced by `RalphParser.implements`. 306 | * @param ctx the parse tree 307 | * @return the visitor result 308 | */ 309 | visitImplements?: (ctx: ImplementsContext) => Result; 310 | 311 | /** 312 | * Visit a parse tree produced by `RalphParser.stdAnnotation`. 313 | * @param ctx the parse tree 314 | * @return the visitor result 315 | */ 316 | visitStdAnnotation?: (ctx: StdAnnotationContext) => Result; 317 | 318 | /** 319 | * Visit a parse tree produced by `RalphParser.interface`. 320 | * @param ctx the parse tree 321 | * @return the visitor result 322 | */ 323 | visitInterface?: (ctx: InterfaceContext) => Result; 324 | 325 | /** 326 | * Visit a parse tree produced by `RalphParser.event`. 327 | * @param ctx the parse tree 328 | * @return the visitor result 329 | */ 330 | visitEvent?: (ctx: EventContext) => Result; 331 | 332 | /** 333 | * Visit a parse tree produced by `RalphParser.emit`. 334 | * @param ctx the parse tree 335 | * @return the visitor result 336 | */ 337 | visitEmit?: (ctx: EmitContext) => Result; 338 | 339 | /** 340 | * Visit a parse tree produced by `RalphParser.annotation`. 341 | * @param ctx the parse tree 342 | * @return the visitor result 343 | */ 344 | visitAnnotation?: (ctx: AnnotationContext) => Result; 345 | 346 | /** 347 | * Visit a parse tree produced by `RalphParser.block`. 348 | * @param ctx the parse tree 349 | * @return the visitor result 350 | */ 351 | visitBlock?: (ctx: BlockContext) => Result; 352 | 353 | /** 354 | * Visit a parse tree produced by `RalphParser.statement`. 355 | * @param ctx the parse tree 356 | * @return the visitor result 357 | */ 358 | visitStatement?: (ctx: StatementContext) => Result; 359 | 360 | /** 361 | * Visit a parse tree produced by `RalphParser.simpleStmt`. 362 | * @param ctx the parse tree 363 | * @return the visitor result 364 | */ 365 | visitSimpleStmt?: (ctx: SimpleStmtContext) => Result; 366 | 367 | /** 368 | * Visit a parse tree produced by `RalphParser.emptyStmt`. 369 | * @param ctx the parse tree 370 | * @return the visitor result 371 | */ 372 | visitEmptyStmt?: (ctx: EmptyStmtContext) => Result; 373 | 374 | /** 375 | * Visit a parse tree produced by `RalphParser.returnStmt`. 376 | * @param ctx the parse tree 377 | * @return the visitor result 378 | */ 379 | visitReturnStmt?: (ctx: ReturnStmtContext) => Result; 380 | 381 | /** 382 | * Visit a parse tree produced by `RalphParser.ifStmt`. 383 | * @param ctx the parse tree 384 | * @return the visitor result 385 | */ 386 | visitIfStmt?: (ctx: IfStmtContext) => Result; 387 | 388 | /** 389 | * Visit a parse tree produced by `RalphParser.elseStmt`. 390 | * @param ctx the parse tree 391 | * @return the visitor result 392 | */ 393 | visitElseStmt?: (ctx: ElseStmtContext) => Result; 394 | 395 | /** 396 | * Visit a parse tree produced by `RalphParser.whileStmt`. 397 | * @param ctx the parse tree 398 | * @return the visitor result 399 | */ 400 | visitWhileStmt?: (ctx: WhileStmtContext) => Result; 401 | 402 | /** 403 | * Visit a parse tree produced by `RalphParser.forStmt`. 404 | * @param ctx the parse tree 405 | * @return the visitor result 406 | */ 407 | visitForStmt?: (ctx: ForStmtContext) => Result; 408 | 409 | /** 410 | * Visit a parse tree produced by `RalphParser.eos`. 411 | * @param ctx the parse tree 412 | * @return the visitor result 413 | */ 414 | visitEos?: (ctx: EosContext) => Result; 415 | } 416 | 417 | -------------------------------------------------------------------------------- /src/parser/parser.ts: -------------------------------------------------------------------------------- 1 | import { CharStreams, CommonTokenStream } from 'antlr4ts' 2 | import { Uri } from 'vscode' 3 | import { RalphLexer } from './RalphLexer' 4 | import { RalphParser } from './RalphParser' 5 | import { RalphVisitor } from '../visitors/ralphVisitor' 6 | import { Identifier } from '../ast/identifier' 7 | 8 | export default function Parser(uri: Uri, text: string): Identifier[]{ 9 | // Create the lexer and parser 10 | const charStream = CharStreams.fromString(text, uri.path) 11 | const lexer = new RalphLexer(charStream) 12 | const tokenStream = new CommonTokenStream(lexer) 13 | const parser = new RalphParser(tokenStream) 14 | // Parse the input, where `sourceFile` is whatever entry point you defined 15 | const tree = parser.sourceFile() 16 | const visitor = new RalphVisitor(uri, parser, lexer, charStream, tokenStream) 17 | visitor.visit(tree) 18 | return visitor.cache 19 | } 20 | -------------------------------------------------------------------------------- /src/prettier/defaultOptions.ts: -------------------------------------------------------------------------------- 1 | export const defaultOptions = { 2 | tabWidth: 4, 3 | printWidth: 120, 4 | } 5 | -------------------------------------------------------------------------------- /src/prettier/index.ts: -------------------------------------------------------------------------------- 1 | import { languages } from './languages' 2 | import { parsers } from './parsers' 3 | import { printers } from './printers' 4 | import { options } from './options' 5 | import { defaultOptions } from './defaultOptions' 6 | 7 | export { languages, parsers, printers, options, defaultOptions } 8 | -------------------------------------------------------------------------------- /src/prettier/languages.ts: -------------------------------------------------------------------------------- 1 | import { SupportLanguage } from 'prettier' 2 | 3 | // https://prettier.io/docs/en/plugins.html#languages 4 | export const languages: SupportLanguage[] = [ 5 | { 6 | name: 'ralph', 7 | extensions: ['.ral'], 8 | parsers: ['ralph'], 9 | filenames: ['*.ral'], 10 | vscodeLanguageIds: ['ralph'], 11 | // linguistLanguageId: 602937027, 12 | }, 13 | ] 14 | -------------------------------------------------------------------------------- /src/prettier/loc.ts: -------------------------------------------------------------------------------- 1 | import { Token } from 'antlr4ts/Token' 2 | 3 | export function locStart(node: Token) { 4 | // TODO: fix 5 | return node.startIndex 6 | } 7 | 8 | export function locEnd(node: Token) { 9 | // TODO: fix 10 | return node.stopIndex 11 | } 12 | -------------------------------------------------------------------------------- /src/prettier/options.ts: -------------------------------------------------------------------------------- 1 | import { SupportOption } from 'prettier' 2 | 3 | export const options: Record = {} 4 | -------------------------------------------------------------------------------- /src/prettier/parsers.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from 'prettier' 2 | import { CharStreams, CommonTokenStream } from 'antlr4ts' 3 | import { locStart, locEnd } from './loc' 4 | import { RalphParser } from '../parser/RalphParser' 5 | import { RalphLexer } from '../parser/RalphLexer' 6 | 7 | export const parsers: Record = { 8 | ralph: { 9 | parse, 10 | locStart, 11 | locEnd, 12 | astFormat: 'ralph', 13 | }, 14 | } 15 | 16 | export function parse(text: string, parsers: any, options: any) { 17 | // Create the lexer and parser 18 | const inputStream = CharStreams.fromString(text) 19 | const lexer = new RalphLexer(inputStream) 20 | const tokenStream = new CommonTokenStream(lexer) 21 | const parser = new RalphParser(tokenStream) 22 | // Parse the input, where `sourceFile` is whatever entry point you defined 23 | const root = parser.sourceFile() 24 | } 25 | -------------------------------------------------------------------------------- /src/prettier/plugin.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'prettier' 2 | import { languages } from './languages' 3 | import { parsers } from './parsers' 4 | import { printers } from './printers' 5 | import { options } from './options' 6 | import { defaultOptions } from './defaultOptions' 7 | 8 | export const plugin: Plugin = { 9 | languages, 10 | parsers, 11 | printers, 12 | options, 13 | defaultOptions, 14 | } 15 | -------------------------------------------------------------------------------- /src/prettier/prettier.ts: -------------------------------------------------------------------------------- 1 | /* 2 | defaultOptions: { 3 | tabWidth: 2; 4 | } 5 | 6 | function locStart(cstNode) { 7 | // TODO: TBD 8 | } 9 | 10 | function locEnd(cstNode) { 11 | // TODO: TBD 12 | } 13 | 14 | // https://prettier.io/docs/en/plugins.html#languages 15 | const languages = [ 16 | { 17 | extensions: ['.ral'], 18 | name: 'ralph', 19 | parsers: ['ralph'], 20 | type: 'data', 21 | filenames: ['*.ral'], 22 | tmScope: 'source.ralph', 23 | vscodeLanguageIds: ['ralph'] 24 | } 25 | ]; 26 | 27 | // https://prettier.io/docs/en/plugins.html#parsers 28 | const parsers = { 29 | ralph: { 30 | astFormat: 'ralph-cst', 31 | parse: (text, parsers, options) => parse(text), 32 | locStart, 33 | locEnd 34 | } 35 | }; 36 | 37 | // https://prettier.io/docs/en/plugins.html#printers 38 | const printers = { 39 | 'ralph-cst': { 40 | print 41 | } 42 | }; 43 | 44 | module.exports = { 45 | languages, 46 | parsers, 47 | printers 48 | // TODO: are any options/default options needed? 49 | // - Prefer certain inline variants when possible? 50 | // - Indent nested props? 51 | }; 52 | 53 | */ 54 | -------------------------------------------------------------------------------- /src/prettier/printers.ts: -------------------------------------------------------------------------------- 1 | import { Printer, AstPath, Doc, ParserOptions, doc } from 'prettier' 2 | 3 | const { concat } = doc.builders 4 | 5 | export const printers: Record = { 6 | 'ralph-ast': { 7 | print, 8 | }, 9 | } 10 | 11 | function print(path: AstPath, options: ParserOptions, print: (path: AstPath) => Doc): Doc { 12 | return '' 13 | } 14 | -------------------------------------------------------------------------------- /src/provider/builtIn/contractBuiltIn.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { Contract } from '../../ast/contract' 3 | import { Property } from '../../ast/property' 4 | import cache from '../../cache/cache' 5 | import { Func } from './func' 6 | 7 | export const ContractBuiltInFuncs: Record Func> = { 8 | encodeImmFields: getEncodeImmFieldsFunc, 9 | encodeMutFields: getEncodeMutFieldsFunc, 10 | encodeFields: getEncodeFieldsFunc, 11 | } 12 | 13 | export const ContractBuiltInFuncNames = Object.keys(ContractBuiltInFuncs) 14 | 15 | function getEncodeFunc(fields: Property[], funcName: string, returnTypes: string, doc: string, returns: string): Func { 16 | const fieldsNameAndType = fields.map((field) => [field.name!, field.type_!.name!]) 17 | const fieldsSig = fieldsNameAndType.map(([name, type]) => `${name}:${type}`).join(', ') 18 | const signature = `fn ${funcName}(${fieldsSig}) -> ${returnTypes}` 19 | return { 20 | name: funcName, 21 | category: 'Contract', 22 | signature, 23 | doc, 24 | params: fieldsNameAndType.map(([name, type]) => `${name}: ${type}`), 25 | paramNames: fieldsNameAndType.map(([name, _]) => name), 26 | returns, 27 | } 28 | } 29 | 30 | export function getEncodeImmFieldsFunc(contract: Contract): Func { 31 | const immFields = contract.getImmutableFields() 32 | return getEncodeFunc( 33 | immFields, 34 | 'encodeImmFields', 35 | 'ByteVec', 36 | 'Encode contract immutable fields to bytevec', 37 | '@returns a ByteVec encoding the inputs' 38 | ) 39 | } 40 | 41 | export function getEncodeMutFieldsFunc(contract: Contract): Func { 42 | const mutFields = contract.getMutableFields() 43 | return getEncodeFunc( 44 | mutFields, 45 | 'encodeMutFields', 46 | 'ByteVec', 47 | 'Encode contract mutable fields to bytevec', 48 | '@returns a ByteVec encoding the inputs' 49 | ) 50 | } 51 | 52 | export function getEncodeFieldsFunc(contract: Contract): Func { 53 | const fields = contract.getFields() 54 | return getEncodeFunc( 55 | fields, 56 | 'encodeFields', 57 | '(ByteVec, ByteVec)', 58 | 'Encode contract fields', 59 | '@returns two ByteVec values, where the first is the encoded immutable fields and the second is the encoded mutable fields' 60 | ) 61 | } 62 | 63 | export function getContractBiltInFunction(contract: Contract): Func[] { 64 | return [getEncodeImmFieldsFunc(contract), getEncodeMutFieldsFunc(contract), getEncodeFieldsFunc(contract)] 65 | } 66 | 67 | export function tryGetContractBuiltInFunction( 68 | document: vscode.TextDocument, 69 | range: vscode.Range, 70 | builtInFuncName: string 71 | ): Func | undefined { 72 | if (!builtInFuncName.endsWith('!')) { 73 | return undefined 74 | } 75 | const funcName = builtInFuncName.slice(0, -1) 76 | if (!ContractBuiltInFuncNames.includes(funcName)) { 77 | return undefined 78 | } 79 | const regex = new RegExp(`[A-Z][a-zA-Z0-9_]*\\.${funcName}`) 80 | const fullyQualifiedRange = document.getWordRangeAtPosition(range.start, regex) 81 | const fullyQualifiedName = document.getText(fullyQualifiedRange) 82 | const typeId = fullyQualifiedName.split('.')[0] 83 | const contractDef = cache.get(document.uri, typeId) 84 | if (contractDef === undefined || !(contractDef instanceof Contract)) { 85 | return undefined 86 | } 87 | return ContractBuiltInFuncs[`${funcName}`](contractDef) 88 | } 89 | -------------------------------------------------------------------------------- /src/provider/builtIn/func.ts: -------------------------------------------------------------------------------- 1 | // https://raw.githubusercontent.com/alephium/alephium/master/protocol/src/main/resources/ralph-built-in-functions.json 2 | export interface Func { 3 | name: string 4 | 5 | category: string 6 | 7 | signature: string 8 | 9 | doc: string 10 | 11 | params: string[] 12 | 13 | paramNames: string[] 14 | 15 | returns: string 16 | } 17 | -------------------------------------------------------------------------------- /src/provider/completion/annotationProvider.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import * as vscode from 'vscode' 3 | import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, Position, TextDocument } from 'vscode' 4 | import { Filter } from '../filter' 5 | 6 | const annotations: Map = new Map([ 7 | ['using', ['preapprovedAssets', 'assetsInContract', 'checkExternalCaller', 'updateFields']], 8 | ['std', ['id', 'enabled']], 9 | ['unused', []], 10 | ]) 11 | 12 | export class AnnotationProvider extends Filter implements vscode.CompletionItemProvider { 13 | provideCompletionItems( 14 | document: TextDocument, 15 | position: Position, 16 | token: CancellationToken, 17 | context: CompletionContext 18 | ): vscode.ProviderResult { 19 | if (this.isSkip(document, position)) return undefined 20 | 21 | const keys = Array.from(annotations.keys()) 22 | if (context.triggerCharacter === '@') { 23 | return keys.map((item) => new CompletionItem({ label: item }, CompletionItemKind.Keyword)) 24 | } 25 | 26 | if (context.triggerCharacter === '(' || context.triggerCharacter === ')') { 27 | const { text } = document.lineAt(position) 28 | const trimmedText = text.trim() 29 | const selectedKey = keys.find((key) => trimmedText.includes(`@${key}`)) 30 | if (selectedKey === undefined) { 31 | return undefined 32 | } 33 | const fields = annotations.get(selectedKey) 34 | if (fields === undefined || fields.length === 0) { 35 | return undefined 36 | } 37 | return fields.map((field) => new CompletionItem({ label: `${field} = ` }, CompletionItemKind.Field)) 38 | } 39 | return undefined 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/provider/completion/builtInProvider.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import * as vscode from 'vscode' 3 | import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, Position, SnippetString, TextDocument } from 'vscode' 4 | import jsonData from '../builtIn/ralph-built-in-functions.json' 5 | import { Func } from '../builtIn/func' 6 | import { Identifier } from '../../ast/identifier' 7 | import { BuiltInType } from '../../ast/builtInType' 8 | import { builtInType } from '../hover/builtIn/primitives' 9 | import { keyword } from '../hover/builtIn/keyword' 10 | import { Filter } from '../filter' 11 | 12 | export class BuiltInProvider extends Filter implements vscode.CompletionItemProvider { 13 | items: Array 14 | 15 | builtInType: Array 16 | 17 | builtInLiteral: Array 18 | 19 | constructor() { 20 | super() 21 | this.items = Object.assign(new Array(), jsonData) 22 | this.builtInType = builtInType.map((value) => { 23 | const obj = new BuiltInType(value.name) 24 | obj.description = `${value.kind}: ${value.detail}` 25 | return obj 26 | }) 27 | this.builtInLiteral = ['true', 'false'] 28 | } 29 | 30 | provideCompletionItems( 31 | document: TextDocument, 32 | position: Position, 33 | token: CancellationToken, 34 | context: CompletionContext 35 | ): vscode.ProviderResult { 36 | if (this.isSkip(document, position)) return undefined 37 | const items = this.builtInType 38 | .map((value) => value.completionItem!()) 39 | .concat(this.builtInLiteral.map((value) => new CompletionItem({ label: value }, CompletionItemKind.Value))) 40 | .concat(keyword.map((value) => new CompletionItem({ label: value.name, description: value.detail }, CompletionItemKind.Keyword))) 41 | 42 | if (document.getWordRangeAtPosition(position, /\([a-zA-Z_][0-9a-zA-Z_, :!().;]*\)/i)) return items 43 | return this.items 44 | .map((item) => { 45 | const method = new CompletionItem( 46 | { 47 | // label: `🔓${item.name}!${item.signature.substring(item.signature.indexOf('('), item.signature.indexOf(')') + 1)}`, 48 | // label: `🔓${item.signature.substring(3)}`, 49 | label: `🔓${item.name}!`, 50 | // detail: item.signature, 51 | description: item.signature, 52 | }, 53 | CompletionItemKind.Function 54 | ) 55 | 56 | method.filterText = item.name 57 | method.sortText = item.name 58 | method.preselect = true 59 | method.insertText = new SnippetString(`${item.name}!`) 60 | return method 61 | }) 62 | .concat(items) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/provider/completion/contractBuiltinProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, Position, SnippetString, TextDocument } from 'vscode' 3 | import { Filter } from '../filter' 4 | import cache from '../../cache/cache' 5 | import { Contract } from '../../ast/contract' 6 | import { getContractBiltInFunction } from '../builtIn/contractBuiltIn' 7 | 8 | export class ContractBuiltInProvider extends Filter implements vscode.CompletionItemProvider { 9 | provideCompletionItems( 10 | document: TextDocument, 11 | position: Position, 12 | token: CancellationToken, 13 | context: CompletionContext 14 | ): vscode.ProviderResult { 15 | if (this.isSkip(document, position)) return undefined 16 | 17 | const line = document.lineAt(position.line) 18 | const matched = line.text.substring(0, position.character).match(/([A-Z][a-zA-Z0-9_]*\.([a-z][a-zA-Z0-9_]*)?)/) 19 | if (matched === null || matched.length < 1) { 20 | return undefined 21 | } 22 | const staticCall = matched[0] 23 | const tokens = staticCall.split('.') 24 | if (tokens.length < 1) { 25 | return undefined 26 | } 27 | const typeId = tokens[0] 28 | const contractDef = cache.get(document.uri, typeId) 29 | if (contractDef === undefined || !(contractDef instanceof Contract)) { 30 | return undefined 31 | } 32 | const funcs = getContractBiltInFunction(contractDef) 33 | return funcs.map((func) => { 34 | const method = new CompletionItem( 35 | { 36 | label: `🔓${func.name}!`, 37 | description: func.signature, 38 | }, 39 | CompletionItemKind.Function 40 | ) 41 | 42 | method.filterText = func.name 43 | method.sortText = func.name 44 | method.preselect = true 45 | method.insertText = new SnippetString(`${func.name}!`) 46 | return method 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/provider/completion/emitProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { CancellationToken, ProviderResult } from 'vscode' 3 | import cache from '../../cache/cache' 4 | import { Filter } from '../filter' 5 | import { Contract } from '../../ast/contract' 6 | import { Event } from '../../ast/event' 7 | import { Identifier } from '../../ast/identifier' 8 | 9 | export class EmitProvider extends Filter implements vscode.CompletionItemProvider { 10 | provideCompletionItems( 11 | document: vscode.TextDocument, 12 | position: vscode.Position, 13 | token: vscode.CancellationToken, 14 | context: vscode.CompletionContext 15 | ): vscode.ProviderResult { 16 | return this.emitter(cache.container(document.uri, { point: position, uri: document.uri })) 17 | } 18 | 19 | resolveCompletionItem(item: vscode.CompletionItem, token: CancellationToken): ProviderResult { 20 | return undefined 21 | } 22 | 23 | emitter(container: Identifier | undefined): vscode.ProviderResult { 24 | if (container) { 25 | if (container instanceof Contract) { 26 | return container 27 | .defs() 28 | .filter((value) => value instanceof Event) 29 | .map((value) => value.completionItem!()) 30 | } 31 | if (container.parent) { 32 | return this.emitter(container.parent) 33 | } 34 | } 35 | return undefined 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/provider/completion/enumProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import cache from '../../cache/cache' 3 | import { Filter } from '../filter' 4 | 5 | export class EnumProvider extends Filter implements vscode.CompletionItemProvider { 6 | provideCompletionItems( 7 | document: vscode.TextDocument, 8 | position: vscode.Position, 9 | token: vscode.CancellationToken, 10 | context: vscode.CompletionContext 11 | ): vscode.ProviderResult { 12 | const word = this.word(document, position, -1) 13 | if (word) { 14 | const member = cache.def(document.uri, word) 15 | if (member) return member?.getChild?.().map((value) => value.completionItem!()) 16 | } 17 | return undefined 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/provider/completion/globalProvider.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import * as vscode from 'vscode' 3 | import cache from '../../cache/cache' 4 | import { Filter } from '../filter' 5 | 6 | export class GlobalProvider extends Filter implements vscode.CompletionItemProvider { 7 | provideCompletionItems( 8 | document: vscode.TextDocument, 9 | position: vscode.Position, 10 | token: vscode.CancellationToken, 11 | context: vscode.CompletionContext 12 | ): vscode.ProviderResult { 13 | if (this.isSkip(document, position)) return undefined 14 | return cache.defs(document.uri)?.map((value) => value.completionItem!()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/provider/completion/identifierProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import cache from '../../cache/cache' 3 | import { Identifier } from '../../ast/identifier' 4 | import { Filter } from '../filter' 5 | 6 | export class IdentifierProvider extends Filter implements vscode.CompletionItemProvider { 7 | provideCompletionItems( 8 | document: vscode.TextDocument, 9 | position: vscode.Position, 10 | token: vscode.CancellationToken, 11 | context: vscode.CompletionContext 12 | ): vscode.ProviderResult { 13 | if (this.isSkip(document, position)) return undefined 14 | const pos = { uri: document.uri, point: position } 15 | const container = cache.container(document.uri, pos) 16 | return this.defs(container) 17 | } 18 | 19 | defs(identifier: Identifier | undefined): vscode.CompletionItem[] { 20 | if (identifier) { 21 | const subItems = identifier.defs?.().map((value) => value.completionItem!()) 22 | const items: vscode.CompletionItem[] = [] 23 | if (subItems) items.push(...subItems) 24 | items.push(...this.defs(identifier.parent)) 25 | return items 26 | } 27 | return [] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/provider/completion/memberProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import cache from '../../cache/cache' 3 | import { Filter } from '../filter' 4 | import { Method } from '../../ast/method' 5 | import { EnumMember } from '../../ast/enum' 6 | 7 | export class MemberProvider extends Filter implements vscode.CompletionItemProvider { 8 | provideCompletionItems( 9 | document: vscode.TextDocument, 10 | position: vscode.Position, 11 | token: vscode.CancellationToken, 12 | context: vscode.CompletionContext 13 | ): vscode.ProviderResult { 14 | const word = this.word(document, position, -1) 15 | if (word) { 16 | return cache 17 | .def(document.uri, word) 18 | ?.getType?.() 19 | ?.defs?.() 20 | .filter((value) => { 21 | if (value instanceof Method) return value.isPub 22 | return value instanceof EnumMember 23 | }) 24 | ?.map((value) => value.completionItem!()) 25 | } 26 | return undefined 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/provider/definitionProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { Definition, DefinitionLink, Position, ProviderResult, TextDocument } from 'vscode' 3 | import { Filter } from './filter' 4 | 5 | export class DefinitionProvider extends Filter implements vscode.DefinitionProvider { 6 | provideDefinition(document: TextDocument, position: Position): ProviderResult { 7 | return this.callChain(document, position)?.location?.() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/provider/filter.ts: -------------------------------------------------------------------------------- 1 | import { Position, TextDocument } from 'vscode' 2 | import { Word } from '../ast/word' 3 | import { Identifier } from '../ast/identifier' 4 | import cache from '../cache/cache' 5 | import { Base } from '../ast/base' 6 | 7 | export class Filter { 8 | isSkip(document: TextDocument, position: Position): boolean { 9 | const { text } = document.lineAt(position) 10 | if (text) { 11 | return text.indexOf('//') >= 0 && text.indexOf('//') < position.character 12 | } 13 | return true 14 | } 15 | 16 | word(document: TextDocument, position: Position, offset = 0): Word | undefined { 17 | if (this.isSkip(document, position)) return undefined 18 | const range = document.getWordRangeAtPosition(position.with(position.line, position.character + offset)) 19 | return { 20 | name: document.getText(range), 21 | point: position, 22 | uri: document.uri, 23 | } 24 | } 25 | 26 | callChain(document: TextDocument, position: Position): Identifier | undefined { 27 | if (this.isSkip(document, position)) return undefined 28 | const callChain = document.getText(document.getWordRangeAtPosition(position, /[a-zA-Z_][0-9a-zA-Z._]*/i)) 29 | const positionWord = document.getText(document.getWordRangeAtPosition(position, /[a-zA-Z_][0-9a-zA-Z_]*/i)) 30 | let wordSet = callChain.split('.') 31 | wordSet = wordSet.slice(0, wordSet.indexOf(positionWord) + 1) 32 | const word = { 33 | name: wordSet[0], 34 | point: position, 35 | uri: document.uri, 36 | } 37 | let caller = cache.def(document.uri, word) 38 | let i = 0 39 | while (true) { 40 | if (wordSet.length - 1 === i) { 41 | return caller 42 | } 43 | const type_ = caller?.getType?.() 44 | if (type_ instanceof Base) { 45 | i += 1 46 | caller = type_.members.get(wordSet[i]) 47 | } else { 48 | return undefined 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/provider/hover/builtIn/function.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { MarkdownString } from 'vscode' 3 | import jsonData from '../../builtIn/ralph-built-in-functions.json' 4 | import { Filter } from '../../filter' 5 | import { Func } from '../../builtIn/func' 6 | import { tryGetContractBuiltInFunction } from '../../builtIn/contractBuiltIn' 7 | 8 | export class FunctionHoverProvider extends Filter implements vscode.HoverProvider { 9 | builtItems: Map 10 | 11 | items: Array 12 | 13 | constructor() { 14 | super() 15 | this.builtItems = new Map() 16 | this.items = Object.assign(new Array(), jsonData) 17 | this.items.forEach((item) => this.builtItems.set(`${item?.name}!`, item)) 18 | } 19 | 20 | provideHover(document: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult { 21 | if (this.isSkip(document, position)) return undefined 22 | const range = document.getWordRangeAtPosition(position) 23 | if (range === undefined) { 24 | return undefined 25 | } 26 | const funcName = document.getText(range.with(range.start, new vscode.Position(range.end.line, range.end.character + 1))) 27 | const item = this.builtItems.get(funcName) 28 | if (item !== undefined) { 29 | return this.getHoverDetail(item, false) 30 | } 31 | const contractBuiltInFunc = tryGetContractBuiltInFunction(document, range, funcName) 32 | if (contractBuiltInFunc === undefined) { 33 | return undefined 34 | } 35 | return this.getHoverDetail(contractBuiltInFunc, true) 36 | } 37 | 38 | private getHoverDetail(func: Func, isContractBuiltInFunc: boolean): vscode.Hover { 39 | const detail = new MarkdownString() 40 | detail.appendMarkdown(` 41 | ${func.signature} 42 | \n---\n\t 43 | `) 44 | if (!isContractBuiltInFunc) { 45 | func.params.map((param) => detail.appendMarkdown(`${param}\n\t`)) 46 | } 47 | detail.appendMarkdown(`${func.returns}\n\t`) 48 | return new vscode.Hover(detail) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/provider/hover/builtIn/item.ts: -------------------------------------------------------------------------------- 1 | import { Hover, MarkdownString } from 'vscode' 2 | 3 | export class Item { 4 | name: string 5 | 6 | detail: string 7 | 8 | kind: string 9 | 10 | constructor(name: string, detail: string, kind: string) { 11 | this.name = name 12 | this.detail = detail 13 | this.kind = kind 14 | } 15 | 16 | hover(): Hover { 17 | const value = new MarkdownString() 18 | value.appendMarkdown(` 19 | builtIn global ${this.kind}: ${this.name} 20 | ${this.detail} 21 | `) 22 | return new Hover(value) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/provider/hover/builtIn/keyword.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { Item } from './item' 3 | import { Filter } from '../../filter' 4 | 5 | export const keyword = [ 6 | { 7 | name: 'const', 8 | detail: 'const varName = value', 9 | kind: 'keyword', 10 | }, 11 | { 12 | name: 'let', 13 | detail: 'Assignment: let [mut] = ...', 14 | kind: 'keyword', 15 | }, 16 | { 17 | name: 'mut', 18 | detail: 'Assignment: let [mut] = ...', 19 | kind: 'keyword', 20 | }, 21 | { 22 | name: '=', 23 | detail: 'Assignment: = or (, ) = funcMultipleRetVals()', 24 | kind: 'keyword', 25 | }, 26 | { 27 | name: 'pub', 28 | detail: 'Function: [pub] fn (arg: ) -> { return }', 29 | kind: 'keyword', 30 | }, 31 | { 32 | name: 'fn', 33 | detail: 'Function: [pub] fn (arg: ) -> { return }', 34 | kind: 'keyword', 35 | }, 36 | { 37 | name: 'return', 38 | detail: 'Function: [pub] fn (arg: ) -> { return }', 39 | kind: 'keyword', 40 | }, 41 | { 42 | name: 'if', 43 | detail: 'Conditional: if { } [else if { } else { ... }]', 44 | kind: 'keyword', 45 | }, 46 | { 47 | name: 'else', 48 | detail: 'Conditional: if { } [else if { } else { ... }]', 49 | kind: 'keyword', 50 | }, 51 | { 52 | name: 'while', 53 | detail: 'Iteration: while { }', 54 | kind: 'keyword', 55 | }, 56 | { 57 | name: 'for', 58 | detail: 'for (let mut index = 0; index <= length; index = index + 1){...}', 59 | kind: 'keyword', 60 | }, 61 | { 62 | name: 'event', 63 | detail: 'event (field1: , field2: , fieldN: , ...)', 64 | kind: 'keyword', 65 | }, 66 | { 67 | name: 'emit', 68 | detail: 'emit (, , , ...)', 69 | kind: 'keyword', 70 | }, 71 | { 72 | name: 'Interface', 73 | detail: 'Interface { ... }', 74 | kind: 'keyword', 75 | }, 76 | { 77 | name: 'AssetScript', 78 | detail: 'AssetScript ([mut] fieldN: ) { ... }', 79 | kind: 'keyword', 80 | }, 81 | { 82 | name: 'Contract', 83 | detail: 'Contract ContractName([mut] fieldN: ) [extends ] { ... }', 84 | kind: 'keyword', 85 | }, 86 | { 87 | name: 'extends', 88 | detail: 'Contract ContractName([mut] fieldN: ) [extends ] { ... }', 89 | kind: 'keyword', 90 | }, 91 | { 92 | name: 'implements', 93 | detail: 'Contract ContractName([mut] fieldN: ) [implements ] { ... }', 94 | kind: 'keyword', 95 | }, 96 | { 97 | name: 'TxScript', 98 | detail: 'TxScript ([mut] fieldN: ) { ... }', 99 | kind: 'keyword', 100 | }, 101 | { 102 | name: 'enum', 103 | detail: 'enum{...}', 104 | kind: 'keyword', 105 | }, 106 | { 107 | name: 'Abstract', 108 | detail: 'Abstract', 109 | kind: 'keyword', 110 | }, 111 | { 112 | name: 'using', 113 | detail: '@using(approvedAssets = true)', 114 | kind: 'keyword', 115 | }, 116 | { 117 | name: '@using', 118 | detail: '@using(approvedAssets = true)', 119 | kind: 'keyword', 120 | }, 121 | { 122 | name: 'unused', 123 | detail: '@unused value: type', 124 | kind: 'keyword', 125 | }, 126 | { 127 | name: '@unused', 128 | detail: '@unused value: type', 129 | kind: 'keyword', 130 | }, 131 | { 132 | name: 'alph', 133 | detail: 'alph', 134 | kind: 'keyword', 135 | }, 136 | ] 137 | 138 | export class KeywordHoverProvider extends Filter implements vscode.HoverProvider { 139 | builtItems!: Map 140 | 141 | constructor() { 142 | super() 143 | this.builtItems = new Map() 144 | keyword.forEach((item) => this.builtItems.set(item.name, new Item(item.name, item.detail, item.kind))) 145 | } 146 | 147 | provideHover(document: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult { 148 | const word = this.word(document, position) 149 | return this.builtItems.get(word?.name)?.hover() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/provider/hover/builtIn/primitives.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { Item } from './item' 3 | import { Filter } from '../../filter' 4 | 5 | export const builtInType = [ 6 | { 7 | name: '()', 8 | detail: 'Constructor: None (a return statement with nothing)', 9 | kind: 'primitives type', 10 | }, 11 | { 12 | name: 'Bool', 13 | detail: 'Constructor: false, true: <, >, >=, <=, ==, !=, &&, ||, !', 14 | kind: 'primitives type', 15 | }, 16 | { 17 | name: 'I256', 18 | detail: 'Constructor: -, i, +, -, *, /, %, ⊕, ⊖, ⊗, <<, >>, >, ^, |', 19 | kind: 'primitives type', 20 | }, 21 | { 22 | name: 'U256', 23 | detail: 'Constructor: , u, +, -, *, /, %, ⊕, ⊖, ⊗, <<, >>, >, ^, |', 24 | kind: 'primitives type', 25 | }, 26 | { 27 | name: 'Address', 28 | detail: 'Constructor: @
, nullAddress!()', 29 | kind: 'primitives type', 30 | }, 31 | { 32 | name: 'ByteVec', 33 | detail: 'Constructor: #, ++', 34 | kind: 'primitives type', 35 | }, 36 | { 37 | name: '[type; size] ', 38 | detail: 'Constructor: [], type example: [Address; 6]', 39 | kind: 'primitives type', 40 | }, 41 | ] 42 | 43 | export class BuiltInPrimitivesHoverProvider extends Filter implements vscode.HoverProvider { 44 | builtItems!: Map 45 | 46 | constructor() { 47 | super() 48 | this.builtItems = new Map() 49 | builtInType.forEach((item) => this.builtItems.set(item.name, new Item(item.name, item.detail, item.kind))) 50 | } 51 | 52 | provideHover(document: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult { 53 | const word = this.word(document, position) 54 | return this.builtItems.get(word?.name)?.hover() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/provider/hover/hoverProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { Filter } from '../filter' 3 | 4 | export class HoverProvider extends Filter implements vscode.HoverProvider { 5 | provideHover(document: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult { 6 | return this.callChain(document, position)?.hover?.() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/provider/hover/providers.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { FunctionHoverProvider } from './builtIn/function' 3 | import { KeywordHoverProvider } from './builtIn/keyword' 4 | import { BuiltInPrimitivesHoverProvider } from './builtIn/primitives' 5 | import { HoverProvider } from './hoverProvider' 6 | 7 | export function Providers(): vscode.HoverProvider[] { 8 | return [new FunctionHoverProvider(), new KeywordHoverProvider(), new BuiltInPrimitivesHoverProvider(), new HoverProvider()] 9 | } 10 | -------------------------------------------------------------------------------- /src/provider/implementationProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { CancellationToken, Definition, DefinitionLink, Position, ProviderResult, TextDocument } from 'vscode' 3 | import { Filter } from './filter' 4 | import cache from '../cache/cache' 5 | import { Interface } from '../ast/interface' 6 | 7 | export class RalphImplementationProvider extends Filter implements vscode.ImplementationProvider { 8 | provideImplementation( 9 | document: TextDocument, 10 | position: Position, 11 | token: CancellationToken 12 | ): ProviderResult { 13 | const word = this.word(document, position) 14 | if (word && word.name) { 15 | const base = cache.get(document.uri, word.name) 16 | if (base instanceof Interface) { 17 | return Array.from(base.implementer.values()).map((value) => value.location()) 18 | } 19 | } 20 | return undefined 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/provider/referenceProvider.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, Location, Position, ProviderResult, ReferenceContext, ReferenceProvider, TextDocument } from 'vscode' 2 | import { Filter } from './filter' 3 | import cache from '../cache/cache' 4 | 5 | export class RalphReferenceProvider extends Filter implements ReferenceProvider { 6 | /** 7 | * Provide a set of project-wide references for the given position and document. 8 | * 9 | * @param document The document in which the command was invoked. 10 | * @param position The position at which the command was invoked. 11 | * @param token A cancellation token. 12 | * 13 | * @return An array of locations or a thenable that resolves to such. The lack of a result can be 14 | * signaled by returning `undefined`, `null`, or an empty array. 15 | */ 16 | provideReferences( 17 | document: TextDocument, 18 | position: Position, 19 | context: ReferenceContext, 20 | token: CancellationToken 21 | ): ProviderResult { 22 | if (this.isSkip(document, position)) return undefined 23 | const word = document.getText(document.getWordRangeAtPosition(position, /[a-zA-Z_][0-9a-zA-Z_]*/i)) 24 | return cache.findAll(document.uri, { name: word }).map((value) => value.location!()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/provider/renameProvider.ts: -------------------------------------------------------------------------------- 1 | import { RenameProvider, WorkspaceEdit, TextDocument, Position, ProviderResult, Uri } from 'vscode' 2 | import * as vscode from 'vscode' 3 | import { Filter } from './filter' 4 | import cache from '../cache/cache' 5 | 6 | export class RalphRenameProvider extends Filter implements RenameProvider { 7 | /** 8 | * Provide an edit that describes changes that have to be made to one 9 | * or many resources to rename a symbol to a different name. 10 | * 11 | * @param document The document in which the command was invoked. 12 | * @param position The position at which the command was invoked. 13 | * @param newName The new name of the symbol. If the given name is not valid, the provider must return a rejected promise. 14 | * @param token A cancellation token. 15 | * @return A workspace edit or a thenable that resolves to such. The lack of a result can be 16 | * signaled by returning `undefined` or `null`. 17 | */ 18 | provideRenameEdits(document: TextDocument, position: Position, newName: string): ProviderResult { 19 | // const identifier = this.callChain(document, position) 20 | const edit = new WorkspaceEdit() 21 | // if (identifier) { 22 | // if (identifier.parent) { 23 | // identifier.parent.findAll?.({ name: identifier.name }).forEach((member) => 24 | // edit.replace(member.getUri?.(), member.getWordRange?.(), newName, { 25 | // label: member.name!, 26 | // needsConfirmation: true, 27 | // }) 28 | // ) 29 | // } 30 | // } 31 | const word = document.getText(document.getWordRangeAtPosition(position, /[a-zA-Z_][0-9a-zA-Z_]*/i)) 32 | cache.findAll(document.uri, { name: word }).forEach((member) => 33 | edit.replace(member.getUri?.(), member.getWordRange?.(), newName, { 34 | label: member.name!, 35 | needsConfirmation: true, 36 | }) 37 | ) 38 | return edit 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/provider/signatureHelpProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { 3 | CancellationToken, 4 | ParameterInformation, 5 | Position, 6 | ProviderResult, 7 | SignatureHelp, 8 | SignatureHelpContext, 9 | SignatureInformation, 10 | TextDocument, 11 | } from 'vscode' 12 | import { Filter } from './filter' 13 | import jsonData from './builtIn/ralph-built-in-functions.json' 14 | import { Func } from './builtIn/func' 15 | import { SemanticsKind } from '../ast/kinder' 16 | import { tryGetContractBuiltInFunction } from './builtIn/contractBuiltIn' 17 | 18 | export class RalphSignatureHelpProvider extends Filter implements vscode.SignatureHelpProvider { 19 | items: Array 20 | 21 | builtItems: Map 22 | 23 | constructor() { 24 | super() 25 | this.builtItems = new Map() 26 | this.items = Object.assign(new Array(), jsonData) 27 | this.items.forEach((item) => this.builtItems.set(`${item?.name}!`, item)) 28 | } 29 | 30 | splitParam(fun: Func) { 31 | fun.paramNames = fun.params.map((param) => { 32 | const values = param.trim().split(/\s+/) 33 | return values[1] 34 | }) 35 | } 36 | 37 | provideSignatureHelp( 38 | document: TextDocument, 39 | position: Position, 40 | token: CancellationToken, 41 | context: SignatureHelpContext 42 | ): ProviderResult { 43 | if (this.isSkip(document, position)) return undefined 44 | if (context.isRetrigger) { 45 | if (context.activeSignatureHelp) { 46 | if (context.triggerCharacter === ',') { 47 | context.activeSignatureHelp.activeParameter += 1 48 | } 49 | return context.activeSignatureHelp 50 | } 51 | } 52 | const range = document.getWordRangeAtPosition(position.with(position.line, position.character - 1), /[a-zA-Z_][0-9a-zA-Z_]*!?/i) 53 | const funcName = document.getText(range) 54 | const item = this.builtItems.get(funcName) 55 | if (item) { 56 | this.splitParam(item) 57 | return this.toSignatureHelp(item) 58 | } 59 | if (funcName.endsWith('!')) { 60 | return this.tryGetContractBuiltIn(document, range, funcName) 61 | } 62 | const callMethod = this.callChain(document, position.with(position.line, position.character - 1)) 63 | if (callMethod && callMethod.semanticsKind === SemanticsKind.Def) { 64 | const signature = new SignatureHelp() 65 | signature.signatures.push(callMethod.signatureInformation!()) 66 | return signature 67 | } 68 | return undefined 69 | } 70 | 71 | private toSignatureHelp(func: Func): vscode.SignatureHelp { 72 | const signature = new SignatureHelp() 73 | const info = new SignatureInformation(func.signature) 74 | info.parameters.push(...func.paramNames.map((value) => new ParameterInformation(value))) 75 | signature.signatures.push(info) 76 | return signature 77 | } 78 | 79 | private tryGetContractBuiltIn( 80 | document: vscode.TextDocument, 81 | range: vscode.Range | undefined, 82 | funcName: string 83 | ): SignatureHelp | undefined { 84 | if (range === undefined) { 85 | return undefined 86 | } 87 | const func = tryGetContractBuiltInFunction(document, range, funcName) 88 | if (func === undefined) { 89 | return undefined 90 | } 91 | return this.toSignatureHelp(func) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/provider/symbolProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { CancellationToken, ProviderResult, SymbolInformation } from 'vscode' 3 | import cache from '../cache/cache' 4 | 5 | export class SymbolProvider implements vscode.DocumentSymbolProvider, vscode.WorkspaceSymbolProvider { 6 | /** 7 | * Provide symbol information for the given document. 8 | * 9 | * @param document The document in which the command was invoked. 10 | * @param token A cancellation token. 11 | * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be 12 | * signaled by returning `undefined`, `null`, or an empty array. 13 | */ 14 | provideDocumentSymbols(document: vscode.TextDocument): vscode.ProviderResult { 15 | return cache 16 | .defs(document.uri) 17 | ?.filter((c) => c.getUri?.()?.path === document.uri.path) 18 | .map((item) => item.documentSymbol!()) 19 | } 20 | 21 | /** 22 | * Project-wide search for a symbol matching the given query string. 23 | * 24 | * The `query`-parameter should be interpreted in a *relaxed way* as the editor will apply its own highlighting 25 | * and scoring on the results. A good rule of thumb is to match case-insensitive and to simply check that the 26 | * characters of *query* appear in their order in a candidate symbol. Don't use prefix, substring, or similar 27 | * strict matching. 28 | * 29 | * To improve performance implementors can implement `resolveWorkspaceSymbol` and then provide symbols with partial 30 | * {@link SymbolInformation.location location}-objects, without a `range` defined. The editor will then call 31 | * `resolveWorkspaceSymbol` for selected symbols only, e.g. when opening a workspace symbol. 32 | * 33 | * @param query A query string, can be the empty string in which case all symbols should be returned. 34 | * @param token A cancellation token. 35 | * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be 36 | * signaled by returning `undefined`, `null`, or an empty array. 37 | */ 38 | provideWorkspaceSymbols(query: string, token: CancellationToken): ProviderResult { 39 | if (vscode.window.activeTextEditor) { 40 | return cache.findAll(vscode.window.activeTextEditor.document.uri, { name: query }).map((value) => value.symbolInformation!()) 41 | } 42 | return undefined 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/provider/typeDefinitionProvider.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, Definition, DefinitionLink, Position, ProviderResult, TextDocument, TypeDefinitionProvider } from 'vscode' 2 | import { Filter } from './filter' 3 | import cache from '../cache/cache' 4 | 5 | export class RalphTypeDefinitionProvider extends Filter implements TypeDefinitionProvider { 6 | provideTypeDefinition( 7 | document: TextDocument, 8 | position: Position, 9 | token: CancellationToken 10 | ): ProviderResult { 11 | const word = this.word(document, position) 12 | if (word && word.name) return cache.get(document.uri, word.name)?.location?.() 13 | return undefined 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/provider/typeHierarchyProvider.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, Position, ProviderResult, TextDocument, TypeHierarchyItem, TypeHierarchyProvider } from 'vscode' 2 | import { Filter } from './filter' 3 | import cache from '../cache/cache' 4 | import { Contract } from '../ast/contract' 5 | import { Interface } from '../ast/interface' 6 | 7 | export class RalphTypeHierarchyProvider extends Filter implements TypeHierarchyProvider { 8 | /** 9 | * Bootstraps type hierarchy by returning the item that is denoted by the given document 10 | * and position. This item will be used as entry into the type graph. Providers should 11 | * return `undefined` or `null` when there is no item at the given location. 12 | * 13 | * @param document The document in which the command was invoked. 14 | * @param position The position at which the command was invoked. 15 | * @param token A cancellation token. 16 | * @returns One or multiple type hierarchy items or a thenable that resolves to such. The lack of a result can be 17 | * signaled by returning `undefined`, `null`, or an empty array. 18 | */ 19 | prepareTypeHierarchy( 20 | document: TextDocument, 21 | position: Position, 22 | token: CancellationToken 23 | ): ProviderResult { 24 | const word = this.word(document, position) 25 | if (word && word.name) return cache.get(document.uri, word.name)?.typeHierarchyItem?.() 26 | return undefined 27 | } 28 | 29 | /** 30 | * Provide all supertypes for an item, e.g all types from which a type is derived/inherited. In graph terms this describes directed 31 | * and annotated edges inside the type graph, e.g the given item is the starting node and the result is the nodes 32 | * that can be reached. 33 | * 34 | * @param item The hierarchy item for which super types should be computed. 35 | * @param token A cancellation token. 36 | * @returns A set of direct supertypes or a thenable that resolves to such. The lack of a result can be 37 | * signaled by returning `undefined` or `null`. 38 | */ 39 | provideTypeHierarchySupertypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult { 40 | const instance = cache.get(item.uri, item.name) 41 | const parts: TypeHierarchyItem[] = [] 42 | if (instance instanceof Contract) { 43 | if (instance.interfaces) 44 | Array.from(instance.interfaces.values()).forEach((value) => { 45 | if (value) parts.push(value.typeHierarchyItem()) 46 | }) 47 | if (instance.parentClass) 48 | Array.from(instance.parentClass.values()).forEach((value) => { 49 | if (value) parts.push(value.typeHierarchyItem()) 50 | }) 51 | } 52 | return parts 53 | } 54 | 55 | /** 56 | * Provide all subtypes for an item, e.g all types which are derived/inherited from the given item. In 57 | * graph terms this describes directed and annotated edges inside the type graph, e.g the given item is the starting 58 | * node and the result is the nodes that can be reached. 59 | * 60 | * @param item The hierarchy item for which subtypes should be computed. 61 | * @param token A cancellation token. 62 | * @returns A set of direct subtypes or a thenable that resolves to such. The lack of a result can be 63 | * signaled by returning `undefined` or `null`. 64 | */ 65 | provideTypeHierarchySubtypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult { 66 | const instance = cache.get(item.uri, item.name) 67 | const parts = [] 68 | if (instance instanceof Contract) parts.push(...Array.from(instance.subclass.values()).map((value) => value.typeHierarchyItem())) 69 | if (instance instanceof Interface) parts.push(...Array.from(instance.implementer.values()).map((value) => value.typeHierarchyItem())) 70 | return parts 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/util/kind.ts: -------------------------------------------------------------------------------- 1 | import { SymbolKind } from 'vscode' 2 | import { RalphParser } from '../parser/RalphParser' 3 | 4 | const mapKinds = new Map([ 5 | [RalphParser.FN, SymbolKind.Function], 6 | [RalphParser.CONTRACT, SymbolKind.Class], 7 | [RalphParser.TXSCRIPT, SymbolKind.Class], 8 | [RalphParser.INTERFACE, SymbolKind.Interface], 9 | [RalphParser.ASSETSCRIPT, SymbolKind.Class], 10 | [RalphParser.ENUM, SymbolKind.Enum], 11 | [RalphParser.BOOL, SymbolKind.Boolean], 12 | [RalphParser.I256, SymbolKind.Number], 13 | [RalphParser.U256, SymbolKind.Number], 14 | [RalphParser.BYTE, SymbolKind.Array], 15 | [RalphParser.BYTEVEC, SymbolKind.Array], 16 | [RalphParser.ADDRESS, SymbolKind.Constant], 17 | [RalphParser.LET, SymbolKind.Variable], 18 | [RalphParser.CONST, SymbolKind.Constant], 19 | ]) 20 | 21 | export default function MapKinds() { 22 | return mapKinds 23 | } 24 | 25 | // File = 0, 26 | // Module = 1, 27 | // Namespace = 2, 28 | // Package = 3, 29 | // Class = 4, 30 | // Method = 5, 31 | // Property = 6, 32 | // Field = 7, 33 | // Constructor = 8, 34 | // Enum = 9, 35 | // Interface = 10, 36 | // Function = 11, 37 | // Variable = 12, 38 | // Constant = 13, 39 | // String = 14, 40 | // Number = 15, 41 | // Boolean = 16, 42 | // Array = 17, 43 | // Object = 18, 44 | // Key = 19, 45 | // Null = 20, 46 | // EnumMember = 21, 47 | // Struct = 22, 48 | // Event = 23, 49 | // Operator = 24, 50 | // TypeParameter = 25 51 | -------------------------------------------------------------------------------- /src/util/util.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as vscode from 'vscode' 3 | import * as request from 'request' 4 | import { Logger } from '../logger/logger' 5 | 6 | const requestProgress = require('request-progress') 7 | 8 | export function download(srcUrl: string, destPath: fs.PathLike, progress: (state: any) => void): Promise { 9 | return new Promise((resolve, reject) => { 10 | requestProgress(request.get(srcUrl)) 11 | .on( 12 | 'progress', 13 | (state: { 14 | percent: number 15 | speed: number 16 | size: { total: number; transferred: number } 17 | time: { elapsed: number; remaining: number } 18 | }) => progress(state) 19 | ) 20 | .on('complete', () => resolve()) 21 | .on('error', (err: any) => reject(err)) 22 | .pipe(fs.createWriteStream(destPath)) 23 | }) 24 | } 25 | 26 | export function getWorkspaceFolder(): string | undefined { 27 | if (vscode.workspace.workspaceFolders === undefined) { 28 | vscode.window.showErrorMessage('Working folder not found, open a folder and try again') 29 | return undefined 30 | } 31 | return vscode.workspace.workspaceFolders[0].uri.path 32 | } 33 | 34 | export function getConfigString(key: string, defaultValue: string): string { 35 | return vscode.workspace.getConfiguration().get(key) ?? defaultValue 36 | } 37 | 38 | export function getCompileCommand(): string { 39 | const cmd = getConfigString('ralph.compile.command', 'npx --yes @alephium/cli compile -n devnet') 40 | const version = getConfigString('ralph.compile.cliVersion', 'latest') 41 | return updateCompileCommandVersion(cmd, version) 42 | } 43 | 44 | export function updateCompileCommandVersion(initialCmd: string, newVersion: string): string { 45 | return initialCmd.replace(/@alephium\/cli[^\s]*/, `@alephium/cli@${newVersion}`) 46 | } 47 | 48 | export async function fsExists(path: fs.PathLike): Promise { 49 | try { 50 | await fs.promises.access(path) 51 | return true 52 | } catch { 53 | return false 54 | } 55 | } 56 | 57 | export function isOSWindows(): boolean { 58 | return process.platform === 'win32' 59 | } 60 | 61 | export function isOSUnixoid(): boolean { 62 | const { platform } = process 63 | return platform === 'linux' || platform === 'darwin' || platform === 'freebsd' || platform === 'openbsd' 64 | } 65 | 66 | export function correctBinname(binname: string): string { 67 | return binname + (process.platform === 'win32' ? '.exe' : '') 68 | } 69 | 70 | export function correctScriptName(binname: string): string { 71 | return binname + (process.platform === 'win32' ? '.bat' : '') 72 | } 73 | 74 | export interface Status { 75 | // Updates the message. 76 | update(msg: string): void 77 | 78 | dispose(): void 79 | } 80 | 81 | // Encapsulates a status bar item. 82 | export class StatusBarEntry implements Status { 83 | private barItem: vscode.StatusBarItem 84 | 85 | private prefix?: string 86 | 87 | private disposed = false 88 | 89 | constructor(context: vscode.ExtensionContext, prefix?: string) { 90 | this.prefix = prefix 91 | this.barItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left) 92 | context.subscriptions.push(this.barItem) 93 | } 94 | 95 | show(): void { 96 | this.barItem.show() 97 | } 98 | 99 | update(msg: string): void { 100 | this.barItem.text = `${this.prefix} ${msg}` 101 | } 102 | 103 | dispose(): void { 104 | if (!this.disposed) { 105 | this.disposed = true 106 | this.barItem.dispose() 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/visitors/ralphVisitor.ts: -------------------------------------------------------------------------------- 1 | import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor' 2 | import { Uri } from 'vscode' 3 | import { CodePointCharStream } from 'antlr4ts/CodePointCharStream' 4 | import { CommonTokenStream } from 'antlr4ts' 5 | import { ParserRuleContext } from 'antlr4ts/ParserRuleContext' 6 | import { 7 | AssetScriptContext, 8 | ContractContext, 9 | ContractExtendsContext, 10 | ExtendsContext, 11 | ImplementsContext, 12 | InterfaceContext, 13 | ParamListContext, 14 | RalphParser, 15 | TxScriptContext, 16 | TypeStructBodyContext, 17 | } from '../parser/RalphParser' 18 | import { RalphParserVisitor } from '../parser/RalphParserVisitor' 19 | 20 | import { Contract } from '../ast/contract' 21 | import { Method } from '../ast/method' 22 | import { Event } from '../ast/event' 23 | import { Base } from '../ast/base' 24 | import { Interface } from '../ast/interface' 25 | import { TxScript } from '../ast/txScript' 26 | import cache from '../cache/cache' 27 | import { Context } from '../ast/context' 28 | import { Enum } from '../ast/enum' 29 | import { AssetScript } from '../ast/assetScript' 30 | import { Identifier } from '../ast/identifier' 31 | import { SemanticNode } from '../ast/ast' 32 | import { RalphLexer } from '../parser/RalphLexer' 33 | 34 | type Result = Base | Identifier | undefined 35 | 36 | export class RalphVisitor extends AbstractParseTreeVisitor implements RalphParserVisitor { 37 | charStream: CodePointCharStream 38 | 39 | lexer: RalphLexer 40 | 41 | tokenStream: CommonTokenStream 42 | 43 | parser: RalphParser 44 | 45 | cache: Identifier[] 46 | 47 | uri: Uri 48 | 49 | constructor(uri: Uri, parser: RalphParser, lexer: RalphLexer, charStream: CodePointCharStream, tokenStream: CommonTokenStream) { 50 | super() 51 | this.charStream = charStream 52 | this.lexer = lexer 53 | this.tokenStream = tokenStream 54 | this.parser = parser 55 | this.uri = uri 56 | this.cache = [] 57 | } 58 | 59 | protected defaultResult(): Result { 60 | return new SemanticNode() 61 | } 62 | 63 | visitBody(ctx: TypeStructBodyContext, base: Base) { 64 | const context = new Context(base) 65 | base.append(...context.statementContexts(ctx.statement())) 66 | // method 67 | ctx.methodDecl().forEach((method) => base.add(Method.FromContext(method).setParent(base))) 68 | // event 69 | ctx.event().forEach((eventCtx) => base.add(Event.FromContext(eventCtx).setParent(base))) 70 | ctx.enum().forEach((value) => base.add(Enum.FromContext(value).setParent(base))) 71 | } 72 | 73 | visitParams(ctx: ParamListContext | undefined, base: Base) { 74 | if (ctx) { 75 | const context = new Context(base) 76 | base.append(...context.paramListContext(ctx)) 77 | } 78 | } 79 | 80 | visitStruct(ctx: Struct, base: Base): Result { 81 | base._parser = this.parser 82 | base.setRuleContext(ctx) 83 | base.uri = this.uri 84 | base._sourceIntervalDetail = ctx.sourceInterval.differenceNotProperlyContained(ctx.typeStructBody().sourceInterval) 85 | base.setParent(cache.root(this.uri)) 86 | base.setRange(ctx.start, ctx.stop) 87 | this.visitParams(ctx.paramList?.(), base) 88 | this.visitBody(ctx.typeStructBody?.(), base) 89 | this.cache.push(base) 90 | return base 91 | } 92 | 93 | visitContract(ctx: ContractContext): Result { 94 | const contract = new Contract(ctx.IDENTIFIER()) 95 | const extendList = ctx.extends() 96 | if (extendList) this.visitExtendsContext(extendList, contract) 97 | const implementer = ctx.implements() 98 | if (implementer) this.visitImplementsContext(implementer, contract) 99 | return this.visitStruct(ctx, contract) 100 | } 101 | 102 | visitExtendsContext(ctx: ExtendsContext, contract: Contract) { 103 | ctx.contractExtends().forEach((value) => this.visitContractExtendsContext(value, contract)) 104 | } 105 | 106 | visitContractExtendsContext(ctx: ContractExtendsContext, contract: Contract) { 107 | const block = new Context(contract) 108 | const identifier = ctx.IDENTIFIER() 109 | contract.append(block.typeNode(identifier)) 110 | const parent = cache.get(this.uri, identifier.symbol.text!) 111 | if (parent instanceof Contract) { 112 | contract.parentClass.set(parent.name!, parent) 113 | parent.subclass.set(contract.name!, contract) 114 | // todo fix 115 | Array.from(parent.members.values()).forEach((member) => contract.add(member)) 116 | } else { 117 | contract.parentClass.set(identifier.symbol.text!, undefined) 118 | } 119 | const list = ctx.expressionList() 120 | if (list) { 121 | contract.append(...block.expressionListContext(list)) 122 | } 123 | } 124 | 125 | visitImplementsContext(ctx: ImplementsContext, contract: Contract) { 126 | const identifier = ctx.IDENTIFIER() 127 | const block = new Context(contract) 128 | contract.append(block.typeNode(identifier)) 129 | const parent = cache.get(this.uri, identifier.symbol.text!) 130 | if (parent instanceof Interface) { 131 | contract.interfaces.set(parent.name!, parent) 132 | parent.implementer.set(contract.name!, contract) 133 | // todo fix 134 | Array.from(parent.members.values()).forEach((member) => contract.add(member)) 135 | } else { 136 | contract.interfaces.set(identifier.symbol.text!, undefined) 137 | } 138 | } 139 | 140 | visitInterface(ctx: InterfaceContext): Result { 141 | return this.visitStruct(ctx, new Interface(ctx.IDENTIFIER(0))) 142 | } 143 | 144 | visitTxScript(ctx: TxScriptContext): Result { 145 | return this.visitStruct(ctx, new TxScript(ctx.IDENTIFIER())) 146 | } 147 | 148 | visitAssetScript(ctx: AssetScriptContext): Result { 149 | return this.visitStruct(ctx, new AssetScript(ctx.IDENTIFIER())) 150 | } 151 | } 152 | 153 | declare class Struct extends ParserRuleContext { 154 | paramList?(): ParamListContext | undefined 155 | 156 | typeStructBody(): TypeStructBodyContext 157 | } 158 | -------------------------------------------------------------------------------- /syntaxes/ralph.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ralph", 3 | "fileTypes": ["ral"], 4 | "scopeName": "source.ral", 5 | "patterns": [ 6 | { "include": "#comment" }, 7 | { "include": "#imports" }, 8 | { "include": "#types" }, 9 | { "include": "#storages" }, 10 | { "include": "#functions" }, 11 | { "include": "#keywords" }, 12 | { "include": "#global" }, 13 | { "include": "#number" }, 14 | { "include": "#constant" }, 15 | { "include": "#variables" } 16 | ], 17 | "repository": { 18 | "comment": { 19 | "patterns": [ 20 | { 21 | "name": "comment.line.double-slash.ralph", 22 | "begin": "(\\/\\/)", 23 | "beginCaptures": { 24 | "1": { 25 | "name": "punctuation.definition.comment.ralph" 26 | } 27 | }, 28 | "end": "(?:\\n|$)" 29 | } 30 | ] 31 | }, 32 | "imports": { 33 | "patterns": [ 34 | { 35 | "begin": "\\b(import)\\s+\"", 36 | "beginCaptures": { 37 | "1": { 38 | "name": "keyword.import.ralph" 39 | } 40 | }, 41 | "end": "\"", 42 | "patterns": [ 43 | { 44 | "name": "entity.name.import.ralph", 45 | "match": "[^\"]*" 46 | } 47 | ] 48 | } 49 | ] 50 | }, 51 | "operators": { 52 | "comment": "Note that the order here is very important!", 53 | "patterns": [ 54 | { 55 | "match": "=", 56 | "name": "keyword.operator.assignment.ralph" 57 | }, 58 | { 59 | "match": "\\+\\+", 60 | "name": "keyword.operator.concat.ralph" 61 | }, 62 | { 63 | "match": "(\\+|\\-|\\*|/|%|⊕|`+`|⊖|`-`|⊗|`*`|<<|>>|&|\\^|\\|)", 64 | "name": "keyword.operator.arithmetic.ralph" 65 | }, 66 | { 67 | "match": "(==|!=|<|<=|>|>=)", 68 | "name": "keyword.operator.test.ralph" 69 | }, 70 | { 71 | "match": "(&&|\\|\\||!)", 72 | "name": "keyword.operator.logical.ralph" 73 | } 74 | ] 75 | }, 76 | "keywords": { 77 | "patterns": [ 78 | { 79 | "name": "keyword.control.ralph", 80 | "match": "\\b(if|else|while|for|return)\\b" 81 | }, 82 | { 83 | "match": "\\b(alph)\\b", 84 | "name": "keyword.alph.ralph" 85 | }, 86 | { 87 | "match": "\\b(let|mut|const)\\b", 88 | "name": "keyword.let.ralph" 89 | }, 90 | { 91 | "match": "\\b(fn|pub)\\b", 92 | "name": "keyword.function.ralph" 93 | }, 94 | { 95 | "match": "\\b(emit)\\b", 96 | "name": "keyword.emit.ralph" 97 | }, 98 | { 99 | "match": "(@using|@unused|@std)\\b", 100 | "name": "keyword.annotation.ralph" 101 | }, 102 | { "include": "#operators" } 103 | ] 104 | }, 105 | "storages": { 106 | "patterns": [ 107 | { 108 | "match": "\\b(Interface|TxScript|Contract|AssetScript|enum|event)\\b", 109 | "name": "storage.type.ralph" 110 | }, 111 | { 112 | "match": "\\b(extends|Abstract|implements)\\b", 113 | "name": "storage.modifier.ralph" 114 | } 115 | ] 116 | }, 117 | "types": { 118 | "patterns": [ 119 | { 120 | "comment": "numeric types", 121 | "match": "(? ([U256; 2]) { 5 | return addPrivate(array) 6 | } 7 | 8 | fn addPrivate(array: [U256; 2]) -> ([U256; 2]) { 9 | emit Add(array[0], array[1]) 10 | result = result + array[0] + array[1] 11 | return [result, sub.sub(array)] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/contracts/greeter/greeter.ral: -------------------------------------------------------------------------------- 1 | Contract Greeter(btcPrice: U256) implements GreeterInterface { 2 | pub fn greet() -> U256 { 3 | checkCaller!(true, 0) 4 | return btcPrice 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/contracts/greeter/greeter_interface.ral: -------------------------------------------------------------------------------- 1 | Interface GreeterInterface { 2 | pub fn greet() -> U256 3 | } 4 | -------------------------------------------------------------------------------- /tests/contracts/greeter_main.ral: -------------------------------------------------------------------------------- 1 | TxScript GreeterMain(greeterContractId: ByteVec) { 2 | let greeter0 = Greeter(greeterContractId) 3 | assert!(greeter0.greet() == 1, 0) 4 | 5 | let greeter1 = GreeterInterface(greeterContractId) 6 | assert!(greeter1.greet() == 1, 0) 7 | } 8 | -------------------------------------------------------------------------------- /tests/contracts/main.ral: -------------------------------------------------------------------------------- 1 | TxScript Main(addContractId: ByteVec) { 2 | let add = Add(addContractId) 3 | add.add([2, 1]) 4 | } 5 | -------------------------------------------------------------------------------- /tests/contracts/sub/sub.ral: -------------------------------------------------------------------------------- 1 | Contract Sub(mut result : U256) { 2 | event Sub(x: U256, y: U256) 3 | 4 | @using(checkExternalCaller = false) 5 | pub fn sub(array: [U256; 2]) -> U256 { 6 | emit Sub(array[0], array[1]) 7 | result = result + array[0] - array[1] 8 | return result 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/contracts/test/metadata.ral: -------------------------------------------------------------------------------- 1 | Contract MetaData() { 2 | fn baz() -> () { 3 | return 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/contracts/test/warnings.ral: -------------------------------------------------------------------------------- 1 | Contract Warnings(@unused a: U256, b: U256) { 2 | const C = 0 3 | 4 | pub fn foo(@unused x: U256, y: U256) -> () { 5 | return 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2021", 5 | "outDir": "dist", 6 | "lib": ["es2021", "DOM"], 7 | "sourceMap": true, 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "resolveJsonModule": true, 11 | "strict": true 12 | }, 13 | "exclude": ["node_modules", "tests"] 14 | } 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const path = require('path') 4 | 5 | /** @type {import('webpack').Configuration} */ 6 | const config = { 7 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 8 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 9 | 10 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 11 | output: { 12 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 13 | path: path.resolve(__dirname, 'dist'), 14 | filename: 'extension.js', 15 | libraryTarget: 'commonjs2', 16 | }, 17 | devtool: 'nosources-source-map', 18 | externals: { 19 | vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 20 | }, 21 | resolve: { 22 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 23 | extensions: ['.ts', '.js'], 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.ts$/, 29 | exclude: /node_modules/, 30 | use: [ 31 | { 32 | loader: 'ts-loader', 33 | }, 34 | ], 35 | }, 36 | ], 37 | }, 38 | } 39 | module.exports = config 40 | --------------------------------------------------------------------------------