├── .eslintignore ├── .eslintrc.base.json ├── .github └── dependabot.yml ├── .gitignore ├── .mocharc.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── azure-pipelines.yml ├── client ├── .eslintrc.json ├── package-lock.json ├── package.json ├── src │ ├── common │ │ ├── commands.ts │ │ ├── compositeDisposable.ts │ │ └── disposable.ts │ ├── extension.ts │ └── test │ │ ├── diagnostics.test.ts │ │ ├── helper.ts │ │ ├── index.ts │ │ └── runTest.ts ├── testFixture │ └── test.src └── tsconfig.json ├── doc ├── MacroLanguage.ebnf ├── Tokens.md ├── key_words.txt └── settings.example.json ├── gulpfile.js ├── i18n ├── chs │ ├── package.i18n.json │ └── server │ │ └── out │ │ ├── macroLanguageService │ │ ├── parser │ │ │ └── macroErrors.i18n.json │ │ └── services │ │ │ └── lintRules.i18n.json │ │ └── server.i18n.json └── deu │ ├── package.i18n.json │ └── server │ └── out │ ├── macroLanguageService │ ├── parser │ │ └── macroErrors.i18n.json │ └── services │ │ └── lintRules.i18n.json │ └── server.i18n.json ├── package-lock.json ├── package.json ├── package.nls.json ├── resources ├── codelens.png ├── icon.png ├── implementations.gif ├── mrworkspaces.png ├── navigation.gif ├── no_semantic.png ├── projectsetting.png ├── references.gif ├── sequenceref.gif ├── sequences.gif ├── validation.gif └── with_semantic.png ├── scripts └── e2e.sh ├── server ├── .eslintrc.json ├── package-lock.json ├── package.json ├── src │ ├── fileProvider.ts │ ├── macroLanguageService │ │ ├── languageFacts │ │ │ └── builtinData.ts │ │ ├── macroLanguageService.ts │ │ ├── macroLanguageTypes.ts │ │ ├── parser │ │ │ ├── macroErrors.ts │ │ │ ├── macroNodes.ts │ │ │ ├── macroParser.ts │ │ │ ├── macroScanner.ts │ │ │ └── macroSymbolScope.ts │ │ ├── services │ │ │ ├── lint.ts │ │ │ ├── lintRules.ts │ │ │ ├── macroCallHierarchy.ts │ │ │ ├── macroCommands.ts │ │ │ ├── macroCompletions.ts │ │ │ ├── macroDocumentFormatting.ts │ │ │ ├── macroHover.ts │ │ │ ├── macroNavigation.ts │ │ │ ├── macroSemantic.ts │ │ │ └── macroValidation.ts │ │ └── test │ │ │ ├── command.test.ts │ │ │ ├── completion.test.ts │ │ │ ├── fileProviderMock.ts │ │ │ ├── navigation.test.ts │ │ │ ├── nodes.test.ts │ │ │ ├── parser.test.ts │ │ │ └── scanner.test.ts │ └── server.ts └── tsconfig.json ├── snippets └── snippets.json ├── syntaxes ├── macro.configuration.json └── macro.tmLanguage.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | client/node_modules/** 3 | client/out/** 4 | server/node_modules/** 5 | server/out/** -------------------------------------------------------------------------------- /.eslintrc.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "node": true 9 | }, 10 | "rules": { 11 | "semi": "error", 12 | "no-extra-semi": "warn", 13 | "curly": "warn", 14 | "quotes": ["error", "single", { "allowTemplateLiterals": true } ], 15 | "eqeqeq": "error", 16 | "indent": ["warn", "tab", { "SwitchCase": 1 } ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | - package-ecosystem: "npm" 2 | directory: "/" 3 | schedule: 4 | interval: "daily" 5 | target-branch: "develop" 6 | labels: 7 | - "dependencies" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test 4 | 5 | !i18n/*/*/out/ -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec":"./server/out/macroLanguageService/test", 3 | "extension": ["js"], 4 | "recursive": true, 5 | "ui" : "tdd" 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "name": "Launch Client", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceFolder}" ], 11 | "sourceMaps": true, 12 | "outFiles": ["${workspaceFolder}/client/out/**/*.js"], 13 | "preLaunchTask": "npm: watch" 14 | }, 15 | { 16 | "type": "node", 17 | "request": "attach", 18 | "name": "Attach to Server", 19 | "address": "localhost", 20 | "port": 6011, 21 | "sourceMaps": true, 22 | "outFiles": ["${workspaceFolder}/server/out/**/*.js"] 23 | }, 24 | { 25 | "name": "Extension Tests", 26 | "type": "extensionHost", 27 | "request": "launch", 28 | "runtimeExecutable": "${execPath}", 29 | "args": [ 30 | "--extensionDevelopmentPath=${workspaceFolder}", 31 | "--extensionTestsPath=${workspaceFolder}/client/out/test/index" 32 | ], 33 | "outFiles": ["${workspaceFolder}client/out/test/**/*.js"] 34 | }, 35 | { 36 | "name": "Language Service Tests", 37 | "type": "node", 38 | "request": "launch", 39 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 40 | "args": [ 41 | "--timeout", 42 | "999999", 43 | "--colors" 44 | ], 45 | "cwd": "${workspaceRoot}", 46 | "runtimeExecutable": null, 47 | "runtimeArgs": [], 48 | "env": {}, 49 | "sourceMaps": true, 50 | "outFiles": [ 51 | "${workspaceRoot}/server/out/**" 52 | ], 53 | "skipFiles": [ 54 | "/**" 55 | ], 56 | "smartStep": true, 57 | "preLaunchTask": "npm: watch" 58 | } 59 | ], 60 | "compounds": [ 61 | { 62 | "name": "Client + Server", 63 | "configurations": ["Launch Client", "Attach to Server"] 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false, 3 | "tslint.enable": true, 4 | "typescript.tsc.autoDetect": "off", 5 | "typescript.preferences.quoteStyle": "single", 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll.eslint": "explicit" 8 | }, 9 | "editor.bracketPairColorization.enabled": true 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": [ 13 | "$tsc" 14 | ] 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "watch", 19 | "isBackground": true, 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "presentation": { 25 | "panel": "dedicated", 26 | "reveal": "never" 27 | }, 28 | "problemMatcher": [ 29 | "$tsc-watch" 30 | ] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/**/* 2 | **/*.ts 3 | **/*.map 4 | **/tsconfig.json 5 | **/tsconfig.base.json 6 | **/*.code-workspace 7 | **/package-lock.json 8 | .gitignore 9 | .eslintignore 10 | .eslintrc.json 11 | .mocharc.json 12 | .travis.yml 13 | 14 | gulpfile.js 15 | contributing.md 16 | azure-pipelines.yml 17 | TODO 18 | 19 | scripts/ 20 | **/testFixture/ 21 | doc/ 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 (October, 2024) 2 | - Definition file dependency validation 3 | - Validation command 4 | - Version numbering changed 5 | - Fix number parsing [#38](https://github.com/iSorp/macro-executor/issues/38) 6 | - Fix address parsing [#32](https://github.com/iSorp/macro-executor/issues/32) 7 | 8 | ## 0.4.2 (July 6, 2022) 9 | - Fix a bug if an undefined symbol crashes the validator 10 | 11 | ## 0.4.1 (July 5, 2022) 12 | - Fix a bug when using custom keywords for semantic highlighting 13 | 14 | ## 0.4.0 (July 1, 2022) 15 | - Function call hierarchy 16 | - Formatting provider 17 | - Prepare rename provider for better rename experience 18 | - Outline shows the linked symbol code 19 | - Add support for system variables 20 | - Bug fixes 21 | - https://github.com/iSorp/macro-executor/issues/23 22 | - https://github.com/iSorp/macro-executor/issues/22 23 | - https://github.com/iSorp/macro-executor/issues/21 24 | - Ignore duplicate sequence number on data input G10/G11 25 | - Fix error when using single files instead of workspaces 26 | 27 | ## 0.3.3 (April 16, 2021) 28 | - NN address parsing 29 | 30 | ## 0.3.2 (April 07, 2021) 31 | - Bug fixes 32 | - Appropriate string statements 33 | - Bug fix for Local codelens references 34 | - Error message for duplicate imported symbols 35 | 36 | ## 0.3.0 (March 29, 2021) 37 | - Advanced Parser 38 | - A virtual symbol replacement allows any definition value 39 | - Concatenated statements are now valid (no blanks required) 40 | - BLOCKDEL support 41 | - Additional unit tests 42 | 43 | ## 0.2.4 (November 10, 2020) 44 | - Bug fixes 45 | - Completion symbol comment text fix 46 | - bcd/bin function text 47 | - Allowed symbol char '$' added 48 | 49 | 50 | ## 0.2.3 (October 13, 2020) 51 | - Additional linker parameter 52 | - Bug fixes 53 | - Comment reference for labels 54 | - Comment highlighting after include statement 55 | - No space between comment (/*) and statement needed 56 | 57 | ## 0.2.2 (August 4, 2020) 58 | - README 16bit compiler 59 | - Force document parsing when changing .def file 60 | - Line comments 61 | - Bugfix set export path 62 | 63 | 64 | ## 0.2.1 (July 14, 2020) 65 | - node module languageserver/client update 66 | - Multiline keyword description 67 | 68 | ## 0.2.0 (July 12, 2020) 69 | - Multi-Root Workspace 70 | - Custom Keywords (symbol, semantic scope, description) 71 | - M-Code, G-Code, macro variable(#..) and address reference search 72 | - Bug fixes Parser 73 | - NewLine checks 74 | - Tests 75 | 76 | ## 0.1.17 (June 25, 2020) 77 | - Language service tests 78 | - Sequence number refactoring skip on G10/G11 79 | - Sequence number reference search 80 | - GOTO Label/Sequence number implementation search 81 | - GOTO Label/Sequence validation 82 | - Bug fixes Parser 83 | - Error matches while statement 84 | 85 | ## 0.1.16 (June 18, 2020) 86 | - Bugfix -Fl export path 87 | - Bugfix compiler problem matcher 88 | 89 | ## 0.1.15 (June 17, 2020) 90 | - Bugfix Parameter symbol definition (e.g. @PARAM F) 91 | - Building: Relative paths for fanuc executables (compiler, linker, formater) 92 | 93 | ## 0.1.14 (June 11, 2020) 94 | - Signatures for build-in functions 95 | - Array support 96 | - Custom macro commands 97 | 98 | ## 0.1.13 (June 7, 2020) 99 | - Syntax highlighting 100 | - Semantic highlighting 101 | - Bug fixes Parser (% eof sign added) 102 | - Additional compiler parameter 103 | 104 | ## 0.1.12 (May 26, 2020) 105 | - Rename provider 106 | - Block skip support 107 | - clean up 108 | - while control statement do/end number check (nesting) 109 | 110 | ## 0.1.11 (May 20, 2020) 111 | - Text fixes 112 | - Directory tree with more than one level 113 | - Additional compiler selections 114 | 115 | ## 0.1.10 (May 18, 2020) 116 | - Sequence number deep search 117 | 118 | ## 0.1.9 (May 18, 2020) 119 | - Lint settings 120 | - Label-Sequence number overlap check 121 | - Duplicate value check 122 | - Conditional logic check (max 4 statements, no mixed up operators) 123 | - Supported display languages (English, Deutsch, 中文) 124 | - Bug fixes (Sequence number refactoring if duplicates exists) 125 | 126 | ## 0.1.8 (May 11, 2020) 127 | - Bug fixes Parser 128 | - Parsing of declared statements (@var G04P10) 129 | - Declared G and M codes shown as events (outline, completion) 130 | - Allow expression in declarations e.g @var #[1+[1]] 131 | - Axis number based command & e.g. G01 &A 1 132 | - Code completion specified for certain nodes 133 | - Error matching improved 134 | - Allow Backslash in strings and includes 135 | - Sequence number (N-Number) completion 136 | - Sequence number refactoring 137 | 138 | ## 0.1.7 (May 06, 2020) 139 | - Completions provider 140 | 141 | ## 0.1.6 (May 06, 2020) 142 | - Performance update 143 | 144 | ## 0.1.5 (May 04, 2020) 145 | - Node package issue 146 | 147 | ## 0.1.4 (May 02, 2020) 148 | - Opening links with capital letters issue fixed 149 | - Duplicate label statement warning 150 | - CodeLens for variable and label declarations 151 | 152 | ## 0.1.3 (May 01, 2020) 153 | - Missing arithmetic functions implemented 154 | - Implementation provider for sub programs 155 | - Error on global reference search fixed 156 | 157 | ## 0.1.2 (April 29, 2020) 158 | - File extension search non casesensitive 159 | 160 | ## 0.1.1 (April 29, 2020) 161 | - Prevent problem matcher from deleting existing problems 162 | - Symbol detection of left conditional term 163 | 164 | ## 0.1.0 (April 28, 2020) 165 | - Macro language server 166 | - Symbol provider 167 | - Navigation provider 168 | - Syntax validation 169 | 170 | ## 0.0.4 (March 31, 2020) 171 | - Problem matcher file search path is now "${fileDirname}" 172 | 173 | ## 0.0.3 (March 30, 2020) 174 | - While Snippet 175 | - HoverProvider for visualizing parameter definition 176 | 177 | ## 0.0.2 (March 27, 2020) 178 | - Package aliases 179 | 180 | ## 0.0.1 (March 27, 2020) 181 | - Initial release 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Simon Wälti 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 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - master 5 | - develop 6 | tags: 7 | include: 8 | - refs/tags/v* 9 | 10 | strategy: 11 | matrix: 12 | linux: 13 | imageName: 'ubuntu-latest' 14 | mac: 15 | imageName: 'macOS-latest' 16 | windows: 17 | imageName: 'windows-latest' 18 | 19 | pool: 20 | vmImage: $(imageName) 21 | 22 | steps: 23 | 24 | - task: NodeTool@0 25 | inputs: 26 | versionSpec: '20.x' 27 | displayName: 'Install Node.js' 28 | 29 | - bash: | 30 | /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 31 | echo ">>> Started xvfb" 32 | displayName: Start xvfb 33 | condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) 34 | 35 | - bash: | 36 | echo ">>> Run Fanuc Macro Executor integration test" 37 | yarn && yarn compile && yarn test 38 | echo ">>> Run Fanuc Macro Executor Language Service tests" 39 | yarn unit-test 40 | displayName: Run Tests 41 | env: 42 | DISPLAY: ':99.0' 43 | 44 | - bash: | 45 | echo ">>> Publish" 46 | yarn deploy 47 | displayName: Publish 48 | condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['Agent.OS'], 'Windows_NT')) 49 | env: 50 | VSCE_PAT: $(VSCE_PAT) 51 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc.base.json", 3 | "rules": { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsp-macro-client", 3 | "description": "Fanuc Macro-Executor Programming Language", 4 | "author": "iSorp", 5 | "license": "MIT", 6 | "version": "0.1.0", 7 | "publisher": "iSorp", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/iSorp/macro-executor.git" 11 | }, 12 | "engines": { 13 | "vscode": "^1.92.0" 14 | }, 15 | "dependencies": { 16 | "rxjs": "^7.5.2", 17 | "vscode-languageclient": "7.0.0" 18 | }, 19 | "devDependencies": { 20 | "@types/vscode": "^1.92.0", 21 | "@vscode/test-electron": "^2.4.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/common/commands.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import * as path from 'path'; 8 | import CompositeDisposable from './compositeDisposable'; 9 | import { pickFolder } from '../extension'; 10 | 11 | const COMPILER = ['MCOMPI', 'MCOMP0', 'MCOMP30I', 'MCOMP15', 'MCOMP15I']; 12 | 13 | type Systems = { 14 | [key: string]: { 15 | compiler:string; 16 | linker: string; 17 | card: string 18 | } 19 | }; 20 | 21 | const BUILD_SYSTEMS:Systems = { 22 | 'MCOMP30I': {compiler: 'MCOMP30I', linker: 'MLINK30I', card: 'MCARD30I' }, 23 | 'MCOMPI': {compiler: 'MCOMPI', linker: 'MLINKI', card: 'MMCARDI' }, 24 | 'MCOMP0': {compiler: 'MCOMP0', linker: 'MLINK', card: 'MMCARD' }, 25 | 'MCOMP15': {compiler: 'MCOMP15', linker: 'MLINK', card: 'MMCARD15' }, 26 | 'MCOMP15I': {compiler: 'MCOMP15I', linker: 'MLINK15I', card: 'MCARD15I' } 27 | }; 28 | 29 | const CONTROL_TYPE = ['', '0', '30', 'PM', '0F']; 30 | const build_ext_glob = ['ref','REL','PRG','ROM','MEM','MAP']; 31 | 32 | 33 | export default function registerCommands() : CompositeDisposable { 34 | 35 | let disposables = new CompositeDisposable(); 36 | disposables.add(vscode.commands.registerCommand('macro.setExportPath', setExportPath)); 37 | disposables.add(vscode.commands.registerCommand('macro.setControlType', setControlType)); 38 | disposables.add(vscode.commands.registerCommand('macro.setCompiler', setCompiler)); 39 | 40 | const project = new ProjectService(); 41 | disposables.add(vscode.commands.registerCommand('macro.compile', project.compile.bind(project))); 42 | disposables.add(vscode.commands.registerCommand('macro.build', project.build.bind(project))); 43 | disposables.add(vscode.commands.registerCommand('macro.clean', project.clean.bind(project))); 44 | 45 | return disposables; 46 | } 47 | 48 | 49 | // Set Compiler 50 | async function setCompiler() { 51 | pickFolder(workspace => { 52 | const config = vscode.workspace.getConfiguration('macro', workspace); 53 | const quickPickOptions = { 54 | matchOnDetail: true, 55 | matchOnDescription: false, 56 | placeHolder: `current: ${config.build.compiler}` 57 | }; 58 | 59 | vscode.window.showQuickPick(COMPILER, quickPickOptions).then(value => { 60 | if (value !== undefined) { 61 | config.update('build.compiler', value, vscode.ConfigurationTarget.WorkspaceFolder).then(() => { 62 | //Done 63 | }, reason => { 64 | vscode.window.showErrorMessage(`Failed to set 'compilerPath'. Error: ${reason.message}`); 65 | console.error(reason); 66 | }); 67 | } 68 | }); 69 | }); 70 | } 71 | 72 | async function setControlType () { 73 | pickFolder(workspace => { 74 | const config = vscode.workspace.getConfiguration('macro', workspace); 75 | const quickPickOptions = { 76 | matchOnDetail: true, 77 | 78 | matchOnDescription: false, 79 | placeHolder: `current: ${config.build.controlType}` 80 | }; 81 | 82 | vscode.window.showQuickPick(CONTROL_TYPE, quickPickOptions).then(value => { 83 | if (value !== undefined) { 84 | config.update('build.controlType', value, vscode.ConfigurationTarget.WorkspaceFolder).then(() => { 85 | //Done 86 | }, reason => { 87 | vscode.window.showErrorMessage(`Failed to set 'controlType'. Error: ${reason.message}`); 88 | console.error(reason); 89 | }); 90 | } 91 | }); 92 | }); 93 | } 94 | 95 | // Set Export Path 96 | function setExportPath () { 97 | const config = vscode.workspace.getConfiguration('macro'); 98 | const OpenDialogOptions = { 99 | matchOnDetail: true, 100 | matchOnDescription: false, 101 | placeHolder: `current: ${config.project.exportPath}`, 102 | canSelectMany: false, 103 | canSelectFiles : false, 104 | canSelectFolders : true, 105 | openLabel: 'Select', 106 | }; 107 | 108 | // Set Export Path 109 | vscode.window.showOpenDialog(OpenDialogOptions).then(value => { 110 | if (value !== undefined) { 111 | const macroConfig = vscode.workspace.getConfiguration('macro'); 112 | macroConfig.update('project.exportPath', value[0].fsPath, vscode.ConfigurationTarget.Global).then(() => { 113 | //Done 114 | }, reason => { 115 | vscode.window.showErrorMessage(`Failed to set 'export path'. Error: ${reason.message}`); 116 | console.error(reason); 117 | }); 118 | } 119 | }); 120 | } 121 | 122 | class ProjectService { 123 | 124 | private getRelativePath(p:string, workspacePath:string, includeWsFolder:boolean = false) : string { 125 | const rel = path.normalize(vscode.workspace.asRelativePath(p, includeWsFolder)); 126 | if (p === workspacePath) { 127 | return ''; 128 | } 129 | return rel; 130 | } 131 | 132 | public async clean() { 133 | pickFolder(async workspace => { 134 | const command = await this.getCleanCommand(workspace); 135 | if (command){ 136 | vscode.tasks.executeTask(new vscode.Task({type:'shell'}, workspace, 'Clean','macro', new vscode.ShellExecution(command))); 137 | } 138 | }); 139 | } 140 | 141 | /** 142 | * Compile current file 143 | */ 144 | public async compile() { 145 | const workspace = vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri); 146 | if (workspace) { 147 | const command = this.getCompileCommand(workspace); 148 | if (command){ 149 | vscode.tasks.executeTask(new vscode.Task({type:'shell'}, workspace, 'Compile','macro', new vscode.ShellExecution(command),'$macro')); 150 | } 151 | } 152 | } 153 | 154 | public async build() { 155 | 156 | pickFolder(async workspace => { 157 | 158 | const config = vscode.workspace.getConfiguration('macro', workspace); 159 | 160 | if (config.validate.onBuild) { 161 | vscode.commands.executeCommand('macro.action.validate', workspace.uri.toString()); 162 | } 163 | 164 | const makeFile = config.build.makeFile; 165 | const exportPath= config.project.exportPath; 166 | const compiler = config.build.compiler; 167 | const type = config.build.controlType; 168 | 169 | if (makeFile && makeFile.length > 0) { 170 | const arg = type?'-'+type:''; 171 | const args = [exportPath, 'make', compiler, arg]; 172 | const t = new vscode.Task({ type: 'shell'}, workspace, 'Make', 'macro', new vscode.ShellExecution('.\\'+makeFile, args)); 173 | vscode.tasks.executeTask(t); 174 | return; 175 | } 176 | 177 | const compileCommand = new Promise(async (resolve, reject) => { 178 | const source = this.getRelativePath(config.project.sourcePath, workspace.uri.fsPath, true); 179 | let glob = '**/*.{[sS][rR][cC]}'; 180 | if (source){ 181 | glob = path.join(source, '/', glob); 182 | } 183 | const srcFiles = await vscode.workspace.findFiles(new vscode.RelativePattern(workspace, glob)); 184 | if (!srcFiles){ 185 | reject(); 186 | return; 187 | } 188 | 189 | // filter file directories to compile all files in a directory at once 190 | let dirs:string[] = srcFiles.map(a => path.dirname(a.fsPath)); 191 | dirs = dirs.filter((v,i) => dirs.indexOf(v) === i); 192 | 193 | const lines:string[] = []; 194 | for (const dir of dirs) { 195 | const command = this.getCompileCommand(workspace, dir); 196 | if (command){ 197 | lines.push(command); 198 | lines.push('\n\r'); 199 | } 200 | } 201 | resolve(lines.join(' ')); 202 | }); 203 | 204 | const linkCommand = this.getLinkCommand(workspace); 205 | const result = await Promise.all([compileCommand, linkCommand]); 206 | 207 | const buildPath = config.project.buildPath; 208 | if (buildPath){ 209 | await vscode.workspace.fs.createDirectory(vscode.Uri.file(buildPath)); 210 | } 211 | 212 | if (result[0] && result[1]) { 213 | vscode.tasks.executeTask(new vscode.Task({type:'shell'}, workspace, 'Make','macro', new vscode.ShellExecution(result[0]+result[1]),'$macro')); 214 | } 215 | }); 216 | } 217 | 218 | public getCompileCommand(workspace:vscode.WorkspaceFolder, dir:string|undefined=undefined) : string | undefined { 219 | const config = vscode.workspace.getConfiguration('macro', workspace); 220 | const compiler = config.build.compiler; 221 | const type = config.build.controlType; 222 | const prm = config.build.compilerParams; 223 | const buildPath = config.project.buildPath; 224 | const currentFile = vscode.window.activeTextEditor?.document.uri.fsPath; 225 | 226 | let fileDir = ''; 227 | let filesPattern = ''; 228 | 229 | if (dir !== undefined) { 230 | const relativ = this.getRelativePath(dir, workspace.uri.fsPath); 231 | fileDir = relativ; 232 | filesPattern = path.join(relativ, '*.src'); 233 | } 234 | else if (currentFile){ 235 | fileDir = this.getRelativePath(path.dirname(currentFile), workspace.uri.fsPath); 236 | filesPattern = this.getRelativePath(currentFile, workspace.uri.fsPath); 237 | } 238 | else { 239 | return undefined; 240 | } 241 | 242 | let args:any[] = []; 243 | args.push(filesPattern); 244 | args.push('-'+type); 245 | args.push(prm); 246 | 247 | if (buildPath){ 248 | const buildDir = this.getRelativePath(buildPath, workspace.uri.fsPath); 249 | args.push('-Fo' + buildDir); 250 | args.push('-Fr' + buildDir); 251 | args.push('-Fp' + buildDir); 252 | } 253 | 254 | if (fileDir) { 255 | args.push('-Fl' + fileDir); 256 | } 257 | return compiler + ' '+ args.join(' '); 258 | } 259 | 260 | public async getLinkCommand(workspace:vscode.WorkspaceFolder, dir:string|undefined=undefined) : Promise { 261 | const config = vscode.workspace.getConfiguration('macro', workspace); 262 | const buildPath = config.project.buildPath; 263 | const exportPath= config.project.exportPath; 264 | const compiler = config.build.compiler; 265 | const params = config.build.linkerParams; 266 | const link = this.getRelativePath(config.project.linkPath, workspace.uri.fsPath); 267 | 268 | let glob = '**/*.{[lL][nN][kK]}'; 269 | if (link){ 270 | glob = path.join(link,'/', glob); 271 | } 272 | 273 | const lnkFiles = await vscode.workspace.findFiles(new vscode.RelativePattern(workspace, glob)); 274 | if (!lnkFiles) { 275 | return undefined; 276 | } 277 | 278 | const lines:string[] = []; 279 | let linkPath = ''; 280 | if (buildPath) { 281 | lines.push('cd ' + buildPath); 282 | lines.push('\n\r'); 283 | linkPath = '..\\'; 284 | } 285 | 286 | for (const file of lnkFiles) { 287 | lines.push(BUILD_SYSTEMS[compiler].linker + ' ' + params); 288 | lines.push(linkPath + this.getRelativePath(file.fsPath, workspace.uri.fsPath)); 289 | lines.push('\n\r'); 290 | lines.push(BUILD_SYSTEMS[compiler].card); 291 | lines.push(path.parse(this.getRelativePath(path.basename(file.fsPath), workspace.uri.fsPath)).name); 292 | lines.push('\n\r'); 293 | } 294 | 295 | if (exportPath) { 296 | let p = ''; 297 | if (path.isAbsolute(exportPath)) { 298 | p = path.normalize(exportPath); 299 | } 300 | else { 301 | p = path.join('..\\',this.getRelativePath(exportPath, workspace.uri.fsPath)); 302 | } 303 | lines.push('copy *.mem'); 304 | lines.push(p); 305 | } 306 | return lines.join(' '); 307 | } 308 | 309 | public async getCleanCommand(workspace:vscode.WorkspaceFolder) : Promise { 310 | const config = vscode.workspace.getConfiguration('macro', workspace); 311 | const makeFile = config.build.makeFile; 312 | 313 | // Execute internal build script 314 | if (makeFile === undefined || makeFile === '') { 315 | 316 | 317 | let source = this.getRelativePath(config.project.sourcePath, workspace.uri.fsPath); 318 | let build = this.getRelativePath(config.project.buildPath, workspace.uri.fsPath); 319 | if (source){ 320 | source = path.join(source,'/'); 321 | } 322 | else { 323 | source = ''; 324 | } 325 | if (build){ 326 | build = path.join(build,'/'); 327 | } 328 | else { 329 | build = ''; 330 | } 331 | const lines:string[] = []; 332 | lines.push('del ' + source + '*.LST'); 333 | lines.push('\n\r'); 334 | 335 | for (const ext of build_ext_glob){ 336 | lines.push('del ' + build + '*.'+ ext); 337 | lines.push('\n\r'); 338 | } 339 | return lines.join(' '); 340 | } 341 | else { 342 | const make = this.getRelativePath(path.dirname(makeFile), workspace.uri.fsPath); 343 | let glob = '**/{[cC][lL][eE][aA][nN]}.*'; 344 | if (make) { 345 | glob = path.join(make, '/', glob); 346 | } 347 | 348 | const files = await vscode.workspace.findFiles(new vscode.RelativePattern(workspace, glob)); 349 | if (files.length > 0) { 350 | const cleanFile = this.getRelativePath(files[0].fsPath, workspace.uri.fsPath); 351 | return '.\\' + cleanFile; 352 | } 353 | else { 354 | return '.\\' + makeFile + ' ' + ['0', 'clean'].join(' '); 355 | } 356 | } 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /client/src/common/compositeDisposable.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Subscription } from 'rxjs'; 7 | import Disposable, { IDisposable } from './disposable'; 8 | 9 | export default class CompositeDisposable extends Disposable { 10 | private disposables = new Subscription(); 11 | 12 | constructor(...disposables: IDisposable[]) { 13 | super(() => this.disposables.unsubscribe()); 14 | 15 | for (const disposable of disposables) { 16 | if (disposable) { 17 | this.add(disposable); 18 | } 19 | else { 20 | throw new Error('null disposables are not supported'); 21 | } 22 | } 23 | } 24 | 25 | public add(disposable: IDisposable) { 26 | if (!disposable) { 27 | throw new Error('disposable cannot be null'); 28 | } 29 | 30 | this.disposables.add(() => disposable.dispose()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client/src/common/disposable.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { Subscription } from 'rxjs'; 6 | 7 | export default class Disposable implements IDisposable { 8 | private onDispose: { (): void }; 9 | 10 | constructor(onDispose: { (): void } | Subscription) { 11 | if (!onDispose) { 12 | throw new Error('onDispose cannot be null or empty.'); 13 | } 14 | 15 | if (onDispose instanceof Subscription) { 16 | this.onDispose = () => onDispose.unsubscribe(); 17 | } 18 | else { 19 | this.onDispose = onDispose; 20 | } 21 | } 22 | 23 | public dispose = (): void => { 24 | this.onDispose(); 25 | } 26 | } 27 | 28 | export interface IDisposable { 29 | dispose: () => void; 30 | } -------------------------------------------------------------------------------- /client/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as vscode from 'vscode'; 3 | import * as lc from 'vscode-languageclient/node'; 4 | import * as ls from 'vscode-languageserver-protocol'; 5 | 6 | import registerCommands from './common/commands'; 7 | 8 | import CompositeDisposable from './common/compositeDisposable'; 9 | 10 | let client: lc.LanguageClient; 11 | let disposables = new CompositeDisposable(); 12 | 13 | interface ConfigurationSettings { 14 | validate? : { 15 | enable: boolean; 16 | workspace:boolean; 17 | }; 18 | codelens?: { 19 | enable:boolean; 20 | }; 21 | sequence?: { 22 | base:number; 23 | increment:number; 24 | } 25 | lint?: Object; 26 | keywords: Object[], 27 | workspaceFolder?: lc.WorkspaceFolder | undefined; 28 | callFunctions?: string[]; 29 | } 30 | 31 | interface CodeLensReferenceArgument { 32 | position: ls.Position, 33 | locations: ls.Location[] 34 | } 35 | 36 | export function activate(context: vscode.ExtensionContext) { 37 | 38 | // The server is implemented in node 39 | let serverModule = context.asAbsolutePath( 40 | path.join('server', 'out', 'server.js') 41 | ); 42 | 43 | let debugOptions = { execArgv: ['--nolazy', '--inspect=6011'], cwd: process.cwd() }; 44 | let serverOptions: lc.ServerOptions = { 45 | run: { module: serverModule, transport: lc.TransportKind.ipc, options: { cwd: process.cwd() } }, 46 | debug: { 47 | module: serverModule, 48 | transport: lc.TransportKind.ipc, 49 | options: debugOptions 50 | } 51 | }; 52 | 53 | let clientOptions: lc.LanguageClientOptions = { 54 | documentSelector: [{ language: 'macro', scheme: 'file' }], 55 | initializationOptions: vscode.workspace.getConfiguration('macro'), 56 | synchronize: { 57 | fileEvents: vscode.workspace.createFileSystemWatcher('**/*.{[sS][rR][cC],[dD][eE][fF],[lL][nN][kK]}') 58 | }, 59 | diagnosticCollectionName: 'macro', 60 | progressOnInitialization: true, 61 | revealOutputChannelOn: lc.RevealOutputChannelOn.Never, 62 | middleware: { 63 | 64 | executeCommand: async (command:string, args:any[], next:lc.ExecuteCommandSignature) => { 65 | if (command === 'macro.codelens.references') { 66 | const arg:CodeLensReferenceArgument = args[0]; 67 | 68 | 69 | const position = client.protocol2CodeConverter.asPosition(arg.position); 70 | const locations:vscode.Location[] = []; 71 | for (const location of arg.locations){ 72 | locations.push(client.protocol2CodeConverter.asLocation(location)); 73 | } 74 | 75 | if (vscode.window.activeTextEditor) { 76 | vscode.commands.executeCommand('editor.action.showReferences', vscode.window.activeTextEditor.document.uri, position, locations); 77 | } 78 | } 79 | else if (command === 'macro.action.refactorsequeces' || command === 'macro.action.addsequeces') { 80 | function validate(input:string): string | null { 81 | return Number.isInteger(Number(input)) ? null : 'Integer expected'; 82 | } 83 | 84 | const config = vscode.workspace.getConfiguration('macro'); 85 | let start = undefined; 86 | if (command === 'macro.action.refactorsequeces') { 87 | start = await vscode.window.showInputBox({ 88 | prompt: 'Start sequence number', 89 | value: config.sequence.base, 90 | validateInput: validate 91 | }); 92 | } 93 | 94 | const increment = await vscode.window.showInputBox({ 95 | prompt: 'Sequence number increment', 96 | value: config.sequence.increment, 97 | validateInput: validate 98 | }); 99 | 100 | if (vscode.window.activeTextEditor) { 101 | if (command === 'macro.action.addsequeces' && increment) { 102 | return next(command, [vscode.window.activeTextEditor.document.uri.toString(), vscode.window.activeTextEditor.selection.start, increment]); 103 | } 104 | else if (command === 'macro.action.refactorsequeces' && start && increment) { 105 | return next(command, [vscode.window.activeTextEditor.document.uri.toString(), vscode.window.activeTextEditor.selection.start, start, increment]); 106 | } 107 | } 108 | } 109 | else if (command === 'macro.action.validate') { 110 | const workspaceUri = args[0]; 111 | if (workspaceUri) { 112 | return next(command, [workspaceUri]); 113 | } 114 | else { 115 | pickFolder(workspace => { 116 | return next(command, [workspace.uri.toString()]); 117 | }); 118 | } 119 | } 120 | }, 121 | workspace : { 122 | configuration: async (params, _token, _next): Promise => { 123 | if (params.items === undefined) { 124 | return []; 125 | } 126 | const result: (ConfigurationSettings | null)[] = []; 127 | for (const item of params.items) { 128 | if (item.section || !item.scopeUri) { 129 | result.push(null); 130 | continue; 131 | } 132 | const resource = client.protocol2CodeConverter.asUri(item.scopeUri); 133 | const workspaceFolder = vscode.workspace.getWorkspaceFolder(resource); 134 | const config = vscode.workspace.getConfiguration('macro', workspaceFolder); 135 | const settings: ConfigurationSettings = { 136 | codelens: config.get('codelens'), 137 | lint:config.get('lint', {}), 138 | sequence: config.get('sequence'), 139 | validate: config.get('validate'), 140 | keywords: config.get('keywords'), 141 | callFunctions: config.get('callFunctions'), 142 | }; 143 | 144 | if (workspaceFolder !== undefined) { 145 | settings.workspaceFolder = { 146 | name: workspaceFolder.name, 147 | uri: client.code2ProtocolConverter.asUri(workspaceFolder.uri), 148 | }; 149 | } 150 | result.push(settings); 151 | } 152 | return result; 153 | } 154 | } 155 | } as any 156 | }; 157 | 158 | // Create the language client and start the client. 159 | client = new lc.LanguageClient( 160 | 'macroLanguageServer', 161 | 'Macro Language Server', 162 | serverOptions, 163 | clientOptions 164 | ); 165 | 166 | disposables.add(registerCommands()); 167 | context.subscriptions.push(disposables); 168 | context.subscriptions.push(client.start()); 169 | } 170 | 171 | export function deactivate(): Thenable | undefined { 172 | if (!client) { 173 | return undefined; 174 | } 175 | return client.stop(); 176 | } 177 | 178 | export function pickFolder(cb:(workspace: vscode.WorkspaceFolder) => void) { 179 | const folders = vscode.workspace.workspaceFolders; 180 | if (!folders) { 181 | return; 182 | } 183 | 184 | if (folders.length === 1) { 185 | cb(folders[0]); 186 | return; 187 | } 188 | vscode.window.showWorkspaceFolderPick({placeHolder:'', ignoreFocusOut:true}).then(selected => { 189 | if (selected) { 190 | cb(selected); 191 | } 192 | }); 193 | if (folders.length === 1) { 194 | cb(folders[0]); 195 | return; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /client/src/test/diagnostics.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Simon Waelti. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import * as assert from 'assert'; 8 | import { getDocUri, activate } from './helper'; 9 | 10 | suite('FanucMacroExecutor Extension Tests', () => { 11 | 12 | test('Test checking', async () => { 13 | await testDiagnostics(getDocUri('test.src'), [ 14 | ]); 15 | }); 16 | }); 17 | 18 | function toRange(sLine: number, sChar: number, eLine: number, eChar: number) { 19 | const start = new vscode.Position(sLine, sChar); 20 | const end = new vscode.Position(eLine, eChar); 21 | return new vscode.Range(start, end); 22 | } 23 | 24 | async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[]) { 25 | await activate(docUri); 26 | 27 | const actualDiagnostics = vscode.languages.getDiagnostics(docUri); 28 | 29 | assert.strictEqual(actualDiagnostics.length, expectedDiagnostics.length); 30 | 31 | expectedDiagnostics.forEach((expectedDiagnostic, i) => { 32 | const actualDiagnostic = actualDiagnostics[i]; 33 | assert.strictEqual(actualDiagnostic.message, expectedDiagnostic.message); 34 | assert.deepStrictEqual(actualDiagnostic.range, expectedDiagnostic.range); 35 | assert.strictEqual(actualDiagnostic.severity, expectedDiagnostic.severity); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /client/src/test/helper.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import * as vscode from 'vscode'; 7 | import * as path from 'path'; 8 | 9 | export let doc: vscode.TextDocument; 10 | export let editor: vscode.TextEditor; 11 | export let documentEol: string; 12 | export let platformEol: string; 13 | 14 | /** 15 | * Activates the extension 16 | */ 17 | export async function activate(docUri: vscode.Uri) { 18 | // The extensionId is `publisher.name` from package.json 19 | const ext = vscode.extensions.getExtension('isorp.macro-executor')!; 20 | await ext.activate(); 21 | try { 22 | doc = await vscode.workspace.openTextDocument(docUri); 23 | editor = await vscode.window.showTextDocument(doc); 24 | await sleep(2000); // Wait for server activation 25 | } catch (e) { 26 | console.error(e); 27 | } 28 | } 29 | 30 | async function sleep(ms: number) { 31 | return new Promise(resolve => setTimeout(resolve, ms)); 32 | } 33 | 34 | export const getDocPath = (p: string) => { 35 | return path.resolve(__dirname, '../../testFixture', p); 36 | }; 37 | export const getDocUri = (p: string) => { 38 | return vscode.Uri.file(getDocPath(p)); 39 | }; 40 | 41 | export async function setTestContent(content: string): Promise { 42 | const all = new vscode.Range( 43 | doc.positionAt(0), 44 | doc.positionAt(doc.getText().length) 45 | ); 46 | return editor.edit(eb => eb.replace(all, content)); 47 | } 48 | -------------------------------------------------------------------------------- /client/src/test/index.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import * as path from 'path'; 6 | import * as Mocha from 'mocha'; 7 | import * as glob from 'glob'; 8 | 9 | export function run(): Promise { 10 | // Create the mocha test 11 | const mocha = new Mocha({ 12 | ui: 'tdd', 13 | }); 14 | mocha.timeout(100000); 15 | 16 | const testsRoot = __dirname; 17 | 18 | return new Promise((resolve, reject) => { 19 | glob('**.test.js', { cwd: testsRoot }, (err, files) => { 20 | if (err) { 21 | return reject(err); 22 | } 23 | 24 | // Add files to the test suite 25 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 26 | 27 | try { 28 | // Run the mocha test 29 | mocha.run(failures => { 30 | if (failures > 0) { 31 | reject(new Error(`${failures} tests failed.`)); 32 | } else { 33 | resolve(); 34 | } 35 | }); 36 | } catch (err) { 37 | console.error(err); 38 | reject(err); 39 | } 40 | }); 41 | }); 42 | } -------------------------------------------------------------------------------- /client/src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as path from 'path'; 6 | 7 | import { runTests } from '@vscode/test-electron'; 8 | 9 | async function main() { 10 | try { 11 | // The folder containing the Extension Manifest package.json 12 | // Passed to `--extensionDevelopmentPath` 13 | const extensionDevelopmentPath = path.resolve(__dirname, '../../../'); 14 | 15 | // The path to test runner 16 | // Passed to --extensionTestsPath 17 | const extensionTestsPath = path.resolve(__dirname, './index'); 18 | 19 | // Download VS Code, unzip it and run the integration test 20 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 21 | } catch (err) { 22 | console.error('Failed to run tests'); 23 | process.exit(1); 24 | } 25 | } 26 | 27 | main(); 28 | -------------------------------------------------------------------------------- /client/testFixture/test.src: -------------------------------------------------------------------------------- 1 | @CONST 1000 /* 2 | @adr 1000 /* 3 | @var #1000 /* 4 | 5 | @CALL M98P /* 6 | @RETURN M99 /* 7 | 8 | @LOOP_I 1 /* 9 | 10 | @PARAM F /* 11 | 12 | @FUNCTIONS 1 /* 13 | @OPERATORS 2 /* 14 | @CONDITIONALS 3 /* 15 | @STATEMENTS 4 /* 16 | @ARRAY 5 /* 17 | @i 200 /* 18 | @k 200 /* 19 | @j 200 /* 20 | @[#_SYSVAR] #3000 /* 21 | @[#_SYSVAR[123]] #3123 /* 22 | 23 | /* 24 | O FUNCTIONS /* 25 | popen /* 26 | pclos /* 27 | dprnt[abcd] /* 28 | dprnt[X#100[3]**Y#101[3]***M#102[0]] /* 29 | bprnt[X#100[3]**Y#101[3]***M#102[0]] /* 30 | setvn 100[abc] /* 31 | fgen(1,1,1) /* 32 | fdel(1,1) /* 33 | fopen(1,1,1) /* 34 | fclos(1) /* 35 | fpset(1,1,1) /* 36 | fread(1,1,1) /* 37 | fwrit(1,1,1) /* 38 | #1 = sin[1] /* 39 | #1 = cos[1] /* 40 | #1 = tan[1] /* 41 | #1 = asin[1] /* 42 | #1 = acos[1] /* 43 | #1 = atan[1] /* 44 | #1 = ATAN[1, 2] /* 45 | #1 = atan[1]/[1] /* 46 | #1 = sqrt[1] /* 47 | #1 = abs[1] /* 48 | #1 = bin[1] /* 49 | #1 = bcd[1] /* 50 | #1 = round[1] /* 51 | #1 = fix[1] /* 52 | #1 = fup[1] /* 53 | #1 = ln[1] /* 54 | #1 = exp[1] /* 55 | #1 = POW[1, 1] /* 56 | #1 = adp[1] /* 57 | #1 = prm[1]/[1] /* 58 | #1 = prm[1] /* 59 | #1 = PRM[1, var] /* 60 | RETURN /* 61 | 62 | /* 63 | O OPERATORS /* 64 | #i=#j /* 65 | #i=#j+#k /* 66 | #i=#j-#k /* 67 | #i=#j OR #k /* 68 | #i=#j XOR #k /* 69 | #i=#j*#k /* 70 | #i=#j/#k /* 71 | #i=#j AND #k /* 72 | #i=#j MOD #k /* 73 | RETURN /* 74 | 75 | /* 76 | O ARRAY /* 77 | var = var<1+#1<1+#1+[111]>> /* 78 | RETURN /* 79 | 80 | /* 81 | O CONDITIONALS /* 82 | >L_Label 10 /* 83 | 84 | L_Label /* 85 | 86 | GOTO L_Label /* 87 | 88 | IF [var] THEN var = 1 /* 89 | ELSE var = 1 /* 90 | 91 | IF [var] THEN /* 92 | var = 1 /* 93 | ELSE var = 1 /* 94 | 95 | IF [var] THEN var = 1 /* 96 | 97 | ELSE /* 98 | var = 1 /* 99 | ENDIF /* 100 | 101 | IF [#k] THEN /* 102 | IF [#k] THEN #k = 1 /* 103 | IF [#k] THEN #k = 1 /* 104 | ENDIF /* 105 | ENDIF /* 106 | 107 | WHILE [var LT CONST] DO LOOP_I /* 108 | GOTO L_Label /* 109 | END LOOP_I /* 110 | RETURN /* 111 | 112 | /* 113 | O STATEMENTS 114 | 115 | N100 G01 G4.1 G[1] G var X1 Y-[#1+1] F360. /* 116 | N110 G01 X-#1 F360.1 /* 117 | PARAM 1 /* 118 | [#_SYSVAR[123]] = 1 119 | #1 = [#_SYSVAR] 120 | /* 121 | RETURN /* 122 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019"], 6 | "outDir": "out", 7 | "rootDir": "src", 8 | "sourceMap": true 9 | }, 10 | "include": ["src", "src/test"], 11 | "exclude": ["node_modules", ".vscode-test"] 12 | } -------------------------------------------------------------------------------- /doc/MacroLanguage.ebnf: -------------------------------------------------------------------------------- 1 | rNumber # Real number 2 | nNumber # Natural number 3 | integer # Natural number pos and neg 4 | filePath # Windows file path absolut or relative to the workspace 5 | 6 | nwl = '\n' 7 | digit = '0' | … | '9' 8 | char = 'A' | … | 'Z' | 'a' | … | 'z' 9 | 10 | condOp = 'eq' | 'ne' | 'le' | 'ge' | 'lt' | 'gt' 11 | logOp = '||' | '&&' 12 | binOp = 'and' | 'or' | 'xor' | '/' | '*' | '+' | '-' 13 | unOp = '+' | '-' 14 | fFunc = 'sin' | 'cos' | ... 15 | 16 | symbol = char | char symbol | nNumber | nNumber symbol # ab12cd 17 | 18 | varVal = nNumber | rNumber | integer # 100; 100.0; -+100 19 | = char, nNumber | char, rNumber # R100; R100.0 20 | = char, nNumber, char # M98P 21 | 22 | varDec = '@', symbol, varValue 23 | labelDec= '>', symbol, nNumber 24 | 25 | var = symbol | '#', symbol # symbol with varDec declaration 26 | label = symbol # symbol with labelDec declaration 27 | seq = 'N', nNumber 28 | ncCode = char, nNumber | char, nNumber, '.', digit # G01; G01.1 29 | 30 | term = [unOp], (var | label | Ffunc | address | symbol) 31 | 32 | decType = label | var # declaration type 33 | 34 | (* Binary expression*) 35 | binExpr = [ '[' ], term, [ ']' ] # var 36 | = [ '[' ], term, binOp, term , [ ']' ] # var + var 37 | = [ '[' ], binExpr, binOp, binExpr , [ ']' ] # var + var + var 38 | 39 | (* Conditional expression*) 40 | conExpr = '[', binExpr, ']' # 1+1 41 | = '[', binExpr, conOp, binExpr, ']' # 1+1 EQ 1 42 | = '[', conExpr, logOp, conExpr, ']' # 1+1 EQ 1 || 1 43 | 44 | (* Macro statement*) 45 | macroSt = var, '=', binExpr 46 | 47 | (* NC statement*) 48 | ncPrm = char, nNumber | char, binExpr # P10; P[1+2+3] 49 | ncSt = ncCode # G01 50 | = ncCode, ncPrm # G04 P10 51 | 52 | (* Conditionals *) 53 | then = 'THEN', macroSt, nwl 54 | else = 'ELSE', macroSt, nwl # then var = 123 55 | goto = 'GOTO', (label | var | nNumber), nwl 56 | 57 | if = 'IF', conExpr, goto 58 | = 'IF', conExpr, then, [ else ] 59 | = 'IF', conExpr, 'THEN', nwl, fbody, nwl, else 60 | = 'IF', conExpr, 'THEN', nwl, fbody, [nwl, 'ELSE', nwl, fbody ] nwl, 'ENDIF' 61 | 62 | while = 'WHILE', conExpr, 'DO', (label | nNumber), nwl, fbody, nwl, 'END', (label | nNumber), nwl 63 | 64 | (* Fuction *) 65 | fbody = { if | while | goto | macroSt | ncSt } 66 | ident = nNumber | symbol 67 | func = 'O', ident, fbody 68 | 69 | (* Macro file *) 70 | include = '$INCLUDE', filePath, '.def' 71 | mFile = { include | varDec | labelDec | function }, [ '%' ] 72 | 73 | 74 | (* Def file *) 75 | dFile = '$NOLIST' { varDec | labelDec } '$LIST' 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /doc/Tokens.md: -------------------------------------------------------------------------------- 1 | 2 | | Tokens | | | 3 | |---------------|-------|-------| 4 | EOF | | | End of file 5 | Dollar/Delim | $ | File def keyords 6 | AT/Delim | @ | Variable declaration 7 | GTS/Delim | > | Label declaration 8 | Hash | # | Macro value 9 | BracketL | [ | 10 | BracketR | ] | 11 | LogigOr | \|\| | 12 | LogigAnd | && | 13 | Comma | , | 14 | NewLine | \n | 15 | Whitespace | | 16 | Comment | /* | 17 | Comment | ; | 18 | Address | R100.0| 19 | AddressPartial | R100. | 20 | String | () ('') (**) | 21 | BadString | | 22 | UnquotedString | | 23 | Keyword | Keyword Tokens | 24 | Ffunc | Function Tokens | 25 | Delim | Delimiters | 26 | Symbol | Symbols | 27 | 28 | 29 | | Symbols 30 | |---------------| 31 | Macro identifiers, NC statements and numbers. A symbol always may defined as a declaration identifier.
32 | Below a few possible characters. 33 | || 34 | |---------------| 35 | [A-Z] | 36 | [a-z] | 37 | [0-9] | 38 | ! | 39 | . | 40 | _ | 41 | 42 | 43 | 44 | 45 | | Keyword Tokens 46 | |---------------| 47 | if | 48 | then | 49 | else | 50 | endif | 51 | goto | 52 | while | 53 | do | 54 | end | 55 | eq | 56 | ne | 57 | le | 58 | ge | 59 | lt | 60 | gt | 61 | and | 62 | or | 63 | xor | 64 | mod | 65 | 66 | 67 | | Function Tokens 68 | |---------------| 69 | sin | 70 | cos | 71 | tan | 72 | asin | 73 | acos | 74 | atan | 75 | sqrt | 76 | abs | 77 | bin | 78 | bcd | 79 | round | 80 | fix | 81 | fup | 82 | ln | 83 | exp | 84 | pow | 85 | adp | 86 | prm | 87 | 88 | 89 | | Delimiters 90 | |---------------| 91 | % | 92 | = | 93 | + | 94 | - | 95 | / | 96 | * | 97 | -------------------------------------------------------------------------------- /doc/key_words.txt: -------------------------------------------------------------------------------- 1 | 2 | // TextMate Grammars 3 | 4 | source.macro // Scope 5 | 6 | meta.function.macro 7 | storage.type.function.macro // O 8 | entity.name.function.macro // O 9 | 10 | constant.language.macro // TRUE, FALSE 11 | 12 | 13 | constant.numeric.macro // Numbers 14 | constant.numeric.macrovar.macro // Macro variable # 15 | punctuation.section.tag.macro // N-Number 16 | 17 | keyword.operator.arithmetic.macro // +, -, *, / 18 | keyword.operator.assignment.macro // = 19 | keyword.other.macro // EQ, LT, OR, AND, ... 20 | keyword.control.conditional.macro // IF, DO, ... 21 | 22 | support.function.math.macro // SIN, COS, ... 23 | keyword.other.macro // M-,G-Function 24 | storage.type.parameter.macro // P, X, R, ... 25 | 26 | 27 | meta.definition.label.macro 28 | entity.name.label.macro // Labels 29 | punctuation.definition.keyword.label.macro // > 30 | 31 | meta.definition.variable.macro 32 | punctuation.definition.keyword.variable.macro // @ 33 | variable.other.symbol.macro // @ 34 | 35 | variable.macro // any symbol 36 | 37 | variable.language.macro // $LIST, $INCLUDE, $, %, #, .. 38 | 39 | meta.brace.square.macro // [] 40 | 41 | string.other.macro // () 42 | string.quoted.single.macro // ('') 43 | string.quoted.double.macro // ("") 44 | string.quoted.other.macr // (**) 45 | 46 | markup.link.macro // include paths 47 | 48 | markup.italic.block_skip.macro // / 49 | 50 | comment.line.macro // /*, ; 51 | 52 | 53 | // Semantic Tokens 54 | "number": [ 55 | "constant.numeric" 56 | ], 57 | "variable": [ 58 | "variable.other.field" 59 | ], 60 | "symbol": [ 61 | "variable.other.numeric" 62 | ], 63 | "constant": [ 64 | "constant.other.numeric" 65 | ], 66 | "label": [ 67 | "entity.name.label" 68 | ], 69 | "code": [ 70 | "keyword.other" 71 | ], 72 | "parameter": [ 73 | "storage.type.parameter" 74 | ], 75 | "address": [ 76 | "support.variable.property" 77 | ] -------------------------------------------------------------------------------- /doc/settings.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.semanticTokenColorCustomizations": { 3 | "enabled": true, 4 | "rules": { 5 | "custom_1": "#089627", 6 | "custom_2": "#ff2600" 7 | } 8 | }, 9 | 10 | "macro.keywords": [ 11 | 12 | { 13 | "symbol": "10000", 14 | "scope":"number", 15 | "nodeType": "Variable", 16 | "description": [ 17 | "# some", 18 | "*markdown*", 19 | "text", 20 | "lines" 21 | ] 22 | }, 23 | 24 | {"symbol": "ret_arg", "scope":"symbol", "nodeType": "Variable"}, 25 | {"symbol": "error_1", "scope":"custom_2", "nodeType": "Variable"}, 26 | {"symbol": "error_2", "scope":"custom_2", "nodeType": "Variable"}, 27 | {"symbol": "TRUE", "scope":"number"}, 28 | {"symbol": "FALSE", "scope":"number"}, 29 | {"symbol": "TRUE", "scope":"number"}, 30 | {"symbol": "LOOP_I", "scope":"number"}, 31 | {"symbol": "LOOP_II", "scope":"number"}, 32 | {"symbol": "LOOP_III", "scope":"number"}, 33 | 34 | {"symbol": "M03", "description": "Spindle start clockwise"}, 35 | {"symbol": "M04", "description": "Spindle start counterclockwise"}, 36 | {"symbol": "M05", "description": "Spindle stop"} 37 | 38 | ] 39 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const ts = require('gulp-typescript'); 3 | const typescript = require('typescript'); 4 | const sourcemaps = require('gulp-sourcemaps'); 5 | const del = require('del'); 6 | const es = require('event-stream'); 7 | const vsce = require('@vscode/vsce'); 8 | const nls = require('vscode-nls-dev'); 9 | const sourceMap = require('source-map'); 10 | 11 | const clientProject = ts.createProject('./client/tsconfig.json', { typescript }); 12 | const serverProject = ts.createProject('./server/tsconfig.json', { typescript }); 13 | 14 | const inlineMap = true; 15 | const inlineSource = false; 16 | const clientOutDest = 'client/out'; 17 | const serverOutDest = 'server/out'; 18 | 19 | const languages = [ 20 | { id: 'de', folderName: 'deu' }, 21 | { id: 'zh-cn', folderName: 'chs', transifexId: 'zh-hans' } 22 | ]; 23 | 24 | sourceMap.SourceMapConsumer.initialize({ 25 | 'lib/mappings.wasm': require.resolve('source-map/lib/mappings.wasm') 26 | }); 27 | 28 | const cleanTask = function() { 29 | return del(['client/out/**', 'server/out/**', 'package.nls.*.json', 'macro-executor*.vsix']); 30 | }; 31 | 32 | const internalCompileTask = function() { 33 | let ret = doCompile(false, clientProject, clientOutDest); 34 | if (ret){ 35 | ret = doCompile(false, serverProject, serverOutDest); 36 | } 37 | return ret; 38 | }; 39 | 40 | const internalNlsCompileTask = function() { 41 | let ret = doCompile(true, clientProject, clientOutDest); 42 | if (ret){ 43 | ret = doCompile(true, serverProject, serverOutDest); 44 | } 45 | return ret; 46 | }; 47 | 48 | const addI18nTask = function() { 49 | return gulp.src(['package.nls.json']) 50 | .pipe(nls.createAdditionalLanguageFiles(languages, 'i18n')) 51 | .pipe(gulp.dest('.')); 52 | }; 53 | 54 | const buildTask = gulp.series(cleanTask, internalNlsCompileTask, addI18nTask); 55 | 56 | const doCompile = function (buildNls, project, out) { 57 | var r = project.src() 58 | .pipe(sourcemaps.init()) 59 | .pipe(project()).js 60 | .pipe(buildNls ? nls.rewriteLocalizeCalls() : es.through()) 61 | .pipe(buildNls ? nls.createAdditionalLanguageFiles(languages, 'i18n', out) : es.through()); 62 | 63 | if (inlineMap && inlineSource) { 64 | r = r.pipe(sourcemaps.write()); 65 | } else { 66 | r = r.pipe(sourcemaps.write('../out', { 67 | // no inlined source 68 | includeContent: inlineSource, 69 | // Return relative source map root directories per file. 70 | sourceRoot: '../src' 71 | })); 72 | } 73 | 74 | return r.pipe(gulp.dest(out)); 75 | }; 76 | 77 | const vscePublishTask = function() { 78 | return vsce.publish(); 79 | }; 80 | 81 | const vscePackageTask = function() { 82 | return vsce.createVSIX(); 83 | }; 84 | 85 | gulp.task('default', buildTask); 86 | 87 | gulp.task('clean', cleanTask); 88 | 89 | gulp.task('compile', gulp.series(cleanTask, internalCompileTask)); 90 | 91 | gulp.task('build', buildTask); 92 | 93 | gulp.task('publish', gulp.series(buildTask, vscePublishTask)); 94 | 95 | gulp.task('package', gulp.series(buildTask, vscePackageTask)); 96 | -------------------------------------------------------------------------------- /i18n/chs/package.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "macro.compile": "编译文件", 3 | "macro.build": "编译与连接项目", 4 | "macro.clean": "清除项目", 5 | "macro.setCompiler": "选择宏编译器", 6 | "macro.setExportPath": "选择输出路径", 7 | "macro.setControlType": "选择NC系统类型", 8 | "macro.action.addsequeces": "添加缺失的顺序号", 9 | "macro.action.refactorsequeces": "重排顺序号", 10 | "macro.action.validate" : "检查项目文件夹", 11 | "macro.callFunctions.description": "子程序调用", 12 | "macro.lint.description": "Lint设置", 13 | "macro.sequence.base.description": "顺序起始号", 14 | "macro.sequence.increment.description": "顺序号增量", 15 | "macro.validate.enable.description": "代码校验", 16 | "macro.validate.workspace.description": "检查项目文件夹仅在 `#macro.validate.enable#` 设为`真`时有效", 17 | "macro.validate.onBuild.description": "在编译项目的时候启用或者禁用检查项目文件夹。仅在 `#macro.validate.enable#` 设为`真`时有效", 18 | "macro.build.compiler.description": "选择宏编译器", 19 | "macro.build.controlType.description": "选择NC系统类型", 20 | "macro.build.compilerParams.description": "附加其他编译参数 -NR -L1 -L2 -L3 -PR", 21 | "macro.build.linkerParams.description" : "附加其他链接参数: -NR -NL -Fm -Fr", 22 | "macro.build.makeFile.description": "生成文件路径。使用以下参数:输出路径、 [make, clean]、编译器、控制类型。若路径下未发现Clean.bat,则自动调用参数clean。若未指定路径,则使用内部生成系统。", 23 | "macro.project.exportPath.description": "存储卡文件(.mem) 路径", 24 | "macro.project.sourcePath.description": "源文件 (.src)路径", 25 | "macro.project.buildPath.description": "生成文件路径", 26 | "macro.project.linkPath.description": "链接文件(.lnk) 与库文件(.mex)路径" 27 | } 28 | -------------------------------------------------------------------------------- /i18n/chs/server/out/macroLanguageService/parser/macroErrors.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "expected.definition": "预定义文件缺失", 3 | "unknown.keyword": "未知关键词", 4 | "expected.endif": "endif缺失", 5 | "expected.end": "end缺失", 6 | "expected.do": "do缺失", 7 | "expected.thenGoto": "Then或者goto缺失", 8 | "expected.newline": "换行符缺失", 9 | "expected.lsquare": "[缺失", 10 | "expected.rsquare": "]缺失", 11 | "expected.lparent" : "(缺失", 12 | "expected.rparent" : ")缺失", 13 | "expected.rangle": ">缺失", 14 | "expected.badstring": "分隔符缺失", 15 | "expected.operator": "运算符缺失", 16 | "expected.ident": "标识符缺失", 17 | "expected.funcident": "函数标识符缺失", 18 | "expected.address": "地址缺失", 19 | "expected.macrovariable": "#变量缺失", 20 | "expected.prntformat" : "格式[F]缺失或错误", 21 | "expected.body": "函数缺失", 22 | "expected.label": "标签缺失", 23 | "expected.term": "语句缺失", 24 | "expected.number": "数值缺失", 25 | "expected.integer": "整数缺失", 26 | "expected.parameter": "参数缺失", 27 | "expected.invalidstatement": "无效的注释", 28 | "expected.expression": "语句缺失", 29 | "expected.unexpectedToken": "符号缺失", 30 | "expected.equalExpected": "=号缺失", 31 | "symbol.symbolError": "符号定义或使用错误", 32 | "invalid.variableName": "无效的变量名" 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /i18n/chs/server/out/macroLanguageService/services/lintRules.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "rule.duplicateInclude": "预加载重复", 3 | "rule.duplicateDeclaration": "符号声明重复", 4 | "rule.duplicateFunction": "函数重复", 5 | "rule.duplicateAddress": "地址重复", 6 | "rule.duplicateSequence": "顺序号重复", 7 | "rule.duplicateLabel": "标签号重复", 8 | "rule.duplicateLabelSequence": "顺序号和标签号重复", 9 | "rule.unknownSymbol": "未定义符号", 10 | "rule.whileLogicOperator": "WHILE语句中不能使用&&与||运算符", 11 | "rule.doEndNumberTooBig": "DO或END数值太大'", 12 | "rule.doEndNumberNotEqual": "END数值与DO数值不同", 13 | "rule.nestingTooDeep": "嵌套太深", 14 | "rule.mixedConditionals": "一个IF语句中不可同时使用&&与||运算符", 15 | "rule.tooManyConditionals": "条件过多", 16 | "rule.seqNotFound" : "顺序号或标签未找到", 17 | "rule.duplicateDoEndNumber": "DO或END号码重复", 18 | "rule.incompleteParameter": "参数不完整,G代码或M代码需要数值或变量做参数", 19 | "rule.includeNotFound": "预加载文件未找到", 20 | "rule.assignmentConstant": "常数已赋值", 21 | "rule.blockDelNumber" : "功能块编号错误", 22 | "rule.unsuitableNNAddress" : "地址NN在G10/G11块之外", 23 | "rule.dataInputNotClosed" : "G10模式下缺少G11" 24 | } 25 | -------------------------------------------------------------------------------- /i18n/chs/server/out/server.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "message.refactorsequeces.success": "重排顺序号完成", 3 | "message.addsequeces.success": "添加顺序号完成", 4 | "message.validationdisabled" : "项目文件夹验证未启用" 5 | } 6 | -------------------------------------------------------------------------------- /i18n/deu/package.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "macro.compile": "Datei kompilieren", 3 | "macro.build": "Projekt erstellen", 4 | "macro.clean": "Projekt bereinigen", 5 | "macro.setCompiler": "Macro Compiler auswählen", 6 | "macro.setExportPath": "Exportpfad auswählen", 7 | "macro.setControlType": "Steuerungstyp auswählen", 8 | "macro.action.addsequeces": "Fehlende Sequenznummern hinzufügen", 9 | "macro.action.refactorsequeces": "Sequenznummern neu ordnen", 10 | "macro.action.validate" : "Überprüfung des gesamten Arbeitsbereichs", 11 | "macro.callFunctions.description": "Benutzerdefinierte Funktionen zum Aufrufen von Unterprogrammen", 12 | "macro.lint.description": "Lint Einstellungen", 13 | "macro.sequence.base.description": "Startnummer der Sequenzen für das Refactoring", 14 | "macro.sequence.increment.description": "Inkrement der Sequenzen für Refactoring", 15 | "macro.validate.enable.description": "Aktiviert oder deaktiviert alle Überprüfungen", 16 | "macro.validate.workspace.description": "Aktiviert oder deaktiviert die Überprüfung des gesamten Arbeitsbereichs. Gilt nur wenn `#macro.validate.enable#` auf `true` gesetzt ist.", 17 | "macro.validate.onBuild.description": "Aktiviert oder deaktiviert die Überprüfung des gesamten Arbeitsbereichs beim Erstellen des Projekts. Gilt nur wenn `#macro.validate.enable#` auf `true` gesetzt ist.", 18 | "macro.build.compiler.description": "Auswahl des Macro-Compilers", 19 | "macro.build.controlType.description": "Auswahl des Steuerungstyps", 20 | "macro.build.compilerParams.description": "Zusätzliche Compilerparameter: -NR, -L1, -L2, -L3, -PR", 21 | "macro.build.linkerParams.description" : "Zusätzliche Linkerparameter: -NR, -NL, -Fm, -Fr", 22 | "macro.build.makeFile.description": "Der Pfad zum Makefile. Folgende Argumente werden übergeben: Exportverzeichnis, [make, clean], Compiler, Steuerungstyp. Wenn im Verzeichnis kein Clean.bat gefunden wird, wird als Argument 'clean' verwendet. Wenn kein Pfad angegeben wird, wird das interne Buildsystem verwendet.", 23 | "macro.project.exportPath.description": "Der Pfad zu dem Verzeichnis für die Speicherkartendatein (.mem)", 24 | "macro.project.sourcePath.description": "Der Pfad zu dem Verzeichnis wo die Source-Dateien (.src) liegen", 25 | "macro.project.buildPath.description": "Der Pfad zu dem Verzeichnis wo der Compiler die erstellten Dateien ablegt", 26 | "macro.project.linkPath.description": "Der Pfad zu dem Verzeichnis wo die Link-Dateien (.lnk) liegen. Im gleichen Verzeichnis befindet sich auch die Bibliothek .mex" 27 | } 28 | -------------------------------------------------------------------------------- /i18n/deu/server/out/macroLanguageService/parser/macroErrors.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "expected.definition": "Definitionsdatei erwartet", 3 | "unknown.keyword": "Unbekanntes Schlüsselwort", 4 | "expected.endif": "ENDIF erwartet", 5 | "expected.end": "END erwartet", 6 | "expected.do": "DO erwartet", 7 | "expected.thenGoto": "THEN oder GOTO erwartet", 8 | "expected.newline": "Zeilenumbruch erwartet", 9 | "expected.lsquare": "[ erwartet", 10 | "expected.rsquare": "] erwartet", 11 | "expected.lparent" : "( erwartet", 12 | "expected.rparent" : ") erwartet", 13 | "expected.rangle": "> erwartet", 14 | "expected.badstring": "Fehlendes Trennzeichen [', *, )]", 15 | "expected.operator": "Operator erwartet", 16 | "expected.ident": "Symbol erwartet", 17 | "expected.funcident": "Funktionssymbol erwartet", 18 | "expected.address": "Adresse erwartet", 19 | "expected.macrovariable": "Variable (#) erwartet", 20 | "expected.prntformat" : "Format [F] erwartet", 21 | "expected.body": "Body erwartet", 22 | "expected.label": "Label erwartet", 23 | "expected.term": "Term erwartet", 24 | "expected.number": "Nummer erwartet", 25 | "expected.integer": "Ganzzahlig erwartet", 26 | "expected.parameter": "Parameter erwartet", 27 | "expected.invalidstatement": "Ungültige Anweisung", 28 | "expected.expression": "Ausdruck erwartet", 29 | "expected.unexpectedToken": "Unerwartetes Token", 30 | "expected.equalExpected": " = erwartet", 31 | "symbol.symbolError": "Unangebrachte Symboldefinition", 32 | "invalid.variableName": "Ungültiger Variablennamen" 33 | } 34 | -------------------------------------------------------------------------------- /i18n/deu/server/out/macroLanguageService/services/lintRules.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "rule.duplicateInclude": "Include Datei mehrfach vorhanden", 3 | "rule.duplicateDeclaration": "Doppelte Symboldeklaration", 4 | "rule.duplicateFunction": "Doppelter Funktionsbezeichner", 5 | "rule.duplicateAddress": "Doppelte Adresse", 6 | "rule.duplicateSequence": "Doppelte Sequenznummer", 7 | "rule.duplicateLabel": "Doppelte Labelnummer", 8 | "rule.duplicateLabelSequence": "Sequenznummer und Label definieren den gleichen Wert", 9 | "rule.unknownSymbol": "Symboldeklaration nicht gefunden", 10 | "rule.whileLogicOperator": "Logischer Operator in WHILE Anweisung [&&, ||]", 11 | "rule.doEndNumberTooBig": "DO oder END Nummer zu hoch", 12 | "rule.doEndNumberNotEqual": "DO und END Nummer stimmen nicht überein", 13 | "rule.nestingTooDeep": "Verschachtelung zu tief", 14 | "rule.mixedConditionals": "Vermischte logische operatoren [&&, ||]", 15 | "rule.tooManyConditionals": "Zu viele Bedingungen", 16 | "rule.seqNotFound" : "Sequenznummer oder label nicht vorhanden", 17 | "rule.duplicateDoEndNumber" : "Doppelte DO oder END Nummer", 18 | "rule.incompleteParameter": "Unvollständiger Parameter gefunden. G-Code oder M-Code benötigen möglicherweise einen numerischen Wert oder eine Variable als Parameter", 19 | "rule.includeNotFound": "Datei nicht gefunden", 20 | "rule.assignmentConstant": "Die Zuweisung erfolgt auf eine Konstante", 21 | "rule.blockDelNumber" : "BLOCKDEL Nummer liegt ausserhalb des zulässigen Bereichs 1-9", 22 | "rule.unsuitableNNAddress" : "Die Adresse NN befindet sich außerhalb eines G10 / G11-Blocks", 23 | "rule.dataInputNotClosed" : "Die Dateneingabe nicht mittels G11 beendet" 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /i18n/deu/server/out/server.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "message.refactorsequeces.success": "Sequenznummern erfolgreich geändert", 3 | "message.addsequeces.success": "Sequenznummern erfolgreich hinzugefügt", 4 | "message.validationdisabled" : "Die Validierung ist deaktiviert" 5 | } 6 | -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "macro.compile": "Compile File", 3 | "macro.build": "Compile and Link Project", 4 | "macro.clean": "Clean Project", 5 | "macro.setCompiler": "Select Macro Compiler", 6 | "macro.setExportPath": "Select Export Path", 7 | "macro.setControlType": "Select Control Type", 8 | "macro.action.addsequeces": "Add missing sequence numbers", 9 | "macro.action.refactorsequeces": "Reorganize sequence numbers", 10 | "macro.action.validate" : "Validate Workspace", 11 | "macro.callFunctions.description": "Custom call functions", 12 | "macro.lint.description": "Lint settings", 13 | "macro.sequence.base.description": "Sequences start number for refactoring", 14 | "macro.sequence.increment.description": "Sequences increment for refactoring", 15 | "macro.validate.enable.description": "Enables or disables the validation", 16 | "macro.validate.workspace.description": "Enables or disables the workspace validation. Only applies when `#macro.validate.enable#` is set to `true`.", 17 | "macro.validate.onBuild.description": "Enables or disables the workspace validation when building the project. Only applies when `#macro.validate.enable#` is set to `true`.", 18 | "macro.build.compiler.description": "Selection of the macro compiler", 19 | "macro.build.controlType.description": "Selection of the control type", 20 | "macro.build.compilerParams.description": "Additional compiler parameters: -NR, -L1, -L2, -L3, -PR", 21 | "macro.build.linkerParams.description" : "Additional linker parameters: -NR, -NL, -Fm, -Fr", 22 | "macro.build.makeFile.description": "The path to the makefile. The following parameters are passed: Export directory, [make, clean], Compiler, Control type. If in the directory no Clean.bat is found, the parameter 'clean' is used. If no path is specified, the internal build system is used.", 23 | "macro.project.exportPath.description": "The path to the directory for the memory card file (.mem)", 24 | "macro.project.sourcePath.description": "The path to the directory for the source files (.src)", 25 | "macro.project.buildPath.description": "The path to the directory for the build files", 26 | "macro.project.linkPath.description": "The path to the directory for the link files (.lnk)" 27 | } 28 | -------------------------------------------------------------------------------- /resources/codelens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/codelens.png -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/icon.png -------------------------------------------------------------------------------- /resources/implementations.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/implementations.gif -------------------------------------------------------------------------------- /resources/mrworkspaces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/mrworkspaces.png -------------------------------------------------------------------------------- /resources/navigation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/navigation.gif -------------------------------------------------------------------------------- /resources/no_semantic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/no_semantic.png -------------------------------------------------------------------------------- /resources/projectsetting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/projectsetting.png -------------------------------------------------------------------------------- /resources/references.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/references.gif -------------------------------------------------------------------------------- /resources/sequenceref.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/sequenceref.gif -------------------------------------------------------------------------------- /resources/sequences.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/sequences.gif -------------------------------------------------------------------------------- /resources/validation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/validation.gif -------------------------------------------------------------------------------- /resources/with_semantic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iSorp/macro-executor/8a10e211b145936685e73ac2d93da5e0f7cf9a72/resources/with_semantic.png -------------------------------------------------------------------------------- /scripts/e2e.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export CODE_TESTS_PATH="$(pwd)/client/out/test" 4 | export CODE_TESTS_WORKSPACE="$(pwd)/client/src/test/testFixture" 5 | 6 | node "$(pwd)/client/out/test/runTest" -------------------------------------------------------------------------------- /server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc.base.json", 3 | "rules": { 4 | "no-console": "error" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsp-macro-server", 3 | "version": "0.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "lsp-macro-server", 9 | "version": "0.1.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "glob": "^7.2.0", 13 | "vscode-languageserver": "7.0.0", 14 | "vscode-languageserver-textdocument": "^1.0.12", 15 | "vscode-nls": "^5.2.0", 16 | "vscode-uri": "^3.0.8" 17 | }, 18 | "engines": { 19 | "node": "*" 20 | } 21 | }, 22 | "node_modules/balanced-match": { 23 | "version": "1.0.2", 24 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 25 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 26 | }, 27 | "node_modules/brace-expansion": { 28 | "version": "1.1.11", 29 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 30 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 31 | "dependencies": { 32 | "balanced-match": "^1.0.0", 33 | "concat-map": "0.0.1" 34 | } 35 | }, 36 | "node_modules/concat-map": { 37 | "version": "0.0.1", 38 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 39 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 40 | }, 41 | "node_modules/fs.realpath": { 42 | "version": "1.0.0", 43 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 44 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 45 | }, 46 | "node_modules/glob": { 47 | "version": "7.2.0", 48 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 49 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 50 | "dependencies": { 51 | "fs.realpath": "^1.0.0", 52 | "inflight": "^1.0.4", 53 | "inherits": "2", 54 | "minimatch": "^3.0.4", 55 | "once": "^1.3.0", 56 | "path-is-absolute": "^1.0.0" 57 | }, 58 | "engines": { 59 | "node": "*" 60 | }, 61 | "funding": { 62 | "url": "https://github.com/sponsors/isaacs" 63 | } 64 | }, 65 | "node_modules/inflight": { 66 | "version": "1.0.6", 67 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 68 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 69 | "dependencies": { 70 | "once": "^1.3.0", 71 | "wrappy": "1" 72 | } 73 | }, 74 | "node_modules/inherits": { 75 | "version": "2.0.4", 76 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 77 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 78 | }, 79 | "node_modules/minimatch": { 80 | "version": "3.0.4", 81 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 82 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 83 | "dependencies": { 84 | "brace-expansion": "^1.1.7" 85 | }, 86 | "engines": { 87 | "node": "*" 88 | } 89 | }, 90 | "node_modules/once": { 91 | "version": "1.4.0", 92 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 93 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 94 | "dependencies": { 95 | "wrappy": "1" 96 | } 97 | }, 98 | "node_modules/path-is-absolute": { 99 | "version": "1.0.1", 100 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 101 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 102 | "engines": { 103 | "node": ">=0.10.0" 104 | } 105 | }, 106 | "node_modules/vscode-jsonrpc": { 107 | "version": "6.0.0", 108 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", 109 | "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", 110 | "engines": { 111 | "node": ">=8.0.0 || >=10.0.0" 112 | } 113 | }, 114 | "node_modules/vscode-languageserver": { 115 | "version": "7.0.0", 116 | "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", 117 | "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", 118 | "dependencies": { 119 | "vscode-languageserver-protocol": "3.16.0" 120 | }, 121 | "bin": { 122 | "installServerIntoExtension": "bin/installServerIntoExtension" 123 | } 124 | }, 125 | "node_modules/vscode-languageserver-protocol": { 126 | "version": "3.16.0", 127 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", 128 | "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", 129 | "dependencies": { 130 | "vscode-jsonrpc": "6.0.0", 131 | "vscode-languageserver-types": "3.16.0" 132 | } 133 | }, 134 | "node_modules/vscode-languageserver-textdocument": { 135 | "version": "1.0.12", 136 | "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", 137 | "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==" 138 | }, 139 | "node_modules/vscode-languageserver-types": { 140 | "version": "3.16.0", 141 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", 142 | "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" 143 | }, 144 | "node_modules/vscode-nls": { 145 | "version": "5.2.0", 146 | "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", 147 | "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==" 148 | }, 149 | "node_modules/vscode-uri": { 150 | "version": "3.0.8", 151 | "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", 152 | "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" 153 | }, 154 | "node_modules/wrappy": { 155 | "version": "1.0.2", 156 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 157 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 158 | } 159 | }, 160 | "dependencies": { 161 | "balanced-match": { 162 | "version": "1.0.2", 163 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 164 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 165 | }, 166 | "brace-expansion": { 167 | "version": "1.1.11", 168 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 169 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 170 | "requires": { 171 | "balanced-match": "^1.0.0", 172 | "concat-map": "0.0.1" 173 | } 174 | }, 175 | "concat-map": { 176 | "version": "0.0.1", 177 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 178 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 179 | }, 180 | "fs.realpath": { 181 | "version": "1.0.0", 182 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 183 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 184 | }, 185 | "glob": { 186 | "version": "7.2.0", 187 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 188 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 189 | "requires": { 190 | "fs.realpath": "^1.0.0", 191 | "inflight": "^1.0.4", 192 | "inherits": "2", 193 | "minimatch": "^3.0.4", 194 | "once": "^1.3.0", 195 | "path-is-absolute": "^1.0.0" 196 | } 197 | }, 198 | "inflight": { 199 | "version": "1.0.6", 200 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 201 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 202 | "requires": { 203 | "once": "^1.3.0", 204 | "wrappy": "1" 205 | } 206 | }, 207 | "inherits": { 208 | "version": "2.0.4", 209 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 210 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 211 | }, 212 | "minimatch": { 213 | "version": "3.0.4", 214 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 215 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 216 | "requires": { 217 | "brace-expansion": "^1.1.7" 218 | } 219 | }, 220 | "once": { 221 | "version": "1.4.0", 222 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 223 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 224 | "requires": { 225 | "wrappy": "1" 226 | } 227 | }, 228 | "path-is-absolute": { 229 | "version": "1.0.1", 230 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 231 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 232 | }, 233 | "vscode-jsonrpc": { 234 | "version": "6.0.0", 235 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", 236 | "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==" 237 | }, 238 | "vscode-languageserver": { 239 | "version": "7.0.0", 240 | "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", 241 | "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", 242 | "requires": { 243 | "vscode-languageserver-protocol": "3.16.0" 244 | } 245 | }, 246 | "vscode-languageserver-protocol": { 247 | "version": "3.16.0", 248 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", 249 | "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", 250 | "requires": { 251 | "vscode-jsonrpc": "6.0.0", 252 | "vscode-languageserver-types": "3.16.0" 253 | } 254 | }, 255 | "vscode-languageserver-textdocument": { 256 | "version": "1.0.12", 257 | "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", 258 | "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==" 259 | }, 260 | "vscode-languageserver-types": { 261 | "version": "3.16.0", 262 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", 263 | "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" 264 | }, 265 | "vscode-nls": { 266 | "version": "5.2.0", 267 | "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", 268 | "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==" 269 | }, 270 | "vscode-uri": { 271 | "version": "3.0.8", 272 | "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", 273 | "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" 274 | }, 275 | "wrappy": { 276 | "version": "1.0.2", 277 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 278 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsp-macro-server", 3 | "description": "Fanuc Macro-Executor Programming Language", 4 | "version": "0.1.0", 5 | "author": "iSorp", 6 | "publisher": "iSorp", 7 | "license": "MIT", 8 | "engines": { 9 | "node": "*" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/iSorp/macro-executor.git" 14 | }, 15 | "dependencies": { 16 | "glob": "^7.2.0", 17 | "vscode-languageserver": "7.0.0", 18 | "vscode-languageserver-textdocument": "^1.0.12", 19 | "vscode-nls": "^5.2.0", 20 | "vscode-uri": "^3.0.8" 21 | }, 22 | "scripts": {} 23 | } 24 | -------------------------------------------------------------------------------- /server/src/fileProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as path from 'path'; 8 | import { readFileSync } from 'fs'; 9 | import { 10 | MacroFileInfo, 11 | MacroFileProvider, 12 | FileProviderParams, 13 | ALL_FILES, 14 | MacroFileType, 15 | Macrofile 16 | } from './macroLanguageService/macroLanguageTypes'; 17 | import { Parser } from './macroLanguageService/parser/macroParser'; 18 | import * as glob from 'glob'; 19 | 20 | import { 21 | TextDocuments, 22 | } from 'vscode-languageserver/node'; 23 | 24 | import { 25 | TextDocument, 26 | } from 'vscode-languageserver-textdocument'; 27 | 28 | import { URI, Utils } from 'vscode-uri'; 29 | 30 | export const parsedDocuments: Map = new Map(); 31 | 32 | export class FileProvider implements MacroFileProvider { 33 | 34 | constructor(private workspaceFolder:string, private documents: TextDocuments, private connection?:any) {} 35 | 36 | public get(file: string): MacroFileInfo | undefined { 37 | 38 | const uri = this.resolveReference(file); 39 | if (uri) { 40 | 41 | let doc = getParsedDocument(this.documents, uri, (document => { 42 | const parser = new Parser(this); 43 | return parser.parseMacroFile(document); 44 | })); 45 | if (!doc) { 46 | try { 47 | const file = readFileSync(URI.parse(uri).fsPath, 'utf-8'); 48 | const document = TextDocument.create(uri!, 'macro', 1, file.toString()); 49 | try { 50 | const macrofile = new Parser(this).parseMacroFile(document); 51 | doc = { 52 | macrofile: macrofile, 53 | document: document, 54 | version: 1, 55 | type: getMacroFileType(document.uri) 56 | }; 57 | parsedDocuments.set(uri, doc); 58 | } 59 | catch (err){ 60 | this.connection?.console.log(err); 61 | } 62 | } 63 | catch (err) { 64 | this.connection?.console.log(err); 65 | return undefined; 66 | } 67 | } 68 | return doc; 69 | } 70 | return undefined; 71 | } 72 | 73 | public getAll(param?:FileProviderParams) { 74 | let types:MacroFileInfo[] = []; 75 | 76 | try { 77 | const dir = URI.parse(this.workspaceFolder).fsPath; 78 | let files:string[] = []; 79 | if (param?.uris){ 80 | files = param.uris; 81 | } 82 | else if (param?.glob) { 83 | files = glob.sync(dir + param.glob); 84 | } 85 | else { 86 | files = glob.sync(dir + ALL_FILES); 87 | } 88 | 89 | for (const file of files) { 90 | let type = this.get(file); 91 | if (type){ 92 | types.push(type); 93 | } 94 | } 95 | } catch (err){ 96 | this.connection.console.log(err); 97 | } 98 | return types; 99 | } 100 | 101 | public resolveReference(ref: string): string | undefined { 102 | 103 | if (ref.startsWith('file:///')) { 104 | return ref; 105 | } 106 | let absolutPath = ref; 107 | if (!path.isAbsolute(ref)) { 108 | absolutPath = Utils.resolvePath(URI.parse(this.workspaceFolder), ref).fsPath; 109 | } 110 | absolutPath = this.resolvePathCaseSensitive(absolutPath); 111 | return absolutPath ? URI.file(absolutPath).toString() :undefined; 112 | } 113 | 114 | private resolvePathCaseSensitive(file:string) { 115 | let norm = path.normalize(file); 116 | let root = path.parse(norm).root; 117 | let p = norm.slice(Math.max(root.length - 1, 0)); 118 | return glob.sync(p, { nocase: true, cwd: root })[0]; 119 | } 120 | } 121 | 122 | export function getMacroFileType(uri: string) : MacroFileType { 123 | var fileExt = uri.split('.').pop().toLocaleLowerCase(); 124 | switch(fileExt) { 125 | case 'def': 126 | return MacroFileType.DEF; 127 | case 'lnk': 128 | return MacroFileType.LNK; 129 | default: 130 | return MacroFileType.SRC; 131 | } 132 | } 133 | 134 | export function getParsedDocument(documents: TextDocuments, uri: string, parser:((document:TextDocument) => Macrofile), parse:boolean=false) : MacroFileInfo | undefined { 135 | let document = documents.get(uri); 136 | if (document) { 137 | let parsed = parsedDocuments.get(uri); 138 | if (parsed) { 139 | if (document.version !== parsed.version || parse) { 140 | parsedDocuments.set(uri , { 141 | macrofile: parser(document), 142 | document: document, 143 | version: document.version, 144 | type: getMacroFileType(document.uri) 145 | }); 146 | } 147 | } 148 | else { 149 | parsedDocuments.set(uri, { 150 | macrofile: parser(document), 151 | document: document, 152 | version: document.version, 153 | type: getMacroFileType(document.uri) 154 | }); 155 | } 156 | } 157 | return parsedDocuments.get(uri); 158 | } 159 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/macroLanguageService.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import { Parser } from './parser/macroParser'; 8 | import { MacroHover } from './services/macroHover'; 9 | import { MacroNavigation as MacroNavigation } from './services/macroNavigation'; 10 | import { MacroValidation } from './services/macroValidation'; 11 | import { MacroCompletion } from './services/macroCompletions'; 12 | import { MacroCommand } from './services/macroCommands'; 13 | import { MacroCallHierarchy } from './services/macroCallHierarchy'; 14 | import { MacroSemantic } from './services/macroSemantic'; 15 | import { MacroDocumentFormatting } from './services/macroDocumentFormatting'; 16 | 17 | import { 18 | LanguageSettings, LanguageServiceOptions, DocumentContext, 19 | DocumentLink, SymbolInformation, Diagnostic, Position, Hover, 20 | Location, TextDocument, CompletionList, CodeLens, 21 | TextDocumentEdit, WorkspaceEdit,SignatureHelp, Range, SemanticTokens, 22 | CallHierarchyItem, CallHierarchyIncomingCall, CallHierarchyOutgoingCall, 23 | FormattingOptions, TextEdit, Macrofile 24 | } from './macroLanguageTypes'; 25 | 26 | export interface LanguageService { 27 | doValidation(document: TextDocument, file: Macrofile, documentSettings: LanguageSettings): Diagnostic[]; 28 | parseMacroFile(document: TextDocument): Macrofile; 29 | doHover(document: TextDocument, position: Position, macroFile: Macrofile, documentSettings: LanguageSettings):Hover | null; 30 | doComplete(document: TextDocument, position: Position, macroFile: Macrofile, documentSettings: LanguageSettings): CompletionList; 31 | doSignature(document: TextDocument, position: Position, macroFile: Macrofile, documentSettings: LanguageSettings):SignatureHelp | null; 32 | findDefinition(document: TextDocument, position: Position, macroFile: Macrofile): Location | null; 33 | findReferences(document: TextDocument, position: Position, macroFile: Macrofile): Location[]; 34 | findImplementations(document: TextDocument, position: Position, macroFile: Macrofile): Location[]; 35 | findDocumentLinks(document: TextDocument, macrofile: Macrofile): DocumentLink[]; 36 | findDocumentSymbols(document: TextDocument, macrofile: Macrofile): SymbolInformation[]; 37 | findCodeLenses(document: TextDocument, macrofile: Macrofile): CodeLens[]; 38 | doPrepareRename(document: TextDocument, position: Position, macroFile: Macrofile): Range | null; 39 | doRename(document: TextDocument, position: Position, newName: string, macroFile: Macrofile): WorkspaceEdit; 40 | doRefactorSequences(document: TextDocument, position: Position, macrofile: Macrofile, documentSettings: LanguageSettings) : TextDocumentEdit | null; 41 | doCreateSequences(document: TextDocument, position: Position, macrofile: Macrofile, documentSettings: LanguageSettings) : TextDocumentEdit | null; 42 | doSemanticHighlighting(document: TextDocument, macrofile: Macrofile, documentSettings: LanguageSettings, range?:Range) : SemanticTokens; 43 | doPrepareCallHierarchy(document: TextDocument, position: Position, macrofile: Macrofile): CallHierarchyItem[] | null; 44 | doIncomingCalls(document: TextDocument, item: CallHierarchyItem, macrofile: Macrofile, documentSettings: LanguageSettings): CallHierarchyIncomingCall[] | null; 45 | doOutgoingCalls(document: TextDocument, item: CallHierarchyItem, macrofile: Macrofile, documentSettings: LanguageSettings): CallHierarchyOutgoingCall[] | null; 46 | doDocumentFormatting(document: TextDocument, options: FormattingOptions, macrofile: Macrofile): TextEdit[] | null; 47 | } 48 | 49 | function createFacade(parser: Parser, 50 | hover: MacroHover, 51 | completion: MacroCompletion, 52 | navigation: MacroNavigation, 53 | validation: MacroValidation, 54 | command: MacroCommand, 55 | semantic:MacroSemantic, 56 | hierarchy:MacroCallHierarchy, 57 | formatting:MacroDocumentFormatting): LanguageService { 58 | return { 59 | doValidation: validation.doValidation.bind(validation), 60 | parseMacroFile: parser.parseMacroFile.bind(parser), 61 | doHover: hover.doHover.bind(hover), 62 | doComplete: completion.doComplete.bind(completion), 63 | doSignature: completion.doSignature.bind(completion), 64 | findDefinition: navigation.findDefinition.bind(navigation), 65 | findReferences: navigation.findReferences.bind(navigation), 66 | findImplementations: navigation.findImplementations.bind(navigation), 67 | findDocumentLinks: navigation.findDocumentLinks.bind(navigation), 68 | findDocumentSymbols: navigation.findDocumentSymbols.bind(navigation), 69 | findCodeLenses: navigation.findCodeLenses.bind(navigation), 70 | doPrepareRename: navigation.doPrepareRename.bind(navigation), 71 | doRename: navigation.doRename.bind(navigation), 72 | doRefactorSequences: command.doRefactorSequences.bind(command), 73 | doCreateSequences: command.doCreateSequences.bind(command), 74 | doSemanticHighlighting: semantic.doSemanticHighlighting.bind(semantic), 75 | doPrepareCallHierarchy: hierarchy.doPrepareCallHierarchy.bind(hierarchy), 76 | doIncomingCalls: hierarchy.doIncomingCalls.bind(hierarchy), 77 | doOutgoingCalls: hierarchy.doOutgoingCalls.bind(hierarchy), 78 | doDocumentFormatting: formatting.doDocumentFormatting.bind(formatting) 79 | }; 80 | } 81 | 82 | export function getMacroLanguageService(options: LanguageServiceOptions): LanguageService { 83 | 84 | return createFacade( 85 | new Parser(options && options.fileProvider), 86 | new MacroHover(options && options.fileProvider), 87 | new MacroCompletion(options && options.fileProvider), 88 | new MacroNavigation(options && options.fileProvider), 89 | new MacroValidation(options && options.fileProvider), 90 | new MacroCommand(options && options.fileProvider), 91 | new MacroSemantic(), 92 | new MacroCallHierarchy(options && options.fileProvider), 93 | new MacroDocumentFormatting() 94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/macroLanguageTypes.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import { Location } from 'vscode-languageserver-types'; 8 | import { TextDocument } from 'vscode-languageserver-textdocument'; 9 | 10 | export { TextDocument } from 'vscode-languageserver-textdocument'; 11 | export { DocumentFormattingOptions, FormattingOptions, TextEdit } from 'vscode-languageserver'; 12 | export { SemanticTokensBuilder } from 'vscode-languageserver/lib/common/semanticTokens'; 13 | export { NodeType } from './parser/macroNodes'; 14 | export * from './languageFacts/builtinData'; 15 | export { 16 | SemanticTokenModifiers, 17 | SemanticTokenTypes, 18 | SemanticTokensParams, 19 | SemanticTokensLegend, 20 | SemanticTokens, 21 | SemanticTokensClientCapabilities 22 | } from 'vscode-languageserver-protocol/lib/common/protocol.semanticTokens'; 23 | 24 | export * from 'vscode-languageserver-types'; 25 | 26 | export const ALL_FILES:string = '/**/*.{[sS][rR][cC],[dD][eE][fF]}'; 27 | export const SRC_FILES:string = '/**/*.[sS][rR][cC]'; 28 | 29 | export interface LanguageSettings { 30 | validate? : { 31 | enable: boolean; 32 | workspace:boolean; 33 | }; 34 | codelens?: { 35 | enable:boolean; 36 | }; 37 | sequence?: { 38 | base:number; 39 | increment:number; 40 | } 41 | lint?: LintSettings; 42 | keywords?: CustomKeywords[]; 43 | callFunctions?: string[]; 44 | } 45 | export type LintSettings = { 46 | rules : { 47 | [key: string]: any // e.g rule.duplicateInclude: 'ignore' | 'warning' | 'error' 48 | } 49 | }; 50 | 51 | export type CustomKeywords = { 52 | symbol: string; 53 | scope:string; 54 | description:any; 55 | nodeType:string 56 | }; 57 | 58 | export interface DocumentContext { 59 | resolveReference(ref: string, base?: string): string | undefined; 60 | } 61 | 62 | export interface LanguageServiceOptions { 63 | 64 | fileProvider: MacroFileProvider; 65 | } 66 | 67 | export enum MacroFileType { 68 | SRC, 69 | DEF, 70 | LNK 71 | } 72 | 73 | export type Macrofile = {}; 74 | 75 | export interface MacroFileInfo { 76 | macrofile: Macrofile; 77 | document: TextDocument; 78 | version: number; 79 | type: MacroFileType; 80 | } 81 | 82 | export type MacroFileInclude = { 83 | getIncludes() : string[] | null; 84 | }; 85 | 86 | export interface FileProviderParams { 87 | uris?:string[], 88 | glob?:string 89 | } 90 | 91 | export interface MacroFileProvider { 92 | get(string, workspaceFolder?: string) : MacroFileInfo | undefined; 93 | getAll(param?: FileProviderParams, base?: string, workspaceFolder?: string) : MacroFileInfo[]; 94 | resolveReference(ref: string, workspaceFolder?: string): string | undefined; 95 | } 96 | 97 | export interface MacroCodeLensType { 98 | title: string; 99 | locations? : Location[] | undefined; 100 | type?: MacroCodeLensCommand; 101 | } 102 | 103 | export enum MacroCodeLensCommand { 104 | References 105 | } 106 | 107 | export enum TokenTypes { 108 | number = 1, 109 | macrovar = 2, 110 | constant = 3, 111 | language = 4, 112 | label = 5, 113 | code = 6, 114 | parameter = 7, 115 | address = 8, 116 | custom_1 = 9, 117 | custom_2 = 10, 118 | custom_3 = 11, 119 | custom_4 = 12, 120 | custom_5 = 13, 121 | _ = 14, 122 | } 123 | 124 | 125 | export enum TokenModifiers { 126 | _ = 0 127 | } 128 | 129 | export type FunctionSignatureParam = { 130 | _option?: string; 131 | _bracket?: string; 132 | _escape?: string; 133 | _param?: {[name:string]:any}[]; 134 | } 135 | 136 | export type FunctionSignature = { 137 | description?:string, 138 | delimiter?:string, 139 | param:FunctionSignatureParam[] 140 | } 141 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/parser/macroErrors.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as nodes from './macroNodes'; 8 | 9 | import * as nls from 'vscode-nls'; 10 | 11 | const localize: nls.LocalizeFunc = nls.loadMessageBundle(); 12 | 13 | export class MacroIssueType implements nodes.IRule { 14 | id: string; 15 | message: string; 16 | 17 | public constructor(id: string, message: string) { 18 | this.id = id; 19 | this.message = message; 20 | } 21 | } 22 | 23 | export const ParseError = { 24 | DefinitionExpected: new MacroIssueType('macro-definitionexpected', localize('expected.definition', 'Definition file expected')), 25 | UnknownKeyword: new MacroIssueType('macro-unknownkeyword', localize('unknown.keyword', 'Unknown keyword')), 26 | EndifExpected: new MacroIssueType('macro-endifexpected', localize('expected.endif', 'Endif expected')), 27 | EndExpected: new MacroIssueType('macro-endexpected', localize('expected.end', 'End expected')), 28 | DoExpected: new MacroIssueType('macro-doexpected', localize('expected.do', 'Do expected')), 29 | ThenGotoExpected: new MacroIssueType('macro-thengotoexpected', localize('expected.thenGoto', 'Then or goto expected')), 30 | NewLineExpected: new MacroIssueType('macro-newlineexpected', localize('expected.newline', 'Newline expected')), 31 | LeftSquareBracketExpected: new MacroIssueType('macro-lbracketexpected', localize('expected.lsquare', '[ expected')), 32 | RightSquareBracketExpected: new MacroIssueType('macro-rbracketexpected', localize('expected.rsquare', '] expected')), 33 | LeftParenthesisExpected: new MacroIssueType('macro-lparentexpected', localize('expected.lparent', '( expected')), 34 | RightParenthesisExpected: new MacroIssueType('macro-rparentexpected', localize('expected.rparent', ') expected')), 35 | RightAngleBracketExpected: new MacroIssueType('macro-ranglebracketexpected', localize('expected.rangle', '> expected')), 36 | Badstring: new MacroIssueType('macro-badstring', localize('expected.badstring', 'Missing string delimiter')), 37 | OperatorExpected: new MacroIssueType('macro-operatorexpected', localize('expected.operator', 'Operator expected')), 38 | IdentifierExpected: new MacroIssueType('macro-identifierexpected', localize('expected.ident', 'Identifier expected')), 39 | FunctionIdentExpected: new MacroIssueType('macro-functionidentifierexpected', localize('expected.funcident', 'Function identifier expected')), 40 | AddressExpected: new MacroIssueType('macro-addressexpected', localize('expected.address', 'Address expected')), 41 | MacroVariableExpected: new MacroIssueType('macro-macrovariableexpected', localize('expected.macrovariable', 'Variable (#) expected')), 42 | PrntFormatExpected: new MacroIssueType('macro-prntformatexpected', localize('expected.prntformat', 'Format [F] expected')), 43 | BodyExpected: new MacroIssueType('macro-bodyexpected', localize('expected.body', 'Body expected')), 44 | LabelExpected: new MacroIssueType('macro-labelexpected', localize('expected.label', 'Label expected')), 45 | TermExpected: new MacroIssueType('macro-termexpected', localize('expected.term', 'Term expected')), 46 | NumberExpected: new MacroIssueType('macro-numberexpected', localize('expected.number', 'Number expected')), 47 | IntegerExpected: new MacroIssueType('macro-integerexpected', localize('expected.integer', 'Integer expected')), 48 | ParameterExpected: new MacroIssueType('macro-numberexpected', localize('expected.parameter', 'Parameter expected')), 49 | InvalidStatement: new MacroIssueType('macro-invalidstatement', localize('expected.invalidstatement', 'Invalid statement')), 50 | ExpressionExpected: new MacroIssueType('macro-expressionexpected', localize('expected.expression', 'Expression expected')), 51 | UnexpectedToken: new MacroIssueType('macro-unexpectedToken', localize('expected.unexpectedToken', 'Unexpected token')), 52 | EqualExpected: new MacroIssueType('macro-equalExpected', localize('expected.equalExpected', ' = expected')), 53 | SymbolError: new MacroIssueType('macro-symbolError', localize('symbol.symbolError', 'Inappropriate symbol definition')), 54 | InvalidVariableName: new MacroIssueType('macro-invalidVariableName', localize('invalid.variableName', 'Invalid variable name')), 55 | }; 56 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/parser/macroSymbolScope.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as nodes from './macroNodes'; 8 | 9 | export class Scope { 10 | 11 | public parent: Scope | null; 12 | public children: Scope[]; 13 | public uri?:string; 14 | 15 | private symbols: Symbol[]; 16 | 17 | constructor() { 18 | this.symbols = []; 19 | this.parent = null; 20 | this.children = []; 21 | } 22 | 23 | public addChild(scope: Scope): void { 24 | this.children.push(scope); 25 | scope.setParent(this); 26 | } 27 | 28 | public setParent(scope: Scope): void { 29 | this.parent = scope; 30 | } 31 | 32 | public addSymbol(symbol: Symbol): void { 33 | this.symbols.push(symbol); 34 | } 35 | 36 | public getSymbol(name: string, type: nodes.ReferenceType): Symbol | null { 37 | for (let index = 0; index < this.symbols.length; index++) { 38 | const symbol = this.symbols[index]; 39 | if (symbol.name === name && symbol.refType === type) { 40 | return symbol; 41 | } 42 | } 43 | return null; 44 | } 45 | 46 | public getSymbols(): Symbol[] { 47 | return this.symbols; 48 | } 49 | } 50 | 51 | export interface SymbolContext { 52 | symbols: Symbol[], 53 | uri?: string 54 | } 55 | 56 | export class Symbol { 57 | 58 | public name: string; 59 | public refType: nodes.ReferenceType; 60 | public valueType: nodes.NodeType; 61 | public node: nodes.Node; 62 | 63 | constructor(name: string, node: nodes.Node, refType: nodes.ReferenceType, valueType:nodes.NodeType = undefined) { 64 | this.name = name; 65 | this.node = node; 66 | this.refType = refType; 67 | this.valueType = valueType; 68 | } 69 | } 70 | 71 | export class ScopeBuilder implements nodes.IVisitor { 72 | 73 | public scope: Scope; 74 | 75 | constructor(scope: Scope) { 76 | this.scope = scope; 77 | } 78 | 79 | private addSymbol(node: nodes.Node, name: string, refType: nodes.ReferenceType, valueType: nodes.NodeType = undefined): void { 80 | if (node.offset !== -1) { 81 | const current = this.scope; 82 | if (current) { 83 | if (!current.getSymbol(name, refType)){ 84 | current.addSymbol(new Symbol(name, node, refType, valueType)); 85 | } 86 | 87 | } 88 | } 89 | } 90 | 91 | public visitNode(node: nodes.Node): boolean { 92 | 93 | switch (node.type) { 94 | case nodes.NodeType.SymbolDef: 95 | const symbol = (node); 96 | this.addSymbol(node, symbol.getName(), nodes.ReferenceType.Symbol, symbol.getValue()?.type); 97 | return false; 98 | case nodes.NodeType.LabelDef: 99 | const label = (node); 100 | this.addSymbol(node, label.getName(), nodes.ReferenceType.Label, label.getValue()?.type); 101 | return false; 102 | case nodes.NodeType.SequenceNumber: 103 | const sequence = (node); 104 | this.addSymbol(node, sequence.getNumber()?.getText(), nodes.ReferenceType.Sequence); 105 | return true; 106 | case nodes.NodeType.Code: 107 | this.addSymbol(node, node.getText(), nodes.ReferenceType.Code); 108 | return true; 109 | case nodes.NodeType.Variable: 110 | const variable = (node); 111 | this.addSymbol(node, variable.getText(), nodes.ReferenceType.Variable); 112 | return true; 113 | case nodes.NodeType.Address: 114 | this.addSymbol(node, node.getText(), nodes.ReferenceType.Address); 115 | return true; 116 | } 117 | return true; 118 | } 119 | } 120 | 121 | export class Symbols { 122 | 123 | private global: Scope; 124 | 125 | constructor(file: nodes.MacroFile, uri?:string) { 126 | this.global = new Scope(); 127 | this.global.uri = uri; 128 | file.acceptVisitor(new ScopeBuilder(this.global)); 129 | } 130 | 131 | public getScope() : Scope { 132 | return this.global; 133 | } 134 | 135 | public findSymbols(referenceType: nodes.ReferenceType, valueTypes:nodes.NodeType[] | undefined = undefined): SymbolContext[] { 136 | let scope = this.global; 137 | const result: SymbolContext[] = []; 138 | const names: { [name: string]: boolean } = {}; 139 | let index = 0; 140 | while (scope){ 141 | const symbols = scope.getSymbols(); 142 | const symbolFound:Symbol[] = []; 143 | for (let i = 0; i < symbols.length; i++) { 144 | const symbol = symbols[i]; 145 | if (valueTypes) { 146 | if (symbol.refType === referenceType && valueTypes.indexOf(symbol.valueType) !== -1 && !names[symbol.name]) { 147 | symbolFound.push(symbol); 148 | names[symbol.name] = true; 149 | } 150 | } 151 | else { 152 | 153 | if (symbol.refType === referenceType && !names[symbol.name]) { 154 | symbolFound.push(symbol); 155 | names[symbol.name] = true; 156 | } 157 | } 158 | } 159 | result.push({ 160 | symbols: symbolFound, 161 | uri:scope.uri 162 | }); 163 | scope = this.global.children[index++]; 164 | } 165 | 166 | return result; 167 | } 168 | 169 | private internalFindSymbol(node: nodes.Reference): Symbol | null { 170 | if (!node) { 171 | return null; 172 | } 173 | 174 | const name = node.getText(); 175 | let scope = this.global; // only global scope available 176 | 177 | if (scope) { 178 | for (let value in nodes.ReferenceType) { 179 | let num = Number(value); 180 | if (isNaN(num)) { 181 | continue; 182 | } 183 | 184 | if (node.hasReferenceType(num)) { 185 | const symbol = scope.getSymbol(name, num); 186 | if (symbol) { 187 | return symbol; 188 | } 189 | } 190 | } 191 | } 192 | return null; 193 | } 194 | 195 | private evaluateReferenceTypes(node: nodes.Node): nodes.ReferenceType | null { 196 | 197 | const referenceTypes = (node).referenceTypes; 198 | if (referenceTypes) { 199 | return referenceTypes; 200 | } 201 | 202 | return null; 203 | } 204 | 205 | public findSymbolFromNode(node: nodes.Node): Symbol | null { 206 | if (!node) { 207 | return null; 208 | } 209 | if (this.evaluateReferenceTypes(node)) { 210 | return this.internalFindSymbol(node); 211 | } 212 | return null; 213 | } 214 | 215 | public matchesSymbol(node: nodes.Node, symbol: Symbol): boolean { 216 | if (!node) { 217 | return false; 218 | } 219 | 220 | if (node.getText() !== symbol.name) { 221 | return false; 222 | } 223 | 224 | if (node instanceof nodes.Reference) { 225 | if (!(node).hasReferenceType(symbol.refType)) { 226 | return false; 227 | } 228 | const nodeSymbol = this.internalFindSymbol(node); 229 | return nodeSymbol === symbol; 230 | } 231 | return false; 232 | } 233 | } -------------------------------------------------------------------------------- /server/src/macroLanguageService/services/lint.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as nodes from '../parser/macroNodes'; 8 | 9 | import { 10 | TextDocument, 11 | MacroFileProvider 12 | } from '../macroLanguageTypes'; 13 | import { 14 | LintConfiguration, 15 | Rules, 16 | Rule 17 | } from './lintRules'; 18 | 19 | const MAX_CONDITIONALS = 4; 20 | const MAX_WHILE_DEPTH = 3; 21 | const MAX_IF_DEPTH = 10; 22 | 23 | export class LintVisitor implements nodes.IVisitor { 24 | 25 | static entries(macrofile: nodes.Node, document: TextDocument, fileProvider: MacroFileProvider, settings: LintConfiguration): nodes.IMarker[] { 26 | const visitor = new LintVisitor(fileProvider, settings); 27 | macrofile.acceptVisitor(visitor); 28 | visitor.completeValidations(); 29 | return visitor.getEntries(); 30 | } 31 | 32 | private definitions:Map = new Map(); 33 | private sequenceList:FunctionMap = new FunctionMap(); 34 | private gotoList:FunctionMap = new FunctionMap(); 35 | private duplicateList: string[] = []; 36 | private imports: string[] = []; 37 | 38 | private rules: nodes.IMarker[] = []; 39 | private functionList = new Array(); 40 | private inDataInput:nodes.Node = null; 41 | 42 | private constructor(private fileProvider: MacroFileProvider, private settings: LintConfiguration) { } 43 | 44 | public getEntries(filter: number = (nodes.Level.Warning | nodes.Level.Error)): nodes.IMarker[] { 45 | return this.rules.filter(entry => { 46 | return (entry.getLevel() & filter) !== 0; 47 | }); 48 | } 49 | 50 | public addEntry(node: nodes.Node, rule: Rule, details?: string): void { 51 | const entry = new nodes.Marker(node, rule, this.settings.getRule(rule), details); 52 | this.rules.push(entry); 53 | } 54 | 55 | private completeValidations() { 56 | 57 | if (this.inDataInput) { 58 | this.addEntry(this.inDataInput, Rules.DataInputNotClosed); 59 | } 60 | 61 | // Check GOTO number occurrence 62 | for (const tuple of this.gotoList.elements) { 63 | const func = tuple[0]; 64 | const gotoStatements = tuple[1]; 65 | const sequences = this.sequenceList.get(func); 66 | for (const node of gotoStatements) { 67 | const jumpLabel = node.getLabel(); 68 | if (jumpLabel) { 69 | const number = Number(jumpLabel.getNonSymbolText()); 70 | if (!number) { 71 | continue; 72 | } 73 | 74 | if (sequences && sequences.some(a => { 75 | return a.value === number; 76 | })) { 77 | continue; 78 | } 79 | else { 80 | this.addEntry(jumpLabel, Rules.SeqNotFound); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | public visitNode(node: nodes.Node): boolean { 88 | switch (node.type) { 89 | case nodes.NodeType.MacroFile: 90 | return this.visitGlobalScope(node); 91 | case nodes.NodeType.Include: 92 | return this.visitIncludes(node); 93 | case nodes.NodeType.Symbol: 94 | return this.visitSymbols(node); 95 | case nodes.NodeType.SymbolDef: 96 | return this.visitDefinition(node, true); 97 | case nodes.NodeType.LabelDef: 98 | return this.visitDefinition(node, true); 99 | case nodes.NodeType.Program: 100 | return this.visitFunction(node); 101 | case nodes.NodeType.Parameter: 102 | return this.visitParameter(node); 103 | case nodes.NodeType.Goto: 104 | return this.visitGoto(node); 105 | case nodes.NodeType.SequenceNumber: 106 | return this.visitSequenceNumber(node); 107 | case nodes.NodeType.NNAddress: 108 | return this.visitNNAddress(node); 109 | case nodes.NodeType.Assignment: 110 | return this.visitAssignment(node); 111 | case nodes.NodeType.If: 112 | return this.visitIfStatement(node); 113 | case nodes.NodeType.While: 114 | return this.visitWhileStatement(node); 115 | case nodes.NodeType.BlockDel: 116 | return this.visitBlockDel(node); 117 | case nodes.NodeType.Statement: 118 | return this.visitStatement(node); 119 | } 120 | return true; 121 | } 122 | 123 | private visitIncludes(node: nodes.Include) : boolean { 124 | let uri = node.getChild(0); 125 | if (!uri) { 126 | return false; 127 | } 128 | 129 | if (this.imports.indexOf(uri?.getText()) > -1){ 130 | this.addEntry(node, Rules.DuplicateInclude); 131 | } 132 | else { 133 | this.imports.push(uri.getText()); 134 | let declaration = this.fileProvider?.get(uri.getText()); 135 | 136 | if (declaration) { 137 | (declaration?.macrofile).accept(candidate => { 138 | let found = false; 139 | if (candidate.type === nodes.NodeType.SymbolDef || candidate.type === nodes.NodeType.LabelDef) { 140 | this.visitDefinition(candidate, false); 141 | found = true; 142 | } 143 | return !found; 144 | }); 145 | } 146 | else { 147 | this.addEntry(node, Rules.IncludeNotFound); 148 | } 149 | } 150 | return false; 151 | } 152 | 153 | private visitGlobalScope(node: nodes.Node) : boolean { 154 | return true; 155 | } 156 | 157 | private visitFunction(node: nodes.Program): boolean { 158 | 159 | const ident = node.getIdentifier(); 160 | if (ident) { 161 | const number = ident.getNonSymbolText(); 162 | if (this.functionList.indexOf(number) === -1) { 163 | this.functionList.push(number); 164 | } 165 | else { 166 | this.addEntry(ident, Rules.DuplicateFunction); 167 | } 168 | } 169 | return true; 170 | } 171 | 172 | private visitSymbols(node: nodes.Symbol) : boolean { 173 | 174 | if (node.getParent()?.type === nodes.NodeType.SymbolRoot || node.getParent()?.type === nodes.NodeType.SymbolDef) { 175 | return false; 176 | } 177 | 178 | if (this.duplicateList.indexOf(node.getText()) !== -1) { 179 | this.addEntry(node, Rules.DuplicateDeclaration); 180 | } 181 | 182 | // Check references 183 | if (!this.definitions.has(node.getText())) { 184 | this.addEntry(node, Rules.UnknownSymbol); 185 | } 186 | 187 | return true; 188 | } 189 | 190 | private visitGoto(node: nodes.GotoStatement) : boolean { 191 | const func = node.findAParent(nodes.NodeType.Program); 192 | if (func) { 193 | this.gotoList.add(func, node); 194 | } 195 | return true; 196 | } 197 | 198 | /** 199 | * 200 | * @param node 201 | * @param local if true execute some checks only for the current file 202 | */ 203 | private visitDefinition(node: nodes.Node, local:boolean) : boolean { 204 | // scan local declarations 205 | let def = node; 206 | let ident = def.getName(); 207 | 208 | if (ident) { 209 | if (this.definitions.has(ident)) { 210 | this.duplicateList.push(ident); 211 | if (local) { 212 | this.addEntry(def, Rules.DuplicateDeclaration); 213 | } 214 | } 215 | 216 | if (local) { 217 | const value = def.getValue()?.getText(); 218 | for (const element of this.definitions.values()) { 219 | if ((def.type === nodes.NodeType.Address 220 | || def.type === nodes.NodeType.Numeric) 221 | && (def.type === element.type && element.getValue()?.getText() === value)) { 222 | this.addEntry(def, Rules.DuplicateAddress); 223 | } 224 | } 225 | } 226 | 227 | if (node.type === nodes.NodeType.SymbolDef) { 228 | this.definitions.set(ident, node); 229 | } 230 | else if (node.type === nodes.NodeType.LabelDef) { 231 | this.definitions.set(ident, node); 232 | } 233 | } 234 | return true; 235 | } 236 | 237 | private visitStatement(node: nodes.NcStatement): boolean { 238 | for (const child of node.getChildren()) { 239 | if (child.getNonSymbolText().toLocaleLowerCase() === 'g10') { 240 | this.inDataInput = node; 241 | } 242 | else if (child.getNonSymbolText().toLocaleLowerCase() === 'g11') { 243 | this.inDataInput = null; 244 | } 245 | } 246 | return true; 247 | } 248 | 249 | private visitParameter(node: nodes.Parameter): boolean { 250 | if (!node.findAParent(nodes.NodeType.SymbolDef)) { 251 | if (!node.hasChildren()) { 252 | this.addEntry(node, Rules.IncompleteParameter); 253 | } 254 | } 255 | return true; 256 | } 257 | 258 | private visitSequenceNumber(node: nodes.SequenceNumber): boolean { 259 | 260 | if (this.inDataInput) { 261 | return true; 262 | } 263 | 264 | const number = node.getNumber(); 265 | if (number) { 266 | const func = node.findAParent(nodes.NodeType.Program); 267 | const list = this.sequenceList.get(func); 268 | const duplicate = list?.some(a => { 269 | if (a.value === node.value) { 270 | if (a.symbol?.type !== number.symbol?.type) { 271 | this.addEntry(number, Rules.DuplicateLabelSequence); 272 | } 273 | else { 274 | if (number.symbol instanceof nodes.Label) { 275 | this.addEntry(number, Rules.DuplicateLabel); 276 | } 277 | else { 278 | this.addEntry(number, Rules.DuplicateSequence); 279 | } 280 | } 281 | return true; 282 | } 283 | return false; 284 | }); 285 | if (!duplicate) { 286 | this.sequenceList.add(func, node); 287 | } 288 | } 289 | return true; 290 | } 291 | 292 | private visitNNAddress(node: nodes.Node): boolean { 293 | 294 | if (!this.inDataInput) { 295 | this.addEntry(node, Rules.UnsuitableNNAddress); 296 | } 297 | 298 | return true; 299 | } 300 | 301 | private visitAssignment(node: nodes.Assignment): boolean { 302 | if (node.getLeft() instanceof nodes.Variable) { 303 | const body = (node.getLeft()).getBody(); 304 | if (body.symbol?.attrib === nodes.ValueAttribute.Constant) { 305 | this.addEntry(body, Rules.AssignmentConstant); 306 | } 307 | } 308 | return true; 309 | } 310 | 311 | private visitIfStatement(node: nodes.IfStatement): boolean { 312 | /** 313 | * Check the logic operators of a conitional expression. 314 | * Operators (|| and && ) can not mixed up e.g. [1 EQ #var || 2 EQ #var && 3 EQ #var] 315 | * Max number of statements MAX_CONDITIONALS 316 | */ 317 | let conditional = node.getConditional(); 318 | let count = 1; 319 | if (conditional) { 320 | const first = conditional.logic?.getNonSymbolText(); 321 | while (conditional?.getNext()) { 322 | if (++count > MAX_CONDITIONALS){ 323 | this.addEntry(conditional, Rules.TooManyConditionals); 324 | break; 325 | } 326 | 327 | const op = conditional.getNext()?.logic; 328 | if (!op) { 329 | break; 330 | } 331 | if (op.getNonSymbolText() !== first) { 332 | this.addEntry(op, Rules.MixedConditionals); 333 | } 334 | conditional = conditional.getNext(); 335 | } 336 | } 337 | 338 | // check level from in to out 339 | let level = 0; 340 | const path = nodes.getNodePath(node, node.offset); 341 | for (let i = path.length-1; i > -1; i--) { 342 | const element = path[i]; 343 | if (element.type === nodes.NodeType.If) { 344 | ++level; 345 | if (level > MAX_IF_DEPTH) { 346 | this.addEntry(node, Rules.NestingTooDeep); 347 | return false; 348 | } 349 | } 350 | } 351 | return true; 352 | } 353 | 354 | private visitWhileStatement(node: nodes.WhileStatement): boolean { 355 | 356 | let depth = 0; 357 | let doNumber:number = 0; 358 | // Check no logic operators allowed 359 | const conditional = node.getConditional(); 360 | if (conditional && conditional.logic) { 361 | this.addEntry(conditional, Rules.WhileLogicOperator); 362 | } 363 | 364 | // Check DO END label/number agreement 365 | if (node.dolabel && node.endlabel) { 366 | if (node.dolabel?.getNonSymbolText() !== node.endlabel?.getNonSymbolText()) { 367 | this.addEntry(node.dolabel, Rules.DoEndNumberNotEqual); 368 | this.addEntry(node.endlabel, Rules.DoEndNumberNotEqual); 369 | } 370 | 371 | if (Number(node.dolabel.getNonSymbolText())) { 372 | doNumber = Number(node.dolabel.getNonSymbolText()); 373 | if (doNumber && doNumber > MAX_WHILE_DEPTH) { 374 | this.addEntry(node.dolabel, Rules.DoEndNumberTooBig); 375 | } 376 | 377 | const endNumber = Number(node.endlabel.getNonSymbolText()); 378 | if (endNumber && endNumber > MAX_WHILE_DEPTH) { 379 | this.addEntry(node.endlabel, Rules.DoEndNumberTooBig); 380 | } 381 | } 382 | } 383 | 384 | const path = nodes.getNodePath(node, node.offset); 385 | for (let i = path.length-1; i > -1; i--) { 386 | const element = path[i]; 387 | if (element.type !== nodes.NodeType.While) { 388 | continue; 389 | } 390 | 391 | const child = element; 392 | 393 | // Check while depth 394 | if (depth >= MAX_WHILE_DEPTH) { 395 | this.addEntry(node, Rules.NestingTooDeep); 396 | return false; 397 | } 398 | 399 | // Check duplicate DO number 400 | if (depth > 0) { 401 | if (doNumber === Number(child.dolabel?.getNonSymbolText())) { 402 | this.addEntry(child.dolabel!, Rules.DuplicateDoEndNumber); 403 | } 404 | } 405 | ++depth; 406 | } 407 | return true; 408 | } 409 | 410 | private visitBlockDel(node: nodes.BlockDel): boolean { 411 | const number = Number(node.getNumber().getNonSymbolText()); 412 | if (number < 1 || number > 9) { 413 | this.addEntry(node, Rules.BlockDelNumber); 414 | } 415 | return true; 416 | } 417 | } 418 | 419 | class FunctionMap { 420 | public elements:Map = new Map(); 421 | public add(key:T, value:V) { 422 | if (!this.elements.has(key)) { 423 | this.elements.set(key, new Array()); 424 | } 425 | this.elements.get(key)?.push(value); 426 | } 427 | 428 | public get(key:T) : V[] | undefined { 429 | return this.elements.get(key); 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/services/lintRules.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as nodes from '../parser/macroNodes'; 8 | import * as nls from 'vscode-nls'; 9 | import { 10 | LintSettings } from '../macroLanguageTypes'; 11 | 12 | const localize: nls.LocalizeFunc = nls.loadMessageBundle(); 13 | const Warning = nodes.Level.Warning; 14 | const Error = nodes.Level.Error; 15 | const Ignore = nodes.Level.Ignore; 16 | 17 | export class Rule implements nodes.IRule { 18 | 19 | public constructor(public id: string, public message: string, public defaultValue: nodes.Level) {} 20 | } 21 | 22 | export const Rules = { 23 | DuplicateInclude: new Rule('duplicateInclude', localize('rule.duplicateInclude', 'Duplicate include'), Error), 24 | DuplicateDeclaration: new Rule('duplicateDeclaration', localize('rule.duplicateDeclaration', 'Duplicate symbol declaration'), Error), 25 | DuplicateFunction: new Rule('duplicateFunction', localize('rule.duplicateFunction', 'Duplicate function'), Warning), 26 | DuplicateAddress: new Rule('duplicateAddress', localize('rule.duplicateAddress', 'Duplicate address'), Ignore), 27 | DuplicateSequence: new Rule('duplicateSequence', localize('rule.duplicateSequence', 'Duplicate sequence number'), Warning), 28 | DuplicateLabel: new Rule('duplicateLabel', localize('rule.duplicateLabel', 'Duplicate label number'), Warning), 29 | DuplicateLabelSequence: new Rule('duplicateLabelSequence', localize('rule.duplicateLabelSequence', 'Sequence number and label define the same value'), Warning), 30 | 31 | UnknownSymbol: new Rule('unknownSymbol', localize('rule.unknownSymbol', 'Unknown symbol.'), Error), 32 | WhileLogicOperator: new Rule('whileLogicOperator', localize('rule.whileLogicOperator', 'Logic operator in WHILE statement [&&, ||]'), Error), 33 | DoEndNumberTooBig: new Rule('doEndNumberTooBig', localize('rule.doEndNumberTooBig', 'DO or END number too big'), Error), 34 | DoEndNumberNotEqual: new Rule('doEndNumberNotEqual', localize('rule.doEndNumberNotEqual', 'Not agree END statement number to pair of DO'), Error), 35 | NestingTooDeep: new Rule('nestingTooDeep', localize('rule.nestingTooDeep', 'Nesting too deep'), Error), 36 | DuplicateDoEndNumber: new Rule('duplicateDoEndNumber', localize('rule.duplicateDoEndNumber', 'Duplicate DO or END number'), Warning), 37 | MixedConditionals: new Rule('mixedConditionals', localize('rule.mixedConditionals', 'Mixed conditionals [&&, ||]'), Error), 38 | TooManyConditionals: new Rule('tooManyConditionals', localize('rule.tooManyConditionals', 'Too many conditional statements'), Error), 39 | SeqNotFound: new Rule('seqNotFound', localize('rule.seqNotFound', 'Sequence number or label not found'), Error), 40 | IncompleteParameter: new Rule('incompleteParameter', localize('rule.incompleteParameter', 'Incomplete parameter found. G-Code or M-Code may need a numeric value or a variable as parameter'), Error), 41 | IncludeNotFound: new Rule('includeNotFound', localize('rule.includeNotFound', 'Include file not found'), Error), 42 | AssignmentConstant: new Rule('assignmentConstant', localize('rule.assignmentConstant', 'Assignment to constant'), Ignore), 43 | BlockDelNumber: new Rule('blockDelNumber', localize('rule.blockDelNumber', 'BLOCKDEL number not match 1-9'), Error), 44 | UnsuitableNNAddress: new Rule('unsuitableNNAddress', localize('rule.unsuitableNNAddress', 'Address NN is outside of a G10/G11 block'), Warning), 45 | DataInputNotClosed: new Rule('dataInputNotClosed', localize('rule.dataInputNotClosed', 'Data input not close with G11'), Error), 46 | }; 47 | 48 | export class LintConfiguration { 49 | constructor(private conf?: LintSettings) {} 50 | 51 | getRule(rule: Rule): nodes.Level { 52 | if (this.conf?.rules){ 53 | const level = toLevel(this.conf?.rules[rule.id]); 54 | if (level) { 55 | return level; 56 | } 57 | } 58 | return rule.defaultValue; 59 | } 60 | } 61 | 62 | function toLevel(level: string): nodes.Level | null { 63 | switch (level) { 64 | case 'ignore': return nodes.Level.Ignore; 65 | case 'warning': return nodes.Level.Warning; 66 | case 'error': return nodes.Level.Error; 67 | } 68 | return null; 69 | } 70 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/services/macroCallHierarchy.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import { URI, Utils } from 'vscode-uri'; 8 | import * as path from 'path'; 9 | import { 10 | MacroFileProvider, TextDocument, Position, Range, 11 | CallHierarchyItem, CallHierarchyIncomingCall, CallHierarchyOutgoingCall, 12 | SymbolKind, LanguageSettings, Location 13 | } from '../macroLanguageTypes'; 14 | import * as nodes from '../parser/macroNodes'; 15 | import { MacroNavigation } from './macroNavigation'; 16 | 17 | export class MacroCallHierarchy { 18 | 19 | private navigation: MacroNavigation; 20 | 21 | constructor(private fileProvider: MacroFileProvider) { 22 | this.navigation = new MacroNavigation(this.fileProvider); 23 | } 24 | 25 | public doPrepareCallHierarchy(document: TextDocument, position: Position, macroFile: nodes.Node): CallHierarchyItem[] | null { 26 | 27 | const offset = document.offsetAt(position); 28 | const node = nodes.getNodeAtOffset(macroFile, offset); 29 | 30 | if (node) { 31 | const range = this.getRange(node, document); 32 | return [ 33 | { 34 | name: node.getText(), 35 | uri: document.uri, 36 | kind: SymbolKind.Function, 37 | range: range, 38 | selectionRange: range 39 | } 40 | ]; 41 | } else { 42 | return null; 43 | } 44 | } 45 | 46 | public doIncomingCalls(document: TextDocument, item: CallHierarchyItem, macroFile: nodes.Node, settings: LanguageSettings): CallHierarchyIncomingCall[] | null { 47 | 48 | const items: Map = new Map(); 49 | 50 | const macrofile = this.fileProvider.get(item.uri)?.macrofile; 51 | if (!macrofile) { 52 | return null; 53 | } 54 | 55 | const locations = this.navigation.findReferences(document, item.range.start, macrofile); 56 | for (const location of locations) { 57 | const macroFileType = this.fileProvider.get(location.uri); 58 | 59 | if (!macroFileType) { 60 | continue; 61 | } 62 | 63 | const callerFromIdent = this.getNodeFromLocation(macroFileType.document, macroFileType.macrofile, location); 64 | const parameter = callerFromIdent.findAParent(nodes.NodeType.Parameter); 65 | if (parameter) { 66 | const callFunction = parameter.getLastSibling(); 67 | if (callFunction && (settings?.callFunctions.find(a => a === callFunction.getNonSymbolText()))) { 68 | 69 | const callerFromProgram = callerFromIdent.findAParent(nodes.NodeType.Program); 70 | const callerFromRange = this.getRange(callerFromIdent, macroFileType.document); 71 | const key = callerFromProgram.identifier.getNonSymbolText()+macroFileType.document.uri; 72 | 73 | if (!items.has(key)) { 74 | 75 | const callerToRange = this.getRange(callerFromProgram.identifier, macroFileType.document); 76 | const filename = path.basename(URI.parse(macroFileType.document.uri).fsPath); 77 | 78 | items.set(key, { 79 | from: { 80 | name: callerFromProgram.identifier.getText(), 81 | uri: macroFileType.document.uri, 82 | kind: SymbolKind.Function, 83 | detail: macroFileType.document.uri === document.uri? null : filename, 84 | range: callerToRange, 85 | selectionRange: callerToRange 86 | }, 87 | fromRanges: [callerFromRange] 88 | }); 89 | } 90 | else { 91 | items.get(key).fromRanges.push(callerFromRange); 92 | } 93 | } 94 | } 95 | } 96 | 97 | return [...items.values()]; 98 | } 99 | 100 | public doOutgoingCalls(document: TextDocument, item: CallHierarchyItem, macroFile: nodes.MacroFile, settings: LanguageSettings): CallHierarchyOutgoingCall[] | null { 101 | 102 | const items: Map = new Map(); 103 | 104 | const locations = this.navigation.findImplementations(document, item.range.start, macroFile); 105 | for (const location of locations) { 106 | const macroFileType = this.fileProvider.get(location.uri); 107 | 108 | if (!macroFileType) { 109 | continue; 110 | } 111 | 112 | const sourceProgramIdent = this.getNodeFromLocation(macroFileType.document, macroFileType.macrofile, location); 113 | if (sourceProgramIdent) { 114 | const sourceProgram = sourceProgramIdent.getParent(); 115 | 116 | sourceProgram.accept(candidate => { 117 | 118 | if (settings?.callFunctions.find(a => a === candidate.getNonSymbolText())) { 119 | 120 | const parameter = candidate.getNextSibling(); 121 | const callerFromIdent = parameter.getChild(0); 122 | const callerFromRange = this.getRange(callerFromIdent, macroFileType.document); 123 | const locations = this.navigation.findImplementations(macroFileType.document, macroFileType.document.positionAt(callerFromIdent.offset), macroFile); 124 | 125 | for (const location of locations) { 126 | const macroFileType = this.fileProvider.get(location.uri); 127 | 128 | if (!macroFileType) { 129 | continue; 130 | } 131 | 132 | const callerToIdent = this.getNodeFromLocation(macroFileType.document, macroFileType.macrofile, location); 133 | if (callerToIdent) { 134 | const callerToProgram = callerToIdent.getParent(); 135 | const key = callerToProgram.identifier.getNonSymbolText()+macroFileType.document.uri; 136 | 137 | if (!items.has(key)) { 138 | 139 | const callerToRange = this.getRange(callerToProgram.identifier, macroFileType.document); 140 | const filename = path.basename(URI.parse(macroFileType.document.uri).fsPath); 141 | 142 | items.set(key, { 143 | to: { 144 | name: callerToProgram.identifier.getText(), 145 | uri: macroFileType.document.uri, 146 | kind: SymbolKind.Function, 147 | detail: macroFileType.document.uri === document.uri? null : filename, 148 | range: callerToRange, 149 | selectionRange: callerToRange // Range beim entferten symbol 150 | }, 151 | fromRanges: [callerFromRange] 152 | }); 153 | } 154 | else { 155 | items.get(key).fromRanges.push(callerFromRange); 156 | } 157 | 158 | return false; 159 | } 160 | } 161 | } 162 | return true; 163 | }); 164 | } 165 | 166 | break; 167 | } 168 | 169 | return [...items.values()]; 170 | } 171 | 172 | private getNodeFromLocation(document: TextDocument, macroFile: nodes.MacroFile, location: Location) : nodes.Node { 173 | const offset = document.offsetAt(location.range.start); 174 | return nodes.getNodeAtOffset(macroFile, offset, nodes.NodeType.SymbolRoot); 175 | } 176 | 177 | private getRange(node: nodes.Node, document: TextDocument): Range { 178 | return Range.create(document.positionAt(node.offset), document.positionAt(node.end)); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/services/macroCommands.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import { 8 | MacroFileProvider, TextEdit, 9 | TextDocumentEdit, TextDocument, Position, Range, 10 | LanguageSettings 11 | } from '../macroLanguageTypes'; 12 | import * as nodes from '../parser/macroNodes'; 13 | 14 | export class MacroCommand { 15 | 16 | constructor(private fileProvider: MacroFileProvider) {} 17 | 18 | public doRefactorSequences(document: TextDocument, position: Position, macroFile: nodes.MacroFile, settings: LanguageSettings) : TextDocumentEdit | null { 19 | let edit:TextDocumentEdit | null = null; 20 | const node = nodes.getNodeAtOffset(macroFile, document.offsetAt(position)); 21 | if (node){ 22 | const func = node.findAParent(nodes.NodeType.Program); 23 | const inc = settings?.sequence?.increment ? Number(settings?.sequence?.increment) : 10; 24 | let seq = settings?.sequence?.base ? Number(settings?.sequence?.base) : 1000; 25 | let gotoLabelList: nodes.Node[] = []; 26 | 27 | if (func) { 28 | const textEdits:TextEdit[] = []; 29 | func.accept(candidate => { 30 | if (candidate.type === nodes.NodeType.Goto) { 31 | const gnode = (candidate).getLabel(); 32 | if (gnode && !gnode.symbol) { 33 | const gotoNumber = Number(gnode.getText()); 34 | if (Number.isInteger(gotoNumber)) { 35 | gotoLabelList.push(gnode); 36 | } 37 | return false; 38 | } 39 | } 40 | return true; 41 | }); 42 | 43 | func.accept(candidate => { 44 | this.skip(candidate, () => { 45 | if (candidate.type === nodes.NodeType.SequenceNumber) { 46 | const nnode = (candidate).getNumber(); 47 | 48 | if (nnode && !nnode.symbol) { 49 | const labels = gotoLabelList.filter(a => a.getText() === nnode?.getText()); 50 | const start = document.positionAt(nnode.offset); 51 | const end = document.positionAt(nnode.end); 52 | textEdits.push(TextEdit.del(Range.create(start, end))); 53 | textEdits.push(TextEdit.insert(start, seq+'')); 54 | 55 | for (const label of labels) { 56 | const start = document.positionAt(label.offset); 57 | const end = document.positionAt(label.end); 58 | textEdits.push(TextEdit.del(Range.create(start, end))); 59 | textEdits.push(TextEdit.insert(start, seq.toString())); 60 | var index = gotoLabelList.indexOf(label); 61 | if (index > -1) { 62 | gotoLabelList.splice(index, 1); 63 | } 64 | } 65 | seq = seq + inc; 66 | } 67 | } 68 | }); 69 | return true; 70 | }); 71 | if (textEdits.length > 0) { 72 | edit = TextDocumentEdit.create({ 73 | uri: document.uri, 74 | version: document.version 75 | }, 76 | textEdits 77 | ); 78 | } 79 | } 80 | } 81 | return edit; 82 | } 83 | 84 | public doCreateSequences(document: TextDocument, position: Position, macroFile: nodes.MacroFile, settings: LanguageSettings) : TextDocumentEdit | null { 85 | let edit:TextDocumentEdit | null = null; 86 | const node = nodes.getNodeAtOffset(macroFile, document.offsetAt(position)); 87 | if (node) { 88 | const func = node.findAParent(nodes.NodeType.Program); 89 | const inc = settings?.sequence?.increment ? Number(settings?.sequence?.increment) : 10; 90 | let seq = this.getMaxSequenceNumber(func); 91 | if (seq <= 0) { 92 | seq = settings?.sequence?.base ? Number(settings?.sequence?.base) : 1000; 93 | } 94 | else { 95 | seq += inc; 96 | } 97 | 98 | if (func) { 99 | const textEdits:TextEdit[] = []; 100 | func.accept(candidate => { 101 | this.skip(candidate, () => { 102 | if (candidate.type === nodes.NodeType.Statement && !candidate.findAParent(nodes.NodeType.SequenceNumber)) { 103 | const statement = (candidate); 104 | if (statement) { 105 | const n = 'N' + seq + ' '; 106 | const start = document.positionAt(statement.offset); 107 | textEdits.push(TextEdit.insert(start, n)); 108 | seq = seq + inc; 109 | return false; 110 | } 111 | } 112 | }); 113 | return true; 114 | }); 115 | 116 | if (textEdits.length > 0) { 117 | edit = TextDocumentEdit.create({ 118 | uri: document.uri, 119 | version: document.version 120 | }, 121 | textEdits 122 | ); 123 | } 124 | } 125 | } 126 | return edit; 127 | } 128 | 129 | private getMaxSequenceNumber(node: nodes.Node) : number { 130 | let seq = -1; 131 | node.accept(candidate => { 132 | this.skip(candidate, () => { 133 | if (candidate.type === nodes.NodeType.SequenceNumber) { 134 | const nnode = (candidate).getNumber(); 135 | const number = Number(nnode?.getText().toLocaleLowerCase().split('n').pop()); 136 | if (nnode && !Number.isNaN(number)) { 137 | seq = Math.max(seq, number); 138 | return false; 139 | } 140 | } 141 | }); 142 | return true; 143 | }); 144 | return seq; 145 | } 146 | 147 | private _skip = false; 148 | private skip(candidate:nodes.Node, f:() => void) { 149 | function checkSkip(candidate:nodes.Node, code:string) : boolean { 150 | let result = false; 151 | for (const child of candidate.getChildren()) { 152 | result = checkSkip(child, code); 153 | const childText = child.getText().toLocaleLowerCase(); 154 | if (childText === code) { 155 | result = true; 156 | return result; 157 | } 158 | } 159 | return result; 160 | } 161 | 162 | if (candidate.type === nodes.NodeType.Statement || candidate.type === nodes.NodeType.SequenceNumber) { 163 | if (checkSkip(candidate, 'g11')) { 164 | this._skip = false; 165 | } 166 | if (!this._skip) { 167 | f(); 168 | } 169 | if (checkSkip(candidate, 'g10')) { 170 | this._skip = true; 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/services/macroDocumentFormatting.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2022 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import { notDeepEqual } from 'assert'; 8 | import { 9 | TextEdit, TextDocument, Position, Range, 10 | FormattingOptions 11 | } from '../macroLanguageTypes'; 12 | import * as nodes from '../parser/macroNodes'; 13 | 14 | export type VisitorFunction = (node: nodes.Node, level: number) => boolean; 15 | 16 | export class MacroDocumentFormatting { 17 | 18 | private options: FormattingOptions; 19 | private edits: TextEdit[]; 20 | private document: TextDocument; 21 | private indent: string; 22 | 23 | public doDocumentFormatting(document: TextDocument, options: FormattingOptions, macrofile: nodes.MacroFile): TextEdit[] | null { 24 | 25 | this.document = document; 26 | this.options = options; 27 | this.edits = []; 28 | 29 | if (options.insertSpaces) { 30 | this.indent = ' '.repeat(options.tabSize); 31 | } 32 | else { 33 | this.indent = '\t'; 34 | } 35 | 36 | this.doDocumentFormattingInternal(macrofile, 0); 37 | 38 | if (options.trimFinalNewlines) { 39 | const len = document.getText().length; 40 | const last = document.getText().charAt(len - 1); 41 | if (last === '\n') { 42 | const trimmed = TextDocument.create('', 'macro', 1, document.getText().trimEnd()); 43 | 44 | let pos: Position; 45 | if (options.insertFinalNewline) { 46 | pos = Position.create(trimmed.lineCount, 0); 47 | } 48 | else { 49 | pos = trimmed.positionAt(trimmed.getText().length); 50 | } 51 | 52 | this.edits.push(TextEdit.del(Range.create(pos, Position.create(document.lineCount, 0) ))); 53 | } 54 | } 55 | else if (options.insertFinalNewline) { 56 | const len = document.getText().length; 57 | const last = document.getText().charAt(len - 1); 58 | if (last !== '\n') { 59 | this.edits.push(TextEdit.insert(Position.create(document.lineCount, 0), '\n')); 60 | } 61 | } 62 | 63 | return this.edits; 64 | } 65 | 66 | private doDocumentFormattingInternal(node: nodes.Node, level: number) { 67 | 68 | if (node.hasChildren()) { 69 | for (const child of node.getChildren()) { 70 | 71 | const space = this.indent.repeat(level); 72 | 73 | if (child.type === nodes.NodeType.SymbolDef 74 | || child.type === nodes.NodeType.LabelDef 75 | || child.type === nodes.NodeType.Include 76 | || child.type === nodes.NodeType.ControlStatement) { 77 | this.indentFirstLine(child, ''); 78 | } 79 | else if (child.type === nodes.NodeType.Comment && child.parent.type === nodes.NodeType.MacroFile) { 80 | this.indentFirstLine(child, ''); 81 | } 82 | else if (child.type === nodes.NodeType.Body) { 83 | this.doDocumentFormattingInternal(child, level + 1); 84 | } 85 | else if (child.type === nodes.NodeType.MacroFile 86 | || child.type === nodes.NodeType.DefFile) { 87 | this.doDocumentFormattingInternal(child, level); 88 | } 89 | else if (child.type === nodes.NodeType.Program) { 90 | this.indentFirstLine(child, space); 91 | this.doDocumentFormattingInternal(child, level); 92 | } 93 | else if (child.type === nodes.NodeType.If) { 94 | this.doDocumentFormattingInternal(child, level); 95 | } 96 | else if (child.type === nodes.NodeType.While || child.type === nodes.NodeType.Then) { 97 | this.indentFirstLine(child, space); 98 | this.doDocumentFormattingInternal(child, level); 99 | this.indentLastLine(child, space); 100 | } 101 | else if (child.type === nodes.NodeType.Else) { 102 | this.indentFirstLine(child, space); 103 | this.doDocumentFormattingInternal(child, level); 104 | } 105 | else if (child.parent.type === nodes.NodeType.If 106 | && (child.type === nodes.NodeType.ThenTerm || child.type === nodes.NodeType.Goto)) { 107 | this.indentBody(child, space); 108 | } 109 | else if (child.parent.type === nodes.NodeType.Body) { 110 | this.indentBody(child, space); 111 | } 112 | } 113 | } 114 | } 115 | 116 | private indentFirstLine(node: nodes.Node, space: string) { 117 | const node_start_pos = this.document.positionAt(node.offset); 118 | this.indentLine(space, node_start_pos.line); 119 | } 120 | 121 | private indentLastLine(node: nodes.Node, space: string) { 122 | const node_end_pos = this.document.positionAt(node.end); 123 | this.indentLine(space, node_end_pos.line); 124 | } 125 | 126 | private indentBody(node: nodes.Node, space: string) { 127 | const node_start_pos = this.document.positionAt(node.offset); 128 | const node_end_pos = this.document.positionAt(node.end); 129 | const count = node_end_pos.line - node_start_pos.line; 130 | 131 | for (let i = 0; i < count + 1; i++) { 132 | this.indentLine(space, node_start_pos.line + i); 133 | } 134 | } 135 | 136 | private indentLine(space: string, line: number) { 137 | const currentText = this.document.getText(Range.create(line, 0, line + 1, 0))?.replace(/[\n\r]/g, ''); 138 | const range = Range.create(line, 0, line, currentText.length); 139 | 140 | let text: string; 141 | if (this.options.trimTrailingWhitespace) { 142 | text = this.document.getText(range).trim(); 143 | } 144 | else { 145 | text = this.document.getText(range).trimStart(); 146 | } 147 | 148 | if (this.edits.length < 1 || range.start.line !== this.edits[this.edits.length-1].range.start.line) { 149 | this.edits.push(TextEdit.replace(range, space + text)); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/services/macroHover.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as nodes from '../parser/macroNodes'; 8 | import { MacroNavigation } from './macroNavigation'; 9 | import { getComment } from '../parser/macroScanner'; 10 | import { 11 | TextDocument, Range, Position, Location, Hover, 12 | MacroFileProvider, functionSignatures, 13 | NcCodeDescription, 14 | SymbolText, 15 | LanguageSettings, 16 | MacroFileInfo 17 | } from '../macroLanguageTypes'; 18 | import { MarkupKind } from 'vscode-languageserver'; 19 | 20 | 21 | export class MacroHover { 22 | 23 | private settings: LanguageSettings; 24 | private textDocument: TextDocument; 25 | 26 | constructor(private fileProvider: MacroFileProvider) { } 27 | 28 | public doHover(document: TextDocument, position: Position, macroFile: nodes.MacroFile, settings: LanguageSettings): Hover | null { 29 | this.textDocument = document; 30 | this.settings = settings; 31 | 32 | try { 33 | let navigation = new MacroNavigation(this.fileProvider); 34 | let hover: Hover | null = null; 35 | 36 | const offset = document.offsetAt(position); 37 | const nodepath = nodes.getNodePath(macroFile, offset); 38 | 39 | let defFile:MacroFileInfo = null; 40 | 41 | const location = navigation.findDefinition(document, position, macroFile); 42 | if (location) { 43 | defFile = this.fileProvider?.get(location.uri); 44 | } 45 | 46 | for (let i = nodepath.length-1; i >= 0; i--) { 47 | const node = nodepath[i]; 48 | 49 | if (location && defFile) { 50 | if (node.type === nodes.NodeType.Label) { 51 | let text:string[] = []; 52 | const detail = this.getMarkedStringForDefinition(node, defFile!.macrofile, defFile!.document, location); 53 | const comment = getComment(defFile.document.offsetAt(location.range.start), defFile.document.getText()).trim(); 54 | const custom = this.getCustomKeywordDescription(node.getText(), node.type); 55 | if (detail) { 56 | text.push(detail); 57 | if (custom || comment){ 58 | text.push('','***',''); 59 | } 60 | } 61 | if (comment) { 62 | text.push(comment); 63 | } 64 | if (custom) { 65 | text.push(custom); 66 | } 67 | 68 | hover = { 69 | contents: { 70 | kind:MarkupKind.Markdown, 71 | value: text.join('\n\n') 72 | }, 73 | range: this.getRange((node).identifier) 74 | }; 75 | break; 76 | } 77 | else if (node.type === nodes.NodeType.Symbol) { 78 | let text:string[] = []; 79 | const detail = this.getMarkedStringForDefinition(node, defFile!.macrofile, defFile!.document, location); 80 | const comment = getComment(defFile.document.offsetAt(location.range.start), defFile.document.getText()).trim(); 81 | const custom = this.getCustomKeywordDescription(node.getText(), node.type); 82 | if (detail) { 83 | text.push(detail); 84 | if (custom || comment){ 85 | text.push('','***',''); 86 | } 87 | } 88 | if (comment) { 89 | text.push(comment); 90 | } 91 | if (custom) { 92 | text.push(custom); 93 | } 94 | 95 | hover = { 96 | contents: { 97 | kind:MarkupKind.Markdown, 98 | value: text.join('\n\n') 99 | }, 100 | range: this.getRange((node).identifier) 101 | }; 102 | break; 103 | } 104 | } 105 | 106 | if (!node.symbol) { 107 | if (node.type === nodes.NodeType.Code) { 108 | let text:string[] = []; 109 | const nodeText = node.getNonSymbolText(); 110 | const custom = this.getCustomKeywordDescription(nodeText, nodes.NodeType.Code); 111 | const desc = NcCodeDescription[nodeText]; 112 | const type = (node).codeType + '-code'; 113 | text.push(['```macro',`(${type}) ` + `${nodeText}`,'```'].join('\n')); 114 | 115 | if (custom || desc){ 116 | text.push('','***',''); 117 | } 118 | if (custom) { 119 | text.push(custom); 120 | } 121 | else if (desc) { 122 | text.push(desc); 123 | } 124 | 125 | hover = { 126 | contents: { 127 | kind:MarkupKind.Markdown, 128 | value: text.join('\n') 129 | }, 130 | range: this.getRange(node) 131 | }; 132 | break; 133 | } 134 | else if (node.type === nodes.NodeType.Ffunc || node.type === nodes.NodeType.Fcmd) { 135 | const text = this.getMarkedStringForFunction(node); 136 | hover = { 137 | contents: { 138 | kind:MarkupKind.Markdown, 139 | value: [`${text}`].join('\n') 140 | }, 141 | range: this.getRange(node) 142 | }; 143 | } 144 | } 145 | } 146 | return hover; 147 | } 148 | catch (error) { 149 | this.settings = null!; 150 | } 151 | finally { 152 | this.textDocument = null!; 153 | this.settings = null!; 154 | } 155 | } 156 | 157 | private getCustomKeywordDescription(text:string, type:nodes.NodeType) :string { 158 | const customKey = this.settings.keywords.find(a => a.symbol === text && (!a.nodeType || nodes.NodeType[a.nodeType] === type)); 159 | if (customKey && customKey.description) { 160 | if (customKey.description instanceof Array) { 161 | return customKey.description.join('\n\n'); 162 | } 163 | return customKey.description; 164 | } 165 | return ''; 166 | } 167 | 168 | private getMarkedStringForDefinition(node: nodes.Node, macroFile: nodes.Node, document:TextDocument, location:Location) : string { 169 | let text:string[] = []; 170 | 171 | const def = nodes.getNodeAtOffset(macroFile, document.offsetAt(location.range.start)); 172 | 173 | if (def instanceof nodes.AbstractDefinition) { 174 | let type = ''; 175 | if (def.type === nodes.NodeType.SymbolDef) { 176 | type = 'symbol'; 177 | } 178 | else { 179 | type = 'label'; 180 | } 181 | 182 | const name = def.getName(); 183 | const address = def.getValue()?.getText(); 184 | 185 | let valueType = ''; 186 | if (node['attrib'] !== nodes.ValueAttribute.None) { 187 | valueType = SymbolText[nodes.ValueAttribute[node['attrib']]]; 188 | } 189 | else { 190 | valueType = SymbolText[nodes.NodeType[def?.value.type]]; 191 | } 192 | 193 | let punctation = ''; 194 | if (type === 'label') { 195 | punctation = '>'; 196 | } 197 | else if (type === 'symbol') { 198 | punctation = '@'; 199 | } 200 | 201 | if (valueType) { 202 | text.push('```macro',`(${type}:${valueType}) ` + `${punctation}${name} `+` ${address}`, '```'); 203 | } 204 | else { 205 | text.push('```macro',`(${type}) ` + `${punctation}${name} `+` ${address}`, '```'); 206 | } 207 | } 208 | return text.join('\n'); 209 | } 210 | 211 | private getMarkedStringForFunction(node: nodes.Ffunc) : string { 212 | const func:nodes.Ffunc = node; 213 | const ident = func.getIdentifier()?.getText().toLocaleLowerCase(); 214 | if (!ident) { 215 | return ''; 216 | } 217 | const signatureIndex = func.getData('signature'); 218 | const signature = functionSignatures[ident][signatureIndex]; 219 | let deliminator = ''; 220 | let first = true; 221 | let text:string[] = []; 222 | let ftext:string[] = []; 223 | text.push('```macro'); 224 | ftext.push('(function) ' + ident); 225 | 226 | if (signature) { 227 | for (const element of signature.param) { 228 | 229 | if (element._bracket){ 230 | deliminator = ''; 231 | ftext.push(element._bracket); 232 | } 233 | else if (element._escape){ 234 | deliminator = ''; 235 | ftext.push(element._escape); 236 | } 237 | 238 | if (element._param) { 239 | 240 | // Space if the first part is a parameter 241 | // e.g setvnvar[name] -> setvn var[name] 242 | if (first) { 243 | ftext.push(' '); 244 | } 245 | 246 | for (const param of element._param) { 247 | if (signature.delimiter) { 248 | ftext.push(deliminator); 249 | } 250 | ftext.push(Object.keys(param)[0]); 251 | deliminator = signature.delimiter + ' '; 252 | } 253 | } 254 | first = false; 255 | } 256 | } 257 | text.push(ftext.join('')); 258 | text.push('```'); 259 | text.push('','***',''); 260 | text.push(signature.description); 261 | return text.join('\n'); 262 | } 263 | 264 | private getRange(node: nodes.Node) { 265 | return Range.create(this.textDocument.positionAt(node.offset), this.textDocument.positionAt(node.end)); 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/services/macroSemantic.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import { 8 | TextDocument, LanguageSettings, CustomKeywords, 9 | SemanticTokens, SemanticTokensBuilder, TokenTypes, Range 10 | } from '../macroLanguageTypes'; 11 | import * as nodes from '../parser/macroNodes'; 12 | 13 | 14 | export class MacroSemantic { 15 | private builder:SemanticTokensBuilder; 16 | private document: TextDocument; 17 | private customKeywords:CustomKeywords[]; 18 | private prevLine = -1; 19 | private prevChar = -1; 20 | 21 | constructor() {} 22 | 23 | public doSemanticHighlighting(document: TextDocument, macroFile: nodes.MacroFile, settings: LanguageSettings, range:Range | undefined) : SemanticTokens { 24 | this.builder = new SemanticTokensBuilder(); 25 | this.document = document; 26 | this.customKeywords = settings.keywords; 27 | this.prevLine = -1; 28 | this.prevChar = -1; 29 | 30 | try { 31 | let start:number; 32 | let end:number; 33 | if (range){ 34 | start = document.offsetAt(range.start); 35 | end = document.offsetAt(range.end); 36 | } 37 | macroFile.accept(candidate => { 38 | if (range) { 39 | if (candidate.offset < start) { 40 | return true; 41 | } 42 | if (candidate.offset > end) { 43 | return false; 44 | } 45 | } 46 | 47 | if (candidate.symbol) { 48 | const symbol = candidate.symbol; 49 | if (symbol.type === nodes.NodeType.Symbol) { 50 | switch (symbol.valueType) { 51 | case nodes.NodeType.Numeric: 52 | if (symbol.attrib === nodes.ValueAttribute.Constant) { 53 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.constant); 54 | } 55 | else if (symbol.attrib !== nodes.ValueAttribute.Program) { 56 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.number); 57 | } 58 | break; 59 | case nodes.NodeType.Code: 60 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.code); 61 | break; 62 | case nodes.NodeType.Parameter: 63 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.parameter); 64 | break; 65 | case nodes.NodeType.Statement: 66 | if (symbol.attrib === nodes.ValueAttribute.GCode || symbol.attrib === nodes.ValueAttribute.MCode) { 67 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.code); 68 | } 69 | else { 70 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.parameter); 71 | } 72 | break; 73 | case nodes.NodeType.Address: 74 | if (symbol.attrib === nodes.ValueAttribute.GCode || symbol.attrib === nodes.ValueAttribute.MCode) { 75 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.code); 76 | } 77 | else if (symbol.attrib === nodes.ValueAttribute.Parameter) { 78 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.parameter); 79 | } 80 | else { 81 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.address); 82 | } 83 | break; 84 | case nodes.NodeType.SequenceNumber: 85 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.label); 86 | break; 87 | case nodes.NodeType.Variable: 88 | this.build(symbol, nodes.NodeType.Symbol, TokenTypes.macrovar); 89 | break; 90 | } 91 | } 92 | else if (symbol.type === nodes.NodeType.Label) { 93 | this.build(symbol, nodes.NodeType.Label, TokenTypes.label); 94 | } 95 | } 96 | else if (!candidate.symbol) { 97 | if (candidate.type === nodes.NodeType.Variable) { 98 | this.build((candidate)?.body, nodes.NodeType.Variable); 99 | } 100 | else if (candidate.type === nodes.NodeType.Code) { 101 | this.build(candidate, nodes.NodeType.Code); 102 | } 103 | } 104 | return true; 105 | }); 106 | return this.builder.build(); 107 | } 108 | finally { 109 | this.customKeywords = null!; 110 | this.document = null!; 111 | this.builder = null!; 112 | } 113 | } 114 | 115 | private build(node:nodes.Node, type:nodes.NodeType, tokenType?:TokenTypes) { 116 | if (!node) { 117 | return; 118 | } 119 | 120 | const pos = this.document.positionAt(node.offset); 121 | let token:TokenTypes = tokenType; 122 | const customKey = this.customKeywords.find(a => a.symbol === node.getText() && (!a.nodeType || nodes.NodeType[a.nodeType] === type)); 123 | if (customKey && customKey.scope) { 124 | token = TokenTypes[customKey.scope]; 125 | } 126 | 127 | if (token) { 128 | if (node.type === nodes.NodeType.Symbol || node.type === nodes.NodeType.Label) { 129 | if (this.prevLine !== pos.line || this.prevChar !== pos.character) { 130 | this.prevLine = pos.line; 131 | this.prevChar = pos.character; 132 | this.builder.push(pos.line, pos.character, node.getText().length, token, 0); 133 | } 134 | } 135 | else { 136 | this.builder.push(pos.line, pos.character, node.length, token, 0); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/services/macroValidation.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2020 Simon Waelti 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as nodes from '../parser/macroNodes'; 8 | import { LintVisitor } from './lint'; 9 | import { 10 | TextDocument, 11 | Range, 12 | Diagnostic, 13 | LanguageSettings, 14 | MacroFileProvider 15 | } from '../macroLanguageTypes'; 16 | import { 17 | LintConfiguration 18 | } from './lintRules'; 19 | 20 | export class MacroValidation { 21 | 22 | constructor( private fileProvider: MacroFileProvider) {} 23 | 24 | public doValidation(document: TextDocument, macroFile: nodes.MacroFile, settings: LanguageSettings): Diagnostic[] { 25 | if (settings && settings?.validate?.enable === false) { 26 | return []; 27 | } 28 | 29 | const entries: nodes.IMarker[] = []; 30 | entries.push.apply(entries, nodes.ParseErrorCollector.entries(macroFile)); 31 | entries.push.apply(entries, LintVisitor.entries(macroFile, document, this.fileProvider, new LintConfiguration(settings && settings.lint))); 32 | 33 | function toDiagnostic(marker: nodes.IMarker): Diagnostic { 34 | const range = Range.create(document.positionAt(marker.getOffset()), document.positionAt(marker.getOffset() + marker.getLength())); 35 | const source = document.languageId; 36 | 37 | return { 38 | code: marker.getRule().id, 39 | source: source, 40 | message: marker.getMessage(), 41 | severity: marker.getLevel(), 42 | range: range 43 | }; 44 | } 45 | 46 | return entries.filter(entry => entry.getLevel() !== nodes.Level.Ignore).map(toDiagnostic); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/test/command.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Simon Waelti. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as assert from 'assert'; 8 | import { 9 | getMacroLanguageService, LanguageService 10 | } from '../macroLanguageService'; 11 | import { 12 | TextDocument, 13 | LanguageSettings, 14 | Range, 15 | Position, 16 | TextDocumentEdit 17 | } from '../macroLanguageTypes'; 18 | 19 | let settings:LanguageSettings = { 20 | sequence: { 21 | base : 1000, 22 | increment : 10 23 | } 24 | }; 25 | 26 | declare type commandFunction = (document, position, macroFile, settings) => TextDocumentEdit; 27 | 28 | function assertEdits(service: LanguageService, input: string, positions:Position[], newTexts:string[], f:commandFunction) { 29 | let document = TextDocument.create(`test://test/test.src`, 'macro', 0, input); 30 | let macroFile = service.parseMacroFile(document); 31 | 32 | let edits = f(document, document.positionAt(0), macroFile, settings); 33 | assert.strictEqual(edits.edits.length, newTexts.length, input); 34 | 35 | let nWrites = 0; 36 | for (let i = 0; i < edits.edits.length; i++) { 37 | const edit = edits.edits[i]; 38 | const newText = newTexts[i]; 39 | const position = positions[i]; 40 | if (edit.newText === newText) { 41 | nWrites++; 42 | } 43 | 44 | const expectedRange = Range.create(position, Position.create(position.line, position.character)); 45 | const start1 = document.offsetAt(edit.range.start); 46 | const start2 = document.offsetAt(expectedRange.start); 47 | assert.strictEqual(start1, start2); 48 | } 49 | assert.strictEqual(nWrites, newTexts.length, input); 50 | } 51 | 52 | suite('Commands', () => { 53 | 54 | test('Create sequence number', function () { 55 | const service = getMacroLanguageService(null); 56 | 57 | assertEdits(service, 'O 100\nG01\n', 58 | [{line:1,character:0}], 59 | ['N1000 '], 60 | service.doCreateSequences.bind(service)); 61 | 62 | assertEdits(service, 'O 100\nG01\nG02\n', 63 | [ 64 | {line:1,character:0}, 65 | {line:2,character:0} 66 | ], ['N1000 ', 'N1010 '], 67 | service.doCreateSequences.bind(service)); 68 | 69 | // Skip G10/G11 70 | assertEdits(service, 'O 100\nG01\nG10\nG01\nG11\nG01', 71 | [ 72 | {line:1,character:0}, 73 | {line:2,character:0}, 74 | {line:4,character:0}, 75 | {line:5,character:0} 76 | ], 77 | ['N1000 ', 'N1010 ', 'N1020 ', 'N1030 '], 78 | service.doCreateSequences.bind(service)); 79 | }); 80 | 81 | 82 | test('Refactor sequence number', function () { 83 | const service = getMacroLanguageService(null); 84 | 85 | assertEdits(service, 'O 100\nN100G01\n', 86 | [ 87 | {line:1,character:1}, 88 | {line:1,character:1} 89 | ], 90 | ['', '1000'], 91 | service.doRefactorSequences.bind(service)); 92 | 93 | assertEdits(service, 'O 100\nN100G01\nN100 G01\n', 94 | [ 95 | {line:1,character:1}, 96 | {line:1,character:1}, 97 | {line:2,character:1}, 98 | {line:2,character:1} 99 | ], 100 | ['', '1000', '', '1010'], 101 | service.doRefactorSequences.bind(service)); 102 | 103 | // Skip G10/G11 104 | assertEdits(service, 'O 100\nN100G01\nN100 G10\nN10 R100\nG11', 105 | [ 106 | {line:1,character:1}, 107 | {line:1,character:1}, 108 | {line:2,character:1}, 109 | {line:2,character:1}, 110 | ], 111 | ['', '1000', '', '1010'], 112 | service.doRefactorSequences.bind(service)); 113 | 114 | assertEdits(service, 'O 100\nGOTO 100\nN100\nGOTO 100\n', 115 | [ 116 | {line:2,character:1}, 117 | {line:2,character:1}, 118 | {line:1,character:5}, 119 | {line:1,character:5}, 120 | {line:3,character:5}, 121 | {line:3,character:5} 122 | ], 123 | ['', '1000', '', '1000', '','1000'], 124 | service.doRefactorSequences.bind(service)); 125 | }); 126 | 127 | }); 128 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/test/completion.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Simon Waelti. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as assert from 'assert'; 8 | import { Position } from 'vscode-languageserver-textdocument'; 9 | import { FileProviderMock, documents } from './fileProviderMock'; 10 | import { 11 | LanguageService, 12 | getMacroLanguageService, 13 | } from '../macroLanguageService'; 14 | 15 | import { 16 | TextDocument, 17 | LanguageSettings, 18 | CompletionList 19 | } from '../macroLanguageTypes'; 20 | 21 | import { 22 | Parser, 23 | } from '../parser/macroParser'; 24 | 25 | declare type completionFunction = (document, position, macroFile, settings) => CompletionList | null; 26 | 27 | function assertCompletion(service: LanguageService, input: string, position:Position, expected: string, f:completionFunction) { 28 | 29 | let settings:LanguageSettings = { 30 | keywords:[], 31 | sequence: { 32 | base: 1000, 33 | increment: 5 34 | } 35 | }; 36 | 37 | let document = TextDocument.create(`test://test/test.src`, 'macro', 0, input); 38 | let macroFile = service.parseMacroFile(document); 39 | let result = f(document, position, macroFile, settings); 40 | 41 | assert.strictEqual(result.items[7].textEdit.newText, expected, input); 42 | } 43 | 44 | let fileProviderMock = new FileProviderMock(); 45 | let service = getMacroLanguageService({ fileProvider: fileProviderMock }); 46 | 47 | suite('Completion', () => { 48 | 49 | test('N-Number completion', function () { 50 | 51 | assertCompletion(service, 'O 100\nN10\nN\n', 52 | { line:2,character:1}, 53 | 'N${1:15} $0', 54 | service.doComplete.bind(service) 55 | ); 56 | }); 57 | }); 58 | 59 | 60 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/test/fileProviderMock.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TextDocument, 3 | MacroFileProvider, 4 | MacroFileType, 5 | MacroFileInfo, 6 | FileProviderParams, 7 | } from '../macroLanguageTypes'; 8 | 9 | import { 10 | Parser, 11 | } from '../parser/macroParser'; 12 | 13 | export { FileProviderMock }; 14 | 15 | export const documents: Map = new Map(); 16 | 17 | class FileProviderMock implements MacroFileProvider { 18 | 19 | constructor() {} 20 | 21 | public get(file: string): MacroFileInfo | undefined { 22 | const document = documents.get(file); 23 | const macroFile = new Parser(this).parseMacroFile(document); 24 | return { 25 | document: document, 26 | macrofile: macroFile, 27 | version: 1, 28 | type: this.getMacroFileType(file) 29 | }; 30 | } 31 | 32 | public getAll(param?:FileProviderParams) { 33 | let types:MacroFileInfo[] = []; 34 | for (const file of documents.keys()) { 35 | let type = this.get(file); 36 | if (type){ 37 | types.push(type); 38 | } 39 | } 40 | return types; 41 | } 42 | 43 | public resolveReference(ref: string, base?: string): string | undefined { 44 | return undefined; 45 | } 46 | 47 | public getMacroFileType(file: string) : MacroFileType { 48 | var fileExt = file.split('.').pop().toLocaleLowerCase(); 49 | switch(fileExt) { 50 | case 'def': 51 | return MacroFileType.DEF; 52 | case 'lnk': 53 | return MacroFileType.LNK; 54 | default: 55 | return MacroFileType.SRC; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/test/navigation.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Simon Waelti. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as assert from 'assert'; 8 | import { Position } from 'vscode-languageserver-textdocument'; 9 | import { FileProviderMock, documents } from './fileProviderMock'; 10 | import { 11 | LanguageService, 12 | getMacroLanguageService, 13 | } from '../macroLanguageService'; 14 | import { 15 | TextDocument, 16 | Location, 17 | Range 18 | } from '../macroLanguageTypes'; 19 | 20 | import { 21 | Parser, 22 | } from '../parser/macroParser'; 23 | 24 | declare type locationFunction = (document, position, macroFile) => Location | null; 25 | declare type locationsFunction = (document, position, macroFile) => Location[]; 26 | 27 | function assertLocation(service: LanguageService, input: string, position:Position, location:Location, f:locationFunction) { 28 | 29 | let uri = 'test://test/test.src'; 30 | let document = TextDocument.create(uri, 'macro', 0, input); 31 | documents.set(uri, document); 32 | 33 | let macroFile = service.parseMacroFile(document); 34 | let result = f(document, position, macroFile); 35 | 36 | assert.strictEqual(result.uri, location.uri, input); 37 | assert.strictEqual(result.range.start.line, location.range.start.line, input); 38 | assert.strictEqual(result.range.start.character, location.range.start.character, input); 39 | assert.strictEqual(result.range.end.line, location.range.end.line, input); 40 | assert.strictEqual(result.range.end.character, location.range.end.character, input); 41 | } 42 | 43 | function assertLocations(service: LanguageService, input: string, position:Position, locations:Location[], f:locationsFunction) { 44 | 45 | let uri = 'test://test/test.src'; 46 | let document = TextDocument.create(uri, 'macro', 0, input); 47 | documents.set(uri, document); 48 | 49 | let macroFile = service.parseMacroFile(document); 50 | let result = f(document, position, macroFile); 51 | 52 | assert.strictEqual(result.length, locations.length); 53 | 54 | for (let i = 0; i < result.length; i++) { 55 | assert.strictEqual(result[i].uri, locations[i].uri, input); 56 | assert.strictEqual(result[i].range.start.line, locations[i].range.start.line, input); 57 | assert.strictEqual(result[i].range.start.character, locations[i].range.start.character, input); 58 | assert.strictEqual(result[i].range.end.line, locations[i].range.end.line, input); 59 | assert.strictEqual(result[i].range.end.character, locations[i].range.end.character, input); 60 | } 61 | 62 | } 63 | 64 | let fileProviderMock = new FileProviderMock(); 65 | let service = getMacroLanguageService({ fileProvider: fileProviderMock }); 66 | 67 | suite('Navigation', () => { 68 | 69 | test('Find object definition', function () { 70 | 71 | documents.set('test://test/test.def', TextDocument.create('test://test/test.def', 'macro', 0, '@dummy #1\n@var #1')); 72 | 73 | assertLocation(service, '@var #1\nO 100\nvar = 1\n', 74 | { line:2,character:0}, 75 | { range: Range.create(0, 0, 0, 7), uri: 'test://test/test.src' }, 76 | service.findDefinition.bind(service) 77 | ); 78 | 79 | assertLocation(service, '$INCLUDE test://test/test.def\nO 100\nvar = 1\n', 80 | { line:2,character:0}, 81 | { range: Range.create(1, 0, 1, 7), uri: 'test://test/test.def' }, 82 | service.findDefinition.bind(service) 83 | ); 84 | }); 85 | 86 | test('Find label definition', function () { 87 | 88 | documents.set('test://test/test.def', TextDocument.create('test://test/test.def', 'macro', 0, '>dummy 1\n>LABEL 1')); 89 | 90 | assertLocation(service, '>LABEL 1\nO 100\nLABEL\n', 91 | { line:2,character:0}, 92 | { range: Range.create(0, 0, 0, 8), uri: 'test://test/test.src' }, 93 | service.findDefinition.bind(service) 94 | ); 95 | 96 | assertLocation(service, '$INCLUDE test://test/test.def\nO 100\nLABEL\n', 97 | { line:2,character:0}, 98 | { range: Range.create(1, 0, 1, 8), uri: 'test://test/test.def' }, 99 | service.findDefinition.bind(service) 100 | ); 101 | }); 102 | 103 | test('Find goto sequence definition', function () { 104 | assertLocation(service, 'O 100\nN1000\nGOTO 1000\n', 105 | { line:2,character:5}, 106 | { range: Range.create(1, 0, 1, 5), uri: 'test://test/test.src' }, 107 | service.findDefinition.bind(service) 108 | ); 109 | }); 110 | 111 | test('Find object references', function () { 112 | 113 | documents.set('test://test/test.def', TextDocument.create('test://test/test.def', 'macro', 0, '@dummy #1\n@var #1')); 114 | 115 | assertLocations(service, '@var #1\nO 100\nvar = 1\nvar = 1\n', 116 | { line:2,character:0}, 117 | [ 118 | { range: Range.create(0, 1, 0, 4), uri: 'test://test/test.src' }, 119 | { range: Range.create(2, 0, 2, 3), uri: 'test://test/test.src' }, 120 | { range: Range.create(3, 0, 3, 3), uri: 'test://test/test.src' } 121 | ], 122 | service.findReferences.bind(service) 123 | ); 124 | 125 | assertLocations(service, '$INCLUDE test://test/test.def\nO 100\nvar = 1\nvar = 1\n', 126 | { line:2,character:0}, 127 | [ 128 | { range: Range.create(2, 0, 2, 3), uri: 'test://test/test.src' }, 129 | { range: Range.create(3, 0, 3, 3), uri: 'test://test/test.src' }, 130 | { range: Range.create(1, 1, 1, 4), uri: 'test://test/test.def' } 131 | ], 132 | service.findReferences.bind(service) 133 | ); 134 | }); 135 | 136 | test('Find label references', function () { 137 | 138 | documents.set('test://test/test.def', TextDocument.create('test://test/test.def', 'macro', 0, '>dummy 1\n>LABEL 1\n>SEQ N1000')); 139 | 140 | assertLocations(service, '>LABEL 1\nO 100\nLABEL\nLABEL\n', 141 | { line:2,character:0}, 142 | [ 143 | { range: Range.create(0, 1, 0, 6), uri: 'test://test/test.src' }, 144 | { range: Range.create(2, 0, 2, 5), uri: 'test://test/test.src' }, 145 | { range: Range.create(3, 0, 3, 5), uri: 'test://test/test.src' } 146 | ], 147 | service.findReferences.bind(service) 148 | ); 149 | 150 | assertLocations(service, '$INCLUDE test://test/test.def\nO 100\nLABEL\nLABEL\n', 151 | { line:2,character:0}, 152 | [ 153 | { range: Range.create(2, 0, 2, 5), uri: 'test://test/test.src' }, 154 | { range: Range.create(3, 0, 3, 5), uri: 'test://test/test.src' }, 155 | { range: Range.create(1, 1, 1, 6), uri: 'test://test/test.def' } 156 | ], 157 | service.findReferences.bind(service) 158 | ); 159 | 160 | assertLocations(service, '@SEQ N1000\nO 100\nN1000\nN1000\n', 161 | { line:2,character:1}, 162 | [ 163 | { range: Range.create(0, 6, 0, 10), uri: 'test://test/test.src' }, 164 | { range: Range.create(2, 1, 2, 5), uri: 'test://test/test.src' }, 165 | { range: Range.create(3, 1, 3, 5), uri: 'test://test/test.src' } 166 | ], 167 | service.findReferences.bind(service) 168 | ); 169 | 170 | assertLocations(service, '$INCLUDE test://test/test.def\nO 100\nN1000\nN1000\n', 171 | { line:2,character:1}, 172 | [ 173 | { range: Range.create(2, 1, 2, 5), uri: 'test://test/test.src' }, 174 | { range: Range.create(3, 1, 3, 5), uri: 'test://test/test.src' } 175 | ], 176 | service.findReferences.bind(service) 177 | ); 178 | }); 179 | 180 | 181 | test('Find object implementations', function () { 182 | 183 | documents.set('test://test/test.def', TextDocument.create('test://test/test.def', 'macro', 0, '@dummy 1\n@var 100')); 184 | 185 | assertLocations(service, '$INCLUDE test://test/test.def\nO var\nM123 var\n', 186 | { line:2,character:5}, 187 | [ 188 | { range: Range.create(1, 2, 1, 5), uri: 'test://test/test.src' }, 189 | ], 190 | service.findImplementations.bind(service) 191 | ); 192 | }); 193 | 194 | test('Find label implementations', function () { 195 | 196 | documents.set('test://test/test.def', TextDocument.create('test://test/test.def', 'macro', 0, '@dummy 1\n>LABEL 100')); 197 | 198 | assertLocations(service, '$INCLUDE test://test/test.def\nO 100\nLABEL\n', 199 | { line:2,character:0}, 200 | [ 201 | { range: Range.create(2, 0, 2, 5), uri: 'test://test/test.src' }, 202 | ], 203 | service.findImplementations.bind(service) 204 | ); 205 | 206 | assertLocations(service, 'O 100\nGOTO100\nN100', 207 | { line:1,character:4}, 208 | [ 209 | { range: Range.create(2, 1, 2, 4), uri: 'test://test/test.src' }, 210 | ], 211 | service.findImplementations.bind(service) 212 | ); 213 | }); 214 | 215 | }); 216 | 217 | 218 | -------------------------------------------------------------------------------- /server/src/macroLanguageService/test/nodes.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Simon Waelti. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as assert from 'assert'; 8 | import * as nodes from '../parser/macroNodes'; 9 | 10 | export class PrintingVisitor implements nodes.IVisitor { 11 | 12 | public tree: string[] = []; 13 | 14 | public visitNode(node: nodes.Node): boolean { 15 | this.tree.push(nodes.NodeType[node.type].toLowerCase()); 16 | return true; 17 | } 18 | } 19 | 20 | export function assertNodes(fn: (input: string) => nodes.Node, input: string, expected: string): void { 21 | let node = fn(input); 22 | let visitor = new PrintingVisitor(); 23 | 24 | node.acceptVisitor(visitor); 25 | 26 | let actual = visitor.tree.join(',') + ','; 27 | let segments = expected.split(','); 28 | let oldIndex: number | undefined = undefined; 29 | let index = -1; 30 | 31 | while (segments.length > 0) { 32 | let segment = segments.shift()!; 33 | if (segment === '...') { 34 | continue; 35 | } 36 | index = actual.indexOf(segment + ',', oldIndex); 37 | if (oldIndex && index <= oldIndex) { 38 | assert.ok(false, segment + ' NOT found in ' + actual); 39 | } 40 | oldIndex = index + segment.length; 41 | } 42 | 43 | assert.ok(true); 44 | } 45 | 46 | suite('Macro - Nodes', () => { 47 | 48 | test('Test Node', function () { 49 | 50 | let node = new nodes.Node(); 51 | assert.strictEqual(node.offset, -1); 52 | assert.strictEqual(node.length, -1); 53 | assert.strictEqual(node.parent, null); 54 | assert.strictEqual(node.getChildren().length, 0); 55 | 56 | let c = 0; 57 | node.accept((n: nodes.Node) => { 58 | assert.ok(n === node); 59 | c += 1; 60 | return true; 61 | }); 62 | assert.strictEqual(c, 1); 63 | 64 | let child = new nodes.Node(); 65 | node.adoptChild(child); 66 | 67 | c = 0; 68 | let expects = [node, child]; 69 | node.accept((n: nodes.Node) => { 70 | assert.ok(n === expects[c]); 71 | c += 1; 72 | return true; 73 | }); 74 | assert.strictEqual(c, 2); 75 | }); 76 | 77 | test('Test Adopting', function () { 78 | 79 | let child = new nodes.Node(); 80 | let p1 = new nodes.Node(); 81 | let p2 = new nodes.Node(); 82 | 83 | assert.ok(child.parent === null); 84 | assert.strictEqual(p1.getChildren().length, 0); 85 | assert.strictEqual(p2.getChildren().length, 0); 86 | 87 | p1.adoptChild(child); 88 | assert.ok(child.parent === p1); 89 | 90 | assert.strictEqual(p1.getChildren().length, 1); 91 | assert.strictEqual(p2.getChildren().length, 0); 92 | 93 | p2.adoptChild(child); 94 | assert.ok(child.parent === p2); 95 | assert.strictEqual(p1.getChildren().length, 0); 96 | assert.strictEqual(p2.getChildren().length, 1); 97 | }); 98 | }); -------------------------------------------------------------------------------- /server/src/macroLanguageService/test/scanner.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Simon Waelti. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as assert from 'assert'; 8 | import { IToken, Scanner, TokenType } from '../parser/macroScanner'; 9 | 10 | 11 | suite('Scanner', () => { 12 | 13 | function assertSingleToken(scanner: Scanner, source: string, len: number, offset: number, text: string, ...tokenTypes: TokenType[]): void { 14 | scanner.setSource(source); 15 | let token = scanner.scan(); 16 | assert.strictEqual(token.len, len); 17 | assert.strictEqual(token.offset, offset); 18 | assert.strictEqual(token.text, text); 19 | assert.strictEqual(token.type, tokenTypes[0]); 20 | for (let i = 1; i < tokenTypes.length; i++) { 21 | assert.strictEqual(scanner.scan().type, tokenTypes[i], source); 22 | } 23 | assert.strictEqual(scanner.scan().type, TokenType.EOF, source); 24 | } 25 | 26 | function assertSingleTokenNonSymolStatement(scanner: Scanner, source: string, len: number, offset: number, text: string, ...tokenTypes: TokenType[]): void { 27 | scanner.setSource(source); 28 | let token = scanner.scanNonSymbol(); 29 | if (tokenTypes.length > 0) { 30 | assert.strictEqual(token.len, len); 31 | assert.strictEqual(token.offset, offset); 32 | assert.strictEqual(token.text, text); 33 | assert.strictEqual(token.type, tokenTypes[0]); 34 | for (let i = 1; i < tokenTypes.length; i++) { 35 | assert.strictEqual(scanner.scanNonSymbol().type, tokenTypes[i], source); 36 | } 37 | } 38 | else { 39 | assert.strictEqual(token, null); 40 | } 41 | } 42 | 43 | test('Whitespace', function () { 44 | let scanner = new Scanner(); 45 | assertSingleToken(scanner, ' $', 1, 1, '$', TokenType.Delim); 46 | 47 | scanner = new Scanner(); 48 | scanner.ignoreWhitespace = false; 49 | assertSingleToken(scanner, ' $', 1, 0, ' ', TokenType.Whitespace, TokenType.Delim); 50 | 51 | scanner = new Scanner(); 52 | scanner.ignoreComment = false; 53 | assertSingleToken(scanner, ' /*comment', 9, 1, '/*comment', TokenType.Comment); 54 | 55 | scanner = new Scanner(); 56 | assertSingleToken(scanner, ' ', 0, 1, '', TokenType.EOF); 57 | assertSingleToken(scanner, ' ', 0, 6, '', TokenType.EOF); 58 | }); 59 | 60 | test('Token Newline', function () { 61 | let scanner = new Scanner(); 62 | scanner.ignoreNewLine = false; 63 | assertSingleToken(scanner, ' \n', 1, 1, '\n', TokenType.NewLine); 64 | assertSingleToken(scanner, ' \r\n', 2, 1, '\r\n', TokenType.NewLine); 65 | assertSingleToken(scanner, ' \f', 1, 1, '\f', TokenType.NewLine); 66 | 67 | 68 | scanner.ignoreNewLine = true; 69 | assertSingleToken(scanner, ' \n', 0, 2, '', TokenType.EOF); 70 | }); 71 | 72 | test('Token Symbol', function () { 73 | let scanner = new Scanner(); 74 | assertSingleToken(scanner, 'var', 3, 0, 'var', TokenType.Symbol); 75 | assertSingleToken(scanner, 'Var', 3, 0, 'Var', TokenType.Symbol); 76 | assertSingleToken(scanner, '_var', 4, 0, '_var', TokenType.Symbol); 77 | assertSingleToken(scanner, 'var1', 4, 0, 'var1', TokenType.Symbol); 78 | assertSingleToken(scanner, '1var', 4, 0, '1var', TokenType.Symbol); 79 | assertSingleToken(scanner, '123', 3, 0, '123', TokenType.Symbol); 80 | assertSingleToken(scanner, '12.', 3, 0, '12.', TokenType.Symbol); 81 | assertSingleToken(scanner, 'var.', 4, 0, 'var.', TokenType.Symbol); 82 | assertSingleToken(scanner, 'var?', 4, 0, 'var?', TokenType.Symbol); 83 | assertSingleToken(scanner, 'var!', 4, 0, 'var!', TokenType.Symbol); 84 | assertSingleToken(scanner, 'var$abc', 7, 0, 'var$abc', TokenType.Symbol); 85 | }); 86 | 87 | test('Token Dollar', function () { 88 | let scanner = new Scanner(); 89 | assertSingleToken(scanner, '$INCLUDE', 8, 0, '$INCLUDE', TokenType.Dollar); 90 | assertSingleToken(scanner, '$', 1, 0, '$', TokenType.Delim); 91 | }); 92 | 93 | test('Token Hash', function () { 94 | let scanner = new Scanner(); 95 | assertSingleToken(scanner, '#var', 1, 0, '#', TokenType.Hash, TokenType.Symbol); 96 | 97 | }); 98 | 99 | test('Token At', function () { 100 | let scanner = new Scanner(); 101 | assertSingleToken(scanner, '@var', 1, 0, '@', TokenType.AT, TokenType.Symbol); 102 | 103 | }); 104 | 105 | test('Token Gts', function () { 106 | let scanner = new Scanner(); 107 | assertSingleToken(scanner, '>var', 1, 0, '>', TokenType.GTS, TokenType.Symbol); 108 | assertSingleToken(scanner, '<', 1, 0, '<', TokenType.LTS); 109 | }); 110 | 111 | test('Token Comments', function () { 112 | let scanner = new Scanner(); 113 | assertSingleToken(scanner, '/*', 0, 2, '', TokenType.EOF); 114 | assertSingleToken(scanner, ';', 0, 1, '', TokenType.EOF); 115 | scanner.ignoreComment = false; 116 | assertSingleToken(scanner, '/*', 2, 0, '/*', TokenType.Comment); 117 | assertSingleToken(scanner, ';', 1, 0, ';', TokenType.Comment); 118 | }); 119 | 120 | test('Token Strings', function () { 121 | let scanner = new Scanner(); 122 | assertSingleToken(scanner, '()', 2, 0, '()', TokenType.String); 123 | assertSingleToken(scanner, '(\'\')', 4, 0, '(\'\')', TokenType.String); 124 | assertSingleToken(scanner, '("")', 4, 0, '("")', TokenType.String); 125 | assertSingleToken(scanner, '(**)', 4, 0, '(**)', TokenType.String); 126 | 127 | assertSingleToken(scanner, '(', 1, 0, '(', TokenType.BadString); 128 | assertSingleToken(scanner, ')', 1, 0, ')', TokenType.Delim); 129 | 130 | assertSingleToken(scanner, '(\')', 3, 0, '(\')', TokenType.BadString); 131 | assertSingleToken(scanner, '(")', 3, 0, '(")', TokenType.BadString); 132 | assertSingleToken(scanner, '(*)', 3, 0, '(*)', TokenType.BadString); 133 | }); 134 | 135 | test('Token Delim', function () { 136 | let scanner = new Scanner(); 137 | assertSingleToken(scanner, '+', 1, 0, '+', TokenType.Delim); 138 | assertSingleToken(scanner, '-', 1, 0, '-', TokenType.Delim); 139 | assertSingleToken(scanner, '/', 1, 0, '/', TokenType.Delim); 140 | assertSingleToken(scanner, '*', 1, 0, '*', TokenType.Delim); 141 | assertSingleToken(scanner, '\' ', 1, 0, '\'', TokenType.Delim); 142 | assertSingleToken(scanner, '"', 1, 0, '"', TokenType.Delim); 143 | 144 | scanner = new Scanner(); 145 | scanner.ignoreBadString = true; 146 | assertSingleToken(scanner, '(', 1, 0, '(', TokenType.Delim); 147 | }); 148 | 149 | test('Token singletokens', function () { 150 | let scanner = new Scanner(); 151 | assertSingleToken(scanner, '[ ', 1, 0, '[', TokenType.BracketL); 152 | assertSingleToken(scanner, '] ', 1, 0, ']', TokenType.BracketR); 153 | assertSingleToken(scanner, ', ', 1, 0, ',', TokenType.Comma); 154 | assertSingleToken(scanner, '& ', 1, 0, '&', TokenType.Ampersand); 155 | }); 156 | 157 | test('Token keywords', function () { 158 | let scanner = new Scanner(); 159 | assertSingleToken(scanner, 'if ', 2, 0, 'if', TokenType.KeyWord); 160 | }); 161 | 162 | test('Token functions', function () { 163 | let scanner = new Scanner(); 164 | assertSingleToken(scanner, 'sin ', 3, 0, 'sin', TokenType.Ffunc); 165 | assertSingleToken(scanner, 'popen ', 5, 0, 'popen', TokenType.Fcmd); 166 | }); 167 | 168 | test('Token for nonsymbolic statement', function () { 169 | let scanner = new Scanner(); 170 | assertSingleTokenNonSymolStatement(scanner, 'ANDSIN', 3, 0, 'AND', TokenType.KeyWord); 171 | assertSingleTokenNonSymolStatement(scanner, 'R', 1, 0, 'R', TokenType.Parameter); 172 | assertSingleTokenNonSymolStatement(scanner, '1', 1, 0, '1', TokenType.Number); 173 | //assertSingleTokenNonSymolStatement(scanner, '1.0', 3, 0, '1.0', TokenType.Number); 174 | assertSingleTokenNonSymolStatement(scanner, 'G01G01', 1, 0, 'G', TokenType.Parameter, TokenType.Number, TokenType.Parameter, TokenType.Number); 175 | 176 | assertSingleTokenNonSymolStatement(scanner, 'EQ', 2, 0, 'EQ', TokenType.KeyWord); 177 | assertSingleTokenNonSymolStatement(scanner, 'R10EQ1', 1, 0, 'R', TokenType.Parameter, TokenType.Number, TokenType.KeyWord, TokenType.Number); 178 | 179 | assertSingleTokenNonSymolStatement(scanner, 'R.0', 1, 0, 'R', TokenType.Parameter); 180 | assertSingleTokenNonSymolStatement(scanner, 'ABC', 3, 0, 'ABC', TokenType.Symbol); 181 | }); 182 | 183 | test('Token N', function () { 184 | let scanner = new Scanner(); 185 | assertSingleTokenNonSymolStatement(scanner, 'N', 1, 0, 'N', TokenType.Sequence); 186 | }); 187 | 188 | test('Token NN', function () { 189 | let scanner = new Scanner(); 190 | assertSingleTokenNonSymolStatement(scanner, 'NN', 2, 0, 'NN', TokenType.NNAddress); 191 | }); 192 | 193 | test('Token SystemVar', function () { 194 | let scanner = new Scanner(); 195 | assertSingleToken(scanner, '[#_TEST]', 8, 0, '[#_TEST]', TokenType.SystemVar); 196 | assertSingleToken(scanner, '[#_TEST[123]]', 13, 0, '[#_TEST[123]]', TokenType.SystemVar); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019"], 6 | "outDir": "out", 7 | "rootDir": "src", 8 | "sourceMap": true 9 | }, 10 | "include": ["src", "test"], 11 | "exclude": ["node_modules"] 12 | } -------------------------------------------------------------------------------- /snippets/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "func": { 3 | "prefix": "func", 4 | "body": "O ${1:name} \n\t$4\nRETURN\n", 5 | "description": "", 6 | "scope": "source.macro" 7 | }, 8 | "if": { 9 | "prefix": "if", 10 | "body": "IF [${1:condition}] THEN\n\t$2\nENDIF\n", 11 | "description": "if ...", 12 | "scope": "source.macro" 13 | }, 14 | "ifelse": { 15 | "prefix": "ifelse", 16 | "body": "IF [${1:condition}] THEN\n\t$2\nELSE \n\t$3\nENDIF\n", 17 | "description": "if ... else ...", 18 | "scope": "source.macro" 19 | }, 20 | "ifthen": { 21 | "prefix": "ifthen", 22 | "body": "IF [${1:condition}] THEN\t$0", 23 | "description": "if [condition] then expression", 24 | "scope": "source.macro" 25 | }, 26 | "ifgoto": { 27 | "prefix": "ifgoto", 28 | "body": "IF [${1:condition}] GOTO\t$0", 29 | "description": "if [condition] goto expression", 30 | "scope": "source.macro" 31 | }, 32 | "else": { 33 | "prefix": "else", 34 | "body": "ELSE\n\t$2\nENDIF\n", 35 | "description": "else ...", 36 | "scope": "source.macro" 37 | }, 38 | "while": { 39 | "prefix": "while", 40 | "body": "WHILE [${1:condition}] DO LOOP_${2:I}\n\t$3\nEND LOOP_${4:I}", 41 | "description": "WHILE ... DO ... LOOP_I ... END ... LOOP_I", 42 | "scope": "source.macro" 43 | }, 44 | "goto": { 45 | "prefix": "goto", 46 | "body": "GOTO ${0:label}", 47 | "description": "GOTO label", 48 | "scope": "source.macro" 49 | }, 50 | "popen": { 51 | "prefix": "popen", 52 | "body": "POPEN", 53 | "description": "The POPEN command establishes a connection to an external input/output device. It must be specified before a sequence of data output commands. The CNC outputs a DC2 control code", 54 | "scope": "source.macro" 55 | }, 56 | "pclos": { 57 | "prefix": "pclos", 58 | "body": "PCLOS", 59 | "description": "The PCLOS command releases a connection to an external input/output device. Specify this command when all data output commands have terminated. DC4 control code is output from the CNC", 60 | "scope": "source.macro" 61 | }, 62 | "dprnt": { 63 | "prefix": "dprnt", 64 | "body": "DPRNT[${1:data}]", 65 | "description": "The DPRNT command outputs characters and each digit in the value of a variable according to the code set in the settings (ISO)", 66 | "scope": "source.macro" 67 | }, 68 | "bprnt": { 69 | "prefix": "bprnt", 70 | "body": "BPRNT[${1:data}]", 71 | "description": "The BPRNT command outputs characters and variable values in binary", 72 | "scope": "source.macro" 73 | }, 74 | "setvn": { 75 | "prefix": "setvn", 76 | "body": "SETVN ${1:var}[${2:name}]", 77 | "description": "This function expands number of variables to which name is set by SETVN", 78 | "scope": "source.macro" 79 | }, 80 | "fgen": { 81 | "prefix": "fgen", 82 | "body": "FGEN(${1:file}, ${2:size}, ${3:result})", 83 | "description": "File creation command", 84 | "scope": "source.macro" 85 | }, 86 | "fdel": { 87 | "prefix": "fdel", 88 | "body": "FDEL(${1:line}, ${2:status})", 89 | "description": "File deletion command", 90 | "scope": "source.macro" 91 | }, 92 | "fopen": { 93 | "prefix": "fopen", 94 | "body": "FOPEN(${1:file}, ${2:mode}, ${3:result})", 95 | "description": "File opening command", 96 | "scope": "source.macro" 97 | }, 98 | "fclos": { 99 | "prefix": "fclos", 100 | "body": "FCLOS(${1:file})", 101 | "description": "File closing command", 102 | "scope": "source.macro" 103 | }, 104 | "fpset": { 105 | "prefix": "fpset", 106 | "body": "FPSET(${1:file}, ${2:type}, ${3:pointer})", 107 | "description": "Command for setting file pointer", 108 | "scope": "source.macro" 109 | }, 110 | "fread": { 111 | "prefix": "fread", 112 | "body": "FREAD(${1:file}, ${2:type}, ${3:var})", 113 | "description": "Command for reading files", 114 | "scope": "source.macro" 115 | }, 116 | "fwrit": { 117 | "prefix": "fwrit", 118 | "body": "FWRIT(${1:file}, ${2:type}, ${3:data})", 119 | "description": "Command for writing files", 120 | "scope": "source.macro" 121 | }, 122 | "sin": { 123 | "prefix": "sin", 124 | "body": "SIN[${1:value}]", 125 | "description": "Sine (in degrees)", 126 | "scope": "source.macro" 127 | }, 128 | "cos": { 129 | "prefix": "cos", 130 | "body": "COS[${1:value}]", 131 | "description": "Cosine (in degrees)", 132 | "scope": "source.macro" 133 | }, 134 | "tan": { 135 | "prefix": "tan", 136 | "body": "TAN[${1:value}]", 137 | "description": "Tangent (in degrees)", 138 | "scope": "source.macro" 139 | }, 140 | "asin": { 141 | "prefix": "asin", 142 | "body": "ASIN[${1:value}]", 143 | "description": "Arcsine", 144 | "scope": "source.macro" 145 | }, 146 | "acos": { 147 | "prefix": "acos", 148 | "body": "ACOS[${1:value}]", 149 | "description": "Arccosine", 150 | "scope": "source.macro" 151 | }, 152 | "atan": { 153 | "prefix": "atan", 154 | "body": "ATAN[${1:value}]", 155 | "description": "Arc tangent (one parameter), ATN can also be used", 156 | "scope": "source.macro" 157 | }, 158 | "atan2": { 159 | "prefix": "atan", 160 | "body": "ATAN[${1:value1}]/[${2:value2}]", 161 | "description": "Arc tangent (two parameters), ATN can also be used", 162 | "scope": "source.macro" 163 | }, 164 | "atan3": { 165 | "prefix": "atan", 166 | "body": "ATAN[${1:name1},${2:name2}]", 167 | "description": "Arc tangent (two parameters), ATN can also be used", 168 | "scope": "source.macro" 169 | }, 170 | "atn": { 171 | "prefix": "atn", 172 | "body": "ATN[${1:value}]", 173 | "description": "Arc tangent (one parameter)", 174 | "scope": "source.macro" 175 | }, 176 | "atn2": { 177 | "prefix": "atn", 178 | "body": "ATN[${1:value1}]/[${2:value2}]", 179 | "description": "Arc tangent (two parameters)", 180 | "scope": "source.macro" 181 | }, 182 | "atn3": { 183 | "prefix": "atn", 184 | "body": "ATN[${1:name1},${2:name2}]", 185 | "description": "Arc tangent (two parameters)", 186 | "scope": "source.macro" 187 | }, 188 | "sqrt": { 189 | "prefix": "sqrt", 190 | "body": "SQRT[${1:value}]", 191 | "description": "Square root, SQR can also be used.", 192 | "scope": "source.macro" 193 | }, 194 | "sqr": { 195 | "prefix": "sqr", 196 | "body": "SQR[${1:value}]", 197 | "description": "Square root", 198 | "scope": "source.macro" 199 | }, 200 | "abs": { 201 | "prefix": "abs", 202 | "body": "ABS[${1:value}]", 203 | "description": "Absolute value", 204 | "scope": "source.macro" 205 | }, 206 | "bin": { 207 | "prefix": "bin", 208 | "body": "BIN[${1:value}]", 209 | "description": "Conversion from BCD to binary function", 210 | "scope": "source.macro" 211 | }, 212 | "bcd": { 213 | "prefix": "bcd", 214 | "body": "BCD[${1:value}]", 215 | "description": "Conversion from binary to BCD function", 216 | "scope": "source.macro" 217 | }, 218 | "round": { 219 | "prefix": "round", 220 | "body": "ROUND[${1:value}]", 221 | "description": "Rounding off, RND can also be used", 222 | "scope": "source.macro" 223 | }, 224 | "rnd": { 225 | "prefix": "rnd", 226 | "body": "RND[${1:value}]", 227 | "description": "Rounding off", 228 | "scope": "source.macro" 229 | }, 230 | "fix": { 231 | "prefix": "fix", 232 | "body": "FIX[${1:value}]", 233 | "description": "Rounding down to an integer", 234 | "scope": "source.macro" 235 | }, 236 | "fup": { 237 | "prefix": "fup", 238 | "body": "FUP[${1:value}]", 239 | "description": "Rounding up to an integer", 240 | "scope": "source.macro" 241 | }, 242 | "ln": { 243 | "prefix": "ln", 244 | "body": "LN[${1:value}]", 245 | "description": "Natural logarithm", 246 | "scope": "source.macro" 247 | }, 248 | "exp": { 249 | "prefix": "exp", 250 | "body": "EXP[${1:name}]", 251 | "description": "Exponent using base e (2.718...)", 252 | "scope": "source.macro" 253 | }, 254 | "pow": { 255 | "prefix": "pow", 256 | "body": "POW[${1:base}, ${2:exponent}]", 257 | "description": "Power (#j to the #kth power)", 258 | "scope": "source.macro" 259 | }, 260 | "adp": { 261 | "prefix": "adp", 262 | "body": "ADP[${1:value}]", 263 | "description": "Addition of a decimal point", 264 | "scope": "source.macro" 265 | }, 266 | "prm": { 267 | "prefix": "prm", 268 | "body": "PRM[${1:value}]", 269 | "description": "Parameter reading (system common, path, or machine group parameter)", 270 | "scope": "source.macro" 271 | }, 272 | "prm2": { 273 | "prefix": "prm", 274 | "body": "PRM[${1:value1}, ${2:value2}]", 275 | "description": "Parameter reading (system common, path, or machine group parameter bit number specification)", 276 | "scope": "source.macro" 277 | }, 278 | "prm3": { 279 | "prefix": "prm", 280 | "body": "PRM[${1:value1}, ${2:value2}]/[${3:value3}]", 281 | "description": "Parameter reading (axis or spindle parameter bit number specification)", 282 | "scope": "source.macro" 283 | }, 284 | "parmQ": { 285 | "prefix": "q", 286 | "body": "Q[${1|TRUE,FALSE|}]", 287 | "description": "Q[TRUE]", 288 | "scope": "source.macro" 289 | }, 290 | "write": { 291 | "prefix": "write", 292 | "body": "WRITE ${1:address} Q[${2|TRUE,FALSE|}] L${3|1,2,4|}", 293 | "description": "Q[TRUE]", 294 | "scope": "source.macro" 295 | }, 296 | "read": { 297 | "prefix": "read", 298 | "body": "READ ${1:address} P[${2:variable}] L${3|1,2,4|}", 299 | "description": "Q[TRUE]", 300 | "scope": "source.macro" 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /syntaxes/macro.configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "/*" 4 | }, 5 | "brackets": [ 6 | ["[", "]"] 7 | ], 8 | "autoClosingPairs": [ 9 | ["[","]"], 10 | ["(",")"], 11 | ["<", ">"], 12 | ["'", "'"] 13 | ], 14 | "surroundingPairs": [ 15 | ["[", "]"], 16 | ["(", ")"], 17 | ["<", ">"], 18 | ["'", "'"] 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /syntaxes/macro.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Macro", 3 | "scopeName":"source.macro", 4 | "fileTypes":[ 5 | "src", 6 | "lnk", 7 | "def" 8 | ], 9 | "patterns": [ 10 | { 11 | "include": "#file" 12 | } 13 | ], 14 | "repository":{ 15 | "file":{ 16 | "begin":"\\A", 17 | "end":"(?=\\%)", 18 | "patterns": [ 19 | { 20 | "include": "#comments" 21 | }, 22 | { 23 | "include":"#string_statement" 24 | }, 25 | { 26 | "include": "#language_keywords" 27 | }, 28 | { 29 | "include":"#control_statement" 30 | }, 31 | { 32 | "include":"#include" 33 | }, 34 | { 35 | "include":"#var_def" 36 | }, 37 | { 38 | "include":"#label_def" 39 | }, 40 | { 41 | "include":"#sub" 42 | } 43 | ] 44 | }, 45 | "sub" : { 46 | "begin" : "(?i)(^\\s*O{1})(?:(\\s+\\w+)|(\\d+))", 47 | "name":"meta.function.macro", 48 | "beginCaptures" : { 49 | "1": { 50 | "name":"storage.type.function.macro" 51 | }, 52 | "2": { 53 | "name":"entity.name.function.macro" 54 | }, 55 | "3": { 56 | "name":"entity.name.function.macro" 57 | } 58 | }, 59 | "end":"(?=(?i)(^\\s*O{1})(?:(\\s+\\w+)|(\\d+)))|(?=\\%)", 60 | "patterns" : [ 61 | { 62 | "include":"#comments" 63 | }, 64 | { 65 | "include":"#string_statement" 66 | }, 67 | { 68 | "include": "#language_keywords" 69 | }, 70 | { 71 | "include": "#control_statement" 72 | }, 73 | { 74 | "include":"#var_def" 75 | }, 76 | { 77 | "include":"#label_def" 78 | }, 79 | { 80 | "include":"#functions_math" 81 | }, 82 | { 83 | "include":"#functions_command" 84 | }, 85 | { 86 | "include":"#conditionals_1" 87 | }, 88 | { 89 | "include":"#conditionals_2" 90 | }, 91 | { 92 | "include":"#keyword_operator" 93 | }, 94 | { 95 | "include":"#keyword_operator_arithmetic" 96 | }, 97 | { 98 | "include":"#keyword_operator_assignment" 99 | }, 100 | { 101 | "include":"#boolean" 102 | }, 103 | { 104 | "include":"#sequence" 105 | }, 106 | { 107 | "include":"#nnAddress" 108 | }, 109 | { 110 | "include":"#variable" 111 | }, 112 | { 113 | "include":"#number" 114 | }, 115 | { 116 | "include":"#assignment" 117 | }, 118 | { 119 | "include":"#nc_code" 120 | }, 121 | { 122 | "include":"#parameter" 123 | }, 124 | { 125 | "include":"#symbol" 126 | }, 127 | { 128 | "include":"#brackets" 129 | } 130 | ] 131 | }, 132 | "string_statement" : { 133 | "patterns" : [ 134 | { 135 | "include":"#string_a" 136 | }, 137 | { 138 | "include":"#string_b" 139 | }, 140 | { 141 | "include":"#string_c" 142 | }, 143 | { 144 | "include":"#string" 145 | } 146 | ] 147 | }, 148 | "control_statement" : { 149 | "patterns": [ 150 | { 151 | "name":"variable.language.macro", 152 | "match":"\\$NOLIST" 153 | }, 154 | { 155 | "name":"variable.language.macro", 156 | "match":"\\$LIST" 157 | }, 158 | { 159 | "name":"variable.language.macro", 160 | "match":"\\$EJECT" 161 | } 162 | ] 163 | }, 164 | "include":{ 165 | "begin" : "(?i)(?=\\$INCLUDE)", 166 | "end":"\\Z", 167 | "patterns":[ 168 | { 169 | "name":"variable.language.macro", 170 | "match":"(?i)\\$INCLUDE" 171 | }, 172 | { 173 | "match":"(?i).+\\.def", 174 | "name":"markup.link.macro" 175 | }, 176 | { 177 | "include":"#comments" 178 | } 179 | ] 180 | }, 181 | "language_keywords" : { 182 | "name":"variable.language.macro", 183 | "match":"%|#" 184 | }, 185 | "brackets":{ 186 | "name" : "meta.brace.square.macro", 187 | "match":"\\[|\\]" 188 | }, 189 | "boolean" : { 190 | "name":"constant.language.macro", 191 | "match":"(?i)(?<=\\W|\\d)(TRUE|FALSE)(?=\\d*\\W)" 192 | }, 193 | "conditionals_1" : { 194 | "name":"keyword.control.conditional.macro", 195 | "match":"(?i)(?<=^|\\W|\\d)(IF|ELSE|THEN|ENDIF|WHILE)(?=\\W)" 196 | }, 197 | "conditionals_2" : { 198 | "name":"keyword.control.conditional.macro", 199 | "match":"(?i)(?<=^|\\W|\\d)(END|DO|GOTO)(?![a-zA-Z])" 200 | }, 201 | "functions_math" : { 202 | "name":"support.function.math.macro", 203 | "match":"(?i)(?<=^|\\W|\\d)(SIN|COS|TAN|ASIN|ACOS|ATAN|ATN|SQRT|SQR|ABS|BIN|BCD|ROUND|RND|FIX|FUP|LN|EXP|POW|ADP|PRM)(?=\\s*\\[)" 204 | }, 205 | "functions_command" : { 206 | "name":"support.function.macro", 207 | "match":"(?i)(?<=^|\\W|\\d)(POPEN|PCLOS|DPRNT|BPRNT|SETVN|FGEN|FDEL|FOPEN|FCLOS|FPSET|FREAD|FWRIT)" 208 | }, 209 | "keyword_operator": { 210 | "match":"(?i)(?<=\\W|\\d|\\w)(EQ|NE|LT|LE|GE|GT|OR|XOR|AND|MOD|\\&\\&|\\|\\||\\/)(?-i)(?=\\d|\\W|[A-Z]\\d)", 211 | "name":"keyword.other.macro" 212 | }, 213 | "keyword_operator_arithmetic": { 214 | "match":"(\\+|\\-|\\/|\\*)", 215 | "name":"keyword.operator.arithmetic.macro" 216 | }, 217 | "keyword_operator_assignment": { 218 | "match":"=", 219 | "name":"keyword.operator.assignment.macro" 220 | }, 221 | "symbol" : { 222 | "name":"variable.macro", 223 | "match":"[0-9a-zA-Z_?!.\\$]+" 224 | }, 225 | "variable": { 226 | "name": "constant.numeric.macrovar.macro", 227 | "match": "(?<=#)[0-9]+" 228 | }, 229 | "sequence" : { 230 | "name":"punctuation.section.tag.macro", 231 | "match":"(?i)((?<=\\/)|(^\\s*))(\\s*N\\d+)" 232 | }, 233 | "nnAddress" : { 234 | "name":"keyword.other.macro", 235 | "match":"(?i)(\\s*NN)(?=\\s|#|\\d)" 236 | }, 237 | "number": { 238 | "name":"constant.numeric.macro", 239 | "match":"(\\d+)(\\.\\d+)*" 240 | }, 241 | "comments":{ 242 | "name":"comment.line.macro", 243 | "match":"((\\/\\*)|(\\;)).*\\n" 244 | }, 245 | "string":{ 246 | "name":"string.other.macro", 247 | "match":"(\\()(.*)(\\))" 248 | }, 249 | "string_a":{ 250 | "name":"string.quoted.single.macro", 251 | "match":"(\\(\\')(.*)(\\'\\))" 252 | }, 253 | "string_b":{ 254 | "name":"string.quoted.double.macro", 255 | "match":"(\\(\")(.*)(\"\\))" 256 | }, 257 | "string_c":{ 258 | "name":"string.quoted.other.macro", 259 | "match":"(\\(\\*)(.*)(\\*\\))" 260 | }, 261 | "block_skip":{ 262 | "name":"markup.italic.block_skip.macro", 263 | "match":"(^\\/)" 264 | }, 265 | "nc_code": { 266 | "name":"keyword.other.macro", 267 | "match":"(?i)([MG]\\d{1,4})(\\.\\d)?" 268 | }, 269 | "parameter": { 270 | "name":"storage.type.parameter.macro", 271 | "match":"(?i)(?)([0-9a-zA-Z_?!.\\$]+)", 315 | "beginCaptures":{ 316 | "1":{ 317 | "name":"punctuation.definition.keyword.label.macro" 318 | }, 319 | "2":{ 320 | "name":"entity.name.label.macro" 321 | } 322 | }, 323 | "end":"\\Z", 324 | "name":"meta.definition.label.macro", 325 | "patterns":[ 326 | { 327 | "include":"#number" 328 | }, 329 | { 330 | "include":"#comments" 331 | }, 332 | { 333 | "include":"#string_statement" 334 | } 335 | ] 336 | }, 337 | "var_def": { 338 | "begin":"(\\@)([0-9a-zA-Z_?!.\\$]+)", 339 | "beginCaptures":{ 340 | "1":{ 341 | "name":"punctuation.definition.keyword.variable.macro" 342 | }, 343 | "2":{ 344 | "name":"variable.other.symbol.macro" 345 | } 346 | }, 347 | "end":"\\Z", 348 | "name":"meta.definition.variable.macro", 349 | 350 | "patterns":[ 351 | { 352 | "name":"variable.language.macro", 353 | "match":"#" 354 | }, 355 | { 356 | "name":"variable.language.macro", 357 | "match":"&" 358 | }, 359 | { 360 | "include":"#comments" 361 | }, 362 | { 363 | "include":"#string_statement" 364 | }, 365 | { 366 | "include":"#functions_math" 367 | }, 368 | { 369 | "include":"#functions_command" 370 | }, 371 | { 372 | "include":"#conditionals_1" 373 | }, 374 | { 375 | "include":"#conditionals_2" 376 | }, 377 | { 378 | "include":"#keyword_operator" 379 | }, 380 | { 381 | "include":"#keyword_operator_arithmetic" 382 | }, 383 | { 384 | "include":"#keyword_operator_assignment" 385 | }, 386 | { 387 | "include":"#boolean" 388 | }, 389 | { 390 | "include":"#sequence" 391 | }, 392 | { 393 | "include":"#nnAddress" 394 | }, 395 | { 396 | "include":"#variable" 397 | }, 398 | { 399 | "include":"#number" 400 | }, 401 | { 402 | "include":"#assignment" 403 | }, 404 | { 405 | "include":"#nc_code" 406 | }, 407 | { 408 | "include":"#parameter" 409 | }, 410 | { 411 | "include":"#symbol" 412 | }, 413 | { 414 | "include":"#brackets" 415 | } 416 | ] 417 | } 418 | } 419 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "outDir": "out", 7 | "rootDir": "src", 8 | "sourceMap": true 9 | }, 10 | "include": [ 11 | "src" 12 | ], 13 | "exclude": [ 14 | "node_modules", 15 | ".vscode-test" 16 | ], 17 | "references": [ 18 | { "path": "./client" }, 19 | { "path": "./server" } 20 | ] 21 | } 22 | --------------------------------------------------------------------------------