├── .github └── workflows │ ├── build.yml │ └── go.yml ├── .gitignore ├── DaedalusBuiltins ├── G1 │ ├── builtins.src │ ├── camera.d │ ├── fight.d │ ├── gothic.d │ ├── menu.d │ ├── music.d │ ├── particlefx.d │ ├── sfx.d │ └── visualfx.d └── G2A │ ├── builtins.src │ ├── camera.d │ ├── fight.d │ ├── gothic.d │ ├── menu.d │ ├── music.d │ ├── particlefx.d │ ├── sfx.d │ └── visualfx.d ├── LICENSE ├── README.md ├── antlr_parser ├── Daedalus.g4 └── compile-parser-go.bat ├── build.darwin.bat ├── build.linux.bat ├── build.win32.bat ├── build.win64.bat ├── builtins.go ├── builtins_test.go ├── cmd └── DaedalusLanguageServer │ ├── dlsSlog.go │ ├── main.go │ └── pprof_server.go ├── daedalus ├── parser │ ├── daedalus_base_listener.go │ ├── daedalus_lexer.go │ ├── daedalus_listener.go │ └── daedalus_parser.go └── symbol │ └── symbol.go ├── go.mod ├── go.sum ├── javadoc ├── function.go ├── javadoc.go └── javadoc_test.go ├── langserver ├── base_lsphandler.go ├── bufferManager.go ├── bufferedDocument.go ├── bufferedDocument_test.go ├── combined_listener.go ├── command_setupworkspace.go ├── completionItems.go ├── concurrent.go ├── daedalus_identifier_listener.go ├── daedalus_parser.go ├── daedalus_parser_regular.go ├── daedalus_parser_test.go ├── daedalus_stateful_listener.go ├── daedalus_validating_listener.go ├── findpath.go ├── findpath_test.go ├── format_code.go ├── format_code_test.go ├── handle_codelens.go ├── handle_document_completion.go ├── handle_document_definition.go ├── handle_document_hover.go ├── handle_document_implementation.go ├── handle_document_inlayhints.go ├── handle_document_semantictokens.go ├── handle_document_semantictokens_test.go ├── handle_document_symbol.go ├── handle_document_typeDefinition.go ├── handle_references.go ├── handle_signature_help.go ├── handle_workspace_symbol.go ├── lsphandler.go ├── lspworkspace.go ├── parse_result.go ├── parseresultsmanager.go ├── parseresultsmanager_test.go ├── semantic_parse_result.go ├── signature_completion.go ├── syntax_error.go ├── syntax_error_listener.go ├── testdata │ ├── Gothic.src │ ├── LeGo │ │ ├── AI_Function.d │ │ ├── Anim8.d │ │ ├── Bars.d │ │ ├── BinaryMachines.d │ │ ├── Bloodsplats.d │ │ ├── Buffs.d │ │ ├── Buttons.d │ │ ├── ConsoleCommands.d │ │ ├── Cursor.d │ │ ├── Dialoggestures.d │ │ ├── Draw3D.d │ │ ├── EngineAdr_G1.d │ │ ├── EngineAdr_G2.d │ │ ├── EventHandler.d │ │ ├── Focusnames.d │ │ ├── FrameFunctions.d │ │ ├── Gamestate.d │ │ ├── Hashtable.d │ │ ├── Header_G2.src │ │ └── _Hashtable.d │ └── demo.d └── textdocumentsync.go ├── logger.go ├── protocol ├── gen │ ├── README.md │ ├── code.ts │ ├── src │ │ └── readme.md │ ├── tmp │ │ └── .gitkeep │ ├── tsconfig.json │ └── util.ts ├── methods_client.go ├── methods_server.go ├── semantic.go └── tsprotocol.go ├── rpcContext.go ├── rpcMux.go └── rpcMux_test.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build and test 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - '**' 8 | pull_request: 9 | branches: 10 | - '**' 11 | 12 | defaults: 13 | run: 14 | shell: bash 15 | 16 | jobs: 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | matrix: 23 | os: [linux, windows, darwin] 24 | arch: [amd64, arm64] 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | 29 | - name: Set up Go 30 | uses: actions/setup-go@v3 31 | with: 32 | go-version: '1.24.x' 33 | 34 | - name: build all binaries 35 | run: go build -v ./... 36 | 37 | - name: Test 38 | run: go test -v ./... 39 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | env: 14 | BINARY_NAME: DaedalusLanguageServer 15 | 16 | jobs: 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | matrix: 23 | os: [linux, windows, darwin] 24 | arch: [amd64, arm64] 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | 29 | - name: Set up Go 30 | uses: actions/setup-go@v3 31 | with: 32 | go-version: '1.24.x' 33 | 34 | - name: try building 35 | run: go build -v ./... 36 | 37 | - name: Test 38 | run: go test -v ./... 39 | 40 | - name: Setup final build env 41 | run: | 42 | export tagName=${GITHUB_REF/refs\/tags\//} 43 | echo "Tag: ${tagName}" 44 | echo "RELEASE_VERSION=${tagName}" >> $GITHUB_ENV 45 | echo "GOARCH=${{ matrix.arch }}" >> $GITHUB_ENV 46 | echo "GOOS=${{ matrix.os }}" >> $GITHUB_ENV 47 | 48 | - name: Adjust naming for Windows 49 | if: matrix.os == 'windows' 50 | run: | 51 | export BINARY_NAME=$BINARY_NAME.exe 52 | echo "BINARY_NAME=$BINARY_NAME" >> $GITHUB_ENV 53 | 54 | - name: Build 55 | run: go build -v -o $BINARY_NAME -ldflags '-extldflags "-static" -s -w' ./cmd/DaedalusLanguageServer/ 56 | 57 | - name: Calculate distribution hash 58 | working-directory: ${{env.GITHUB_WORKSPACE}} 59 | run: | 60 | sha256sum "$BINARY_NAME" > "$BINARY_NAME.sha256" 61 | cat "$BINARY_NAME.sha256" 62 | 63 | - name: Upload artifacts 64 | uses: actions/upload-artifact@v4 65 | with: 66 | name: ${{ matrix.os }}-${{matrix.arch}} 67 | path: | 68 | ${{ env.BINARY_NAME }} 69 | ${{ env.BINARY_NAME }}.sha256 70 | 71 | - name: Create Zip 72 | run: zip -D ${{ matrix.os }}_${{ matrix.arch }}.zip "$BINARY_NAME" "${{ env.BINARY_NAME }}.sha256" 73 | 74 | - name: Create github release 75 | run: gh release create $RELEASE_VERSION --generate-notes || exit 0 76 | if: startsWith(github.ref, 'refs/tags/') 77 | env: 78 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 79 | 80 | - name: Upload zip to release 81 | run: | 82 | gh release upload $RELEASE_VERSION "${{ matrix.os }}_${{ matrix.arch }}.zip" 83 | gh release edit $RELEASE_VERSION --draft=false --prerelease=false 84 | exit 0 85 | if: startsWith(github.ref, 'refs/tags/') 86 | env: 87 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | *.exe 3 | 4 | 5 | *.tokens 6 | *.interp 7 | /antlr_parser/*.jar 8 | antlr_parser/.antlr 9 | 10 | protocol/gen/node_modules 11 | protocol/gen/*.js 12 | protocol/gen/*.js.* 13 | protocol/gen/package*.json 14 | protocol/gen/tmp/* 15 | !protocol/gen/tmp/.gitkeep -------------------------------------------------------------------------------- /DaedalusBuiltins/G1/builtins.src: -------------------------------------------------------------------------------- 1 | *.d -------------------------------------------------------------------------------- /DaedalusBuiltins/G1/camera.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; 2 | -------------------------------------------------------------------------------- /DaedalusBuiltins/G1/fight.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; 2 | -------------------------------------------------------------------------------- /DaedalusBuiltins/G1/menu.d: -------------------------------------------------------------------------------- 1 | // Vanilla Menu externals with docu comments for DLS implementation 2 | // 3 | // Source: https://github.com/muczc1wek/daedalus-externals 4 | 5 | var instance instance_help; 6 | 7 | /// Updates `C_MENU_ITEM` choice box with the current setting 8 | /// This specific boxes are hardcoded in the function itself: 9 | /// `MENUITEM_AUDIO_PROVIDER_CHOICE`, 10 | /// `MENUITEM_VID_DEVICE_CHOICE`, 11 | /// `MENUITEM_VID_RESOLUTION_CHOICE` 12 | /// 13 | /// @param choicebox name of the one of the hardcoded choice boxes 14 | func void Update_ChoiceBox(var string choicebox) {}; 15 | 16 | /// Applies the options for the performance, analyzes the system and changes the settings if necessary 17 | func void Apply_Options_Performance() {}; 18 | 19 | /// Applies the options for the video, changes the resolution and graphics device if necessary 20 | func void Apply_Options_Video() {}; 21 | 22 | /// [deprecated] Meant to apply the options for the audio, does nothing apart playing an apply sound 23 | func void Apply_Options_Audio() {}; 24 | 25 | /// [deprecated] Meant to apply the options for the game, does nothing apart playing an apply sound 26 | func void Apply_Options_Game() {}; 27 | 28 | /// [deprecated] Meant to apply the options for the controls, does nothing apart playing an apply sound 29 | func void Apply_Options_Controls() {}; 30 | 31 | /// Plays a video 32 | /// 33 | /// @param filename name of the video file 34 | /// @return TRUE if the video was played successfully, FALSE otherwise 35 | func int PlayVideo(var string filename) {}; 36 | -------------------------------------------------------------------------------- /DaedalusBuiltins/G1/music.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; 2 | -------------------------------------------------------------------------------- /DaedalusBuiltins/G1/particlefx.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; -------------------------------------------------------------------------------- /DaedalusBuiltins/G1/sfx.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; -------------------------------------------------------------------------------- /DaedalusBuiltins/G1/visualfx.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; -------------------------------------------------------------------------------- /DaedalusBuiltins/G2A/builtins.src: -------------------------------------------------------------------------------- 1 | *.d -------------------------------------------------------------------------------- /DaedalusBuiltins/G2A/camera.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; 2 | -------------------------------------------------------------------------------- /DaedalusBuiltins/G2A/fight.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; 2 | -------------------------------------------------------------------------------- /DaedalusBuiltins/G2A/menu.d: -------------------------------------------------------------------------------- 1 | // Vanilla Menu externals with docu comments for DLS implementation 2 | // 3 | // Source: https://github.com/muczc1wek/daedalus-externals 4 | 5 | var instance instance_help; 6 | 7 | /// Updates `C_MENU_ITEM` choice box with the current setting 8 | /// This specific boxes are hardcoded in the function itself: 9 | /// `MENUITEM_AUDIO_PROVIDER_CHOICE`, 10 | /// `MENUITEM_VID_DEVICE_CHOICE`, 11 | /// `MENUITEM_VID_RESOLUTION_CHOICE` 12 | /// 13 | /// @param choicebox name of the one of the hardcoded choice boxes 14 | func void Update_ChoiceBox(var string choicebox) {}; 15 | 16 | /// Applies the options for the performance, analyzes the system and changes the settings if necessary 17 | func void Apply_Options_Performance() {}; 18 | 19 | /// Applies the options for the video, changes the resolution and graphics device if necessary 20 | func void Apply_Options_Video() {}; 21 | 22 | /// [deprecated] Meant to apply the options for the audio, does nothing apart playing an apply sound 23 | func void Apply_Options_Audio() {}; 24 | 25 | /// [deprecated] Meant to apply the options for the game, does nothing apart playing an apply sound 26 | func void Apply_Options_Game() {}; 27 | 28 | /// [deprecated] Meant to apply the options for the controls, does nothing apart playing an apply sound 29 | func void Apply_Options_Controls() {}; 30 | 31 | /// Plays a video 32 | /// 33 | /// @param filename name of the video file 34 | /// @return TRUE if the video was played successfully, FALSE otherwise 35 | func int PlayVideo(var string filename) {}; 36 | -------------------------------------------------------------------------------- /DaedalusBuiltins/G2A/music.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; 2 | -------------------------------------------------------------------------------- /DaedalusBuiltins/G2A/particlefx.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; -------------------------------------------------------------------------------- /DaedalusBuiltins/G2A/sfx.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; -------------------------------------------------------------------------------- /DaedalusBuiltins/G2A/visualfx.d: -------------------------------------------------------------------------------- 1 | var instance instance_help; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kirides 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/kirides/DaedalusLanguageServer/actions/workflows/build.yml/badge.svg)](https://github.com/kirides/DaedalusLanguageServer/actions/workflows/build.yml) 2 | 3 | ## Usage 4 | 5 | see: https://github.com/kirides/vscode-daedalus 6 | 7 | ## Custom externals 8 | 9 | The server looks for a `_externals\` directory located in the workspace root. 10 | When there is a `_externals\externals.src` it will try to parse it and all referenced files right after parsing the built-in externals and before parsing user scripts. 11 | - If there is no `_externals\externals.src` we look for a `_externals\externals.d` and try to parse that. 12 | 13 | This externals should be provided from Union plugins such as zParserExtender. 14 | 15 | ## Aknowledgements 16 | 17 | ### Gothic Classic - Nintendo Switch 18 | 19 | https://github.com/kirides/DaedalusLanguageServer/assets/13602143/91162090-0b7f-4bbd-a461-4e6f9b1f6078 20 | 21 | 22 | ## neovim configuration 23 | 24 | Minimum configuration needed to run the language server 25 | 26 | 27 | _somewhere in init.lua or any lua script_ 28 | ```lua 29 | vim.api.nvim_create_autocmd("FileType", { 30 | pattern = "d", 31 | callback = function(ev) 32 | local root_dir = vim.fs.dirname( 33 | vim.fs.find({ 'Gothic.src', 'Camera.src', 'Menu.src', 'Music.src', 'ParticleFX.src', 'SFX.src', 'VisualFX.src' }, { upward = true })[1] 34 | ) 35 | 36 | local client = vim.lsp.start({ 37 | name = 'DLS', 38 | cmd = {'C:/.../DaedalusLanguageServer.exe'}, 39 | root_dir = root_dir, 40 | -- configure language server specific options 41 | settings = { 42 | daedalusLanguageServer = { 43 | loglevel = 'debug', 44 | inlayHints = { constants = true }, 45 | numParserThreads = 16, 46 | fileEncoding = 'Windows-1252', 47 | srcFileEncoding ='Windows-1252', 48 | }, 49 | }, 50 | }) 51 | 52 | vim.lsp.buf_attach_client(0, client) 53 | end 54 | }) 55 | 56 | 57 | vim.cmd([[highlight! link LspSignatureActiveParameter Search]]) 58 | ``` 59 | -------------------------------------------------------------------------------- /antlr_parser/Daedalus.g4: -------------------------------------------------------------------------------- 1 | grammar Daedalus; 2 | 3 | @lexer::members { 4 | const ( 5 | // Channel that contains comments 6 | COMMENTS int = 2; 7 | ) 8 | } 9 | 10 | // lexer 11 | IntegerLiteral: Digit+; 12 | FloatLiteral: PointFloat | ExponentFloat; 13 | StringLiteral: '"' (~["\r\n])* '"'; 14 | 15 | Const: C O N S T; 16 | Var: V A R; 17 | If: I F; 18 | Int: I N T; 19 | Else: E L S E; 20 | Func: F U N C; 21 | StringKeyword: S T R I N G; 22 | Class: C L A S S; 23 | Void: V O I D; 24 | Return: R E T U R N; 25 | Float: F L O A T; 26 | Prototype: P R O T O T Y P E; 27 | Instance: I N S T A N C E; 28 | Namespace: N A M E S P A C E; 29 | Null: N U L L; 30 | Meta: M E T A; 31 | 32 | LeftParen: '('; 33 | RightParen: ')'; 34 | 35 | LeftBracket: '['; 36 | RightBracket: ']'; 37 | 38 | LeftBrace: '{'; 39 | RightBrace: '}'; 40 | 41 | BitAnd: '&'; 42 | And: '&&'; 43 | BitOr: '|'; 44 | Or: '||'; 45 | Plus: '+'; 46 | Minus: '-'; 47 | Div: '/'; 48 | Star: '*'; 49 | Tilde: '~'; 50 | Not: '!'; 51 | Assign: '='; 52 | Less: '<'; 53 | Greater: '>'; 54 | 55 | PlusAssign: '+='; 56 | MinusAssign: '-='; 57 | StarAssign: '*='; 58 | DivAssign: '/='; 59 | AndAssign: '&='; 60 | OrAssign: '|='; 61 | 62 | Dot: '.'; 63 | Semi: ';'; 64 | 65 | Identifier: (NonDigit | Digit) IdContinue*; 66 | 67 | Whitespace: [ \t]+ -> skip; 68 | Newline: ('\r' '\n'? | '\n') -> skip; 69 | BlockComment: '/*' .*? '*/' -> skip; 70 | LineComment: '//' ~[\r\n]* -> channel(2); 71 | 72 | // fragments 73 | fragment NonDigit: GermanCharacter | [a-zA-Z_]; 74 | fragment IdContinue: NonDigit | IdSpecial | Digit; 75 | fragment IdSpecial: [@^]; 76 | fragment GermanCharacter: 77 | // ß Ä ä Ö ö Ü ü 78 | [\u00DF\u00C4\u00E4\u00D6\u00F6\u00DC\u00FC]; 79 | fragment Digit: [0-9]; 80 | fragment PointFloat: Digit* '.' Digit+ | Digit+ '.'; 81 | fragment ExponentFloat: (Digit+ | PointFloat) Exponent; 82 | fragment Exponent: [eE] [+-]? Digit+; 83 | 84 | fragment A: [Aa]; 85 | fragment B: [Bb]; 86 | fragment C: [Cc]; 87 | fragment D: [Dd]; 88 | fragment E: [Ee]; 89 | fragment F: [Ff]; 90 | fragment G: [Gg]; 91 | fragment H: [Hh]; 92 | fragment I: [Ii]; 93 | fragment J: [Jj]; 94 | fragment K: [Kk]; 95 | fragment L: [Ll]; 96 | fragment M: [Mm]; 97 | fragment N: [Nn]; 98 | fragment O: [Oo]; 99 | fragment P: [Pp]; 100 | fragment Q: [Qq]; 101 | fragment R: [Rr]; 102 | fragment S: [Ss]; 103 | fragment T: [Tt]; 104 | fragment U: [Uu]; 105 | fragment V: [Vv]; 106 | fragment W: [Ww]; 107 | fragment X: [Xx]; 108 | fragment Y: [Yy]; 109 | fragment Z: [Zz]; 110 | 111 | //parser 112 | daedalusFile: mainBlock? EOF; 113 | blockDef: 114 | ( 115 | functionDef 116 | | classDef 117 | | prototypeDef 118 | | instanceDef 119 | | namespaceDef 120 | ) Semi; 121 | inlineDef: (constDef | varDecl | instanceDecl) Semi; 122 | 123 | functionDef: 124 | Func typeReference nameNode parameterList statementBlock; 125 | constDef: 126 | Const typeReference (constValueDef | constArrayDef) ( 127 | ',' (constValueDef | constArrayDef) 128 | )*; 129 | classDef: Class nameNode LeftBrace (varDecl Semi)*? RightBrace; 130 | prototypeDef: 131 | Prototype nameNode LeftParen parentReference RightParen statementBlock; 132 | instanceDef: 133 | Instance nameNode LeftParen parentReference RightParen statementBlock; 134 | instanceDecl: 135 | Instance nameNode (',' nameNode)*? LeftParen parentReference RightParen; 136 | namespaceDef: 137 | Namespace nameNode LeftBrace contentBlock*? RightBrace; 138 | mainBlock: zParserExtenderMetaBlock? contentBlock+; 139 | contentBlock: (blockDef | inlineDef); 140 | varDecl: 141 | Var typeReference (varValueDecl | varArrayDecl) ( 142 | ',' (varDecl | varValueDecl | varArrayDecl) 143 | )*; 144 | 145 | metaValue: .+?; 146 | zParserExtenderMeta: nameNode Assign metaValue Semi; 147 | zParserExtenderMetaBlock: 148 | Meta LeftBrace zParserExtenderMeta*? RightBrace Semi; 149 | constArrayDef: 150 | nameNode LeftBracket arraySize RightBracket constArrayAssignment; 151 | constArrayAssignment: 152 | Assign LeftBrace (expressionBlock (',' expressionBlock)*?) RightBrace; 153 | 154 | constValueDef: nameNode constValueAssignment; 155 | constValueAssignment: Assign expressionBlock; 156 | 157 | varArrayDecl: nameNode LeftBracket arraySize RightBracket; 158 | varValueDecl: nameNode; 159 | 160 | parameterList: 161 | LeftParen (parameterDecl (',' parameterDecl)*?)? RightParen; 162 | parameterDecl: 163 | Var typeReference nameNode ( 164 | LeftBracket arraySize RightBracket 165 | )?; 166 | statementBlock: 167 | LeftBrace ((statement Semi) | ( ifBlockStatement Semi?))*? RightBrace; 168 | statement: 169 | assignment 170 | | returnStatement 171 | | constDef 172 | | varDecl 173 | | expression; 174 | funcCall: 175 | nameNode LeftParen ( 176 | funcArgExpression (',' funcArgExpression)*? 177 | )? RightParen; 178 | assignment: reference assignmentOperator expressionBlock; 179 | ifCondition: expressionBlock; 180 | elseBlock: Else statementBlock; 181 | elseIfBlock: Else If ifCondition statementBlock; 182 | ifBlock: If ifCondition statementBlock; 183 | ifBlockStatement: ifBlock ( elseIfBlock)*? ( elseBlock)?; 184 | returnStatement: Return ( expressionBlock)?; 185 | 186 | funcArgExpression: 187 | expressionBlock; // we use that to detect func call args 188 | expressionBlock: 189 | expression; // we use that expression to force parser threat expression as a block 190 | 191 | expression: 192 | LeftParen expression RightParen # bracketExpression 193 | | unaryOperator expression # unaryOperation 194 | | expression multOperator expression # multExpression 195 | | expression addOperator expression # addExpression 196 | | expression bitMoveOperator expression # bitMoveExpression 197 | | expression compOperator expression # compExpression 198 | | expression eqOperator expression # eqExpression 199 | | expression binAndOperator expression # binAndExpression 200 | | expression binOrOperator expression # binOrExpression 201 | | expression logAndOperator expression # logAndExpression 202 | | expression logOrOperator expression # logOrExpression 203 | | value # valExpression; 204 | 205 | arrayIndex: IntegerLiteral | referenceAtom; 206 | arraySize: IntegerLiteral | referenceAtom; 207 | 208 | value: 209 | IntegerLiteral # integerLiteralValue 210 | | FloatLiteral # floatLiteralValue 211 | | StringLiteral # stringLiteralValue 212 | | Null # nullLiteralValue 213 | | funcCall # funcCallValue 214 | | reference # referenceValue; 215 | 216 | referenceAtom: nameNode ( LeftBracket arrayIndex RightBracket)?; 217 | reference: referenceAtom ( Dot referenceAtom)?; 218 | 219 | typeReference: ( 220 | Identifier 221 | | Void 222 | | Int 223 | | Float 224 | | StringKeyword 225 | | Func 226 | | Instance 227 | ); 228 | anyIdentifier: ( 229 | Void 230 | | Var 231 | | Int 232 | | Float 233 | | StringKeyword 234 | | Func 235 | | Instance 236 | | Class 237 | | Prototype 238 | | Null 239 | | Meta 240 | | Namespace 241 | | Identifier 242 | ); 243 | 244 | nameNode: anyIdentifier; 245 | 246 | parentReference: Identifier; 247 | 248 | assignmentOperator: 249 | Assign 250 | | StarAssign 251 | | DivAssign 252 | | PlusAssign 253 | | MinusAssign 254 | | AndAssign 255 | | OrAssign; 256 | 257 | unaryOperator: Plus | Tilde | Minus | Not; 258 | 259 | addOperator: '+' | '-'; 260 | bitMoveOperator: '<<' | '>>'; 261 | compOperator: '<' | '>' | '<=' | '>='; 262 | eqOperator: '==' | '!='; 263 | multOperator: '*' | '/' | '%'; 264 | binAndOperator: '&'; 265 | binOrOperator: '|'; 266 | logAndOperator: '&&'; 267 | logOrOperator: '||'; -------------------------------------------------------------------------------- /antlr_parser/compile-parser-go.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PUSHD %~dp0 3 | REM Change this to antlr command line: 4 | SET ANTLR=java -jar "antlr-4.13.2-complete.jar" 5 | %ANTLR% -o ../daedalus/parser -Dlanguage=Go -no-visitor Daedalus.g4 6 | REM %ANTLR% -visitor -o ../langserver/parser -Dlanguage=Go Daedalus.g4 7 | popd 8 | PAUSE 9 | -------------------------------------------------------------------------------- /build.darwin.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | set "GOOS=darwin" 4 | 5 | set "GOARCH=arm64" 6 | echo Building executable for %GOARCH% 7 | go build -o DaedalusLanguageServer_darwin.arm64 -ldflags "-extldflags ^"-static^" -s -w" ./cmd/DaedalusLanguageServer/ 8 | 9 | set "GOARCH=amd64" 10 | echo Building executable for %GOARCH% 11 | go build -o DaedalusLanguageServer_darwin.x64 -ldflags "-extldflags ^"-static^" -s -w" ./cmd/DaedalusLanguageServer/ 12 | -------------------------------------------------------------------------------- /build.linux.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | set "GOARCH=amd64" 3 | set "GOOS=linux" 4 | 5 | 6 | set "GOARCH=amd64" 7 | echo Building executable for %GOARCH% 8 | go build -o DaedalusLanguageServer.x64 -ldflags "-extldflags ^"-static^" -s -w" ./cmd/DaedalusLanguageServer/ 9 | 10 | set "GOARCH=386" 11 | echo Building executable for %GOARCH% 12 | go build -o DaedalusLanguageServer.x86 -ldflags "-extldflags ^"-static^" -s -w" ./cmd/DaedalusLanguageServer/ 13 | -------------------------------------------------------------------------------- /build.win32.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | set "GOARCH=386" 3 | set "UPX_PARAMS=-9 --lzma --strip-relocs=0" 4 | 5 | echo Building executable for %GOARCH% 6 | go build -o DaedalusLanguageServer.exe -ldflags "-s -w" ./cmd/DaedalusLanguageServer/ 7 | 8 | EXIT /B 9 | 10 | DEL DaedalusLanguageServer_upx.exe 11 | echo Compressing binary using UXP 12 | upx.exe -o"DaedalusLanguageServer_upx.exe" %UPX_PARAMS% DaedalusLanguageServer.exe 13 | -------------------------------------------------------------------------------- /build.win64.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | set "GOARCH=amd64" 3 | set "UPX_PARAMS=-9 --lzma --strip-relocs=0" 4 | 5 | echo Building executable for %GOARCH% 6 | REM See https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels 7 | set "GOAMD64=v2" 8 | go build -o DaedalusLanguageServer.x64.exe ./cmd/DaedalusLanguageServer/ 9 | 10 | EXIT /B 11 | 12 | DEL DaedalusLanguageServer_upx.exe 13 | echo Compressing binary using UXP 14 | upx.exe -o"DaedalusLanguageServer_upx.x64.exe" %UPX_PARAMS% DaedalusLanguageServer.x64.exe 15 | -------------------------------------------------------------------------------- /builtins.go: -------------------------------------------------------------------------------- 1 | package DaedalusLanguageServer 2 | 3 | import "embed" 4 | 5 | //go:embed DaedalusBuiltins/* 6 | var BuiltinsFS embed.FS 7 | 8 | const DaedalusBuiltinsPath = "DaedalusBuiltins" 9 | -------------------------------------------------------------------------------- /builtins_test.go: -------------------------------------------------------------------------------- 1 | package DaedalusLanguageServer 2 | 3 | import ( 4 | "io/fs" 5 | "path" 6 | "testing" 7 | ) 8 | 9 | func TestBuiltinsCanBeFound(t *testing.T) { 10 | fs.WalkDir(BuiltinsFS, path.Join(DaedalusBuiltinsPath, "G2A"), func(path string, d fs.DirEntry, err error) error { 11 | if err != nil { 12 | return nil 13 | } 14 | if d.IsDir() { 15 | return nil 16 | } 17 | content, err := fs.ReadFile(BuiltinsFS, path) 18 | if err != nil { 19 | t.Fatalf("File not found %q", path) 20 | return nil 21 | } 22 | _ = content 23 | return nil 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/DaedalusLanguageServer/dlsSlog.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "log/slog" 7 | 8 | dls "github.com/kirides/DaedalusLanguageServer" 9 | ) 10 | 11 | type dlsSlog struct { 12 | debug *log.Logger 13 | info *log.Logger 14 | warn *log.Logger 15 | error *log.Logger 16 | } 17 | 18 | func newDlsSlog(handler slog.Handler) *dlsSlog { 19 | return &dlsSlog{ 20 | debug: slog.NewLogLogger(handler, slog.LevelDebug), 21 | info: slog.NewLogLogger(handler, slog.LevelInfo), 22 | warn: slog.NewLogLogger(handler, slog.LevelWarn), 23 | error: slog.NewLogLogger(handler, slog.LevelError), 24 | } 25 | } 26 | 27 | func (d *dlsSlog) Debugf(template string, args ...interface{}) { d.debug.Printf(template, args...) } 28 | func (d *dlsSlog) Infof(template string, args ...interface{}) { d.info.Printf(template, args...) } 29 | func (d *dlsSlog) Warnf(template string, args ...interface{}) { d.warn.Printf(template, args...) } 30 | func (d *dlsSlog) Errorf(template string, args ...interface{}) { d.error.Printf(template, args...) } 31 | 32 | var _ dls.Logger = (*dlsSlog)(nil) 33 | -------------------------------------------------------------------------------- /cmd/DaedalusLanguageServer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "os" 10 | "os/signal" 11 | "runtime/debug" 12 | "strings" 13 | "sync" 14 | "syscall" 15 | 16 | "log/slog" 17 | 18 | "github.com/goccy/go-json" 19 | 20 | "github.com/kirides/DaedalusLanguageServer/langserver" 21 | 22 | _ "net/http/pprof" 23 | 24 | "go.lsp.dev/jsonrpc2" 25 | ) 26 | 27 | func BV(s []debug.BuildSetting, key string) string { 28 | for _, v := range s { 29 | if v.Key == key { 30 | if key == "vcs.revision" && len(v.Value) > 7 { 31 | return v.Value[:7] 32 | } 33 | return v.Value 34 | } 35 | } 36 | return "" 37 | } 38 | 39 | func logBuildInfo(log *slog.Logger) { 40 | bi, ok := debug.ReadBuildInfo() 41 | if !ok { 42 | return 43 | } 44 | 45 | BV := func(key string) string { 46 | for _, v := range bi.Settings { 47 | if v.Key == key { 48 | if key == "vcs.revision" && len(v.Value) > 7 { 49 | return v.Value[:7] 50 | } 51 | return v.Value 52 | } 53 | } 54 | return "" 55 | } 56 | 57 | log.Info("Running", "revision", BV("vcs.revision"), "go_version", bi.GoVersion, "built_at", BV("vcs.time")) 58 | } 59 | 60 | func main() { 61 | pprofPort := flag.Int("pprof", -1, "enables pprof on the specified port") 62 | logLevel := &slog.LevelVar{} 63 | flag.TextVar(logLevel, "loglevel", logLevel, "debug/info/warn/error") 64 | flag.Parse() 65 | 66 | handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ 67 | Level: logLevel, 68 | }) 69 | logger := slog.New(handler) 70 | 71 | logBuildInfo(logger) 72 | 73 | pprofServer := &pprofServer{} 74 | defer pprofServer.Stop() 75 | 76 | if *pprofPort > 0 { 77 | pprofServer.ChangeAddr(fmt.Sprintf("127.0.0.1:%d", *pprofPort)) 78 | } 79 | ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill, syscall.SIGTERM) 80 | defer stop() 81 | 82 | conn := connectLanguageServer(&rwc{os.Stdin, os.Stdout}) 83 | lspHandler := langserver.NewLspHandler(conn, newDlsSlog(logger.Handler())) 84 | 85 | lspHandler.OnConfigChanged(func(config langserver.LspConfig) { 86 | level := strings.ToLower(config.LogLevel) 87 | if len(level) < 1 { 88 | return 89 | } 90 | switch level[0] { 91 | case 'd': 92 | logLevel.Set(slog.LevelDebug) 93 | case 'w': 94 | logLevel.Set(slog.LevelWarn) 95 | case 'e': 96 | logLevel.Set(slog.LevelError) 97 | case 'i': 98 | logLevel.Set(slog.LevelInfo) 99 | default: 100 | logLevel.Set(slog.LevelInfo) 101 | } 102 | }) 103 | 104 | lspHandler.OnConfigChanged(func(config langserver.LspConfig) { 105 | if *pprofPort <= 0 { 106 | // only when not set by args 107 | logger.Info("Updating pprof address", "addr", config.PprofAddr) 108 | pprofServer.ChangeAddr(config.PprofAddr) 109 | } 110 | }) 111 | 112 | runningRequests := &sync.Map{} 113 | cancelRequest := func(id string) { 114 | v, ok := runningRequests.LoadAndDelete(id) 115 | if !ok { 116 | return 117 | } 118 | v.(context.CancelFunc)() 119 | } 120 | addRequest := func(ctx context.Context, id string) context.Context { 121 | ctx, cancel := context.WithCancel(ctx) 122 | runningRequests.Store(id, cancel) 123 | return ctx 124 | } 125 | 126 | conn.Go(ctx, func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { 127 | go func() { 128 | if req.Method() == "$/cancelRequest" { 129 | var idPayload struct { 130 | Request struct { 131 | ID string `json:"id"` 132 | } `json:"request"` 133 | } 134 | if err := json.Unmarshal(req.Params(), &idPayload); err != nil { 135 | logger.Warn("invalid request, missing \"id\"") 136 | return 137 | } 138 | logger.Debug("cancelling request", "request_id", idPayload.Request.ID) 139 | cancelRequest(idPayload.Request.ID) 140 | return 141 | } 142 | cancelId := "" 143 | if r, ok := req.(*jsonrpc2.Call); ok { 144 | id := r.ID() 145 | idVal, err := (&id).MarshalJSON() 146 | if err != nil { 147 | logger.Warn("invalid call, missing \"id\".", "err", err) 148 | return 149 | } 150 | cancelId = strings.Trim(string(idVal), "\"") 151 | 152 | ctx = addRequest(ctx, cancelId) 153 | defer cancelRequest(cancelId) 154 | } 155 | err := lspHandler.Handle(ctx, reply, req) 156 | if err != nil { 157 | if errors.Is(err, langserver.ErrUnhandled) { 158 | logger.Debug(err.Error(), "method", req.Method(), "request", string(req.Params())) 159 | } else { 160 | logger.Error("Error", "method", req.Method(), "err", err) 161 | } 162 | } else { 163 | runningRequests.Delete(cancelId) 164 | } 165 | }() 166 | 167 | return nil // unhandled 168 | }) 169 | <-conn.Done() 170 | } 171 | 172 | type rwc struct { 173 | r io.ReadCloser 174 | w io.WriteCloser 175 | } 176 | 177 | func (rwc *rwc) Read(b []byte) (int, error) { return rwc.r.Read(b) } 178 | func (rwc *rwc) Write(b []byte) (int, error) { return rwc.w.Write(b) } 179 | func (rwc *rwc) Close() error { 180 | rwc.r.Close() 181 | return rwc.w.Close() 182 | } 183 | 184 | func connectLanguageServer(rwc io.ReadWriteCloser) jsonrpc2.Conn { 185 | bufStream := jsonrpc2.NewStream(rwc) 186 | rootConn := jsonrpc2.NewConn(bufStream) 187 | return rootConn 188 | 189 | } 190 | -------------------------------------------------------------------------------- /cmd/DaedalusLanguageServer/pprof_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | type pprofServer struct { 10 | pprofAddr string 11 | 12 | server *http.Server 13 | running bool 14 | } 15 | 16 | func (p *pprofServer) ChangeAddr(addr string) { 17 | if p.pprofAddr == addr { 18 | return 19 | } 20 | 21 | var a, b, c, d byte 22 | var port uint16 23 | if _, err := fmt.Sscanf(addr, "%d.%d.%d.%d:%d", &a, &b, &c, &d, &port); err != nil { 24 | return 25 | } 26 | if port == 0 { 27 | return 28 | } 29 | addr = fmt.Sprintf("%d.%d.%d.%d:%d", a, b, c, d, port) 30 | 31 | p.pprofAddr = addr 32 | p.Stop() 33 | p.Start() 34 | } 35 | 36 | func (p *pprofServer) Start() { 37 | if p.server == nil { 38 | p.server = &http.Server{Handler: http.DefaultServeMux} 39 | p.running = false 40 | } 41 | if p.running { 42 | return 43 | } 44 | if p.pprofAddr == "" { 45 | return 46 | } 47 | 48 | defer recover() 49 | p.server.Addr = p.pprofAddr 50 | p.running = true 51 | 52 | go func() { 53 | p.server.ListenAndServe() 54 | p.running = false 55 | }() 56 | } 57 | func (p *pprofServer) Stop() { 58 | if p.server != nil { 59 | p.server.Shutdown(context.Background()) 60 | } 61 | p.server = nil 62 | } 63 | -------------------------------------------------------------------------------- /daedalus/symbol/symbol.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | import ( 4 | "io" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Symbol ... 10 | type Symbol interface { 11 | Name() string 12 | Source() string 13 | Documentation() string 14 | Definition() Definition 15 | String() string 16 | } 17 | 18 | type symbolBase struct { 19 | NameValue string 20 | SymbolSource string 21 | SymbolDocumentation string 22 | SymbolDefinition Definition 23 | } 24 | 25 | var _ Symbol = (*Function)(nil) 26 | var _ Symbol = (*ArrayVariable)(nil) 27 | var _ Symbol = (*Variable)(nil) 28 | var _ Symbol = (*Constant)(nil) 29 | var _ Symbol = (*ProtoTypeOrInstance)(nil) 30 | var _ Symbol = (*Class)(nil) 31 | var _ Symbol = (*ConstantArray)(nil) 32 | 33 | func newSymbolBase(name, source, doc string, def Definition) symbolBase { 34 | return symbolBase{ 35 | NameValue: name, 36 | SymbolSource: source, 37 | SymbolDocumentation: doc, 38 | SymbolDefinition: def, 39 | } 40 | } 41 | 42 | // NewFunction ... 43 | func NewFunction(name, source, doc string, def Definition, retType string, bodyDef Definition, params []Variable, locals []Symbol) Function { 44 | return Function{ 45 | symbolBase: newSymbolBase(name, source, doc, def), 46 | ReturnType: retType, 47 | BodyDefinition: bodyDef, 48 | Parameters: params, 49 | LocalVariables: locals, 50 | } 51 | } 52 | 53 | // NewVariable ... 54 | func NewVariable(name, varType, source, documentation string, definiton Definition) Variable { 55 | return Variable{ 56 | Type: varType, 57 | symbolBase: newSymbolBase(name, source, documentation, definiton), 58 | } 59 | } 60 | 61 | // NewVariableSymbol ... 62 | func NewArrayVariable(name, varType, sizeText, source, documentation string, definiton Definition) ArrayVariable { 63 | return ArrayVariable{ 64 | Type: varType, 65 | ArraySizeText: sizeText, 66 | symbolBase: newSymbolBase(name, source, documentation, definiton), 67 | } 68 | } 69 | 70 | // NewConstant ... 71 | func NewConstant(name, varType, source, documentation string, definiton Definition, value string) Constant { 72 | return Constant{ 73 | Variable: NewVariable(name, varType, source, documentation, definiton), 74 | Value: value, 75 | } 76 | } 77 | 78 | // NewConstantArray ... 79 | func NewConstantArray(name, varType, arraySizeText, source, documentation string, definiton Definition, value string, elements []ArrayElement) ConstantArray { 80 | return ConstantArray{ 81 | Variable: NewVariable(name, varType, source, documentation, definiton), 82 | Value: value, 83 | ArraySizeText: arraySizeText, 84 | Elements: elements, 85 | } 86 | } 87 | 88 | // NewClass ... 89 | func NewClass(name, source, documentation string, definiton Definition, bodyDef Definition, fields []Symbol) Class { 90 | return Class{ 91 | symbolBase: newSymbolBase(name, source, documentation, definiton), 92 | BodyDefinition: bodyDef, 93 | Fields: fields, 94 | } 95 | } 96 | 97 | // NewPrototypeOrInstance ... 98 | func NewPrototypeOrInstance(name, parent, source, documentation string, definiton Definition, bodyDef Definition, isInstance bool) ProtoTypeOrInstance { 99 | return ProtoTypeOrInstance{ 100 | Parent: parent, 101 | symbolBase: newSymbolBase(name, source, documentation, definiton), 102 | IsInstance: isInstance, 103 | BodyDefinition: bodyDef, 104 | } 105 | } 106 | 107 | // NewNamespace ... 108 | func NewNamespace(name string, parent *Namespace, source, documentation string, definiton Definition, bodyDef Definition) Namespace { 109 | ns := Namespace{ 110 | Parent: parent, 111 | symbolBase: newSymbolBase(name, source, documentation, definiton), 112 | BodyDefinition: bodyDef, 113 | Constants: make(map[string]Symbol, 1), 114 | Variables: make(map[string]Symbol, 1), 115 | Functions: make(map[string]Function, 1), 116 | Classes: make(map[string]Class, 1), 117 | Prototypes: make(map[string]ProtoTypeOrInstance, 1), 118 | Instances: make(map[string]ProtoTypeOrInstance, 1), 119 | } 120 | ns.fullName = ns.getFullName() 121 | return ns 122 | } 123 | 124 | func writeParameterVariables(w io.StringWriter, symbols []Variable) { 125 | for i, s := range symbols { 126 | if i > 0 { 127 | w.WriteString(", ") 128 | } 129 | w.WriteString(s.String()) 130 | } 131 | } 132 | 133 | // Name ... 134 | func (s symbolBase) Name() string { 135 | return s.NameValue 136 | } 137 | 138 | // Source ... 139 | func (s symbolBase) Source() string { 140 | return s.SymbolSource 141 | } 142 | 143 | // Documentation ... 144 | func (s symbolBase) Documentation() string { 145 | return s.SymbolDocumentation 146 | } 147 | 148 | // Definition ... 149 | func (s symbolBase) Definition() Definition { 150 | return s.SymbolDefinition 151 | } 152 | 153 | // Function ... 154 | type Function struct { 155 | ReturnType string 156 | Parameters []Variable 157 | LocalVariables []Symbol 158 | symbolBase 159 | BodyDefinition Definition 160 | } 161 | 162 | // GetType ... 163 | func (s Function) GetType() string { 164 | return s.ReturnType 165 | } 166 | 167 | // String ... 168 | func (s Function) String() string { 169 | sb := strings.Builder{} 170 | sb.WriteString("func ") 171 | sb.WriteString(s.ReturnType) 172 | sb.WriteString(" ") 173 | sb.WriteString(s.Name()) 174 | sb.WriteString("(") 175 | writeParameterVariables(&sb, s.Parameters) 176 | sb.WriteString(")") 177 | 178 | return sb.String() 179 | } 180 | 181 | // Variable ... 182 | type Variable struct { 183 | Type string 184 | symbolBase 185 | } 186 | 187 | // String ... 188 | func (s Variable) String() string { 189 | return "var " + s.Type + " " + s.Name() 190 | } 191 | 192 | // GetType ... 193 | func (s Variable) GetType() string { 194 | return s.Type 195 | } 196 | 197 | // VariableSymbol ... 198 | type ArrayVariable struct { 199 | Type string 200 | ArraySizeText string 201 | symbolBase 202 | } 203 | 204 | // String ... 205 | func (s ArrayVariable) String() string { 206 | return "var " + s.Type + " " + s.Name() + "[" + s.ArraySizeText + "]" 207 | } 208 | 209 | func (s ArrayVariable) Format(w io.StringWriter, resolvedSize int) { 210 | w.WriteString("var ") 211 | w.WriteString(s.Type) 212 | w.WriteString(" ") 213 | w.WriteString(s.Name()) 214 | w.WriteString("[") 215 | w.WriteString(s.ArraySizeText) 216 | if resolvedSize != -1 { 217 | w.WriteString(":") 218 | w.WriteString(strconv.Itoa(resolvedSize)) 219 | } 220 | w.WriteString("]") 221 | } 222 | 223 | // GetType ... 224 | func (s ArrayVariable) GetType() string { 225 | return s.Type 226 | } 227 | 228 | // Constant ... 229 | type Constant struct { 230 | Value string 231 | Variable 232 | } 233 | 234 | // String ... 235 | func (s Constant) String() string { 236 | return "const " + s.Type + " " + s.Name() + " = " + s.Value 237 | } 238 | 239 | // GetType ... 240 | func (s Constant) GetType() string { 241 | return s.Type 242 | } 243 | 244 | type ConstantArray struct { 245 | Variable 246 | Value string 247 | ArraySizeText string 248 | Elements []ArrayElement 249 | } 250 | 251 | // String ... 252 | func (s ConstantArray) String() string { 253 | sb := strings.Builder{} 254 | s.Format(&sb, -1) 255 | return sb.String() 256 | } 257 | 258 | func (s ConstantArray) Format(w io.StringWriter, resolvedSize int) { 259 | w.WriteString("const ") 260 | w.WriteString(s.Type) 261 | w.WriteString(" ") 262 | w.WriteString(s.Name()) 263 | w.WriteString("[") 264 | w.WriteString(s.ArraySizeText) 265 | if resolvedSize != -1 { 266 | w.WriteString(":") 267 | w.WriteString(strconv.Itoa(resolvedSize)) 268 | } 269 | w.WriteString("] = ") 270 | w.WriteString(s.Value) 271 | } 272 | 273 | // GetType ... 274 | func (s ConstantArray) GetType() string { 275 | return s.Type 276 | } 277 | 278 | // Class ... 279 | type Class struct { 280 | Fields []Symbol 281 | symbolBase 282 | BodyDefinition Definition 283 | } 284 | 285 | // String ... 286 | func (s Class) String() string { 287 | return "class " + s.Name() 288 | } 289 | 290 | // ProtoTypeOrInstance ... 291 | type ProtoTypeOrInstance struct { 292 | Parent string 293 | Fields []Constant 294 | symbolBase 295 | BodyDefinition Definition 296 | IsInstance bool 297 | } 298 | 299 | // String ... 300 | func (s ProtoTypeOrInstance) String() string { 301 | if s.IsInstance { 302 | return "instance " + s.Name() + "(" + s.Parent + ")" 303 | } 304 | return "prototype " + s.Name() + "(" + s.Parent + ")" 305 | } 306 | 307 | type Namespace struct { 308 | symbolBase 309 | 310 | Parent *Namespace 311 | 312 | Constants map[string]Symbol 313 | Variables map[string]Symbol 314 | Functions map[string]Function 315 | Classes map[string]Class 316 | Prototypes map[string]ProtoTypeOrInstance 317 | Instances map[string]ProtoTypeOrInstance 318 | 319 | BodyDefinition Definition 320 | fullName string 321 | } 322 | 323 | func (s Namespace) getFullName() string { 324 | var parents []*Namespace 325 | for p := s.Parent; p != nil; p = p.Parent { 326 | parents = append(parents, p) 327 | } 328 | name := s.Name() 329 | for i := len(parents) - 1; i >= 0; i-- { 330 | name = parents[i].Name() + ":" + name 331 | } 332 | return name 333 | } 334 | 335 | func (s Namespace) FullName() string { 336 | return s.fullName 337 | } 338 | 339 | // String ... 340 | func (s Namespace) String() string { 341 | return "namespace " + s.FullName() 342 | } 343 | 344 | // ArrayElement ... 345 | type ArrayElement struct { 346 | Value string 347 | Definition Definition 348 | } 349 | 350 | func NewArrayElement(value string, definiton Definition) ArrayElement { 351 | return ArrayElement{ 352 | Value: value, 353 | Definition: definiton, 354 | } 355 | } 356 | 357 | func (el ArrayElement) GetValue() string { 358 | return el.Value 359 | } 360 | 361 | // Definition ... 362 | type Definition struct { 363 | Start DefinitionIndex 364 | End DefinitionIndex 365 | } 366 | 367 | // NewDefinition ... 368 | func NewDefinition(startLine, startCol, endLine, endCol int) Definition { 369 | return Definition{ 370 | Start: DefinitionIndex{ 371 | Line: startLine, 372 | Column: startCol, 373 | }, 374 | End: DefinitionIndex{ 375 | Line: endLine, 376 | Column: endCol, 377 | }, 378 | } 379 | } 380 | 381 | // InBBox ... 382 | func (sd Definition) InBBox(di DefinitionIndex) bool { 383 | if di.Line < sd.Start.Line { 384 | return false 385 | } 386 | if di.Line > sd.End.Line { 387 | return false 388 | } 389 | if sd.Start.Line <= di.Line && di.Line < sd.End.Line { 390 | return true 391 | } 392 | if di.Line == sd.End.Line && di.Column <= sd.End.Column { 393 | return true 394 | } 395 | if sd.Start.Line <= di.Line && 396 | sd.Start.Column <= di.Column && 397 | di.Line <= sd.End.Line && 398 | di.Column <= sd.End.Column { 399 | return true 400 | } 401 | 402 | return false 403 | } 404 | 405 | // DefinitionIndex ... 406 | type DefinitionIndex struct { 407 | Line int 408 | Column int 409 | } 410 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kirides/DaedalusLanguageServer 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/antlr4-go/antlr/v4 v4.13.1 7 | github.com/goccy/go-json v0.10.5 8 | github.com/google/uuid v1.6.0 9 | go.lsp.dev/jsonrpc2 v0.10.0 10 | go.lsp.dev/uri v0.3.0 11 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 12 | golang.org/x/text v0.24.0 13 | ) 14 | 15 | require ( 16 | github.com/segmentio/asm v1.2.0 // indirect 17 | github.com/segmentio/encoding v0.4.0 // indirect 18 | golang.org/x/sys v0.19.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= 2 | github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= 3 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 4 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 5 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 6 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 7 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 8 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 9 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 10 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 11 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 12 | github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOVmy8= 13 | github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= 14 | go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= 15 | go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= 16 | go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= 17 | go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= 18 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= 19 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= 20 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 21 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 22 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 23 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 24 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 25 | -------------------------------------------------------------------------------- /javadoc/function.go: -------------------------------------------------------------------------------- 1 | package javadoc 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 9 | ) 10 | 11 | type Func struct { 12 | ReturnType string 13 | Parameters []string 14 | } 15 | 16 | func NewFunc(ret string, params ...string) Func { 17 | p := make([]string, len(params)) 18 | for i, par := range params { 19 | p[i] = strings.TrimSpace(par) 20 | } 21 | return Func{ 22 | ReturnType: strings.TrimSpace(ret), 23 | Parameters: p, 24 | } 25 | } 26 | 27 | func parseFuncSigantureDirective(input string) (Func, error) { 28 | parts := strings.Split(input, ",") 29 | if len(parts) < 1 { 30 | return Func{}, fmt.Errorf("signature directive needs to contain at least one type") 31 | } 32 | return NewFunc(parts[0], parts[1:]...), nil 33 | } 34 | 35 | func getFuncSignatureString(input []string) (string, error) { 36 | directive := strings.Join(input, ",") 37 | sign, err := parseFuncSigantureDirective(directive) 38 | if err == nil { 39 | return sign.String(), nil 40 | } 41 | return "", err 42 | } 43 | 44 | func (fs Func) String() string { 45 | pars := fs.Parameters 46 | for i, p := range pars { 47 | pars[i] = "var " + p + " p" + strconv.Itoa(i) 48 | } 49 | return "func " + fs.ReturnType + " fn(" + strings.Join(pars, ", ") + ")" 50 | 51 | } 52 | 53 | func (fs Func) EqualSym(other symbol.Function) bool { 54 | if !strings.EqualFold(fs.ReturnType, other.ReturnType) { 55 | return false 56 | } 57 | if len(fs.Parameters) != len(other.Parameters) { 58 | return false 59 | } 60 | for index, p := range fs.Parameters { 61 | if !strings.EqualFold(p, other.Parameters[index].Type) { 62 | return false 63 | } 64 | } 65 | return true 66 | } 67 | func (fs Func) Equal(other Func) bool { 68 | if !strings.EqualFold(fs.ReturnType, other.ReturnType) { 69 | return false 70 | } 71 | if len(fs.Parameters) != len(other.Parameters) { 72 | return false 73 | } 74 | for index, p := range fs.Parameters { 75 | if !strings.EqualFold(p, other.Parameters[index]) { 76 | return false 77 | } 78 | } 79 | return true 80 | } 81 | -------------------------------------------------------------------------------- /javadoc/javadoc.go: -------------------------------------------------------------------------------- 1 | package javadoc 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "strings" 7 | 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 9 | ) 10 | 11 | var ( 12 | javadocGlobal = []byte("@global") 13 | javadocParam = []byte("@param") 14 | javadocReturn = []byte("@return") 15 | ) 16 | 17 | type javadoc struct { 18 | Summary string 19 | Globals string 20 | Parameters string 21 | Return string 22 | } 23 | 24 | func appendMarkdownEscaped(sb *strings.Builder, text string) { 25 | for _, v := range text { 26 | if v == '*' { 27 | sb.WriteRune('\\') 28 | } 29 | sb.WriteRune(v) 30 | } 31 | } 32 | 33 | func formatGlobal(sb *strings.Builder, line string) { 34 | name, desc, ok := strings.Cut(line, " ") 35 | if ok { 36 | name = strings.TrimSpace(name) 37 | sb.WriteString("- `") 38 | sb.WriteString(name) 39 | sb.WriteString("` - ") 40 | appendMarkdownEscaped(sb, desc) 41 | sb.WriteString(" \n") 42 | } else { 43 | appendMarkdownEscaped(sb, line) 44 | sb.WriteString(" \n") 45 | } 46 | 47 | } 48 | 49 | func formatParams(sb *strings.Builder, param, desc string) { 50 | sb.WriteString("- `") 51 | appendMarkdownEscaped(sb, param) 52 | sb.WriteString("` - *") 53 | 54 | const ( 55 | PREFIX_INST = "{" 56 | PREFIX_ENUM = "[" 57 | PREFIX_FUNC = "<" 58 | 59 | INST_OPEN = PREFIX_INST 60 | INST_CLOSE = "}" 61 | 62 | ENUM_OPEN = PREFIX_ENUM 63 | ENUM_CLOSE = "]" 64 | 65 | FUNC_OPEN = PREFIX_FUNC 66 | FUNC_CLOSE = ">" 67 | ) 68 | 69 | if strings.HasPrefix(desc, PREFIX_INST) || strings.HasPrefix(desc, PREFIX_ENUM) || strings.HasPrefix(desc, PREFIX_FUNC) { 70 | insts, desc := ParseWithinDedup(desc, INST_OPEN, INST_CLOSE) 71 | enums, desc := ParseWithinDedup(desc, ENUM_OPEN, ENUM_CLOSE) 72 | fnSigdata, desc := ParseWithin(desc, FUNC_OPEN, FUNC_CLOSE) 73 | 74 | appendMarkdownEscaped(sb, desc) 75 | sb.WriteString("* \n") 76 | 77 | if len(insts) != 0 { 78 | sb.WriteString("Types: ") 79 | for i := 0; i < len(insts); i++ { 80 | insts[i] = "`" + insts[i] + "`" 81 | } 82 | sb.WriteString(strings.Join(insts, ", ")) 83 | sb.WriteString("\n") 84 | } 85 | if len(enums) != 0 { 86 | sb.WriteString("Valid Values: ") 87 | for i := 0; i < len(enums); i++ { 88 | enums[i] = "`" + enums[i] + "`" 89 | } 90 | sb.WriteString(strings.Join(enums, ", ")) 91 | sb.WriteString("\n") 92 | } 93 | if len(fnSigdata) != 0 { 94 | sb.WriteString("Required function signature: ") 95 | sig, err := getFuncSignatureString(fnSigdata) 96 | if err == nil { 97 | // I wanted daedalus syntax highlightin here, but it does not work for me 98 | sb.WriteString("\n```daedalus\n" + sig + "\n```") 99 | } else { 100 | sb.WriteString("") 101 | } 102 | sb.WriteString("\n") 103 | } 104 | } else { 105 | appendMarkdownEscaped(sb, desc) 106 | sb.WriteString("*\n") 107 | } 108 | } 109 | 110 | // RemoveTokens removes all javadoc tokens from a text and makes it plain text 111 | func RemoveTokens(desc string) string { 112 | if strings.HasPrefix(desc, "{") || strings.HasPrefix(desc, "[") || strings.HasPrefix(desc, "<") { 113 | _, desc = ParseWithin(desc, "{", "}") 114 | _, desc = ParseWithin(desc, "[", "]") 115 | _, desc = ParseWithin(desc, "<", ">") 116 | } 117 | return desc 118 | } 119 | 120 | func parseJavadocMdEscaped(sym symbol.Symbol) javadoc { 121 | r := javadoc{ 122 | Summary: sym.Documentation(), 123 | } 124 | fn, ok := sym.(symbol.Function) 125 | if !ok { 126 | return r 127 | } 128 | 129 | summary := strings.Builder{} 130 | globals := strings.Builder{} 131 | params := strings.Builder{} 132 | returns := strings.Builder{} 133 | 134 | scn := bufio.NewScanner(strings.NewReader(sym.Documentation())) 135 | 136 | for scn.Scan() { 137 | line := bytes.TrimSpace(scn.Bytes()) 138 | 139 | if bytes.HasPrefix(line, javadocReturn) { 140 | line = bytes.TrimSpace(bytes.TrimPrefix(line, javadocReturn)) 141 | appendMarkdownEscaped(&returns, string(line)) 142 | } else if bytes.HasPrefix(line, javadocParam) { 143 | param, desc, ok := parseJavadocParam(string(line)) 144 | if !ok { 145 | continue 146 | } 147 | for _, p := range fn.Parameters { 148 | if strings.EqualFold(p.Name(), param) { 149 | formatParams(¶ms, param, desc) 150 | break 151 | } 152 | } 153 | } else if bytes.HasPrefix(line, javadocGlobal) { 154 | line = bytes.TrimSpace(bytes.TrimPrefix(line, javadocGlobal)) 155 | formatGlobal(&globals, string(line)) 156 | } else { 157 | appendMarkdownEscaped(&summary, string(line)) 158 | summary.WriteString(" \n") 159 | } 160 | } 161 | r.Summary = strings.TrimSpace(summary.String()) 162 | if r.Globals != "" { 163 | globals.WriteString("\n") 164 | } 165 | r.Globals = strings.TrimSpace(globals.String()) 166 | r.Parameters = strings.TrimSpace(params.String()) 167 | r.Return = strings.TrimSpace(returns.String()) 168 | return r 169 | } 170 | 171 | func MarkdownSimple(sym symbol.Symbol) string { 172 | doc := parseJavadocMdEscaped(sym) 173 | return Markdown(doc) 174 | } 175 | 176 | func Markdown(doc javadoc) string { 177 | sb := strings.Builder{} 178 | 179 | sb.WriteString(doc.Summary) 180 | sb.WriteString("\n") 181 | sb.WriteString("\n") 182 | if doc.Globals != "" { 183 | sb.WriteString("### Globals\n") 184 | sb.WriteString(doc.Globals) 185 | sb.WriteString("\n") 186 | } 187 | if doc.Parameters != "" { 188 | sb.WriteString("### Parameters\n") 189 | sb.WriteString(doc.Parameters) 190 | } 191 | if doc.Return != "" { 192 | sb.WriteString("\n\n*returns ") 193 | sb.WriteString(doc.Return) 194 | sb.WriteString("*") 195 | } 196 | 197 | return strings.TrimSpace(sb.String()) 198 | } 199 | 200 | func parseJavadocParam(line string) (param, desc string, ok bool) { 201 | line = strings.TrimSpace(line[7:]) 202 | 203 | if len(line) < 8 { 204 | // minimum "@param a" 205 | return "", "", false 206 | } 207 | 208 | param, desc, ok = strings.Cut(line, " ") 209 | 210 | param = strings.TrimSpace(param) 211 | desc = strings.TrimSpace(desc) 212 | return 213 | } 214 | 215 | func FindParam(doc, key string) string { 216 | scn := bufio.NewScanner(strings.NewReader(doc)) 217 | 218 | for scn.Scan() { 219 | line := bytes.TrimSpace(scn.Bytes()) 220 | if !bytes.HasPrefix(line, javadocParam) { 221 | continue 222 | } 223 | line = bytes.TrimSpace(bytes.TrimPrefix(line, javadocParam)) 224 | param, doc, ok := bytes.Cut(line, []byte(" ")) 225 | 226 | if ok && strings.EqualFold(key, string(param)) { 227 | return string(bytes.TrimSpace(doc)) 228 | } 229 | } 230 | 231 | return "" 232 | } 233 | 234 | // dedupI returns a slice without any duplicates (insensitive) 235 | func dedupI(in []string) []string { 236 | var result []string 237 | for _, v := range in { 238 | add := true 239 | for _, existing := range result { 240 | if strings.EqualFold(existing, v) { 241 | add = false 242 | break 243 | } 244 | } 245 | if add { 246 | result = append(result, v) 247 | } 248 | } 249 | return result 250 | } 251 | 252 | func ParseWithinDedup(doc, open, close string) (instances []string, remaining string) { 253 | 254 | idxOpen := strings.Index(doc, open) 255 | if idxOpen == -1 { 256 | return nil, doc 257 | } 258 | 259 | idxClose := strings.Index(doc, close) 260 | if idxClose == -1 || idxClose < idxOpen { 261 | return nil, doc 262 | } 263 | 264 | rem := doc[idxClose+1:] 265 | 266 | instances = strings.Split(doc[idxOpen+1:idxClose], ",") 267 | 268 | for i := 0; i < len(instances); i++ { 269 | instances[i] = strings.TrimSpace(instances[i]) 270 | } 271 | 272 | return dedupI(instances), strings.TrimSpace(rem) 273 | } 274 | func ParseWithin(doc, open, close string) (instances []string, remaining string) { 275 | 276 | idxOpen := strings.Index(doc, open) 277 | if idxOpen == -1 { 278 | return nil, doc 279 | } 280 | 281 | idxClose := strings.Index(doc, close) 282 | if idxClose == -1 || idxClose < idxOpen { 283 | return nil, doc 284 | } 285 | 286 | rem := doc[idxClose+1:] 287 | 288 | instances = strings.Split(doc[idxOpen+1:idxClose], ",") 289 | 290 | for i := 0; i < len(instances); i++ { 291 | instances[i] = strings.TrimSpace(instances[i]) 292 | } 293 | 294 | return instances, strings.TrimSpace(rem) 295 | } 296 | -------------------------------------------------------------------------------- /javadoc/javadoc_test.go: -------------------------------------------------------------------------------- 1 | package javadoc_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 8 | "github.com/kirides/DaedalusLanguageServer/javadoc" 9 | "github.com/kirides/DaedalusLanguageServer/langserver" 10 | ) 11 | 12 | func TestJavadocParam(t *testing.T) { 13 | 14 | s := symbol.NewFunction("Fn", "", `@param p decides things 15 | `, 16 | symbol.Definition{}, 17 | "void", 18 | symbol.Definition{}, 19 | []symbol.Variable{symbol.NewVariable("p", "int", "", "", symbol.Definition{})}, 20 | []symbol.Symbol{}) 21 | 22 | jd := javadoc.MarkdownSimple(s) 23 | 24 | if !strings.Contains(jd, "- `p` - *decides things*") { 25 | t.Fatalf("expected markdown property content, actual: %s", jd) 26 | } 27 | } 28 | 29 | func TestJavadocParamWithParser(t *testing.T) { 30 | result := &langserver.ParseResult{ 31 | Functions: map[string]symbol.Function{ 32 | "INITDAMAGE": symbol.NewFunction( 33 | "INITDAMAGE", 34 | ".", 35 | "Something\n\n@param amount the amount", 36 | symbol.Definition{}, 37 | "void", 38 | symbol.Definition{}, 39 | []symbol.Variable{ 40 | symbol.NewVariable("amount", "int", ".", "", symbol.Definition{}), 41 | }, 42 | []symbol.Symbol{}, 43 | ), 44 | }, 45 | } 46 | 47 | fn := result.Functions["INITDAMAGE"] 48 | 49 | jd := javadoc.MarkdownSimple(fn) 50 | 51 | if !strings.Contains(jd, "- `amount` - *the amount*") { 52 | t.Fatalf("expected markdown property content, actual: %s", jd) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /langserver/base_lsphandler.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | dls "github.com/kirides/DaedalusLanguageServer" 10 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 11 | "go.lsp.dev/jsonrpc2" 12 | "go.lsp.dev/uri" 13 | ) 14 | 15 | type EmptyHandler struct{} 16 | 17 | var errNotImplemented = errors.New("not implemented") 18 | var ErrUnhandled = errors.New("unhandled") 19 | 20 | func (h *EmptyHandler) Handle(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { 21 | return errNotImplemented 22 | } 23 | 24 | type baseLspHandler struct { 25 | EmptyHandler 26 | conn jsonrpc2.Conn 27 | logger dls.Logger 28 | } 29 | 30 | func (h *baseLspHandler) Handle(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { 31 | return fmt.Errorf("%w: %s", ErrUnhandled, req.Method()) 32 | } 33 | 34 | func (h *baseLspHandler) LogDebug(format string, params ...interface{}) { 35 | h.logger.Debugf(format, params...) 36 | } 37 | func (h *baseLspHandler) LogInfo(format string, params ...interface{}) { 38 | h.logger.Infof(format, params...) 39 | } 40 | func (h *baseLspHandler) LogWarn(format string, params ...interface{}) { 41 | h.logger.Warnf(format, params...) 42 | } 43 | func (h *baseLspHandler) LogError(format string, params ...interface{}) { 44 | h.logger.Errorf(format, params...) 45 | } 46 | 47 | // workaround for unsupported file paths (invalid file://-prefix ) 48 | func fixURI(s string) (string, bool) { 49 | if !strings.HasPrefix(s, "file:///") { 50 | // VS Code sends URLs with only two slashes, which are invalid. golang/go#39789. 51 | if strings.HasPrefix(s, "file://") { 52 | return "file:///" + s[len("file://"):], true 53 | } 54 | return "", false 55 | } 56 | return s, true 57 | } 58 | 59 | func uriToFilename(v lsp.DocumentURI) string { 60 | s := string(v) 61 | if strings.HasPrefix(s, "git:/") { 62 | return "" 63 | } 64 | fixed, ok := fixURI(s) 65 | 66 | if !ok { 67 | return "" 68 | } 69 | 70 | vr := uri.URI(fixed) 71 | 72 | return vr.Filename() 73 | } 74 | -------------------------------------------------------------------------------- /langserver/bufferManager.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // BufferManager ... 11 | type BufferManager struct { 12 | documents map[string]BufferedDocument 13 | mtx sync.RWMutex 14 | } 15 | 16 | // NewBufferManager ... 17 | func NewBufferManager() *BufferManager { 18 | return &BufferManager{ 19 | documents: make(map[string]BufferedDocument), 20 | } 21 | } 22 | 23 | // DeleteBuffer ... 24 | func (m *BufferManager) DeleteBuffer(documentURI string) { 25 | m.mtx.Lock() 26 | defer m.mtx.Unlock() 27 | delete(m.documents, documentURI) 28 | } 29 | 30 | // UpdateBuffer ... 31 | func (m *BufferManager) UpdateBuffer(documentURI string, buf string) { 32 | m.mtx.Lock() 33 | defer m.mtx.Unlock() 34 | d := BufferedDocument(buf) 35 | m.documents[documentURI] = d 36 | } 37 | 38 | // GetBuffer ... 39 | func (m *BufferManager) GetBuffer(documentURI string) BufferedDocument { 40 | m.mtx.RLock() 41 | defer m.mtx.RUnlock() 42 | if doc, ok := m.documents[documentURI]; ok { 43 | return doc 44 | } 45 | return "" 46 | } 47 | 48 | // GetBuffer ... 49 | func (m *BufferManager) GetBufferCtx(ctx context.Context, documentURI string) (BufferedDocument, error) { 50 | ticker := time.NewTicker(60 * time.Second) // 60s delay until request is aborted 51 | defer ticker.Stop() 52 | for { 53 | m.mtx.RLock() 54 | doc, ok := m.documents[documentURI] 55 | m.mtx.RUnlock() 56 | if ok { 57 | return doc, nil 58 | } 59 | select { 60 | case <-ticker.C: 61 | return "", fmt.Errorf("timeout: document %q not found", documentURI) 62 | case <-time.After(100 * time.Millisecond): 63 | case <-ctx.Done(): 64 | return "", fmt.Errorf("document %q not found", documentURI) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /langserver/bufferedDocument.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | "unicode" 8 | 9 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 10 | ) 11 | 12 | var ( 13 | rxFunctionDef = regexp.MustCompile(`^\s*func\s+`) 14 | rxStringValues = regexp.MustCompile(`(".*? "|'.*?')`) 15 | rxFuncCall = regexp.MustCompile(`\([\w@^_,:\/"'=\s\[\]]*\)`) 16 | ) 17 | 18 | // BufferedDocument ... 19 | type BufferedDocument string 20 | 21 | // GetWordRangeAtPosition ... 22 | func (m BufferedDocument) GetWordRangeAtPosition(position lsp.Position) (string, lsp.Range) { 23 | if m == "" { 24 | return "", lsp.Range{} 25 | } 26 | if position.Character < 2 && position.Line < 1 { 27 | return "", lsp.Range{} 28 | } 29 | line := 0 30 | offset := 0 31 | wordLine := int(position.Line) 32 | doc := string(m) 33 | for line < wordLine { 34 | line++ 35 | lineEnd := strings.IndexRune(doc, '\n') 36 | if lineEnd != -1 { 37 | offset += lineEnd 38 | if len(doc) < lineEnd+1 { 39 | break 40 | } 41 | offset++ 42 | doc = doc[lineEnd+1:] 43 | } 44 | } 45 | center := int(position.Character) - 1 46 | if center < 0 { 47 | center = 0 48 | } 49 | start := center 50 | end := center 51 | if start >= len(doc) { 52 | return "", lsp.Range{} 53 | } 54 | if end >= len(doc) { 55 | end = len(doc) 56 | } 57 | for start >= 0 && isIdentifier(doc[start]) { 58 | start-- 59 | } 60 | for end < len(doc) && isIdentifier(doc[end]) { 61 | end++ 62 | } 63 | if start < center { 64 | start++ // skip the first bad character 65 | } 66 | return doc[start : start+(end-start)], lsp.Range{Start: lsp.Position{Line: position.Line, Character: uint32(start)}, End: lsp.Position{Line: position.Line, Character: uint32(end)}} 67 | } 68 | 69 | func (m BufferedDocument) GetIdentifier(position lsp.Position) (partial string, err error) { 70 | doc := string(m) 71 | c := doc 72 | currentLine := 0 73 | offset := 0 74 | line := int(position.Line) 75 | startOfLine := 0 76 | for currentLine < line && offset < len(doc) { 77 | currentLine++ 78 | lineEnd := strings.IndexRune(c, '\n') 79 | if lineEnd == -1 { 80 | break 81 | } 82 | offset += lineEnd 83 | if len(c) < lineEnd+1 { 84 | break 85 | } 86 | offset++ 87 | c = c[lineEnd+1:] 88 | } 89 | 90 | startOfLine = offset 91 | if position.Character > 0 { 92 | offset += int(position.Character) 93 | subDoc := doc[offset:] 94 | 95 | idx := strings.IndexFunc(subDoc, func(r rune) bool { 96 | return !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_') 97 | }) 98 | if idx > 0 { 99 | offset += idx 100 | } 101 | } 102 | 103 | lineContent := doc[startOfLine:offset] 104 | 105 | end := len(lineContent) 106 | o := end - 1 107 | 108 | for o >= 0 { 109 | token := lineContent[o] 110 | r := rune(token) 111 | if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { 112 | o-- 113 | continue 114 | } 115 | break 116 | } 117 | 118 | return lineContent[o+1 : end], nil 119 | } 120 | 121 | func (m BufferedDocument) GetParentSymbolReference(position lsp.Position) (parent string, partial string, err error) { 122 | doc := string(m) 123 | c := doc 124 | currentLine := 0 125 | offset := 0 126 | line := int(position.Line) 127 | startOfLine := 0 128 | for currentLine < line && offset < len(doc) { 129 | currentLine++ 130 | lineEnd := strings.IndexRune(c, '\n') 131 | if lineEnd == -1 { 132 | break 133 | } 134 | offset += lineEnd 135 | if len(c) < lineEnd+1 { 136 | break 137 | } 138 | offset++ 139 | c = c[lineEnd+1:] 140 | } 141 | 142 | startOfLine = offset 143 | if position.Character > 0 { 144 | offset += int(position.Character) 145 | } 146 | 147 | lineContent := doc[startOfLine:offset] 148 | 149 | o := len(lineContent) - 1 150 | start := o 151 | 152 | foundDot := false 153 | rightPart := lineContent 154 | leftPart := lineContent 155 | for o >= 0 { 156 | token := lineContent[o] 157 | r := rune(token) 158 | if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { 159 | o-- 160 | continue 161 | } 162 | if r == '.' { 163 | if start != o { 164 | rightPart = rightPart[o+1:] 165 | } else { 166 | rightPart = rightPart[o:] 167 | } 168 | leftPart = leftPart[:o] 169 | foundDot = true 170 | } 171 | break 172 | } 173 | if !foundDot { 174 | return "", "", fmt.Errorf("not found") 175 | } 176 | 177 | foundIdentifier := false 178 | actualLeftPart := leftPart 179 | for i := len(leftPart) - 1; i >= 0; i-- { 180 | r := rune(leftPart[i]) 181 | if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { 182 | foundIdentifier = true 183 | actualLeftPart = leftPart[i:] 184 | continue 185 | } 186 | break 187 | } 188 | 189 | if foundIdentifier { 190 | return actualLeftPart, rightPart, nil 191 | } 192 | return "", "", fmt.Errorf("not found") 193 | } 194 | 195 | // GetMethodCall ... 196 | func (m BufferedDocument) GetMethodCall(position lsp.Position) string { 197 | doc := string(m) 198 | c := doc 199 | currentLine := 0 200 | offset := 0 201 | line := int(position.Line) 202 | 203 | for currentLine < line && offset < len(doc) { 204 | currentLine++ 205 | lineEnd := strings.IndexRune(c, '\n') 206 | if lineEnd == -1 { 207 | break 208 | } 209 | offset += lineEnd 210 | if len(c) < lineEnd+1 { 211 | break 212 | } 213 | offset++ 214 | c = c[lineEnd+1:] 215 | } 216 | 217 | if position.Character > 0 { 218 | offset += int(position.Character) 219 | } 220 | o := offset 221 | skipOpen := 1 222 | 223 | for o >= 0 { 224 | token := doc[o] 225 | o-- 226 | if token == ';' || token == '}' || token == '{' { 227 | break 228 | } else if token == '(' && skipOpen <= 0 { 229 | break 230 | } else if token == ')' { 231 | skipOpen++ 232 | } else if token == '(' { 233 | skipOpen-- 234 | } else if token == '\n' || token == '\r' { 235 | continue 236 | } 237 | } 238 | if o+1 > len(doc) { 239 | return doc[o:] 240 | } 241 | st := o + 2 242 | if st >= len(doc) { 243 | st = len(doc) - 1 244 | } 245 | ed := st + (offset - o - 2) 246 | if ed < st { 247 | ed = st + 1 248 | } 249 | methodCallLine := doc[st:ed] 250 | for i := 0; i < len(methodCallLine); i++ { 251 | if !unicode.IsSpace(rune(methodCallLine[i])) { 252 | methodCallLine = methodCallLine[i:] 253 | break 254 | } 255 | } 256 | 257 | return methodCallLine 258 | } 259 | 260 | func isIdentifier(b byte) bool { 261 | if unicode.IsDigit(rune(b)) || unicode.IsLetter(rune(b)) { 262 | return true 263 | } 264 | return b == '_' || b == '@' || b == '^' 265 | } 266 | -------------------------------------------------------------------------------- /langserver/bufferedDocument_test.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "testing" 5 | 6 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 7 | ) 8 | 9 | func TestBufferedDocumentWordRange(t *testing.T) { 10 | doc := BufferedDocument(` 11 | const int X_PRINTSCREEN_USE_SMOOTH_PRINTS = TRUE; 12 | 13 | func void X_AI_PrintScreen(var string txt, var int x, var int y, var string fontName, var int timeout) { 14 | AI_PrintScreen(txt, x, y, fontName, timeout); 15 | }; 16 | 17 | func void X_PrintScreen(var string txt, var int x, var int y, var string fontName, var int timeout) { 18 | if (X_PRINTSCREEN_USE_SMOOTH_PRINTS) { 19 | if (y <= 100 && y > 0) { 20 | PrintScreenS_Ext(txt, x, (PS_VMax * y / 100), COL_White, fontName); 21 | } else { 22 | PrintScreenS_Ext(txt, x, (PS_VMax * 30 / 100), COL_White, fontName); 23 | // PrintScreenCXS_Ext(txt, COL_White, fontName); 24 | }; 25 | } else { 26 | PrintScreen(txt, x, y, fontName, timeout); 27 | }; 28 | };`) 29 | 30 | word, pos := doc.GetWordRangeAtPosition(lsp.Position{ 31 | Character: 29, 32 | Line: 8, 33 | }) 34 | t.Logf("Found word: %q at %v", word, pos) 35 | } 36 | func TestBufferedDocumentMethodCall(t *testing.T) { 37 | doc := BufferedDocument(` 38 | const int X_PRINTSCREEN_USE_SMOOTH_PRINTS = TRUE; 39 | 40 | func void X_AI_PrintScreen(var string txt, var int x, var int y, var string fontName, var int timeout) { 41 | AI_PrintScreen(txt, x, y, fontName, timeout); 42 | }; 43 | 44 | func void X_PrintScreen(var string txt, var int x, var int y, var string fontName, var int timeout) { 45 | if (X_PRINTSCREEN_USE_SMOOTH_PRINTS) { 46 | if (y <= 100 && y > 0) { 47 | PrintScreenS_Ext(txt, x, (PS_VMax * y / 100), COL_White, fontName); 48 | } else { 49 | PrintScreenS_Ext(txt, x, (PS_VMax * 30 / 100), COL_White, fontName); 50 | // PrintScreenCXS_Ext(txt, COL_White, fontName); 51 | }; 52 | } else { 53 | PrintScreen(txt, x, y, fontName, timeout); 54 | }; 55 | };`) 56 | 57 | word := doc.GetMethodCall(lsp.Position{ 58 | Character: 40, 59 | Line: 4, 60 | }) 61 | t.Logf("Found Call: %q", word) 62 | } 63 | -------------------------------------------------------------------------------- /langserver/command_setupworkspace.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | "strings" 9 | 10 | dls "github.com/kirides/DaedalusLanguageServer" 11 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 12 | ) 13 | 14 | const CommandSetupWorkspace = "daedalus.dls-setup-workspace" 15 | 16 | type DirFs interface { 17 | fs.FS 18 | fs.ReadDirFS 19 | fs.ReadFileFS 20 | } 21 | 22 | func GetExternalsFSByEngine() map[string]DirFs { 23 | result := map[string]DirFs{} 24 | 25 | engines, err := dls.BuiltinsFS.ReadDir(dls.DaedalusBuiltinsPath) 26 | if err != nil { 27 | return result 28 | } 29 | 30 | for _, v := range engines { 31 | if !v.IsDir() { 32 | continue 33 | } 34 | 35 | result[v.Name()] = dls.BuiltinsFS 36 | } 37 | return result 38 | } 39 | 40 | type DlsMessageRequest struct { 41 | lsp.ShowMessageRequestParams 42 | 43 | WorkspaceURI lsp.DocumentURI 44 | } 45 | 46 | func (h *LspWorkspace) commandSetupWorkspace(ws *LspWorkspace, argStr string) error { 47 | engines := GetExternalsFSByEngine() 48 | h.logger.Debugf("Available Engines:\n%#v", engines) 49 | engine, ok := engines[argStr] 50 | if !ok { 51 | h.logger.Errorf("Engine not supported %q.", argStr) 52 | return nil 53 | } 54 | 55 | targetDir := filepath.Join(ws.path, ".dls", "externals") 56 | err := os.MkdirAll(targetDir, 0640) 57 | if err != nil && !os.IsExist(err) { 58 | h.logger.Errorf("Error creating directory %q. %v", targetDir, err) 59 | return nil 60 | } 61 | root := path.Join(dls.DaedalusBuiltinsPath, argStr) 62 | fs.WalkDir(engine, root, func(entryPath string, d fs.DirEntry, err error) error { 63 | if err != nil { 64 | return nil 65 | } 66 | if d.IsDir() { 67 | return nil 68 | } 69 | 70 | targetFile := filepath.Join(targetDir, strings.TrimPrefix(entryPath, root)) 71 | f, err := os.Create(targetFile) 72 | if err != nil { 73 | h.logger.Errorf("failed to create file %q. %v", targetFile, err) 74 | return nil 75 | } 76 | defer f.Close() 77 | content, err := fs.ReadFile(engine, entryPath) 78 | if err != nil { 79 | h.logger.Errorf("failed to read embedded file %q. %v", entryPath, err) 80 | return nil 81 | } 82 | 83 | if _, err := f.Write(content); err != nil { 84 | h.logger.Errorf("failed to write file %q. %v", targetFile, err) 85 | return nil 86 | } 87 | f.Chmod(0444) 88 | return nil 89 | }) 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /langserver/completionItems.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 7 | "github.com/kirides/DaedalusLanguageServer/javadoc" 8 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 9 | ) 10 | 11 | // DO NOT USE INSIDE Walk... Functions! This RLocks the symbolsProvider and leads to deadlocks otherwise 12 | func completionItemFromSymbol(symbols SymbolProvider, s symbol.Symbol) (lsp.CompletionItem, error) { 13 | kind, err := completionItemKindForSymbol(s) 14 | if err != nil { 15 | return lsp.CompletionItem{}, err 16 | } 17 | return lsp.CompletionItem{ 18 | Kind: kind, 19 | Label: s.Name(), 20 | Detail: SymbolToReadableCode(symbols, s), 21 | Documentation: lsp.MarkupContent{ 22 | Kind: lsp.Markdown, 23 | Value: javadoc.MarkdownSimple(s), 24 | }, 25 | }, nil 26 | } 27 | 28 | func completionItemKindForSymbol(s symbol.Symbol) (lsp.CompletionItemKind, error) { 29 | switch s.(type) { 30 | case symbol.ArrayVariable, symbol.Variable: 31 | return lsp.VariableCompletion, nil 32 | case symbol.Constant, symbol.ConstantArray: 33 | return lsp.ConstantCompletion, nil 34 | case symbol.Function: 35 | return lsp.FunctionCompletion, nil 36 | case symbol.Class: 37 | return lsp.ClassCompletion, nil 38 | case symbol.ProtoTypeOrInstance: 39 | return lsp.ClassCompletion, nil 40 | } 41 | return lsp.CompletionItemKind(-1), fmt.Errorf("symbol not found") 42 | } 43 | -------------------------------------------------------------------------------- /langserver/concurrent.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import "sync" 4 | 5 | type concurrentSet[K comparable] struct { 6 | m sync.Map 7 | } 8 | 9 | func (c *concurrentSet[K]) Store(key K) { 10 | c.m.Store(key, struct{}{}) 11 | } 12 | 13 | func (c *concurrentSet[K]) Contains(key K) bool { 14 | _, loaded := c.m.Load(key) 15 | return loaded 16 | } 17 | -------------------------------------------------------------------------------- /langserver/daedalus_identifier_listener.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/kirides/DaedalusLanguageServer/daedalus/parser" 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 8 | 9 | "github.com/antlr4-go/antlr/v4" 10 | ) 11 | 12 | type Identifier struct { 13 | nameValue string 14 | definition symbol.Definition 15 | } 16 | 17 | func (s Identifier) Name() string { return s.nameValue } 18 | func (s Identifier) Definition() symbol.Definition { return s.definition } 19 | 20 | // Function ... 21 | type FunctionWithIdentifiers struct { 22 | symbol.Function 23 | Identifiers []Identifier 24 | } 25 | 26 | // GetType ... 27 | func (s FunctionWithIdentifiers) GetType() string { 28 | return s.ReturnType 29 | } 30 | 31 | // String ... 32 | func (s FunctionWithIdentifiers) String() string { 33 | return s.Function.String() 34 | } 35 | 36 | // DaedalusIdentifierListener ... 37 | type DaedalusIdentifierListener struct { 38 | parser.BaseDaedalusListener 39 | 40 | GlobalIdentifiers []Identifier 41 | source string 42 | 43 | Bbox symbol.Definition 44 | } 45 | 46 | // NewDaedalusIdentifierListener ... 47 | func NewDaedalusIdentifierListener(source string) *DaedalusIdentifierListener { 48 | return &DaedalusIdentifierListener{ 49 | source: source, 50 | GlobalIdentifiers: []Identifier{}, 51 | Bbox: symbol.Definition{}, 52 | } 53 | } 54 | 55 | func (l *DaedalusIdentifierListener) symbolDefinitionForRuleContext(ctx antlr.ParserRuleContext) symbol.Definition { 56 | return symbol.NewDefinition(ctx.GetStart().GetLine(), ctx.GetStart().GetColumn(), ctx.GetStop().GetLine(), ctx.GetStop().GetColumn()+len(ctx.GetText())) 57 | } 58 | 59 | func (l *DaedalusIdentifierListener) filterRange(ctx antlr.ParserRuleContext) bool { 60 | if l.Bbox != (symbol.Definition{}) { 61 | if !l.Bbox.InBBox(symbol.DefinitionIndex{Line: ctx.GetStart().GetLine(), Column: ctx.GetStart().GetColumn()}) { 62 | return true 63 | } 64 | } 65 | return false 66 | } 67 | 68 | func (l *DaedalusIdentifierListener) onIdentifier(ctx antlr.ParserRuleContext) { 69 | if l.filterRange(ctx) { 70 | return 71 | } 72 | txt := ctx.GetText() 73 | 74 | if strings.Trim(txt, "0123456789") == "" { 75 | // just numbers 76 | return 77 | } 78 | // only plain tokens for now 79 | if strings.EqualFold(txt, "true") || 80 | // strings.EqualFold(txt, "null") || // people actually use "null" as variable names 81 | strings.EqualFold(txt, "false") || 82 | 83 | strings.EqualFold(txt, "hero") || 84 | strings.EqualFold(txt, "self") || 85 | strings.EqualFold(txt, "other") || 86 | strings.EqualFold(txt, "victim") || 87 | 88 | strings.EqualFold(txt, "end") || 89 | strings.EqualFold(txt, "repeat") || 90 | strings.EqualFold(txt, "continue") || 91 | strings.EqualFold(txt, "break") || 92 | strings.EqualFold(txt, "while") || 93 | 94 | strings.EqualFold(txt, "item") { 95 | return 96 | } 97 | 98 | l.GlobalIdentifiers = append(l.GlobalIdentifiers, Identifier{ 99 | nameValue: txt, 100 | definition: l.symbolDefinitionForRuleContext(ctx), 101 | }) 102 | } 103 | 104 | func (l *DaedalusIdentifierListener) EnterAssignment(ctx *parser.AssignmentContext) { 105 | refAtom := ctx.Reference() 106 | l.onIdentifier(refAtom) 107 | } 108 | 109 | func (l *DaedalusIdentifierListener) EnterArrayIndex(ctx *parser.ArrayIndexContext) { 110 | refAtom := ctx.ReferenceAtom() 111 | if refAtom == nil { 112 | return 113 | } 114 | nameNode := refAtom.NameNode() 115 | l.onIdentifier(nameNode) 116 | } 117 | 118 | func (l *DaedalusIdentifierListener) EnterReferenceValue(ctx *parser.ReferenceValueContext) { 119 | refAtom := ctx.Reference() 120 | l.onIdentifier(refAtom) 121 | } 122 | 123 | func (l *DaedalusIdentifierListener) EnterNullLiteralValue(ctx *parser.NullLiteralValueContext) { 124 | l.onIdentifier(ctx) 125 | } 126 | -------------------------------------------------------------------------------- /langserver/daedalus_parser.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/antlr4-go/antlr/v4" 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/parser" 9 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 10 | ) 11 | 12 | type DaedalusGrammarParser interface { 13 | RemoveErrorListeners() 14 | AddErrorListener(antlr.ErrorListener) 15 | SetInputStream(antlr.TokenStream) 16 | GetInterpreter() *antlr.ParserATNSimulator 17 | 18 | NewDaedalusFile() parser.IDaedalusFileContext 19 | } 20 | 21 | type Parser interface { 22 | Parse(source, content string, listener antlr.ParseTreeListener, errListener antlr.ErrorListener) parser.IDaedalusFileContext 23 | } 24 | 25 | type parserPool struct { 26 | inner sync.Pool 27 | } 28 | 29 | func newParserPool(newFn func() DaedalusGrammarParser) *parserPool { 30 | return &parserPool{ 31 | inner: sync.Pool{New: func() interface{} { return newFn() }}, 32 | } 33 | } 34 | func (p *parserPool) Get() DaedalusGrammarParser { return p.inner.Get().(DaedalusGrammarParser) } 35 | func (p *parserPool) Put(v DaedalusGrammarParser) { p.inner.Put(v) } 36 | 37 | type lexerPool struct { 38 | inner sync.Pool 39 | } 40 | 41 | func newLexerPool(newFn func() *parser.DaedalusLexer) *lexerPool { 42 | return &lexerPool{ 43 | inner: sync.Pool{New: func() interface{} { return newFn() }}, 44 | } 45 | } 46 | func (p *lexerPool) Get() *parser.DaedalusLexer { return p.inner.Get().(*parser.DaedalusLexer) } 47 | func (p *lexerPool) Put(v *parser.DaedalusLexer) { p.inner.Put(v) } 48 | 49 | // ParseAndValidateScript ... 50 | func (m *parseResultsManager) ParseAndValidateScript(source, content string) *ParseResult { 51 | stateful := NewDaedalusStatefulListener(source, m) 52 | validating := NewDaedalusValidatingListener(source, m) 53 | errListener := &SyntaxErrorListener{} 54 | m.ParseScriptListener(source, content, combineListeners(stateful, validating), errListener) 55 | 56 | result := &ParseResult{ 57 | SyntaxErrors: errListener.SyntaxErrors, 58 | GlobalVariables: stateful.Globals.Variables, 59 | GlobalConstants: stateful.Globals.Constants, 60 | Functions: stateful.Globals.Functions, 61 | Classes: stateful.Globals.Classes, 62 | Prototypes: stateful.Globals.Prototypes, 63 | Instances: stateful.Globals.Instances, 64 | Namespaces: stateful.Namespaces, 65 | Source: source, 66 | lastModifiedAt: time.Now(), 67 | } 68 | return result 69 | } 70 | 71 | // ParseScriptListener ... 72 | func (m *parseResultsManager) ParseScriptListener(source, content string, listener parser.DaedalusListener, errListener antlr.ErrorListener) parser.IDaedalusFileContext { 73 | return m.parser.Parse(source, content, listener, errListener) 74 | } 75 | 76 | // ParseScript ... 77 | func (m *parseResultsManager) ParseScript(source, content string, lastModifiedAt time.Time) *ParseResult { 78 | m.mtx.RLock() 79 | if existing, ok := m.parseResults[source]; ok && existing.lastModifiedAt.Equal(lastModifiedAt) { 80 | m.mtx.RUnlock() 81 | return existing 82 | } 83 | m.mtx.RUnlock() 84 | 85 | listener := NewDaedalusStatefulListener(source, m) 86 | errListener := &SyntaxErrorListener{} 87 | 88 | daedalusFile := m.ParseScriptListener(source, content, listener, errListener) 89 | 90 | result := &ParseResult{ 91 | Ast: daedalusFile, 92 | SyntaxErrors: errListener.SyntaxErrors, 93 | GlobalVariables: listener.Globals.Variables, 94 | GlobalConstants: listener.Globals.Constants, 95 | Functions: listener.Globals.Functions, 96 | Classes: listener.Globals.Classes, 97 | Prototypes: listener.Globals.Prototypes, 98 | Instances: listener.Globals.Instances, 99 | Namespaces: listener.Namespaces, 100 | Source: source, 101 | lastModifiedAt: lastModifiedAt, 102 | } 103 | return result 104 | } 105 | 106 | func (m *parseResultsManager) ValidateAst(source string, ast parser.IDaedalusFileContext) []SyntaxError { 107 | listener := NewDaedalusValidatingListener(source, m) 108 | errListener := &SyntaxErrorListener{} 109 | antlr.NewParseTreeWalker().Walk(listener, ast) 110 | 111 | return errListener.SyntaxErrors 112 | } 113 | 114 | // SymbolType ... 115 | type SymbolType uint 116 | 117 | const ( 118 | // SymbolNone ... 119 | SymbolNone SymbolType = 0 120 | // SymbolClass ... 121 | SymbolClass SymbolType = 1 << 0 122 | // SymbolConstant ... 123 | SymbolConstant SymbolType = 1 << 1 124 | // SymbolFunction ... 125 | SymbolFunction SymbolType = 1 << 2 126 | // SymbolInstance ... 127 | SymbolInstance SymbolType = 1 << 3 128 | // SymbolPrototype ... 129 | SymbolPrototype SymbolType = 1 << 4 130 | // SymbolVariable ... 131 | SymbolVariable SymbolType = 1 << 5 132 | // SymbolAll ... 133 | SymbolAll SymbolType = 0xFFFFFFFF 134 | ) 135 | 136 | // LookupGlobalSymbol ... 137 | func (p *ParseResult) LookupGlobalSymbol(name string, types SymbolType) (symbol.Symbol, bool) { 138 | if (types & SymbolClass) != 0 { 139 | if s, ok := p.Classes[name]; ok { 140 | return s, true 141 | } 142 | } 143 | if (types & SymbolConstant) != 0 { 144 | if s, ok := p.GlobalConstants[name]; ok { 145 | return s, true 146 | } 147 | } 148 | if (types & SymbolFunction) != 0 { 149 | if s, ok := p.Functions[name]; ok { 150 | return s, true 151 | } 152 | } 153 | if (types & SymbolInstance) != 0 { 154 | if s, ok := p.Instances[name]; ok { 155 | return s, true 156 | } 157 | } 158 | if (types & SymbolPrototype) != 0 { 159 | if s, ok := p.Prototypes[name]; ok { 160 | return s, true 161 | } 162 | } 163 | if (types & SymbolVariable) != 0 { 164 | if s, ok := p.GlobalVariables[name]; ok { 165 | return s, true 166 | } 167 | } 168 | return nil, false 169 | } 170 | 171 | // WalkGlobalSymbols ... 172 | func (p *ParseResult) WalkGlobalSymbols(walkFn func(symbol.Symbol) error, types SymbolType) error { 173 | if (types & SymbolClass) != 0 { 174 | for _, s := range p.Classes { 175 | if err := walkFn(s); err != nil { 176 | return err 177 | } 178 | } 179 | } 180 | if (types & SymbolConstant) != 0 { 181 | for _, s := range p.GlobalConstants { 182 | if err := walkFn(s); err != nil { 183 | return err 184 | } 185 | } 186 | } 187 | if (types & SymbolFunction) != 0 { 188 | for _, s := range p.Functions { 189 | if err := walkFn(s); err != nil { 190 | return err 191 | } 192 | } 193 | } 194 | if (types & SymbolInstance) != 0 { 195 | for _, s := range p.Instances { 196 | if err := walkFn(s); err != nil { 197 | return err 198 | } 199 | } 200 | } 201 | if (types & SymbolPrototype) != 0 { 202 | for _, s := range p.Prototypes { 203 | if err := walkFn(s); err != nil { 204 | return err 205 | } 206 | } 207 | } 208 | if (types & SymbolVariable) != 0 { 209 | for _, s := range p.GlobalVariables { 210 | if err := walkFn(s); err != nil { 211 | return err 212 | } 213 | } 214 | } 215 | return nil 216 | } 217 | -------------------------------------------------------------------------------- /langserver/daedalus_parser_regular.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "github.com/kirides/DaedalusLanguageServer/daedalus/parser" 5 | 6 | "github.com/antlr4-go/antlr/v4" 7 | ) 8 | 9 | type regularGrammarParser struct { 10 | *parser.DaedalusParser 11 | } 12 | 13 | func (rp *regularGrammarParser) NewDaedalusFile() parser.IDaedalusFileContext { 14 | return rp.DaedalusFile() 15 | } 16 | 17 | var _ DaedalusGrammarParser = (*regularGrammarParser)(nil) 18 | var _ Parser = (*RegularParser)(nil) 19 | 20 | type RegularParser struct { 21 | pooledParsers *parserPool 22 | pooledLexers *lexerPool 23 | } 24 | 25 | func newRegularParser() *RegularParser { 26 | return &RegularParser{ 27 | pooledParsers: newParserPool(func() DaedalusGrammarParser { return ®ularGrammarParser{parser.NewDaedalusParser(nil)} }), 28 | pooledLexers: newLexerPool(func() *parser.DaedalusLexer { return parser.NewDaedalusLexer(nil) }), 29 | } 30 | } 31 | 32 | func (rp *RegularParser) Parse(source, content string, listener antlr.ParseTreeListener, errListener antlr.ErrorListener) parser.IDaedalusFileContext { 33 | inputStream := antlr.NewInputStream(content) 34 | 35 | lexer := rp.pooledLexers.Get() 36 | p := rp.pooledParsers.Get() 37 | defer func() { 38 | lexer.SetInputStream(nil) 39 | rp.pooledLexers.Put(lexer) 40 | p.SetInputStream(nil) 41 | rp.pooledParsers.Put(p) 42 | }() 43 | lexer.SetInputStream(inputStream) 44 | tokenStream := antlr.NewCommonTokenStream(lexer, 0) 45 | 46 | p.SetInputStream(tokenStream) 47 | 48 | p.RemoveErrorListeners() 49 | p.AddErrorListener(errListener) 50 | // Use SLL prediction 51 | p.GetInterpreter().SetPredictionMode(antlr.PredictionModeSLL) 52 | 53 | parsed := p.NewDaedalusFile() 54 | antlr.NewParseTreeWalker().Walk(listener, parsed) 55 | return parsed 56 | } 57 | -------------------------------------------------------------------------------- /langserver/daedalus_parser_test.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "github.com/kirides/DaedalusLanguageServer/protocol" 13 | "golang.org/x/exp/slices" 14 | "golang.org/x/text/encoding/charmap" 15 | ) 16 | 17 | type nopLogger struct{} 18 | 19 | func (nopLogger) Debugf(template string, args ...interface{}) {} 20 | func (nopLogger) Infof(template string, args ...interface{}) {} 21 | func (nopLogger) Warnf(template string, args ...interface{}) {} 22 | func (nopLogger) Errorf(template string, args ...interface{}) {} 23 | 24 | func TestParseSingleScript(t *testing.T) { 25 | script := ` 26 | /// Summary line 1 27 | /// Summary Line 2 28 | /// --------------- 29 | /// summary line 4 30 | func void InitDamage() { 31 | const int dmg = 0; 32 | if (dmg) { return; }; 33 | HookEngineF(6736583/*0x66CAC7*/, 5, _DMG_OnDmg_Post); 34 | const int oCNpc__OnDamage_Hit = 6710800; 35 | HookEngineF(oCNpc__OnDamage_Hit, 7, _DMG_OnDmg_Pre); 36 | dmg = 1; 37 | };` 38 | 39 | m := newParseResultsManager(nopLogger{}) 40 | result := m.ParseScript("C:\\temp", script, time.Now()) 41 | if _, ok := result.Functions[strings.ToUpper("InitDamage")]; !ok { 42 | t.Fail() 43 | } 44 | } 45 | 46 | func TestZParserExtender(t *testing.T) { 47 | script := ` 48 | namespace zPE { 49 | func void InitDamage() { 50 | const int dmg = 0; 51 | if (dmg) { return; }; 52 | HookEngineF(6736583/*0x66CAC7*/, 5, _DMG_OnDmg_Post); 53 | const int oCNpc__OnDamage_Hit = 6710800; 54 | HookEngineF(oCNpc__OnDamage_Hit, 7, _DMG_OnDmg_Pre); 55 | dmg = 1; 56 | }; 57 | }; 58 | ` 59 | 60 | m := newParseResultsManager(nopLogger{}) 61 | result := m.ParseScript("C:\\temp", script, time.Now()) 62 | if _, ok := result.Namespaces[strings.ToUpper("zPE")].Functions[strings.ToUpper("InitDamage")]; !ok { 63 | for _, v := range result.SyntaxErrors { 64 | t.Logf("%d:%d %s: %s", v.Line, v.Column, v.ErrorCode.Code, v.ErrorCode.Description) 65 | } 66 | t.Fail() 67 | } 68 | } 69 | 70 | func TestParseSingleScriptFromFile(t *testing.T) { 71 | src := filepath.Join("testdata", "demo.d") 72 | fileBody, _ := os.ReadFile(src) 73 | script, _ := charmap.Windows1252.NewDecoder().Bytes(fileBody) 74 | 75 | m := newParseResultsManager(nopLogger{}) 76 | result := m.ParseScript(src, string(script), time.Now()) 77 | if _, ok := result.Functions[strings.ToUpper("Do")]; !ok { 78 | t.Fail() 79 | } 80 | } 81 | 82 | func TestGothicSrc(t *testing.T) { 83 | m := newParseResultsManager(nopLogger{}) 84 | m.NumParserThreads = 8 85 | result, err := m.ParseSource(context.TODO(), filepath.Join("testdata", "Gothic.src")) 86 | if err != nil { 87 | t.Error(err) 88 | } 89 | _ = result 90 | } 91 | 92 | func BenchmarkGothicSrc(b *testing.B) { 93 | b.Logf("Running %d iterations", b.N) 94 | for i := 0; i < b.N; i++ { 95 | m := newParseResultsManager(nopLogger{}) 96 | m.NumParserThreads = 1 97 | _, err := m.ParseSource(context.TODO(), filepath.Join("testdata", "Gothic.src")) 98 | if err != nil { 99 | b.Error(err) 100 | } 101 | } 102 | } 103 | 104 | func TestParseSemanticForGlobals(t *testing.T) { 105 | script := `var int CurrentLevel; 106 | const int ADDONWORLD_ZEN = 4; 107 | 108 | func void FuncName() { 109 | CurrentLevel = ADDONWORLD_ZEN; 110 | };` 111 | 112 | data := []struct { 113 | expected string 114 | }{ 115 | {expected: "ADDONWORLD_ZEN"}, 116 | {expected: "CurrentLevel"}, 117 | } 118 | m := newParseResultsManager(nopLogger{}) 119 | result, _ := m.ParseSemanticsContentDataTypesRange(context.Background(), "C:\\temp", script, protocol.Range{Start: protocol.Position{Line: 0, Character: 0}, End: protocol.Position{Line: 999, Character: 999}}, DataAll) 120 | 121 | for _, v := range data { 122 | t.Run(fmt.Sprintf("Expect %q", v.expected), func(t *testing.T) { 123 | if !slices.ContainsFunc(result.GlobalIdentifiers, func(i Identifier) bool { 124 | return i.Name() == v.expected 125 | }) { 126 | t.Fail() 127 | } 128 | }) 129 | } 130 | } 131 | 132 | func TestParseSemanticForLocals(t *testing.T) { 133 | script := ` 134 | func void FuncName() { 135 | var int CurrentLevel; 136 | const int ADDONWORLD_ZEN = 4; 137 | 138 | CurrentLevel = ADDONWORLD_ZEN; 139 | };` 140 | 141 | data := []struct { 142 | expected string 143 | }{ 144 | {expected: "ADDONWORLD_ZEN"}, 145 | {expected: "CurrentLevel"}, 146 | } 147 | m := newParseResultsManager(nopLogger{}) 148 | result, _ := m.ParseSemanticsContentDataTypesRange(context.Background(), "C:\\temp", script, protocol.Range{Start: protocol.Position{Line: 0, Character: 0}, End: protocol.Position{Line: 999, Character: 999}}, DataAll) 149 | 150 | for _, v := range data { 151 | t.Run(fmt.Sprintf("Expect %q", v.expected), func(t *testing.T) { 152 | if !slices.ContainsFunc(result.GlobalIdentifiers, func(i Identifier) bool { 153 | return i.Name() == v.expected 154 | }) { 155 | t.Fail() 156 | } 157 | }) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /langserver/daedalus_validating_listener.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/parser" 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 9 | "golang.org/x/exp/slices" 10 | 11 | "github.com/antlr4-go/antlr/v4" 12 | ) 13 | 14 | // DaedalusValidatingListener ... 15 | type DaedalusValidatingListener struct { 16 | parser.BaseDaedalusListener 17 | knownSymbols SymbolProvider 18 | source string 19 | } 20 | 21 | // NewDaedalusValidatingListener ... 22 | func NewDaedalusValidatingListener(source string, knownSymbols SymbolProvider) *DaedalusValidatingListener { 23 | return &DaedalusValidatingListener{ 24 | source: source, 25 | knownSymbols: knownSymbols, 26 | } 27 | } 28 | 29 | type listenerSyntaxError struct { 30 | *antlr.BaseRecognitionException 31 | Code SyntaxErrorCode 32 | } 33 | 34 | func (l *DaedalusValidatingListener) report(parser antlr.Parser, ctx antlr.RuleContext, symbol antlr.Token, code SyntaxErrorCode) { 35 | ex := antlr.NewBaseRecognitionException(code.Description, parser, parser.GetInputStream(), ctx) 36 | parser.NotifyErrorListeners(ex.GetMessage(), symbol, &listenerSyntaxError{BaseRecognitionException: ex, Code: code}) 37 | } 38 | 39 | // EnterStatementBlock ... 40 | func (l *DaedalusValidatingListener) EnterStatementBlock(ctx *parser.StatementBlockContext) { 41 | for i, stmt := range ctx.GetChildren() { 42 | if ifStmt, ok := stmt.(*parser.IfBlockStatementContext); ok { 43 | if ctx.GetChildCount()-1 == i { 44 | l.report(ctx.GetParser(), ctx, ifStmt.GetStop(), D0003MissingSemicolonMightCauseIssues) 45 | continue 46 | } 47 | child := ctx.GetChild(i + 1) 48 | if termNode, ok := child.(*antlr.TerminalNodeImpl); !(ok && termNode.GetText() == ";") { 49 | l.report(ctx.GetParser(), ctx, ifStmt.GetStop(), D0003MissingSemicolonMightCauseIssues) 50 | } 51 | } 52 | } 53 | } 54 | 55 | // EnterAnyIdentifier ... 56 | func (l *DaedalusValidatingListener) EnterAnyIdentifier(ctx *parser.AnyIdentifierContext) { 57 | if ctx.Identifier() != nil { 58 | id := ctx.Identifier().GetText() 59 | 60 | if len(id) > 0 && (id[0] >= 0x30 && id[0] <= 0x39) { 61 | l.report(ctx.GetParser(), ctx, ctx.Identifier().GetSymbol(), D0001NoIdentifierWithStartingDigits) 62 | } 63 | } 64 | } 65 | 66 | func (l *DaedalusValidatingListener) lookupSymbol(name string, symbol SymbolType) symbol.Symbol { 67 | foundSymbol, _ := l.knownSymbols.LookupGlobalSymbol(name, symbol) 68 | return foundSymbol 69 | } 70 | 71 | // EnterFuncCall ... 72 | func (l *DaedalusValidatingListener) EnterFuncCall(ctx *parser.FuncCallContext) { 73 | nameNode := ctx.NameNode() 74 | funcName := nameNode.GetText() 75 | funcName = strings.ToUpper(funcName) 76 | sym := l.lookupSymbol(funcName, SymbolFunction) 77 | 78 | if sym == nil { 79 | return 80 | } 81 | 82 | if len(ctx.AllFuncArgExpression()) < len(sym.(symbol.Function).Parameters) { 83 | l.report(ctx.GetParser(), ctx, nameNode.GetStop(), D0004NotEnoughArgumentsSpecified) 84 | } else if len(ctx.AllFuncArgExpression()) > len(sym.(symbol.Function).Parameters) { 85 | l.report(ctx.GetParser(), ctx, nameNode.GetStop(), D0005TooManyArgumentsSpecified) 86 | } 87 | } 88 | 89 | // zParserExtender 90 | 91 | var metaFields = map[string][]string{ 92 | strings.ToUpper("Parser"): {"GAME", "SFX", "PFX", "VFX", "CAMERA", "MENU", "MUSIC"}, 93 | strings.ToUpper("MergeMode"): {"0", "1", "TRUE", "FALSE"}, 94 | strings.ToUpper("Engine"): {"G1", "G1A", "G2", "G2A"}, 95 | strings.ToUpper("NativeWhile"): {"0", "1", "TRUE", "FALSE"}, 96 | strings.ToUpper("Namespace"): nil, 97 | strings.ToUpper("Using"): nil, 98 | strings.ToUpper("Mod"): nil, 99 | strings.ToUpper("After"): nil, 100 | } 101 | 102 | // EnterZParserExtenderMeta implements parser.DaedalusListener 103 | func (l *DaedalusValidatingListener) EnterZParserExtenderMeta(ctx *parser.ZParserExtenderMetaContext) { 104 | nameNode := ctx.NameNode() 105 | if nameNode == nil || ctx.MetaValue() == nil { 106 | return 107 | } 108 | key := strings.ToUpper(nameNode.GetText()) 109 | if values, ok := metaFields[key]; ok { 110 | // nil means there are no checks 111 | if values == nil { 112 | return 113 | } 114 | switch key { 115 | // these are comma separated 116 | case "ENGINE", "NAMESPACE", "USING", "AFTER": 117 | items := strings.Split(ctx.MetaValue().GetText(), ",") 118 | for _, v := range items { 119 | if !slices.Contains(values, strings.ToUpper(strings.TrimSpace(v))) { 120 | l.report(ctx.GetParser(), 121 | ctx.MetaValue(), 122 | ctx.MetaValue().GetStart(), 123 | NewSyntaxErrorCode("D0000", fmt.Sprintf("Unrecognized values. Known values are: %q", strings.Join(values, ", ")), SeverityError)) 124 | return 125 | } 126 | } 127 | default: 128 | if !slices.Contains(values, strings.ToUpper(ctx.MetaValue().GetText())) { 129 | l.report(ctx.GetParser(), 130 | ctx.MetaValue(), 131 | ctx.MetaValue().GetStart(), 132 | NewSyntaxErrorCode("D0000", fmt.Sprintf("Unrecognized value. Known values are: %q", strings.Join(values, ", ")), SeverityError)) 133 | } 134 | } 135 | } else { 136 | l.report(ctx.GetParser(), nameNode, nameNode.GetStart(), D0006UnrecognizedMetaField) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /langserver/findpath.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "regexp" 8 | "runtime" 9 | "strings" 10 | "unicode" 11 | ) 12 | 13 | // Generates case insensitive pattern used with the filepat Glob() funciton 14 | // source: https://wenzr.wordpress.com/2018/04/09/go-glob-case-insensitive/ 15 | func InsensitivePattern(path string) string { 16 | if runtime.GOOS == "windows" { 17 | return path 18 | } 19 | p := strings.Builder{} 20 | for _, r := range path { 21 | if unicode.IsLetter(r) { 22 | fmt.Fprintf(&p, "[%c%c]", unicode.ToLower(r), unicode.ToUpper(r)) 23 | } else { 24 | p.WriteRune(r) 25 | } 26 | } 27 | return p.String() 28 | } 29 | 30 | func findPaths(file string) ([]string, error) { 31 | abs, err := filepath.Abs(file) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | pattern := InsensitivePattern(abs) 37 | matches, err := filepath.Glob(pattern) 38 | if err != nil { 39 | return nil, err 40 | } 41 | if len(matches) == 0 { 42 | return nil, os.ErrNotExist 43 | } 44 | return matches, nil 45 | } 46 | 47 | func findPath(file string) (string, error) { 48 | matches, err := findPaths(file) 49 | if err != nil { 50 | return "", err 51 | } 52 | if len(matches) == 0 { 53 | return "", os.ErrNotExist 54 | } 55 | return matches[0], nil 56 | } 57 | 58 | func findPathAnywhereUpToRoot(dir, segment string) (string, error) { 59 | absDir, err := filepath.Abs(dir) 60 | if err != nil { 61 | return "", err 62 | } 63 | p, err := findPath(filepath.Join(dir, segment)) 64 | if err == nil { 65 | return p, nil 66 | } 67 | 68 | nextDir := filepath.Dir(absDir) 69 | if nextDir == absDir { 70 | return "", fmt.Errorf("could not find %q anywhere", segment) 71 | } 72 | return findPathAnywhereUpToRoot(nextDir, segment) 73 | } 74 | 75 | func insensitiveRxPattern(path string) string { 76 | p := strings.Builder{} 77 | for _, r := range path { 78 | if unicode.IsLetter(r) { 79 | fmt.Fprintf(&p, "[%c%c]", unicode.ToLower(r), unicode.ToUpper(r)) 80 | } else if r == '*' { 81 | p.WriteRune('.') 82 | p.WriteRune(r) 83 | } else { 84 | p.WriteString(regexp.QuoteMeta(string(r))) 85 | } 86 | } 87 | return p.String() 88 | } 89 | 90 | func ResolvePathsCaseInsensitive(directory, fileName string) (files []string, err error) { 91 | containingDir, err := findPath(directory) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | entries, err := os.ReadDir(containingDir) 97 | if err != nil { 98 | return nil, err 99 | } 100 | pattern := insensitiveRxPattern(fileName) 101 | rxName, err := regexp.Compile(pattern) 102 | if err != nil { 103 | return nil, err 104 | } 105 | for _, file := range entries { 106 | if rxName.MatchString(file.Name()) { 107 | files = append(files, filepath.Join(containingDir, file.Name())) 108 | } 109 | } 110 | 111 | return files, nil 112 | } 113 | -------------------------------------------------------------------------------- /langserver/findpath_test.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "path/filepath" 5 | "runtime" 6 | "testing" 7 | ) 8 | 9 | func TestFindFile(t *testing.T) { 10 | fname := "FINDpath.go" 11 | f, err := findPath(fname) 12 | if err != nil { 13 | t.Fatalf("Err should be nil. %v\n", err) 14 | } 15 | 16 | expectedfname := "findpath.go" 17 | if runtime.GOOS == "windows" { 18 | expectedfname = fname 19 | } 20 | p, _ := filepath.Abs(expectedfname) 21 | if f != p { 22 | t.Fatalf("filepath does not match. Expected %q, got %q,\n", p, f) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /langserver/format_code.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/antlr4-go/antlr/v4" 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/parser" 8 | ) 9 | 10 | func FormatCode(code string) { 11 | inputStream := antlr.NewInputStream(code) 12 | lexer := parser.NewDaedalusLexer(inputStream) 13 | 14 | sb := strings.Builder{} 15 | state := formatState{} 16 | for token := lexer.NextToken(); token.GetTokenType() != antlr.TokenEOF; token = lexer.NextToken() { 17 | txt := token.GetText() 18 | 19 | writeToken(txt, &sb, &state) 20 | updateState(txt, &sb, &state) 21 | 22 | if strings.EqualFold(txt, "func") { 23 | formatFunc(lexer, &sb) 24 | } else if strings.EqualFold(txt, "class") { 25 | formatClass(lexer, &sb) 26 | } else if strings.EqualFold(txt, "instance") { 27 | formatClass(lexer, &sb) 28 | } else if strings.EqualFold(txt, "prototype") { 29 | formatClass(lexer, &sb) 30 | } 31 | 32 | } 33 | final := strings.TrimSpace(sb.String()) 34 | 35 | _ = final 36 | } 37 | 38 | type formatState struct { 39 | IsNewLine bool 40 | Indent int 41 | PrintSpace bool 42 | } 43 | 44 | func writeToken(txt string, sb *strings.Builder, state *formatState) { 45 | if state.IsNewLine { 46 | state.IsNewLine = false 47 | sb.WriteString("\r\n") 48 | 49 | if txt != "}" { 50 | for i := 0; i < state.Indent; i++ { 51 | sb.WriteString(" ") 52 | } 53 | } 54 | } else if strings.ContainsAny(txt, "();") { 55 | 56 | } else if state.PrintSpace && txt != "," { 57 | sb.WriteString(" ") 58 | } 59 | 60 | sb.WriteString(txt) 61 | } 62 | 63 | func updateState(txt string, sb *strings.Builder, state *formatState) { 64 | if strings.TrimSpace(sb.String()) == "};" { 65 | state.IsNewLine = true 66 | } 67 | 68 | state.PrintSpace = !strings.ContainsAny(txt, "(") 69 | if txt == "{" { 70 | state.IsNewLine = true 71 | } 72 | 73 | switch { 74 | case txt == "{": 75 | state.Indent++ 76 | case txt == "}": 77 | state.Indent-- 78 | if state.Indent < 0 { 79 | state.Indent = 0 80 | } 81 | } 82 | } 83 | 84 | func formatFunc(lexer antlr.Lexer, sb *strings.Builder) { 85 | state := formatState{PrintSpace: true} 86 | init := true 87 | for token := lexer.NextToken(); token.GetTokenType() != antlr.TokenEOF; token = lexer.NextToken() { 88 | txt := token.GetText() 89 | 90 | writeToken(txt, sb, &state) 91 | updateState(txt, sb, &state) 92 | 93 | if state.Indent != 0 { 94 | init = false 95 | } 96 | 97 | if state.Indent == 0 && !init { 98 | break 99 | } 100 | } 101 | } 102 | 103 | func formatClass(lexer antlr.Lexer, sb *strings.Builder) { 104 | state := formatState{PrintSpace: true} 105 | init := true 106 | for token := lexer.NextToken(); token.GetTokenType() != antlr.TokenEOF; token = lexer.NextToken() { 107 | txt := token.GetText() 108 | 109 | writeToken(txt, sb, &state) 110 | updateState(txt, sb, &state) 111 | 112 | if txt == ";" { 113 | state.IsNewLine = true 114 | } 115 | 116 | if state.Indent != 0 { 117 | init = false 118 | } 119 | 120 | if state.Indent == 0 && !init { 121 | break 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /langserver/format_code_test.go: -------------------------------------------------------------------------------- 1 | package langserver_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kirides/DaedalusLanguageServer/langserver" 7 | ) 8 | 9 | func TestFormatCode(t *testing.T) { 10 | langserver.FormatCode(` 11 | func void 12 | DoStuff (var int val, 13 | var float b) { 14 | 15 | }; 16 | 17 | class C_ITEM { var int v1; var int v2; var string str; 18 | }; 19 | `) 20 | } 21 | -------------------------------------------------------------------------------- /langserver/handle_codelens.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/goccy/go-json" 9 | 10 | dls "github.com/kirides/DaedalusLanguageServer" 11 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 12 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 13 | "go.lsp.dev/uri" 14 | ) 15 | 16 | type codeLensProvider interface { 17 | Provide(ctx context.Context, source *ParseResult) []lsp.CodeLens 18 | } 19 | 20 | type funcCodeLensProvider func(context.Context, *ParseResult) []lsp.CodeLens 21 | 22 | func (f funcCodeLensProvider) Provide(ctx context.Context, source *ParseResult) []lsp.CodeLens { 23 | return f(ctx, source) 24 | } 25 | 26 | type codeLensHandler struct { 27 | lsp *LspWorkspace 28 | symbolProvider SymbolProvider 29 | providers []codeLensProvider 30 | } 31 | 32 | func (p *codeLensHandler) Provide(ctx context.Context, source *ParseResult) []lsp.CodeLens { 33 | var result []lsp.CodeLens 34 | for _, v := range p.providers { 35 | result = append(result, v.Provide(ctx, source)...) 36 | } 37 | return result 38 | } 39 | 40 | func (p *codeLensHandler) provideImplementations(ctx context.Context, source *ParseResult) []lsp.CodeLens { 41 | countClassesAndPrototypes := 0 42 | 43 | source.WalkGlobalSymbols(func(s symbol.Symbol) error { 44 | countClassesAndPrototypes++ 45 | return nil 46 | }, SymbolClass|SymbolPrototype) 47 | 48 | if countClassesAndPrototypes == 0 { 49 | return nil 50 | } 51 | 52 | // Collect all classes and prototypes from the current document 53 | currentClassesAndPrototypes := make(map[string]symbol.Symbol, countClassesAndPrototypes) 54 | source.WalkGlobalSymbols(func(s symbol.Symbol) error { 55 | currentClassesAndPrototypes[strings.ToUpper(s.Name())] = s 56 | return nil 57 | }, SymbolClass|SymbolPrototype) 58 | 59 | // grab all instances and prototypes 60 | allInstancesAndPrototypesByParentCount := 0 61 | p.symbolProvider.WalkGlobalSymbols(func(s symbol.Symbol) error { 62 | if s.Source() == "" { 63 | return nil 64 | } 65 | 66 | allInstancesAndPrototypesByParentCount++ 67 | return nil 68 | }, SymbolInstance|SymbolPrototype) 69 | 70 | allInstancesAndPrototypesByParent := make(map[string][]lsp.Location, allInstancesAndPrototypesByParentCount) 71 | p.symbolProvider.WalkGlobalSymbols(func(s symbol.Symbol) error { 72 | if s.Source() == "" { 73 | return nil 74 | } 75 | 76 | key := strings.ToUpper(s.(symbol.ProtoTypeOrInstance).Parent) 77 | if _, ok := currentClassesAndPrototypes[key]; !ok { 78 | return nil 79 | } 80 | items, ok := allInstancesAndPrototypesByParent[key] 81 | if !ok { 82 | items = make([]lsp.Location, 0, 10) 83 | } 84 | allInstancesAndPrototypesByParent[key] = append(items, getSymbolLocation(s)) 85 | return nil 86 | }, SymbolInstance|SymbolPrototype) 87 | 88 | result := make([]lsp.CodeLens, 0, len(currentClassesAndPrototypes)) 89 | for k, v := range currentClassesAndPrototypes { 90 | locations, ok := allInstancesAndPrototypesByParent[k] 91 | if !ok { 92 | continue 93 | } 94 | 95 | var txt string 96 | if len(locations) > 1 { 97 | txt = fmt.Sprintf("%d implementations", len(locations)) 98 | } else { 99 | txt = "1 implementation" 100 | } 101 | location := getSymbolLocation(v) 102 | lens := lsp.CodeLens{ 103 | Range: location.Range, 104 | Command: &lsp.Command{ 105 | Title: txt, 106 | Command: "editor.action.showReferences", 107 | Arguments: []any{getSymbolLocation(v).URI, location.Range.Start, locations}, 108 | }, 109 | } 110 | 111 | result = append(result, lens) 112 | } 113 | return result 114 | } 115 | 116 | type resolveType string 117 | 118 | const ( 119 | resolveReferences resolveType = "references" 120 | resolveImplementations resolveType = "implementations" 121 | ) 122 | 123 | type codeLensResolveMetadata struct { 124 | Command string 125 | SourceURI lsp.DocumentURI 126 | Type resolveType 127 | SourceLocation lsp.Location 128 | ReferenceParams lsp.ReferenceParams 129 | } 130 | 131 | func (p *codeLensHandler) provideReferences(ctx context.Context, source *ParseResult) []lsp.CodeLens { 132 | 133 | allSymbolsCount := 0 134 | source.WalkGlobalSymbols(func(s symbol.Symbol) error { 135 | allSymbolsCount++ 136 | return nil 137 | }, SymbolAll) 138 | 139 | if allSymbolsCount == 0 { 140 | return nil 141 | } 142 | 143 | allSymbols := make([]symbol.Symbol, 0, allSymbolsCount) 144 | source.WalkGlobalSymbols(func(s symbol.Symbol) error { 145 | allSymbols = append(allSymbols, s) 146 | return nil 147 | }, SymbolAll) 148 | 149 | result := make([]lsp.CodeLens, 0, len(allSymbols)) 150 | 151 | for _, v := range allSymbols { 152 | location := getSymbolLocation(v) 153 | 154 | meta := codeLensResolveMetadata{ 155 | SourceURI: location.URI, 156 | SourceLocation: location, 157 | Type: resolveReferences, 158 | Command: "editor.action.showReferences", 159 | ReferenceParams: lsp.ReferenceParams{ 160 | TextDocumentPositionParams: lsp.TextDocumentPositionParams{ 161 | TextDocument: lsp.TextDocumentIdentifier{ 162 | URI: lsp.DocumentURI(uri.File(v.Source())), 163 | }, 164 | Position: lsp.Position{ 165 | Line: uint32(v.Definition().Start.Line - 1), 166 | Character: uint32(v.Definition().Start.Column + 1), 167 | }, 168 | }}, 169 | } 170 | metaJson, err := json.Marshal(meta) 171 | if err != nil { 172 | continue 173 | } 174 | lens := lsp.CodeLens{ 175 | Command: nil, 176 | Range: location.Range, 177 | Data: metaJson, 178 | } 179 | 180 | result = append(result, lens) 181 | } 182 | 183 | return result 184 | } 185 | 186 | func newCodeLensHandler(h *LspWorkspace, symbolProvider SymbolProvider) *codeLensHandler { 187 | handler := &codeLensHandler{ 188 | lsp: h, 189 | symbolProvider: symbolProvider, 190 | } 191 | handler.providers = []codeLensProvider{ 192 | funcCodeLensProvider(handler.provideImplementations), 193 | funcCodeLensProvider(handler.provideReferences), 194 | } 195 | 196 | return handler 197 | } 198 | 199 | func (h *LspHandler) handleCodeLensResolve(req dls.RpcContext, params lsp.CodeLens) error { 200 | var meta codeLensResolveMetadata 201 | if err := json.Unmarshal(params.Data, &meta); err != nil { 202 | req.Reply(req.Context(), nil, err) 203 | return err 204 | } 205 | if meta.Type != resolveReferences { 206 | return req.Reply(req.Context(), nil, nil) 207 | } 208 | 209 | ws := h.GetWorkspace(meta.SourceURI) 210 | if ws == nil { 211 | return req.Reply(req.Context(), nil, nil) 212 | } 213 | 214 | refsCh := ws.getAllReferences(req.Context(), meta.ReferenceParams) 215 | var locations []lsp.Location 216 | 217 | for v := range refsCh { 218 | if locations == nil { 219 | locations = make([]lsp.Location, 0, 100) 220 | } 221 | locations = append(locations, v) 222 | } 223 | var txt string 224 | if len(locations) > 1 { 225 | txt = fmt.Sprintf("%d references", len(locations)) 226 | } else if len(locations) == 1 { 227 | txt = "1 references" 228 | } else { 229 | txt = "no references" 230 | } 231 | 232 | params.Command = &lsp.Command{ 233 | Title: txt, 234 | } 235 | if len(locations) != 0 { 236 | params.Command.Command = meta.Command 237 | params.Command.Arguments = []any{meta.SourceURI, meta.SourceLocation.Range.Start, locations} 238 | } 239 | return req.Reply(req.Context(), params, nil) 240 | } 241 | 242 | func (h *LspHandler) handleTextDocumentCodeLens(req dls.RpcContext, params lsp.CodeLensParams) error { 243 | ws := h.GetWorkspace(params.TextDocument.URI) 244 | if ws == nil { 245 | return req.Reply(req.Context(), nil, nil) 246 | } 247 | 248 | source := uriToFilename(params.TextDocument.URI) 249 | if source == "" { 250 | 251 | return req.Reply(req.Context(), nil, nil) 252 | } 253 | 254 | r, err := ws.parsedDocuments.GetCtx(req.Context(), source) 255 | if err != nil { 256 | req.Reply(req.Context(), nil, err) 257 | return err 258 | } 259 | locations := newCodeLensHandler(ws, ws.parsedDocuments).Provide(req.Context(), r) 260 | return req.Reply(req.Context(), locations, nil) 261 | } 262 | -------------------------------------------------------------------------------- /langserver/handle_document_completion.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | dls "github.com/kirides/DaedalusLanguageServer" 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 9 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 10 | ) 11 | 12 | type textDocumentCompletion struct { 13 | symbols SymbolProvider 14 | } 15 | 16 | func (tdc textDocumentCompletion) fieldsToCompletionItems(fields []symbol.Symbol) []lsp.CompletionItem { 17 | result := make([]lsp.CompletionItem, 0, len(fields)) 18 | for _, v := range fields { 19 | ci, err := completionItemFromSymbol(tdc.symbols, v) 20 | if err != nil { 21 | continue 22 | } 23 | result = append(result, ci) 24 | } 25 | return result 26 | } 27 | 28 | func (tdc textDocumentCompletion) getTypeFieldsAsCompletionItems(docs SymbolProvider, symbolName string, locals map[string]symbol.Symbol) ([]lsp.CompletionItem, error) { 29 | symName := strings.ToUpper(symbolName) 30 | sym, ok := locals[symName] 31 | if !ok { 32 | sym, ok = docs.LookupGlobalSymbol(symName, SymbolVariable|SymbolClass|SymbolInstance|SymbolPrototype) 33 | } 34 | 35 | if !ok { 36 | return []lsp.CompletionItem{}, nil 37 | } 38 | if clsSym, ok := sym.(symbol.Class); ok { 39 | return tdc.fieldsToCompletionItems(clsSym.Fields), nil 40 | } 41 | if protoSym, ok := sym.(symbol.ProtoTypeOrInstance); ok { 42 | return tdc.getTypeFieldsAsCompletionItems(docs, protoSym.Parent, locals) 43 | } 44 | if varSym, ok := sym.(symbol.Variable); ok { 45 | return tdc.getTypeFieldsAsCompletionItems(docs, varSym.Type, locals) 46 | } 47 | // no way for e.g. C_NPC arrays 48 | // if varSym, ok := sym.(ArrayVariableSymbol); ok { 49 | // return getTypeFieldsAsCompletionItems(docs, varSym.Type, locals) 50 | // } 51 | return []lsp.CompletionItem{}, nil 52 | } 53 | 54 | func (h *LspWorkspace) getTextDocumentCompletion(params *lsp.CompletionParams) ([]lsp.CompletionItem, error) { 55 | // TODO: split up further 56 | tdc := textDocumentCompletion{symbols: h.parsedDocuments} 57 | 58 | result := make([]lsp.CompletionItem, 0, 200) 59 | parsedDoc, err := h.parsedDocuments.Get(uriToFilename(params.TextDocument.URI)) 60 | 61 | partialIdentifier := "" 62 | 63 | if err == nil { 64 | doc := h.bufferManager.GetBuffer(uriToFilename(params.TextDocument.URI)) 65 | di := symbol.DefinitionIndex{Line: int(params.Position.Line), Column: int(params.Position.Character)} 66 | 67 | scci, found, err := getSignatureCompletions(params, h) 68 | if err != nil { 69 | h.logger.Debugf("signature completion error %v: ", err) 70 | } 71 | if found { 72 | return scci, nil 73 | } 74 | 75 | // dot-completion 76 | proto, _, err := doc.GetParentSymbolReference(params.Position) 77 | if err == nil && proto != "" { 78 | locals := parsedDoc.ScopedVariables(di) 79 | return tdc.getTypeFieldsAsCompletionItems(h.parsedDocuments, proto, locals) 80 | } 81 | partialIdentifier, _ = doc.GetIdentifier(params.Position) 82 | 83 | // locally scoped variables ordered at the top 84 | localSortIdx := 0 85 | for _, fn := range parsedDoc.Functions { 86 | if fn.BodyDefinition.InBBox(di) { 87 | for _, p := range fn.Parameters { 88 | if partialIdentifier != "" && !stringContainsAllAnywhere(p.Name(), partialIdentifier) { 89 | continue 90 | } 91 | ci, err := completionItemFromSymbol(h.parsedDocuments, p) 92 | if err != nil { 93 | continue 94 | } 95 | localSortIdx++ 96 | ci.Detail += " (parameter)" 97 | ci.SortText = fmt.Sprintf("%000d", localSortIdx) 98 | result = append(result, ci) 99 | } 100 | for _, p := range fn.LocalVariables { 101 | if partialIdentifier != "" && !stringContainsAllAnywhere(p.Name(), partialIdentifier) { 102 | continue 103 | } 104 | ci, err := completionItemFromSymbol(h.parsedDocuments, p) 105 | if err != nil { 106 | continue 107 | } 108 | localSortIdx++ 109 | ci.Detail += " (local)" 110 | ci.SortText = fmt.Sprintf("%000d", localSortIdx) 111 | result = append(result, ci) 112 | } 113 | break 114 | } 115 | } 116 | for _, fn := range parsedDoc.Instances { 117 | if fn.BodyDefinition.InBBox(di) { 118 | return tdc.getTypeFieldsAsCompletionItems(h.parsedDocuments, fn.Parent, map[string]symbol.Symbol{}) 119 | } 120 | } 121 | for _, fn := range parsedDoc.Prototypes { 122 | if fn.BodyDefinition.InBBox(di) { 123 | return tdc.getTypeFieldsAsCompletionItems(h.parsedDocuments, fn.Parent, map[string]symbol.Symbol{}) 124 | } 125 | } 126 | } 127 | 128 | symbols := make([]symbol.Symbol, 0, 200) 129 | h.parsedDocuments.WalkGlobalSymbols(func(s symbol.Symbol) error { 130 | if partialIdentifier != "" && !stringContainsAllAnywhere(s.Name(), partialIdentifier) { 131 | return nil 132 | } 133 | symbols = append(symbols, s) 134 | return nil 135 | }, SymbolAll) 136 | 137 | for _, v := range symbols { 138 | ci, err := completionItemFromSymbol(h.parsedDocuments, v) 139 | if err != nil { 140 | continue 141 | } 142 | result = append(result, ci) 143 | } 144 | 145 | return result, nil 146 | } 147 | 148 | func (h *LspHandler) handleTextDocumentCompletion(req dls.RpcContext, data lsp.CompletionParams) error { 149 | ws := h.GetWorkspace(data.TextDocument.URI) 150 | if ws == nil { 151 | return req.Reply(req.Context(), nil, nil) 152 | } 153 | items, err := ws.getTextDocumentCompletion(&data) 154 | req.ReplyEither(req.Context(), items, err) 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /langserver/handle_document_definition.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | dls "github.com/kirides/DaedalusLanguageServer" 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 9 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 10 | "go.lsp.dev/uri" 11 | ) 12 | 13 | func getSymbolLocation(symbol symbol.Symbol) lsp.Location { 14 | return lsp.Location{ 15 | URI: lsp.DocumentURI(uri.File(symbol.Source())), 16 | Range: lsp.Range{ 17 | Start: lsp.Position{ 18 | Character: uint32(symbol.Definition().Start.Column), 19 | Line: uint32(symbol.Definition().Start.Line - 1), 20 | }, 21 | End: lsp.Position{ 22 | Character: uint32(symbol.Definition().Start.Column + len(symbol.Name())), 23 | Line: uint32(symbol.Definition().Start.Line - 1), 24 | }, 25 | }} 26 | } 27 | 28 | func (h *LspWorkspace) getDotCompletedSymbol(params *lsp.TextDocumentPositionParams) (symbol.Symbol, error) { 29 | parsedDoc, err := h.parsedDocuments.Get(uriToFilename(params.TextDocument.URI)) 30 | 31 | if err != nil { 32 | return nil, fmt.Errorf("not found") 33 | 34 | } 35 | doc := h.bufferManager.GetBuffer(uriToFilename(params.TextDocument.URI)) 36 | di := symbol.DefinitionIndex{Line: int(params.Position.Line), Column: int(params.Position.Character)} 37 | 38 | // dot-completion 39 | proto, _, err := doc.GetParentSymbolReference(params.Position) 40 | if err != nil || proto == "" { 41 | identifier, err := doc.GetIdentifier(params.Position) 42 | if err != nil { 43 | return nil, fmt.Errorf("could not find identifier below cursor") 44 | } 45 | 46 | sym, ok := parsedDoc.LookupScopedVariable(di, identifier) 47 | if !ok { 48 | sym, ok = h.parsedDocuments.LookupGlobalSymbol(identifier, SymbolAll) 49 | } 50 | if !ok { 51 | return nil, fmt.Errorf("could not find local or global variable") 52 | } 53 | 54 | return sym, nil 55 | } 56 | 57 | fieldName, _ := doc.GetIdentifier(params.Position) 58 | 59 | parentIdentifier := strings.ToUpper(strings.TrimSpace(proto)) 60 | sym, ok := parsedDoc.LookupScopedVariable(di, parentIdentifier) 61 | if !ok { 62 | sym, ok = h.parsedDocuments.LookupGlobalSymbol(parentIdentifier, SymbolAll) 63 | } 64 | if !ok { 65 | return nil, fmt.Errorf("could not find parent for member access (PARENT.Member)") 66 | } 67 | 68 | switch s := sym.(type) { 69 | case symbol.Variable: 70 | sym, ok = h.parsedDocuments.LookupGlobalSymbol(strings.ToUpper(s.GetType()), SymbolClass|SymbolInstance|SymbolPrototype) 71 | for ok { 72 | switch s := sym.(type) { 73 | case symbol.ProtoTypeOrInstance: 74 | for _, f := range s.Fields { 75 | if strings.EqualFold(f.NameValue, fieldName) { 76 | return f, nil 77 | } 78 | } 79 | sym, ok = h.parsedDocuments.LookupGlobalSymbol(strings.ToUpper(s.Parent), SymbolClass|SymbolInstance|SymbolPrototype) 80 | case symbol.Class: 81 | for _, f := range s.Fields { 82 | if strings.EqualFold(f.Name(), fieldName) { 83 | return f, nil 84 | } 85 | } 86 | // still not found the actual definition, just return the class 87 | return s, nil 88 | } 89 | } 90 | } 91 | 92 | if !ok { 93 | return nil, fmt.Errorf("not found") 94 | } 95 | return sym, nil 96 | } 97 | 98 | func (h *LspHandler) handleTextDocumentDefinition(req dls.RpcContext, data lsp.TextDocumentPositionParams) error { 99 | ws := h.GetWorkspace(data.TextDocument.URI) 100 | if ws == nil { 101 | return req.Reply(req.Context(), nil, nil) 102 | } 103 | 104 | symbol, err := ws.lookUpSymbol(uriToFilename(data.TextDocument.URI), data.Position) 105 | if err != nil { 106 | sym, err := ws.getDotCompletedSymbol(&data) 107 | if err != nil { 108 | return req.Reply(req.Context(), nil, nil) 109 | } 110 | symbol = FoundSymbol{Symbol: sym, Location: FoundGlobal} 111 | } 112 | 113 | return req.Reply(req.Context(), getSymbolLocation(symbol.Symbol), nil) 114 | } 115 | -------------------------------------------------------------------------------- /langserver/handle_document_hover.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | 6 | dls "github.com/kirides/DaedalusLanguageServer" 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 8 | "github.com/kirides/DaedalusLanguageServer/javadoc" 9 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 10 | ) 11 | 12 | func (h *LspHandler) handleTextDocumentHover(req dls.RpcContext, data lsp.TextDocumentPositionParams) error { 13 | ws := h.GetWorkspace(data.TextDocument.URI) 14 | if ws == nil { 15 | return req.Reply(req.Context(), nil, nil) 16 | } 17 | 18 | found, err := ws.lookUpSymbol(uriToFilename(data.TextDocument.URI), data.Position) 19 | if err != nil { 20 | sym, err := ws.getDotCompletedSymbol(&data) 21 | if err != nil { 22 | return req.Reply(req.Context(), nil, nil) 23 | } 24 | found = FoundSymbol{Symbol: sym, Location: FoundGlobal} 25 | } 26 | h.LogDebug("Found Symbol for Hover: %s", found.Symbol.String()) 27 | 28 | codeSnippet := SymbolToReadableCode(ws.parsedDocuments, found.Symbol) 29 | codeSnippetFinal := codeSnippet 30 | 31 | if found.Location == FoundField { 32 | cnst, ok := found.Symbol.(symbol.Constant) 33 | ogVal := cnst.Value 34 | for ok { 35 | var cnst2 symbol.Symbol 36 | cnst2, ok = ws.parsedDocuments.LookupGlobalSymbol(strings.ToUpper(cnst.Value), SymbolConstant) 37 | if ok { 38 | cnst = cnst2.(symbol.Constant) 39 | } 40 | } 41 | if ogVal != cnst.Value { 42 | codeSnippetFinal = codeSnippet + " // " + cnst.Value 43 | } 44 | } 45 | 46 | return req.Reply(req.Context(), lsp.Hover{ 47 | Range: lsp.Range{ 48 | Start: data.Position, 49 | End: data.Position, 50 | }, 51 | Contents: lsp.MarkupContent{ 52 | Kind: lsp.Markdown, 53 | Value: strings.TrimSpace(javadoc.MarkdownSimple(found.Symbol) + "\n\n```daedalus\n" + codeSnippetFinal + "\n```"), 54 | }, 55 | }, nil) 56 | } 57 | -------------------------------------------------------------------------------- /langserver/handle_document_implementation.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | 6 | dls "github.com/kirides/DaedalusLanguageServer" 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 8 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 9 | ) 10 | 11 | func (h *LspHandler) handleTextDocumentImplementation(req dls.RpcContext, params lsp.ImplementationParams) error { 12 | ws := h.GetWorkspace(params.TextDocument.URI) 13 | if ws == nil { 14 | return req.Reply(req.Context(), nil, nil) 15 | } 16 | 17 | found, err := ws.lookUpSymbol(uriToFilename(params.TextDocument.URI), params.Position) 18 | if err != nil { 19 | req.Reply(req.Context(), nil, nil) 20 | h.LogDebug("Failed to lookup symbol. %v", err) 21 | return nil 22 | } 23 | 24 | if _, ok := found.Symbol.(symbol.Class); !ok { 25 | protoOrSymbol, ok := found.Symbol.(symbol.ProtoTypeOrInstance) 26 | if !ok || protoOrSymbol.IsInstance { 27 | req.Reply(req.Context(), nil, nil) 28 | return nil 29 | } 30 | } 31 | 32 | result := []lsp.Location{} 33 | ws.parsedDocuments.WalkGlobalSymbols(func(s symbol.Symbol) error { 34 | switch v := s.(type) { 35 | case symbol.ProtoTypeOrInstance: 36 | if strings.EqualFold(found.Symbol.Name(), v.Parent) { 37 | result = append(result, getSymbolLocation(v)) 38 | } 39 | } 40 | return nil 41 | }, SymbolInstance|SymbolPrototype) 42 | 43 | req.Reply(req.Context(), result, nil) 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /langserver/handle_document_inlayhints.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | dls "github.com/kirides/DaedalusLanguageServer" 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 9 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 10 | ) 11 | 12 | func (h *LspHandler) handleInlayHints(req dls.RpcContext, params lsp.InlayHintParams) error { 13 | ws := h.GetWorkspace(params.TextDocument.URI) 14 | if ws == nil { 15 | return req.Reply(req.Context(), nil, nil) 16 | } 17 | 18 | var hints []lsp.InlayHint 19 | 20 | if !h.config.InlayHints.Constants { 21 | // the only setting currently 22 | req.Reply(req.Context(), nil, nil) 23 | return nil 24 | } 25 | 26 | source := uriToFilename(params.TextDocument.URI) 27 | if source == "" { 28 | req.Reply(req.Context(), nil, nil) 29 | return nil 30 | } 31 | 32 | buf, err := ws.bufferManager.GetBufferCtx(req.Context(), source) 33 | if err != nil { 34 | req.Reply(req.Context(), nil, err) 35 | return err 36 | } 37 | 38 | parsed, err := ws.parsedDocuments.ParseSemanticsContentRange(req.Context(), source, string(buf), lsp.Range{}) 39 | if err != nil { 40 | req.Reply(req.Context(), nil, err) 41 | return err 42 | } 43 | 44 | bbox := symbol.Definition{ 45 | Start: symbol.DefinitionIndex{ 46 | Line: int(params.Range.Start.Line) + 1, 47 | Column: int(params.Range.Start.Character), 48 | }, 49 | End: symbol.DefinitionIndex{ 50 | Line: int(params.Range.End.Line) + 1, 51 | Column: int(params.Range.End.Character), 52 | }, 53 | } 54 | 55 | getSymbolFromToken := func(v token) string { 56 | name := v.Name() 57 | idxOpen := strings.Index(name, "[") 58 | if idxOpen == -1 { 59 | return name 60 | } 61 | actualName := name[:idxOpen] 62 | return actualName 63 | } 64 | 65 | onSymbolFound := func(v token, found FoundSymbol) { 66 | kind := lsp.InlayHintKind(0) 67 | if found.Location == FoundParameter { 68 | kind = lsp.Parameter 69 | } 70 | 71 | value := found.Location.String() 72 | if cnst, ok := found.Symbol.(symbol.Constant); ok { 73 | for ok { 74 | var cnst2 symbol.Symbol 75 | cnst2, ok = ws.parsedDocuments.LookupGlobalSymbol(strings.ToUpper(cnst.Value), SymbolConstant) 76 | if ok { 77 | cnst = cnst2.(symbol.Constant) 78 | } 79 | } 80 | value = ":" + cnst.Value 81 | 82 | } else if cnst, ok := found.Symbol.(symbol.ConstantArray); ok { 83 | name := v.Name() 84 | idxOpen := strings.Index(name, "[") 85 | idxClose := strings.Index(name, "]") 86 | if idxOpen > 0 && idxClose > idxOpen { 87 | index := name[idxOpen+1 : idxClose] 88 | if i, err := strconv.Atoi(index); err == nil && i >= 0 && i < len(cnst.Elements) { 89 | value = ":" + cnst.Elements[i].GetValue() 90 | } else { 91 | // probably a symbol, so lets find it! 92 | resolvedIndex := index 93 | ok := true 94 | for ok { 95 | var cnst2 symbol.Symbol 96 | cnst2, ok = ws.parsedDocuments.LookupGlobalSymbol(strings.ToUpper(resolvedIndex), SymbolConstant) 97 | if ok { 98 | resolvedIndex = cnst2.(symbol.Constant).Value 99 | } 100 | } 101 | if i, err := strconv.Atoi(resolvedIndex); err == nil && i >= 0 && i < len(cnst.Elements) { 102 | value = ":" + cnst.Elements[i].GetValue() 103 | } 104 | } 105 | } 106 | if value == "" { 107 | value = ":" + cnst.Value // atleast show preview for constant 108 | } 109 | } else { 110 | // only show value from constants for now 111 | _ = value 112 | return 113 | } 114 | hints = append(hints, lsp.InlayHint{ 115 | Position: &lsp.Position{ 116 | Line: uint32(v.Definition().End.Line - 1), 117 | Character: uint32(v.Definition().End.Column), 118 | }, 119 | PaddingLeft: true, 120 | PaddingRight: false, 121 | Kind: kind, 122 | Label: []lsp.InlayHintLabelPart{ 123 | {Value: value}, 124 | }, 125 | }) 126 | } 127 | 128 | for _, v := range parsed.GlobalIdentifiers { 129 | if bbox.InBBox(v.definition.Start) || bbox.InBBox(v.definition.End) { 130 | found, ok := parsed.FindScopedVariableDeclaration(v.Definition().Start, getSymbolFromToken(v)) 131 | if !ok { 132 | var symb symbol.Symbol 133 | symb, ok = ws.parsedDocuments.LookupGlobalSymbol(strings.ToUpper(getSymbolFromToken(v)), SymbolAll) 134 | found = FoundSymbol{symb, FoundGlobal} 135 | } 136 | if !ok { 137 | continue 138 | } 139 | onSymbolFound(v, found) 140 | } 141 | } 142 | 143 | req.Reply(req.Context(), hints, nil) 144 | return nil 145 | } 146 | -------------------------------------------------------------------------------- /langserver/handle_document_semantictokens.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "sort" 5 | "strings" 6 | 7 | dls "github.com/kirides/DaedalusLanguageServer" 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 9 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 10 | ) 11 | 12 | type SemanticTokensLegend struct { 13 | TokenTypes []lsp.SemanticTokenTypes `json:"tokenTypes"` 14 | TokenModifiers []lsp.SemanticTokenModifiers `json:"tokenModifiers"` 15 | } 16 | type SemanticTokensOptions struct { 17 | Legend SemanticTokensLegend `json:"legend"` 18 | Range bool `json:"range,omitempty"` 19 | Full bool `json:"full,omitempty"` 20 | } 21 | 22 | var ( 23 | semanticTypes = [...]lsp.SemanticTokenTypes{ 24 | "namespace", "type", "class", "enum", "interface", 25 | "struct", "typeParameter", "parameter", "variable", "property", "enumMember", 26 | "event", "function", "method", "macro", "keyword", "modifier", "comment", 27 | "string", "number", "regexp", "operator", 28 | } 29 | semanticModifiers = [...]lsp.SemanticTokenModifiers{ 30 | "declaration", "definition", "readonly", "static", 31 | "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", 32 | } 33 | ) 34 | 35 | func SemanticTypes() []lsp.SemanticTokenTypes { 36 | return semanticTypes[:] 37 | } 38 | 39 | func SemanticTypeFor(t lsp.SemanticTokenTypes) int { 40 | for i, v := range SemanticTypes() { 41 | if v == t { 42 | return i 43 | } 44 | } 45 | return -1 46 | } 47 | 48 | func SemanticModifiers() []lsp.SemanticTokenModifiers { 49 | return semanticModifiers[:] 50 | } 51 | func SemanticModifiersFor(mods ...lsp.SemanticTokenModifiers) uint32 { 52 | result := uint32(0) 53 | for _, v := range mods { 54 | for j, m := range SemanticModifiers() { 55 | if v == m { 56 | result |= 1 << j 57 | } 58 | } 59 | } 60 | return result 61 | } 62 | 63 | type token interface { 64 | Name() string 65 | Definition() symbol.Definition 66 | } 67 | 68 | type semanticTokensHandler struct { 69 | parsedDocuments *parseResultsManager 70 | } 71 | 72 | func (s semanticTokensHandler) semanticsForToken(t token) (int, uint32) { 73 | switch s := t.(type) { 74 | case symbol.Class: 75 | return SemanticTypeFor(lsp.SemanticTokenClass), 0 76 | case symbol.ProtoTypeOrInstance: 77 | mod := uint32(0) 78 | if !s.IsInstance { 79 | mod |= SemanticModifiersFor(lsp.SemanticTokenModifierAbstract) 80 | } 81 | return SemanticTypeFor(lsp.SemanticTokenClass), mod 82 | case FunctionWithIdentifiers, symbol.Function: 83 | return SemanticTypeFor(lsp.SemanticTokenFunction), 0 84 | case symbol.Constant, symbol.ConstantArray: 85 | return SemanticTypeFor(lsp.SemanticTokenVariable), SemanticModifiersFor(lsp.SemanticTokenModifierReadonly) 86 | case symbol.Variable, symbol.ArrayVariable: 87 | return SemanticTypeFor(lsp.SemanticTokenVariable), 0 88 | } 89 | return -1, 0 90 | } 91 | 92 | func (h *semanticTokensHandler) getSemanticTokens(r *SemanticParseResult, area *lsp.Range) []uint32 { 93 | type item struct { 94 | token token 95 | t uint32 96 | m uint32 97 | } 98 | syms := make([]item, 0, r.CountSymbols()) 99 | 100 | onIdentifier := func(id *Identifier) (t, m uint32, rok bool) { 101 | var identified symbol.Symbol 102 | isParam := false 103 | 104 | r.WalkScopedVariables(id.Definition().Start, func(t symbol.Symbol, isp bool) bool { 105 | if strings.EqualFold(t.Name(), id.Name()) { 106 | identified = t 107 | isParam = isp 108 | return false 109 | } 110 | return true 111 | }) 112 | 113 | if identified != nil { 114 | if !isParam { 115 | switch identified.(type) { 116 | case symbol.Constant, symbol.ConstantArray: 117 | default: 118 | // do not highlight local VARIABLEs 119 | return 120 | } 121 | } 122 | if typ, mod := h.semanticsForToken(identified); typ != -1 { 123 | t = uint32(typ) 124 | if isParam { 125 | typ = SemanticTypeFor(lsp.SemanticTokenParameter) 126 | if typ != -1 { 127 | t = uint32(typ) 128 | } 129 | } 130 | m = mod 131 | rok = true 132 | } 133 | } else { 134 | found, ok := h.parsedDocuments.LookupGlobalSymbol(strings.ToUpper(id.Name()), SymbolAll) 135 | if !ok { 136 | return 137 | } 138 | if typ, mod := h.semanticsForToken(found); typ != -1 { 139 | t = uint32(typ) 140 | // global symbol, mark static 141 | m = mod | SemanticModifiersFor(lsp.SemanticTokenModifierStatic) 142 | rok = true 143 | } 144 | } 145 | return 146 | } 147 | 148 | r.WalkSymbols(func(s token) error { 149 | itm := item{ 150 | token: s, 151 | m: SemanticModifiersFor(lsp.SemanticTokenModifierDeclaration), 152 | } 153 | 154 | switch s := s.(type) { 155 | case Identifier: 156 | t, m, ok := onIdentifier(&s) 157 | if ok { 158 | itm.t, itm.m = t, m 159 | syms = append(syms, itm) 160 | } 161 | case symbol.Class: 162 | if t := SemanticTypeFor(lsp.SemanticTokenClass); t != -1 { 163 | itm.t = uint32(t) 164 | syms = append(syms, itm) 165 | } 166 | case symbol.ProtoTypeOrInstance: 167 | if t := SemanticTypeFor(lsp.SemanticTokenClass); t != -1 { 168 | mod := uint32(0) 169 | if !s.IsInstance { 170 | mod |= SemanticModifiersFor(lsp.SemanticTokenModifierAbstract) 171 | } 172 | itm.t = uint32(t) 173 | itm.m = mod 174 | syms = append(syms, itm) 175 | } 176 | case FunctionWithIdentifiers: 177 | if t := SemanticTypeFor(lsp.SemanticTokenFunction); t != -1 { 178 | itm.t = uint32(t) 179 | // ignore functions, they're handled without semantics 180 | // syms = append(syms, itm) 181 | 182 | if t := SemanticTypeFor(lsp.SemanticTokenParameter); t != -1 { 183 | for _, v := range s.Parameters { 184 | syms = append(syms, item{ 185 | token: v, 186 | t: uint32(t), 187 | m: SemanticModifiersFor(lsp.SemanticTokenModifierDeclaration), 188 | }) 189 | } 190 | } 191 | if t := SemanticTypeFor(lsp.SemanticTokenVariable); t != -1 { 192 | for _, v := range s.LocalVariables { 193 | switch v.(type) { 194 | // We don't highlight local variables 195 | // case symbol.Variable, symbol.ArrayVariable: 196 | // syms = append(syms, item{ 197 | // token: v, 198 | // t: uint32(t), 199 | // m: SemanticModifiersFor(lsp.SemanticTokenModifierDeclaration), 200 | // }) 201 | case symbol.Constant, symbol.ConstantArray: 202 | syms = append(syms, item{ 203 | token: v, 204 | t: uint32(t), 205 | m: SemanticModifiersFor(lsp.SemanticTokenModifierDeclaration, lsp.SemanticTokenModifierReadonly), 206 | }) 207 | } 208 | } 209 | } 210 | for _, id := range s.Identifiers { 211 | t, m, ok := onIdentifier(&id) 212 | if ok { 213 | syms = append(syms, item{ 214 | token: id, 215 | t: t, 216 | m: m, 217 | }) 218 | } 219 | } 220 | } 221 | case symbol.Constant, symbol.ConstantArray: 222 | if t := SemanticTypeFor(lsp.SemanticTokenVariable); t != -1 { 223 | itm.t = uint32(t) 224 | itm.m |= SemanticModifiersFor(lsp.SemanticTokenModifierReadonly, lsp.SemanticTokenModifierStatic) 225 | syms = append(syms, itm) 226 | } 227 | case symbol.Variable, symbol.ArrayVariable: 228 | if t := SemanticTypeFor(lsp.SemanticTokenVariable); t != -1 { 229 | itm.t = uint32(t) 230 | itm.m |= SemanticModifiersFor(lsp.SemanticTokenModifierStatic) 231 | syms = append(syms, itm) 232 | } 233 | } 234 | 235 | return nil 236 | }) 237 | 238 | sort.Slice(syms, func(i, j int) bool { 239 | if syms[i].token.Definition().Start.Line != syms[j].token.Definition().Start.Line { 240 | return syms[i].token.Definition().Start.Line < syms[j].token.Definition().Start.Line 241 | } 242 | return syms[i].token.Definition().Start.Column < syms[j].token.Definition().Start.Column 243 | }) 244 | 245 | x := make([]uint32, 5*len(syms)) 246 | var j int 247 | var last token 248 | for i := 0; i < len(syms); i++ { 249 | item := syms[i] 250 | 251 | start := item.token.Definition().Start.Line - 1 252 | if j == 0 { 253 | x[0] = uint32(syms[0].token.Definition().Start.Line - 1) 254 | } else { 255 | x[j] = uint32(start - (last.Definition().Start.Line - 1)) 256 | } 257 | x[j+1] = uint32(item.token.Definition().Start.Column) 258 | if j > 0 && x[j] == 0 { 259 | x[j+1] = uint32(item.token.Definition().Start.Column - last.Definition().Start.Column) 260 | } 261 | x[j+2] = uint32(len(item.token.Name())) 262 | x[j+3] = item.t 263 | x[j+4] = item.m 264 | j += 5 265 | last = item.token 266 | } 267 | 268 | return x[:j] 269 | } 270 | 271 | func (h *LspHandler) handleSemanticTokensFull(req dls.RpcContext, params lsp.SemanticTokensParams) error { 272 | ws := h.GetWorkspace(params.TextDocument.URI) 273 | if ws == nil { 274 | return req.Reply(req.Context(), nil, nil) 275 | } 276 | handler := &semanticTokensHandler{parsedDocuments: ws.parsedDocuments} 277 | 278 | source := uriToFilename(params.TextDocument.URI) 279 | if source == "" { 280 | req.Reply(req.Context(), nil, nil) 281 | return nil 282 | } 283 | 284 | buf, err := ws.bufferManager.GetBufferCtx(req.Context(), source) 285 | if err != nil { 286 | req.Reply(req.Context(), nil, err) 287 | return err 288 | } 289 | 290 | parsed, err := handler.parsedDocuments.ParseSemanticsContentRange(req.Context(), source, string(buf), lsp.Range{}) 291 | if err != nil { 292 | req.Reply(req.Context(), nil, err) 293 | return err 294 | } 295 | 296 | result := &lsp.SemanticTokens{ 297 | Data: handler.getSemanticTokens(parsed, nil), 298 | } 299 | 300 | // h.LogInfo("MethodSemanticTokensFull:\n%v", result) 301 | req.Reply(req.Context(), result, nil) 302 | return nil 303 | } 304 | 305 | func (h *LspHandler) handleSemanticTokensRange(req dls.RpcContext, params lsp.SemanticTokensRangeParams) error { 306 | ws := h.GetWorkspace(params.TextDocument.URI) 307 | if ws == nil { 308 | return req.Reply(req.Context(), nil, nil) 309 | } 310 | 311 | handler := &semanticTokensHandler{parsedDocuments: ws.parsedDocuments} 312 | 313 | source := uriToFilename(params.TextDocument.URI) 314 | if source == "" { 315 | req.Reply(req.Context(), nil, nil) 316 | return nil 317 | } 318 | 319 | buf, err := ws.bufferManager.GetBufferCtx(req.Context(), source) 320 | if err != nil { 321 | req.Reply(req.Context(), nil, err) 322 | return err 323 | } 324 | 325 | parsed, err := handler.parsedDocuments.ParseSemanticsContentRange(req.Context(), source, string(buf), params.Range) 326 | if err != nil { 327 | req.Reply(req.Context(), nil, err) 328 | return err 329 | } 330 | 331 | result := &lsp.SemanticTokens{ 332 | Data: handler.getSemanticTokens(parsed, nil), 333 | } 334 | 335 | // h.LogInfo("MethodSemanticTokensFull:\n%v", result) 336 | req.Reply(req.Context(), result, nil) 337 | return nil 338 | } 339 | -------------------------------------------------------------------------------- /langserver/handle_document_semantictokens_test.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "context" 5 | "slices" 6 | "testing" 7 | 8 | "github.com/kirides/DaedalusLanguageServer/protocol" 9 | ) 10 | 11 | func TestSemanticForParameterVariable(t *testing.T) { 12 | script := `func void FuncName(var int CurrentLevel) { 13 | CurrentLevel = ADDONWORLD_ZEN; 14 | };` 15 | 16 | m := newParseResultsManager(nopLogger{}) 17 | fullArea := protocol.Range{Start: protocol.Position{Line: 0, Character: 0}, End: protocol.Position{Line: 999, Character: 999}} 18 | 19 | result, _ := m.ParseSemanticsContentDataTypesRange(context.Background(), "C:\\temp", script, fullArea, DataAll) 20 | h := semanticTokensHandler{parsedDocuments: m} 21 | 22 | tokens := h.getSemanticTokens(result, &fullArea) 23 | 24 | if !slices.Equal(tokens, []uint32{0x1, 0x1, 0xc, 0x7, 0x0}) { 25 | t.Fatalf("Expected exactly one set of semantic tokens") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /langserver/handle_document_symbol.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | 6 | dls "github.com/kirides/DaedalusLanguageServer" 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 8 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 9 | ) 10 | 11 | type textDocumentDocumentSymbol struct{} 12 | 13 | func (ds textDocumentDocumentSymbol) getDocumentSymbol(s symbol.Symbol) lsp.DocumentSymbol { 14 | rn := getSymbolLocation(s).Range 15 | return lsp.DocumentSymbol{ 16 | Name: s.Name(), 17 | Kind: getSymbolKind(s), 18 | Range: rn, 19 | SelectionRange: rn, 20 | } 21 | } 22 | 23 | func (ds textDocumentDocumentSymbol) collectDocumentSymbols(result []lsp.DocumentSymbol, s symbol.Symbol) []lsp.DocumentSymbol { 24 | mainSymb := ds.getDocumentSymbol(s) 25 | 26 | if cls, ok := s.(symbol.Class); ok { 27 | for _, v := range cls.Fields { 28 | si := ds.getDocumentSymbol(v) 29 | mainSymb.Children = append(mainSymb.Children, si) 30 | } 31 | mainSymb.Range = lsp.Range{ 32 | Start: mainSymb.Range.Start, 33 | End: lsp.Position{ 34 | Line: uint32(cls.BodyDefinition.End.Line) - 1, 35 | Character: uint32(cls.BodyDefinition.End.Column), 36 | }, 37 | } 38 | } else if cls, ok := s.(symbol.ProtoTypeOrInstance); ok { 39 | mainSymb.Name = mainSymb.Name + " (" + cls.Parent + ")" 40 | for _, v := range cls.Fields { 41 | si := ds.getDocumentSymbol(v) 42 | mainSymb.Children = append(mainSymb.Children, si) 43 | } 44 | mainSymb.Range = lsp.Range{ 45 | Start: mainSymb.Range.Start, 46 | End: lsp.Position{ 47 | Line: uint32(cls.BodyDefinition.End.Line) - 1, 48 | Character: uint32(cls.BodyDefinition.End.Column), 49 | }, 50 | } 51 | } else if cls, ok := s.(symbol.Function); ok { 52 | mainSymb.Name = cls.ReturnType + " " + mainSymb.Name + "(" 53 | for _, v := range cls.Parameters { 54 | mainSymb.Name += v.Name() + ", " 55 | } 56 | mainSymb.Name = strings.TrimSuffix(mainSymb.Name, ", ") 57 | mainSymb.Name += ")" 58 | mainSymb.Range = lsp.Range{ 59 | Start: mainSymb.Range.Start, 60 | End: lsp.Position{ 61 | Line: uint32(cls.BodyDefinition.End.Line) - 1, 62 | Character: uint32(cls.BodyDefinition.End.Column), 63 | }, 64 | } 65 | } 66 | 67 | result = append(result, mainSymb) 68 | return result 69 | } 70 | 71 | func (h *LspHandler) handleDocumentSymbol(req dls.RpcContext, params lsp.DocumentSymbolParams) error { 72 | ws := h.GetWorkspace(params.TextDocument.URI) 73 | if ws == nil { 74 | return req.Reply(req.Context(), nil, nil) 75 | } 76 | source := uriToFilename(params.TextDocument.URI) 77 | if source == "" { 78 | req.Reply(req.Context(), nil, nil) 79 | return nil 80 | } 81 | 82 | ds := textDocumentDocumentSymbol{} 83 | 84 | r, err := ws.parsedDocuments.GetCtx(req.Context(), source) 85 | if err != nil { 86 | req.Reply(req.Context(), nil, err) 87 | return err 88 | } 89 | numSymbols := r.CountSymbols() 90 | result := make([]lsp.DocumentSymbol, 0, numSymbols) 91 | 92 | err = r.WalkGlobalSymbols(func(s symbol.Symbol) error { 93 | if req.Context().Err() != nil { 94 | h.logger.Debugf("request cancelled", "method", req.Request().Method()) 95 | return req.Context().Err() 96 | } 97 | result = ds.collectDocumentSymbols(result, s) 98 | return nil 99 | }, SymbolAll) 100 | if err != nil { 101 | return nil 102 | } 103 | 104 | req.Reply(req.Context(), result, nil) 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /langserver/handle_document_typeDefinition.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | 6 | dls "github.com/kirides/DaedalusLanguageServer" 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 8 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 9 | ) 10 | 11 | func (h *LspHandler) handleTextDocumentTypeDefinition(req dls.RpcContext, data lsp.TextDocumentPositionParams) error { 12 | ws := h.GetWorkspace(data.TextDocument.URI) 13 | if ws == nil { 14 | return req.Reply(req.Context(), nil, nil) 15 | } 16 | 17 | ssymbol, err := ws.lookUpSymbol(uriToFilename(data.TextDocument.URI), data.Position) 18 | if err != nil { 19 | sym, err := ws.getDotCompletedSymbol(&data) 20 | if err != nil { 21 | return req.Reply(req.Context(), nil, nil) 22 | } 23 | ssymbol = FoundSymbol{Symbol: sym, Location: FoundGlobal} 24 | } 25 | if err != nil { 26 | return req.Reply(req.Context(), nil, nil) 27 | } 28 | 29 | switch s := ssymbol.Symbol.(type) { 30 | case symbol.ProtoTypeOrInstance, symbol.Class, symbol.Function: 31 | return req.Reply(req.Context(), getSymbolLocation(s), nil) 32 | } 33 | 34 | if getTyper, ok := ssymbol.Symbol.(interface{ GetType() string }); ok { 35 | typ := getTyper.GetType() 36 | sym, ok := ws.parsedDocuments.LookupGlobalSymbol(strings.ToUpper(typ), SymbolClass|SymbolInstance|SymbolPrototype) 37 | if ok { 38 | return req.Reply(req.Context(), getSymbolLocation(sym), nil) 39 | } 40 | } 41 | 42 | return req.Reply(req.Context(), nil, nil) 43 | } 44 | -------------------------------------------------------------------------------- /langserver/handle_references.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os" 7 | "regexp" 8 | "time" 9 | 10 | dls "github.com/kirides/DaedalusLanguageServer" 11 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 12 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 13 | "go.lsp.dev/uri" 14 | ) 15 | 16 | var rxBlockComment = regexp.MustCompile(`(?s)\/\*.*?\*\/`) 17 | 18 | func (h *LspWorkspace) getAllReferences(req context.Context, params lsp.ReferenceParams) <-chan lsp.Location { 19 | resultCh := make(chan lsp.Location, 1) 20 | 21 | doc := uriToFilename(params.TextDocument.URI) 22 | if doc == "" { 23 | close(resultCh) 24 | return resultCh 25 | } 26 | 27 | content, err := h.bufferManager.GetBufferCtx(req, doc) 28 | if err != nil { 29 | close(resultCh) 30 | return resultCh 31 | } 32 | word, pos := content.GetWordRangeAtPosition(params.Position) 33 | if word == "" { 34 | close(resultCh) 35 | return resultCh 36 | } 37 | 38 | parsed, err := h.parsedDocuments.GetCtx(req, doc) 39 | if err != nil { 40 | close(resultCh) 41 | return resultCh 42 | } 43 | wordDefIndex := symbol.DefinitionIndex{ 44 | Line: int(pos.Start.Line) + 1, 45 | Column: int(pos.Start.Character), 46 | } 47 | 48 | inScope := false 49 | bbox := symbol.NewDefinition(0, 0, 999999, 999999) 50 | 51 | if _, ok := parsed.LookupScopedVariable(wordDefIndex, word); ok { 52 | scopeSymbol, ok := parsed.FindContainingScope(wordDefIndex) 53 | if ok { 54 | inScope = true 55 | switch v := scopeSymbol.(type) { 56 | case symbol.Function: 57 | bbox = v.BodyDefinition 58 | case symbol.ProtoTypeOrInstance: 59 | bbox = v.BodyDefinition 60 | case symbol.Class: 61 | bbox = v.BodyDefinition 62 | } 63 | } 64 | } 65 | 66 | go func() { 67 | buffer := bufferPool.Get().(*bytes.Buffer) 68 | defer bufferPool.Put(buffer) 69 | 70 | rxWord := regexp.MustCompile(`(?i)\b` + regexp.QuoteMeta(word) + `\b`) 71 | 72 | getLineCol := func(buf *bytes.Buffer, start, end int) ([]byte, int, int) { 73 | line := 1 74 | col := 0 75 | lineStart := 0 76 | for i := 0; i < buf.Len() && i < start; i++ { 77 | if buf.Bytes()[i] == '\n' { 78 | line++ 79 | col = 0 80 | lineStart = i + 1 81 | } else { 82 | col++ 83 | } 84 | } 85 | return buf.Bytes()[lineStart : lineStart+col], line, col 86 | } 87 | defer close(resultCh) 88 | 89 | isCommentOrString := func(buf *bytes.Buffer, blockComments [][]int, segment []byte, idxInSegment int, startEnd []int) bool { 90 | segment = bytes.TrimSpace(segment) 91 | if bytes.HasPrefix(segment, []byte("//")) { 92 | // definitely inside comment 93 | return true 94 | } 95 | 96 | isString := false 97 | for i := 0; i < len(segment); i++ { 98 | if segment[i] == '"' { 99 | isString = !isString 100 | continue 101 | } 102 | if !isString && bytes.HasPrefix(segment[i:], []byte("//")) { 103 | // is inline comment at the end somewhere 104 | return true 105 | } 106 | } 107 | if isString { 108 | // if it's not a comment and still in a string, then it's not a reference 109 | return true 110 | } 111 | 112 | for _, block := range blockComments { 113 | if startEnd[0] >= block[0] && startEnd[1] <= block[1] { 114 | // definitely inside block-comment 115 | return true 116 | } 117 | } 118 | return false 119 | } 120 | 121 | if inScope { 122 | buffer.WriteString(string(content)) 123 | indices := rxWord.FindAllIndex(buffer.Bytes(), -1) 124 | var blockComments [][]int 125 | if len(indices) > 0 { 126 | blockComments = rxBlockComment.FindAllIndex(buffer.Bytes(), -1) 127 | } 128 | 129 | for _, startEnd := range indices { 130 | segment, line, col := getLineCol(buffer, startEnd[0], startEnd[1]) 131 | if line > bbox.End.Line { 132 | return 133 | } 134 | 135 | if !bbox.InBBox(symbol.DefinitionIndex{Line: line, Column: col}) { 136 | continue 137 | } 138 | 139 | if wordDefIndex.Line == line+1 && wordDefIndex.Column == col { 140 | // ignore itself 141 | continue 142 | } 143 | 144 | if isCommentOrString(buffer, blockComments, segment, col, startEnd) { 145 | continue 146 | } 147 | 148 | resultCh <- lsp.Location{ 149 | URI: lsp.DocumentURI(uri.File(doc)), 150 | Range: lsp.Range{ 151 | Start: lsp.Position{ 152 | Character: uint32(col), 153 | Line: uint32(line - 1), 154 | }, 155 | End: lsp.Position{ 156 | Character: uint32(col + len(word)), 157 | Line: uint32(line - 1), 158 | }, 159 | }} 160 | } 161 | } else { 162 | for k := range h.parsedDocuments.parseResults { 163 | if req.Err() != nil { 164 | return 165 | } 166 | 167 | var lastMod time.Time 168 | if stat, err := os.Stat(k); err == nil { 169 | lastMod = stat.ModTime() 170 | } else { 171 | lastMod = time.Now() 172 | } 173 | err := h.parsedDocuments.LoadFileBuffer(req, k, buffer) 174 | if err != nil { 175 | continue 176 | } 177 | 178 | indices := rxWord.FindAllIndex(buffer.Bytes(), -1) 179 | 180 | var blockComments [][]int 181 | if len(indices) > 0 { 182 | blockComments = rxBlockComment.FindAllIndex(buffer.Bytes(), -1) 183 | } 184 | 185 | var parsed *ParseResult 186 | for _, startEnd := range indices { 187 | segment, line, col := getLineCol(buffer, startEnd[0], startEnd[1]) 188 | 189 | if k == doc { 190 | if wordDefIndex.Line == line && wordDefIndex.Column == col { 191 | // ignore itself 192 | continue 193 | } 194 | } 195 | 196 | if isCommentOrString(buffer, blockComments, segment, col, startEnd) { 197 | continue 198 | } 199 | 200 | if parsed == nil { 201 | parsed = h.parsedDocuments.ParseScript(".", buffer.String(), lastMod) 202 | } 203 | if parsed != nil { 204 | if _, ok := parsed.LookupScopedVariable(symbol.DefinitionIndex{ 205 | Line: line, 206 | Column: col, 207 | }, word); ok { 208 | // we're looking for a GLOBAL variable, so skip any locals with the same name 209 | continue 210 | } 211 | } 212 | 213 | resultCh <- lsp.Location{ 214 | URI: lsp.DocumentURI(uri.File(k)), 215 | Range: lsp.Range{ 216 | Start: lsp.Position{ 217 | Character: uint32(col), 218 | Line: uint32(line - 1), 219 | }, 220 | End: lsp.Position{ 221 | Character: uint32(col + len(word)), 222 | Line: uint32(line - 1), 223 | }, 224 | }} 225 | } 226 | } 227 | } 228 | }() 229 | 230 | return resultCh 231 | } 232 | 233 | func (h *LspHandler) handleTextDocumentReferences(req dls.RpcContext, params lsp.ReferenceParams) error { 234 | ws := h.GetWorkspace(params.TextDocument.URI) 235 | if ws == nil { 236 | return req.Reply(req.Context(), nil, nil) 237 | } 238 | var result []lsp.Location 239 | 240 | refsCh := ws.getAllReferences(req.Context(), params) 241 | for v := range refsCh { 242 | if result == nil { 243 | result = make([]lsp.Location, 0, 100) 244 | } 245 | result = append(result, v) 246 | } 247 | 248 | return req.Reply(req.Context(), result, nil) 249 | } 250 | -------------------------------------------------------------------------------- /langserver/handle_signature_help.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | dls "github.com/kirides/DaedalusLanguageServer" 8 | "github.com/kirides/DaedalusLanguageServer/javadoc" 9 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 10 | ) 11 | 12 | func (h *LspWorkspace) getSignatureInfo(ctx context.Context, params *lsp.TextDocumentPositionParams) (lsp.SignatureHelp, error) { 13 | doc := h.bufferManager.GetBuffer(uriToFilename(params.TextDocument.URI)) 14 | fnCtx, err := getFunctionCallContext(doc, h.parsedDocuments, params.Position) 15 | if err != nil { 16 | return lsp.SignatureHelp{}, err 17 | } 18 | 19 | fn := fnCtx.Function 20 | 21 | var fnParams []lsp.ParameterInformation 22 | for _, p := range fn.Parameters { 23 | doc := javadoc.FindParam(fn.Documentation(), p.Name()) 24 | var mdDoc interface{} 25 | if doc != "" { 26 | mdDoc = &lsp.MarkupContent{ 27 | Kind: lsp.Markdown, 28 | Value: fmt.Sprintf("**%s** - *%s*", p.Name(), javadoc.RemoveTokens(doc)), 29 | } 30 | } 31 | fnParams = append(fnParams, lsp.ParameterInformation{ 32 | Label: p.String(), 33 | Documentation: mdDoc, 34 | }) 35 | } 36 | 37 | return lsp.SignatureHelp{ 38 | Signatures: []lsp.SignatureInformation{ 39 | { 40 | Documentation: lsp.MarkupContent{ 41 | Kind: lsp.Markdown, 42 | Value: javadoc.MarkdownSimple(fn), 43 | }, 44 | Label: fn.String(), 45 | Parameters: fnParams, 46 | }, 47 | }, 48 | ActiveParameter: uint32(fnCtx.ParamIdx), 49 | ActiveSignature: 0, 50 | }, nil 51 | } 52 | 53 | func (h *LspHandler) handleTextDocumentSignatureHelp(req dls.RpcContext, data lsp.TextDocumentPositionParams) error { 54 | ws := h.GetWorkspace(data.TextDocument.URI) 55 | if ws == nil { 56 | return req.Reply(req.Context(), nil, nil) 57 | } 58 | result, err := ws.getSignatureInfo(req.Context(), &data) 59 | if err != nil { 60 | return req.Reply(req.Context(), nil, nil) 61 | } 62 | return req.Reply(req.Context(), result, nil) 63 | } 64 | -------------------------------------------------------------------------------- /langserver/handle_workspace_symbol.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | 6 | dls "github.com/kirides/DaedalusLanguageServer" 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 8 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 9 | ) 10 | 11 | type workspaceSymbol struct{} 12 | 13 | func (w workspaceSymbol) getSymbolInformation(s symbol.Symbol) lsp.SymbolInformation { 14 | return lsp.SymbolInformation{ 15 | Name: s.Name(), 16 | Kind: getSymbolKind(s), 17 | Location: getSymbolLocation(s), 18 | } 19 | } 20 | 21 | func (w workspaceSymbol) collectWorkspaceSymbols(result []lsp.SymbolInformation, s symbol.Symbol) []lsp.SymbolInformation { 22 | mainSymb := w.getSymbolInformation(s) 23 | 24 | if cls, ok := s.(symbol.Class); ok { 25 | for _, v := range cls.Fields { 26 | si := w.getSymbolInformation(v) 27 | si.ContainerName = s.Name() 28 | result = append(result, si) 29 | } 30 | } else if cls, ok := s.(symbol.ProtoTypeOrInstance); ok { 31 | mainSymb.Name = mainSymb.Name + " (" + cls.Parent + ")" 32 | for _, v := range cls.Fields { 33 | si := w.getSymbolInformation(v) 34 | si.ContainerName = s.Name() 35 | result = append(result, si) 36 | } 37 | } 38 | result = append(result, mainSymb) 39 | return result 40 | } 41 | 42 | func (h *LspHandler) handleWorkspaceSymbol(req dls.RpcContext, params lsp.WorkspaceSymbolParams) error { 43 | 44 | w := workspaceSymbol{} 45 | var result []lsp.SymbolInformation 46 | 47 | for _, ws := range h.workspaces { 48 | numSymbols := ws.parsedDocuments.CountSymbols() 49 | if result == nil { 50 | result = make([]lsp.SymbolInformation, 0, numSymbols) 51 | } 52 | buffer := make([]lsp.SymbolInformation, 0, 50) 53 | 54 | qlower := strings.ToLower(params.Query) 55 | 56 | err := ws.parsedDocuments.WalkGlobalSymbols(func(s symbol.Symbol) error { 57 | if req.Context().Err() != nil { 58 | h.logger.Debugf("request cancelled", "method", req.Request().Method()) 59 | return req.Context().Err() 60 | } 61 | if qlower == "" { 62 | result = w.collectWorkspaceSymbols(result, s) 63 | return nil 64 | } 65 | 66 | // pre filtering 67 | buffer = buffer[:0] 68 | buffer = w.collectWorkspaceSymbols(buffer, s) 69 | for _, v := range buffer { 70 | if stringContainsAllAnywhere(v.Name, params.Query) { 71 | result = append(result, v) 72 | } 73 | } 74 | return nil 75 | }, SymbolAll) 76 | 77 | if err != nil { 78 | h.logger.Warnf("Error collecting workspace symbols. %v", err) 79 | continue 80 | } 81 | } 82 | 83 | return req.Reply(req.Context(), result, nil) 84 | } 85 | -------------------------------------------------------------------------------- /langserver/parse_result.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/kirides/DaedalusLanguageServer/daedalus/parser" 8 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 9 | ) 10 | 11 | // ParseResult ... 12 | type ParseResult struct { 13 | Ast parser.IDaedalusFileContext 14 | Instances map[string]symbol.ProtoTypeOrInstance 15 | GlobalVariables map[string]symbol.Symbol 16 | GlobalConstants map[string]symbol.Symbol 17 | Functions map[string]symbol.Function 18 | Classes map[string]symbol.Class 19 | Prototypes map[string]symbol.ProtoTypeOrInstance 20 | Namespaces map[string]symbol.Namespace 21 | Source string 22 | SyntaxErrors []SyntaxError 23 | 24 | lastModifiedAt time.Time 25 | } 26 | 27 | func (r *ParseResult) CountSymbols() int64 { 28 | numSymbols := int64(len(r.Classes)) + 29 | int64(len(r.Functions)) + 30 | int64(len(r.GlobalConstants)) + 31 | int64(len(r.GlobalVariables)) + 32 | int64(len(r.Instances)) + 33 | int64(len(r.Prototypes)) 34 | 35 | for _, v := range r.Classes { 36 | numSymbols += int64(len(v.Fields)) 37 | } 38 | 39 | for _, v := range r.Instances { 40 | numSymbols += int64(len(v.Fields)) 41 | } 42 | for _, v := range r.Prototypes { 43 | numSymbols += int64(len(v.Fields)) 44 | } 45 | 46 | // for _, v := range r.Functions { 47 | // numSymbols += int64(len(v.Parameters)) 48 | // numSymbols += int64(len(v.LocalVariables)) 49 | // } 50 | 51 | return numSymbols 52 | } 53 | 54 | func (parsedDoc *ParseResult) LookupScopedVariable(di symbol.DefinitionIndex, name string) (symbol.Symbol, bool) { 55 | for _, fn := range parsedDoc.Functions { 56 | if fn.BodyDefinition.InBBox(di) { 57 | for _, p := range fn.Parameters { 58 | if strings.EqualFold(p.Name(), name) { 59 | return p, true 60 | } 61 | } 62 | for _, p := range fn.LocalVariables { 63 | if strings.EqualFold(p.Name(), name) { 64 | return p, true 65 | } 66 | } 67 | break 68 | } 69 | } 70 | for _, fn := range parsedDoc.Classes { 71 | if fn.BodyDefinition.InBBox(di) { 72 | for _, p := range fn.Fields { 73 | if strings.EqualFold(p.Name(), name) { 74 | return p, true 75 | } 76 | } 77 | break 78 | } 79 | } 80 | return nil, false 81 | } 82 | 83 | func (parsedDoc *ParseResult) WalkScopedVariables(di symbol.DefinitionIndex, walkFn func(symbol.Symbol) bool) { 84 | for _, fn := range parsedDoc.Functions { 85 | if fn.BodyDefinition.InBBox(di) { 86 | for _, p := range fn.Parameters { 87 | if !walkFn(p) { 88 | return 89 | } 90 | } 91 | for _, p := range fn.LocalVariables { 92 | if !walkFn(p) { 93 | return 94 | } 95 | } 96 | break 97 | } 98 | } 99 | for _, fn := range parsedDoc.Classes { 100 | if fn.BodyDefinition.InBBox(di) { 101 | for _, p := range fn.Fields { 102 | if !walkFn(p) { 103 | return 104 | } 105 | } 106 | break 107 | } 108 | } 109 | } 110 | 111 | func (parsedDoc *ParseResult) FindContainingScope(di symbol.DefinitionIndex) (symbol.Symbol, bool) { 112 | for _, fn := range parsedDoc.Functions { 113 | if fn.BodyDefinition.InBBox(di) { 114 | return fn, true 115 | } 116 | } 117 | for _, fn := range parsedDoc.Classes { 118 | if fn.BodyDefinition.InBBox(di) { 119 | return fn, true 120 | } 121 | } 122 | for _, fn := range parsedDoc.Prototypes { 123 | if fn.BodyDefinition.InBBox(di) { 124 | return fn, true 125 | } 126 | } 127 | for _, fn := range parsedDoc.Instances { 128 | if fn.BodyDefinition.InBBox(di) { 129 | return fn, true 130 | } 131 | } 132 | return nil, false 133 | } 134 | 135 | func (parsedDoc *ParseResult) ScopedVariables(di symbol.DefinitionIndex) map[string]symbol.Symbol { 136 | locals := map[string]symbol.Symbol{} 137 | 138 | parsedDoc.WalkScopedVariables(di, func(s symbol.Symbol) bool { 139 | locals[strings.ToUpper(s.Name())] = s 140 | return true 141 | }) 142 | 143 | return locals 144 | } 145 | -------------------------------------------------------------------------------- /langserver/parseresultsmanager_test.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "context" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestParseSourceResolvesInsensitivee(t *testing.T) { 10 | 11 | mgr := newParseResultsManager(nopLogger{}) 12 | p, _ := filepath.Abs(filepath.Join("testdata", "Gothic.src")) 13 | _, err := mgr.ParseSource(context.TODO(), p) 14 | if err != nil { 15 | t.Fatalf("%v", err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /langserver/semantic_parse_result.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/kirides/DaedalusLanguageServer/daedalus/symbol" 7 | ) 8 | 9 | type SemanticParseResult struct { 10 | ParseResult 11 | GlobalIdentifiers []Identifier 12 | } 13 | 14 | func (r *SemanticParseResult) CountSymbols() int64 { 15 | numSymbols := r.ParseResult.CountSymbols() + int64(len(r.GlobalIdentifiers)) 16 | 17 | return numSymbols 18 | } 19 | 20 | // WalkSymbols ... 21 | func walkSymbolsMapHelper[V token](items map[string]V, walkFn func(token) error, previousError error) error { 22 | if previousError != nil { 23 | return previousError 24 | } 25 | 26 | for _, s := range items { 27 | if err := walkFn(s); err != nil { 28 | return err 29 | } 30 | } 31 | return nil 32 | } 33 | func walkSymbolsSliceHelper[V token](items []V, walkFn func(token) error, previousError error) error { 34 | if previousError != nil { 35 | return previousError 36 | } 37 | 38 | for _, s := range items { 39 | if err := walkFn(s); err != nil { 40 | return err 41 | } 42 | } 43 | return nil 44 | } 45 | func (p *SemanticParseResult) WalkSymbols(walkFn func(token) error) error { 46 | var err error 47 | err = walkSymbolsMapHelper(p.Classes, walkFn, err) 48 | err = walkSymbolsMapHelper(p.GlobalConstants, walkFn, err) 49 | err = walkSymbolsMapHelper(p.Functions, walkFn, err) 50 | err = walkSymbolsMapHelper(p.Instances, walkFn, err) 51 | err = walkSymbolsMapHelper(p.Prototypes, walkFn, err) 52 | err = walkSymbolsMapHelper(p.GlobalVariables, walkFn, err) 53 | err = walkSymbolsSliceHelper(p.GlobalIdentifiers, walkFn, err) 54 | return err 55 | } 56 | 57 | // WalkScoped walks all higher-class symbols that are contained in `di` 58 | func (p *SemanticParseResult) WalkScoped(bbox symbol.Definition, walkFn func(sym token) error) error { 59 | var err error 60 | scopedWalk := func(t token) error { 61 | if bbox.InBBox(t.Definition().Start) || bbox.InBBox(t.Definition().End) { 62 | return walkFn(t) 63 | } 64 | return nil 65 | } 66 | err = walkSymbolsMapHelper(p.Classes, scopedWalk, err) 67 | err = walkSymbolsMapHelper(p.GlobalConstants, scopedWalk, err) 68 | err = walkSymbolsMapHelper(p.Functions, scopedWalk, err) 69 | err = walkSymbolsMapHelper(p.Instances, scopedWalk, err) 70 | err = walkSymbolsMapHelper(p.Prototypes, scopedWalk, err) 71 | err = walkSymbolsMapHelper(p.GlobalVariables, scopedWalk, err) 72 | err = walkSymbolsSliceHelper(p.GlobalIdentifiers, scopedWalk, err) 73 | return err 74 | } 75 | 76 | type ScopedVariable struct { 77 | Location FoundLocation 78 | } 79 | 80 | func (parsedDoc *SemanticParseResult) FindScopedVariableDeclaration(di symbol.DefinitionIndex, name string) (FoundSymbol, bool) { 81 | for _, fn := range parsedDoc.Functions { 82 | if fn.BodyDefinition.InBBox(di) { 83 | for _, p := range fn.Parameters { 84 | if strings.EqualFold(p.Name(), name) { 85 | return FoundSymbol{p, FoundParameter}, true 86 | } 87 | } 88 | for _, p := range fn.LocalVariables { 89 | if strings.EqualFold(p.Name(), name) { 90 | return FoundSymbol{p, FoundLocal}, true 91 | } 92 | } 93 | break 94 | } 95 | } 96 | for _, fn := range parsedDoc.Classes { 97 | if fn.BodyDefinition.InBBox(di) { 98 | for _, p := range fn.Fields { 99 | if strings.EqualFold(p.Name(), name) { 100 | return FoundSymbol{p, FoundField}, true 101 | } 102 | } 103 | break 104 | } 105 | } 106 | for _, fn := range parsedDoc.Prototypes { 107 | if fn.BodyDefinition.InBBox(di) { 108 | for _, p := range fn.Fields { 109 | if strings.EqualFold(p.Name(), name) { 110 | return FoundSymbol{p, FoundField}, true 111 | } 112 | } 113 | break 114 | } 115 | } 116 | for _, fn := range parsedDoc.Instances { 117 | if fn.BodyDefinition.InBBox(di) { 118 | for _, p := range fn.Fields { 119 | if strings.EqualFold(p.Name(), name) { 120 | return FoundSymbol{p, FoundField}, true 121 | } 122 | } 123 | break 124 | } 125 | } 126 | return FoundSymbol{}, false 127 | } 128 | 129 | func (parsedDoc *SemanticParseResult) WalkScopedVariables(di symbol.DefinitionIndex, walkFn func(sym symbol.Symbol, isParam bool) bool) { 130 | for _, fn := range parsedDoc.Functions { 131 | if fn.BodyDefinition.InBBox(di) { 132 | for _, p := range fn.Parameters { 133 | if !walkFn(p, true) { 134 | return 135 | } 136 | } 137 | for _, p := range fn.LocalVariables { 138 | if !walkFn(p, false) { 139 | return 140 | } 141 | } 142 | break 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /langserver/syntax_error.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 5 | ) 6 | 7 | // ErrorSeverity ... 8 | type ErrorSeverity int 9 | 10 | const ( 11 | // SeverityInfo ... 12 | SeverityInfo ErrorSeverity = 0 13 | // SeverityWarning ... 14 | SeverityWarning ErrorSeverity = 1 15 | // SeverityError ... 16 | SeverityError ErrorSeverity = 2 17 | ) 18 | 19 | // SyntaxError ... 20 | type SyntaxError struct { 21 | ErrorCode SyntaxErrorCode 22 | Line int 23 | Column int 24 | } 25 | 26 | // SyntaxErrorCode ... 27 | type SyntaxErrorCode struct { 28 | Code string 29 | Description string 30 | Severity ErrorSeverity 31 | } 32 | 33 | var ( 34 | // D0001NoIdentifierWithStartingDigits ... 35 | D0001NoIdentifierWithStartingDigits = NewSyntaxErrorCode("D0001", "Do not start identifiers with digits", SeverityWarning) 36 | // D0002SplitMultipleVarDecl ... 37 | D0002SplitMultipleVarDecl = NewSyntaxErrorCode("D0002", "Split multiple 'var TYPE ..., var TYPE ...' into separate statements.", SeverityWarning) 38 | // D0003MissingSemicolonMightCauseIssues ... 39 | D0003MissingSemicolonMightCauseIssues = NewSyntaxErrorCode("D0003", "Missing ';' might cause issues.", SeverityError) 40 | // D0004NotEnoughArgumentsSpecified ... 41 | D0004NotEnoughArgumentsSpecified = NewSyntaxErrorCode("D0004", "Not enough arguments specified", SeverityError) 42 | // D0005TooManyArgumentsSpecified ... 43 | D0005TooManyArgumentsSpecified = NewSyntaxErrorCode("D0005", "Too many arguments specified", SeverityError) 44 | // D0006UnrecognizedMetaField ... 45 | D0006UnrecognizedMetaField = NewSyntaxErrorCode("D0006", "Unrecognized META field. Expected any of: \"Parser, MergeMode, Engine, NativeWhile, Namespace, Using, Mod, After\"", SeverityWarning) 46 | ) 47 | 48 | // NewSyntaxError ... 49 | func NewSyntaxError(line, col int, code SyntaxErrorCode) SyntaxError { 50 | return SyntaxError{ 51 | Line: line, 52 | Column: col, 53 | ErrorCode: code, 54 | } 55 | } 56 | 57 | // NewSyntaxErrorCode ... 58 | func NewSyntaxErrorCode(code, desc string, severity ErrorSeverity) SyntaxErrorCode { 59 | return SyntaxErrorCode{ 60 | Code: code, 61 | Description: desc, 62 | Severity: severity, 63 | } 64 | } 65 | 66 | // Diagnostic returns a diagnostic based on this sytax error 67 | func (se SyntaxError) Diagnostic() lsp.Diagnostic { 68 | return lsp.Diagnostic{ 69 | Code: se.ErrorCode.Code, 70 | Message: se.ErrorCode.Description, 71 | Source: "vscode-daedalus", 72 | Severity: lspSeverityFromSeverity(se.ErrorCode.Severity), 73 | Range: lsp.Range{ 74 | Start: lsp.Position{ 75 | Line: uint32(se.Line - 1), 76 | Character: uint32(se.Column), 77 | }, 78 | End: lsp.Position{ 79 | Line: uint32(se.Line - 1), 80 | Character: uint32(se.Column), 81 | }, 82 | }, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /langserver/syntax_error_listener.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import "github.com/antlr4-go/antlr/v4" 4 | 5 | // SyntaxErrorListener ... 6 | type SyntaxErrorListener struct { 7 | antlr.DefaultErrorListener 8 | SyntaxErrors []SyntaxError 9 | } 10 | 11 | // SyntaxError ... 12 | func (l *SyntaxErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) { 13 | if se, ok := e.(*listenerSyntaxError); ok { 14 | l.SyntaxErrors = append(l.SyntaxErrors, NewSyntaxError(line, column, se.Code)) 15 | } else { 16 | l.SyntaxErrors = append(l.SyntaxErrors, NewSyntaxError(line, column, NewSyntaxErrorCode("D0000", msg, SeverityWarning))) 17 | } 18 | } 19 | 20 | // NoOpErrorListener ... 21 | type NoOpErrorListener struct { 22 | antlr.DefaultErrorListener 23 | } 24 | 25 | // SyntaxError ... 26 | func (l *NoOpErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) { 27 | } 28 | -------------------------------------------------------------------------------- /langserver/testdata/Gothic.src: -------------------------------------------------------------------------------- 1 | LEGO\HEADER_G2.SRC 2 | -------------------------------------------------------------------------------- /langserver/testdata/LeGo/AI_Function.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/AI_Function.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Anim8.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/Anim8.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Bars.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/Bars.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/BinaryMachines.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/BinaryMachines.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Bloodsplats.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/Bloodsplats.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Buffs.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/Buffs.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/ConsoleCommands.d: -------------------------------------------------------------------------------- 1 | /***********************************\ 2 | CONSOLECOMMANDS 3 | \***********************************/ 4 | 5 | //======================================== 6 | // [intern] Class / Variables 7 | //======================================== 8 | class CCItem { 9 | var int fncID; 10 | var string cmd; 11 | }; 12 | instance CCItem@(CCItem); 13 | 14 | const int _CC_List = 0; // Non-persistent record of all CCs 15 | 16 | //======================================== 17 | // Check if CC is registered 18 | //======================================== 19 | func int CC_Active(var func function) { 20 | if (!_CC_List) { 21 | return FALSE; 22 | }; 23 | 24 | var int symID; symID = MEM_GetFuncID(function); 25 | 26 | // Iterate over all registered CCs 27 | var zCArray a; a = _^(_CC_List); 28 | repeat(i, a.numInArray); var int i; 29 | var CCItem cc; cc = _^(MEM_ReadIntArray(a.array, i)); 30 | if (cc.fncID == symID) { 31 | return TRUE; 32 | }; 33 | end; 34 | 35 | return FALSE; 36 | }; 37 | 38 | //======================================== 39 | // Remove CC 40 | //======================================== 41 | func void CC_Remove(var func function) { 42 | if (!_CC_List) { 43 | return; 44 | }; 45 | 46 | var int symID; symID = MEM_GetFuncID(function); 47 | 48 | // Iterate over all registered CCs 49 | var zCArray a; a = _^(_CC_List); 50 | repeat(i, a.numInArray); var int i; 51 | var int ccPtr; ccPtr = MEM_ReadIntArray(a.array, i); 52 | var CCItem cc; cc = _^(ccPtr); 53 | 54 | if (cc.fncID == symID) { 55 | MEM_ArrayRemoveIndex(_CC_List, ccPtr); 56 | MEM_Free(ccPtr); 57 | }; 58 | end; 59 | }; 60 | 61 | //======================================== 62 | // [intern] Register auto-completion 63 | //======================================== 64 | func void CC_AutoComplete(var string commandPrefix, var string description) { 65 | var int descPtr; descPtr = _@s(description); 66 | var int comPtr; comPtr = _@s(commandPrefix); 67 | 68 | const int call = 0; 69 | if (CALL_Begin(call)) { 70 | CALL_PtrParam(_@(descPtr)); 71 | CALL_PtrParam(_@(comPtr)); 72 | CALL__thiscall(_@(zcon_address_lego), zCConsole__Register); 73 | call = CALL_End(); 74 | }; 75 | }; 76 | 77 | //======================================== 78 | // Register new CC 79 | //======================================== 80 | func void CC_Register(var func function, var string commandPrefix, var string description) { 81 | // Remove any left over handles (from LeGo 2.4.0) if they are unarchived from old game saves 82 | if (hasHndl(CCItem@)) { 83 | foreachHndl(CCItem@, _CCItem_deleteHandles); 84 | }; 85 | 86 | // Only add if not already present 87 | if (CC_Active(function)) { 88 | return; 89 | }; 90 | 91 | // Check validity of function signature 92 | var int symID; symID = MEM_GetFuncID(function); 93 | var zCPar_Symbol symb; symb = _^(MEM_GetSymbolByIndex(symID)); 94 | if ((symb.bitfield & zCPar_Symbol_bitfield_ele) != 1) || (symb.offset != (zPAR_TYPE_STRING >> 12)) { 95 | MEM_Error(ConcatStrings("CONSOLECOMMANDS: Function has to have one parameter and needs to return a string: ", 96 | symb.name)); 97 | return; 98 | }; 99 | symb = _^(MEM_GetSymbolByIndex(symID+1)); 100 | if ((symb.bitfield & zCPar_Symbol_bitfield_type) != zPAR_TYPE_STRING) { 101 | MEM_Error(ConcatStrings("CONSOLECOMMANDS: Function parameter needs to be a string: ", symb.name)); 102 | return; 103 | }; 104 | 105 | // Register auto-completion 106 | commandPrefix = STR_Upper(commandPrefix); 107 | CC_AutoComplete(commandPrefix, description); 108 | 109 | // Create CC object 110 | var int ccPtr; ccPtr = create(CCItem@); 111 | var CCItem cc; cc = _^(ccPtr); 112 | cc.fncID = symID; 113 | cc.cmd = commandPrefix; 114 | 115 | // Initialize once 116 | if (!_CC_List) { 117 | _CC_List = MEM_ArrayCreate(); 118 | }; 119 | 120 | // Add CC to 'list' 121 | MEM_ArrayInsert(_CC_List, ccPtr); 122 | }; 123 | 124 | //======================================== 125 | // [intern] Engine hook 126 | //======================================== 127 | func void _CC_Hook() { 128 | if (!_CC_List) { 129 | return; 130 | }; 131 | 132 | // Get query entered into console 133 | var int stackOffset; stackOffset = MEMINT_SwitchG1G2(/*2ach*/ 684, /*424h*/ 1060); 134 | var string query; query = MEM_ReadString(MEM_ReadInt(ESP+stackOffset+4)); 135 | 136 | // Iterate over all registered CCs 137 | var zCArray a; a = _^(_CC_List); 138 | repeat(i, a.numInArray); var int i; 139 | var CCItem cc; cc = _^(MEM_ReadIntArray(a.array, i)); 140 | 141 | // Check if entered query starts with defined command 142 | if (STR_StartsWith(query, cc.cmd)) { 143 | // Cut off everything after the command 144 | var int cmdLen; cmdLen = STR_Len(cc.cmd); 145 | var int qryLen; qryLen = STR_Len(query); 146 | STR_SubStr(query, cmdLen, qryLen-cmdLen); // Leave on data stack 147 | 148 | // Call the CC function (argument already on data stack) 149 | MEM_CallByID(cc.fncID); 150 | var string ret; ret = MEM_PopStringResult(); 151 | 152 | // If the CC function returns a valid string, stop the loop 153 | // This additional check allows multiple CCs with the same command 154 | if (!Hlp_StrCmp(ret, "")) { 155 | MEM_WriteString(EAX, ret); 156 | break; 157 | }; 158 | }; 159 | end; 160 | }; 161 | 162 | //======================================= 163 | // Simple LeGo console command 164 | //======================================= 165 | func string CC_LeGo(var string _) { 166 | var int s; s = SB_New(); 167 | SB(LeGO_Version); 168 | SBc(10); SBc(13); 169 | _LeGo_Flags; 170 | MEM_Call(LeGo_FlagsHR); 171 | SB(MEM_PopStringResult()); 172 | var string ret; ret = SB_ToString(); 173 | SB_Destroy(); 174 | return ret; 175 | }; 176 | 177 | //======================================== 178 | // [intern] Old game save compatibility 179 | //======================================== 180 | // ConsoleCommands have been rewritten to not be stored to game saves. To ensure compatibility with old game saves from 181 | // LeGo 2.4.0, this function is necessary to prevent errors on unarchiving, because 'fncID' was stored as string. 182 | func void CCItem_Unarchiver(var CCItem this) {}; 183 | func int _CCItem_deleteHandles(var int hndl) { 184 | delete(hndl); 185 | return rContinue; 186 | }; 187 | -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Cursor.d: -------------------------------------------------------------------------------- 1 | /***********************************\ 2 | CURSOR 3 | \***********************************/ 4 | 5 | //======================================== 6 | // Uservariablen 7 | //======================================== 8 | var int Cursor_X; 9 | var int Cursor_Y; 10 | var int Cursor_RelX; // float 11 | var int Cursor_RelY; // float 12 | var int Cursor_Wheel; 13 | var int Cursor_Left; 14 | var int Cursor_Mid; 15 | var int Cursor_Right; 16 | var int Cursor_NoEngine; 17 | 18 | var int Cursor_Event; // gCEvent(h) 19 | 20 | //======================================== 21 | // [intern] Variablen 22 | //======================================== 23 | var int Cursor_fX; 24 | var int Cursor_fY; 25 | 26 | //======================================== 27 | // Cursor verstecken 28 | //======================================== 29 | var int Cursor_Hndl; 30 | func void Cursor_Hide() { 31 | if(!Hlp_IsValidHandle(Cursor_Hndl)) { return; }; 32 | 33 | View_Close(Cursor_Hndl); 34 | }; 35 | 36 | //======================================== 37 | // Cursor anzeigen 38 | //======================================== 39 | func void Cursor_Show() { 40 | if(Hlp_IsValidHandle(Cursor_Hndl)) { View_Open(Cursor_Hndl); return; }; 41 | Print_GetScreenSize(); 42 | Cursor_X = Print_Screen[PS_X] / 2; 43 | Cursor_Y = Print_Screen[PS_Y] / 2; 44 | Cursor_fX = mkf(Cursor_X); 45 | Cursor_fY = mkf(Cursor_Y); 46 | Cursor_Hndl = View_CreatePxl(Cursor_X, Cursor_Y, Cursor_X+Cursor_Texture_W, Cursor_Y+Cursor_Texture_H); 47 | View_SetTexture(Cursor_Hndl, Cursor_Texture); 48 | View_Open(Cursor_Hndl); 49 | }; 50 | 51 | //======================================== 52 | // Maussteuerung An-/Ausschalten 53 | //======================================== 54 | func void SetMouseEnabled(var int bEnabled) { 55 | CALL_IntParam(!!bEnabled /*Nur zur Sicherheit*/); 56 | CALL_IntParam(2); 57 | CALL__thiscall(MEM_ReadInt(zCInput_zinput), zCInput_Win32__SetDeviceEnabled); 58 | }; 59 | 60 | //======================================== 61 | // [intern] Klasse (von Engine genutzt) 62 | //======================================== 63 | class _Cursor { 64 | var int relX; 65 | var int relY; 66 | var int wheel; 67 | var int keyLeft; 68 | var int keyMid; 69 | var int keyRight; 70 | }; 71 | 72 | //======================================== 73 | // [intern] Tasten 74 | //======================================== 75 | func void Cursor_KeyState(var int ptr, var int pressed) { 76 | var int keyState; keyState = MEM_ReadInt(ptr); 77 | // Kopiert aus der Ikarus.d 78 | if (keyState == KEY_UP) { 79 | if (pressed) { 80 | keyState = KEY_PRESSED; 81 | }; 82 | } else if (keyState == KEY_PRESSED) { 83 | if (pressed) { 84 | keyState = KEY_HOLD; 85 | } else { 86 | keyState = KEY_RELEASED; 87 | }; 88 | } else if (keyState == KEY_HOLD) { 89 | if (!pressed) { 90 | keyState = KEY_RELEASED; 91 | }; 92 | } else { 93 | if (pressed) { 94 | keyState = KEY_PRESSED; 95 | } else { 96 | keyState = KEY_UP; 97 | }; 98 | }; 99 | MEM_WriteInt(ptr, keyState); 100 | return; 101 | }; 102 | 103 | //======================================== 104 | // [intern] Enginehook 105 | //======================================== 106 | 107 | func void Cursor_Update() { 108 | View_Top(Cursor_Hndl); 109 | }; 110 | 111 | func void _Cursor_GetVal() { 112 | var _Cursor c; c = _^(Cursor_Ptr); 113 | 114 | Cursor_RelX = c.relX; 115 | Cursor_RelY = c.relY; 116 | Cursor_fX = addf(mulf(mkf(Cursor_RelX), mulf(MEM_ReadInt(Cursor_sX), mkf(2))), Cursor_fX); 117 | Cursor_fY = addf(mulf(mkf(Cursor_RelY), mulf(MEM_ReadInt(Cursor_sY), mkf(2))), Cursor_fY); 118 | 119 | Cursor_X = roundf(Cursor_fX); 120 | Cursor_Y = roundf(Cursor_fY); 121 | Cursor_Wheel = c.wheel; 122 | 123 | Cursor_KeyState(_@(Cursor_Left), c.keyLeft); 124 | Cursor_KeyState(_@(Cursor_Right), c.keyRight); 125 | Cursor_KeyState(_@(Cursor_Mid), c.keyMid); 126 | 127 | if(Cursor_Left == KEY_PRESSED) { 128 | Event_Execute(Cursor_Event, CUR_LeftClick); 129 | }; 130 | if(Cursor_Right == KEY_PRESSED) { 131 | Event_Execute(Cursor_Event, CUR_RightClick); 132 | }; 133 | if(Cursor_Mid == KEY_PRESSED) { 134 | Event_Execute(Cursor_Event, CUR_MidClick); 135 | }; 136 | if(Cursor_Wheel != 0) { 137 | if(Cursor_Wheel > 0) { 138 | Event_Execute(Cursor_Event, CUR_WheelUp); 139 | } 140 | else { 141 | Event_Execute(Cursor_Event, CUR_WheelDown); 142 | }; 143 | }; 144 | 145 | Print_GetScreenSize(); 146 | if((Cursor_X + Cursor_Texture_W) > Print_Screen[PS_X]) { 147 | Cursor_X = Print_Screen[PS_X]-Cursor_Texture_W; 148 | Cursor_fX = mkf(Cursor_X); 149 | } 150 | else if(Cursor_X < 0) { 151 | Cursor_X = 0; 152 | Cursor_fX = mkf(Cursor_X); 153 | }; 154 | if((Cursor_Y+Cursor_Texture_H) > Print_Screen[PS_Y]) { 155 | Cursor_Y = Print_Screen[PS_Y]-Cursor_Texture_H; 156 | Cursor_fY = mkf(Cursor_Y); 157 | } 158 | else if(Cursor_Y < 0) { 159 | Cursor_Y = 0; 160 | Cursor_fY = mkf(Cursor_Y); 161 | }; 162 | 163 | if(Cursor_NoEngine) { 164 | c.relX = 0; 165 | c.relY = 0; 166 | c.keyLeft = 0; 167 | c.keyMid = 0; 168 | c.keyRight = 0; 169 | c.wheel = 0; 170 | }; 171 | 172 | if(!Hlp_IsValidHandle(Cursor_Hndl)) { return; }; 173 | 174 | View_MoveToPxl(Cursor_Hndl, Cursor_X, Cursor_Y); 175 | Cursor_Update(); 176 | }; 177 | 178 | 179 | -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Dialoggestures.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/Dialoggestures.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/EngineAdr_G1.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/EngineAdr_G1.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/EngineAdr_G2.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/EngineAdr_G2.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/EventHandler.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/EventHandler.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Focusnames.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/Focusnames.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/FrameFunctions.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/FrameFunctions.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Gamestate.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/Gamestate.d -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Hashtable.d: -------------------------------------------------------------------------------- 1 | class HT_Array { 2 | var int array; 3 | var int numalloc; 4 | var int numinarray; 5 | }; instance HT@(HT_Array) { 6 | }; 7 | 8 | func void HT_Array_Archiver(var HT_Array this) { 9 | PM_SaveInt("length", this.numAlloc); // Hate to do it this way, but that's how I implemented it back then :/ 10 | PM_SaveInt("elements", this.numInArray); 11 | 12 | var int ctr; ctr = 0; 13 | var int k; k = 0; 14 | repeat(k, this.numAlloc/4); 15 | var int ptr; ptr = MEM_ReadInt(this.array+k*4); 16 | if (!ptr) { continue; }; 17 | 18 | PM_SaveClassPtr(IntToString(ctr), ptr, "zCArray"); 19 | PM_SaveInt(ConcatStrings("pos", IntToString(ctr)), k); 20 | ctr += 1; 21 | end; 22 | PM_SaveInt("subArrays", ctr); 23 | }; 24 | 25 | func void HT_Array_Unarchiver(var HT_Array this) { 26 | this.numAlloc = PM_Load("length"); 27 | this.numInArray = PM_Load("elements"); 28 | this.array = MEM_Alloc(this.numAlloc); 29 | 30 | var int k; k = 0; 31 | repeat(k, PM_Load("subArrays")); 32 | var int pos; pos = PM_Load(ConcatStrings("pos", IntToString(k))); 33 | MEM_WriteInt(this.array+pos*4, PM_Load(IntToString(k))); 34 | end; 35 | }; 36 | 37 | func void HT_Array_delete(var HT_Array ht) { 38 | _HT_Destroy(_@(ht)); 39 | }; 40 | 41 | func int HT_CreateSized(var int size) { 42 | return wrap(HT@, _HT_CreatePtr(size)); 43 | }; 44 | 45 | func int HT_Create() { 46 | return +HT_CreateSized(HT_SIZE); 47 | }; 48 | 49 | func void HT_Insert(var int hndl, var int val, var int key) { 50 | _HT_Insert(getPtr(hndl), val, key); 51 | }; 52 | 53 | func void HT_Resize(var int hndl, var int size) { 54 | _HT_Resize(getPtr(hndl), size); 55 | }; 56 | 57 | func int HT_Get(var int hndl, var int key) { 58 | return _HT_Get(getPtr(hndl), key); 59 | }; 60 | 61 | func int HT_Has(var int hndl, var int key) { 62 | return _HT_Has(getPtr(hndl), key); 63 | }; 64 | 65 | func void HT_Remove(var int hndl, var int key) { 66 | _HT_Remove(getPtr(hndl), key); 67 | }; 68 | 69 | func void HT_Change(var int hndl, var int val, var int key) { 70 | _HT_Change(getPtr(hndl), val, key); 71 | }; 72 | 73 | func void HT_InsertOrChange(var int hndl, var int val, var int key) { 74 | _HT_InsertOrChange(getPtr(hndl), val, key); 75 | }; 76 | 77 | func int HT_GetNumber(var int hndl) { 78 | return _HT_GetNumber(getPtr(hndl)); 79 | }; 80 | 81 | func void HT_ForEach(var int hndl, var func fnc) { 82 | _HT_ForEach(getPtr(hndl), fnc); 83 | }; 84 | 85 | func void HT_Destroy(var int hndl) { 86 | delete(hndl); 87 | }; 88 | 89 | -------------------------------------------------------------------------------- /langserver/testdata/LeGo/Header_G2.src: -------------------------------------------------------------------------------- 1 | ////////// 2 | // LeGo // 3 | ////////// 4 | 5 | Misc.d 6 | EngineAdr_G2.d 7 | Timer.d 8 | Userconst.d 9 | ItemHelper.d 10 | StringBuilder.d 11 | Locals.d 12 | BinaryMachines.d 13 | _Hashtable.d 14 | HookEngine.d 15 | HookDaedalus.d 16 | PermMem.d 17 | List.d 18 | PermMem_Structs.d 19 | Queue.d 20 | EventHandler.d 21 | Anim8.d 22 | FrameFunctions.d 23 | ConsoleCommands.d 24 | Talents.d 25 | Random.d 26 | AI_Function.d 27 | Interface.d 28 | View.d 29 | Cursor.d 30 | Bloodsplats.d 31 | Buffs.d 32 | Trialoge.d 33 | Names.d 34 | Bars.d 35 | Buttons.d 36 | Dialoggestures.d 37 | Focusnames.d 38 | Gamestate.d 39 | Render.d 40 | Draw3D.d 41 | Saves.d 42 | Int64.d 43 | Sprite.d 44 | Hashtable.d 45 | LeGo.d 46 | 47 | /// USER ADDITIONS 48 | Interface_user.d 49 | List_user.d 50 | -------------------------------------------------------------------------------- /langserver/testdata/LeGo/_Hashtable.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/langserver/testdata/LeGo/_Hashtable.d -------------------------------------------------------------------------------- /langserver/testdata/demo.d: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// Comment 4 | // comment2 5 | func void Do() { 6 | 7 | }; 8 | -------------------------------------------------------------------------------- /langserver/textdocumentsync.go: -------------------------------------------------------------------------------- 1 | package langserver 2 | 3 | import ( 4 | "context" 5 | 6 | dls "github.com/kirides/DaedalusLanguageServer" 7 | lsp "github.com/kirides/DaedalusLanguageServer/protocol" 8 | "go.lsp.dev/uri" 9 | ) 10 | 11 | type textDocumentSync struct { 12 | baseLspHandler 13 | 14 | GetWorkspace func(lsp.DocumentURI) *LspWorkspace 15 | } 16 | 17 | func lspSeverityFromSeverity(severity ErrorSeverity) lsp.DiagnosticSeverity { 18 | switch severity { 19 | case SeverityInfo: 20 | return lsp.SeverityInformation 21 | case SeverityWarning: 22 | return lsp.SeverityWarning 23 | } 24 | return lsp.SeverityError 25 | } 26 | 27 | func (h *textDocumentSync) updateBuffer(ctx context.Context, ws *LspWorkspace, filePath, content string) { 28 | chars := content 29 | ws.bufferManager.UpdateBuffer(filePath, chars) 30 | p, _ := ws.parsedDocuments.Update(filePath, content) 31 | 32 | diagnostics := []lsp.Diagnostic{} 33 | if p.SyntaxErrors != nil && len(p.SyntaxErrors) > 0 { 34 | for _, se := range p.SyntaxErrors { 35 | diagnostics = append(diagnostics, se.Diagnostic()) 36 | } 37 | } 38 | h.conn.Notify(ctx, lsp.MethodTextDocumentPublishDiagnostics, lsp.PublishDiagnosticsParams{ 39 | URI: lsp.DocumentURI(uri.File(filePath)), 40 | Diagnostics: diagnostics, 41 | }) 42 | 43 | h.LogDebug("Updated buffer for %q with %d chars", filePath, len(chars)) 44 | } 45 | 46 | func (h *textDocumentSync) handleTextDocumentDidClose(req dls.RpcContext, data lsp.DidCloseTextDocumentParams) error { 47 | ws := h.GetWorkspace(data.TextDocument.URI) 48 | if ws == nil { 49 | return req.Reply(req.Context(), nil, nil) 50 | } 51 | 52 | documentUri := uriToFilename(data.TextDocument.URI) 53 | if documentUri != "" { 54 | ws.bufferManager.DeleteBuffer(documentUri) 55 | } 56 | return nil 57 | } 58 | func (h *textDocumentSync) handleTextDocumentDidOpen(req dls.RpcContext, data lsp.DidOpenTextDocumentParams) error { 59 | ws := h.GetWorkspace(data.TextDocument.URI) 60 | if ws == nil { 61 | return req.Reply(req.Context(), nil, nil) 62 | } 63 | 64 | documentUri := uriToFilename(data.TextDocument.URI) 65 | if documentUri != "" { 66 | h.updateBuffer(req.Context(), ws, documentUri, data.TextDocument.Text) 67 | } 68 | return nil 69 | } 70 | func (h *textDocumentSync) handleTextDocumentDidChange(req dls.RpcContext, data lsp.DidChangeTextDocumentParams) error { 71 | ws := h.GetWorkspace(data.TextDocument.URI) 72 | if ws == nil { 73 | return req.Reply(req.Context(), nil, nil) 74 | } 75 | 76 | documentUri := uriToFilename(data.TextDocument.URI) 77 | if documentUri != "" && len(data.ContentChanges) > 0 { 78 | h.updateBuffer(req.Context(), ws, documentUri, data.ContentChanges[0].Text) 79 | } 80 | return nil 81 | } 82 | 83 | func (h *textDocumentSync) handleTextDocumentDidSave(req dls.RpcContext, data lsp.DidSaveTextDocumentParams) error { 84 | ws := h.GetWorkspace(data.TextDocument.URI) 85 | if ws == nil { 86 | return req.Reply(req.Context(), nil, nil) 87 | } 88 | 89 | documentUri := uriToFilename(data.TextDocument.URI) 90 | if documentUri != "" { 91 | text := "" 92 | if data.Text != nil { 93 | text = *data.Text 94 | } 95 | h.updateBuffer(req.Context(), ws, documentUri, text) 96 | } 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package DaedalusLanguageServer 2 | 3 | type Logger interface { 4 | Debugf(template string, args ...interface{}) 5 | Infof(template string, args ...interface{}) 6 | Warnf(template string, args ...interface{}) 7 | Errorf(template string, args ...interface{}) 8 | } 9 | -------------------------------------------------------------------------------- /protocol/gen/README.md: -------------------------------------------------------------------------------- 1 | # Generate Go types and signatures for the LSP protocol 2 | 3 | ## Setup 4 | 5 | Make sure `node` and `tsc` are installed and in your PATH. There are detailed instructions below. 6 | (`tsc -v` should be at least `4.2.4`.) 7 | Get the typescript code for the jsonrpc protocol with 8 | 9 | `git clone git@github.com:microsoft vscode-languageserver-node.git` or 10 | `git clone https://github.com/microsoft/vscode-languageserver-node.git` 11 | 12 | `util.ts` expects it to be in your HOME directory 13 | 14 | If you want to reproduce the existing files you need to be on a branch with the same git hash that `util.ts` expects, for instance, `git checkout 7b90c29` 15 | 16 | ## Usage 17 | 18 | Code is generated and normalized by 19 | 20 | `tsc && node code.js && gofmt -w ts*.go` 21 | 22 | (`code.ts` imports `util.ts`.) This generates 3 files in the current directory, `tsprotocol.go` 23 | containing type definitions, and `tsserver.go`, `tsclient.go` containing API stubs. 24 | 25 | ## Notes 26 | 27 | 1. `code.ts` and `util.ts` use the Typescript compiler's API, which is [introduced](https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview) in their wiki. 28 | 2. Because the Typescript and Go type systems are incompatible, `code.ts` and `util.ts` are filled with heuristics and special cases. Therefore they are tied to a specific commit of `vscode-languageserver-node`. The hash code of the commit is included in the header of 29 | the generated files and stored in the variable `gitHash` in `go.ts`. It is checked (see `git()` in `util.ts`) on every execution. 30 | 3. Generating the `ts*.go` files is only semi-automated. Please file an issue if the released version is too far behind. 31 | 4. For the impatient, first change `gitHash` by hand (`git()` shows how to find the hash). 32 | 1. Then try to run `code.ts`. This will likely fail because the heuristics don't cover some new case. For instance, some simple type like `string` might have changed to a union type `string | [number,number]`. Another example is that some generated formal parameter may have anonymous structure type, which is essentially unusable. 33 | 2. Next step is to move the generated code to `internal/lsp/protocol` and try to build `gopls` and its tests. This will likely fail because types have changed. Generally the fixes are fairly easy. Then run all the tests. 34 | 3. Since there are not adequate integration tests, the next step is to run `gopls`. 35 | 36 | ## Detailed instructions for installing node and typescript 37 | 38 | (The instructions are somewhat different for Linux and MacOS. They install some things locally, so `$PATH` needs to be changed.) 39 | 40 | 1. For Linux, it is possible to build node from scratch, but if there's a package manager, that's simpler. 41 | 1. To use the Ubuntu package manager 42 | 1. `sudo apt update` (if you can't `sudo` then these instructions are not helpful) 43 | 2. `sudo apt install nodejs` (this may install `/usr/bin/nodejs` rather than `/usr/bin/node`. For me, `/usr/bin/nodejs` pointed to an actual executable `/etc/alternatives/nodejs`, which should be copied to `/usr/bin/node`) 44 | 3. `sudo apt intall npm` 45 | 1. To build from scratch 46 | 1. Go to the [node site](https://nodejs.org), and download the one recommended for most users, and then you're on your own. (It's got binaries in it. Untar the file somewhere and put its `bin` directory in your path, perhaps?) 47 | 2. The Mac is easier. Download the macOS installer from [nodejs](https://nodejs.org), click on it, and let it install. 48 | 3. (There's a good chance that soon you will be asked to upgrade your new npm. `sudo npm install -g npm` is the command.) 49 | 4. For either system, node and nvm should now be available. Running `node -v` and `npm -v` should produce version numbers. 50 | 5. `npm install typescript` 51 | 1. This may give warning messages that indicate you've failed to set up a project. Ignore them. 52 | 2. Your home directory will now have new directories `.npm` and `node_modules` (and a `package_lock.json` file) 53 | 3. The typescript executable `tsc` will be in `node_modules/.bin`, so put that directory in your path. 54 | 4. `tsc -v` should print "Version 4.2.4" (or later). If not you may (as I did) have an obsolete tsc earlier in your path. 55 | 6. `npm install @types/node` (Without this there will be many incomprehensible typescript error messages.) 56 | -------------------------------------------------------------------------------- /protocol/gen/src/readme.md: -------------------------------------------------------------------------------- 1 | 2 | Steps to get the source 3 | ``` 4 | git clone https://github.com/microsoft/vscode-languageserver-node.git 5 | cd vscode-languageserver-node 6 | git checkout 696f9285bf849b73745682fdb1c1feac73eb8772 7 | ``` -------------------------------------------------------------------------------- /protocol/gen/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirides/DaedalusLanguageServer/5d674da4c2611c825bf775873845adc7b2951608/protocol/gen/tmp/.gitkeep -------------------------------------------------------------------------------- /protocol/gen/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "isolatedModules": true, 4 | "moduleResolution": "node", 5 | "lib":["ES2020"], 6 | "sourceMap": true, // sourceMap or inlineSourceMap? and see inlineSources 7 | "target": "ES5", 8 | 9 | "noFallthroughCasesInSwitch": false, // there is one legitimate on 10 | "noImplicitReturns": true, 11 | "noPropertyAccessFromIndexSignature": true, 12 | "noUncheckedIndexedAccess": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": false, 15 | "noEmitOnError": true, 16 | 17 | // "extendedDiagnostics": true, // for occasional amusement 18 | 19 | // "strict": true, // too many undefineds in types, etc 20 | "alwaysStrict": true, 21 | "noImplicitAny": true, 22 | "noImplicitThis": true, 23 | "strictBindCallApply": true, 24 | "strictFunctionTypes": true, 25 | "strictNullChecks": false, // doesn't like arrray access, among other things. 26 | //"strictPropertyInitialization": true, // needs strictNullChecks 27 | }, 28 | "files": ["./code.ts", "./util.ts"] 29 | } 30 | -------------------------------------------------------------------------------- /protocol/gen/util.ts: -------------------------------------------------------------------------------- 1 | 2 | // for us typescript ignorati, having an import makes this file a module 3 | import * as fs from 'fs'; 4 | // import * as process from 'process'; 5 | import * as ts from 'typescript'; 6 | 7 | // This file contains various utilities having to do with producing strings 8 | // and managing output 9 | 10 | // ------ create files 11 | let dir = './src'; 12 | const srcDir = '/vscode-languageserver-node'; 13 | export const fnames = [ 14 | `${dir}${srcDir}/protocol/src/common/protocol.ts`, 15 | `${dir}/${srcDir}/protocol/src/browser/main.ts`, `${dir}${srcDir}/types/src/main.ts`, 16 | `${dir}${srcDir}/jsonrpc/src/node/main.ts` 17 | ]; 18 | export const gitHash = '696f9285bf849b73745682fdb1c1feac73eb8772'; 19 | let outFname = 'tsprotocol.go'; 20 | let fda: number, fdb: number, fde: number; // file descriptors 21 | 22 | export function createOutputFiles() { 23 | fda = fs.openSync('./tmp/ts-a', 'w'); // dump of AST 24 | fdb = fs.openSync('./tmp/ts-b', 'w'); // unused, for debugging 25 | fde = fs.openSync(outFname, 'w'); // generated Go 26 | } 27 | export function pra(s: string) { 28 | return (fs.writeSync(fda, s)); 29 | } 30 | export function prb(s: string) { 31 | return (fs.writeSync(fdb, s)); 32 | } 33 | export function prgo(s: string) { 34 | return (fs.writeSync(fde, s)); 35 | } 36 | 37 | // Get the hash value of the git commit 38 | export function git(): string { 39 | let a = fs.readFileSync(`${dir}${srcDir}/.git/HEAD`).toString(); 40 | // ref: refs/heads/foo, or a hash like 41 | // cc12d1a1c7df935012cdef5d085cdba04a7c8ebe 42 | if (a.charAt(a.length - 1) == '\n') { 43 | a = a.substring(0, a.length - 1); 44 | } 45 | if (a.length == 40) { 46 | return a; // a hash 47 | } 48 | if (a.substring(0, 5) == 'ref: ') { 49 | const fname = `${dir}${srcDir}/.git/` + a.substring(5); 50 | let b = fs.readFileSync(fname).toString(); 51 | if (b.length == 41) { 52 | return b.substring(0, 40); 53 | } 54 | } 55 | throw new Error('failed to find the git commit hash'); 56 | } 57 | 58 | // Produce a header for Go output files 59 | export function computeHeader(pkgDoc: boolean): string { 60 | let lastMod = 0; 61 | let lastDate = new Date(); 62 | for (const f of fnames) { 63 | const st = fs.statSync(f); 64 | if (st.mtimeMs > lastMod) { 65 | lastMod = st.mtimeMs; 66 | lastDate = st.mtime; 67 | } 68 | } 69 | const cp = `// Copyright 2019 The Go Authors. All rights reserved. 70 | // Use of this source code is governed by a BSD-style 71 | // license that can be found in the LICENSE file. 72 | 73 | `; 74 | const a = 75 | '// Package protocol contains data types and code for LSP json rpcs\n' + 76 | '// generated automatically from vscode-languageserver-node\n' + 77 | `// commit: ${gitHash}\n` + 78 | `// last fetched ${lastDate}\n`; 79 | const b = 'package protocol\n'; 80 | const c = '\n// Code generated (see typescript/README.md) DO NOT EDIT.\n\n'; 81 | if (pkgDoc) { 82 | return cp + c + a + b; 83 | } 84 | else { 85 | return cp + c+ b + a; 86 | } 87 | } 88 | 89 | // Turn a typescript name into an exportable Go name, and appease lint 90 | export function goName(s: string): string { 91 | let ans = s; 92 | if (s.charAt(0) == '_') { 93 | // in the end, none of these are emitted. 94 | ans = 'Inner' + s.substring(1); 95 | } 96 | else { ans = s.substring(0, 1).toUpperCase() + s.substring(1); } 97 | ans = ans.replace(/Uri$/, 'URI'); 98 | ans = ans.replace(/Id$/, 'ID'); 99 | return ans; 100 | } 101 | 102 | // Generate JSON tag for a struct field 103 | export function JSON(n: ts.PropertySignature): string { 104 | const json = `\`json:"${n.name.getText()}${n.questionToken !== undefined ? ',omitempty' : ''}"\``; 105 | return json; 106 | } 107 | 108 | // Generate modifying prefixes and suffixes to ensure 109 | // consts are unique. (Go consts are package-level, but Typescript's are 110 | // not.) Use suffixes to minimize changes to gopls. 111 | export function constName(nm: string, type: string): string { 112 | let pref = new Map([ 113 | ['DiagnosticSeverity', 'Severity'], ['WatchKind', 'Watch'], 114 | ['SignatureHelpTriggerKind', 'Sig'], ['CompletionItemTag', 'Compl'], 115 | ['Integer', 'INT_'], ['Uinteger', 'UINT_'], ['CodeActionTriggerKind', 'CodeAction'] 116 | ]); // typeName->prefix 117 | let suff = new Map([ 118 | ['CompletionItemKind', 'Completion'], ['InsertTextFormat', 'TextFormat'], 119 | ['SymbolTag', 'Symbol'], ['FileOperationPatternKind', 'Op'], 120 | ]); 121 | let ans = nm; 122 | if (pref.get(type)) ans = pref.get(type) + ans; 123 | if (suff.has(type)) ans = ans + suff.get(type); 124 | return ans; 125 | } 126 | 127 | // Find the comments associated with an AST node 128 | export function getComments(node: ts.Node): string { 129 | const sf = node.getSourceFile(); 130 | const start = node.getStart(sf, false); 131 | const starta = node.getStart(sf, true); 132 | const x = sf.text.substring(starta, start); 133 | return x; 134 | } 135 | 136 | 137 | // --------- printing the AST, for debugging 138 | 139 | export function printAST(program: ts.Program) { 140 | // dump the ast, for debugging 141 | const f = function (n: ts.Node) { 142 | describe(n, pra); 143 | }; 144 | for (const sourceFile of program.getSourceFiles()) { 145 | if (!sourceFile.isDeclarationFile) { 146 | // walk the tree to do stuff 147 | ts.forEachChild(sourceFile, f); 148 | } 149 | } 150 | pra('\n'); 151 | for (const key of Object.keys(seenThings).sort()) { 152 | pra(`${key}: ${seenThings.get(key)} \n`); 153 | } 154 | } 155 | 156 | // Used in printing the AST 157 | let seenThings = new Map(); 158 | function seenAdd(x: string) { 159 | const u = seenThings.get(x); 160 | seenThings.set(x, u === undefined ? 1 : u + 1); 161 | } 162 | 163 | // eslint-disable-next-line no-unused-vars 164 | function describe(node: ts.Node, pr: (_: string) => any) { 165 | if (node === undefined) { 166 | return; 167 | } 168 | let indent = ''; 169 | 170 | function f(n: ts.Node) { 171 | seenAdd(kinds(n)); 172 | if (ts.isIdentifier(n)) { 173 | pr(`${indent} ${loc(n)} ${strKind(n)} ${n.text} \n`); 174 | } 175 | else if (ts.isPropertySignature(n) || ts.isEnumMember(n)) { 176 | pra(`${indent} ${loc(n)} ${strKind(n)} \n`); 177 | } 178 | else if (ts.isTypeLiteralNode(n)) { 179 | let m = n.members; 180 | pr(`${indent} ${loc(n)} ${strKind(n)} ${m.length} \n`); 181 | } 182 | else if (ts.isStringLiteral(n)) { 183 | pr(`${indent} ${loc(n)} ${strKind(n)} ${n.text} \n`); 184 | } 185 | else { pr(`${indent} ${loc(n)} ${strKind(n)} \n`); } 186 | indent += ' .'; 187 | ts.forEachChild(n, f); 188 | indent = indent.slice(0, indent.length - 2); 189 | } 190 | f(node); 191 | } 192 | 193 | 194 | // For debugging, say where an AST node is in a file 195 | export function loc(node: ts.Node | undefined): string { 196 | if (!node) throw new Error('loc called with undefined (cannot happen!)'); 197 | const sf = node.getSourceFile(); 198 | const start = node.getStart(); 199 | const x = sf.getLineAndCharacterOfPosition(start); 200 | const full = node.getFullStart(); 201 | const y = sf.getLineAndCharacterOfPosition(full); 202 | let fn = sf.fileName; 203 | const n = fn.search(/-node./); 204 | fn = fn.substring(n + 6); 205 | return `${fn} ${x.line + 1}: ${x.character + 1} (${y.line + 1}: ${y.character + 1})`; 206 | } 207 | 208 | // --- various string stuff 209 | 210 | // return a string of the kinds of the immediate descendants 211 | // as part of printing the AST tree 212 | function kinds(n: ts.Node): string { 213 | let res = 'Seen ' + strKind(n); 214 | function f(n: ts.Node): void { res += ' ' + strKind(n); } 215 | ts.forEachChild(n, f); 216 | return res; 217 | } 218 | 219 | // What kind of AST node is it? This would just be typescript's 220 | // SyntaxKind[n.kind] except that the default names for some nodes 221 | // are misleading 222 | export function strKind(n: ts.Node | undefined): string { 223 | if (n == null || n == undefined) { 224 | return 'null'; 225 | } 226 | return kindToStr(n.kind); 227 | } 228 | 229 | function kindToStr(k: ts.SyntaxKind): string { 230 | const x = ts.SyntaxKind[k]; 231 | // some of these have two names 232 | switch (x) { 233 | default: 234 | return x; 235 | case 'FirstAssignment': 236 | return 'EqualsToken'; 237 | case 'FirstBinaryOperator': 238 | return 'LessThanToken'; 239 | case 'FirstCompoundAssignment': 240 | return 'PlusEqualsToken'; 241 | case 'FirstContextualKeyword': 242 | return 'AbstractKeyword'; 243 | case 'FirstLiteralToken': 244 | return 'NumericLiteral'; 245 | case 'FirstNode': 246 | return 'QualifiedName'; 247 | case 'FirstTemplateToken': 248 | return 'NoSubstitutionTemplateLiteral'; 249 | case 'LastTemplateToken': 250 | return 'TemplateTail'; 251 | case 'FirstTypeNode': 252 | return 'TypePredicate'; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /protocol/methods_client.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | const ( 4 | // MethodProgress method name of "$/progress". 5 | MethodProgress = "$/progress" 6 | 7 | // MethodWorkDoneProgressCreate method name of "window/workDoneProgress/create". 8 | MethodWorkDoneProgressCreate = "window/workDoneProgress/create" 9 | 10 | // MethodWindowShowMessage method name of "window/showMessage". 11 | MethodWindowShowMessage = "window/showMessage" 12 | 13 | // MethodWindowShowMessageRequest method name of "window/showMessageRequest. 14 | MethodWindowShowMessageRequest = "window/showMessageRequest" 15 | 16 | // MethodWindowLogMessage method name of "window/logMessage. 17 | MethodWindowLogMessage = "window/logMessage" 18 | 19 | // MethodTelemetryEvent method name of "telemetry/event. 20 | MethodTelemetryEvent = "telemetry/event" 21 | 22 | // MethodClientRegisterCapability method name of "client/registerCapability. 23 | MethodClientRegisterCapability = "client/registerCapability" 24 | 25 | // MethodClientUnregisterCapability method name of "client/unregisterCapability. 26 | MethodClientUnregisterCapability = "client/unregisterCapability" 27 | 28 | // MethodTextDocumentPublishDiagnostics method name of "textDocument/publishDiagnostics. 29 | MethodTextDocumentPublishDiagnostics = "textDocument/publishDiagnostics" 30 | 31 | // MethodWorkspaceApplyEdit method name of "workspace/applyEdit. 32 | MethodWorkspaceApplyEdit = "workspace/applyEdit" 33 | 34 | // MethodWorkspaceConfiguration method name of "workspace/configuration. 35 | MethodWorkspaceConfiguration = "workspace/configuration" 36 | 37 | // MethodWorkspaceWorkspaceFolders method name of "workspace/workspaceFolders". 38 | MethodWorkspaceWorkspaceFolders = "workspace/workspaceFolders" 39 | ) 40 | -------------------------------------------------------------------------------- /protocol/methods_server.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // list of server methods. 4 | const ( 5 | // MethodCancelRequest method name of "$/cancelRequest". 6 | MethodCancelRequest = "$/cancelRequest" 7 | 8 | // MethodInitialize method name of "initialize". 9 | MethodInitialize = "initialize" 10 | 11 | // MethodInitialized method name of "initialized". 12 | MethodInitialized = "initialized" 13 | 14 | // MethodShutdown method name of "shutdown". 15 | MethodShutdown = "shutdown" 16 | 17 | // MethodExit method name of "exit". 18 | MethodExit = "exit" 19 | 20 | // MethodWorkDoneProgressCancel method name of "window/workDoneProgress/cancel". 21 | MethodWorkDoneProgressCancel = "window/workDoneProgress/cancel" 22 | 23 | // MethodLogTrace method name of "$/logTrace". 24 | MethodLogTrace = "$/logTrace" 25 | 26 | // MethodSetTrace method name of "$/setTrace". 27 | MethodSetTrace = "$/setTrace" 28 | 29 | // MethodTextDocumentCodeAction method name of "textDocument/codeAction". 30 | MethodTextDocumentCodeAction = "textDocument/codeAction" 31 | 32 | // MethodTextDocumentCodeLens method name of "textDocument/codeLens". 33 | MethodTextDocumentCodeLens = "textDocument/codeLens" 34 | 35 | // MethodCodeLensResolve method name of "codeLens/resolve". 36 | MethodCodeLensResolve = "codeLens/resolve" 37 | 38 | // MethodTextDocumentColorPresentation method name of "textDocument/colorPresentation". 39 | MethodTextDocumentColorPresentation = "textDocument/colorPresentation" 40 | 41 | // MethodTextDocumentCompletion method name of "textDocument/completion". 42 | MethodTextDocumentCompletion = "textDocument/completion" 43 | 44 | // MethodCompletionItemResolve method name of "completionItem/resolve". 45 | MethodCompletionItemResolve = "completionItem/resolve" 46 | 47 | // MethodTextDocumentDeclaration method name of "textDocument/declaration". 48 | MethodTextDocumentDeclaration = "textDocument/declaration" 49 | 50 | // MethodTextDocumentDefinition method name of "textDocument/definition". 51 | MethodTextDocumentDefinition = "textDocument/definition" 52 | 53 | // MethodTextDocumentDidChange method name of "textDocument/didChange". 54 | MethodTextDocumentDidChange = "textDocument/didChange" 55 | 56 | // MethodWorkspaceDidChangeConfiguration method name of "workspace/didChangeConfiguration". 57 | MethodWorkspaceDidChangeConfiguration = "workspace/didChangeConfiguration" 58 | 59 | // MethodWorkspaceDidChangeWatchedFiles method name of "workspace/didChangeWatchedFiles". 60 | MethodWorkspaceDidChangeWatchedFiles = "workspace/didChangeWatchedFiles" 61 | 62 | // MethodWorkspaceDidChangeWorkspaceFolders method name of "workspace/didChangeWorkspaceFolders". 63 | MethodWorkspaceDidChangeWorkspaceFolders = "workspace/didChangeWorkspaceFolders" 64 | 65 | // MethodTextDocumentDidClose method name of "textDocument/didClose". 66 | MethodTextDocumentDidClose = "textDocument/didClose" 67 | 68 | // MethodTextDocumentDidOpen method name of "textDocument/didOpen". 69 | MethodTextDocumentDidOpen = "textDocument/didOpen" 70 | 71 | // MethodTextDocumentDidSave method name of "textDocument/didSave". 72 | MethodTextDocumentDidSave = "textDocument/didSave" 73 | 74 | // MethodTextDocumentDocumentColor method name of"textDocument/documentColor". 75 | MethodTextDocumentDocumentColor = "textDocument/documentColor" 76 | 77 | // MethodTextDocumentDocumentHighlight method name of "textDocument/documentHighlight". 78 | MethodTextDocumentDocumentHighlight = "textDocument/documentHighlight" 79 | 80 | // MethodTextDocumentDocumentLink method name of "textDocument/documentLink". 81 | MethodTextDocumentDocumentLink = "textDocument/documentLink" 82 | 83 | // MethodDocumentLinkResolve method name of "documentLink/resolve". 84 | MethodDocumentLinkResolve = "documentLink/resolve" 85 | 86 | // MethodTextDocumentDocumentSymbol method name of "textDocument/documentSymbol". 87 | MethodTextDocumentDocumentSymbol = "textDocument/documentSymbol" 88 | 89 | // MethodWorkspaceExecuteCommand method name of "workspace/executeCommand". 90 | MethodWorkspaceExecuteCommand = "workspace/executeCommand" 91 | 92 | // MethodTextDocumentFoldingRange method name of "textDocument/foldingRange". 93 | MethodTextDocumentFoldingRange = "textDocument/foldingRange" 94 | 95 | // MethodTextDocumentFormatting method name of "textDocument/formatting". 96 | MethodTextDocumentFormatting = "textDocument/formatting" 97 | 98 | // MethodTextDocumentHover method name of "textDocument/hover". 99 | MethodTextDocumentHover = "textDocument/hover" 100 | 101 | // MethodTextDocumentImplementation method name of "textDocument/implementation". 102 | MethodTextDocumentImplementation = "textDocument/implementation" 103 | 104 | // MethodTextDocumentOnTypeFormatting method name of "textDocument/onTypeFormatting". 105 | MethodTextDocumentOnTypeFormatting = "textDocument/onTypeFormatting" 106 | 107 | // MethodTextDocumentPrepareRename method name of "textDocument/prepareRename". 108 | MethodTextDocumentPrepareRename = "textDocument/prepareRename" 109 | 110 | // MethodTextDocumentRangeFormatting method name of "textDocument/rangeFormatting". 111 | MethodTextDocumentRangeFormatting = "textDocument/rangeFormatting" 112 | 113 | // MethodTextDocumentReferences method name of "textDocument/references". 114 | MethodTextDocumentReferences = "textDocument/references" 115 | 116 | // MethodTextDocumentRename method name of "textDocument/rename". 117 | MethodTextDocumentRename = "textDocument/rename" 118 | 119 | // MethodTextDocumentSignatureHelp method name of "textDocument/signatureHelp". 120 | MethodTextDocumentSignatureHelp = "textDocument/signatureHelp" 121 | 122 | // MethodWorkspaceSymbol method name of "workspace/symbol". 123 | MethodWorkspaceSymbol = "workspace/symbol" 124 | 125 | // MethodTextDocumentTypeDefinition method name of "textDocument/typeDefinition". 126 | MethodTextDocumentTypeDefinition = "textDocument/typeDefinition" 127 | 128 | // MethodTextDocumentWillSave method name of "textDocument/willSave". 129 | MethodTextDocumentWillSave = "textDocument/willSave" 130 | 131 | // MethodTextDocumentWillSaveWaitUntil method name of "textDocument/willSaveWaitUntil". 132 | MethodTextDocumentWillSaveWaitUntil = "textDocument/willSaveWaitUntil" 133 | 134 | // MethodShowDocument method name of "window/showDocument". 135 | MethodShowDocument = "window/showDocument" 136 | 137 | // MethodWillCreateFiles method name of "workspace/willCreateFiles". 138 | MethodWillCreateFiles = "workspace/willCreateFiles" 139 | 140 | // MethodDidCreateFiles method name of "workspace/didCreateFiles". 141 | MethodDidCreateFiles = "workspace/didCreateFiles" 142 | 143 | // MethodWillRenameFiles method name of "workspace/willRenameFiles". 144 | MethodWillRenameFiles = "workspace/willRenameFiles" 145 | 146 | // MethodDidRenameFiles method name of "workspace/didRenameFiles". 147 | MethodDidRenameFiles = "workspace/didRenameFiles" 148 | 149 | // MethodWillDeleteFiles method name of "workspace/willDeleteFiles". 150 | MethodWillDeleteFiles = "workspace/willDeleteFiles" 151 | 152 | // MethodDidDeleteFiles method name of "workspace/didDeleteFiles". 153 | MethodDidDeleteFiles = "workspace/didDeleteFiles" 154 | 155 | // MethodCodeLensRefresh method name of "workspace/codeLens/refresh". 156 | MethodCodeLensRefresh = "workspace/codeLens/refresh" 157 | 158 | // MethodTextDocumentPrepareCallHierarchy method name of "textDocument/prepareCallHierarchy". 159 | MethodTextDocumentPrepareCallHierarchy = "textDocument/prepareCallHierarchy" 160 | 161 | // MethodCallHierarchyIncomingCalls method name of "callHierarchy/incomingCalls". 162 | MethodCallHierarchyIncomingCalls = "callHierarchy/incomingCalls" 163 | 164 | // MethodCallHierarchyOutgoingCalls method name of "callHierarchy/outgoingCalls". 165 | MethodCallHierarchyOutgoingCalls = "callHierarchy/outgoingCalls" 166 | 167 | // MethodSemanticTokensFull method name of "textDocument/semanticTokens/full". 168 | MethodSemanticTokensFull = "textDocument/semanticTokens/full" 169 | 170 | // MethodSemanticTokensFullDelta method name of "textDocument/semanticTokens/full/delta". 171 | MethodSemanticTokensFullDelta = "textDocument/semanticTokens/full/delta" 172 | 173 | // MethodSemanticTokensRange method name of "textDocument/semanticTokens/range". 174 | MethodSemanticTokensRange = "textDocument/semanticTokens/range" 175 | 176 | // MethodSemanticTokensRefresh method name of "workspace/semanticTokens/refresh". 177 | MethodSemanticTokensRefresh = "workspace/semanticTokens/refresh" 178 | 179 | // MethodLinkedEditingRange method name of "textDocument/linkedEditingRange". 180 | MethodLinkedEditingRange = "textDocument/linkedEditingRange" 181 | 182 | // MethodMoniker method name of "textDocument/moniker". 183 | MethodMoniker = "textDocument/moniker" 184 | 185 | // MethodTextDocumentInlayHint method name of "textDocument/inlayHint". 186 | MethodTextDocumentInlayHint = "textDocument/inlayHint" 187 | 188 | // MethodTextDocumentInlineValue method name of "textDocument/inlineValue". 189 | MethodTextDocumentInlineValue = "textDocument/inlineValue" 190 | ) 191 | -------------------------------------------------------------------------------- /protocol/semantic.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // SemanticTokenTypes represents a type of semantic token. 4 | // 5 | // @since 3.16.0. 6 | type SemanticTokenTypes string 7 | 8 | // list of SemanticTokenTypes. 9 | const ( 10 | SemanticTokenNamespace SemanticTokenTypes = "namespace" 11 | 12 | // Represents a generic type. Acts as a fallback for types which 13 | // can't be mapped to a specific type like class or enum. 14 | SemanticTokenType SemanticTokenTypes = "type" 15 | SemanticTokenClass SemanticTokenTypes = "class" 16 | SemanticTokenEnum SemanticTokenTypes = "enum" 17 | SemanticTokenInterface SemanticTokenTypes = "interface" 18 | SemanticTokenStruct SemanticTokenTypes = "struct" 19 | SemanticTokenTypeParameter SemanticTokenTypes = "typeParameter" 20 | SemanticTokenParameter SemanticTokenTypes = "parameter" 21 | SemanticTokenVariable SemanticTokenTypes = "variable" 22 | SemanticTokenProperty SemanticTokenTypes = "property" 23 | SemanticTokenEnumMember SemanticTokenTypes = "enumMember" 24 | SemanticTokenEvent SemanticTokenTypes = "event" 25 | SemanticTokenFunction SemanticTokenTypes = "function" 26 | SemanticTokenMethod SemanticTokenTypes = "method" 27 | SemanticTokenMacro SemanticTokenTypes = "macro" 28 | SemanticTokenKeyword SemanticTokenTypes = "keyword" 29 | SemanticTokenModifier SemanticTokenTypes = "modifier" 30 | SemanticTokenComment SemanticTokenTypes = "comment" 31 | SemanticTokenString SemanticTokenTypes = "string" 32 | SemanticTokenNumber SemanticTokenTypes = "number" 33 | SemanticTokenRegexp SemanticTokenTypes = "regexp" 34 | SemanticTokenOperator SemanticTokenTypes = "operator" 35 | ) 36 | 37 | // SemanticTokenModifiers represents a modifiers of semantic token. 38 | // 39 | // @since 3.16.0. 40 | type SemanticTokenModifiers string 41 | 42 | // list of SemanticTokenModifiers. 43 | const ( 44 | SemanticTokenModifierDeclaration SemanticTokenModifiers = "declaration" 45 | SemanticTokenModifierDefinition SemanticTokenModifiers = "definition" 46 | SemanticTokenModifierReadonly SemanticTokenModifiers = "readonly" 47 | SemanticTokenModifierStatic SemanticTokenModifiers = "static" 48 | SemanticTokenModifierDeprecated SemanticTokenModifiers = "deprecated" 49 | SemanticTokenModifierAbstract SemanticTokenModifiers = "abstract" 50 | SemanticTokenModifierAsync SemanticTokenModifiers = "async" 51 | SemanticTokenModifierModification SemanticTokenModifiers = "modification" 52 | SemanticTokenModifierDocumentation SemanticTokenModifiers = "documentation" 53 | SemanticTokenModifierDefaultLibrary SemanticTokenModifiers = "defaultLibrary" 54 | ) 55 | -------------------------------------------------------------------------------- /rpcContext.go: -------------------------------------------------------------------------------- 1 | package DaedalusLanguageServer 2 | 3 | import ( 4 | "context" 5 | 6 | "go.lsp.dev/jsonrpc2" 7 | ) 8 | 9 | type RpcContext interface { 10 | Context() context.Context 11 | Reply(ctx context.Context, result interface{}, err error) error 12 | ReplyEither(ctx context.Context, result interface{}, err error) error 13 | Request() jsonrpc2.Request 14 | } 15 | 16 | type rpcContext struct { 17 | ctx context.Context 18 | reply jsonrpc2.Replier 19 | req jsonrpc2.Request 20 | } 21 | 22 | func (d rpcContext) Context() context.Context { return d.ctx } 23 | func (d rpcContext) Reply(ctx context.Context, result interface{}, err error) error { 24 | return d.reply(ctx, result, err) 25 | } 26 | func (d rpcContext) Request() jsonrpc2.Request { return d.req } 27 | 28 | func (d rpcContext) ReplyEither(ctx context.Context, result interface{}, err error) error { 29 | if err != nil { 30 | return d.Reply(ctx, nil, err) 31 | } 32 | return d.Reply(ctx, result, nil) 33 | } 34 | -------------------------------------------------------------------------------- /rpcMux.go: -------------------------------------------------------------------------------- 1 | package DaedalusLanguageServer 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/goccy/go-json" 8 | 9 | "go.lsp.dev/jsonrpc2" 10 | ) 11 | 12 | type Handler func(RpcContext) error 13 | 14 | func NewMux() *RpcMux { 15 | return &RpcMux{ 16 | pathToType: map[string]Handler{}, 17 | } 18 | } 19 | 20 | type RpcMux struct { 21 | pathToType map[string]Handler 22 | } 23 | 24 | func (d *RpcMux) Handle(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) (bool, error) { 25 | if handler, ok := d.pathToType[req.Method()]; ok { 26 | return true, handler(rpcContext{ctx, reply, req}) 27 | } 28 | return false, fmt.Errorf("no handler") 29 | } 30 | 31 | func (d *RpcMux) Register(p string, fn Handler) { 32 | d.pathToType[p] = fn 33 | } 34 | 35 | func MakeHandler[T any](fn func(req RpcContext, data T) error) Handler { 36 | return func(req RpcContext) error { 37 | var val T 38 | 39 | if err := json.Unmarshal(req.Request().Params(), &val); err != nil { 40 | return err 41 | } 42 | 43 | return fn(req, val) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /rpcMux_test.go: -------------------------------------------------------------------------------- 1 | package DaedalusLanguageServer 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/kirides/DaedalusLanguageServer/protocol" 8 | "go.lsp.dev/jsonrpc2" 9 | "go.lsp.dev/uri" 10 | ) 11 | 12 | func TestRegister(t *testing.T) { 13 | 14 | d := &RpcMux{pathToType: make(map[string]Handler)} 15 | 16 | d.Register("text/sync", MakeHandler(func(req RpcContext, d protocol.TextDocumentPositionParams) error { 17 | t.Logf("%#v", d) 18 | return nil 19 | })) 20 | 21 | call, _ := jsonrpc2.NewCall(jsonrpc2.NewNumberID(1), "text/sync", protocol.TextDocumentPositionParams{TextDocument: protocol.TextDocumentIdentifier{URI: protocol.DocumentURI(uri.File(`C:\demo.txt`))}}) 22 | handled, err := d.Handle(context.Background(), nil, call) 23 | _ = handled 24 | 25 | if err != nil { 26 | txt := err.Error() 27 | 28 | _ = txt 29 | } 30 | 31 | t.Name() 32 | } 33 | --------------------------------------------------------------------------------