From 49aada8a0e5cbb3a6e65b2f6151f9aa8213ccd72 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Fri, 6 Jun 2025 14:53:58 +0800 Subject: [PATCH 1/8] Update scripts and workflow --- .github/workflows/node.js.yml | 8 +++----- contributing.md | 24 +++++++++++------------- package.json | 30 ++++++++++++++---------------- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index d14ee86..76e64e2 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -29,12 +29,10 @@ jobs: - run: npm ci # For some reason, antlr4ng writes to a different location on the VM # than it does locally, preventing compile. Command added to move the generated files. - - run: npm run textMate - - name: npm run antlr + - name: npm run generate run: | - npm run antlr4ng - npm run antlr4ngPre - npm run antlr4ngFmt + npm run antlr + npm run build:textMate mv ./server/src/antlr/out/server/src/antlr/* ./server/src/antlr/out - run: npm run build - run: xvfb-run -a npm test diff --git a/contributing.md b/contributing.md index 9ab0689..a418c07 100644 --- a/contributing.md +++ b/contributing.md @@ -1,21 +1,19 @@ # Contributing -**Signed commits are turned on for this repo.** +This repository is under active development. Please ensure your contributions do not cause merge conflicts with the dev branch. ## Setup 1. Fork this repo -1. Install VS Code Extension [esBuild Problem Matchers](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers). -2. Install [Java](https://www.oracle.com/au/java/technologies/downloads/) >= 11 -3. Install [NPM](https://github.com/coreybutler/nvm-windows) -4. `npm install` to install dependencies. -5. `npm run textMate` first time and every time grammar is changed. -6. `npm run antlr` first time and every time grammar is changed. +2. Install VS Code Extension [esBuild Problem Matchers](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers). +3. Install [Java](https://www.oracle.com/au/java/technologies/downloads/) >= 11 +4. Install [NPM](https://github.com/coreybutler/nvm-windows) +5. `npm install` to install dependencies. +6. `npm run test` to build and unit test. 7. Create a `.\sample` directory as a default workspace for client debugging (or update .\\.vscode\\launch.json as preferred). -7. (Optional) Install [ANTLR4 grammar syntax support](https://marketplace.visualstudio.com/items?itemName=mike-lischke.vscode-antlr4) VS Code extension. +8. (Optional) Install [ANTLR4 grammar syntax support](https://marketplace.visualstudio.com/items?itemName=mike-lischke.vscode-antlr4) VS Code extension. Note: to debug a grammar, you'll first need to activate the extension by opening one of the *.g4 files. To contribute, you'll need to [create a pull request from a fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). -**Signed commits are turned on for this repo.** ## Debugging Language Server @@ -41,10 +39,10 @@ Note the grammar file for this project can be found at /server/src/antlr/vba.g4 The grammar call graph is interesting, but not particularly useful. Generating "valid" input generates garbage. -### Run unit tests for TextMate grammar +## TextMate Snapshots -To verify that your changes haven't broken existing syntax or tests, and to ensure any new tests pass, run the following command: +Your edits to the TextMate grammar may cause changes to the snapshot. If these changes are expected and unit tested, update the snapshot. ``` -npm run tmUnitTest -``` \ No newline at end of file +npm run tmSnapUpdate +``` diff --git a/package.json b/package.json index 3d929c6..efadc8a 100644 --- a/package.json +++ b/package.json @@ -144,28 +144,26 @@ }, "scripts": { "vscode:prepublish": "npm run package", - "build": "npm run check-types && node esbuild.js", - "fullBuild": "npm run textMate && npm run antlr", - "build-test": "node esbuild.js --test", + "build": "npm-run-all -p build:* && npm run check-types && node esbuild.js", + "package": "npm-run-all -p build:* && npm run check-types && node esbuild.js --production", "check-types": "tsc --noEmit", "watch": "npm-run-all -p watch:*", "watch:esbuild": "node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", - "package": "npm run textMate && npm run antlr && npm run check-types && node esbuild.js --production", "lint": "eslint ./client/src ./server/src --ext .ts,.tsx", "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", - "textMate": "npx js-yaml client/syntaxes/vba.tmLanguage.yaml > client/syntaxes/vba.tmLanguage.json && npm run tmSnapTest", - "antlr": "npm run antlr4ngPre && npm run antlr4ng && npm run antlr4ngFmt && npm run build", - "antlr4ng": "antlr4ng -Dlanguage=TypeScript -visitor ./server/src/antlr/vba.g4 -o ./server/src/antlr/out/", - "antlr4ngPre": "antlr4ng -Dlanguage=TypeScript -visitor ./server/src/antlr/vbapre.g4 -o ./server/src/antlr/out/", - "antlr4ngFmt": "antlr4ng -Dlanguage=TypeScript -visitor ./server/src/antlr/vbafmt.g4 -o ./server/src/antlr/out/", - "test": "npm run tmSnapTest && npm run tmUnitTest && npm run vsctest", - "testsh": "sh ./scripts/e2e.sh", - "testps": "powershell ./scripts/e2e.ps1", - "tmUnitTest": "vscode-tmgrammar-test ./test/textmate/**/*.vba", - "tmSnapTest": "vscode-tmgrammar-snap ./test/textmate/snapshot/*.??s", + "build:textMate": "npx js-yaml client/syntaxes/vba.tmLanguage.yaml > client/syntaxes/vba.tmLanguage.json", + "antlr": "npm-run-all -p build:antlr*", + "build:antlr": "antlr4ng -Dlanguage=TypeScript -visitor ./server/src/antlr/vba.g4 -o ./server/src/antlr/out/", + "build:antlrPre": "antlr4ng -Dlanguage=TypeScript -visitor ./server/src/antlr/vbapre.g4 -o ./server/src/antlr/out/", + "build:antlrFmt": "antlr4ng -Dlanguage=TypeScript -visitor ./server/src/antlr/vbafmt.g4 -o ./server/src/antlr/out/", "tmSnapUpdate": "vscode-tmgrammar-snap --updateSnapshot ./test/textmate/snapshot/*.??s", - "vsctest": "npm run build-test && vscode-test" + "test": "npm run build && npm-run-all -p test:*:*", + "test:textMate:unit": "vscode-tmgrammar-test ./test/textmate/**/*.vba", + "test:textMate:snap": "vscode-tmgrammar-snap ./test/textmate/snapshot/*.??s", + "test:vsc:unit": "vscode-test", + "testsh": "sh ./scripts/e2e.sh", + "testps": "powershell ./scripts/e2e.ps1" }, "dependencies": { "antlr4ng": "^3.0.16", @@ -189,4 +187,4 @@ "typescript": "^5.8.3", "vscode-tmgrammar-test": "^0.1.3" } -} +} \ No newline at end of file From cf9204b80d3200aae49e8ec6ea3c174795516307 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Wed, 11 Jun 2025 16:45:16 +0800 Subject: [PATCH 2/8] Fixed 'else if' invalid syntax --- server/src/antlr/vbapre.g4 | 217 ++++++++++++++++++++++++++++++++++++- 1 file changed, 212 insertions(+), 5 deletions(-) diff --git a/server/src/antlr/vbapre.g4 b/server/src/antlr/vbapre.g4 index 760b037..3461200 100644 --- a/server/src/antlr/vbapre.g4 +++ b/server/src/antlr/vbapre.g4 @@ -11,13 +11,80 @@ startRule ; document - : (documentLines | compilerIfBlock)* + : (documentLines | compilerIfBlock | constDirectiveStatement)* ; documentLines : (anyOtherLine | endOfLine)+ ; +constDirectiveName + : ANYCHARS + ; + +directiveParenthesizedExpression + : '(' WS? directiveExpression WS? ')' + ; + +directiveUnaryMinusExpression + : '-' WS? directiveExpression + ; + +directiveLiteralExpression + : DATELITERAL + | FLOATLITERAL + | INTEGERLITERAL + | STRINGLITERAL + | literalIdentifier + ; + +literalIdentifier + : booleanLiteralIdentifier + | objectLiteralIdentifier + | variantLiteralIdentifier + ; + +booleanLiteralIdentifier + : TRUE + | FALSE + ; + +objectLiteralIdentifier + : NOTHING + ; + +variantLiteralIdentifier + : EMPTY_X + | NULL_ + ; + +directiveExpression + : directiveLiteralExpression + | directiveParenthesizedExpression + | directiveUnaryMinusExpression + // | directiveExpression wsc? (divOperator | multOperator) wsc? directiveExpression + // | directiveExpression wsc? modOperator wsc? directiveExpression + // | directiveExpression wsc? (plusOperator | minusOperator) wsc? directiveExpression + // | directiveExpression wsc? ampOperator wsc? directiveExpression + // | directiveExpression wsc? ( + // IS + // | LIKE + // | geqOperator + // | leqOperator + // | gtOperator + // | ltOperator + // | neqOperator + // | eqOperator + // ) wsc? directiveExpression + // | notOperatorExpression + // | directiveExpression wsc? (andOperator | orOperator | xorOperator | eqvOperator | impOperator) wsc? directiveExpression + // | lExpression + ; + +constDirectiveStatement + : CONST WS constDirectiveName WS? EQ WS? directiveExpression endOfStatement + ; + compilerIfBlock : compilerConditionalBlock+ compilerDefaultBlock? @@ -76,9 +143,18 @@ compilerConstant | MAC ; -anyWord - : ANYCHARS+ - ; +anyWord: ( + ANYCHARS + | EQ + | STRINGLITERAL + | FLOATLITERAL + | DATELITERAL + | TRUE + | FALSE + | NOTHING + | EMPTY_X + | NULL_ + )+; anyOtherLine : (WS* anyWord)+ @@ -112,12 +188,20 @@ NEWLINE : ([\r\n\u2028\u2029]) (WS* ([\r\n\u2028\u2029]))* ; +AS + : 'AS' + ; + +CONST + : '#CONST' + ; + ELSE : '#ELSE' ; ELSEIF - : '#ELSE IF' + : '#ELSEIF' ; ENDIF @@ -188,6 +272,10 @@ SINGLEQUOTE : '\'' ; +EQ + : '=' + ; + REM : 'REM' ; @@ -216,6 +304,121 @@ NOT : 'NOT' ; +NOTHING + : 'NOTHING' + ; + +NULL_ + : 'NULL' + ; + +TRUE + : 'TRUE' + ; + +FALSE + : 'FALSE' + ; + +EMPTY_X + : 'EMPTY' + ; + +STRINGLITERAL + : '"' (~["\r\n] | '""')* '"' + ; + +INTEGERLITERAL + : (DIGIT DIGIT* | '&H' [0-9A-F]+ | '&' [O]? [0-7]+) [%&^]? + ; + +FLOATLITERAL + : FLOATINGPOINTLITERAL [!#@]? + | DECIMALLITERAL [!#@] + ; + +fragment FLOATINGPOINTLITERAL + : DECIMALLITERAL [DE] [+-]? DECIMALLITERAL + | DECIMALLITERAL '.' DECIMALLITERAL? ([DE] [+-]? DECIMALLITERAL)? + | '.' DECIMALLITERAL ([DE] [+-]? DECIMALLITERAL)? + ; + +fragment DECIMALLITERAL + : DIGIT DIGIT* + ; + +DATELITERAL + : '#' DATEORTIME '#' + ; + +fragment DATEORTIME + : DATEVALUE WS+ TIMEVALUE + | DATEVALUE + | TIMEVALUE + ; + +fragment DATEVALUE + : DATEVALUEPART DATESEPARATOR DATEVALUEPART (DATESEPARATOR DATEVALUEPART)? + ; + +fragment DATEVALUEPART + : DIGIT+ + | MONTHNAME + ; + +fragment DATESEPARATOR + : WS+ + | WS? [/,-] WS? + ; + +fragment MONTHNAME + : ENGLISHMONTHNAME + | ENGLISHMONTHABBREVIATION + ; + +fragment ENGLISHMONTHNAME + : 'JANUARY' + | 'FEBRUARY' + | 'MARCH' + | 'APRIL' + | 'MAY' + | 'JUNE' + | 'JULY' + | 'AUGUST' + | 'SEPTEMBER' + | 'OCTOBER' + | 'NOVEMBER' + | 'DECEMBER' + ; + +// May has intentionally been left out +fragment ENGLISHMONTHABBREVIATION + : 'JAN' + | 'FEB' + | 'MAR' + | 'APR' + | 'JUN' + | 'JUL' + | 'AUG' + | 'SEP' + | 'OCT' + | 'NOV' + | 'DEC' + ; + +fragment TIMEVALUE + : DIGIT+ AMPM + | DIGIT+ TIMESEPARATOR DIGIT+ (TIMESEPARATOR DIGIT+)? AMPM? + ; + +fragment TIMESEPARATOR + : WS? (':' | '.') WS? + ; + +fragment AMPM + : WS? ('AM' | 'PM' | 'A' | 'P') + ; + // Any non-whitespace or new line characters. ANYCHARS @@ -224,4 +427,8 @@ ANYCHARS fragment ANYCHAR : ~[\r\n\u2028\u2029 \t\u0019\u3000] + ; + +fragment DIGIT + : [0-9] ; \ No newline at end of file From 45f6f6a7a85002438e32dd76c4f170f28e5f989d Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Wed, 11 Jun 2025 16:47:48 +0800 Subject: [PATCH 3/8] Fixes #87 --- .../extensions/antlrVbaPreParserExtensions.ts | 10 +- server/src/project/elements/precompiled.ts | 117 ++++++++++++------ server/src/project/parser/vbaListener.ts | 26 +++- 3 files changed, 112 insertions(+), 41 deletions(-) diff --git a/server/src/extensions/antlrVbaPreParserExtensions.ts b/server/src/extensions/antlrVbaPreParserExtensions.ts index 992afbb..aa2b513 100644 --- a/server/src/extensions/antlrVbaPreParserExtensions.ts +++ b/server/src/extensions/antlrVbaPreParserExtensions.ts @@ -1,11 +1,15 @@ // Project -import { CompilerConditionalStatementContext } from '../antlr/out/vbapreParser'; +import { CompilerConditionalStatementContext, DirectiveExpressionContext } from '../antlr/out/vbapreParser'; declare module '../antlr/out/vbapreParser' { interface CompilerConditionalStatementContext { vbaExpression(): string; } + + interface DirectiveExpressionContext { + vbaExpression(): string; + } } @@ -15,3 +19,7 @@ CompilerConditionalStatementContext.prototype.vbaExpression = function (): strin .getText() .toLowerCase(); }; + +DirectiveExpressionContext.prototype.vbaExpression = function (): string { + return this.getText().toLowerCase(); +}; \ No newline at end of file diff --git a/server/src/project/elements/precompiled.ts b/server/src/project/elements/precompiled.ts index 04a64d6..168f63a 100644 --- a/server/src/project/elements/precompiled.ts +++ b/server/src/project/elements/precompiled.ts @@ -2,19 +2,52 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; // Antlr -import { CompilerConditionalBlockContext, CompilerDefaultBlockContext, CompilerIfBlockContext } from '../../antlr/out/vbapreParser'; +import { CompilerConditionalBlockContext, CompilerDefaultBlockContext, CompilerIfBlockContext, ConstDirectiveStatementContext } from '../../antlr/out/vbapreParser'; // Project -import { DiagnosticCapability, FoldingRangeCapability } from '../../capabilities/capabilities'; +import { DiagnosticCapability, FoldingRangeCapability, IdentifierCapability } from '../../capabilities/capabilities'; import { BaseRuleSyntaxElement } from '../elements/base'; import { UnreachableCodeDiagnostic } from '../../capabilities/diagnostics'; +type DocumentSettings = { environment: { os: string, version: string } }; + +export class CompilerDirectiveElement extends BaseRuleSyntaxElement { + identifierCapability: IdentifierCapability; + + constructor(ctx: ConstDirectiveStatementContext, + doc: TextDocument, + private readonly documentSettings: DocumentSettings, + private readonly directiveConstants: Map) { + super(ctx, doc); + + const getNameCtx = () => ctx.constDirectiveName(); + this.identifierCapability = new IdentifierCapability(this, getNameCtx); + } + + evaluate(): string { + const vbaExpression = this.context.rule.directiveExpression().vbaExpression(); + try { + const tsExpression = transpileVbaToTypescript(vbaExpression, this.documentSettings, this.directiveConstants); + const getExpressionResult = Function('"use strict"; return (' + tsExpression + ')'); + return getExpressionResult().toString(); + } catch (e) { + // FIXME Add a diagnostic for if this fails. + return '0'; + } + } +} + + export class CompilerLogicalBlock extends BaseRuleSyntaxElement { conditionalBlocks: CompilerConditionBlock[] = []; inactiveBlocks: CompilerConditionBlock[] = []; - constructor(ctx: CompilerIfBlockContext, doc: TextDocument, env: { environment: { os: string, version: string } }) { + constructor( + ctx: CompilerIfBlockContext, + doc: TextDocument, + documentSettings: DocumentSettings, + directiveConstants: Map) { super(ctx, doc); this.foldingRangeCapability = new FoldingRangeCapability(this); this.foldingRangeCapability.openWord = '#If'; @@ -22,7 +55,11 @@ export class CompilerLogicalBlock extends BaseRuleSyntaxElement { if (x) this.conditionalBlocks.push(new CompilerConditionBlock(x, doc, env)); }); + blocks.map(x => { + if (x) this.conditionalBlocks.push( + new CompilerConditionBlock(x, doc, documentSettings, directiveConstants) + ); + }); // Create the comment elements. let resolved = false; @@ -39,11 +76,12 @@ export class CompilerLogicalBlock extends BaseRuleSyntaxElement { - readonly documentSettings: { environment: { os: string, version: string } }; - - constructor(ctx: CompilerConditionalBlockContext | CompilerDefaultBlockContext, doc: TextDocument, env: { environment: { os: string, version: string } }) { + constructor( + ctx: CompilerConditionalBlockContext | CompilerDefaultBlockContext, + doc: TextDocument, + private readonly documentSettings: DocumentSettings, + private readonly directiveConstants: Map) { super(ctx, doc); - this.documentSettings = env; } get blockLines(): string[] { @@ -56,7 +94,7 @@ class CompilerConditionBlock extends BaseRuleSyntaxElement 'compilerElseStatement' in o)(ctx)) return true; const vbaExpression = ctx.compilerConditionalStatement().vbaExpression(); - const tsExpression = this.transpileVbaToTypescript(vbaExpression); + const tsExpression = transpileVbaToTypescript(vbaExpression, this.documentSettings, this.directiveConstants); // Evaluate the expression and return the result. const result: boolean = Function('"use strict"; return (' + tsExpression + ')')(); @@ -72,32 +110,39 @@ class CompilerConditionBlock extends BaseRuleSyntaxElement { - const isOs = this.documentSettings.environment.os.toLowerCase() == opt; - const isVer = this.documentSettings.environment.version.toLowerCase() == opt; - return isOs || isVer ? 'true' : 'false'; - }; - - // Set up text replacements map. - const constants = ['vba6', 'vba7', 'mac', 'win16', 'win32', 'win64']; - const replacements = new Map(constants.map(x => [x, envToBooleanText(x)])); - replacements.set('or', '||'); - replacements.set('and', '&&'); - replacements.set('not ', '!'); - - // Perform text replacements. - let result = exp; - replacements.forEach((v, k) => { - const regexp = RegExp(`${k}`, 'i'); - if (regexp.test(result)) { - result = result.replace(regexp, v); - } - }); +function transpileVbaToTypescript(exp: string, settings: DocumentSettings, directives: Map): string { + // Convert the environment constant to boolean. + const envToBooleanText = (opt: string) => { + const isOs = settings.environment.os.toLowerCase() == opt; + const isVer = settings.environment.version.toLowerCase() == opt; + return isOs || isVer ? 'true' : 'false'; + }; + + // Set up text replacements map. + const constants = ['vba6', 'vba7', 'mac', 'win16', 'win32', 'win64']; + const replacements = new Map(constants.map(x => [x, envToBooleanText(x)])); + replacements.set('or', '||'); + replacements.set('and', '&&'); + replacements.set('not ', '!'); + + // Perform language text replacements. + let result = exp; + replacements.forEach((v, k) => { + const regexp = RegExp(`${k}`, 'i'); + if (regexp.test(result)) { + result = result.replace(regexp, v); + } + }); - return result; - } -} + // Perform user directives text replacements. + directives.forEach((v, k) => { + const regexp = RegExp(`${k}`, 'i'); + if (regexp.test(result)) { + result = result.replace(regexp, v); + } + }); + + return result; +} \ No newline at end of file diff --git a/server/src/project/parser/vbaListener.ts b/server/src/project/parser/vbaListener.ts index 479e4a4..c957d09 100644 --- a/server/src/project/parser/vbaListener.ts +++ b/server/src/project/parser/vbaListener.ts @@ -6,7 +6,7 @@ import { ErrorNode, ParserRuleContext } from 'antlr4ng'; import { vbaListener } from '../../antlr/out/vbaListener'; import { vbapreListener } from '../../antlr/out/vbapreListener'; import { vbafmtListener } from '../../antlr/out/vbafmtListener'; -import { CompilerIfBlockContext } from '../../antlr/out/vbapreParser'; +import { CompilerIfBlockContext, ConstDirectiveStatementContext} from '../../antlr/out/vbapreParser'; import { AmbiguousIdentifierContext, AnyOperatorContext, @@ -67,7 +67,7 @@ import { import { Services } from '../../injection/services'; import { ExtensionConfiguration } from '../workspace'; import { ErrorRuleElement } from '../elements/generic'; -import { CompilerLogicalBlock } from '../elements/precompiled'; +import { CompilerDirectiveElement, CompilerLogicalBlock } from '../elements/precompiled'; import { UnexpectedEndOfLineElement } from '../elements/utils'; import { VbaClassDocument, VbaModuleDocument } from '../document'; import { DuplicateOperatorElement, IfElseBlockElement, WhileLoopElement } from '../elements/flow'; @@ -489,6 +489,7 @@ export class VbaListener extends vbaListener { export class VbaPreListener extends vbapreListener { common: CommonParserCapability; + directives: Map = new Map(); get text(): string { return this.common.document.redactedText; @@ -505,10 +506,27 @@ export class VbaPreListener extends vbapreListener { return result; } + enterConstDirectiveStatement = (ctx: ConstDirectiveStatementContext) => { + const doc = this.common.document; + const element = new CompilerDirectiveElement( + ctx, + doc.textDocument, + this.common.documentSettings, + this.directives + ); + this.directives.set(element.identifierCapability.name, element.evaluate()); + doc.registerElement(element) + .registerSubtractElement(element); + }; + enterCompilerIfBlock = (ctx: CompilerIfBlockContext) => { const doc = this.common.document; - const docprops = this.common.documentSettings; - const element = new CompilerLogicalBlock(ctx, doc.textDocument, docprops); + const element = new CompilerLogicalBlock( + ctx, + doc.textDocument, + this.common.documentSettings, + this.directives + ); element.inactiveBlocks.forEach( b => doc.registerElement(b) .registerSubtractElement(b) From f46dd04cb60e75db7706240a46beaf73268794a3 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Wed, 11 Jun 2025 21:13:16 +0800 Subject: [PATCH 4/8] Current version now logged when document opened --- server/src/project/workspace.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/project/workspace.ts b/server/src/project/workspace.ts index 45fe2fe..35bab38 100644 --- a/server/src/project/workspace.ts +++ b/server/src/project/workspace.ts @@ -191,9 +191,10 @@ export class Workspace implements IWorkspace { openDocument(document: TextDocument): void { const normalisedUri = document.uri.toFilePath().toFileUri(); const projectDocument = this.projectDocuments.get(normalisedUri); + Services.logger.debug(`existing: ${projectDocument?.version ?? 'None'}`, 1); if (projectDocument) { projectDocument.open(); - if (document.version > projectDocument?.version) { + if (document.version > projectDocument.version) { this.parseDocument(projectDocument); } this.connection.sendDiagnostics(projectDocument.languageServerDiagnostics()); From 3edb32915f98adb6356b392b15695653e30d7b78 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Wed, 11 Jun 2025 21:13:56 +0800 Subject: [PATCH 5/8] Fixed bug causing implicit scopes to replicate --- server/src/capabilities/capabilities.ts | 10 +++++++--- server/src/project/document.ts | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/server/src/capabilities/capabilities.ts b/server/src/capabilities/capabilities.ts index dea7f41..238a3b5 100644 --- a/server/src/capabilities/capabilities.ts +++ b/server/src/capabilities/capabilities.ts @@ -281,12 +281,16 @@ export class ScopeItemCapability { public parent?: ScopeItemCapability, ) { } + clean(): void { + this.deleteInvalidatedScopes(); + this.cleanInvalidatedLinks(); + } + /** * Recursively build from this node down. */ build(): void { - this.deleteInvalidatedScopes(); - this.cleanInvalidatedLinks(); + this.clean(); // Don't build self if invalidated. if (this.isInvalidated) { @@ -800,7 +804,7 @@ export class ScopeItemCapability { } // Add implicitly accessible names to the project scope. - if (item.isPublicScope && this.project) { + if (item.isPublicScope && this.project && this !== this.project) { this.project.implicitDeclarations ??= new Map(); this.addItem(this.project.implicitDeclarations, item, item.name); } diff --git a/server/src/project/document.ts b/server/src/project/document.ts index fb6a941..896c62c 100644 --- a/server/src/project/document.ts +++ b/server/src/project/document.ts @@ -167,6 +167,7 @@ export abstract class BaseProjectDocument { await (new SyntaxParser(Services.logger)).parse(token, this); const projectScope = this.currentScope.project; const buildScope = projectScope?.isDirty ? projectScope : this.currentScope; + projectScope?.clean(); buildScope.build(); buildScope.resolveUnused(); From 31454b27d6b3d3d855fb3fd24f7541408bd2f25f Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:44:23 +0800 Subject: [PATCH 6/8] Bug fix and flesh out precompile grammar --- server/src/antlr/vbapre.g4 | 121 +++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 47 deletions(-) diff --git a/server/src/antlr/vbapre.g4 b/server/src/antlr/vbapre.g4 index 3461200..60e92a3 100644 --- a/server/src/antlr/vbapre.g4 +++ b/server/src/antlr/vbapre.g4 @@ -26,15 +26,11 @@ directiveParenthesizedExpression : '(' WS? directiveExpression WS? ')' ; -directiveUnaryMinusExpression - : '-' WS? directiveExpression - ; - directiveLiteralExpression - : DATELITERAL - | FLOATLITERAL - | INTEGERLITERAL - | STRINGLITERAL + : LITDATE + | LITFLOAT + | LITINTEGER + | LITSTRING | literalIdentifier ; @@ -58,29 +54,31 @@ variantLiteralIdentifier | NULL_ ; +// Operators +orderOfOps1: DIVD | MULT; +orderOfOps2: MOD; +orderOfOps3: PLUS | SUBT; +orderOfOps4: AMP; +orderOfOps5: LIKE | (LT | GT)? (LT | GT | EQ) | EQ; +orderOfOps6: AND | OR | XOR | EQV | IMP; + + directiveExpression : directiveLiteralExpression | directiveParenthesizedExpression - | directiveUnaryMinusExpression - // | directiveExpression wsc? (divOperator | multOperator) wsc? directiveExpression - // | directiveExpression wsc? modOperator wsc? directiveExpression - // | directiveExpression wsc? (plusOperator | minusOperator) wsc? directiveExpression - // | directiveExpression wsc? ampOperator wsc? directiveExpression - // | directiveExpression wsc? ( - // IS - // | LIKE - // | geqOperator - // | leqOperator - // | gtOperator - // | ltOperator - // | neqOperator - // | eqOperator - // ) wsc? directiveExpression - // | notOperatorExpression - // | directiveExpression wsc? (andOperator | orOperator | xorOperator | eqvOperator | impOperator) wsc? directiveExpression - // | lExpression + | directiveExpression WS? orderOfOps1 WS? directiveExpression + | directiveExpression WS? orderOfOps2 WS? directiveExpression + | directiveExpression WS? orderOfOps3 WS? directiveExpression + | directiveExpression WS? orderOfOps4 WS? directiveExpression + | directiveExpression WS? orderOfOps5 WS? directiveExpression + | notDirectiveExpression + | directiveExpression WS? orderOfOps6 WS? directiveExpression + | unreservedWord ; +notDirectiveExpression + : NOT WS directiveExpression; + constDirectiveStatement : CONST WS constDirectiveName WS? EQ WS? directiveExpression endOfStatement ; @@ -131,7 +129,7 @@ booleanExpression ; booleanPart - : WS? (AND | OR | XOR | EQV | IMP)? WS? NOT? WS? (compilerConstant | anyWord) + : WS? (AND | OR | XOR | EQV | IMP)? WS? NOT? WS? (compilerConstant | unreservedWord) ; compilerConstant @@ -143,18 +141,34 @@ compilerConstant | MAC ; -anyWord: ( - ANYCHARS - | EQ - | STRINGLITERAL - | FLOATLITERAL - | DATELITERAL - | TRUE - | FALSE - | NOTHING - | EMPTY_X - | NULL_ - )+; +reservedWord + : AMP + | AND + | AS + | DIVD + | EMPTY_X + | EQ + | MOD + | MULT + | PLUS + | SUBT + | THEN + | compilerConstant + ; + +unreservedWord + : ANYCHARS + | FALSE + | LITDATE + | LITINTEGER + | LITSTRING + | LITFLOAT + | NOTHING + | NULL_ + | TRUE + ; + +anyWord: ( unreservedWord | reservedWord)+; anyOtherLine : (WS* anyWord)+ @@ -175,7 +189,7 @@ endOfStatement commentBody: COMMENT; remStatement: REMCOMMENT; -// wsc: (WS | LINE_CONTINUATION)+; +// WS: (WS | LINE_CONTINUATION)+; @@ -240,6 +254,19 @@ MAC : 'MAC' ; +MULT: '*'; +DIVD: INTDIV | DBLDIV; +MOD: 'MOD'; +PLUS: '+'; +SUBT: '-'; +AMP: '&'; +LIKE: 'LIKE'; +LT: '<'; +GT: '>'; + +fragment INTDIV: '\\'; +fragment DBLDIV: '/'; + REMCOMMENT : COLON? REM WS (LINE_CONTINUATION | ~[\r\n\u2028\u2029])* ; @@ -324,17 +351,17 @@ EMPTY_X : 'EMPTY' ; -STRINGLITERAL +LITSTRING : '"' (~["\r\n] | '""')* '"' ; -INTEGERLITERAL - : (DIGIT DIGIT* | '&H' [0-9A-F]+ | '&' [O]? [0-7]+) [%&^]? +LITINTEGER + : '-'? (DIGIT DIGIT* | '&H' [0-9A-F]+ | '&' [O]? [0-7]+) [%&^]? ; -FLOATLITERAL - : FLOATINGPOINTLITERAL [!#@]? - | DECIMALLITERAL [!#@] +LITFLOAT + : '-'? FLOATINGPOINTLITERAL [!#@]? + | '-'? DECIMALLITERAL [!#@] ; fragment FLOATINGPOINTLITERAL @@ -347,7 +374,7 @@ fragment DECIMALLITERAL : DIGIT DIGIT* ; -DATELITERAL +LITDATE : '#' DATEORTIME '#' ; From ecd3b9aea16e245a8912d6877f74db6e8a2048e0 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:44:42 +0800 Subject: [PATCH 7/8] Fixed bug causing partial matches to trigger replacements --- server/src/project/elements/precompiled.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/project/elements/precompiled.ts b/server/src/project/elements/precompiled.ts index 168f63a..7518d74 100644 --- a/server/src/project/elements/precompiled.ts +++ b/server/src/project/elements/precompiled.ts @@ -130,7 +130,7 @@ function transpileVbaToTypescript(exp: string, settings: DocumentSettings, direc // Perform language text replacements. let result = exp; replacements.forEach((v, k) => { - const regexp = RegExp(`${k}`, 'i'); + const regexp = RegExp(`\\b${k}\\b`, 'i'); if (regexp.test(result)) { result = result.replace(regexp, v); } @@ -138,7 +138,7 @@ function transpileVbaToTypescript(exp: string, settings: DocumentSettings, direc // Perform user directives text replacements. directives.forEach((v, k) => { - const regexp = RegExp(`${k}`, 'i'); + const regexp = RegExp(`\\b${k}\\b`, 'i'); if (regexp.test(result)) { result = result.replace(regexp, v); } From c042a3853ee6052f06a4fb263ab2ae3b7b2315df Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:55:49 +0800 Subject: [PATCH 8/8] Restricted deactivation to block content --- server/src/project/elements/precompiled.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/server/src/project/elements/precompiled.ts b/server/src/project/elements/precompiled.ts index 7518d74..1a7e3e0 100644 --- a/server/src/project/elements/precompiled.ts +++ b/server/src/project/elements/precompiled.ts @@ -68,7 +68,7 @@ export class CompilerLogicalBlock extends BaseRuleSyntaxElement